Microcorruption 0x0B - Novosibirsk
Prev: 0x0A Addis Ababa
Next: 0x0C Jakarta
The manual for this level indicates that features from the previous level were added to HSM-2 version C. Examining “main,” this appears to be the “printf” functionality that we exploited in the prior level.
And, in case you forgot how HSM-2 worked, if we continue looking through “main” we see that “test_password_valid” and “unlock_door” were both removed in favor of “conditional_unlock_door.” This function does not unlock the door directly, but instead sends our input to a separate module which will unlock the door if correct. This means that we no longer have the ability to unlock the door within this program’s normal instructions.
So, if we think back to prior solutions, we may be inclined to try and generate some arbitrary code execution – something that calls INT with the 0x7f argument. But how can we do that?
Well, we still have the vulnerable “printf” available to us, and the function remains unchanged from the prior level, so in theory we should still be able to arbitrarily write to an address using the “%n” conversion character once again. Testing this using “8001256e256e” as our input confirms this: we are able to write 0x2, representing the two bytes read into memory thus far, to the random address (0180) that we provided.
But wait, we receive an error as well: “load address unaligned: 6e25.” For some reason, the program is treating 6e25 like an address to write data into. This didn’t occur in the previous level.
If we rerun the program and break at 4476 in main (just before we print our string with “printf”), we notice that the stack for this level is configured differently from last level. This time, sp is actually two bytes closer to our string, meaning that we don’t need to pass “%n” twice at the end of our string in order to write to the address we provide. We only need to do it once. Cool. Now what are we able to write?
If we check the “getsn” call, we can see that the length argument passed to it is 0x1f4, meaning we can write up to 0x1f2 (0x1f4 minus the two bytes needed for the conversion character “%n”) to an address of our choosing.
Unfortunately, this isn’t too helpful if we’re trying to generate a return address to our shellcode, as our string’s address on the stack starts at 420e. We can’t write anywhere close to that large! What other value, lower than 0x1f2, might be useful to us? What about 0x7f, the input required to unlock the door using the INT function?
If we break at address 44c6 - the "push #0x7e" instruction - and examine the stack, we find the value "0x73" at address 44c8. If we can write "0x7f" to that address instead, we change the instruction entirely.
Let's try to construct our input:
But wait, we have the power to write two bytes from 0x0 to 0x1f2 anywhere in memory. Surely there's another clever solution here! What if we overwrite a return address using this?
If we check the Disassembly, there’s almost no instructions located at an address below 0x1f4, but there is one: “__trap_interrupt” at address 0010, which is a return instruction. Depending on how the stack is structured when this executes, this could be useful.
Let’s break at the 4476 “printf” call, step into it, then break at the return instruction. After running the program, we see that sp is pointing at address 4208 which contains 447a, which is where we will return to. If we overwrite 4208 with the value 0x10 instead, we’ll return to “__trap_interrupt” which is itself another return, meaning sp will increment and we’ll return to the next value on the stack. What is this value? The address of our string – 420c!
This sounds perfect, but don’t forget that the first two bytes of our shellcode must contain the address we are writing to. In this case, that’s “0842.” We’ll need to hope that this doesn’t execute an undesirable instruction that crashes the program or somehow redirects program flow.
Let’s assemble our shellcode so that we can test this solution. I used the instructions from our Montevideo input:
The final input is “08427e407f41b0123c45414141414141256e”
Prev: 0x0A Addis Ababa
Next: 0x0C Jakarta
Next: 0x0C Jakarta
The manual for this level indicates that features from the previous level were added to HSM-2 version C. Examining “main,” this appears to be the “printf” functionality that we exploited in the prior level.
And, in case you forgot how HSM-2 worked, if we continue looking through “main” we see that “test_password_valid” and “unlock_door” were both removed in favor of “conditional_unlock_door.” This function does not unlock the door directly, but instead sends our input to a separate module which will unlock the door if correct. This means that we no longer have the ability to unlock the door within this program’s normal instructions.
So, if we think back to prior solutions, we may be inclined to try and generate some arbitrary code execution – something that calls INT with the 0x7f argument. But how can we do that?
Well, we still have the vulnerable “printf” available to us, and the function remains unchanged from the prior level, so in theory we should still be able to arbitrarily write to an address using the “%n” conversion character once again. Testing this using “8001256e256e” as our input confirms this: we are able to write 0x2, representing the two bytes read into memory thus far, to the random address (0180) that we provided.
But wait, we receive an error as well: “load address unaligned: 6e25.” For some reason, the program is treating 6e25 like an address to write data into. This didn’t occur in the previous level.
If we rerun the program and break at 4476 in main (just before we print our string with “printf”), we notice that the stack for this level is configured differently from last level. This time, sp is actually two bytes closer to our string, meaning that we don’t need to pass “%n” twice at the end of our string in order to write to the address we provide. We only need to do it once. Cool. Now what are we able to write?
If we check the “getsn” call, we can see that the length argument passed to it is 0x1f4, meaning we can write up to 0x1f2 (0x1f4 minus the two bytes needed for the conversion character “%n”) to an address of our choosing.
Unfortunately, this isn’t too helpful if we’re trying to generate a return address to our shellcode, as our string’s address on the stack starts at 420e. We can’t write anywhere close to that large! What other value, lower than 0x1f2, might be useful to us? What about 0x7f, the input required to unlock the door using the INT function?
If we break at address 44c6 - the "push #0x7e" instruction - and examine the stack, we find the value "0x73" at address 44c8. If we can write "0x7f" to that address instead, we change the instruction entirely.
Let's try to construct our input:
- "c844" for the address to write to,
- "41" 125 times (0x7f = 127 in decimal, but subtract two for the address we just wrote), and
- "256e" (or “%n”) to trigger our write.
Enter in that string and when we hit our break again, we see that it has been overwritten.
When we step into the next instruction and check the stack, however, we see that it worked as designed: sp now points to 0x7f.
We've solved the level! The final input is: "c8444141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141256e"
If we check the Disassembly, there’s almost no instructions located at an address below 0x1f4, but there is one: “__trap_interrupt” at address 0010, which is a return instruction. Depending on how the stack is structured when this executes, this could be useful.
Let’s break at the 4476 “printf” call, step into it, then break at the return instruction. After running the program, we see that sp is pointing at address 4208 which contains 447a, which is where we will return to. If we overwrite 4208 with the value 0x10 instead, we’ll return to “__trap_interrupt” which is itself another return, meaning sp will increment and we’ll return to the next value on the stack. What is this value? The address of our string – 420c!
This sounds perfect, but don’t forget that the first two bytes of our shellcode must contain the address we are writing to. In this case, that’s “0842.” We’ll need to hope that this doesn’t execute an undesirable instruction that crashes the program or somehow redirects program flow.
Let’s assemble our shellcode so that we can test this solution. I used the instructions from our Montevideo input:
- mov.b 0x417f, r14
- call 0x453c (which is the address within INT that moves r14 into r15, ultimately providing it as the argument for the lock)
- “0842” for the address to write to,
- “7e407f41b0123c45” for our shellcode,
- “414141414141” to add another 6 bytes to bring us to 0x10, and
- “256e” (or “%n”) to trigger our write.
The final input is “08427e407f41b0123c45414141414141256e”
Prev: 0x0A Addis Ababa
Next: 0x0C Jakarta