Rough writeup of my Microcorruption CTF solutions.
tl;dr Just read the password from memory.
I noticed that main
calls create_password
which appears to set the lock's password by moving fixed bytes into the address stored in register r15
. This is probably the password.
447e <create_password>
447e: 3f40 0024 mov #0x2400, r15
4482: ff40 5400 0000 mov.b #0x54, 0x0(r15)
4488: ff40 2700 0100 mov.b #0x27, 0x1(r15)
448e: ff40 3c00 0200 mov.b #0x3c, 0x2(r15)
4494: ff40 3c00 0300 mov.b #0x3c, 0x3(r15)
449a: ff40 4e00 0400 mov.b #0x4e, 0x4(r15)
44a0: ff40 4b00 0500 mov.b #0x4b, 0x5(r15)
44a6: ff40 5000 0600 mov.b #0x50, 0x6(r15)
44ac: cf43 0700 mov.b #0x0, 0x7(r15)
44b0: 3041 ret
Solution:
> break 44b0
> c
> read r15 8
2400: 5427 3c3c 4e4b 5000 T'<<NKP.
The lock password is T'<<NKP
.
tl;dr Read the password from memory again.
According to the manual, this lock version contains a fix for the last problem.
This is Software Revision 02. We have received reports that the prior version of the lock was bypassable without knowing the password. We have fixed this and removed the password from memory.
There's no longer a create_password
function, so instead I focused on check_password
:
448a <check_password>
448a: bf90 7375 0000 cmp #0x7573, 0x0(r15)
4490: 0d20 jnz $+0x1c
4492: bf90 2424 0200 cmp #0x2424, 0x2(r15)
4498: 0920 jnz $+0x14
449a: bf90 3231 0400 cmp #0x3132, 0x4(r15)
44a0: 0520 jne #0x44ac <check_password+0x22>
44a2: 1e43 mov #0x1, r14
44a4: bf90 217c 0600 cmp #0x7c21, 0x6(r15)
44aa: 0124 jeq #0x44ae <check_password+0x24>
44ac: 0e43 clr r14
44ae: 0f4e mov r14, r15
44b0: 3041 ret
This makes four 2-byte comparisons against the password we input, which is stored at address in r15
. If any of these comparisons are not equal, we jump out and get told the password is wrong. My first thought was to just put these 8 bytes together into the hex input 7573242431327c21
, but this did not break the lock.
MSP430 is little-endian, so we just change byte order to get the password: 737524243231217c
.
TIL: that MSP430 is little-endian.
tl;dr Overflow the stack to set a flag that the lock checks.
Another Lockitall firmware update! The manual includes this interesting tidbit:
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.
Running the program, we also notice that there's a reminder that passwords are between 8 and 16 characters, though this appears to not be checked or enforced at all, which is probably important.
Everything happens in the login
function this time:
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>
452c: 3f40 9e44 mov #0x449e "Remember: passwords are between 8 and 16 characters.", r15
4530: b012 de45 call #0x45de <puts>
4534: 3e40 1c00 mov #0x1c, r14
4538: 3f40 0024 mov #0x2400, r15
453c: b012 ce45 call #0x45ce <getsn>
4540: 3f40 0024 mov #0x2400, r15
4544: b012 5444 call #0x4454 <test_password_valid>
4548: 0f93 tst r15
454a: 0324 jz $+0x8
454c: f240 0100 1024 mov.b #0x1, &0x2410
4552: 3f40 d344 mov #0x44d3 "Testing if password is valid.", r15
4556: b012 de45 call #0x45de <puts>
455a: f290 eb00 1024 cmp.b #0xeb, &0x2410
4560: 0720 jne #0x4570 <login+0x50>
4562: 3f40 f144 mov #0x44f1 "Access granted.", r15
4566: b012 de45 call #0x45de <puts>
456a: b012 4844 call #0x4448 <unlock_door>
456e: 3041 ret
4570: 3f40 0145 mov #0x4501 "That password is not correct.", r15
4574: b012 de45 call #0x45de <puts>
4578: 3041 ret
Inspecting the disassembled program, we see that the program flow is:
- Get the password from user input
- Test if the password is valid using
test_password_valid
- Unlock if
test_password_valid
set the expected flag, as described in the manual
The important part is here, where we see that the test_valid_password
flag should be EB
at address 2410
:
4552: 3f40 d344 mov #0x44d3 "Testing if password is valid.", r15
4556: b012 de45 call #0x45de <puts>
455a: f290 eb00 1024 cmp.b #0xeb, &0x2410
As it turns out, our password is stored right before the test_valid_password
flag. We can overflow the stack and set the flag ourselves.
2400: 7465 7374 696e 6700 0000 0000 0000 0000 testing.........
2410: 0000 0000 0000 0000 0000 0000 0000 0000 ................
Any 17 byte password ending in EB
will work: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEB
.
tl;dr Overflow the stack to change the return address and jump to a location of our choosing
As expected, this Lockitall version fixes our stack overflow from last time.
This is Software Revision 02. We have improved the security of the lock by removing a conditional flag that could accidentally get set by passwords that were too long.
Everything happens in the login
function.
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
We see that the lock unlocks depending on the tst r15
instruction at 4524
. I set a few breakpoints and stepped through with many different inputs, but was never able to change the value of register r15
.
However, when we return from login
at 453e
, the stack pointer is just beyond our input.
|--- our input starts here
V
43e0: 5645 0300 ca45 0000 0a00 0000 3a45 4141 VE...E......:EAA
43f0: 4100 0000 0000 0000 0000 0000 0000 3c44 A.............<D
^
|--- stack pointer is here
Since there's still nothing enforcing that our input is max 16 bytes, we can overwrite the address that login
branches to by supplying a specially crafted input. Making login
branch to the unlock
function (at address 4446
) might be nice.
Any 18-byte password ending in 4644
would work, e.g. 414141414141414141414141414141414644
.
TIL: how call
and ret
work, which was important. When we call
a new function, the address of the next instruction is written to the stack. This is the address we branch to when the function returns. If we can overwrite this value, we can choose where the function branches on return.