Payload Writing: Vault Terminal
Vault Terminal
Section titled “Vault Terminal”The vault terminal is the keypad on the strongroom door. Somebody types an access code and the terminal says whether it matched. What the strongroom holds is written down inside the program, and nothing prints it.
Your goal is to get the vault’s contents printed.
Get the files
Section titled “Get the files”Download payload-vault-v1.0.0.tar into
~/underdunn-course/downloads and check it against this digest:
4888f594db2efceecd1e76605010ae0f79f0f7344de8174c095ab048af445bed payload-vault-v1.0.0.tarcd ~/underdunn-course/downloadssha256sum payload-vault-v1.0.0.tarmkdir -p ~/underdunn-course/labs/payload-vaulttar -xf payload-vault-v1.0.0.tar -C ~/underdunn-course/labs/payload-vaultcd ~/underdunn-course/labs/payload-vault/payload-vault-v1.0.0sha256sum -c CHECKSUMS.sha256sh build.shbuild.sh uses the same flags the lessons used, including the executable stack. This lab also needs
the VM setting from the last two lessons:
sudo sysctl -w kernel.randomize_va_space=0Read TASK.md and vault.c, then run the terminal a few times before you measure anything, with the
code it accepts and with codes it doesn’t. Notice what the accepting run prints, which is not what
you might expect a vault to say.
Hint 1: this delivery path is stricter than the lessons’
Read how the terminal takes its input instead of assuming it matches the programs you’ve already broken. The function it uses stops on a class of bytes rather than on one particular byte.
Whatever that class turns out to be, the constraint applies to every byte you send. Not just the instructions: the padding and the eight bytes of address at the end are input too.
Write the forbidden values down as a list now. You’ll be scanning a complete input against it later and it’s easier than rediscovering it at midnight.
Hint 2: the frame, and an instruction that doesn’t look like one
The instruction that reserves the buffer may not be the one you’ve been looking for. GCC can reserve stack space by adding a negative number instead of subtracting a positive one, so read the operand rather than matching the mnemonic.
objdump -d -Mintel ./vault | sed -n '/<check_code>:/,/^$/p'Take the buffer’s position from the lea that produces its address, apply the layout you derived in
Crashes and Evidence of Control, and state
two numbers: the offset to the saved return address, and the length that covers all eight of its
bytes.
Hint 3: what your payload actually needs
The vault is a global, so nm gives you its address without running anything. Its contents are a
separate question, and the program does something to them before it ever asks for a code.
nm -n ./vault | grep -i vaultstrings ./vault | head -40Run strings and note what it doesn’t show you, then read what main does first. Your payload needs
the vault’s address, not its bytes.
Load that address into rsi as an immediate rather than with a rip-relative lea. Your payload
won’t be running at the address it was assembled for.
Hint 4: placing it and aiming
Assemble with as, extract with objcopy, then disassemble your own extracted bytes to confirm
they’re what you meant, and check the count.
Build the input as sled, then payload, then the aim address, sized so the address lands exactly at the offset from Hint 2. The sled covers the difference between the address you measured in the debugger and the one the buffer actually has in the shell, so aim into the middle of it rather than at its first byte.
Then scan the whole thing against your forbidden list. If your chosen aim contains a forbidden byte, pick a different one inside the sled.
Hint 5: two failures that look like your own mistake
The terminal prints its ordinary last line and exits cleanly. Your input never reached the return slot. Before you change any length, scan the input for a forbidden byte, including the address. The delivery path truncated you somewhere.
It works under GDB and faults in the shell. Check both addresses. The debugger’s environment
isn’t the shell’s, and the sled is what covers the gap between them. Stop at the ret and check
where the aim actually points:
(gdb) break *<the ret in check_code>(gdb) run < <your input file>(gdb) x/gx $rspThat value has to fall inside your sled, not at its edge.
The whole route
Derive the offset and covering length from check_code’s frame, reading the reservation operand
rather than the mnemonic. Get the vault’s address from nm; the contents aren’t in the file because
main transforms them at run time, which is why strings shows nothing useful.
Write the write and exit pair from
Writing Payloads in Assembly, with the
vault’s address in rsi as an immediate. Assemble, extract, disassemble to check, and count the
bytes.
Build sled + payload + aim so the aim sits at the derived offset, pick an aim in the middle of the
sled, and scan every byte of the result against the forbidden list. Confirm with x/gx $rsp at the
ret that the aim lands inside the sled, then run it outside the debugger and check the exit status
next to the output.
The flag
Section titled “The flag”Run the terminal with your input outside the debugger and paste exactly what it printed, with no surrounding whitespace:
A result observed under GDB doesn’t establish what happens in the shell, and a clean exit doesn’t on its own establish that the program produced the intended result.
If something looks wrong
The VM setting didn’t stick. sysctl kernel.randomize_va_space reports the current value, and it
resets on reboot.
The build differs from the lessons’. Restore the supplied source and run sh build.sh
unmodified. It keeps the executable stack.