Skip to content
KitploitKITPLOIT
ToolsExploitsBlog
Log in
Submit
ToolsExploitsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

FeedsContactPrivacy© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
house_of_apple_2 — Interactive GDB walkthrough of the House of Apple 2 FSOP technique on glibc 2.43, with a reproducible sandbox covering vtable bypass, stack pivoting, and ROP. | Kitploit
Tools/GitHubGitHub/jazho76/house_of_apple_2
Memory ForensicsExploitationReverse EngineeringShellcodeDebuggersPapers & ResearchLearning & EducationPayload DevelopmentBinary ExploitationLabs & Practice
GitHubjazho76/house_of_apple_2
511281 month agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

house_of_apple_2

Interactive GDB walkthrough of the House of Apple 2 FSOP technique on glibc 2.43, with a reproducible sandbox covering vtable bypass, stack pivoting, and ROP.

View Repository

Exploring House of Apple 2 on modern glibc

This repository is a self-contained playground inspired by pwn.college's File Struct Exploitation module. It does not introduce a new variation of House of Apple 2, it just answers my curiosity about how the technique holds up on recent versions of glibc and whether it remains a viable exploitation path. The document provides an interactive GDB walkthrough that readers can follow alongside the sandbox to develop a more intuitive understanding of the primitive. All experiments use glibc 2.43, as packaged by Ubuntu 26.04 and Fedora 44 at the time of writing.

File Stream Oriented Programming (FSOP). This is about manipulating glibc file stream structures to hijack control flow. One way to do this is by corrupting the vtable dispatch mechanism of _IO_FILE_plus. Modern glibc validates this vtable, so the obvious approach of replacing it with an arbitrary address doesn't work.

House of Apple 2, originally introduced by Roderick, works around this restriction by using a valid _IO_FILE_plus vtable to reach the wide-character stream machinery, where a secondary vtable is directly dispatched without range validation. This provides an arbitrary call primitive that we can escalate into a stack pivot and a ROP chain.

Exploitation prerequisites

This exploration assumes that we can overwrite a FILE structure and that we have both a heap leak and a libc leak. The target binary already provides this.

Sandbox environment

The sandbox runs Ubuntu 26.04 LTS, giving us a modern environment to explore the technique.

The image includes GDB, pwndbg, pwntools, ropper and tmux. It also contains a target binary with an interactive menu for invoking file stream operations such as fopen, fread, fwrite, and fclose. This gives us a convenient way to manipulate streams while debugging and testing ideas.

Build and run the sandbox with:

./build.sh
./run.sh

Exploration

Let's start by inspecting the _IO_FILE and _IO_FILE_plus structures:

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;
}

In practical terms, _IO_FILE_plus is an _IO_FILE with a vtable pointer. That immediately looks interesting: if we can control this pointer, we may be able to redirect an indirect call and hijack control flow.

Inspecting the file stream vtable

To inspect the vtable, let's examine a FILE pointer returned by fopen.

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>
}

The pointer targets the _IO_file_jumps table.

3

This is a set of 21 function pointers. File stream operations dispatch through different entries depending on the execution path.

Following the fwrite path

For this exploration I'll focus on the fwrite path. After placing breakpoints on each function and calling fwrite, the first breakpoint we hit is _IO_file_xsputn.

4

The call happens at fwrite+216. This matches the glibc source: _IO_sputn is a macro that dispatches through the vtable, resolving to _IO_file_xsputn for this stream.

   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]

Attempting to replace the vtable

For a first attempt, let's overwrite the vtable pointer with desired_func - 0x38 and set a breakpoint at fwrite+216.

pwndbg> p &win
$3 = (<text variable, no debug info> *) 0x4019e1 <win>
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

Execution aborts before reaching the breakpoint. The error suggests that glibc validates the vtable pointer before performing the indirect call. Let's inspect the backtrace and see where this happens.

fwrite reaches _IO_vtable_check which is rejecting the forged vtable pointer.

6

The implementation contains a mechanism for accepting foreign vtables, but it is not under our control. The relevant code is available in vtables.c.

Understanding the vtable validation

By the time _IO_vtable_check is called it's already too late, the vtable validation has failed. The earlier IO_validate_vtable frame in the backtrace is the interesting part, so let's inspect that instead.

pwndbg> disass IO_validate_vtable
❌️ No symbol "IO_validate_vtable" in current context.

GDB cannot resolve IO_validate_vtable as a symbol. Looking at the source, we can see it is inlined into fwrite.

Download Tool