microcorruption - hanoi
Mar 5, 2018 · 5 minute read · Commentsctfexploitassemblymicrocorruptionhanoi
This post is part of the series of solving microcorruption.com ctf challenges which continues from the previous challenge called Sydney. This challenge is titled Hanoi.
If you were to read the description when you enter the challenge, one would see the following towards the bottom:
LockIT Pro Hardware Security Module 1 stores the login password, ensuring users can not access the password through other means. The LockIT Pro can send the LockIT Pro HSM-1 a password, and the HSM will return if the password is correct by setting a flag in memory.
Ok, so mention of a HSM here. Neat! Lets take a look at how that works!
hanoi
This time round, our main
function is quite a bit shorter than the previous programs we have analysed:
4438 <main>
4438: b012 2045 call #0x4520 <login>
443c: 0f43 clr r15
Just a call to login
which in turn has a similar flow to that which we saw in the previous main
functions. A semi-shortened, annotated version of login
is:
4520 <login>
4520: c243 1024 mov.b #0x0, &0x2410
4524: 3f40 7e44 mov #0x447e "Enter the password to continue.", r15
4528: b012 de45 call #0x45de <puts>
; but what if i put in more than 16 characters? ;)
452c: 3f40 9e44 mov #0x449e "Remember: passwords are between 8 and 16 characters.", r15
4530: b012 de45 call #0x45de <puts>
[.. routine to get the password ..]
4544: b012 5444 call #0x4454 <test_password_valid>
4548: 0f93 tst r15
454a: 0324 jz $+0x8
; erm? what is going on here?
454c: f240 5000 1024 mov.b #0x50, &0x2410
4552: 3f40 d344 mov #0x44d3 "Testing if password is valid.", r15
4556: b012 de45 call #0x45de <puts>
; and what about here?
455a: f290 6300 1024 cmp.b #0x63, &0x2410
4560: 0720 jne #0x4570 <login+0x50o>
; grants us access!
4562: 3f40 f144 mov #0x44f1 "Access granted.", r15
4566: b012 de45 call #0x45de <puts>
456a: b012 4844 call #0x4448 <unlock_door>
456e: 3041 ret
; failed
4570: 3f40 0145 mov #0x4501 "That password is not correct.", r15
4574: b012 de45 call #0x45de <puts>
4578: 3041 ret
Some interesting things happening there it seems. I think the next routine to check out is most definitely test_password_valid
. Taking a look at that routine looks as follows:
4454 <test_password_valid>
; move a bunch of stuff around
4454: 0412 push r4
4456: 0441 mov sp, r4
4458: 2453 incd r4
445a: 2183 decd sp
445c: c443 fcff mov.b #0x0, -0x4(r4) ; 0x2400 at this stage
4460: 3e40 fcff mov #0xfffc, r14
4464: 0e54 add r4, r14
4466: 0e12 push r14
4468: 0f12 push r15
; push 0x7d into the stack and prep for a syscall
446a: 3012 7d00 push #0x7d
446e: b012 7a45 call #0x457a <INT>
[.. end this routine ..]
447c: 3041 ret
Hmm ok, a bunch of mov
instructions and other arithmetic and finally syscall with interrupt 0x7d
which is described as “Interface with the HSM-1. Set a flag in memory if the password passed in is
correct.” according to the locks manual.
So not that interesting after all. Looking at login
again, it seems like we would ideally want to have unlock_door
called from login
at 0x456a
to win. But how to get there?
debugging
My static analysis capabilities were pretty much exhausted here, and it was now time for some runtime debugging. I manually stepped through the program, focussing on the test_password_valid
routine. I noticed the password I entered as 12345678
was stored at offset 0x2400
and referenced when the interrupt for the HSM to check was set up.
A few steps through this routine with no obvious ways to fool it, I decided to park it for now and see what happens after test_password_valid
is done. This is now back at login
once test_password_valid
has returned:
; test_password_valid has returned
4552: 3f40 d344 mov #0x44d3 "Testing if password is valid.", r15
4556: b012 de45 call #0x45de <puts>
; what is this cmp doing?
455a: f290 6300 1024 cmp.b #0x63, &0x2410
4560: 0720 jne #0x4570 <login+0x50>
; making the jne not get taken will land us at unlock_door
4562: 3f40 f144 mov #0x44f1 "Access granted.", r15
4566: b012 de45 call #0x45de <puts>
456a: b012 4844 call #0x4448 <unlock_door>
I noticed the cmp.b #0x63, &0x2410
instruction again and realised that 0x2410
is close to the area where my password buffer was being stored in memory. Infact, this was just 16 bytes away from 0x2400
! Now remember that message telling us passwords are supposed to be 8 to 16 characters long? Well, looks like that is because char 17 and 18 forms part of this cmp.b
instruction!
If we can make the aforementioned instruction pass the test checking if the value at that memory address is 0x63
, (ASCII character c
), then we can get past the jne
instruction at 0x4560
, eventually landing us in unlock_door
routine.
So given that the distance to 0x2410
is 16 bytes from 0x2400
where our password buffer is stored, lets see if we can overflow the buffer to 0x2410
.
>>> print('A' * 16 + 'c')
AAAAAAAAAAAAAAAAc
I set a breakpoint at 0x455a
with break 455a
in the debugger and continued the CPU entering AAAAAAAAAAAAAAAAc
as the password when prompted.
After hitting the breakpoint and inspecting the contents of memory address 0x2410
with read 2410
in the debugger reveals that the byte 0x63
is at 0x2410
(thanks to our overflow). This results in the cmp.b
instruction setting the status register to 0x3
(CZ), which in turn means the jump is not taken!
Turns out test_password_valid
was just a decoy and the real vulnerability was a simple buffer overflow.
solution
Enter AAAAAAAAAAAAAAAAc
as ASCII or 4141414141414141414141414141414163
as hex encoded input.
other challenges
For my other write ups in the microcorruption series, checkout this link.