medium.com

Pinky’s Palace V4 Writeup

Lijnk

Lijnk

Intro

The Pinky’s Palace series of CTF boxes on vulnhub are an increasingly difficult series of boxes to break into. Version 4 is the most difficult to this date and makes the user have to create their own exploits to beat it. Due to the time-consuming nature of these boxes, it took a while for first blood to be reached. I decided to wait on creating this write-up to see if anybody else would make it, and while there were many attempts on getting in, it didn’t happen. There are some interesting points in this box though, so I hope this is useful in explaining some of the lower level functionality of the system. I also wrote this so the reader doesn’t get the exact payloads to “beat” the box, but instead gets instructions on how to create their own.

Starting out

To start, we need to do a simple nmap scan on all ports to see how we can get in:

Press enter or click to view image in full size

Nmap scan of the CTF box

The port 80 server is just a standard nginx and if we access it, we’ll see a pink phpinfo() page.

Press enter or click to view image in full size

A lot of neat info, but nothing we can immediately use

While it can be useful to check this out to get a somewhat lay of the land regarding this service (file locations, version numbers, etc), it isn’t particularly useful for us other than we should note that php is running somewhere if we wish to access it. The other service shows another pink page, but just says the application is under development. To further this, if we decide to use dirbuster, we won’t get anything except for that one page on both services.

Press enter or click to view image in full size

“under development” is never a good sign

If instead, we take a look at the headers (see the above nmap results), we’ll find that the headers on the 65535 service shows the server is called “pinkys-HTTP-server” and it’s using “HTTP/1.0” instead of 1.1. Since this is an application under development, it isn’t unreasonable to think the application isn’t the website, but rather the server itself. The next task isn’t totally obvious, but if we play around with the headers inside of Burp’s repeater, we’ll notice that we can use directory traversal using “..”.

Poking around the machine

Press enter or click to view image in full size

Uh oh…

This solidifies that this is a custom HTTP server running and our goal is to break it. This sort of traversal has a few catches when we use it though which is we aren’t able to list directories, but we are able to tell a directory exists. ie: If we put /etc instead of /etc/passwd in the above, then we’ll get an empty 200 response, however if we put /et, we’ll get a 404. The next step from here is to figure out what are we going to do with this. At this point, I try to get as much information about the machine as possible, which means pulling configs, known files, etc. In this case, I’ve found there wasn’t much interesting, other than I need to find out how to at the very least, get directory listing and the other HTTP server is a red herring.

The / etc/passwd file shown above does contain some useful information though. Particularly the phs user, which we can assume stands for “pinkys-HTTP-server”. We’ll see its home directory is in /srv/phs. If we then try a GET request to /srv/phs/index.html, we’ll get the page we originally had. If we go one step further and note the server header and try /srv/phs/pinkys-HTTP-server, we now have the executable of this server. Note that since the entire “../../../../../srv/phs” is actually just “/”, we can simply request /pinkys-HTTP-server and we’ll also get the executable, which is more simple that figuring out how to save it in Burp or writing some manual script to grab it.

Static analysis

From here, we can open it up in a disassembler to figure out what’s going on. To start, the executable is a simple forking server. It takes requests from port 65535, forks itself and serves the files up. This is useful so if we send it something weird, the entire server doesn’t crash, but instead it’s just the instance. We’ll also note that it resets the uid, gid, euid and egid to 0x53a, or 1338 which is the phs user/group.

Resets the permissions, but in the wrong order

Something interesting I noticed here is if we assume this is root, the order of these ID switches is incorrect and will result in the user having uid 1338, but gid of 0. This is due to how the permissions system works when determining if you can change permissions. In Linux, there are three different IDs per user/group within a process: the effective ID, saved ID, and real ID. The real ID is the ID that was started with the process and can only be changed if the process is run as root. The effective and saved ID are used together to bounce between privileged and unprivileged users, but when coding these, you’ll only be dealing with the effective ID as the saved ID is automatically figured out behind the scenes. As for the functions, seteuid() will check if you’re root or if your real/saved UID is equal to the one you want to change to. If so, then it’ll push the current effective UID to the saved UID, then set your effective UID to the new one. The setuid() function on the other hand does the same as seteuid(), but if it switches, then it also changes your real UID meaning you cannot go back. On the other side, the setgid() and setegid() functions will do the same as their setuid() and seteuid() counterparts, but they still check the UID (GID 0 doesn’t actually mean anything special). Because of this, the program will set its RUID, SUID, and EUID to 1338, but it’ll be unable to change its GID and will thus be left alone.

