Skip to content
Courses

Defence Inventory: Service Panel

MitigationsLab 4 of 4240 minutesLab

The service panel records calibration notes for the equipment on site. You write a note, the panel stores it, and it lets you amend it once. It also holds a permit that it never prints.

Somebody else built this one. You get the executable and no source.

Your goal is to get the permit printed and have the program exit cleanly.

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

43af8a928d8950a966de87ffa7886ebceef91067ec8df07d610919080413e15f service-panel-v1.0.0.tar
Terminal window
cd ~/underdunn-course/downloads
sha256sum service-panel-v1.0.0.tar
mkdir -p ~/underdunn-course/labs/service-panel
tar -xf service-panel-v1.0.0.tar -C ~/underdunn-course/labs/service-panel
cd ~/underdunn-course/labs/service-panel/service-panel-v1.0.0
sha256sum -c CHECKSUMS.sha256
ls

Three files: the executable, TASK.md, and the checksums. There’s no build.sh and nothing to compile. Every other program in this course came with source you built yourself. This one doesn’t.

Run the panel and talk to it a few times before you measure anything.

Hint 1: two routes to every answer

For each protection the section covered, establish whether it’s present in this binary, and find two independent routes to each answer. Don’t make either of them a tool that prints a one-line summary; if you reach for one, use it afterwards to check yourself and note where it agreed with you and where it was silent.

Every protection here left two kinds of trace: something the linker recorded in the file’s structure, and something the compiler emitted into the instructions. Those are your two routes and they check each other.

For one of them there’s a third route needing no tools at all. Run the program twice and read what it tells you.

Hint 2: where each trace lives

The ELF header carries the type. The program headers carry the stack’s permissions and whether the relocations are made read-only. The notes carry the processor features the binary asks for. The disassembly of the function that reads your input carries the canary, in the prologue that stores it and the epilogue that checks it.

Terminal window
readelf -h ./panel | grep Type:
readelf -l ./panel | grep -A 1 GNU_STACK
readelf -l ./panel | grep GNU_RELRO
readelf -d ./panel | grep BIND_NOW
readelf -n ./panel | grep -o 'x86 feature:.*'
objdump -d -Mintel ./panel | grep -c 'fs:0x28'

GNU_RELRO present with BIND_NOW absent is not the same answer as both present. Check which one you have.

Hint 3: advertised, supported, enabled

One entry in your inventory is a request the binary makes, not a protection you’ve shown it has. Three separate things are true or not true about it, and they have to stay separate: what the binary advertises, what the platform supports, and what the running thread actually enabled.

Terminal window
readelf -n ./panel | grep -o 'x86 feature:.*'
grep -o 'user_shstk' /proc/cpuinfo | sort -u
grep -E '^x86_Thread_features' /proc/PID/status

Use the panel’s PID, not the shell’s; it blocks on its first read, which keeps it alive long enough to look. Check each command beside its output, including where the output was empty, because “nothing printed” is only evidence if someone can see what you asked. If the status field doesn’t exist on your kernel, that’s a limit on your evidence rather than a negative result.

Hint 4: the most recent technique is the wrong one

What you learned last will fail here, and Hint 2 already established why in a single line of output.

One protection here is present, doing exactly its job, and still won’t stop you, because you got around it two labs ago. The panel echoes your note with %s, which stops at the first zero byte, and you know which value begins with one.

Terminal window
objdump -d -Mintel ./panel | sed -n '/<panel>:/,/^$/p'
nm ./panel | grep -E ' T | B '
ROPgadget --binary ./panel --only 'pop|ret' | grep 'pop rdi'

Subtract the buffer’s offset from rbp from the canary’s; the saved return address is two slots past. Leak with one byte more than that distance, then send filler, the canary, filler for the saved frame pointer, and six chain slots ending in exit@plt. commission prints for one argument value only, and the comparison at the top of the function names it.

Hint 5: the row that is easiest to get wrong

“I defeated this protection” and “this protection was not running” produce identical transcripts. They are not the same finding.

A row saying “canary: enabled” is a tool’s output with the tool’s name removed. A row that supports a conclusion names the evidence, says what it establishes, and says what it leaves open. For the one you got past, the useful sentence isn’t that it failed; it’s what it cost you, and what it would have cost if the program hadn’t handed you the value.

The whole route

The ELF type is EXEC, so nothing moves and a solver that computes a base is doing arithmetic on zero.

The canary is present and active, so leak it: one byte past the distance from your input to it, then rebuild as a zero followed by the seven returned bytes. The stack is non-executable, so the payload is a chain rather than instructions of your own. The binary isn’t stripped, so nm names commission and exit@plt, and a gadget scan finds pop rdi ; ret.

Filler, canary, frame-pointer filler, then the gadget, the argument commission wants, commission, the gadget again, your exit status, exit@plt.

The shadow-stack note in the ELF is an advertisement. Whether the thread you ran had it enabled is a separate question and, on most course VMs, unanswerable from the status field, which makes “unknown” the correct entry rather than a gap.

Paste exactly the line the panel 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.

The permit confirms one line reached standard output. It establishes nothing about the inventory, which is the part of this lab that’s actually new.

The flag checker should accept the permit, and the program should exit cleanly. Use your inspection to distinguish a protection present in the binary from one enabled for the running thread. If runtime support is unavailable, that part remains unknown. A successful run does not by itself tell you which protection was absent or bypassed.

If something looks wrong

The panel won’t run. Check the mode with ls -l. It ships executable; if your extraction stripped that, restore it with chmod +x ./panel and reverify the checksum.

Your solver from the last lab computes a base and then fails. Intended, and not a defect. Read the ELF type again, then work out which lines of that solver are now doing nothing.

It works most of the time and occasionally aborts. Roughly one run in thirty, one of the seven bytes you’re leaking is itself 0x00, so the echo stops early and you rebuild from six real bytes and one belonging to the next prompt. Count what came back rather than assuming, and start a fresh process when it’s short.

The chain stops at its first return. Check /proc/cpuinfo for user_shstk and the thread’s x86_Thread_features. If shadow stacks are enabled for that thread, your answer to Hint 3 differs from the one most learners will see and yours is the more interesting result. Interpret it as a result for the thread you inspected; other configurations can behave differently.