Microcorruption
Introduction
Microcorruption is a Capture the Flag (CTF) type challenge based around Reverse Engineering and binary exploitation - kind of the stuff you might actually think about as “hacking”. There’s no actual flag as such. The system is a microcontroller so there’s no Linux-fu here either. Instead, your “microcontroller” is controlling a lock, and you defeat a challenge by finding the right sequence of inputs to open the lock.
Microcorruption is publically available and hosted on different servers, I’ve been using the one hosted by the NCC group. Hmmm, interesting people, but it’s a fantastic, intuitive setup.
The MSP430
Rather than being given the usual target for CTFs, aka an x86 processor, we have the MSP430 from Texas Instruments. This means I’ve had to learn a slightly novel syntax, although really it’s fairly intuitive - at least, for assembly. The architecture is 16 bit, with 16 registers. Opcodes are of the form:
opcode src dst
And have the following modifiers:
r => value in register r
@r => value at address in register r
#c => literal value c
&c => value at address c
c(r) => value at address in register r + c
$ => current location
Four of the registers are special purpose. You may see these registers referred to either way:
r0 => pc (program counter) r1 => sp (stack pointer) r2 => sr (signal register) r3 => cg (I forget)
The stack grows downwards, so push x
decrements the stack pointer (by 2), and pop x' increments. This means that
calldecrements and
retincrements. In particular,
ret` is:
mov @sp+, pc
i.e. “jump to one word after where the stack pointer is”. I believe in x86, it’s ebp plus one word (four bytes there).
Useful codes:
b012 addr => call addr
3012 7f00 => push 0x7f (door unlock call)
jc => jump if carry (i.e. if sr has carry bit set)
Calling arguments appears to be passed via registers, starting from r15, NOT the stack.
My Setup
Other than the aforementioned excellent server provided by the NCC group, I used the ipython shell for number crunching and a second tmux pane with neovim for note taking. And, where possible, a physical A4 notepad. Paper helps.
New Orleans
Well, this should be the easiest. We start by looking at , which calls, in order, <create_password>, <get_password>, and <check_password>. The latter looks like the most important part to understand.
Decompiled, it might look something like this:
bool check_password(char[] pw) {
return pw[0] == 0x54 &&
pw[1] == 0x58
//etc
;
}
(We don’t actually know if boolean headers have been equipped)
Convert the hex values into ASCII and you’re done. I’m not even giving you the string for this one.
Sydney
check_password() still exists, but now we’re checking two bytes. This teaches you an important lesson, a trap that I shamefully fell into: that the system is little-endian.
A primer: when we said the MSP430 was 16-bit, that means it operates on 16-bit “words”. A byte is 8 bits (kinda), so 16 bits is two bytes. A byte is represented by 0x00 - 0xff, as we’ve seen. So, which order to put them in? So, whilst 0x27 == 0x27, as a string you will have to feed in 3d27 to match 0x273d.
Final string (in hex): 3d27703667605a22
Reykjavik
This one somewhat oddly calls , which… does a bunch of stuff, before you’ve supplied any input. It then calls 0x2400, an un-named region of code. From the function name, we can assume is doing some decryption, and in fact 0x2400 is NULL before is called, whereas afterward, it has useful stuff.
My approach to this was slow: just step through the debugger until you see a cmp
instruction. The first one is at 0x2448, comparing our first password word to 0x84d9.
Final string (in hex): d984
Hanoi
The first one connected to a Hardware Security Module (HSM), the effect of which is you cannot see all the code that is being executed, only the interface to it.
This took me an embarassingly long time, and a lookup. As a challenge, it also looks very similar to the Narnia OvertheWire challenge #0, which makes it all the more embarassing. I was looking for some way to do a polynomial attack, not a buffer overflow.
Anyway, it is indeed a buffer overflow. After copying up to 48 bytes into 0x2400, login() tests to see if 0x2410 == 0xb1, which it will if check_password is valid. Or if you overflow and set it yourself.
Padding bytes: 16 * 0x41 (or whatever padding character you like) Payload: 0xb1
Final string (in hex): 41414141414141414141414141414141b1
Johannesburg
This system uses a “sentinel bit”, the single char ‘^’, to detect a buffer overflow.
Cusco
<Kronk.jpg>
The first “proper” buffer overflow. We want to call the routine <unlock_door>, which here is at 0x4446. So if we can set pc to that when a subroutin returns, we’re set.
A ret
command is equivalent to:
mov @sp+, pc
i.e. “increment the stack pointer, then move whatever is there to the next byte”.
Our buffer starts at 0x43ee. The call instruction is b012, and <unlock_door> is at 4446, so the code b012 4644
plus appropriate padding should work.
For example, the string (in hex): 46444141414141414141414141414141 jumps us back to the the start.
Final string (in hex): ‘b0124644414141414141414141414141ee43’
Whitehorse
Similar to Cusco, we have a buffer overflow.
getsn() reads 48 bytes, from 36ce <-> 36fe. at the end of a traditional “long string” attack, sp => 36e0
If the stack points to 0x7f when the software interrupt is called, the door will unlock - this is the case even with the HSM v2 (this may not be entirely clear). In this case, is located at 0x4532, so we’ll have to call that.
Push 0x7f onto stack: 3012 7f00 Call : b012 3245 Final string (in hex): ‘30127f00b01232454141414141414141ce36’
Montevideo
Starts with a getsn() with… the normal 0x30 (0d48) chars? writing to 0x2400 <-> 2430 then strcpys to 43ee <-> 441e and… memsets 0x2400 to 0, then it is as before. This defeats our initial shellcode, as strcpy() stops at the first NULL byte (7f00). So, we either need a way to jump to unzeroed memory or a non-zero way to trigger the interrupt.
interestingly, the HSM returns 0x7e00 if you fool strcpy, implying some error return? However, r15 is populated with only the lower bytes, i.e. the error is swallowed.
is located at 0x454c. So we should have:
‘30127f00b0124c45’
What we could do is use the push.b
, 0x7012, so that the argument is 0xXX7f - XX can be anything, so doesn’t have to be zero. To create 0x7f (instead of 0x7f00), we’ll need to increment and crement sp by one.
Alternatively, we could push an interrupt code without nulls, then add or subtract such that the correct value is reached. I chose the following:
0x0180
- 0x0101
--------
0x007f
or, alternatively using registers:
34408001 ; mov #0x0180, r4
34800101 ; sub #0x0101, r1
0412 ; push r4
b012 4c45 ; call <INT>
4141 ; padding
ee43 ; ret
Final string (in hex): 34408001348001010412b0124c454141ee43
Santa Cruz
We’re back to the HSM v1, so a <unlock_door> routine to be called exists, and we now have two sgtrings to enter, instead of just one. Both calls read initially into 0x2404 <-> 0x2468, and are then copied into 0x43a2 and 0x43b5, respectively. Interestingly, the username runs into the password. So, I’m thinking: use the null byte from the username ot provide any nulls we need, then the pw to provide the rest of the code.
Line 45ea, we compare to registers and jump if the carry flag is not set to 45fa.
This version has an routine at 0x444a, so we can call that (b012 444a).
If we pass a long username and 8-char password, the program exits with message “Invalid password length: password too short”. The stack looks like this:
43a2 43b2 43b5 43c5 43cc |————–| |———-| | | | username | 0008 10 | pw | 0000 | 4044 |
And the username does indeed trample all over the stack. The error messages “Invalid Paddwords Length” start at 466a and 4696, respectively.
The two values in the stack between the username and password, 08 and 10 respectively, are used for password length checks. So, username should be:
call code: b012 4a44 (4 bytes) padding: ‘41’ * 13 (17 bytes) pw valid: 01 ff (18 bytes) overwritten by pw: ‘41’ * 23 (36 bytes) return address: a2 43 (44 bytes)
The check for pw string length happens twice, oddly. The second time, it tests for zero at 0x43c6, so we need pw to write up to there.
Password needs therefore to be 17 chars long.
Final string (in hex): b0124a444141414141414141414141414101ff4141414141414141414141414141414141414141414141a243
Final password: BBBBBBBBBBBBBBBBB
Jakarta (unsolved)
Looks pretty similar to Santa Cruz, except username+password length is tested together.
Unlock door is at 444c, so we’ll want b0124c44 as the opcode, and 3ff2 is where the username starts, so we’ll want f23f as the return address. We have a buffer up to 4016. That means 36 chars.
The username is tested against a hard-coded number, so we can’t overwrite that. Username is copied to the stack before password is asked for, and the length of the username is subtracted from the password before it is input. The process is a little odd: the pw is strcpy
’d to the stack before length testing. So, we could overwrite the stack as before if only we could pass a single string of 18 chars in twice, rather than the second being truncated. Including null bytes doesn’t seem to achieve much; since, as mentioned, we strcpy
so no null bytes will make it to the stack.
login() {
char unpw[36];
getsn(0x2402, 0xff);
strcpy(char, 0x2402);
r11 = strlen(0x2401); // note actually strlen(), and operates on the original, but both funcs are NULL terminated
if (r11 >= 0x21) {
exit(0x2400); //"Invalid Password length..."
}
puts("please enter pw");
r14 = 0x1ff & (0x1f - r11);
getsn(0x2402, r14);
strcpy(char[r14], 0x2402);
r11 = strlen(0x2401);
if (r11 >= 0x21) {
exit(0x2400); //same as before
}
//login stuff
}
And username+password is checked similarly. Notably, the null byte between username+password appears to be removed on the stack.
Addis Ababa (cheated)
From reading the description, we’re definitely dealing with a format string vuln here. The basic idea of this is - the standard C print function, printf
, accepts control characters that are accessible not just to the programmer, but the user too. These control characters allow you to read, and even, to a degree, write, to memory.
With initial testing, an input of %x prints nothing, %n prints nothing, and %x%n prints “7825”. Which, it took me embarassingly long to realise, is “%x” in decimal.
Next, my lack of thoroughness really hampered me: although our printf is like a normal printf, it’s not exactly the same. Specifically, the normal trick of something like:
"%255x%$1n"
will not work, as both the width specifier and positional specifier are not supported. We’ve only got %x, %s, %n.
I’m going to be honest, I definitely gave up and used a blog for this - specifically, Jaime Lightfood. In my version, I want to write to 0x3468, and the simple “%x%n” trick otherwise works. That’s probably where I give up so - good luck.