Microcorruption 0x09 Santa Cruz
Prev: 0x08 Montevideo
Next: 0x0A Addis Ababa
The manual for this level explains that there has been yet another update which rejects passwords which are too long. Additionally, when we run through the program we can see that we are asked for a username in addition to a password, and at first glance it appears that passwords longer than 16 bytes are rejected, as well as passwords shorter than 8 bytes!
Interesting, but before we examine further, let's try to understand our objective. Checking out the Disassembly, we've found our favorite function: "unlock_door." Let's test the new bounds-checking of this program to see if we can generate an overflow that overwrites a return address on the stack with the address of this "unlock_door" function.
If we check "login" we find a lot going on, so let's start by examining how usernames and passwords are handled:
Although "strcpy" isn't listed in the lock manual, we know it takes two arguments: the address of the string to copy (which based on the address before the calls to "puts" we can see is 2404), and the address to copy the string to (which we can see is the value of r4 plus either 0xffd6 or 0xffe6 depending on whether it's the username or password).
Let's break after the second call to "strcpy" and run the program with 16 A's as the username as 16 B's as the password.
As expected, the username is copied to r4 (which we can think of as ebp here) plus 0xffd6 (which is the same as "minus 2a"), and the password is copied to r4 plus 0xffe6 (which is the same as "minus 1a"). What happens next?
Each byte of the password is tested and once the null byte is found, the address of the null byte (43c5 in a 16-byte password) is subtracted from 43b5, one byte before the start of our password. This results in the length of the string being stored in r11, which is then compared against "-0x18(r4)" which we can see is the value "0x10." If we fail this compare, we receive our favorite "password too long" error.
Looking back up near the top of "login," we can see that "0x10" was placed at this address prior to either of the "strcpy" functions. Could we perhaps overwrite this address? If we check the address "-0x18(r4)," it's right before our password, which means it's right after our username! Let's try to overwrite this address with a value greater than "0x10" by overflowing the username buffer... this should allow us to control the password size permitted by the program.
Let's submit a username that contains 16 bytes to fill the buffer, 2 extra bytes that overwrites the values just prior to the password length check value, and then one byte to overwrite this check value. I entered "41414141414141414141414141414141414130." Then, let's enter a password that's longer than 16 bytes, but less than the 48 bytes ("0x30") we set as the new length check value.
When we get to the compare instruction at 45ea, we pass the check! However, we arrive at a second compare which appears to quit the program if failed:
This time, the length of our password is compared against "-0x19(r4)." If we scroll to the top of "login," we see that this value is "0x8." This must represent the minimum password length, and since we overflowed with A's, this minimum value is currently "41." Whoops.
Let's reset the program and enter a new username value: 16 bytes to fill the buffer, another byte that doesn't appear to be referenced anywhere, one byte to overwrite the "minimum" password length value (such as "0x01"), and then a byte to overwrite the "maximum" password length value (we'll again use "0x30"). I entered "41414141414141414141414141414141410130."
Finally, checking the stack, we can see that the return address of "4440" is placed on the stack 23 bytes after the start of our password. So, let's enter 23 dummy bytes for our password, followed by the address of "unlock_door" (444a). I used "42424242424242424242424242424242424242424242424a44."
Uh oh - we fail again, and the program provides a "password too long" error. But I thought we passed that check? Turns out there's a second check:
Right near the very bottom of "login" we can see that the value at address "-0x6(r4)" is tested, and if it isn't "0x00," we get an error and the program exits. If we check the stack, we discover that this is testing an address that is 16 bytes after the start of our password buffer. In other words, it doesn't matter if we overwrote the "0x10" value earlier which checks the length of the password, as there's a second check which looks for a null byte. And to make things worse, we can't write the null byte here ourselves in the password we enter, since "strcpy" will assume that is the end of our string and stop copying before we are able to overwrite the return address.
So, how can we pass this check? We need a null byte 17 bytes after the start of the password buffer, and we can't write this into the password ourselves, so it appears that the password we enter has to legitimately be 17 bytes or fewer.
What about the username? If we use the same username as before, but add another 17 bytes to fill the password buffer, plus the 7 bytes prior to the return address, and then 2 bytes overwrite the return address, we've accomplished our goal of redirecting program execution once it returns. Then, if we enter a 16-byte password, it will overwrite whatever was copied onto the stack for the username, and "strcpy" will do us the wonderful and much-needed favor of copying the null byte as well, allowing us to pass the final check! The stack will look like this:
At 43b3, we have our overwritten password "minimum" value of "0x01," although this could be any value from "0x00" to "0x10." Then, at 43b4, we have our overwritten password "maximum" value of "0x32," which can be anything greater than "0x11." Then, at 43cc, we have our overwritten return address, which will take us to "unlock_door."
Bam! We're in.
For the username, I used "4141414141414141414141414141414141101241414141414141414141414141414141414141414141414a44."
For the password, I used "4242424242424242424242424242424242."
Prev: 0x08 Montevideo
Next: 0x0A Addis Ababa
Next: 0x0A Addis Ababa
The manual for this level explains that there has been yet another update which rejects passwords which are too long. Additionally, when we run through the program we can see that we are asked for a username in addition to a password, and at first glance it appears that passwords longer than 16 bytes are rejected, as well as passwords shorter than 8 bytes!
Interesting, but before we examine further, let's try to understand our objective. Checking out the Disassembly, we've found our favorite function: "unlock_door." Let's test the new bounds-checking of this program to see if we can generate an overflow that overwrites a return address on the stack with the address of this "unlock_door" function.
If we check "login" we find a lot going on, so let's start by examining how usernames and passwords are handled:
Although "strcpy" isn't listed in the lock manual, we know it takes two arguments: the address of the string to copy (which based on the address before the calls to "puts" we can see is 2404), and the address to copy the string to (which we can see is the value of r4 plus either 0xffd6 or 0xffe6 depending on whether it's the username or password).
Let's break after the second call to "strcpy" and run the program with 16 A's as the username as 16 B's as the password.
As expected, the username is copied to r4 (which we can think of as ebp here) plus 0xffd6 (which is the same as "minus 2a"), and the password is copied to r4 plus 0xffe6 (which is the same as "minus 1a"). What happens next?
Each byte of the password is tested and once the null byte is found, the address of the null byte (43c5 in a 16-byte password) is subtracted from 43b5, one byte before the start of our password. This results in the length of the string being stored in r11, which is then compared against "-0x18(r4)" which we can see is the value "0x10." If we fail this compare, we receive our favorite "password too long" error.
Looking back up near the top of "login," we can see that "0x10" was placed at this address prior to either of the "strcpy" functions. Could we perhaps overwrite this address? If we check the address "-0x18(r4)," it's right before our password, which means it's right after our username! Let's try to overwrite this address with a value greater than "0x10" by overflowing the username buffer... this should allow us to control the password size permitted by the program.
Let's submit a username that contains 16 bytes to fill the buffer, 2 extra bytes that overwrites the values just prior to the password length check value, and then one byte to overwrite this check value. I entered "41414141414141414141414141414141414130." Then, let's enter a password that's longer than 16 bytes, but less than the 48 bytes ("0x30") we set as the new length check value.
When we get to the compare instruction at 45ea, we pass the check! However, we arrive at a second compare which appears to quit the program if failed:
This time, the length of our password is compared against "-0x19(r4)." If we scroll to the top of "login," we see that this value is "0x8." This must represent the minimum password length, and since we overflowed with A's, this minimum value is currently "41." Whoops.
Let's reset the program and enter a new username value: 16 bytes to fill the buffer, another byte that doesn't appear to be referenced anywhere, one byte to overwrite the "minimum" password length value (such as "0x01"), and then a byte to overwrite the "maximum" password length value (we'll again use "0x30"). I entered "41414141414141414141414141414141410130."
Finally, checking the stack, we can see that the return address of "4440" is placed on the stack 23 bytes after the start of our password. So, let's enter 23 dummy bytes for our password, followed by the address of "unlock_door" (444a). I used "42424242424242424242424242424242424242424242424a44."
Uh oh - we fail again, and the program provides a "password too long" error. But I thought we passed that check? Turns out there's a second check:
Right near the very bottom of "login" we can see that the value at address "-0x6(r4)" is tested, and if it isn't "0x00," we get an error and the program exits. If we check the stack, we discover that this is testing an address that is 16 bytes after the start of our password buffer. In other words, it doesn't matter if we overwrote the "0x10" value earlier which checks the length of the password, as there's a second check which looks for a null byte. And to make things worse, we can't write the null byte here ourselves in the password we enter, since "strcpy" will assume that is the end of our string and stop copying before we are able to overwrite the return address.
So, how can we pass this check? We need a null byte 17 bytes after the start of the password buffer, and we can't write this into the password ourselves, so it appears that the password we enter has to legitimately be 17 bytes or fewer.
What about the username? If we use the same username as before, but add another 17 bytes to fill the password buffer, plus the 7 bytes prior to the return address, and then 2 bytes overwrite the return address, we've accomplished our goal of redirecting program execution once it returns. Then, if we enter a 16-byte password, it will overwrite whatever was copied onto the stack for the username, and "strcpy" will do us the wonderful and much-needed favor of copying the null byte as well, allowing us to pass the final check! The stack will look like this:
At 43b3, we have our overwritten password "minimum" value of "0x01," although this could be any value from "0x00" to "0x10." Then, at 43b4, we have our overwritten password "maximum" value of "0x32," which can be anything greater than "0x11." Then, at 43cc, we have our overwritten return address, which will take us to "unlock_door."
Bam! We're in.
For the username, I used "4141414141414141414141414141414141101241414141414141414141414141414141414141414141414a44."
For the password, I used "4242424242424242424242424242424242."
Prev: 0x08 Montevideo
Next: 0x0A Addis Ababa