MicroCorruption CTF (part 1)
So ages back I meant to write up a bunch of notes on the Matasano series, but they're not ready yet because writing isn't as fun as hacking stuff and because I've mostly got enough crypto basics sorted in my head, it's not so useful for me personally.
On the other hand I've been trying to improve on my CTF game generally and saw someone doing the (published: 2013) Matasano Microcorruption CTF 
I'm not going to write up all of the challenges, particularly the early ones in the series the tutorial gives a pretty good introduction to, and I expect (while numerous people in the previous 3 years have done writeups of the whole lot) it's more benefit to people to actually just look at the ealier problems and work them out themselves (and didn't involve much on my part either).
- Each level the software, the hardware or both are updated.
- Two different Hardware Security Module configurations in the game (along with a complete lack of HSM). Understanding how these behave distinctly to each other is critical to solving a lot of the problems.
- You can learn quite a lot by closely reading the manual. if you're used to IA-32 some of the instructions and patterns may seem a bit odd, but actually once you've read up on it it's much much simpler to hack in many ways.
If you want to have a go at it yourself, (and if you are interested in learning to read and exploit binaries you probably should as the graded exercises and simple environment make it much more forgiving than a full scale PC architecture program) bear in mind, there are SPOILERS from here on in.
Skipping quickly over the first few
- New Orleans and Sydney: Strongly reminiscent of the tutorial
- Hanoi: Backdoor/OF - read the code for
CMPs. First HSM-1.
- Cusco: My First Stack Smash and PC Hijack
- Reykjavik: Military Grade Encryption™ (main difficulty caused by minimalist debugger)
- Whitehorse: First HSM-2.
Whitehorse and a quick aside on HSM-1 vs HSM2
HSM1 and HSM2 Serve similar purposes -- offloading security properties of the password verification somewhere software faults can't reach the secret data.
HSM1 provides a secure password store which the software can query to check if a given password is valid before unlocking the deadbolt.
HSM2 augments the secure password store with direct control of the deadbolt itself. The main insight needed here is what does that mean for the attack surface?
Without HSM-1 and HSM-2 there other examples primarily involve either hardcoded keys or some similar form of backdoor.
With HSM-1 as well as looking for back doors the fact that password checking and unlocking are dealt with separately from each other allows us opportunities to interfere with the return value of the validity check before the unlocking code uses it to decide whether the deadbolt should be released.
On the other hand for the HSM-2, there is no such intermediate value to tamper with.
However the Unlocking Interrupt
7F is still available in user-space, so
if we can gain control of
PC, and can run our own code, we can call the
This is exactly what we do for Whitehorse.
<login> add #0xfff0, sp mov #0x4470 "Enter the password to continue.", r15 call #0x4596 <puts> mov #0x4490 "Remember: passwords are between 8 and 16 characters.", r15 call #0x4596 <puts> mov #0x30, r14 mov sp, r15 call #0x4586 <getsn>
Routinely in the disassembly, subtractions happen by adding a Two's Complement format,
signed negative, native word to a register. In this case,
add #0xfff0 is really
another way of saying
sub #0x10. If you're familiar with assembly from other
platforms this is pretty clearly setting up space on the stack to store local variables.
Specifically 16 (0x10) bytes.
But immediately visible below this, is a call to
getsn() to receive up to
48(0x30) bytes from the user. Filling the password buffer with the traditional overflow
debugging payload of as many 'A's as you can fit in the entry point causes us to see
insn address unaligned. Looking at
PC, we see the
two 'A's (4141) in the address. So far very similar to Cusco. However, we're up against
the new improved HSM-2, so we can't just set
PC to the unlock function. We'll
have to make our own. Because there's no ASLR in place, our stack will exist in the same
place every time, so we can just use the assembler to assemble
push 0x7f; call INT;,
place this at the start of our buffer, and place the address that our buffer is saved to
in the word that overwrites the return pointer on the stack (i.e. the word after our local
variable is stored). The password itself fails, but when the login function returns, it
loads our return pointer and executes our unlock code.
Weird behaviour here.
452c: 3150 eeff add #0xffee, sp 4530: f140 d200 1100 mov.b #0xd2, 0x11(sp)
We're told we're allowed a password of up to 16 characters. This is policed by setting
up 18 bytes of stack room, and then setting the 17th byte to a hardcoded value
0xd2) which is checked later to make sure the user hasn't overwritten it. I
guess this could be considered a sort of stack canary?
In any case we can mount a trivial overflow by making sure that when we overflow our variable to hijack the program counter, we set all the preceding bytes to d2 (saving the effort of worrying too much about which offset it's at).
Getting a little harder. We aren't writing directly to the stack any more, but to an
address space somewhat further away. Our input is then being
the stack. One feature of
strcpy is that it expects C-strings which terminate
with a null (
Solving this is largely the same as Whitehorse, except the null byte restriction requires us to be a bit more creative creating the payload. Pushing 0x007f onto the stack to unlock contains a null. However we can xor 0x007f with another value,
push $(0x007f ^ $val) xor $val, SP(0) call <INT>
Which works just fine.
This exercise has a far more complex checking mechanism which took me a while longer
to understand. Similar to numerous previous exercises,
getsn is called with
a much larger value than is necessary, or even fitting for the spaces the user input
is being stored (0x63 bytes). Further more there are some odd assignments early on in
the login program:
mov.b #0x0, -0x6(r4) mov.b #0x8, -0x19(r4) mov.b #0x10, -0x18(r4)
Experimentally throwing large inputs at the program, we find a couple of tight character counting loops which then try to compare the number of characters counted against these values. The length can be between 0x8 (8) and 0x10 (16) characters as described in the service banner.
However, we're perfectly capable of overflowing both of these inputs, and in the process re-writing the parameters for input size as well as the return pointer (which ultimately hands us our win).
We also need to set our second input to a smallish value within the parameters we have set: Before our return pointer is loaded, if loops counting string lengths fail tests, we're automatically failed, and our return pointer never gets loaded. Instead I created a very long username, a short password, and reversed the order of the max and min values so my counts needed to be higher than a big number or smaller than a lower number.
Because this is on HSM-1, all we need to do it control the program counter and
send it to the
Printf format string vuln! 
getsn's some user data, strcpy's it somewhere apparently safe,
printf's it directly back to the screen after checking the
HSM-1 module to see if it is valid.
This version of
printf isn't as fully featured as ones
you'd normally see in C and related languages, but that's okay. Crucially, it
implementes %n. Our payload consists of the (little endian encoded) address of the
validity check byte, followed by
%x (to bump the printf read pointer along
a word so it points to our target address) followed by
%n which instructs
printf to write the number of chars printed so far to that byte.
Since any number other than 0 is recognised as a success, this is all we need to pop the lock.
|||Arguably it's not actually a CTF, as there are no flags, and it's more of a standing wargame anyway. But it does have some fun challenges nonetheless.|
|||I knew about these but I never used one in practice before. This tutorial helped me a lot understanding the practice: https://crypto.stanford.edu/cs155/papers/formatstring-1.2.pdf|