As it turns out though, this doesn’t actually matter as later on, we’ll find out the service is directly running as the phs user, so we won’t actually get the gid of 0. There also aren’t too many files out there that give you extra permissions as group root, but it’s something to keep in mind when using these functions.

If we read further into the code, we’ll see a few useful functions: 0x8048aad handles the requests, 0x804895b sends data, and 0x80489c0 receives data.

Press enter or click to view image in full size

Looking for HEAD or GET requests

The request handler looks to see if you gave it a “GET”, or “HEAD” request and determines if it should respond with a 400 error. Once it’s done, then it’ll parse the path and if you gave it something that ends in a /, it’ll append “index.html” to it, otherwise it passes it directly to open() (which explains the directory traversal) and if it returned a valid file descriptor, then it could open it and it’ll return 200 with its contents, or a 404 if it couldn’t open the file.

Press enter or click to view image in full size

If we find a file, 200; otherwise 404

The send data function simply takes the socket and fires all the data in a string until it reaches a null character. A note that this is not called when sending the contents of a file, which just directly calls the send() function to the socket (meaning it is able to send null characters).

Finally, the receive data function will take the data you sent to the server, then it’ll change all null characters to 0x50 meaning we’ll need to be clever in case we need to use null.

Press enter or click to view image in full size

A safeguard is in place

Now that we know the flow of the program, we can hunt for exploits which turns out isn’t too difficult. If we take a look at the function to handle requests, we’ll see at the top, it passes a stack address into the function to receive data and at the bottom, there is no stack canary check. Putting these two together means we can control the instruction pointer, and sure enough if we send enough characters, we can achieve that.

Press enter or click to view image in full size

We control EIP! Now what?

Making the exploit

Our initial goal was to gain directory traversal to more easily read the machine, but now we can effectively control the executable, we should look further and find a way to gain a shell of some sort. Normally gaining shells in CTFs is easy because at the very least, STDIN and STDOUT are tied to the socket, but in this case it isn’t which means if we try running a shell, we can’t talk to it and it can’t talk to us. A function exists which basically ties two of these file descriptors together called dup2(). If we can call dup2 to map STDIN, STDOUT, and STDERR to the socket’s file descriptor, then we can run the shell and gain access. We have a few hurdles to outline and deal with before continuing though.

Grabbing libc

Our first issue is we don’t have access to dup2, nor execve, and this is not statically linked which means we need access to the version of libc on the box. Normally, this would be located in /lib/libc.so.6, but certain distributions like to change this on us for various reasons. In this case, it’s located in /lib/i386-linux-gnu/libc.so.6. We also need to exfiltrate this file in a different way due to browsers and programs not handling the .. sequences we’re using. I find the easiest way if Burp is still open is to send a GET request to /../../../../../lib/i386-linux-gnu/libc.so.6 via the repeater, then right click the response pane and click “Copy to file”. Next, open it up in vim and delete the first three lines and save it. Other options may include manually scripting it, but that can be left as an exercise for the reader.

File descriptors

As we create the payload, we’ll notice that we need the file descriptor for the content we send to the server. If we take a look at the server’s main function, we’ll know that all programs have descriptors 0, 1 and 2 set as STDIN, STDOUT and STDERR respectively. Next, we’ll see a call to socket() which is what the server uses to listen to requests and that takes up slot 3. Finally, the network request is accepted and passed to the forked process and that takes up slot 4. The good news is we know our file descriptor, the bad news is subsequent requests are incremented which means the second request will be in slot 5, the third in slot 6, and so on. This means unless you were counting the requests beforehand, you won’t know what to put in this position. There’s a couple ways to deal with this which is you can either set your file descriptor for the socket to a higher one and keep sending it until you get a hit, then you’ll keep track from there (this process will be covered later on), or you can simply restart the VM which will restart the count back to 4. After attempting it, it does not seem possible to actually retrieve the file descriptor on-the-fly due to the limitations we have when actually binding the sockets in the beginning.

