
uh yeah welcome everyone uh to this talk titled Elevate and Conquer uh which is a story about our journey into kernel privilege escalation my name is t MERS uh often mispronounced uh it's a Dutch name and people often call me titch me gomers um but it's actually an i in the Netherlands uh and I started hacking when I was around I guess 1 years old uh I hacked some stuff learned about ethics and then I uh started a software engineering study later on a information security study uh and I eventually landed a job as a red team operator uh at North Wave cyber security which is a uh uh cyber security company based in the Netherlands and I work there now as
a red team operator for about 6 years I guess um and besides red team operator uh I also spent time on security research and recent recently I focused a lot on Kel driver research finding Kel driver vulnerabilities and I actually do that together with a colleague uh this colleag is called Alex um and Alex is actually the smart guy of the two of us uh he's a former mare analyst vulnerability researcher and mare developer uh and he actually lives in Japan uh so unfortunately he couldn't be here today but the research that I'll share today uh is actually a shared research of the two of us now why do we perform kernel driver research uh not only because it's fun uh
but also because we can learn from it and we can actually outpace the bad guys we hope to find uh kernal vulnerabilities before they do and we actually hope to get them fixed before they can actually be uh abused and by doing that research we actually gain a lot of knowledge uh so we gain knowledge about the windows internals and how the kernel works and operates and how we can exploit it um so for the vulnerabilities that we find we actually try to uh build full fully functional working exploits which we can then um abuse during actual red team operations so we can really let our customers experience what it's like to be attacked by an advanced threat actor
uh that has the resources to actually uh perform zero day exploitation and this talk uh is actually about one of those zero days that we identify during a red team operation uh and that we were able to exploit and this driver is called JN or GN uh PR rtdi dosis where the JN actually stands for Juniper Networks I guess um I'm not too sure about that actually uh and Juniper Networks does anyone know that company a lot of people do uh if you Google them to date uh they actually state that they are leading in AI um not sure what that's supposed to mean because they're a networking company uh but yeah currently they're still busy doing a lot
of AI stuff um and back in the days uh Juniper Networks together with Juno's pills actually created a new company called pill secure and I guess many of you know pill secure it's a pretty popular VPN client used by around 40,000 organizations worldwide I guess but P secur doesn't really it exists uh but they actually have recently been acquired by another company back in 2020 called ianti uh and ianti Reen the PE secure to something that we see on the right here called ianti secure connect or secure access client they have all kinds of marketing names for it um but it's basically just a guey for VPN uh and a server Appliance included with it um and
it looks like that on your on your laptop underwater it's actually still just P secure uh the driver that we're talking about the gmpr tdist driver uh is actually a driver that has been in there for I I guess 10 years or so and it's never really changed so to understand what we found uh I want to share some of the basics of kernal exploitation and how that is usually performed so we do a quick recap on uh how virtual memory Works in computers but also how you would actually talk to a kernel driver and how you would perform something in a Windows kernel um because that's important to understand if you want to understand the
full exploits that I'm going to show afterwards we'll look at our approach um and I'm going to show you how you actually can find during red theming operations or just on your own computer um how you can actually find lwh hanging fruit in kernel drivers possible vulnerabilities or abuse paths for example I will just briefly uh go into this this um uh because uh most of the time I'll spend you explaining how the actual exploitation process works and how you overcome typical restrictions uh when performing kernal exploitation if that works uh and we'll have to see if we can build a fully functional exploit uh hopefully we can also show a demo uh of exploiting an
actual system uh or if that doesn't work we'll face other defeat unfortunately so let's dive into the recap virtual memory and talking to Kernel drivers so every laptop Windows OS um has physical memory right maybe you've got 8 GB of memory or you've got 16 GB of memory um well virtual memory you've actually got much more the windows memory mapper manages your virtual memory and every process in the Windows operating system actually has 128 terab of virtual memory now that doesn't fit in that 8 GB of physical memory so when you use more than 8 GB it is actually written to a pagefall on disk some sort of cach file now the higher addresses in the
virtual memory range are all reserved for the kernel so n2s kernel. exe which is just a PE file that that the computer runs uh which hosts the kernel uh and there's a lot more stuff in there like the kernel drivers and other kernel stuff then in the middle we have a bit of unmapped memory which isn't really used and the lower addresses are actually all reserved for user processes uh so whenever you spawn a notepad um you will end up in those addresses whenever you spawn your calculator you'll end up in those lower addresses and the funny thing is um those processes so your notepad and your calculator will actually have the same vir World memory addresses so use a
process a and and user process B can both locate the same virtual memory address and host different data on it and this is something that the virtual memory mapper of Windows um manages for you to ensure that there are no collisions in memory now if you want to go from a user process to the kernel uh you cannot just dreference a kernel address uh to read data from it that doesn't work uh the veral memory mapper will prevent you from doing that because you're not authorized to do so uh so this doing this like what I show here in the pseudo code uh it will just press your program and actually it's the same way
uh the other way around so whenever you try to dreference a user address from the kernel uh that will not work and it will actually cause a bug check and because there's no exception handling in the kernel it would crash your entire system and you would get a blue screen um now that's quite interesting why is the kernel not able to read virtual uh memory of a user process uh because it doesn't have the context by default because if you dreference that address how would the kernel know from which process to read the memory from well if you give the kernel a little bit more context about which process it it should read it from uh
then it is possible of course uh so then you can dreference virtual memory of a user process now in kernal exploitation you would want to go from from the user process space to the kernel process space and how that usually works in kernel exploits is by talking to certain kernel drivers that has the facility or some sort of API uh that you can talk to from a user process and this is how it looks like in pseudo code so on the left we see our user process which could be run as a low privilege user and in this case it's our exploit. exe and and on the right we see a kernel driver uh some
pseudo code of Kernel driver on the left here we try to talk to a certain kernel driver um that is able to update the bios so this could be a bios update utility and we just Define a buffer uh which is our new bios which is in this case four pseudo bytes we Define the length and then we call a Windows API that you're able to call in your C code as well uh called device IO control and it accepts four and even more arguments the first one being a handle to the driver that you want to talk to uh which just uh in this case is the BIOS update utility driver the second argument in this case
is the 8,036 it's called an iocl and iocl actually stands for input output control code um and this is just a number representing aun function in the driver it's as simple as that um so you need to determine which number or which function you want to talk to or you want to call on the curent driver and reference that number you could compare this to a HTP API where you have like SL user SL update or/ user delete in this case it's similar and then the third and fourth argument are the buffer that you sent to the driver and then the Kel will actually send an a to the driver and an a stands for input output request packet
and these are basically packets used to communicate within the kernel um you could compare this to for example that HTTP request or a TCP packet which contains a certain stru of data in a certain structure now in the kernel driver A dispatch function will be called with your buffer and this dispatch function is basically a large switch statement which contains all the ocls that the driver supports um and in this case there are three of them so the 72 one is for example able to read the current BIOS version and send it back to your userland process so you can read what current BIOS version is installed the second one is the one we called on the left it's the, 36 one
which actually updates the current bios of the system which you can do in the Kel uh to the given buffer which we in this case set to four bytes uh now usually this isn't possible of course because you need all kinds of uh authorizations for this um but yeah in this case it's Pudo code and this is how that works now we've got another one currently not implemented so with that knowledge uh let's take a look at the ianti driver that we identified the gmpr TDI one um and what our usual approaches to finding bucks in these ocls well what we do and this is something that you can do to date is we Lo we load the cisf fall of the driver
which is in system/ Windows SL Sor uh c/ Windows system32 drivers you've got all your driver files there we load it into Ida and you can actually use the Ida free version or the Ida pro version if you have it um and we search for the a MJ device Control Function which is the large switch statement that we just saw it's always just one function that you can look for um and you can search through the large swi statement for all the isls that the driver supports and see what they do the isls you can reverse them and see if your user buffer ends up in some juicy kernel function for example some memory move in the
kernel or another function that you could abuse lately you see a lot of vulnerabilities where drivers call Z uh ZW terminate process by ID and you can pass a process ID to it and it will terminate that process for for you and because it's coming from the kernel you could for example kill Microsoft Defender you could kill antivirus processes with it that are PPL protected um besides that besides loading it in Ida we also run a fuzzer uh but a bit of a smart fizzer that is able to F those ioctls specifically and one of them that you can use is iocl BF which does specifically that it looks for the I CDLs in an automated way and
it just tries to send all kinds of weird buffers to it to see if the uh uh driver crushes and we did the same we loaded it an Ida R the fizzer uh and Bam blue screen and you can actually see here what field uh it's the gmpr TDI driver so there must be a mistake in here um so we loaded up uh we loaded it in Ida again without running the fuzzer and we took a look at the assembly code now if you don't know assembly uh don't worry I'll explain it a bit High over um just assume that what I say is true um we've got the air MJ device Control Function right here which is the
large switch statement and first off it will move our user buffer into RBX the register which is okay we can do that afterwards it will actually try to dreference that pointer um which means it TR to tries to get or read memory from the address that is located in RBX and it turns out that the fuzzer actually sent n to the driver and so it is not able to dreference that address because there is none uh and it B checks the system and because there's no exception handling in the kernel you just get a blue [Music] screen now even if you passed a valid uh buffer to it which did container pointer a few assembly up codes later it will
actually try to dreference something again a level deeper uh and it would crush the system again uh so you would need to really craft a specific payload or buffer to get this working now knowing that there's such poor input validation um maybe even no input validation at all uh we didn't see any um there must be bugs in here uh apart from the den off service that we already found because we can crash computers and so we took a look at what this is TL that we saw and that CR the system actually does and this specific iocl code is 2018 uh and you can look it up in this driver of ianti today but just
installing the ianti software they fixed it but it's a hot fix um and it isn't really applied yet in the actual main uh Branch the trial Appliance of eon yet here we have the assembly code that we took a look at a few slides before the next thing it does is actually to call IO csq remove IRB um which is a function in N US kernel. exe so it run something in the kernel uh and we didn't actually know what this function was or what it did uh so we took a look and it turns out that iocs remove a can actually remove an a which is the input uh request packet input output request packet the TCP packet of the
kernel um it removes such an a from the queue of the driver and it turns out that kernel drivers have a queue of herbs that they need to process um and hers can come in faster than that the driver can process them uh so that's why drivers use a queue and these airs contain actions that the driver needs to uh process now is csq remove air accepts two arguments the second one being context of which a to remove from the queue so this could be an identifier some sort of uh number for example nothing really special but the first argument is a bit more special uh and this is actually a struct of callbacks callbacks that are
called within the kernel and these callbacks can for example include um the Callback to call when inserting an herb or when removing an herb from the queue uh but also callbacks to acquire a certain mutex and release a certain mutex uh so this fun function the ioc is could remove actually helps to remove herbs from the queue in a fret safe way uh by using callbacks to acquire and release mutexes that you can Define yourself now of course you can also use this function uh to do some something completely else than acquiring mutexes uh you could pass your own uh uh kernal functions to it in in theory in practice this doesn't work because of some
mitigation called control flow guard but you could pause uh you could for example use it to keep track of a counter of how many ears there are on the Queue so you could Define your counter you could Define two callbacks on insert a which increases the counter uh on remove a which decreases the counter uh and then you could initialize the struct with those callbacks and eventually you call ioc csq insert air with the callbacks that you defined and the a that you want to insert and then the call will be C and it will increase the counter by one and the same counts for I screw remove a you pass the struct callbacks again uh and the a that
you want to remove from the queue and it would decrease the counter and it's zero [Music] again now bear with me uh this is how iant calls ioc screw remove a they actually use as the first argument the callbacks and as the second argument the air to remove but it's always in both of the uh arguments are user buffer from our user process so we can actually Define which callbacks are being called in the kernel and this is something that we can likely exploit let's say we want to um for example run a function in the kernel called Hall make beep uh which I'm sure many of you know it's actually when you boot your computer it will make a beep
sound while this is caused by the function H make beep that is called in the kernel so we just initialize a struct of callbacks to three times homeg beep the acquired lock will be home beep instead the remove air function call back will be home make beep instead uh and the release mutex or lock will be H make beep instead as well so we open a handle to our driver we then call the device iio Control Function with that handle to our driver uh and the ictl that we call in this case 2018 uh and our struct of callbacks and what will happen um the windows kernel would then actually call the driver uh dispatch function the driver dispatch
function will call the I screw remove air function with those buffers that we defined and then NS Kel will eventually call those free callbacks uh so you yeah it will actually call H make beep three times for us uh and our computer will beep three times um and this is actually how that looks in actual C code which you can use to exploit this vulnerability to date I'm not going to explain the C code um I'll just show an example that it works in uh in a Windows debugger so let's put a breakpoint on that hem make beep function and let's run our exploit and see if it works uh well it does and we
can see here that it hits the break point uh so we are actually able to run H make beep now if you press on the left top go again it will continue and it will finish the uh the userland process and we called home make beep and so we did uh but again it cred our system unfortunately so something funky is going on and it turns out that after the icsq remove air function the driver will actually call free pool with stch uh which will on our user buffer and free pool with stack is a function in the kernel to free or deallocate a certain pool of Kernel memory now our user buffer isn't really uh kernal memory
it's is virtual memory from a user process so this will actually CR the system because there's no exception handling in the kernel now as the user buffer is expected to be a kernel pool of memory uh likely ianti never imagined that people would call this kernel uh driver from a userland process they likely designed it to talk from kernel driver a to this kernel driver or talk from kernel driver B to this colel driver because then it will be a valid kernel pool of memory so we've got a lot of constraints to actually build a fully functional exploit that does something useful for us for example code execution in the kernel or privilege escalation um and these are some of the
constraints that we work with so there's the free pool of Stack that we need to somehow bypass because it would crush your system and the expert will be useless um but also the free callbacks are called with guard dispatch I call which is Kernel control flow guard which is a mitigation in Windows that ensures that the functions that can be called need to be in N kernel. exe you cannot just pass your own Shell Code to it and execute it that won't work and it would cause a blue screen as well and there are some other limitations um for example that the functions that are called um we have only limited uh ability to change the
arguments that are passed to those functions because they reside in registers that we cannot really alter or change and fun fact some of the functions that you call in the kernel actually overwrite registers as well which would mean that the second and the third call would have different arguments than that we know up front so there would be random arguments passed through those functions um now this might be a lot of information to process and I do not expect you to uh to process this um instead I made a more visual slide uh in which we show the IQ remove a function in Ida and we can see here the free calls being made by guard dispatch I call and
what kind of control we have in those calls so for the first call we can actually Define which function in the kernel should be called we don't really have access to modify the first argument it is actually always the address of our user buffer which we did not really control the second argument is always zero uh that's up as well we cannot control it either and the third argument is always our current ictl we cannot control it either it's always the 2018 ictl and this is actually the same for the third call being made uh we don't really have any way of modifying those arguments apart from the second call the middle one again we can Define the function
being called uh again the first argument is always the address to a user buffer which we did not really control but the second one the second argument is actually a value that we fully control and the third one is again the 2018 iocl so we need to find the function that with those constraints uh can do something useful in the colal like privilege escalation and my colleague Alex actually achieved this uh by making a right what where exploit which allows which allows us to write a single bite to a certain kernel address um that we Define and he did that by doing the following first we needed to bypass the free pool with tech because otherwise it
would CR the system um but actually all the code paths would end up in that function so what he did was he defined the third function in the Callback the third callback to a function called KX weight for spin lock and a wire which is actually a function that accepts one argument which is always our user buffer address um and waits for it to be zero and only if it's zero then it will continue the code execution um but we can ensure that it's never zero because it contains our user buffer uh so this would mean that when defining this uh function as the first callback the code would never continue and it would actually freeze that current threet and
it would never reach the X free pool with tech and does never crash our system so that's one problem solved or so we thought turns out that calling this uh this weight for spin lock would actually increase CPU usage by 50% um and then we've only written one bite to the kernel uh so we likely need to do this eight times to write a pointer or something uh something juicy um which would spike up the CPU usage by 400% uh which makes your computer unusable but we fix that as well by using a function called set threet priority uh in which we set the current threet to the lowest priority giving priority to anything else that happens
on the system uh and this way it would actually work for the second call we found a function called right charar zero in the kernel and this is quite a special kernel function because it actually uh it writes a certain bite to a certain address um but it accepts a destination address as the second argument well usually in the kernel with M set or me move um the destination address is always in the first argument so this is quite a special function and it accepts our um uh it accepts our ictl as the third argument um but in this y Char zero function the third argument is actually used as a counter to increase or
decrease now our ictl 8,000 20 8 is not a valid kernel address and it would D this function right zero would dreference that address uh but it would not exist so it would B check the system and CR the system but apparently calling device iio control uh to talk to the driver actually gives the kernel context of which process is talking to it which allows the kernel to dreference this address if we allocate it in our user process and so we did we allocated that specific specific address set it to zero uh and now this right zero function was actually able to increase or decrease that value on that address so for the first argument it's
always the address to our user buffer which we did not really control and right Char zero uses it or at least it uses the least significant bite of that address to write to a certain kernel address and what we did uh we defined a large pool of user process memory and we used an specific offset to place it on our user buffer to define the least significant bite so let's say we write our user buffer to n uh X th000 um then zero will be written to the kernel uh but let's say we allocated to n x uh 1001 then one would be written to the kernel and thus we control which bite is written to the
kernel and the second value we control it fully uh so we can use a kernel address add of choice there uh to write the B to and then we only have the the first function left that we need to do something uh Stupid with because we don't need it uh first we thought of using homemade beep so whenever you write the pointer the computer with beep eight times uh that was actually not really useful in Red theme operations um and besides that hom make beep actually overrides some registers which uh does some funky stuff with the other arguments so instead we chose to change it to Ka test spin lock which is a function that does barely anything uh it
just checks if a value is one or zero it just doesn't change any registers at all and so our exploit seem to work but there's one thing left one constraint and that is that we need to the callbacks that we Define they need to be addresses in the kernel but we don't know all the addresses of the function that we uh functions that we want to call for for example the right Char zero function is not an exported function in the kernel so you cannot really find the address of it so what we did is we defined something called uh kernel patent searching or kernel egg hunting uh in which we just Define a pattern of how that RAR zero function
look like in op codes uh and we try to search for it in the NS kernel binary uh and when we find that pattern we know that we found the RAR zero offset and based on that we can Define uh where in the current this specific function is located and so we did and that worked uh and we could actually try to write uh some bytes to the kernel so we installed ianti um and uh uh yeah we install the driver the software and we tried to exploit it but then we had the biggest uh face po moment ever turns out that this driver is not disable it's not enabled by default it's actually disabled by default uh so you cannot
really exploit it but uh I'll not leave you with such an anticlimax um actually ianti provided this with a tool called pulse launcher.exe which allows you to from a low privileged user perspective enable the kernel driver which should definitely not be the case um apparently whenever you connect from your ianti client to a server which uses the deprecated driver which is disabled by default uh it will actually enable for you so what we did we just spun up a rogue VPN Appliance we configured it to use that vable component that vable driver and we just let the client or victim connect to that specific Rogue uh VPN using this command we see right here and it would enable the driver for
us so we could try to exploit uh again and th we decided to write a value 41 to this specific Gro address uh and we used the wind dbg the windows debug to verify if it actually worked so currently we see Zero here uh we uh we use our exploit to write that specific bite 41 to that address and yes it did work so we could write one byte to the kernel and now from this point on it's actually fairly easy to write a fully functional exploit usually you would use um you would look up the uh the uh token the privilege token of the system process for example and you would just copy it
to your own process and that means you would have the system privileges um one constraint is that of that is that you need a read primitive in the kernel because you need to find the actual um address of the system token we couldn't do that because we didn't have a read primitive we only had a write primitive so what we did instead was we tried to search for our own uh token address of our own process and we modified our own token manually by writing a eight or 16 bytes to it so we run our exploit 16 times uh in an automated fashion of course um and we did it in such a way that it enabled all the privileges on
our current token uh and with that with all those privileges you basically have a full privilege escalation so I'm going to show you uh a demo of that right
now so here we've got a Cobalt strike interface um which we currently have one Beacon running on uh we can actually um make it sleep zero and then run a wmi which will show which current which current user we are uh and it will also show the Privileges that are set on the process so in this case we have about or exactly actually five privileges set and only one of them is enabled uh so we are really in low priv context right now so we run our exploit that will enable and set all the Privileges which is uh the pulse esque pesque uh peon object F in Cobalt strike there you go it worked um it says
we've acquired all all Privileges and was able to find all the offsets that we needed to the functions that we needed to call uh and it wrote the uh the eight bytes and now we're going to runi again and there we go all privilege is set and enabled and we are now basically uh system or we can now steal the system token so that was uh the uh the presentation uh We've written a full blog post of all the details uh that you can read if you want to read it uh and with that you can actually build a fully functional exploit uh as well uh so this blog post is now life thank you very
much
not sure if there are any questions from the audience I think there's a mic coming your
way
and long to actually bu a fully functional exploit function um to find something juicy uh in in a kernel driver uh I think it took us a few research weeks to find like the interesting drivers that we thought were vulnerable uh with about two or three persons uh then building a fully functional exploit like this it actually took quite a while all I'm not yeah I don't even know how many hours um but I guess uh several weeks fulltime uh to uh uh to build a fully functional exploit this is something that my colleague Alex did and then I think one or two few weeks to build a beacon object file for in Cobalt strike
and other C2 Frameworks um including making it making it so stable that anyone could use it uh so all our red operator can use it as well yeah so quite a while yeah any other
questions okay all right then thanks again okay