This is a write-up of my solution to the Microcorruption CTF challenge "Addis Ababa" (LOCKIT PRO r b.03).
Right off the bat, in the description of the challenge, we get a hint dropped:
OVERVIEW
- We have verified passwords can not be too long.
- Usernames are printed back to the user for verification.
- This lock is attached the the LockIT Pro HSM-1.
Let's see exactly how the program is going to print back the usernames to the user...
This code is significantly larger than the last one. However, it's very clear that they changed one major thing: the use of printf() instead of puts().
Let's just play around with the inputs and see what the program spits back out...
It looks like whatever input string we give it (e.g. AAA:BBB), the program will print back. All signs are pointing to a format string vulnerability, but let's verify that's actually the case by throwing a few %x's at it and seeing if we get anything interesting back...
Upon inputting AA%x%x, we get the output AA4141, which means that we are reading two 1-byte hex-encoded parameters from the stack (i.e. the A's at the beginning of our input). I won't spend time explaining how format string vulnerabilities work in this write-up, but you can check out scut's whitepaper "Exploiting Format String Vulnerabilities" for a useful guide.
Now let's try and see if we can use %n to write to a location in memory. I'll use the input value BA%x%n to start with. Also, I'll set a breakpoint at memory location 0x46b2 inside of login(), which is the cmp.b #0x6e, r14 instruction. This should allow us to break on the handling of the %n (n == 0x6e) format string parameter and lets us examine the registers and memory during execution.
As you can see, by using the %n parameter, we were able to overwrite the memory address 0x4142 (e.g. BA, the first two bytes of our input) with the value that was in register r10 (i.e. 0x2, the number of bytes written out by printf()).
Now, let's take a step back and see exactly what we need to do to unlock the door...
4472: b012 b044 call #0x44b0 <test_password_valid>
4476: 814f 0000 mov r15, 0x0(sp)
447a: 0b12 push r11
447c: b012 c845 call #0x45c8 <printf>
4480: 2153 incd sp
4482: 3f40 0a00 mov #0xa, r15
4486: b012 5045 call #0x4550 <putchar>
448a: 8193 0000 tst 0x0(sp)
448e: 0324 jz #0x4496 <main+0x5e>
4490: b012 da44 call #0x44da <unlock_door>
4494: 053c jmp #0x44a0 <main+0x68>
4496: 3012 1f45 push #0x451f "That entry is not valid."
449a: b012 c845 call #0x45c8 <printf>
Inside of main(), we can see that after the call to printf() returns, the instruction tst 0x0(sp) will execute. This instruction will test the sp register against 0x0, and will jump over the unlock_door() call if it's zero. Let's set a breakpoint on this instruction and input AAAA and see what the registers look like...
Looks like 0x40c4 has a 0x0 value and indeed prevents the call to unlock_door() from executing. Let's use our format string vulnerability and write a non-zero value to the sp register (location 0x40c4)...
c4402578256e