
hello everybody thank you for coming uh so we have omri every time omri comes to v sites we just make him talk because he does really cool stuff um so he's been to four here so far every time he comes to vegas he speaks uh he's going to be talking about installing rootkits on windows 11 so pretty cutting edge uh about as modern as it gets for what he's doing um so without further ado uh we have omri thank you for coming hi everyone thank you for coming i believe we are wrapping up today as i was introduced my name is omri i'm currently in charge of the security research in fortinet's r d site in israel
and sorry yeah okay great um most of my work is around endpoint security um as i as i was introduced again i was i'm not the first it's not the first time i speak here and i've spoken in a few other besides events in other places as well what we're going to talk about today is a bit of a background and introduction about driver signing enforcement a bit how it's been being protected on windows and a couple of new techniques that we found in order to bypass those new protections and uh hopefully uh we'll show the live demos and everything will work without an each and then we'll talk about some suggestions that we have in order to
prevent that and we'll give you a quick summary of basically everything we covered okay i'll just start off with a warning um what you're going to see here today is going to be rough and not because of the content it's because i do my own slides and i'm really really bad in photoshop so i apologize in advance so okay here we go basically what is driver signing enforcement or in short dsc uh coding trade integrity basically is a feature that has been introduced over 15 years ago for drivers it means that basically you have to sign them in order for you to be able to load them into memory and execute them every time you want to do it they are
being checked and validated and basically it gives you the option to improve the security of the operating system because then you can know once they're signed from where they came from and there is some assurance that they haven't been modified when they got to you the way it works is um during uh that basically the windows kernel when it starts running it uses another library called ci dll when it starts running and the machine boots basically it goes from the call flow you see on the left and eventually it said some global flag that says it's enabled and then calls ci dll with the ci initialize function giving it a pointer to a callback structure
that that ci will initialize and set addresses of callbacks to it and it will set another internal flag the ci options that will state that it's also on then when you come to load the driver basically the operating system before it maps it into memory will validate the driver the signature itself on the driver it will call eventually two functions and validate the image adder and validate image data those are wrappers basically that will eventually cause the idl and on the right hand side we can see an example of the disassembly of one of those wrappers all he does is check the flags and check the global flag to see that the ci is that dst is actually enabled
and uh check the the callback the specific object it it wraps is actually valid and not null and then it will just call it simple as that now there have been a few changes what i've described is pretty much uh how it looks on windows 7 which is pretty old by now along all the a few years later basically from one version one os version to another there have been slight changes from windows 8 we no longer have the global variable in the kernel itself only in cidl the callback structure also changed a bit they added a few other callbacks and some of the important callbacks that we mentioned before and the placement in the the structure
changed but essentially those those callbacks are still still there and one other major thing is that uh basically now when the signature is validated then only this the image adder callback is a divided image header kodak is being used so um how do you pass bypass other bypass code integrity um so the easiest thing to do the most straightforward thing to do is use the digital certificate to just sign your payload either that you steal it or if you somehow trick some certificate authority to issue you such a certificate then you just you can just do it the other option is basically use some exploit and gain code execution in kernel usually some shell code or warp chain and then
you can just load up your driver if you want to reflectively but it introduces some complexities and but so the last option that we have is basically just turn off driver signing enforcement on time and then load up the either and continue how is how it's actually being done as i said it's just flipping some flag um so one of the flags basically is being changed or run time you write to it you write to it and change the its value since it's not exported we need to find it somehow so basically there are simple a pattern that you can search for and then you can find those flags this is how it's being done
um right now by some apt groups then you overwrite the value and you just load your onsite driver and once you're done and your driver or wood kit is loaded uh basically you have to quickly restore uh the flag because you want to avoid some protections like a patch guard that will cover in a bit the way that you get the ability to write to the kernel is usually by bringing your own signed driver or bring your own vulnerable driver and just use it in order to load it first because it is signed and then it will bypass the it will just go through the check uh sorry just just go for the check and
the validation and then you can just use it to read and write to the kernel or wherever you want and then you can just flip the bits with it and load your the rest of your payload so in recent years there have been quite a lot of uh cases that use dc tampering um all of the we can see some of the statistics here um in the last couple of years basically there was quite a lot there's a bit of a spike here uh from an average of uh one a year uh one group a year that is being reported on we got to an average of about five and a half um so it's it's quite a lot
uh in light of this critical attack path microsoft did try to uh mitigate it and fight against it and the first thing is the first protection that was used is a patch guard again it's quite quite an old feature it prevents from modifying the kernel or its code for long periods of time once it detects some change it basically will cause a blue screen and then what will make the system stop but in every in every with every new release of windows basically it's being improved and from windows 8 again it started protecting code integrity and the driver signing enforcement the next protection that microsoft tried to introduce is the signing policy itself so first off you can just sign
you can just use any certificate to sign your driver uh you have to acquire a certificate from a specific subset of public series um so that pretty much prevents you of like installing your own ci on the machine and running your driver and then from windows 10 they did a bit of a radical change here and they force you to provide their your driver to them in order for them to place for them as microsoft to sign your driver uh themselves so it's not only that you signed it but they have to sign it as well and if you are an attacker you don't really want to hand out your payload to the defender it's not
not exactly something that you're aiming for usually you try to do the exact opposite of it and but still he didn't he didn't really really help because there are already drivers that are signed and you can they can be abused as we said so for that basically microsoft implemented a block list um just just in order to make it harder to gain that right that kernel right primitive it's enforced via windows defender application control but the downside for it is that basically if you got a new vulnerable driver that you're not aware of so attackers can just find some new samples like that and use that so it's really not a proactive measure the newest
mitigation the newest protection that microsoft tried to use is basically it's called the kdp or canon data protection basically it uses the newest and more advanced features for virtualization-based security also known as vbs and leverage the the fact that now the kernel cannot do whatever he wants inside the inside of kernel space but he has another um another virtual machine that he has to cooperate with it's called the secure kernel um so basically drivers of every software in the kernel can now request from that secure colonel to basically remove the right permissions from certain locations in memory that he wants to keep as read only and hypervisor will basically enforce uh those protections but what's important to note to note
here is it does it only for this the permissions not not for the mapping between uh the virtual address space and the physical address space at the end how it looks like in windows 11 so ci dll was opted in basically some programming in microsoft some developer went to the code base and added another api call as you can see here now ci options is being protected and the flag itself was moved the variable itself was moved to its own um its own separate and new section in the pe and basically it's it's all the and all with all along with all the data that needs to be used for the driver signing enforcement so
let's talk about our new techniques now if we need to recap for a second the tampering procedure basically we first locate whatever we need to find whatever internal variable we need to find and then we overwrite it then we go to load our driver and revert the state and we'll focus on those two initial steps because those are what's matter here the first technique we called it page swapping it's very not very creative but kind of explain what he does um so instead of changing the value of the page um let's just point the page to another page and just this is how we're going to change the value and if we have the ci policy page
if we can see the mapping here and the permissions on the pt is basically every every right we try to do to it won't matter but instead we can allocate can create a new page that we'll own and it will have the right permissions and we can set the value that we want for driver signing enforcement to be disabled but this still doesn't affect the dll doesn't affect the idle and we need to change how the virtual address mapping so all we need to do is change the pfn uh to point uh to a different physical page and the rest will do it the rest will just work the same um okay so how can we actually do it
we just explained it in very high level terms we need to be able to access the page tables um those are fine those are found in the in a space called pte space in the in the kernel mode in killer space sorry and basically in that area we can access the ptes directly using the virtual addresses it really cuts down on the number of the reads and writes we need to do for inform user space to kernel and because we can just calculate everything in pretty much everything in advance and it really simplifies the implementation of the technique um now from windows 10 um the pt is based are basically randomized in the memory due to kslr
but luckily for us we already presented some old research here in these sites that we show how we with a single kernel read basically we can find the new pte base basically there is an exported function in the kernel that holds the the address so we can just find it's a find its address of the function and then go to the offset and read from it the new randomized base so if we go again about the technique uh we need to allocate a new writable page in the kernel then we need to find ci options the flag just as we did before this is you this is done exactly as it's being done in the wild and nothing new
new in this area then we're going to get we're going to read the new pte base and we're going to read pt for the flag for the flex page and read the pt for our page and from there we need to copy the entire page um from the old one and from the dll to ours and so nothing else we will break then we need to modify ci options in order to disable it in our new page and then we just swap the pfns as we talked about before load the driver and restore the original fn later on so this is a bit complicated i wasn't very happy with this it requires a lot of reads and writes
into the kernel and it pretty much means that this technique is not really feasible um happily for us we found some workarounds and adjusted the technique so it will be a bit more more realistic so instead of allocating a page in kernel we can allocate the page in user space there's nothing fancy about it we're already running code in user space and then we do all the locating just as before nothing special here as well we need the three free kernel reads we initialize instead of copying the page we can just initialize it with the default values because this page is now all the variables in this page are now being placed in a separate section especially for kdp
because of kdp it's quite easy to predict what those values are going to be uh so we could just set them in advance and then um we again we disabled once we initialized it we disabled the flag and we switched the peer fence we loaded the driver and restored original pfn so let's go and start our first demo
okay so first off we'll see that we have a windows 11 here
okay it's a pretty recent build i think it's from two or three weeks ago the insider preview build we can see that we have vbs running but only credential guard is on kdp doesn't appear here but in a second we're going to show you it's working so the first half basically we'll in order to show you to prove that kdp is actually working we're going to show [Music] the existing implementation of dc tampering in the world and it will result in a blue screen hopefully hey so if you can see it should say that there is an attempt to read to to write to a non-writable location and the screen is a bit far for me so i
don't really see it revert to the snapshots
and now we're going to hope for a different result that won't be a blue screen
so first we're going to install our driver our rootkit and we're going to try to run it and we see we get an error that the driver is not signed just so you see i'm not messing with you what is right and there is no signature over here
now we're going to run fade swapping and it's waiting for us to load the driver which we did and you can see our wood kit is doing some debug prints just to show us it's alive if we're gonna stop stop the rootkit now you see it stops and if i try to run it again i get there again
thank you
so to sum everything up so everything up um on the left we have what's called flag flipping and this is the what we named the the technique in the wild and we compared it to all of the variants for paint swapping and we got to a variant that it's quite decent it requires only three kernel reads or a bit more if we don't want to completely rely on the default values and the single kernel right but we still weren't exactly happy because if we if frequency on the column on the left the current technique in the white doesn't do any reads doesn't do any candlelits so we continue to look at how we can improve upon it
and the next technique that we found is called callback swapping again very creative name um basically what we got what we'll try to look at now right now is find another point of data that is not protected even before that cidrl is used basically when the callback is being is being the c the callback in cidl is being executed at the end the expected result should be zero the callback should return zero um so what about if instead of going the all the way through it and hoping that it will return a zero what if we can just use another callback that returned zero directly for us immediately without any validation whatsoever happily for us the
uh the callback structure in entity in in endosconal is not protected by get back by kdp so it means that we can freely write to those locations but we still need to find a way you know to locate it because again it's an internal variable um so how can we do it so basically um if we look at the initialization phase uh this the the ci initialize function is imported and then we can find we can look for a call that is actually referencing that import import table entry and once we we do find it we can just go back and look for the parameter assignment in the registers once we do find some some
some such assignment we're just gonna check that the address that it points to will lead to the data section and the data there will be uninitialized and that's how we know we found the callback structure next up we need to find a new callback basically there is a very easy way to do it if we use exported functions from the kernel itself we can just use get pork address and get to get those functions get the address of those functions and if we want to be a bit more creative we can look for exported functions in cidrel itself all some gadgets that will result in the same return value for instance as we can see here in cidr
the benefit for that is that the rights that we need to do to the kernel will be smaller because the addresses are not as far apart once we look in the dealer itself the hardest part here is finding the original callbacks basically we want to do it without reading the kernel and without looking in the kernel in the callback structure in the kernel and all of those functions are internal and not exported in any way so what we can do is look for in the in the dls initialization function for the assignment of the fields of the structure okay this pattern repeats itself pretty much in every version of the operating system and then uh in order to
verify that we actually found uh the right addresses or that the addresses that we got are actually for for functions and not not just some random area we can check the exception directory for entries and that we have the same start address as the offset that we found so with that we completed our locate phase and the override is pretty easy we just write the callback and load the driver and restore the original callbacks again let's try another demo
okay so i'll skip the reverting the machine because we already see that the driver isn't loading and it's already installed
okay
great so as you can see we first disabled dsc loaded our driver and then restored it for a second for us for one last try we're gonna just make sure the restoration worked just
now we stop the the old kids
and we can't load it again
thank you
so if we compare our techniques um we managed to find some some technique that will actually that actually looks pretty similar to the one that is used in the wild we can do everything that we need without with single kernel light in the demos that i used that i showed you i used the non-vulnerable drivers that are being used by attackers in the wild so it's completely feasible and for me for me we've tried to figure out some way that we can improve the situation and just and not just like show you new techniques that you need to worry about um what we thought about was basically why not just confirm the state of dsc when we know a driver is being
loaded and once we do we can decide how to act we can find those internal variables ourselves same as attackers do they proving to be stable so far so why not do it also ourselves um so basically when we boot or when our when our protection starts we can kind of assume that this system is not currently compromised because uh because of all the other protection mechanisms that prevent the compromise to be to be for a very long period of time so when we start we kind of assume we're in a valid state and we can just capture it meaning that we're going to copy the callback structure and the values of the flags uh to a different location
and then we need to figure out how how a driver is loaded or when a driver is loaded and not just that we need to be able to uh manipulate that that moment in time we need to be able to control it so this thing this can be the driver loading can be intercepted by placing a hook on in user space on the api call but that's not really robust and in kernel we can leverage some callbacks like the registry call register operation callbacks or file system callbacks using a mini filter driver for registry callbacks we can check once we see access to registry keys of drivers and then we know uh and if they are happening when
um the the current process is is system is the kernel itself uh we'll know that it's most likely originates due to a driver loading attempt or when the driver is being loaded to memory uh in the driver file is being loaded to memory via a section uh oh it's being mapped into memory uh it's even before it's being loaded um then on those points we can just compare we can just find the variables again and compare them for any change if there was a change prevention is pretty easy we can just block the i o request in the callbacks or we can restore the values of the variables and let the system do its own work for us
so just to summarize you i think that dc tampering is still feasible at least with callback swapping um data-oriented mitigations like kdp they are pretty tricky to implement and also some of the people in microsoft also think think so so i'm not really sure what was the goal behind it defenders can use the mitigation that we suggested we're gonna release hopefully the source code for it so it will help speed up the process and the final thing and i think it's the most important uh point of this talk is there is a real solution for it microsoft did a pretty good job on developing virtualization-based security mechanisms one such as hvci it exists for seven years now
but it's still not as common out in the field hopefully it will be more adopted in the next few years basically what it does it's leveraging the fact that we have a secured space that is actually um is actually being separate from the kernel that the kernel can't really access and there it performs an additional validation once we load the driver it has its own set of policy and its own copy of the ci policy and it validates it as well and in order for you to get execution permissions in the kernel you have to go through it only it can actually set the execution permissions because you have two sets of pt's as we
said that you need to be able to set the permissions on it same as for right you also have the execute permission on that pt and basically that's the real that's the in the end that's the real solution here so if you can just make sure you turn it on that's it any questions
thank you