Microcorruption - Addis Ababa#
Microcorruption gives us a debugger and a password to unlock a device, our job is to find the password reading through assembly code and using some reverse engineering tricks. Each challenge is named after a city, each one more difficult than the previous one.
If you didn’t solve the challenge for yourself, I recommend that you stop reading, solve the challenge and check my solution afterwards, do not spoil the challenge and have fun.
How to solve it#
First of all, let’s check the main function:
4438 <main>
4438: 3150 eaff add #0xffea, sp
443c: 8143 0000 clr 0x0(sp)
4440: 3012 e644 push #0x44e6
4444: b012 c845 call #0x45c8 <printf>
4448: b140 1b45 0000 mov #0x451b ">> ", 0x0(sp)
444e: b012 c845 call #0x45c8 <printf>
4452: 2153 incd sp
4454: 3e40 1300 mov #0x13, r14
4458: 3f40 0024 mov #0x2400, r15
445c: b012 8c45 call #0x458c <getsn>
4460: 0b41 mov sp, r11
4462: 2b53 incd r11
4464: 3e40 0024 mov #0x2400, r14
4468: 0f4b mov r11, r15
446a: b012 de46 call #0x46de <strcpy>
446e: 3f40 0024 mov #0x2400, r15
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 $+0x8 <main+0x5e>
4490: b012 da44 call #0x44da <unlock_door>
4494: 053c jmp $+0xc <main+0x68>
4496: 3012 1f45 push #0x451f
449a: b012 c845 call #0x45c8 <printf>
449e: 2153 incd sp
44a0: 0f43 clr r15
44a2: 3150 1600 add #0x16, spSo, let’s break down what it does:
- Prints on screen the message asking for username:password with
<printf> - Set the limit of characters of input to
0x13and define input position to be written in memory to be0x2400 - Popup prompt asking for username:password with
<getsn> - Copy input string from
0x2400to0x3c6eusing<strcpy> - Test if the password is valid with
<test_password_valid> - Reprint our input to screen using
<printf> - Add some
0xato the stack frame with<putchar> - If the bytes at
0x3c6cis zero, print that the password is incorrect. - If the bytes at
0x3c6cis not zero, unlock the door.
Well, with this information, let’s check the possibilities:
How long the input goes#
As it was said, we are limited to 0x13 characters of input (or 19 characters in decimal).
Maybe we can use the return at <main> to redirect the return to the <unlock_door> function. This will make a common solution, but maybe it will work.
But, there’s a problem. If we check the <main> function, no return call is made, it just “bleeds” to the next function <__stop_progExec__>, making the program halt. So, this is not a possibility.
The Odd in the house : printf#
This is an uncommon function. Most of the challenges before this one, it was used <puts> to print the strings and information, so why change it?
This makes me think that the solution is on this function. This is a common/famous function in the C library, so we are dealing with a C compilation? Let’s check the full printf function:
45c8 <printf>
45c8: 0b12 push r11
45ca: 0a12 push r10
45cc: 0912 push r9
45ce: 0812 push r8
45d0: 0712 push r7
45d2: 0412 push r4
45d4: 0441 mov sp, r4
45d6: 3450 0c00 add #0xc, r4
45da: 2183 decd sp
45dc: 1b44 0200 mov 0x2(r4), r11
45e0: 8441 f2ff mov sp, -0xe(r4)
45e4: 0f4b mov r11, r15
45e6: 0e43 clr r14
45e8: 0b3c jmp $+0x18 <printf+0x38>
45ea: 1f53 inc r15
45ec: 7d90 2500 cmp.b #0x25, r13
45f0: 0720 jnz $+0x10 <printf+0x38>
45f2: 6d9f cmp.b @r15, r13
45f4: 0320 jnz $+0x8 <printf+0x34>
45f6: 1f53 inc r15
45f8: 0d43 clr r13
45fa: 013c jmp $+0x4 <printf+0x36>
45fc: 1d43 mov #0x1, r13
45fe: 0e5d add r13, r14
4600: 6d4f mov.b @r15, r13
4602: 4d93 tst.b r13
4604: f223 jnz $-0x1a <printf+0x22>
4606: 0f4e mov r14, r15
4608: 0f5f add r15, r15
460a: 2f53 incd r15
460c: 018f sub r15, sp
460e: 0941 mov sp, r9
4610: 0c44 mov r4, r12
4612: 2c52 add #0x4, r12
4614: 0f41 mov sp, r15
4616: 0d43 clr r13
4618: 053c jmp $+0xc <printf+0x5c>
461a: af4c 0000 mov @r12, 0x0(r15)
461e: 1d53 inc r13
4620: 2f53 incd r15
4622: 2c53 incd r12
4624: 0d9e cmp r14, r13
4626: f93b jl $-0xc <printf+0x52>
4628: 0a43 clr r10
462a: 3740 0900 mov #0x9, r7
462e: 4a3c jmp $+0x96 <printf+0xfc>
4630: 084b mov r11, r8
4632: 1853 inc r8
4634: 7f90 2500 cmp.b #0x25, r15
4638: 0624 jz $+0xe <printf+0x7e>
463a: 1a53 inc r10
463c: 0b48 mov r8, r11
463e: 8f11 sxt r15
4640: b012 5045 call #0x4550 <putchar>
4644: 3f3c jmp $+0x80 <printf+0xfc>
4646: 6e48 mov.b @r8, r14
4648: 4e9f cmp.b r15, r14
464a: 0620 jnz $+0xe <printf+0x90>
464c: 1a53 inc r10
464e: 3f40 2500 mov #0x25, r15
4652: b012 5045 call #0x4550 <putchar>
4656: 333c jmp $+0x68 <printf+0xf6>
4658: 7e90 7300 cmp.b #0x73, r14
465c: 0b20 jnz $+0x18 <printf+0xac>
465e: 2b49 mov @r9, r11
4660: 053c jmp $+0xc <printf+0xa4>
4662: 1a53 inc r10
4664: 1b53 inc r11
4666: 8f11 sxt r15
4668: b012 5045 call #0x4550 <putchar>
466c: 6f4b mov.b @r11, r15
466e: 4f93 tst.b r15
4670: f823 jnz $-0xe <printf+0x9a>
4672: 253c jmp $+0x4c <printf+0xf6>
4674: 7e90 7800 cmp.b #0x78, r14
4678: 1c20 jnz $+0x3a <printf+0xea>
467a: 2b49 mov @r9, r11
467c: 173c jmp $+0x30 <printf+0xe4>
467e: 0f4b mov r11, r15
4680: 8f10 swpb r15
4682: 3ff0 ff00 and #0xff, r15
4686: 12c3 clrc
4688: 0f10 rrc r15
468a: 0f11 rra r15
468c: 0f11 rra r15
468e: 0f11 rra r15
4690: 1a53 inc r10
4692: 079f cmp r15, r7
4694: 0338 jl $+0x8 <printf+0xd4>
4696: 3f50 3000 add #0x30, r15
469a: 023c jmp $+0x6 <printf+0xd8>
469c: 3f50 5700 add #0x57, r15
46a0: b012 5045 call #0x4550 <putchar>
46a4: 0b5b add r11, r11
46a6: 0b5b add r11, r11
46a8: 0b5b add r11, r11
46aa: 0b5b add r11, r11
46ac: 0b93 tst r11
46ae: e723 jnz $-0x30 <printf+0xb6>
46b0: 063c jmp $+0xe <printf+0xf6>
46b2: 7e90 6e00 cmp.b #0x6e, r14
46b6: 0320 jnz $+0x8 <printf+0xf6>
46b8: 2f49 mov @r9, r15
46ba: 8f4a 0000 mov r10, 0x0(r15)
46be: 2953 incd r9
46c0: 0b48 mov r8, r11
46c2: 1b53 inc r11
46c4: 6f4b mov.b @r11, r15
46c6: 4f93 tst.b r15
46c8: b323 jnz $-0x98 <printf+0x68>
46ca: 1144 f2ff mov -0xe(r4), sp
46ce: 2153 incd sp
46d0: 3441 pop r4
46d2: 3741 pop r7
46d4: 3841 pop r8
46d6: 3941 pop r9
46d8: 3a41 pop r10
46da: 3b41 pop r11
46dc: 3041 retThis is a long function and kinda complex (boring), because it jumps from one place to another with some checks and others without checks.
So, how are we going to analyse this function? Well, first we are going to check the ramifications of this function, in another words, let’s list the “Compare” (cmp) lines.
cmp.b #0x25, r13=> Compares r13 with literal0x25(%)cmp.b @r15, r13=> Compares r13 with @r15cmp r14, r13=> Compares r13 with r14cmp.b #0x25, r15=> Compares r15 with literal0x25(%)cmp.b r15, r14=> Compares r15 with r14cmp.b #0x73, r14=> Compares r14 with literal0x73(s)cmp.b #0x78, r14=> Compares r14 with literal0x78(x)cmp r15, r7=> Compares r15 with r7cmp.b #0x6e, r14=> Compares r14 with literal0x6e(n)
We can start checking the literals ASCII characters, those are %, s, x, n. As we talked before, these are common terms for C developers.
Specifier Characters#
You can check the details in the documentation Here, but we are dealing with:
%s: String of Characters%x: Unsigned hexadecimal integer%n: Nothing is printed and the number of characters written so far is stored in the pointed location
So, making an example with %s :
printf ("%s text here \n", "A string");
// | |________|
// +----------------------
// output : "A string text here"
We get the position of an argument, and replace into the original string. This works as well with %x :
printf ("100 decimal in hex : %x", 100);
// output : "100 decimal in hex : 64"
This one takes an information and read as hexadecimal value. For last the %n specifier:
// create an int variable
int val;
// half A and half B, store %n into the variable val
printf("AA AA A %n BBBBBBBBBBBBB\n", &val);
// print the value of val
printf("val = %d \n", val);
// output : "val = 8"
The output is 8 because %n is taking the length of characters before the invocation. It’s counting everything before it (“AA AA A “) and storing into val, making it equals 8. Everything after %n is ignored, so no counting for the ‘B’ section.
Knowing that, let’s make some experiments.
I tried to make some random inputs, something like:
AAAAAA%s%sAAA
AAA%x%x%x%x%x
%x%s%n%s%xAnd so on, until I tried:
AA%x%n%n%nThis popped an error (“load address unaligned: 4141”) and the program halted in the middle of <printf>. This was interesting, my assumptions was right, it was something about printf. I didn’t know much about C, so I looked for vulnerabilities in printf function. Here’s what I found at OWASP website:
Below are some format parameters which can be used and their consequences:
•"%x" Read data from the stack
•"%s" Read character strings from the process' memory
•"%n" Write an integer to locations in the process’ memoryThis made me think, what if I used this to manipulate the stack and memory?
Modifying Memory and %x%n#
At the start of <printf> function, the registers r11, r10, r9, r8, r7 and r4 values are being pushed to the stack, these values will be used afterwards, maybe some input can manipulate the outcome.
What we can see with our last input (AA%x%n%n%n) the value unaligned was “4141”, which is, in ASCII, “AA”. So it just tried to modify the memory value at 0x4141. Ok, maybe with that we can modify some value.
Remember, to call the <unlock_door> function, the bytes at 0x3c6c must not be zero. Maybe we can modify this address? Let’s try with the following input:
l<%x%n%n%nThe l< is a representation of 0x6c and 0x3c bytes from the ASCII table, which is our target address corrected for endianness.
With that, the value at 0x3c6c is not zero anymore, making the door open.
I tried to clean the input, so the final input I used to open the door was:
l<%x%n