Secure Exam Browser

Writeup for Secure Exam Browser (Rev) - K17 CTF (2025) πŸ’œ

Description

I love academic misconduct

Solution

We'll follow the usual reversing methodology; basic file checks, static analysis, dynamic analysis.

Basic File Checks

64-bit binary, not stripped (easier to reverse).

file seb

seb: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=c07f6718ca003c40c5170f23910830a91e90633e, for GNU/Linux 3.2.0, not stripped

Running the program, it asks us for a password. Apparently they changed it from universal default 😸

./seb

Welcome to Secure Exam Browser
Enter Password: meow
ERR: another process is running!

ltrace and strace don't give a quick win, over to ghidra.

Static Analysis

I started by renaming all the variables in the main function. I still do this out of habit (methodology), but honestly it's easier and faster to use ChatGPT. From the syntax, I can see the application was C++ and while the ghidra output cannot be compiled directly, ChatGPT can re-create it nicely.

main.cpp

decode_flag.cpp

It looks like there's a lot going on in the decode_flag function but hey, did you notice this line in main? (this is the [manually] refactored/commented pseudocode from ghidra)

Dynamic Analysis

Why bother reversing that whole function, when we can just set a breakpoint after it? However, PIE is enabled on this binary - we don't yet know the address to set the breakpoint at, only the offset (0x5106). We could run GDB and break on the main function (or first instruction) and then check the address of the instruction we want to break at.

It's been too long since I did rev/pwn but I could have sworn there was a better way to do this in pwndbg (the GDB plugin). I spend a few minutes asking ChatGPT but it seems incapable of reading the docs (???), so I have to do it myself! I quickly find what I was looking for 😌

Perfect. We can use breakrva (or brva) to set a breakpoint at the desired offset. First run the program, then hit ctrl + C to pause execution.

I try to enter the password but we never reach the breakpoint.

That pesky error again! I didn't see that string in the two functions, let's find it: search -> search for strings

Check the references to string.

We quickly find the problematic exit call.

Binary Patching

The function doesn't seem relevant to the challenge, but is preventing us from proceeding. What if we just patch the binary? The function starts at offset 0x3e67, let's patch that instruction.

Just right click -> patch instruction and change the code to return on entry.

The pseudocode for the op() function now looks like this.

Save the binary and go to file -> export program and save as Original File with Export User Byte Modifications checked.

Finally, it is checking the password! Let's open the patched binary in pwndbg and return to where we were earlier.

Hmmm, it still never triggers. Let's move the breakpoint a bit earlier (0x50dc)

Run the program and this time when we enter the password, GDB hits a breakpoint and we have our flag 😎

Flag: K17{i_heard_that_it's_impossible_to_re_c++!}

Last updated