Overflow and Reuse: Bonded Store
Bonded Store
Section titled “Bonded Store”The bonded store holds goods until customs clears them. It hands over its vault code once a consignment has been cleared and you quote the seal word back to it. Nothing clears a consignment, and nothing prints the seal word.
Your goal is to get the vault code printed.
Get the files
Section titled “Get the files”Download bonded-store-v1.0.0.tar into ~/underdunn-course/downloads
and check it against this digest:
cd47770ac290a8d4d7d81f431d9c98aacf6dfe55d9d5cbb7fba1cb3de72b94af bonded-store-v1.0.0.tarcd ~/underdunn-course/downloadssha256sum bonded-store-v1.0.0.tarmkdir -p ~/underdunn-course/labs/bonded-storetar -xf bonded-store-v1.0.0.tar -C ~/underdunn-course/labs/bonded-storecd ~/underdunn-course/labs/bonded-store/bonded-store-v1.0.0sha256sum -c CHECKSUMS.sha256sh build.shThe build is silent under -Wall -Wextra.
Hint 1: where to look
Use the store normally for a while before you try to make anything go wrong, and run board after
every command so you can see what it thinks it has.
Then compare what the store allocates with what it hands back. Those two sets don’t cancel out the way you might expect, and one record goes back much earlier than you would guess from reading the commands.
Hint 2: the seal word
The build was silent, including under -Wuse-after-free, so nothing is going to point you at this.
A record that has been handed back isn’t erased. The allocator writes over the beginning of it for its own bookkeeping and leaves everything past that untouched, and the lesson on use after free measured exactly how many bytes that is.
The word itself is not in the built file, so strings will not find it. Two different things the
store allocates ask for the same number of bytes, which is what decides whether you can be handed the
same memory back. And only one command prints a whole fixed-size record
rather than stopping at the first terminator.
Hint 3: finding the approval record
For the clearance flag, start from the other end: only one command writes bytes you supply into a record, and it writes as many of them as you tell it to.
The store prints the address of every record it opens, so two commands and a subtraction settle the distance without a debugger. The offset you actually want is eight less than that distance, because a chunk’s header sits in front of a record rather than inside it.
Hint 4: getting past the header
You can’t reach a neighbouring record’s contents without writing across its header on the way, so the question is what that header holds and whether you can put it back.
You can. Chunk sizes are quantised, so a 24-byte request has exactly one possible size field, and nothing about it is secret.
Use board to check whether the flag took. That confirms the write on its own, separately from
whether release then works.
Hint 5: the flag is set and release still kills the process
This is what a first attempt does. The write succeeded. Something else didn’t.
release does one thing before it prints anything at all, and that operation reads the header you
wrote over on your way to the flag. Whatever you left in those eight bytes, release is about to
take it seriously.
The whole route
The seal. close hands the ledger back. A note is the same 48-byte request the ledger was, so
the note is served the ledger’s chunk. note copies with strcpy, so a one-character note disturbs
one byte, and read prints the whole fixed-size record instead of stopping at the terminator. The
allocator damaged the first sixteen bytes on the way out; the seal sits at offset 24, past them.
Run ./bond and enter:
closenote .readquitThe distance. open 0 and approve are both 24-byte requests, so both are 0x20 chunks. The
approval record opens 32 bytes after slot 0, which puts its size field at slot 0 + 0x18 and its
first field at slot 0 + 0x20.
Run ./bond and enter:
open 0approveboardquitThe payload. Twenty-four bytes of filler, then the eight bytes 21 00 00 00 00 00 00 00 to put
the header back, then a non-zero four-byte integer for the flag. Thirty-six bytes, sent with a driver
rather than a shell quote:
import syspayload = b"L" * 24 + (0x21).to_bytes(8, "little") + (1).to_bytes(4, "little")sys.stdout.buffer.write(b"open 0\napprove\nlabel 0 %d\n" % len(payload) + payload + b"\nboard\nrelease <the seal from the first step>\nquit\n")The flag
Section titled “The flag”Paste exactly the code the store printed, with no surrounding whitespace:
If a sanitizer build behaves oddly
AddressSanitizer reports both bugs immediately, which is correct, and it also moves every record, which means the distance above no longer holds and the lab isn’t solvable on that build. Measure on the plain build and use the instrumented one only to confirm the class.