Microcorruption CTF Cusco Write-up
- 8 minsSummary:
This is a write-up of my solution to the Microcorruption CTF challenge “Cusco” (LOCKIT PRO r b.02
).
This version claims to fix the conditional flag overwrite issue that we exploited in the last challenge:
This indeed appears to be the case. Let’s check out the entire program and then see if we can figure out what’s going on:
So I’ll admit, this one took me a little longer to figure out. Let’s go through the basic steps I took to see where I got stumped:
main()
4438 <main>
4438: b012 0045 call #0x4500 <login>
All main()
does is make a call to login()
. Let’s follow login()
…
login()
4500 <login>
4500: 3150 f0ff add #0xfff0, sp
4504: 3f40 7c44 mov #0x447c "Enter the password to continue.", r15
4508: b012 a645 call #0x45a6 <puts>
450c: 3f40 9c44 mov #0x449c "Remember: passwords are between 8 and 16 characters.", r15
4510: b012 a645 call #0x45a6 <puts>
4514: 3e40 3000 mov #0x30, r14
4518: 0f41 mov sp, r15
451a: b012 9645 call #0x4596 <getsn>
451e: 0f41 mov sp, r15
4520: b012 5244 call #0x4452 <test_password_valid>
4524: 0f93 tst r15
4526: 0524 jz #0x4532 <login+0x32>
4528: b012 4644 call #0x4446 <unlock_door>
452c: 3f40 d144 mov #0x44d1 "Access granted.", r15
4530: 023c jmp #0x4536 <login+0x36>
4532: 3f40 e144 mov #0x44e1 "That password is not correct.", r15
4536: b012 a645 call #0x45a6 <puts>
453a: 3150 1000 add #0x10, sp
453e: 3041 ret
After a few calls to puts()
to print out the password prompt message, the code makes a call to test_password_valid()
. Upon returning, login()
tests the value of r15
(at 0x4524
). If r15
is not 0
, we skip the jz
instruction at 0x4526
and proceed to unlock_door()
. Let’s pay attention to r15
while test_password_valid()
is executing…
test_password_valid()
4452 <test_password_valid>
4452: 0412 push r4
4454: 0441 mov sp, r4
4456: 2453 incd r4
4458: 2183 decd sp
445a: c443 fcff mov.b #0x0, -0x4(r4)
445e: 3e40 fcff mov #0xfffc, r14
4462: 0e54 add r4, r14
4464: 0e12 push r14
4466: 0f12 push r15
4468: 3012 7d00 push #0x7d
446c: b012 4245 call #0x4542 <INT>
4470: 5f44 fcff mov.b -0x4(r4), r15
4474: 8f11 sxt r15
4476: 3152 add #0x8, sp
4478: 3441 pop r4
447a: 3041 ret
After shifting values inside of various registers/memory, the instruction mov.b -0x4(r4), r15
eventually sets a 0
value into r15
. The instruction sxt r15
(sign extend) is executed on r15
(effectively doing nothing since the value is 0x0
), and soon returns back into login()
, where r15
is tested against 0
. It does not appear that we have any influence over the memory address that’s moved into r15
.
After racking my brain for a while, and continuously stepping through various parts of program, I noticed something interesting…
This version of the code isn’t storing our input at 0x2400
as it was originally, and instead is much closer to the program counter! Notice how close our user input (AAAAAAAAAA
) is to pc
. I wonder if we can overwrite parts of the program code! Let’s try a bunch of A's
and see what happens…
Looks like we can! Another interesting thing to note, I tried to input about 200 or so A's
, but only 48 were copied onto the stack. I suspect maybe during the copy onto the stack, __do_copy_data()
is being invoked and we’re overwriting instructions as we’re in the middle of executing them? I wasn’t able to step the code to this level of precision so I’m not sure. However, this is sort of a moot point because of what happens as a result of inputting so many bytes…
We can control the pc
register. Sweet! Now all we need to do is to figure out the byte offset to reach pc
and figure out a suitable location to jump the execution to. After some experimenting with input strings of various lengths and keeping an eye on the pc
register, we can see that the 17th and 18th byte of our input will overflow into the pc
register. Let’s try overwriting pc
with 0x4528
, the call #0x4446 <unlock_door>
instruction inside login()
which comes right after the jz
instruction we were trying so hard to bypass…
Flag (mouse over to reveal)
414141414141414141414141414141412845