Canary Bypass: Reception Desk
Reception Desk
Section titled “Reception Desk”The reception desk signs visitors in. You give it a name, it prints a visitor pass, and it lets you correct the name if you got it wrong. It also holds the day’s manifest, which it never shows anyone.
Your goal is to get the manifest printed.
Get the files
Section titled “Get the files”Download reception-desk-v1.0.0.tar into
~/underdunn-course/downloads and check it against this digest:
0e689f857cf23bc912db764a957b23c4f8e960769a28ab09c79a0fb136d164d4 reception-desk-v1.0.0.tarcd ~/underdunn-course/downloadssha256sum reception-desk-v1.0.0.tarmkdir -p ~/underdunn-course/labs/reception-desktar -xf reception-desk-v1.0.0.tar -C ~/underdunn-course/labs/reception-deskcd ~/underdunn-course/labs/reception-desk/reception-desk-v1.0.0sha256sum -c CHECKSUMS.sha256sh build.shThe build prints two warnings. Read TASK.md and desk.c, then talk to the desk a few times before
you measure anything.
Hint 1: the frame, and why the declaration lies
The build’s two warnings are a claim to check rather than a measurement, and the declaration in the
source is the same. It tells you how large the array is. It doesn’t tell you where the array sits in
the frame, and on this program those are two different numbers, because there’s another local below the
buffer. Counting from char badge[96] gives a distance to the canary that’s too small.
objdump -d -Mintel ./desk | sed -n '/<desk>:/,/^$/p'Three instructions carry it: the one that reserves the frame, the one that stores the canary, and the
lea that feeds the first read. GCC can reserve space by adding a negative number rather than
subtracting a positive one, so read the operand rather than matching the mnemonic. The saved frame
pointer is at rbp+0 and the saved return address at rbp+8.
Hint 2: find out what the check actually stopped
Aim the return address at the manifest routine, deliver it, and watch what the desk does. It won’t work. Establish why not.
The diagnostic tells you a comparison failed. It doesn’t tell you how far your write got. Those are different facts and only one of them is on the screen. Stop at the instruction that reloads the stored canary, before the comparison resolves, and read both slots:
(gdb) break *<the canary reload in your build>(gdb) run < <your input file>(gdb) x/gx $rbp+8(gdb) x/gx $rbp-8“The write was prevented” and “the use of the write was prevented” are different claims. Only one of them is true here.
Hint 3: the desk will tell you the value
The desk prints your name back, and the routine that prints it stops at the first zero byte. The value you want begins with a zero byte, as Leaking a Stack Canary showed.
But the thing that stops the print is itself a byte you can write to. Work out the input length that reaches exactly that byte and replaces it, and predict how many bytes of the value survive.
What comes back doesn’t stop at the end of the canary. Establish where it does stop and why, rather than assuming every returned byte belongs to the value you’re after.
Hint 4: check your reconstruction against the program
Capture the echoed line as bytes rather than as text. Rebuild the value as the zero byte you overwrote followed by what came back, in the order it came back, then read the canary at its source in the same run and compare:
(gdb) x/gx $fs_base+0x28If the two disagree, the disagreement is in your byte order or in where you started slicing. It isn’t in the program.
Hint 5: a file cannot carry this one
The input that failed in Hint 2 can’t be reused. The second half of it contains a value that doesn’t exist until the program has read the first half and answered you, and a file is fixed before the process starts.
So drive the desk from Python with subprocess: write stage one, read the answer, write stage two
into the same process. The canary also has to go back exactly where it came from, because the write
that reaches the return address is one continuous write and you can’t step over it.
Two things that will waste an hour otherwise. Use os.read on the pipe’s file descriptor rather than
a fixed-count read on the stream, which will block waiting for bytes the desk isn’t going to send.
And wait for a marker in the desk’s output rather than for a byte count, flushing after each write.
The whole route
Derive the buffer offset from the lea, the canary offset from the instruction that stores it, and
subtract. The return slot is at rbp+8.
Send stage one at exactly one byte past the distance to the canary, so the canary’s leading zero is replaced and the echo runs on into it. Capture the echo as bytes, take the seven that follow, and rebuild the canary as a zero byte followed by those.
Stage two, in order: filler to the distance, the rebuilt canary, eight bytes for the saved frame
pointer, and the address of the manifest routine from nm on your own binary. Write it into the same
process, then close the pipe.
Run it three times. Three different canaries, three successes. If it only works on the run that produced the value you wrote down, it isn’t finished.
The flag
Section titled “The flag”Paste exactly what the desk printed, with no surrounding whitespace:
The canary is per process. A solver that works once and fails on the next run has recovered a number rather than learned a technique, so run it a few times before you call it done.
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 lessons. The desk’s own symbols agree because this build isn’t position-independent. Stack addresses and the canary will not agree, and nothing here depends on it.
A solver run is still alive. Check the command with ps -p PID -o pid=,comm= before you send it
any signal, and close the pipe rather than leaving the desk waiting on a read.