Microcorruption - Hanoi
So a while back I was reworking through Microcorruption, the wonderful online CTF that emulates a basic MSP430 debugging environment. The MSP430 in question is attached to a lock, and your job is to break into the lock using only your ill-gotten skillset.
When I last left off, I had my choice of assignments at either Hanoi or Reykjavik. Naturally I chose Hanoi because, well, it was first.
Upon choosing my assignment, I am presented with the standard "readme" pop up, which informs me of a couple of interesting things right away. Now they have added a "security module" attached to the LockIT Pro, and the software has been updated to be super-mega-peta secure. We'll see.
A little way down the readme, I see how the password gets authenticated. Interesting, it sends a password to the module, and the module writes a memory location with the result. Hey, thanks for that, good to know!
Well, I am eager to get started, so I click the "okay" button and I see the now-familiar debugging screen. Ah, memories! See what I did there? Normally about this time I run through the process once, setting a breakpoint on the main function with b main then c to continue.
The code percolates a bit and stops right on 0x4438, as if it knew what I wanted! Hmm, not a lot in main this time; just a call to <login>. Time to start stepping it up a notch. Did you see what I did again?
I type s to step into the login function, and immediately I see a bunch of cool things happening; prompts to enter the password, warnings that the password has to fit certain criteria, and...oh, what's this? There is a call to <test_password_valid>. I WONDER WHAT THAT DOES?
At this point I have stared at the code a while, just marveling at the tightness and seeming impenetrability. I do notice a couple of interesting things though.
Right after <test_password_valid> there is a test on r15 followed by a jump 8-bytes forward if it's tested zero. So this jumps from 0x454A to 0x4552 if r15 is zero. This puts it at the point where it starts writing the message "Testing if password is valid." Interestingly, right after that message is a compare, a call to <login> again, then it starts printing "Access granted." So it looks like I have the beginnings of a strategy here. If I can bypass or somehow bork that r15 test, that might put me in unlocked territory.
I want to put in a password just to see what happens, so I type c here to continue running the code, and I am presented with the familiar login screen. It tells me I have to enter a password that is between eight and 16 characters, but we all know I am not going to do that, so I enter a password that reflects this hubris.
I never said I was the best speller.
Once I hit send, I see something interesting again! Looking at the live memory dump panel shows me my rebellious password sitting in memory right at 0x2400. I feel a tickling in the back of my head, like something telling me that this section of memory seems familiar, but I can't quite place it. So I ignore it and plow on ahead with the login code.
Whenever the debugger launches the login prompt, it immediately breaks at the return from the function call, which is nice. All I have to do is type f;f to step out of the functions and get back to the <login> code, putting me squarely back at a position right before the password gets tested.
And look, the instruction at 0x4540 moves the memory location where my password is stored into r15! I knew there was something familiar here. So the code from 0x4540 to 0x4548 indicates to me that the content of r15 is responsible for verifying the password validity, and somehow <test_password_valid> changes the contents of that register to be non-zero. Briefly, I consider picking through the code in <test_password_valid>, but then I realize; why don't I just modify the register directly and see what happens?
I step forward into the code until 0x4548, and type l r15 = 1 to change the contents of r15 to 0x1. It could be anything, but I am a simple guy, and one works fine. I step through some more, typing s frantically until I am rewarded with utter failure. It still jumped to the end of the code and gave me the loudest "wa wa waaa" I have ever heard assembly language make.
Dejected, I restarted the debugger and single-stepped through until I landed just after <test_password_valid>, and I whined to myself that I would have to step through that function to understand what's really going on. I should probably change my handle to LazyHacker2020 on AIM. I prepared to step through that code, pencil and paper in hand, when I saw it.
Right after the test of r15 at 0x4548, it either hops over 0x454C, only to compare the same 0x2410 location at 0x455A, or it goes straight through and still compares at 0x2410. Again, that buzzing in the back of my brain tells me something about 0x2410 should be familiar. "Aha!" I yelled.
When I take a look at the really big password in the live memory dump panel, I see that 0x2410 is written to with the contents of that password. So for all the whinging it did about keeping your password between eight and 16 characters, it completely ignored the fact I entered a thirty-character password. And part of that is sitting right where the compare checks!
"It can't be that easy." I tell myself, not remembering that it was kind of that easy last time. I reset everything, clear out all my breakpoints, and type c to continue. I promise the debugger I will respect the password length boundaries...sort of. Just one more byte won't hurt. I make sure to check the box indicating that I am entering hexadecimal, and enter my password, with one special addition at the end.
Hitting the send button now sends a thrill right through my soul. I know this is the answer, and when I check again the live memory dump panel, sure enough the "a3" is sitting right there at 0x2410. This time I set a breakpoint at 0x455A by typing b 0x455A then typing c to continue until the breakpoint is reached.
Checking the live memory dump shows my "a3" still at 0x2410 and the cmp.b instruction queued up at 0x455A, and I get ready to rumble, which really only means I start single-stepping through code.
Voila! The code checks 0x2410 against the value "a3" and, finding it equal, passes through the next jne instruction to proceed right on to the "Access granted." code section. Once again I am rewarded for my errant ways with an unlocked hacker-proof lock.
As usual, in order to get credit for this, I have to reset everything and type solve to run with my solution, which I do. I've let the bad guys in the building and my offshore account is once again flush with cash. Maybe they will stop sending me letters now. All this assembly language stuff has me hankerin' for a TIS-100 session. See ya'll in therapy!
POSTSCRIPT
Naturally, this is a distilled account of what really took place. I ran through several strategies to try to break the lock. One of them involves changing the 0xFD that is passed to the security module to 0x7F, which unlocks the door immediately. Ultimately, none of them showed promise as easy solutions to the puzzle.
Interestingly, there are several places where a single change in a register causes a jmp instruction directly to the unlock routine. The idea isn't to change the content of memory directly, however. Making the code do what I want just by using its own parameters and feeding it garbage, or carefully crafting a password that has code in it, is what makes this so satisfying.