Bufcore was a binary service written for a custom CPU that implemented a password protected key-value store. As already suggested by the service name, bufcore suffered from a buffer overflow vulnerability that enabled attackers to read other users’ secret without knowing their password.
On start, bufcore offered a simple text-based menu allowing the user to store a new key, retrieve an existing key, or exit.
Choose an action 1) Store key 2) Receive key 3) Exit Action:
When storing a key, bufcore would ask for a
password, and the value to store:
Action: 1 You want to store a key Enter key: my_key Enter password: secret_password Enter value: my_secret OK: Your value was stored.
Similar, when receiving a key, bufcore would ask for a
key and a
If the password was correct, the stored value would be output:
Action: 2 You want to receive a key Enter key: my_key Enter password: secret_password OK: my_secret
Otherwise, an error message is displayed to the user
Action: 2 You want to receive a key Enter key: my_key Enter password: WRONG_PW ERROR: WRONG PASSWORD
Password and value are both stored in a file named
<key> as null-terminated strings, each preceded by a big-endian 16-bit field encoding their length
00000000: 0010 7365 6372 6574 5f70 6173 7377 6f72 ..secret_passwor 00000010: 6400 000a 6d79 5f73 6563 7265 7400 d...my_secret.
Disassembling the service
Bufcore came as two files, a compiled python file
bufcore.pyc and a data-file
a.out, that would be passed to the python file as the first argument.
Luckily, uncompyle6 was very helpful in decompiling python bytecode into something readable, and it became clear that the python file did not implement the bufcore service per se, but was just an emulator for a custom 16-bit CPU named
D*Core, a CPU design apparently used for teaching at Hamburg University.
D*Core features 16 registers and a 16-bit instruction encoding, where the first byte is usually the opcode and the second byte holds two 4-bit arguments, which, depending on the instruction, are either taken as register indices or 4-bit immediate values.
Branch and call instructions use (signed) 12-bit immediate values, where the lower 4 bits of the opcode function as the upper 4 bits of the branch/call target.
The emulator initializes register
r14 as the stack pointer (set to
0xf000 initially), register
r15 is used as the link register and holds the return address for call instructions.
Next to this, the emulator also features eight syscalls:
To understand the actual service implementation we thus set out to build a disassembler for
D*Core, which we hacked together based on the decompiled emulator code.
Vulnerability: Buffer overflow
Toying around with the service, we noticed that sending a long password when receiving a key would crash the service with the following error message:
Action: 2 You want to receive a key Enter key: my_key Enter password: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ERROR: WRONG PASSWORD EXCEPTION: ILLEGALINSTRUCTION 0x6163 0x0000
0x6163 is exactly
0x6161 + 0x2, i.e., two bytes from our password plus the instruction size.
It thus appears that we have a buffer overflow that lets us control the instruction pointer.
After some reading and a lot of manual annotations of the disassembly, we found the function responsible for password checking that contained the overflow.
Here, the stack pointer (
r14) is first decremented by a total of 17 (
0x2), however, the buffer size passed to the
read function (at
r1 is set to
1 << 0xc = 4096 bytes, which will conveniently let us overwrite the saved return address that was placed there just before.
checkpw function is called only once inside the receive key handler, after the password and value have been read from the file in four steps (password length, password, value length, value).
If we the password check succeeds, the stored value is then printed at
0x36d, otherwise a branch is taken to the end of the function.
Exploit attempt No 1
Since we would want to output the flag even if we do not know the correct password, our first exploit attempt was overwriting the return address with
0x35d, which should jump just behind the password check and print the flag.
However, this did not quite work to plan:
Choose an action 1) Store key 2) Receive key 3) Exit Action: 2 You want to receive a key Enter key: my_key Enter password: aaaaaaaaaaaaaaaaa\x03\x5d ERROR: WRONG PASSWORD OK: Choose an action 1) Store key 2) Receive key 3) Exit
We can see that jumping to
0x35d does in fact work, as we can see that the string
OK is printed as expected.
However, no flag is printed afterwards.
Digging a little deeper into the disassembly, we found that the input routine would always add a null byte to the end of the input. This means that the first byte on the stack after our return address will be set to 0. Unfortunately, the first byte after the return address is also the first byte of the flag, which thereby effectively becomes an empty string.
Exploit attempt No 2
As by now there were less than 30 minutes left in the game, we decided to take another approach: shellcode.
After all, to fix the null byte issue we only have to adjust the pointer in
r6 by one and call the print function again.
Calling the print function turned out to be the harder part, as most call, branch, and jump instructions take a relative offset.
We therefore resorted to building the address of the print function (
0x487) into a register and jumping to the register instead:
Luckily, our 16 byte shellcode just fits into the 17 bytes we have before the return address. However, actually jumping to our shellcode turned out harder than expected, as the address of our buffer will depend on the stack contents, especially the password and the flag, which were both read onto the stack before. After some local bruteforcing we managed to find a working offset and, with only a few minutes of the game left, were desperate to run our exploit to steal some flags. However, at this time the game server had apparently had some issues, which meant that no flag IDs were available for the service (which would have been the keys for which to steal flags) until the end of the game. We should admit though that we had tested our stack offset only against a local test file, and therefore did not work against actual game server files…
An embarrassingly simple exploit
Looking at the service a little longer after the end of the CTF we managed to find an even easier exploit, that does not require any shellcode: Simply overwriting the return address with
0x4a1 (or any other address holding a
SYSCALL) is sufficient and will print (almost) its entire memory.
Why does this work?
r0was set to
1to indicate that we entered the wrong password. However,
1is also the syscall number for writing.
- In the password-comparison loop,
r1is used to hold the current character of the actual password.
r1is therefore < 256 and thus below our stack.
- The same loop also uses
r2to point to the current password character, which is located on the stack. As the password is placed onto the stack before the flag, even if
mem[:r2]should still contain the flag.
Combined, this means that we will dump the entire memory
mem[r1:r1+r2], including the flag.
Patching the service
While the service could be patched by replacing
which would require only 3 nibbles changed, we took the easy way out:
As it was very clear from the beginning that the service had some vulnerability and the service logic was quite simple to understand, we just replaced it with a python reimplementation before starting our reversing efforts.
Bufcore was a lot of fun (I would even go out an say the coolest service of the entire CTF), as it used a non-standard architecture which made reversing and even “simple” buffer overflow a challenging task. While there were a few issues with the game infrastructure surrounding it (broken startup scripts, wrong and missing flag IDs) and no one managed to steal a single flag during the game we are looking forward to more services like this in the future.