SCTF2019 easy_heap
解法1
和姚老板的题完全一样,exp稍微改改就过了
exp.py
1 | #coding=utf-8 |
解法2(17)
这个exp更舒服一点,比较好理解1
2
3
4
5
6
7
8
9
10
11Alloc(p,0x88)#0
Alloc(p,0x68)#1
Alloc(p,0xf8)#2
Alloc(p,0x68)#3
Delete(p,0)
Fill(p,1,'a'*0x60+p64(0x100))
Delete(p,2)
Delete(p,1)
首先分配几个堆块,把第0个free掉,Edit(1)修改chunk2的prev_size和size,再释放chunk2,0-2合并成一个大的unsorted bin。
Delete(1)让chunk1进入fastbin[0x70],Alloc(0x88)让fastbin的fd和bk被写入main_arena+88。注意此时这个fastbin存在size error,直接从中分配chunk会报错。这时候我们再Free掉刚才的chunk0,再分配一次构造overlapping chunk可以修改fastbin的size为正确的值,从而可以分配fastbin的chunk再部分写泄露libc。
1 | Alloc(p,0x88)#0 |
Delete(2)让0x70的块再次进入fastbin,Delete(1)再分配chunk溢出修改fastbin的fd(刚才的套路),两次分配就可以分配到fake_chunk,继而修改成shell_addr。
最后Malloc触发malloc_hook不好使,这里free(2)和free(5)触发错误调用malloc_hook拿到shell
1 | Delete(p,2)#into 0x70 fast bins |
17.py
1 | #coding=utf-8 |
解法3(官方+x3h1n)
这个解法感觉是一个很好玩的套路,今天查了一天资料总算了解差不多了。emm但是感觉要写的东西太多,还是直接copy师姐的exp和分析(懒)
这个解法利用的IO_FILE攻击,之前我也做过一道题,写了点东西,但是那个只是写了利用的payload,对于具体利用的方式不甚了解,这里会详细一点。
关于各种结构体及名词
_IO_FILE_plus是最全面的一个结构体,它包括一个_IO_FILE结构体和一个IO_jump_t指针,同时,我们一会要用的_IO_list_all是它的结构体指针。尤其要注意的是,_IO_list_all 并不是一个描述文件的结构,而是它指向了一个可以描述文件的结构体头部,通常它指向 IO_2_1_stderr 。
1 | struct _IO_FILE_plus |
_IO_FILE结构体就是我们常常利用的_IO_2_1_stdout_的数据类型,我们可以在Pwngdb中看到一个通常的结构体内容。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42struct _IO_FILE {
int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
/* The following pointers correspond to the C++ streambuf protocol. */
/* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
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;
int _blksize;
int _flags2;
_IO_off_t _old_offset; /* This used to be _offset but it's too small. */
/* 1+column number of pbase(); 0 is unknown. */
unsigned short _cur_column;
signed char _vtable_offset;
char _shortbuf[1];
/* char* _save_gptr; char* _save_egptr; */
_IO_lock_t *_lock;
};
如下图,_chain表示的是链表的下一个节点,紧跟在_IO_FILE后面有一个vtable,即我们所说的虚表,对应_IO_jump_t指针。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29struct _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;
};
这里在gdb去看一下,可以看到vtable里是一堆函数指针
最终拿一张图总结一下(from 安全客)
攻击原理
首先利用堆溢出或者任意写将unsorted bin的bk修改_IO_list_all-0x10,根据unsorted bin攻击的原理,(_IO_list_all-0x10)->fd = unsorted_chunks(av),结果是_IO_list_all的值被改成了main_arena+88,即main_arena+88被作为一个_IO_FILE结构体对待,注意_chain这里,指向的为smallbin[4]即0x60的第一个块。
为此,我们需要在smallbin[4]中构造一个假的_IO_FILE。这里用到的知识是当分配一个较小的size的chunk的时候,不符合unsorted bin的size会被放入到small bin中。我们先利用unlink把unsorted bin的size部分改为0x61,最后再分配一个0x1的chunk就可让这个chunk进入smallbin[4]
因为这个smallbin要作为我们的fake _IO_FILE,我们用unlink修改其vtable为0x555555756070,而这里存储的是mmap的地址,最终我们申请chunk触发unsorted bin attck,程序报错触发_IO_flush_all_lockp,从而执行mmap里的shellcode。
整个的流程可以用下面的图概括
x3h1n.py
1 | from pwn import * |