Microcorruption - Whitehorse#
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: b012 f444 call #0x44f4 <login>The only thing it does is to call the login function, so let’s check that too:
44f4 <login>
44f4: 3150 f0ff add #0xfff0, sp
44f8: 3f40 7044 mov #0x4470 "Enter the password to continue.", r15
44fc: b012 9645 call #0x4596 <puts>
4500: 3f40 9044 mov #0x4490 "Remember: passwords are between 8 and 16 characters.", r15
4504: b012 9645 call #0x4596 <puts>
4508: 3e40 3000 mov #0x30, r14
450c: 0f41 mov sp, r15
450e: b012 8645 call #0x4586 <getsn>
4512: 0f41 mov sp, r15
4514: b012 4644 call #0x4446 <conditional_unlock_door>
4518: 0f93 tst r15
451a: 0324 jz $+0x8 <login+0x2e>
451c: 3f40 c544 mov #0x44c5 "Access granted.", r15
4520: 023c jmp $+0x6 <login+0x32>
4522: 3f40 d544 mov #0x44d5 "That password is not correct.", r15
4526: b012 9645 call #0x4596 <puts>
452a: 3150 1000 add #0x10, sp
452e: 3041 retSo in a few words, this function:
- Write on screen asking for a password with
<puts> - Ask for a password input with
<getsn> - Make a check if the password is correct with
<conditional_unlock_door> - If the password is correct, write that the password is correct
- If the password is not correct, write that the password is not correct
After all that, we can also see that at line 0x452a there’s an stack pointer add, maybe we could use that. But first let’s check the conditional_unlock_door function:
4446 <conditional_unlock_door>
4446: 0412 push r4
4448: 0441 mov sp, r4
444a: 2453 incd r4
444c: 2183 decd sp
444e: c443 fcff mov.b #0x0, -0x4(r4)
4452: 3e40 fcff mov #0xfffc, r14
4456: 0e54 add r4, r14
4458: 0e12 push r14
445a: 0f12 push r15
445c: 3012 7e00 push #0x7e
4460: b012 3245 call #0x4532 <INT>
4464: 5f44 fcff mov.b -0x4(r4), r15
4468: 8f11 sxt r15
446a: 3152 add #0x8, sp
446c: 3441 pop r4
446e: 3041 retThis function, mostly, just send values around pointers with sp (stack pointer), then, send a 0x7e to the stack and call <INT>:
4532 <INT>
4532: 1e41 0200 mov 0x2(sp), r14
4536: 0212 push sr
4538: 0f4e mov r14, r15
453a: 8f10 swpb r15
453c: 024f mov r15, sr
453e: 32d0 0080 bis #0x8000, sr
4542: b012 1000 call #0x10
4546: 3241 pop sr
4548: 3041 retThe INT function read 2 bytes after the sp (stack pointer) and send it to r14, this is moved around with r15 to then make a bis action with 0x8000 and sr. Just think about the bis command as an or command (0x8000 | sr).
Then, it will call the function at 0x10 (<__trap_interrupt>) to see if the door open or not.
Little Intermission : How exactly the door opens
The trap_interrupt (
<__trap_interrupt>) function deals with the hardware, it sends a message through thesrpointer to check what the hardware will do with the operating system.In our case, the value
ff00is necessary to be set atsrfor the door to be opened, even if the function<__trap_interrupt>is called, it will not open with other values.
Running our code with a random password, we can see that the value of sr at the check function is always 0xfe00, close to what we need, but no right yet, we must need to change this value.
If we are going to change some values, first let’s check how long our buffer overflow input reach in the memory. Let’s try adding a bunch of 6’s.
666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666After inputting and making a step command (s), we can see that our input reach between 0x3cb8 and 0x3ce7.
Now let’s see if the command at login at line 0x452a and 0x452e interlaps with our input, those lines are:
452a: 3150 1000 add #0x10, sp
452e: 3041 retThis lines add 0x10 to the sp and then use the sp position to make a return to the function that called, we must see if this position in sp called by return (ret) is modifiable by our input.
To do that:
- Add a breakpoint at
0x452a, and reach it - Check the position of the
spin the memory - If the position is between
0x3cb8and0x3ce7, we can change the return address.
For our luck, the position of return is at 0x3cc8, a position that our input can change it.
With that information, let’s modify the return to another place, let’s move to 0x451c. After some testing, our input will look like:
666666666666666666666666666666661c45This input will make the jump to the line 0x451c, the “Access Granted” text. Making this jump, our door is still not unlocked. This time, we will try to make the return to the <INT> function, maybe running this function twice will make it better for us to manipulate the flow of the program. Our next input is:
666666666666666666666666666666663245After redirecting to the address 0x4532 (<INT>), the stack will be storing even further, making the value in the r14 and r15 be read again, but now at a position that our input can manipulate. This values will be added to the sr pointer, so maybe we can make the sr to be 0xff00 to unlock the door.
The second read happens 3 bytes after the last return value that we modified. Because of that, we will add 2 zero bytes (0000) to our input and then add the unlock value to the sr (0xff00). The input will be something like:
6666666666666666666666666666666632450000ff00With that, we return again to the <INT> function, add more values to the stack, modify the sr to be 0xff00 to unlock the door.