Let’s have a look at recent kernel root exploit.Â
Here is the source code of it, and as you can see,  we have to scroll a bit, so it’s not the shortest.Â
but don’t worry, we will go over it together.  In my opinion it’s very interesting and you canÂ
learn a lot about the Kernel with this. But let me  first show off the exploit to proof that it works.Â
First we are the anon user, then we execute it,  now we have to wait a little bit. I will fastÂ
forward. And then we should drop into a root  shell. With id I can confirm I am root.
So let’s check out how it works. This exploit is actually a race-condition,Â
so it makes sense to carefully take notes  about the order of operations.Â
So get out your pen and paper. This exploit program, which I call `hxp`,Â
obviously starts in main(). And there it  immediately calls prepare_shellcode().Â
We know from the name “shellcode”,  that it’s probably a very short code snippetÂ
that executes something useful for our exploit.  And so here we can already seeÂ
some payload looking bytes. This pointer magic, in the function, mightÂ
look confusing, but here is an assignment. So  this value on the right, is written into thisÂ
here on the left. The value is the address of  the execve function, so this address here, butÂ
subtract from it the address of the entry point,  plus the static offset 12, and 4. So basically youÂ
subtract the address of entry from the address of  execve, which gives you the offset, or distance,Â
from the entry point to execve. So why do we want  to know this offset? Well this calculatedÂ
offset is written to payload + offset. And  payload points to those bytes here. And the staticÂ
offset is 12. So 0,1,2,3,4,5,6,7,8,9,10,11,12.  So this points at this byte. And so we writeÂ
into here the calculated offset. And looking  at the comments of those bytes, we see that thisÂ
is actually the raw bytes of some assembly code. And those 5 bytes here are part of the assemblyÂ
code for a call instruction. So a function call.  And the call instruction uses relativeÂ
offsets, and not absolute addresses. So  when you want to call the function execve,Â
you have to know the memory offset from your  current point of execution to the targetÂ
function. And so that was calculated here. LONG STORY SHORT. Here we just create an execveÂ
shellcode that calls setuid(0), trying to become  root, pushing the arguments for the execveÂ
function, and then calling execve. Anyway... Next we get a shared memory area with mmap.Â
“mmap() creates a new mapping in the virtual  address space of the calling process.” SHAREDÂ
means, that two processes could read and write  this same memory area. We then places the addressÂ
from this new memory into a pointer we call  ready. So when writing, or reading,Â
the address of ready, we interact with  the shared memory. This can be used by twoÂ
different processes to share a ready value. And after that we create thoseÂ
two processes, by callingl fork().  “fork() creates a new process byÂ
duplicating the calling process.  The new process is referred to as the childÂ
process. The calling process is referred to  as the parent process.The child process and theÂ
parent process run in separate memory spaces.“ Because the child and parent get their ownÂ
separate memory, that’s why we created the  SHARED memory area before, becauseÂ
that memory will actually be shared. So after the fork, the exploitÂ
execution branches into a new process.  We have now the child and parent process.
So what does our parent do? Well it seems to wait for the child. It does thisÂ
by having a while loop checking if ready is a 1.  We set it to 0 before, so the child should atÂ
some point write a 1 into the shared memory,  to signal to the parent, that it’s ready.
So what does the child do? The child creates  a lot of strings in a loop, some pathÂ
pointing to something in the tmp folder.  You can see this very well when you executeÂ
the exploit because it prints the progress  of the paths. And for each path itÂ
calls unveil(). So what does that do? “The first call to unveil() removesÂ
visibility of the entire filesystem from  all other filesystem-related system calls (suchÂ
as open, chmod and rename). [...] additional  calls can set permissions at otherÂ
points in the filesystem hierarchy.” So this is like a defense-in-depth securityÂ
feature. A process can basically block access  to any files the process shouldn’t have accessÂ
to. And so here it just blocks access to TOOOONS  of files. Now these temp files don’t exist, butÂ
it doesn’t matter. The kernel has to keep track  of the files you don’t have access to. Mhmhhh..Â
I wonder why this is done. But let’s move on. So when the child is ready, it writesÂ
the expected 1 into the shared memory.  And now the child waits for the parent, withÂ
a while loop, checking if the memory is 2.. So let’s go back to the parent.
The parent now calls ptrace(attach). “The ptrace()  system call provides a means byÂ
which one process (the "tracer")Â Â may observe and control the execution ofÂ
another process (the "tracee"), and examine  and change the tracee's memory and registers.”
So the parent attaches with ptrace to the child. And then calls POKE, to “Copy the dataÂ
to the address in the tracee's memory”.  So this writes the data cccccccc, to theÂ
entry_point address of the child process. Then we change the scheduling priorityÂ
of the child and parent process,  that probably helps with winning the raceÂ
condition. But it’s not that important for us. Now the parent writes 2 into the shared memory,Â
which means the child drops out of the while loop,  and continues. But this time the parentÂ
doesn’t wait. Now child and parent execute  stuff at the same time.
So what does the child do? The child sleeps for a moment and then preparesÂ
a call to execve, executing passwd. And passwd  is a setuid binary, so that binary will run asÂ
root. Alright. So it simply executes passwd. But  how does this help us? This of course is not anÂ
exploit yet, it simply executes a root setuid  binary. And btw, exec means also that the fullÂ
current process is replaced by this new program.  So the child process BECOMES passwd. AnythingÂ
that would come after execve in the child,  would basically never execute. From this pointÂ
on the child should be the new program passwd. So the magic of this exploit isÂ
probably in the parent. Let’s  check out what the parent does in parallel.
Here we see a big endless while loop. And  it starts with a peek into the child processÂ
at the entry point. PEEK “Read a word at the  address in the tracee's memory”. So we read theÂ
value at the entry point, which we have written  previously to be this sentinel value ccccccc.
We also print once in a while the value we have  read. For example when the value changes, isÂ
different to the last_v, then we print it,  along with the current loop counter.
And we expect the value we read to be the  value of the sentinel. If that’s the caseÂ
we simply keep executing this while loop,  we don’t really do anything. But every 1000 loopÂ
iterations we also call CONTINUE on the child  process. For example PT_ATTACH earlier shouldÂ
have stopped the child process. So we signal  here the child process that it can continue.
We keep doing this in a loop, but if the value  we read from the child changes, we suddenly readÂ
a different value, then this code here happens. Let’s have a look at how this looksÂ
like when executing it. Because we  have here those printfs(). But let meÂ
change some stuff for better visuals. So after we unveiled all those paths as before,Â
we will eventually reach that interesting while  loop. BOOM! And now here we see many of the PEEKÂ
attempts. It kept reading from the child memory  the expected sentinel. ccccc. But after a while weÂ
did call PT_CONTINUE, to wake up the child again,  let it continue execute, and then suddenly weÂ
see the printf from the child, that the child  is about to call exec. That’s the exec of passwd.Â
After which we see a change in the value read from  the entry point. And now that the value changed,Â
the parent will drop into this code here, where it  now loops over the prepared payload/shellcode, andÂ
write it right to the entry point. And that’s it.  The parent breaks out of the whileÂ
loop and simply waits for the child. So… uhm… where did we get the shell? Well, you canÂ
kinda take some educated guesses without really  understanding what happened. We did write theÂ
prepared payload to the entry point of the child,  but only AFTER, we see that the code at theÂ
entry point changed. And how would it change?  Well the client called execve to execute passwd.Â
And execve replaces the complete current process,  loading the new binary. And this also loaded newÂ
code at the entry point. And apparently as soon  as the new entry point code was written, theÂ
parent noticed that, and quickly OVERWROTE it  with this shellcode. And this shellcode nowÂ
tries to make itself root, with setuid(0),  takes the prepared arguments and environmentÂ
variables from the original execve by pushing them  on the stack. There argv[0] was /bin/sh. So weÂ
execute /bin/sh as root. Giving us a root shell. As a regular user process you obviouslyÂ
can’t just call setuid(0) to become  root, but that’s the reason why weÂ
executed a setuid binary like passwd.  The kernel sees that this new binaryÂ
executed by the child is a setuid  binary, and then the payload theÂ
parent writes into the new process,  RUNS IN THE NEW PROCESS which is a setuid process,Â
thus allowing the code to become fully root. So to summarize the exploit here,Â
it seems to allow an unprivileged  user process to modify the CODE of a setuidÂ
binary just after it was loaded into memory.  Obviously that should be impossible.Â
That’s why this is a vulnerability.  Normally this is not possible. But here weÂ
exploit a race condition in the Kernel with  PTRACE and EXECVE. Apparently there is a smallÂ
window right after executing a setuid binary,  where we CAN modify the process with ptrace.Â
That’s a very cool and serious root exploit… So… If you have paid very very closeÂ
attention, you might have noticed that  I did not say which kernel this is. Maybe youÂ
immediately thought it’s the Linux kernel,  because this looks like Linux, right?Â
We have syscalls like ptrace and execve.  But we also have the unveil syscall. And ifÂ
you are an even bigger nerd you might know  that Linux doesn’t have unveil(). But OpenBSDÂ
does?! So is this an OpenBSD Kernel exploit? No it’s not. This is a root exploit forÂ
SerenityOS. SerenityOS is a Graphical  Unix-like operating system for x86 computers.
And now maybe you will groan “urgh”. Why would  I be interested in this obscure operating systemÂ
NOBODY USES. Let me tell you why you think that. It’s true, this root exploit is not usefulÂ
to any script kiddy who is looking to copy  privilege escalation exploits for their OSCPÂ
certification. Sorry if you were hoping for one. Let me tell you the reason why YOU SHOULD CAREÂ
if you are interested in security research. There exist several UNIX like operating systems,Â
or kernels, that are widely used. You have of  course Linux, XNU (the macOS kernel), FreeBSDÂ
or OpenBSD. And as a regular user, they feel  very similar. You have a shell. You can type cdÂ
and ls into it. Most of the programs you know  can be compiled and run on those operatingÂ
systems. But they all use different Kernels. You can see that for example whenÂ
you look at the available syscalls.  All of them share the typical POSIX syscalls. LikeÂ
open(), close(), even ptrace(), but they might  also have differences in syscalls. For exampleÂ
the mentioned unveil() syscall. But that’s not  the only difference. Because of course they allÂ
have their own implementation of those syscalls!  It’s not copy and pasted code. I mean historicallyÂ
you can trace influences and similarities,  but basically every kernel has to writeÂ
their own code to implement the same  system calls. And that’s why a Linux kernelÂ
root exploit shouldn’t work on Mac or FreeBSD. Thus also this Serenity Kernel exploit doesn’tÂ
work on Linux. BUT! If you are interested in  security research, interested in operatingÂ
systems and kernels. Would you have thought to  look for this kind of vulnerability in Linux?
This Serenity kernel exploit is still an  absolutely VALID ATTACK IDEA!, that you couldÂ
check on linux or the operating systems. Posing  the research question: “Can you somehow PTRACEÂ
a setuid binary from an unprivileged process?” Hell, there is even a windows equivalentÂ
of that. Can you debug a SYSTEM process  from a regular user process? If you could,Â
you would have found a local root exploit. So yes “SerenityOS” is not an operating systemÂ
anybody seriously runs in production. This  particular root exploit is “not useful”. But theÂ
attack idea is useful. And you who watched this  video, you just learned about this idea. And ifÂ
you ever try to do security research into Kernels  or anything else where this is a separation ofÂ
privileges, you hopefully remember this Serenity  OS root exploit and you ask yourself: “is thereÂ
a race condition where an unprivileged component  can influence a privileged component. AndÂ
could this lead to a privileged escalation?” In the next video we will have a closer look atÂ
the kernel and why this race condition happens. But if you can’t wait, go watch the actualÂ
developer of Serenity OS going over this exploit,  I link it below. It’s awesome.
And by the way, for all the CTF haters,  this was a CTF challenge called wisdom2 from the  excellent hxp CTF. Another greatÂ
example why CTFs are awesome.
in SerenityOS
Is there a CVE or blog post that explains this in prose? I can’t follow YouTube code walkthroughs.
I wish he actually talked about what the race condition is.
I was hoping this would be in Linux for some reason.
yes, wait for child
The more important bit of information being that SerenityOS supports ptrace.