Wow, this one was tricky! Lots of fun though. Santa Cruz not only adds complexity with a username, but also has a variety of checks on the length of the password… for real this time.
Let’s see what the release notes say:
Further down it says:
This is Software Revision 05. We have added further mechanisms to verify that passwords which are too long will be rejected.
Maybe they’re serious this time?
Santa Cruz Code
A quick glance through the code shows the usual
login function has lot going going on, and takes up more room than i can fit in one screenshot:
Not really sure why we have those
mov.bs there but okay.
What’s more important here is that we now have to submit a username and password. Maybe we can use this to our advantage. It says each need to be between 8-16 characters long.
If you scroll through the memory viewer window, you might notice that there’s only error messages for the password length… no error messages for the username length. Interesting.
Hmmmm, this looks tasty too. Making a mental note of that (address 0x444a).
Santa Cruz Strategy
To recap, we have the address of an unconditional, unlock-the-door function. We also have two strings that we can input into the system, and it looks like there’s only a length check on one of them.
Let’s try making the username input REALLY long, and then play by the rules for the password length.
While I know that I want to redirect the program flow eventually, right now I’m taking things one step at a time. There’s a lot of checks in the
login function that we have to get past.
Let’s use a username string of 16 “A"s (0x41). From there, let’s not push it too far… maybe 20 characters in total. I used 45-48 for this. So:
For the password, I just used 16 “Z"s. I used Z to differentiate between the username and password. In hex, that’s 0x5a.
I set a breakpoint at 0x457a, 0x45a4 and 0x45d0. To set a breakpoint, type
break [address] e.g.
If we stop before we enter in the password, we can see that
gets put the username at 0x2404, and
strcpy copied it to 0x43a2.
Our username, despite being over 16 chars, was copied in full to both locations.
Let the program run again, enter in the password of “ZZZZZZZZZZZZZZZZ” and stop at the breakpoint at 0x45d0.
Our memory now looks like:
Two things to note here: in the 0x2404 section, which is just where we hold data before we strcpy it over, there’s some leftover bytes from the username, since the username was longer than the password. I don’t think we can do anything with that here, but it does give me an idea that we’ll use later.
The other thing is that the password got copy/pasted on top of the end of our username. See how we’re missing 0x48 in row 43b0?
The First of Many Checks
This section is a loop where we look at r14 and see if it’s equal to 0. Here’s a lil blurb on what
tst.b means for MSP430s:
TST(.B) xxxis an abbreviation of
CMP(.B) #0, xxx(source)
Here’s our loop:
In this scenario, it finally lets us pass when r14 equals 0x43c4.
Then, we increment again and see if that spot equals zero as well. If so, we get to move onto the password length checks.
I don’t remember struggling with this check, but I thought I’d walk through it all the same.
Password Too Long
Next, we check the password length… kinda. Here’s what we’re up against.
We’ll move 0x43c5 into r11, then subtract the value of r15 from it. We’ll get a value back in r11. Then we’ll move a new value into r15 (relative to r4) and compare the two. If they’re equal or lesser, we’re good. Otherwise, we get sent to the
stop_progExec function. 😢
For this scenario, the magic value in r11 ends up being “0x10” which is 16 in decimal. This corresponds to how many “Z"s we put in. We can test out this theory by resetting the program and only sending 15… the value of r11 is 0x0f in that case, or decimal 15.
So, r11 is the password length we’ve sent the program. The
0x18(r4) value is the comparison. This is stored in r15.
When we get to this line (0x45ea), r15 is… 47. Well that looks familiar.
Here’s our username input, again:
With the setup we have now, we’ll pass this check, because we have a 16-digit password. But if we wanted a longer one, we’d be fine because we presumably overwrote the correct length variable check with “47”.
Password Too Short
If you keep stepping, you’ll eventually end up with an error that the password is too short. Uhhhh, what?
This time, we end up comparing r11 (which is 0x10 right now) to
45fa: 5f44 e7ff mov.b -0x19(r4), r15
This turns out to be 0x46, which is also in our string. That means it’s in our control. Let’s change our username string to:
And try again… you should get past the password-too-short check now.
Aaaalmost There (…?)
Narrator’s voice: she was not almost there.
We can largely ignore this chunk of main. We’ll call the interrupt, and then report back on how things went. We’re not trying to get the username and password right anyway.
Set a breakpoint on that last line, 0x464c, and
continue until you get there.
Heeeeere’s some trouble.
This line is checking to see that there’s a zero in the place it expects. If so, that means means that your password is a valid length.
464c: c493 faff tst.b -0x6(r4)
For the time being, we’re okay here. But adding 0x28 to the sp doesn’t get us to the end of our password… it’s several bytes past that.
We want to replace “4044” with the
At this point, we could make the password really long, and “cheat” by setting our username string to cover for us.
I tried username
4141414141414141414141414141414145194748 and password
If we look at the memory, we can see that the 4a44 is in the right spot (yay) but that we don’t have a 00 in the place we need.
Okay, new idea. What if we have a bunch of 00s in the middle of our password string? Username is
4141414141414141414141414141414145104748 and password
This doesn’t work either, because strcpy cuts off a string after it finds a 0 (null byte).
Despite it being obviously not a good idea in retrospect, I tried doing something similar with the username. If I made that REAAALLY long, I could just throw a bunch of 0s in the middle and–actually wait never mind.
What Did Work
The reason we get a 0 in the correct place is because the password input it’s the password’s null character termination. If we look in guts of main, we see that the strcpy for password is at the same place, regardless of how long the username is.
So, back to our original plan of a reaaaaally long username and a 8-16 char password.
Remember earlier how we saw the end of the 20-char username sticking out at the end of the 16-char password in the 0x2404 memory section? Wow me too.
Here’s the plan: we write out a very long username that has the “codes” to bypass the length checks, and at the very end, we put the new address of 0x444a. Then, we write a normal length password, and make sure that the 0 char ends up in the right spot.
This took a LOT of guess and check (lol the entire challenge thus far did too).
Our username looks like:
- 16 “A"s for filler:
- 4 more bytes of filler, but with one swapped out for a “length check code”:
- A bunch more bytes (counting up, to help with debugging):
- Our address: 4a44
Our password is just a bunch of “99"s:
All filler, no killer.
Here, we can see that our username has the checks it needs, plus the address at the end, in the correct spot. Plus, our correct-length password got us a 0x00 in the right spot.
Santa Cruz Solution
The payload is in two parts: a username and a password. The username is doing almost all the work. We’re using it to redirect the program to the
unlock_door function, while dodging length checks.
solve and then enter: