输入的参数的个数是不固定的,是由前面的格式化字符串决定的,所以我们只要控制了前面的格式化字符串,再结合一些参数,后面输出什么就是由我们决定的;如: %d 用于读取10进制数值 %x 用于读取16进制数值 %s 用于读取字符串值 %n 用于读取前面字符串的长度并写入某个内存地址
| 函数 | 说明 |
|---|---|
printf() | 向标准输出打印格式化内容 |
fprintf() | 向指定文件流输出 |
sprintf() | 输出到字符串 |
snprintf() | 输出到字符串,带长度限制 |
| 格式符 | 含义 | 危险性 |
|---|---|---|
%x | 输出一个4/8字节十六进制值(从栈上读) | 信息泄露 |
%s | 把栈上的值当作地址,输出该地址处的字符串 | 任意地址读 |
%p | 输出一个指针地址 | 地址泄露 |
%n | 将当前输出的字符数写入到栈上的地址 | 任意地址写(极危险) |
高阶用法:
%7$x表示取第七个参数,打印为十六进制值
简单来说就是对第七个参数做%x的操作
%7$s表示取第七个参数,把其当成地址,输出该地址处的字符串
以此类推……
当然,我们也会常见到类似于%7$sAAAA的写法,在后面加上AAAA的目的是为了“对齐”
在64位的程序中,%7$s只是占据了4B,而地址能存放8B数据,因此添加上AAAA能够完成“对齐”操作,使得后续payload不会与之粘连导致运行失败
from pwn import *
p = process('./pwn')
payload = p32(0x0804c044) + b'%1$x %2$x %3$x %4$x %5$x %6$x %7$x %8$x %9$x %10$x'
p.sendlineafter('your name:', payload)
p.interactive()
确定输入的位置
当 printf 解析 %10$n 时,会尝试从函数调用栈上读取「第 10 个参数对应的内存位置」,并将其当作 int* 指针来写入字符个数:
- 该内存位置是栈上的无效地址(超出传入参数的范围,属于未初始化的栈内存或非法内存);
- 要么读取到非法指针,写入时触发段错误崩溃;
- 要么读取到栈上随机有效地址,造成内存污染(破坏程序数据,最终仍会导致程序异常退出)。
payload = p32(0x0804C044) + b'%10$n'
r.sendlineafter("your name:",payload)
r.interactive()
此时password对应的判断字符串位置就被改成了传入的地址的字节数4 password只要输入4就可以了
评论