之前通过攻防世界 String 已经初步了解格式化字符串漏洞的利用原理和利用方式。现在通过这道题精进。
题目名:format_string
提取路径:https://pan.baidu.com/s/1e9I59FhilppS9QIhQWND0A
提取码:F1re
# 代码审计
checksec 一下:
很简单的一道题噢,就只开了 NX 不可执行不让你 shellcode。
扔进 IDA 观察一下,发现漏洞所在地:是个三十二位程序的格式化字符串漏洞。
程序先泄露了 v3 的地址,然后有第一个格式化字符串漏洞,如果满足 v3=12 的话,触发第二个格式化字符串漏洞。
一个后门函数。
# 利用思路
# 第一步:
1. 首先是需要先将 v3 的值改为 12。这可以利用格式化字符 % n 实现。
构造方式是 v3_addr+%() c%()$n。其中两个括号内的内容是我们需要填入的内容。
首先让我们了解一下 % c,% c 除了输出字符之外,还可以通过 % ac 来填充输出内容至 a 个,用空格在原内容左侧填充。例如:
1 | char a='g'; |
以上代码的输出结果就会是 "g",其中 g 前面有三个空格填充总字符至 4 个。
因此合理利用 % c 来填充字符。由于 p32 (v3_addr) 占四个字符,要填充至 % n 前有 12 个字符来使 v3 的值被改为 12,所以我们构造 payload1=p32 (v3_addr)+%8c%()$n 即可。现在只需确定格式化字符串偏移。
# 题目限制一:
1. 题目读入 format 和 v1 时,都限定了长度。这长度限制对于以往的处理方式很致命。
导致的问题有:
①:不能用 aaaa-% x-% x… 来泄露格式化字符串偏移,因为这样读入的字符数太多。
②:填充字符时不能用 aaaaaaa 等长串,需要利用 % c。
解决办法:
利用 % k% x 去泄露不同位置的值。直到找到偏移,我比较笨,不会用 gdb 看偏移。这里我写了一个爆破脚本:通过输入 aaaa - 以及找到其偏移来确定格式化字符串偏移。
1 | from pwn import * |
运行结果:
1 | 14 |
运行到 15 停止,我们运行程序验证一下:
发现偏移确实是 15, 那么第一个限制就解决了:
我们构造 payload1 为:
1 | payload1=p32(v3_addr)+b'%8c%15$n' |
# 第二步:
接下来我们进入了第二个格式化字符串漏洞的利用。这次能输入的长度有 20 个字符。
首先我们明确一下格式化字符串漏洞能做到什么:
①:任意地址写
②:泄露 format 参数所在栈里面的内容。
因此我们有如下思路:
①:去修改某个函数的 GOT 表,让其变成 system 函数的地址。
②:去改写函数返回地址。
很明显,第二个格式化字符串漏洞利用后程序就会终止,在这之后没有函数可以让我们修改 GOT 表。
因此我的思路是:
①:要么直接修改函数返回地址为后门地址
②:修改函数返回地址为 vuln 函数对 v3 赋值为 666 这句语句之后的地址,由于我们之前已经改变过 v3 的值,因此我们不用更改 v3 都可以再触发第二次格式化字符串漏洞。就这样再来两次格式化字符串漏洞,子子孙孙无穷尽也,直到不断利用格式化字符串漏洞达到我们的目的。
很显然哦,第一种简单的多了,那我们先去找到函数返回地址处于何处。
通过 IDA 栈图,var_c 是 v3,而 r 处于 v3_addr+16 的位置。
去 gdb 里面调试验证。
其中 0x0804873E 确实是 call vuln 后的返回地址,那我们只用修改这为我们后门函数的地址值就行。
通过相同的脚本爆破
# 题目限制二:
后门函数要 close,怎么办???
解决办法:
当然是直接跳转不 close 的地址啦
直接跳 0x8048793 就行啦。
我最初构造的 payload2:
1 | payload2=p32(v3_addr+16)+b'%134514569'+b'c%7$n' |
这样理论上是可行的。但是我发现发送 payload2 后系统不停向我发送:
1 | sent b'32'*0xfff |
系统一直给我发空格。这是由于 print(payload2)的时候要输出 134514573 个空格,这样发送大量字符会导致服务器卡顿甚至崩溃。
因此我们基本上都采用一个字节一个字节去改地址的方法,也就是采用 hhn 去改四次。
32 位格式化字符串漏洞里 pwntools 有一个函数:
fmtstr_payload
这个工具原理,实现过程我就不说了,大概就是他会自动帮你构造 paylaod,只需你提供
1. 偏移量。
2. 需要改变的地址。
3. 改变后的地址。
他就是通过每个字节每个字节来改变的。
但是这道题显然不行,fmtstr_payload 返回的 payload 太长了,不满足题目的长度限制。
后来去观察了一下返回地址和后门地址只有最后一个字节不同。那我们就只用改最后一个字节就行了。
最后由于小段字节序的原因,返回地址的最后一个字节的地址就为 v3_addr+16。
因此我们构造的 payload2:(143=0x93-4)
1 | payload2=p32(v3_addr+16)+b'%143'+b'c%7$hhn' |
# 最终 exp:
1 | from pwn import * |