Insomni’hack finals – SH1TTY writeup

This challenge wasn’t solved during the CTF, but StratumAuhuur was pretty close!
The source, binary and exploit for this challenge can be found on our github here.

Description: “Can you write a kernel exploit with your bare hands?”
Also because our theme this year was trolling hollywood hacks, the following video from NCIS was linked: https://www.youtube.com/watch?v=u8qgehH3kEQ

sh1tty was a very basic kernel keylogger, implemented as a module. There are various places where such a keylogger can be implemented, I chose the TTY layer. To do so it creates a new line discipline (ldisc), inherited from the base N_TTY one, replaces that ldisc’s ops->receive_buf2 function pointer with the keylogging part, and replaces N_TTY with our new ldisc.

It had two modes: DUMB and SMART (not so much, but meh.).
In DUMB mode it logs every input to /root/log_tty<TTY_NUMBER>, and in SMART mode it restricts the keylogging to passwords only, and stores it to /root/pw_<TTY_NUMBER>.
To switch to SMART mode, one only needs to type G1v3m3p4ssw0rdz on the keyboard (no <enter> required!).

In SMART mode, before a password is written to the log file, it is formatted using a stack variable in the log_bytes_to_file function, which can be overflowed:

#define BUFFER_MAX_SIZE 512
#define LOG_MAX_SIZE 200

Where BUFFER is our heap buffer that contains all keys pressed before r (yeah, the kernel receives a r when you press enter, not a n, surprise?), and LOG is the stack variable. A dumb vuln for that so called smart-mode, right?

Now this would look like an easy win, but there are a few things that hinder exploitation.

First, you cannot write to any file on the filesystem, all are owned by root (on purpose, obviously), which means you cannot just upload your exploit on the VM and return to userland. fu?

So you’ll need to stay in the kernel. Stack and heap are not RWX on linux x64, so you’re likely going to need ROP.
No real trouble if the payload is simply commit_creds(prepare_kernel_cred(0))

But, the second thing is, during the exploit triggering phase, you are not executing within your shell task’s context, but in a kworker. I don’t know exactly how these work (and more generaly, I have no idea what I’m doing :>), but that raises the following problems:

  • the generic privilege escalation payload from above wouldn’t be executed on a useful task
  • we don’t know the kworker‘s userland, so appending a “swapgs ; iretq” gadget to our ropchain would probably not work as we are going to crash in userland, and who knows what will happen with a kworker

To work around these issues my idea was to execute the following payload and to clean up the stack and registers so the kernel continues peacefully:

pid = find_get_pid(shell_pid);
task = get_pid_task(pid);
creds = prepare_kernel_cred(0);
task->cred = creds;

3 ways to do this (that I can think of):

  • full ROP: no reason it shouldn’t work ; I was simply concerned it might take too much stack space and would’nt be as easy to test & fix eventual stack/register cleaning stuff
  • make the kernel stack executable with set_memory_x() : I tried but that wouldn’t work except if run in a loop for some reason… if anyone has any idea why, please tell me! (flush issue?)
  • move a shellcode to an already RWX section

While the last two solutions are not perfect (wouldn’t work against grsec for example), I chose to move a shellcode to the keylogger module’s bss section, which for some reason is RWX…

The full exploit is included in the source code. It’s not perfect but it does the job reliably as far as the challenge goes.

awe@awe-laptop ~/insomnihack/sh1tty/chall $ ./exploit.py
[+] Connecting to server...
[+] Going to SMART mode...
[+] Getting our shell task's pid...
[+] Obtained current task (shell) pid: 428
[+] Typing a password...
[+] Generating shellcode...
[+] Sending payload...
[+] Interact...
stty -echo
[PEXPECT]$ [PEXPECT]$ [PEXPECT]$ [PEXPECT]$ [PEXPECT]$ sh: ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������BBBBCCCC=�����p: not found
[PEXPECT]$ [PEXPECT]$ [PEXPECT]# [PEXPECT]#
[PEXPECT]# id
uid=0(root) gid=0
[PEXPECT]# cat root/flag
INS{My name? I've had a few. You can call me root.}
[PEXPECT]#

Congratz to tsuro from Stratum for being so close to solve the challenge during the CTF!