0xnhl

Wades Chimichanga Shop

/ Update
4 min read

Wade’s Chimichanga Shop (Heap / UAF / Tcache Poisoning) Writeup#

Challenge Info#

  • Category: Pwn
  • Given files: chall, libc.so.6
  • Remote: nc chals1.apoorvctf.xyz 6001
  • Flag format: apoorvctf{...}

Recovered flag:

apoorvctf{w4d3_4ppr0v3s_0f_y0ur_h34p_sk1llz}


1) Initial Triage#

Files#

ls -la
file chall
strings -n 5 chall
bash

Important hints from strings:

  • chimichanga_count
  • "There's a very special counter somewhere in here."
  • check function in binary: did_i_pass
  • success path compares a value to 0xcafebabe

Symbols and disassembly#

nm -n chall
objdump -d -M intel chall
bash

Key global symbols:

  • chimichanga_count at 0x4040c0
  • orders array at 0x4040e0

2) Reverse Engineering Findings#

  • new_order()
    • finds first empty slot in orders[0..5]
    • allocates malloc(0x28)
    • memset(chunk, 0, 0x28)
  • cancel_order()
    • free(orders[idx])
    • BUG: does not set orders[idx] = NULL
    • gives a dangling pointer (UAF)
  • inspect_order()
    • if slot non-null, prints 0x28 bytes with write(1, orders[idx], 0x28)
    • leaks freed chunk metadata
  • modify_order()
    • reads up to 0x28 bytes into orders[idx]
    • allows writing into freed chunks (UAF write)

Flag gate logic (did_i_pass)#

Relevant logic:

if (chimichanga_count != NULL && *(int*)chimichanga_count == 0xcafebabe) {
    // print success text and open/read /flag.txt
} else {
    puts("\"Wrong number, Francis. Walk it off.\"");
}
c

So objective is clear:

  • make chimichanga_count point somewhere we can write,
  • and ensure first 4 bytes at that pointed memory are 0xcafebabe.

3) Heap Primitive Analysis#

Chunk size: user allocation is 0x28, actual chunk size in tcache bin is 0x30.

With glibc safe-linking, tcache fd is encoded:

stored_fd = next ^ (chunk_addr >> 12)

If we free chunk A and inspect A:

  • first qword leaks stored_fd

If we also know a chunk where next = NULL, then for that chunk:

  • stored_fd = key = (chunk_addr >> 12)

We use this by:

  1. allocate 2 chunks: slot0, slot1
  2. free slot1 first (its next = NULL)
  3. free slot0 second (its next = slot1)
  4. inspect freed slot1 to directly recover key
  5. overwrite freed slot0 fd with target ^ key

Target chosen: 0x4040c0 (address of global pointer chimichanga_count).

Then allocations:

  • malloc #1 pops slot0
  • malloc #2 returns fake chunk at 0x4040c0

So orders[3] = 0x4040c0 (in observed run), and modify slot 3 writes directly into globals.

Write payload at 0x4040c0:

  • qword @ 0x4040c0 = 0x4040c8 (new pointer value)
  • dword @ 0x4040c8 = 0xcafebabe

Now did_i_pass condition succeeds.


4) End-to-End Exploit Procedure (Manual)#

  1. 1 (new) -> slot0
  2. 1 (new) -> slot1
  3. 2 cancel slot1
  4. 2 cancel slot0
  5. 3 inspect slot1 -> read 0x28 bytes, extract first 8 as key
  6. 4 modify slot0 -> write p64(0x4040c0 ^ key)
  7. 1 new (consumes slot0)
  8. 1 new (returns fake chunk at 0x4040c0)
  9. 4 modify slot3 -> write p64(0x4040c8) + p32(0xcafebabe)
  10. 5 claim prize

Output includes flag.


5) Full Exploit Script#


6) Why This Works#

  • UAF read/write exists because freed pointers stay in orders[].
  • inspect leaks tcache metadata from freed chunks.
  • safe-linking can still be bypassed when key is leaked (next=NULL chunk gives key directly).
  • poisoned tcache returns an arbitrary writable address as a future malloc result.
  • we redirect writes into global memory and satisfy the exact flag gate check.

7) Repro Commands#

Run exploit:

python3 exploit.py
bash

Expected output contains:

Wade slow-claps from across the room.
"...Okay. I'll admit it. That was impressive."
apoorvctf{w4d3_4ppr0v3s_0f_y0ur_h34p_sk1llz}
text

8) Final Flag#

apoorvctf{w4d3_4ppr0v3s_0f_y0ur_h34p_sk1llz}

Wades Chimichanga Shop
https://nahil.xyz/vault/writeups/apoorvctf2026/be/wades-chimichanga-shop/
Author Nahil Rasheed
Published at March 24, 2026
Disclaimer This content is provided strictly for educational purposes only.