This challenge was part of the Full Chain series, in which we had to exploit a kernel module to achieve privilege escalation in the kernel part.
The rose module itself didn’t really do much, except providing a device, which can be opened and closed.
On open, the device would kmalloc a 0x400 chunk in kernel heap, which it would kfree, when the device gets closed.
The data pointer is shared between the devices. Thus, we can use that to free the data block over and over again, by opening multiple devices (all pointing to the same data block) and whenever we need to free the data block, we can just close one of the devices.
With this, we can free the data block, put another kernel object into that chunk and free it again via another open rose device.
While using ttystruct might have been useful here, /dev/ptmx wasn’t accessable for normal users. So, while searching for useful kernel objects and testing out different approaches, creating and writing to pipe objects filled up the freed data chunk with pipe_buffer objects.
But to be able to do something useful with them, we would need some kernel leaks first. I thought of using msg_msg objects to read the leaks from the freed buffer, but MSG_COPY didn’t work for me and calling msgrcv on a message in the chunk, while it’s freed would result in a double free kernel panic.
To get the initial leaks, I did
open multiple rose devices
free data buffer
send a message with a size, so it would put its msg_seq buffer in the freed data buffer
free data buffer again (so msg_seq chunk is also freed)
create a pipe and write to it multiple times to fill up the data buffer with pipe_buffer objects
free data buffer again (so the chunk with our pipe_buffers is freed again)
send a message on another message queue with a smaller msg_seq (but big enough, that it will still get into the freed 0x400 chunk) and align it with one of the pipe_buffers
read the message from the first message queue (which can read the complete 0x400 chunk) and get the leaks from it
With those leaks at hand, we could now repair the data chunk by filling it with valid pipe_buffer objects again, so that we can read and write from them.
Getting the flag quick and easy (dirty one)
Having not worked with pipes by now, I struggled quite some time to get the pipes working in a way to do specific reads. But I was already able to use them to successively read the whole kernel memory.
At that point, I opted for a quick ctf solution: Since the flag file is in kernel memory, why not just read memory until we find the hitcon string ;)
Might take some time, but will ultimately find the flag (on remote it was pretty quick).
Not very elegant, but a flag is a flag, so the challenge was solved for now. But that would not help us, if we would want to do the umi challenge later on, which required to successfully go through all Full Chain challenges.
Getting a proper root shell
We can use a similar approach like searching for the flag to get more information about kernel addresses.
I did that to find the rose module itself and then read module offsets from it.
But at that point, I still didn’t knew how the page address from the pipe_buffer object needed to be filled to do specific read/writes, it was more like randomly reading kernel memory, so I took a break and went on with the maria challenge.
When raj joined later on, we took another look at the challenge, since he had already done some exploits with pipe_buffer and extended the exploit to scan for the passwd file instead, overwrote it and then just su‘d into root, which gave us a root shell.
That also contained the missing pieces for me to get a better idea of pipe_buffer.page, which I used afterwards to extend the exploit into a more arb read/write style later on (though this was just for practice and to have it written down somewhere for future ctfs).
So, in find_passwd_off we use the pipe again to read kernel memory until we find the content of /etc/passwd, but this time taking note of the offset to our data chunk. Knowing the offset from the current data page, we can properly setup a pipe_buffer to overwrite data at a specific address.
After searching the offset, we just fixed the pipe by creating multiple pipe_buffer objects pointing to /etc/passwd and wrote them back into data. Then we can use the pipe to overwrite the data in /etc/passwd (put a known password for root). All that’s left is to execute su and manually enter the set password (passwd) to get a proper root shell.
Enhancing the exploit for arbitrary read / write
Though, after getting the idea of using the offset to a known address for setting up proper pipe_buffer objects, I cleaned up the exploit and extended it a bit.
To find an offset to a known address in kernel mapping, I just used the same approach again to find modprobe_path.
Since we now know the offset to modprobe_path (and we also know the real address of it), we can now calculate offsets to any other address based on this.
Though this was just to get a better understanding of pipe_buffer, these functions could now also have been used to find specific functions or rop gadgets in the kernel (defeating FG-KASLR).
If using su would not have been available, we could have used this then to hunt for gadgets and prepare a rop chain instead.