Microcorruption 0x0D - Algiers
Prev: 0x0C Jakarta
Next: 0x0E Vladivostok
The manual for this level mentions a new “Account Manager,” and if we examine the Disassembly we find two new functions: “malloc” and “free.” It appears that this level has some sort of “heap” implementation to store user input, so our challenge will be to exploit that new functionality.
Note that you should absolutely read up on heap memory and dynamic memory allocation if you are not familiar with these concepts.
If we examine the “login” function, we get an idea of how the “malloc” call works: first, it takes the requested buffer size as an argument passed via r15 (“0x10” in these instances), and then it returns some value to r15 (which we assume to be a pointer to these malloc’d buffers). The first pointer returned from “malloc” is moved into r10 (possibly the username), while the second is moved into r11 (possibly the password).
Checking the order of the remaining instructions and their corresponding "getsn" calls, we can confirm our assumptions about r10 and r11. Then, we see some familiar “test_password_valid” and “unlock_door” functions, followed by two calls to “free” which will allocate these borrowed memory chunks back to the “heap.”
Based on "malloc's" initial instructions, we can see that the heap begins at 2400. Let’s start by running the program and examining the heap after both calls to “malloc.”
There’s a lot going on here, but we can locate the familiar components of heap chunk metadata by examining the start of our second chunk at address 241e:
Let’s step through “free” to see how it works. First, 0x6 is subtracted from r15 (via the “add 0xfffa” instruction) to change r15 from the address of the password string to the address of the password chunk on the heap (the start of the metadata).
This allows "free" to reference the metadata through various offsets:
If we quickly skim the “free” function, we see that the size/availability value is used in various checks and has several operations performed on it, most notably some bitwise AND checks (via the “bit” instruction). These checks dictate the flow of the “free” function through subsequent jumps. These must be chunk availability checks, so let’s avoid altering this value for now and try for something simpler so can we can debug with a consistent program flow: either @r15 (the previous chunk pointer), 0x2(r15) (the next chunk pointer), or both. Let’s see how they are used by stepping through “free.”
We can see that @r15 is first used when moved into r14 at instruction 451a. As a result, “free” is now able to check the metadata of the previous chunk by referencing offsets of r14 the same way it does for r15. We can see this in the subsequent instructions when it moves the previous chunk’s size/availability bytes into r12 before performing a “bit” instruction:
So, if we overwrite @r15, whatever value is 4 bytes from that address will be used for an availability check, which will affect program flow. Since we can control this value, let’s look ahead to see what happens if the value is even, indicating to “free” that it is not in use.
After falling through the jump, we see our first two uses of 0x2(r15) at address 452e: first, it’s moved into 0x2(r14), and then it’s moved into r13.
Now we have @r15, the “previous chunk pointer,” in r14. We also have 0x2(r15), the “next chunk pointer,” in r13. These are the two values we are considering overwriting and, if we look at the next instruction, we find something interesting: r14 is moved into the address stored in r13.
This is the most obviously helpful instruction yet: 0x0(r13) will be overwritten with r14. In other words, the value at (0x2)r15 will be overwritten with the value at @r15.
How can this help us? Well, what if we overwrite a return address with the address of “unlock_door?” Let’s give it a shot by assembling a test payload:
If we break at “free” and watch the stack as we step through, we accomplish our objective: the return address for “free,” which is stored at address 4394, is overwritten with “6445.” However, we hit a snag, and you may have seen it coming: the first two instructions of “unlock_door” were overwritten as a result of "free" assuming that 4546 is a chunk metadata header, thus editing some of its bytes.
So, this is unfortunate for us. We can overwrite values at an address X with value Y, but data at address Y becomes overwritten as a result.
We still have some options, however. If you check the Disassembly window and look right above “unlock_door,” you’ll find the “ret” instruction from “free” that we planned to take. In other words, if we can overwrite “ret” with a bogus instruction, we’ll fall through into “unlock_door!” However, we need to make sure that whatever instruction we pass into it is (1) even, since “free” will treat it as an address, (2) has an even-numbered byte at an offset of 0x4 so that we pass the availability check, and (3) isn't an address that is anywhere near here!
Let’s again build a test payload:
However, the code segment of normal software programs is typically not something we can write to, so this method has no chance of working in the real world. Plus, it was extremely fortunate that “unlock_door” was just beneath us. Is there another option?
In theory, we may be able to use this overwrite to return to one of our strings on the heap. Although some of the initial instructions will be modified by “free,” perhaps they will not completely break our shell code? Or better yet, perhaps we can think of a way to avoid them?
Let’s build a test payload, and try to overwrite the return address of “free” with the address of our string in the username chunk:
So, we need our 16-byte buffer to have an even byte at that address. Let’s just replace the As with Bs and enter “424242424242424242424242424242420e24944321”
If we break at the end of “free,” we find sp pointing at “0e24” on the stack, and we return into our username string in the heap! Awesome – we have arbitrary code execution! Or do we?
If we check the start of our string at 240e, we have “4242,” which are the Bs we input. That’s fine. But after that, we have “0000” and “426e,” which must be some junk that “free” generated from our corrupted values. On top of that, the “0000” is treated as an “rrc pc” instruction which completely ruins control flow and crashes the program.
Fortunately, however, if we look ahead we see that we still have our remaining 10 bytes of Bs that are totally unchanged. So, if we can jump from 240e over to 2414, we’re good to go. If we type “jmp 0x6” in the site’s assembler, the instruction is only 4 bytes: “023c.”
Now, all we need is our shellcode and we’re good to go. Let’s simply do a return to “unlock_door” (though we have plenty of other options, some even smaller). Let’s generate our final payload:
Prev: 0x0C Jakarta
Next: 0x0E Vladivostok
Next: 0x0E Vladivostok
The manual for this level mentions a new “Account Manager,” and if we examine the Disassembly we find two new functions: “malloc” and “free.” It appears that this level has some sort of “heap” implementation to store user input, so our challenge will be to exploit that new functionality.
Note that you should absolutely read up on heap memory and dynamic memory allocation if you are not familiar with these concepts.
If we examine the “login” function, we get an idea of how the “malloc” call works: first, it takes the requested buffer size as an argument passed via r15 (“0x10” in these instances), and then it returns some value to r15 (which we assume to be a pointer to these malloc’d buffers). The first pointer returned from “malloc” is moved into r10 (possibly the username), while the second is moved into r11 (possibly the password).
Checking the order of the remaining instructions and their corresponding "getsn" calls, we can confirm our assumptions about r10 and r11. Then, we see some familiar “test_password_valid” and “unlock_door” functions, followed by two calls to “free” which will allocate these borrowed memory chunks back to the “heap.”
Based on "malloc's" initial instructions, we can see that the heap begins at 2400. Let’s start by running the program and examining the heap after both calls to “malloc.”
There’s a lot going on here, but we can locate the familiar components of heap chunk metadata by examining the start of our second chunk at address 241e:
- First, the pointer to the previous heap chunk (2408)
- Then, the pointer to the next chunk (2434),
- Then, presumably some bytes that correspond to size and/or availability (0x21).
Let’s step through “free” to see how it works. First, 0x6 is subtracted from r15 (via the “add 0xfffa” instruction) to change r15 from the address of the password string to the address of the password chunk on the heap (the start of the metadata).
This allows "free" to reference the metadata through various offsets:
- @r15, the previous chunk pointer
- 0x2(r15), the next chunk pointer
- 0x4(r15), the size/availability value
If we quickly skim the “free” function, we see that the size/availability value is used in various checks and has several operations performed on it, most notably some bitwise AND checks (via the “bit” instruction). These checks dictate the flow of the “free” function through subsequent jumps. These must be chunk availability checks, so let’s avoid altering this value for now and try for something simpler so can we can debug with a consistent program flow: either @r15 (the previous chunk pointer), 0x2(r15) (the next chunk pointer), or both. Let’s see how they are used by stepping through “free.”
We can see that @r15 is first used when moved into r14 at instruction 451a. As a result, “free” is now able to check the metadata of the previous chunk by referencing offsets of r14 the same way it does for r15. We can see this in the subsequent instructions when it moves the previous chunk’s size/availability bytes into r12 before performing a “bit” instruction:
So, if we overwrite @r15, whatever value is 4 bytes from that address will be used for an availability check, which will affect program flow. Since we can control this value, let’s look ahead to see what happens if the value is even, indicating to “free” that it is not in use.
After falling through the jump, we see our first two uses of 0x2(r15) at address 452e: first, it’s moved into 0x2(r14), and then it’s moved into r13.
Now we have @r15, the “previous chunk pointer,” in r14. We also have 0x2(r15), the “next chunk pointer,” in r13. These are the two values we are considering overwriting and, if we look at the next instruction, we find something interesting: r14 is moved into the address stored in r13.
This is the most obviously helpful instruction yet: 0x0(r13) will be overwritten with r14. In other words, the value at (0x2)r15 will be overwritten with the value at @r15.
How can this help us? Well, what if we overwrite a return address with the address of “unlock_door?” Let’s give it a shot by assembling a test payload:
- 16 bytes of “A”
- “6445” which is the address of “unlock_door” in little-endian
- “9443” which is the address in sp when the first call to “free” is at its return instruction
- “21” for the sake of keeping the size the same
If we break at “free” and watch the stack as we step through, we accomplish our objective: the return address for “free,” which is stored at address 4394, is overwritten with “6445.” However, we hit a snag, and you may have seen it coming: the first two instructions of “unlock_door” were overwritten as a result of "free" assuming that 4546 is a chunk metadata header, thus editing some of its bytes.
So, this is unfortunate for us. We can overwrite values at an address X with value Y, but data at address Y becomes overwritten as a result.
We still have some options, however. If you check the Disassembly window and look right above “unlock_door,” you’ll find the “ret” instruction from “free” that we planned to take. In other words, if we can overwrite “ret” with a bogus instruction, we’ll fall through into “unlock_door!” However, we need to make sure that whatever instruction we pass into it is (1) even, since “free” will treat it as an address, (2) has an even-numbered byte at an offset of 0x4 so that we pass the availability check, and (3) isn't an address that is anywhere near here!
Let’s again build a test payload:
- 16 bytes of “A”
- “0e9b” which is the bogus instruction I selected (“cmp r11, r14” – harmless and far away)
- “6245” which is the code section address of the ret instruction in “free”
- “21” for the sake of keeping size the same
However, the code segment of normal software programs is typically not something we can write to, so this method has no chance of working in the real world. Plus, it was extremely fortunate that “unlock_door” was just beneath us. Is there another option?
In theory, we may be able to use this overwrite to return to one of our strings on the heap. Although some of the initial instructions will be modified by “free,” perhaps they will not completely break our shell code? Or better yet, perhaps we can think of a way to avoid them?
Let’s build a test payload, and try to overwrite the return address of “free” with the address of our string in the username chunk:
- 16 bytes of “A”
- “0e24” which is the address of our username string on the heap
- “9443” which is the address in sp when the first call to “free” is at its return instruction
- “21” for the sake of keeping the size the same
So, we need our 16-byte buffer to have an even byte at that address. Let’s just replace the As with Bs and enter “424242424242424242424242424242420e24944321”
If we break at the end of “free,” we find sp pointing at “0e24” on the stack, and we return into our username string in the heap! Awesome – we have arbitrary code execution! Or do we?
If we check the start of our string at 240e, we have “4242,” which are the Bs we input. That’s fine. But after that, we have “0000” and “426e,” which must be some junk that “free” generated from our corrupted values. On top of that, the “0000” is treated as an “rrc pc” instruction which completely ruins control flow and crashes the program.
Now, all we need is our shellcode and we’re good to go. Let’s simply do a return to “unlock_door” (though we have plenty of other options, some even smaller). Let’s generate our final payload:
- “023c” for the “jmp 0x6” instruction
- 4 bytes of “B” which we know will be overwritten
- “3f4064450f123041” which is our 8-byte shellcode
- 2 bytes of “B” to fill the remaining bytes of the 16-byte buffer
- “0e24” which is the address of our username string on the heap
- “9443” which is the address we want to overwrite (the return value for “free”)
- “21” for the sake of keeping size the same
Prev: 0x0C Jakarta
Next: 0x0E Vladivostok