Skip to content
Courses

Return-Address Control: Door Controller

Stack SmashingLab 1 of 290 minutesLab

The door controller unlocks the staff entrance. Somebody types in the day’s entry code, the controller checks it, and the door opens. It also keeps the staff roster, which it never prints.

Your goal is to get the roster printed.

Download door-controller-v1.0.0.tar into ~/underdunn-course/downloads and check it against this digest:

cd4f892dc330c1b6402a4d81033f19e421f63ded2ce3d05f909b4a514efdb11b door-controller-v1.0.0.tar
Terminal window
cd ~/underdunn-course/downloads
sha256sum door-controller-v1.0.0.tar
mkdir -p ~/underdunn-course/labs/door-controller
tar -xf door-controller-v1.0.0.tar -C ~/underdunn-course/labs/door-controller
cd ~/underdunn-course/labs/door-controller/door-controller-v1.0.0
sha256sum -c CHECKSUMS.sha256
sh build.sh

The build prints two warnings and nothing else. Read TASK.md and door.c, then talk to the controller a few times before you measure anything.

Hint 1: the code is not the way in

Before you try to go around the check, establish that you can’t go through it. Follow the value it compares against backwards to wherever it gets written, and notice which function fills it and when that happens.

There’s a difference between “I couldn’t guess it” and “no input I can prepare will ever match it,” and only the second one is something the program tells you.

Hint 2: what the compiler already told you

The build warned you twice about the same function. That function reads into a buffer without knowing how large the buffer is.

Separately, run nm over the binary and read the names. One of them is a routine that does exactly what you want and is called from nowhere.

Hint 3: the distance is not the buffer size

The declaration tells you how many bytes the array holds. It doesn’t tell you where the array sits in the frame, and here those are different numbers, because there’s another local in this function and it isn’t below the buffer.

Three instructions in the prologue carry what you need: the one that reserves the frame, the one that initialises the other local, and the lea that produces the address handed to the reading function. The saved frame pointer is at rbp+0 and the saved return address at rbp+8.

Hint 4: the two numbers and where to read them
Terminal window
objdump -d -Mintel ./door | sed -n '/<challenge>:/,/^$/p'
nm -n ./door | grep ' open_door$'

Take the buffer’s offset from rbp out of the lea. The return slot is at rbp+8, so the distance is the difference between those, and the length that covers all eight of its bytes is eight more than that.

Write the input from a script that prints its own byte count, so a length mistake is visible before you run anything.

Hint 5: it faults and you want to change the padding

Don’t. Read the slot instead. Break on the ret in the challenge function and look at the eight bytes at the stack pointer, because that’s exactly what ret is about to consume:

(gdb) break *<the ret in challenge>
(gdb) run < win.bin
(gdb) x/gx $rsp

If those bytes are not the address you meant to put there, your distance is wrong. If they are the address and it still faults, your distance is fine and the address is wrong. Those are different problems and the padding fixes neither of them.

The whole route

The check compares against a global filled at run time from a source your input never touches, so no prepared input satisfies it. gets has no idea how large the buffer is. open_door is compiled in, has a symbol, and is at a fixed address because the build isn’t position-independent.

Terminal window
objdump -d -Mintel ./door | sed -n '/<challenge>:/,/^$/p' # buffer offset from rbp, out of the lea
nm -n ./door | grep ' open_door$' # the address
from pathlib import Path
from struct import pack
payload = b"A" * <the distance you derived> + pack("<Q", <the address>)
Path("win.bin").write_bytes(payload)
print(f"wrote {len(payload)} bytes")
Terminal window
./door < win.bin

Paste exactly what the controller printed, with no surrounding whitespace:

Enter the complete flag, including UNDERDUNN{ and the closing brace. The check happens in your browser; the flag is not sent anywhere.

If something looks wrong

The build reports something other than its two warnings. Restore the supplied source and run sh build.sh unmodified.

Addresses don’t match the lesson’s. They shouldn’t. This isn’t the lesson’s program and none of its numbers carry over. Its own symbols are fixed across runs because the build isn’t position-independent.

It crashes and you want to know where. Run it under GDB with the same input rather than reasoning from the shell’s message. A segmentation fault reported by the shell doesn’t tell you which value was wrong.