Get Lijnk’s stories in your inbox

Join Medium for free to get updates from this writer.

ROP Gadgets

Next is because we need access to calling multiple functions, this often messes with the stack to the point where we may not be able to call everything we want. Luckily, there’s a few functions ranging from 0x8048e13 to 0x8048e2d in the program we can utilize as easy gadgets. The function addresses also contain no null characters when we put them on the stack, which is a bonus. The contents of the functions themselves do some manipulations between the registers and the stack. In particular, 0x8048e15, 0x8048e1b, 0x8048e22, and 0x8048e29 are of interest. 0x8048e15 is useful as it increments a register meaning if we play our cards right, we can bypass the null character limitation by overflowing the register from a high value to a lower one. 0x8048e1b is useful if we want to save a bunch of values to the registers and then push them back with 0x8048e22. Finally 0x8048e29 looks useful for a stack pivot.

Bypassing NULL

Now that we’ve identified initial issues, our first goal should be to bypass null so we can send anything we want to the server. To do this, we want to call read to create a new stack, then pivot into it using the information we used from the above. The way to do this is we need to load our stack to call read and pivot and place them into the registers with -1 being placed in the source file descriptor. Next, we call the function that will increment that register until it hits the desired file descriptor (in my case, I just left it as 4, but all you need to do is add more depending on what file descriptor you are currently on). Finally, we push the registers back onto the stack which when we return will call read to create a stack, then it’ll pivot there. Below is a diagram explaining what the stack will look like:

Press enter or click to view image in full size

Diagram of the stack. Similar colours mean they are related. The calls are made starting from function 1 all the way to function 9.

The next question is where should our stack be located. We need a place that is both readable, writeable and isn’t affected by ASLR. If we run the server and cat the maps file, we’ll see a list of suitable places:

Press enter or click to view image in full size

Some of these look useful

In our case, the 0x804b000–0x804c000 region looks good. Since the stack grows down, we should also choose a higher range in here so that we have enough space. It’s also worth noting the global offset table is located in this range, so we need to be careful.

Payload stage 2

Now that we have effectively bypassed the null character filter, we can continue on and get our dup2 shell running, but we’re not over with the hurdles yet. To get our dup2 shell running, we need to appropriately overwrite our GOT entries for dup2 and execve, bypass ASLR and also set up a way to execute functions via a ROP chain without the arguments stepping over each other. The latter is easy because we can just execute the function that pops the registers off after each regular function so that we’re back in line with the next function, which will make each function will be 32 bytes long and look like the following:

A simple trick to let us not worry about the arguments clashing

The former is a bit more difficult as we need to get the addresses of dup2 and execve, leak the base address for libc, do some calculations and write over the GOT entry.

First thing we need to do is get the dup2 and execve addresses we are going to use (to make things easier, grab the addresses for the CTF box and your own box so that you can have the server in a debugger while trying to figure out if the payload is correct). On the CTF box, the address for execve is 0xb1660 and the address for dup2 is 0xd7470. Next we need to leak the base address of libc. We can do this by leaking one of the GOT entries that have previously been called, then subtract its value in the libc.so file from that entry and pass it back ie: read is located in 0xd6c60 and if the entry was 0xf77e5a60, then the base address is 0xf77e5a60 - 0xd6c60 = 0xf770ee00, then call send to fire it back. Once we have the base address, we add dup2 and execve to that, write the entries in, and call.

Getting a shell

Finally, we can write the payload to get the shell and it’ll consist of the above null character bypass, then making the program leak a GOT address, we calculate the new GOT addresses and pass them back, the program writes those to the GOT and calls dup2 to link STDIN, STDOUT and STDERR to the socket and finally calls execve to spawn a shell.

Using this with the above diagram is how to get a user shell

