NewStarCTF-pwn
- NewStarCTF-pwn
- week1
- overwrite
- read() 参数
- gdb
- overwrite
- week2
- week3
- week4
- week1
week1
overwrite
首先查看文件,保护全开,为64位
放入IDA查看代码,发现关键函数func()
第9-12行代码,首先打印提示信息“pls input the length you want to readin: ”,然后使用__isoc99_scanf
函数接收用户输入的一个整数并存入变量nbytes
中。接着检查nbytes
的值,如果nbytes
大于 48,则程序调用exit(0)
退出。
第13、14行,打印提示信息“pls input want you want to say: ”,然后使用read
函数从标准输入(文件描述符为 0)读取数据,将读取的数据存储到地址为&nbytes_4
的内存区域,读取的字节数为nbytes
转换为无符号整数后的大小(本题需要小于48)。
我们先来学习一下read()函数,它用于从文件描述符(通常是套接字、文件等)读取数据,读取打开文件内容
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
-
read() 参数
-
fd
:- 是文件描述符,可以是套接字、文件等读取的文件(标准输入为 0)
-
buf
:- 是一个指向要读取数据的缓冲区的指针,将读取的内容保存的缓冲区
-
count
:- 是要读取的字节数,文件的长度
-
返回值:
- 如果成功,返回读取的字节数(可能为 0,表示已经读到文件末尾)。
- 如果出错,返回 -1,并设置
errno
表示错误原因。
-
第15行
if ( atoi(nptr) <= 114514 ){puts("bad ,your wallet is empty");}else{puts("oh you have the money to get flag");getflag();}
如果 nptr
中的字符串被转换为数字且大于 114514
,程序会打印 flag
,否则会输出钱包空的信息。其中:
函数atoi ()
int atoi(const char *str);
atoi (表示 ascii to integer)是把字符串转换成整型数的一个函数,会扫描参数 nptr字符串,会跳过前面的空白字符(例如空格,tab缩进)等。如果 nptr不能转换成 int 或者 nptr为空字符串,那么将返回 0。特别注意,该函数要求被转换的字符串是按十进制数理解的。atoi输入的字符串对应数字存在大小限制(与int类型大小有关),若其过大可能报错-1。
const char* str1 = "1234";
const char* str2 = "56abc";
const char* str3 = "abc123";int num1 = atoi(str1); // 转换为整数 1234
int num2 = atoi(str2); // 转换为整数 56,遇到非数字字符后停止
int num3 = atoi(str3); // 转换为整数 0,开头没有数字
具体函数查看atoi()参考文档
回到本题,进行漏洞分析:
nptr
和nbytes_4
之间的偏移是 0x30。如果输入大于 0x30 的正整数,程序将会通过exit()
退出。- 观察到,
read()
函数中使用的是unsigned int nbytes
,但在输入校验时nbytes
是int
类型,进行了有符号比较。这里产生了漏洞。
read(0, &nbytes_4, (unsigned int)nbytes);
例如,-1
的 16 进制表示是 0xffffffff
,对于有符号整数来说,这是一个负数;但在无符号整数中,它代表一个非常大的正整数。因此,利用这一点,输入一个负数值,可以绕过前面的大小检查,将后续输入的数据覆盖到 nptr
,从而完成利用。
exp:
from pwn import *
#context.terminal = ['tmux','splitw','-h']
p = process('./pwn')
#p = remote('ip', port)
p.sendlineafter(b': ', b'-1')
payload = b'a'*0x30 + b'114515'
p.sendafter(b': ', payload)
p.interactive()
观察脚本执行顺序,其顺序为先发送整数-1,使scanf函数接收的整数nbytes的值满足小于48的条件,保持程序的运行,同时利用下面代码使用无符号整型转换的nbytes值比较大小,而无符号的-1又为最大值(0xffffffff)的漏洞,达到想要的效果(后面(unsigned int)nbytes
可以给&nbytes_4
分配nbytes个字节空间),如果输入一个正整数,那空间必然要小于48,就无法溢出数据到nptr[72]了。
- 问题1:为什么脚本构造的是0x30个字符a?
答:分析变量的地址及偏移,0x80-0x50=0x30,所以输入0x30(48)个字符后nbytes_4就满了,再输入就会溢出到nptr[]了
size_t nbytes_4; // [rsp+10h] [rbp-80h] BYREFchar nptr[72]; // [rsp+40h] [rbp-50h] BYREF
- 问题2:还多了114515这个数是干嘛的
答:众所周知
注意,这里用的是字节型数据(b'114515'而不是整型p64(114515)),见上文atoi(nptr)
函数,其接收参数nptr为字符串,之后会转为整型
- 问题3:为何使用此脚本攻击不会触发金丝雀栈保护
答:输入仅影响了缓冲区和部分其他变量,而未触及金丝雀,虽然代码中使用__readfsqword(0x28u)
读取了金丝雀值并存储在v4
中,但攻击者构造的payload
(b'a'*0x30 + b'114515'
)在填充缓冲区并溢出时,尚未覆盖到金丝雀所在的位置,当栈空间被耗尽时,才会栈溢出。此题的溢出全部在栈内完成,故不会触发金丝雀栈保护。注意区分缓冲区溢出和栈溢出两者之间的区别
gdb
首先进行查看,保护全开,64位
放入IDA查看主函数,发发现代码会不一样,既然叫gdb,那肯定就不能只单纯静态调试了
可以看到程序逻辑是对一串字符串执行了加密操作,然后需要我们输入加密后的内容
对于加密函数,可以发现完全看不懂(不是🥰),甚至不知道哪个是加密函数(确信😢)
所以我们选择动调查看加密后的内容
键入 gdb ./gdb
并运行,先运行程序(命令run),再用 b *$rebase()
下断点(断在call 加密函数处)本题call函数在0x181a处,不同环境下可能不同
其中:b *$rebase()
用于设置一个断点
b
是break
的简写,设置断点*
: 表示解引用操作。用于访问指针指向的内存地址的值$rebase
() 是一个 GDB 内置函数,用于将给定的虚拟地址转换为实际的内存地址
综合起来,b *$rebase(0x181a)
的意思是:使用 $rebase(0x181a)
计算出重定位后的地址;然后,在该地址处设置一个断点。
如果用gdb打开直接用b *$rebase(0x181a)会报错
pwndbg> b *$rebase(0x181a)
evaluation of this expression requires the target program to be active
//表示:要对 `$rebase(0x181a)` 这样的表达式设置断点,需要目标程序处于活动(正在运行)的状态。
所以先运行。这里运行程序时遇到一个问题,就是run以后会直接运行到让你输入的那个位置,如果你输入什么了,他就会退出,用ctrl+c退出其函数即可。之后下断点,再run到断点处,这都是dbg的基础操作练习啊。
可以看到,已经运行到call加密函数的位置了
运行到加密函数处,可以发现,rdi 寄存器存的是要加密的内容,rsi 存的是加密的 key
先复制下要加密内容的地址
0x7fffffffd9a7
(不同环境可能不同,此地址为加密内容"0d000721"
所指向的地址)
然后使用 ni
指令步进
注:ni
和n
(next
)的区别在于,ni
是按机器指令来执行的。也就是说,ni
会执行下一条机器指令,而不是下一行源代码。由于一条源代码行可能对应多条机器指令,所以ni
的执行粒度更细。
此时字符串已经完成了加密,我们使用 tel 0x7fffffffd9a7
指令查看字符串(其本身和后继地址)的内容
pwndbg> tel 0x7fffffffd9a7
00:0000│-439 0x7fffffffd9a7 ◂— 0x4557455355431d5d
01:0008│-431 0x7fffffffd9af ◂— 0x6572636573796d00
02:0010│-429 0x7fffffffd9b7 ◂— 'tkey1234567890abcdefghijk'
03:0018│-421 0x7fffffffd9bf ◂— '567890abcdefghijk'
04:0020│-419 0x7fffffffd9c7 ◂— 'cdefghijk'
05:0028│-411 0x7fffffffd9cf ◂— 0x6b /* 'k' */
06:0030│-409 0x7fffffffd9d7 ◂— 0x7ffff7ffe2e000
07:0038│-401 0x7fffffffd9df ◂— 0x7ffff7ffe2e000
其地址内容为一串十六进制数字,不知道是什么,用x指令精准查看指定地址内容
pwndbg> x 0x7fffffffd9a7
0x7fffffffd9a7: "]\035CUSEWE"
文章说b'\x5d\x1d\x43\x55\x53\x45\x57\x45'
便是加密后的内容,经过分析,“]\035CUSEWE”就是加密内容
b'\x5d\x1d\x43\x55\x53\x45\x57\x45' == b']\035CUSEWE'
True
只是要以文章的形式还需要多一步构造过程(本题并未直接显示)所以姑且就直接使用本题所得结果编写脚本
exp
from pwn import *
p = process('./gdb')
elf = ELF('./gdb')
data = b']\035CUSEWE'
p.sendline(data)
p.interactive()
结果
┌──(root㉿kali)-[~/Desktop/New StarCTF/gdb]
└─# python3 act.py
[+] Starting local process './gdb': pid 513654
[*] '/root/Desktop/New StarCTF/gdb/gdb'
...
[*] Switching to interactive mode
[*] Process './gdb' stopped with exit code 0 (pid 513654)
Original: 0d000721
Input your encrypted data: Congratulations!\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\...