Skip to content
Courses

Reads and Writes: Receiving Bay

Bug Classes and PrimitivesLab 1 of 3120 minutesLab

The receiving bay board tracks six loading bays. A bay stays sealed until its delivery has been cleared, and the board hands over its vault code once every bay is clear and you quote the duty word back to it. It never prints the duty word.

Your goal is to get the vault code printed.

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

1660ac9bd861ea949a97e73b0d29ade3f1c3c9a6a2465104abafb170c6615f0c receiving-bay-v1.0.0.tar
Terminal window
cd ~/underdunn-course/downloads
sha256sum receiving-bay-v1.0.0.tar
mkdir -p ~/underdunn-course/labs/receiving-bay
tar -xf receiving-bay-v1.0.0.tar -C ~/underdunn-course/labs/receiving-bay
cd ~/underdunn-course/labs/receiving-bay/receiving-bay-v1.0.0
sha256sum -c CHECKSUMS.sha256
sh build.sh

The build completes without warnings. Read TASK.md and bays.c, and ask the board for its rules before you try to break any of them.

Hint 1: one published rule is not enforced anywhere

Take the rules one at a time and find the code that implements each. Four of them are implemented somewhere. One names a party who’s supposed to do something, and the board never asks who is asking.

Compare rules 1 and 2 against the two functions that mention the booking window. One of those functions performs a check and the other performs none.

Run ./bays and enter:

rules
board
open
board
quit
Hint 2: the duty word is reachable by index

Two of the board’s commands take a number from you. Only one of them checks it.

The board stores its bays as records rather than separate variables, so a request doesn’t move by one byte. Work out what it moves by before you work out how far to go, and note that the record is smaller than the label suggests because the seal is packed in beside it.

Terminal window
gdb -q -batch -ex 'ptype /o struct bay' ./bays
nm ./bays | grep -E ' [bBdD] (bays|duty_word)$'

The symbols are absolute. Subtract the two addresses, divide by the record size, and ask for that index.

Hint 3: you do not choose the byte that gets written

A seal is clear when it holds zero. Look at what your input is made of and ask which part of it is a zero, because you don’t get to pick the bytes of the value the way you picked the bytes of a label.

There’s exactly one zero in a copied string, and it’s always at the end.

Terminal window
python3 -c "print('receive ' + 'A'*15); print('board'); print('quit')" | ./bays

board prints every seal, so sweep a few lengths around the size of a record and watch which column moves. The offset of a seal within a record came out of Hint 2’s ptype /o, and the offset of record n is that plus the stride times n.

Hint 4: each copy undoes the last one

You’ll get five bays clear and never six. Ask what a long copy does to the bytes it passes over on the way, including seals cleared by an earlier request, and predict that before you choose an order.

Run board after every single copy rather than at the end. The column you’re trying to move will show you exactly what the previous copy undid.

The same six operations in a different order will do it.

Hint 5: the refusal is about the wrong moment

If the board still says a bay is sealed after six copies, that refusal describes the state at release time, not the state after each earlier request. Those are different facts, and the first one won’t tell you which copy wiped which seal.

Similarly, an empty duty word doesn’t establish which object your request reached, and a segfault doesn’t establish that anything was read or written. Restart the process to restore the initial state and narrow the claim before you change the numbers.

The whole route

The logic bug. open_window sets the flag and asks nothing. Rule 1 says head office opens the window, and there’s no head office in this program.

The read. A record is sixteen bytes. Take the difference between duty_word and bays, divide by sixteen, and inspect that index; the unchecked command reads straight out of the array and returns the word.

The write. The seal is the last byte of a record, so the seal for bay n sits at 16n + 15 from the start of the array, and a copy of exactly that many characters puts its terminator there. Nothing else does.

The order. Longest first. Six copies from the highest bay to the lowest, then release with the word.

Paste exactly the code the board 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 a sanitizer build behaves oddly

A quiet run under a sanitizer is a diagnostic result, not a verdict about whether the board’s rules were followed. Confirm you actually ran the instrumented binary, and remember that inserting redzones moves the globals apart, so an index computed on the plain build no longer lands where you worked out it would.