Note the addresses used must be a GOT address, but which one it is doesn’t matter. If you accidentally overwrite one of the ones you have and still need (ie: read), then you can still just jump into its original value which will re-resolve what it was meant to be.

Once the payloads are written and we send them, we will now have shell access.

User GET!

Road to root

The first thing that’s recommended is to find a way to get a more permanent shell. That way you don’t have to mess with the file descriptor counter and having to play with the payload that way. An easy way that exists is to modify the index.php file in /var/www/html as it was set to write on other. We are in a limited shell though, but a simple echo into the file should suffice.

Something else we should note is in /etc/iptables/rules.v4 are rules that ban everything except for TCP to ports 80 and 65535, however IPv6 is free.

Next up is we can go through the whole enumeration process by curl/wget’ing some enumeration tools, but we won’t find much on there. We could look around at logs, configurations, etc. but we won’t get much further than the phs and www-data account. If we instead look back to the phpinfo() page (if we still have it up) or type uname, we’ll see something interesting with the name: Pinky’s Linux. There’s nothing totally odd about this as many CTF boxes will create their own kernel for one reason or another (namely to decrease difficulty with things like ASLR and other security bits), but since this is a difficult machine, we shouldn’t discount it. If we take a look at /etc/modules, we’ll find one module exists: pqwd, which depending on how familiar you are with kernel modules, is not one of the regular ones. This means we need to look for pqwd.ko and with a simple find, we will see it’s located in /lib/modules/4.9.110/pqwd/pqwd.ko which we can easily exfiltrate with the http server we just shelled.

Kernel modules

This module is very simple and basically gives you the shell, but there’s a few things to go over to exploit it as it’s different than exploiting a regular binary. The first thing is when you are executing kernel code, you are not in user space which means attempting to execute a shell in here makes no sense. Next is since we are in kernel space, we can also crash the machine, but since this is a kernel module, we are relatively safe from doing that and instead it’ll spam a message on the terminal screen (which we can use to debug if we broke something). The bare bones of a kernel exploit to get root is you want to call this sequence of functions: prepare_kernel_cred(commit_creds(0)), which basically tells the kernel “give me root”, and when we exit from the kernel, we will end up with root and that’s when we execute the shell.

This particular module is very simple with three functions: init_module which creates a proc file called pqwritedev, cleanup_module which removes the module, and qwrite which literally calls address 0 to which, as the name of the function implies, will be called every time someone writes to that proc file. Since that’s where we jump, that’s where our payload will go, but we also need to return or else the module crashes and so does the program. This will make our exploit really simple: map a section of memory from 0 to some address, copy our payload into there, open the /proc/pqwritedev file, write to it, then execute a shell. Our payload on the other hand will be to call prepare_kernel_cred(commit_creds(0)) and return.

Before we can create our shellcode, we need to know the addresses of those particular functions when we execute it and while they do change each time you restart the machine, they will stay the same for every program, meaning you just need to find them once per session. Thankfully this can easily be done by grep’ing the /proc/kallsyms file for those two functions and we get their values right away.

Two simple addresses which are dangerous together

Finally, we create our payload that calls those addresses and returns back to our program:

Note that instead of pushing 0 like user land programs will do to popular arguments, the eax register is used. Kernel space likes to use the registers for arguments as opposed to the stack, so keep that in mind. Also keep in mind when putting the values into an assembler, depending on the assembler used, the offsets may not be calculated correctly. This is due to how the call op code works (it doesn’t actually set the EIP register to that value, but rather adds the given address to the current EIP value to get its location). If your addresses or payload turns out to be wrong, you’ll know as the virtualbox screen will spit out a trace noting the failure, stack, and register information.

After this, all that’s left to do is to create and compile a c program which calls mmap to map a space starting from address 0 which has read, write and execute permissions set, copy the shellcode in with memcpy, open and write something to the pqwritedev and execute a shell. I’ll leave the c code to the reader as this is trivial to complete.

Root GET!

Conclusion

All in all, this was a neat box and I learned a bunch regarding how permissions work, some manual ROP chaining and some cool basics on how kernel exploitation works (even if it was a freebie!).