Last updated
Last updated
There are two vulnerabilities - one in create, one in append. Show and delete are perfectly safe.
You may notice there are two different lengths or sizes the program takes in when creating - content_length and size. To grab your contents data, it uses the read_contents function
As you can see, it uses kmalloc to get a page from the kernel with content_length being the size, and uses copy_from_user to copy into it. Nothing vulnerable here specifically, however
The program kmallocs a new page for the nut contents, but this one's size is the size parameter. Furthermore, it memcpy's size bytes into it. The contents pointer from read_contents is from a chunk of size content_length, but size bytes are copied from it. If size is greater than content_length, that's an out of bounds read. We will use this later to leak.
In the create function, the size is validated before usage. In the append function, the new size calculated is validated, however the size of the data you are appending is not. This doesn't seem consequential, but due to an error in the read_size function, it is.
The program doesn't check if the size outputted from read_size is -EOVERFLOW. Which means, if we send a size such that it is less than 0 or greater than/equal to 1024, it will return -EOVERFLOW as the size. This will be added to the original size of the nut we are appending to, giving the new size.
EOVERFLOW is 75 in the generic linux implementation. This means -75 will be added to get the new size - the new size will be smaller than the old size. Since all of the original data will be copied, and then some from our own contents, this gives oodles of overflow. More specifically - an out of bounds write. More specifically, we get 75 bytes of overflow from the original nut, and an additional -75 & 0x3ff(0x3b5, because of how memcpy_safe ANDs the difference with 0x3ff)
In kmalloc-32, we can cause a seq_operations struct to be allocated by opening a /proc/self/stat file. Said structures are freed when we close the file. What we'll want is for the allocation is to have a seq_operations struct right after it. We can achive this by allocating 20 structs(for good measure) and freeing every other one.
We don't actually need this, but for good measure, the kernel heap can also be leaked. msg_msg structures are very easy to allocate, free and control the size of, making them invaluable for heap spraying. They can be any size from 0x31(controlled by message length) - I decided to target kmalloc-92. For this, a message length of 48 is needed.
We can run msgget, store the qid, and then msgsnd to allocate a msg_msg structure. The beginning of the msg_msg structure has a pointer to the previous structure, so we'll need 2 with a free chunk inbetween.
Now for the final part - gaining the write.
Dumping the kmalloc-92 slab, we see the freelist pointers are in a normal order, and are not hardened. Its a singly linked list too, meaning with the overflow we can overwrite a pointer to force kmalloc to return a chunk wherever we want.
I spammed a few chunks to get rid of any possible free chunks in between other chunks. Then, I used pattern.py to generate a pattern which I dumped into the contents before appending. By appending to a chunk which is 0x60 + 75 in length, we get overflow on a chunk which is in kmalloc-92. When we allocate again with both sizes 0x60...
When trying to grab the next pointer, it will reach a fault, attempting to access controlled pointer + 0x60. When the fault happens, we can subtract 0x60 and use pattern.py to find that the offset until the controlled pointer is 357.
Exploit path:
Now... we have a leak of the kernel base, so where to write?
The easiest place to write to is a symbol in the kernel called modprobe_path
. When the kernel tries to resolve how to run a file with an unknown header, it will call the modprobe binary. The path of the modprobe binary is stored in modprobe_path
. And, best of all, it'll be run as root.
\xff\xff\xff\xff
is pretty much guaranteed to be an unknown header. If we try to run a file consisting of this, the kernel will run modprobe as root. If we've overwritten modprobe_path with the path to a bash script which has some malicious commands.
/home/user/w will copy the flag to the root directory and make it accessable to all users. All that is needed now is to set modprobe_path to /home/user/w.
After doing this, we can extract the flag.
If we, for example, send a content_length of 0x20 and a size of 0x60, 0x40 bytes outside of the first contents chunk will be read. I used to find a list of useful kernel structures.
I copied my modprobe_hax function from fizzbuzz's hashbrown writeup() and changed the commands to fit.