Microcorruption 0x0C - Jakarta
Prev: 0x0B Novosibirsk
Next: 0x0D Algiers
This level’s manual is the least helpful yet. Evidently, “further mechanisms” were added to reject passwords that are too long. Let’s see.
“Main” takes us to a “login” function, and we can get a general sense of the flow by reading through it:
Let’s start by testing the bounds-checking of the username field. Run the program, entering 33 bytes for the username. Break at 459e (after the loop, which tests each byte for our input for a null byte) and continue.
The length of our string is calculated by subtracting the start (2402) from the end in r11 (2423). I entered 33 bytes, so I end up with 0x21 in r11. As you might expect, I fall through the “jnc” instruction at 45b2 as the carry flag is set.
So, that check works as designed. What about the password? Break at 45fa (after the loop) and continue, this time entering 16 bytes for the username and 17 bytes for the password.
Like what happened to our username, the length of our password string is calculated, but this time it is added to the length of our username which is still stored in r11. The total length is… 1f? 31 bytes? But we entered 16 bytes, then 17 bytes, which should total 33? If we continue running the program, we exit without a password length error. What’s happening here? Let’s break at 45c8, after the “puts” call for “Please enter your password:” and dig in.
Interesting, when 0x20 is subtracted from 0x1f, r14 becomes 0xffff. This value corresponds to -1 when cast to a signed data type, and we can see from hovering over the sr register that the “N” (negative) flag is set. But how does this affect the following instructions? Well, the bitwise “and” operation between 0x1ff and 0xffff still results in 0x1ff as we’d expect, which means that 0x1ff is the total number of bytes that are now retrieved from user input! Let’s rerun the program, this time entering 32 bytes for the username and another 16 bytes for the password.
When we hit our earlier break at 45fa, we can see that our password length is correctly calculated as 0x10. Then, after it is added to our username length of 0x20, we fail to take the jump the “cmp.b 0x21, r15” instruction as our total input length (0x30) is greater than 0x21. So close!
Well, if you pay close attention, the key to passing this compare is in the instruction itself: the “.b” modifier means it is only comparing the least significant byte. If we enter a password with a length that, when summed with 0x20, is greater than 0xff but less than 0x121, we’ll pass this test!
Let’s confirm by rerunning the program with 32 bytes of “A” as the username and 224 bytes of “B” - 224 bytes plus 32 bytes puts us at 256, or 0x100, which should pass the byte compare as 0x00 < 0x21. As expected, the program crashes when trying to load “4242” into pc.
If we check the stack, we can see that sp tried to load the 5th and 6th bytes from our password into pc (note that it incremented already prior to this screenshot). If we replace these two bytes with “4c44,” we’ll return into the “unlock_door” function located at 444c!
We have enough to solve the level now. For the username, I entered “4141414141414141414141414141414141414141414141414141414141414141”
For the password, I entered “424242424c444242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242”
Prev: 0x0B Novosibirsk
Next: 0x0D Algiers
Next: 0x0D Algiers
This level’s manual is the least helpful yet. Evidently, “further mechanisms” were added to reject passwords that are too long. Let’s see.
“Main” takes us to a “login” function, and we can get a general sense of the flow by reading through it:
- Some strings are printed (with “puts” this time!), including a request for username input.
- 0xff bytes of input are retrieved from us, copied to the stack, and then some bounds checking occurs, terminating the program if the check fails.
- Another string is printed, requesting a password from us.
- “TBD” bytes of input are retrieved from us and copied to the stack, again before any bounds checking occurs. The total bytes are “to be determined” for us now since it appears that there are “sub” and “and” instructions which affect this value.
- “test_password_valid” is called, presumably on a concatenated string located on the stack which contains both our username and password.
- r15 is tested, and a jump instruction fails us if the value is 0, otherwise calling “unlock_door” if the value is anything else.
Let’s start by testing the bounds-checking of the username field. Run the program, entering 33 bytes for the username. Break at 459e (after the loop, which tests each byte for our input for a null byte) and continue.
The length of our string is calculated by subtracting the start (2402) from the end in r11 (2423). I entered 33 bytes, so I end up with 0x21 in r11. As you might expect, I fall through the “jnc” instruction at 45b2 as the carry flag is set.
So, that check works as designed. What about the password? Break at 45fa (after the loop) and continue, this time entering 16 bytes for the username and 17 bytes for the password.
Like what happened to our username, the length of our password string is calculated, but this time it is added to the length of our username which is still stored in r11. The total length is… 1f? 31 bytes? But we entered 16 bytes, then 17 bytes, which should total 33? If we continue running the program, we exit without a password length error. What’s happening here? Let’s break at 45c8, after the “puts” call for “Please enter your password:” and dig in.
- 0x1f is moved into r14.
- The username string length (r11) is subtracted from r14.
- A bitwise “and” occurs on r14.
- r14 is used as the total bytes retrieved from the user in the “getsn” call.
Interesting, when 0x20 is subtracted from 0x1f, r14 becomes 0xffff. This value corresponds to -1 when cast to a signed data type, and we can see from hovering over the sr register that the “N” (negative) flag is set. But how does this affect the following instructions? Well, the bitwise “and” operation between 0x1ff and 0xffff still results in 0x1ff as we’d expect, which means that 0x1ff is the total number of bytes that are now retrieved from user input! Let’s rerun the program, this time entering 32 bytes for the username and another 16 bytes for the password.
When we hit our earlier break at 45fa, we can see that our password length is correctly calculated as 0x10. Then, after it is added to our username length of 0x20, we fail to take the jump the “cmp.b 0x21, r15” instruction as our total input length (0x30) is greater than 0x21. So close!
Well, if you pay close attention, the key to passing this compare is in the instruction itself: the “.b” modifier means it is only comparing the least significant byte. If we enter a password with a length that, when summed with 0x20, is greater than 0xff but less than 0x121, we’ll pass this test!
Let’s confirm by rerunning the program with 32 bytes of “A” as the username and 224 bytes of “B” - 224 bytes plus 32 bytes puts us at 256, or 0x100, which should pass the byte compare as 0x00 < 0x21. As expected, the program crashes when trying to load “4242” into pc.
If we check the stack, we can see that sp tried to load the 5th and 6th bytes from our password into pc (note that it incremented already prior to this screenshot). If we replace these two bytes with “4c44,” we’ll return into the “unlock_door” function located at 444c!
We have enough to solve the level now. For the username, I entered “4141414141414141414141414141414141414141414141414141414141414141”
For the password, I entered “424242424c444242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242”
Prev: 0x0B Novosibirsk
Next: 0x0D Algiers