Nightclub was a heap note like kernel challenge, that let us add, delete and edit a chunk. It also provided a partial leak, returning the lower 32bit of the relative position from edit_chunk to kmalloc.
The add, delete and edit functions had those weird address checks on top
which I just quickly interpreted as pb’s mean way to disallow us to directly edit or add a chunk over modprobe_path. As it turned out later, this was meant to be used for bruteforcing kernel addresses to get the needed leaks. Well, I just ignored it completely and went a different route :’)
Let’s check the rest of add/edit/delete
Simply allocate a chunk and put our message into it. Though terminating our message with a null byte will lead to an oob null byte overwrite of the follow up chunks next ptr, if we provide a msg_size of 0x20 (didn’t use this, since we have the same in edit_chunk also).
This is getting more useful. Though, it will check that our msg_size is not bigger than 0x20 and offset isn’t bigger than 0x10, if we use those limits, it will allow us to overwrite 0x10 bytes of the follow up chunk (which will be next and prev pointer of the following chunk).
It also has the same oob NULL byte overwrite (when specifying msg_size0x20 and offset0x0) as add_chunk.
This will unlink our chunk from the doubly linked list and free it.
Soooooo, since I totally ignored the intended way to leak kernel addresses, and next and prev pointers were overwritten in del_chunk, I first had to come up with a way to find some proper leaks, to do anything useful with the overwrite in edit_chunk.
Since our messages will always be null terminated, the only thing we can do without knowing exact addresses is to overwrite the LSB of the first qword in a following chunk with a null byte. Putting a msg_msg struct in a freed chunk will also not help us much, since freeing it, would break the message object and we wouldn’t be able to retrieve it anymore after that.
But… a msg_seq object would stay totally fine :)
So, for leaking, I allocated six chunks, deleted the third chunk (which will be at an address ending in a null byte) and sent a message with size 0xfd0+0x20+0x80. This will create a msg_msg object with size 0xfd0 and a msg_seq with size 0x80, thus landing exactly in our just freed chunk.
Then we can use the lsb null overwrite to overwrite the next pointer of the fifth chunk, making it point to the msg_seq object. Then we can just free that chunk, which will trigger the unlink in del_chunk.
After deleting the first two chunks, memory will look like this
With the null byte overwrite we changed the next ptr from chunk 5 from 0xffff888003fcf580 to 0xffff888003fcf500, now pointing to the freed chunk 3 (uid[2]).
Allocating a message with size 0xfd0+0x80+0x20 will then put a msg_seq object in freed chunk 3.
Freeing chunk 5 will now trigger the unlink in del_chunk, which will write the prev pointer of chunk 5 (0xffff888003fcf680) to the next pointer of chunk 3, which is our msg_seq object.
Now we can just msgrcv the complete message, which will contain the prev pointer at offset 0xfd8 :)
Though in hindsight, it wasn’t really needed, but at that point, I was so afraid, that something might break later on, that I decided to “repair” the “heap” and fixed all addresses in the chunks and reallocated them to get back into a “clean” state…
Still, we only have a leak to our kernel heap, but don’t know the kernel base address. But since the module gave us the offset from kmalloc to edit_chunk, all we need for this is a leak to a module address.
Since we now know, where our chunks are allocated in kernel space, we can use the 0x10 byte overwrite to point the next pointer of any chunk to an arbitrary address inside the heap.
With this, we can now create a msg_msg object with size 0x80 and let a next pointer point to it, to edit its size to leak the complete heap.
Also the last allocated chunk will have its prev pointer pointing to master_list, which is in the data section of our module, so we just have to allocate one chunk after the msg_msg object to get that.
The next pointer of chunk 0 will point to msg_msg->msg_type - 0x60, so that its message will overlap msg_type / msg_size / msg_next of the msg_msg struct. The msg_uid for this fake chunk now is inside the message from chunk 0, so we can just use 0x4242424242424242 as msg_uid to edit the fake chunk.
Ok, getting somewhere :)
Since I was still convinced at that point, that those address checks were meant to not allow us adding/editing chunks anywhere outside of the module heap and I wanted to overwrite modprobe_path, I decided that I need another leak to freelist, so that I can allocate a msg_msg object directly on top of modprobe_path.
While I found a leak in my local qemu environment in the same region as the notes were placed, when I finished the exploit and tried to run it remote, it was nowhere to be found anymore :’(
In the end, finding a proper address to leak from, which works local and remote took more time than the complete exploit itself. But, after some time, I found a stable leak at modprobe_path + 0xec520, which worked on both environments.
To read it, I created another msg_msg struct in the module heap and overwrote msg_size and msg_next of it. Receiving that message, will then also read the msg_seq part from our corrupted msg_next (modprobe_path + 0xec520), with which we get a leak from the kernel memory region of the freelist for 0x80 chunks.
Now we can just overwrite the freelist pointer with a pointer to modprobe_path-0x30.
And all that’s left is to allocate another msg_msg with which we can now overwrite modprobe_path and trigger the usual copy flag modprobe exploit.