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

511281个月前尚未审核

最受欢迎

查看全部 →

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

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享

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

## 探索

让我们从检查 `_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> }

该指针指向 `_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.

![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>

仅当 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 处理宽字符流机制的一部分。

下载工具