← All talks

Exploit Development Is Dead, Long Live Exploit Development!

BSides KC · 202147:5710K viewsPublished 2021-11Watch on YouTube ↗
Speakers
Tags
Mentioned in this talk
About this talk
A technical deep-dive into binary exploitation across Windows user and kernel modes. The talk traces exploit development history, examines modern mitigations (SMEP, VBS, HVCI), and demonstrates how adversaries continuously adapt techniques to bypass defenses using real vulnerability case studies.
Show original YouTube description
It is no secret that the days of jmp esp are far gone. In the age of Virtualization-Based Security and Hypervisor Protected Code Integrity -- code execution, as a result of a memory corruption vulnerability, is not as trivial as it once was. However, a few times a year, there is always that vulnerability which makes headlines, is remotely exploitable, and obtains code execution in ring 0. What gives? This talk addresses the history of binary exploitation and the mitigations operating systems instrument to thwart those vulnerabilities, how adversaries constantly adopt novel and creative solutions to bypass said mitigations, and the future of exploit development in both user mode and kernel mode. Connor McGarr (Red Team Consultant at CrowdStrike) Connor is a red team consultant for CrowdStrike. If you can manage to pull him away from WinDbg and IDA, you can find him writing blogs and enjoying time with his family and dog. Connor is passionate about anything related to Windows internals, vulnerability research, C, or offensive tradecraft.
Show transcript [en]

uh but it is now ten o'clock so uh we got connor mcgarr up uh he does some exploit dev and vulnerability research at crowd strike rock's a real solid blog too so go check it out i was peeping that just a little bit ago without any further ado take it away connor

that's all right

okay sorry technology's hard everybody um so thanks for having me here this is actually my first time speaking at a b size in person um so don't crucify me too hard if uh it's not up to the standards um but basically just wanted to say thanks for having me here in kansas city and uh big thanks to all of the sponsors from this year um but with that we'll go ahead and get into it um so this talk is exploit development is dead long live exploit development um so basically i just wanted to talk about something which i'm passionate about um vulnerability research and binary exploitation um so probably everyone in this room has heard of what

this is but you may not deal with it on a day-to-day basis so because of that i thought it'd be interesting to take a look at some of the origins of exploit development and as well as some of the mitigations that modern operating systems have instrumented over time um so my name is conor mcgarr i'm a red team consultant at crowdstrike and so i find myself you know doing more red team pen testing windows active directory environment type engagements um but in my free time i like to obviously study vulnerability research i like to write so anything i do that i find interesting or anything of that nature i post on my blog and as well as outside of infosec i like

to study history and spend time with family um so we'll start off by talking about giving some context of what we mean by exploit development and then we'll get into some of the expert mitigations that we have today and then third on the agenda is using a case study and a recent privilege escalation vulnerability uh in a well-known bios driver to basically outline how enabling a few mitigations which people may not be familiar with or maybe aren't enabled by default how that can help for that type of exploitation and then lastly we'll talk about the practicality and future of binary exploitation um so this is exploit development the quick rundown so when we talk about exploit

development mainly what we're talking about in context of this talk is binary exploitation or memory corruption so we're taking advantage of flaws and compiled code basically our binaries so web vulnerabilities etc are their own classes on the vulnerable exploits but in this case we're talking more of memory corruption um these are attractive because often they're point and shoot so an adversary develops an exploit run they run it and then they achieve their objective they're found in both user mode and kernel mode so typical user mode exploitation is used for initial access and then as well you have kernel mode for escalation of privileges and these exploits basically get delivered through a variety of what are

known as vulnerability classes uh which we'll speak on here in a second i'm so as mentioned these are used for initial access or privilege escalation or both um so citing uh two examples um eternal blue i'm sure most everyone in this room has heard of that that was an example of a remote kernel exploit which basically could be used for initial access if the service is available and privilege escalation and the same with um smb ghost a recent um exploit in the smb protocol um and then kind of what this talk uh outlines is exploit development now is not as trivial as it once was so in the early days of binary exploitation there's or excuse me of um software

development there's not a lot of security in that uh pipeline so moving on uh going back to um vulnerability classes kind of now that we've set the stage i'm sure most people have heard of these types of vulnerability classes before but just to run down if you're not familiar buffer overflow basically you just have an ability to overwrite some adjacent memory use after free which basically um are these are very common with larger c plus plus applications uh basically um if a developer is referencing some type of memory that was previously freed this could cause issues and an attacker could leverage a few primitives in order to try to control that freed memory and do something within the application

um you have out of balance reads which are pretty self-explanatory you can leak some type of sensitive information from adjacent memory and then you have right what wear vulnerabilities these are also called arbitrary rights so these are kind of twofold these can be the result of another vulnerability class or they can be a vulnerability class in and of itself um basically an adversary has a primitive to write some arbitrary memory to a controlled or otherwise uncontrolled location um so those who aren't super familiar with binary exploitation i just wanted to cite a quick example which pretty much has been included in every um exploit development talk ever given um basically this is um a pseudo stack

the um stack data structure where functions will pass arguments parameters etc and then there's some kind of copy operation that writes some sort of memory to this stack location and usually a an adversary has a primitive to control the size and the data and so what that looks like after execution is an adversary can basically control this data structure and since this data structure stores things like return addresses or addresses where a program might redirect execution it's possible for an adversary basically to control the control flow of the program and do some kind of malicious action executing code from the structure or other places in memory so that's more trivial exploitation today that's not really how things look

for the most part that's not to say that you can't find stack buffer overflows today but most exploit chains consist of two components a browser exploit obviously everyone uses a browser so this is a pretty viable target and that's used for initial access because browsers today are sandboxed and then an adversary may [Music] complement that with a kernel exploit in order to break out of that sandbox and escalate privileges on a machine and so basically adversaries have had to get creative because of exploit mitigation which we'll talk about here shortly um basically you have to chain multiple vulnerabilities together these days in order to achieve your objectives so we'll kind of talk about what exploit

mitigations are and um their origins so with exploit mitigations there's kind of two roads that you can go down um you can either make life impossible for an attacker so basically mitigating full vulnerability classes or you can just make life a lot more harder in order to raise the cost for exploits so operating operating systems implement these into the os by default in a lot of cases and they block certain vulnerability classes and exploitation techniques and hopefully this talk kind of outlines what some of these mitigations are and you know give light to some you can enable to help protect yourself better so we'll start with the first vulnerability class which people may be familiar with here data

execution prevention um so as we showed in the previous um stack overflow example an attacker relies on the fact they can control a segment of memory such as this in order to place their code and execute from that same region well with dev enabled basically now code are there are boundaries between code and data segments of memory so for an example if an adversary can write their code to the stack portion of memory well that's a data portion of memory it doesn't really need to execute any actual uh op codes so basically that's not allowed with devenable and then the same thing with um code segments that's uh the text section of a portable executable um basically that's where

you'll find your executable instructions and if you try to execute code in a dated portion of memory you'll get a status access violation as we've seen in the screenshot here um there is kernel mode support um for this so into windows kernel um used to the default pool allocation which basically is pretty synonymous with the heap um in user mode they were executable and now that's no longer the case and this is enforced by what's known as a page table entry bit which we'll talk about in the future but basically at a high level this is a memory address that points to some bits that enforce various permissions and properties of memory so where there's a mitigation there

might most likely be a bypass so if you've ever heard of the term code reuse or rob return oriented programming this is kind of the era that we've been in and we're kind of staying in just because code reuse is like a a utility tool it's very malleable and you can do a lot of different things with it um so basically when you have a data portion of memory you can't execute code from there so what do we do we can look in an application or loaded modules within an application in order to reuse code that's already existing because that adheres to dep's rules and so basically an adversary looks through the application and parses

it for instructions and then takes those existing pointers basically and uses them in order to craft a function call or some kind of other action either to call something sensitive or to just mark the entire region of memory as read write and execute and two common windows exported functions that are used to do this are virtual protect which we'll talk about here in a second and write process memory um so this may look confusing but this is just a pseudo code reuse technique so basically as we talked about if an adversary has control of the stack which is normally what an adversary will try to do they'll take those existing extractions and put those on the stack

and then natural program execution or some kind of control flow hijacking will force the program to start executing these instructions from the stack and so basically we can see the function on windows virtual protect this can basically change the permissions of a given page or region of memory and so an attacker basically will craft a function call at the assembly level um using code reuse in order to call this function to mark a given region of memory as fully read write execute so kind of going full circle if an adversary has control of the stack but can't execute code from there they can just mark the entire region of memory as executable and readable and writeable

and eventually call their malicious code but with this this insinuates an adversary has a reliable way in order to gather those pointers to existing code and they have a primitive to reuse so knowing that that if an adversary has to have a way to read those what if we just randomize some of these memory addresses in order to make the process of gaining these pointers either hard or impossible and that's exactly where address space layout randomization or aslr comes in so in the keynote this was mentioned there as well basically windows on a per boot basis will randomize many of the structures and the images and other items on windows so here for an example on the

top image you can see that kernel 32 is load is loaded at the start address and when the system is rebooted that address changes and this is both supported in kernel mode and user mode as well so as we mentioned in order to bypass depth you need some primitive to gather those existing instructions but with aslr in play an adversary now needs some sort of primitive in order to leak those addresses and use them and because of that you basically need two separate vulnerabilities you need a vulnerability to write or corrupt some memory and you need a vulnerability in order to leak some memory as well so knowing this this makes an adversary's life much harder just with

these two mitigations enabled and this is going to be a recurring theme here about the level of effort with exploitation today so with that um there are many different vulnerability classes we've been talking about stack based vulnerability classes thus far but one of the most popular ways to execute code is by overriding what's known as a function pointer um so building off of that if we take a look at the code on the left hand side it can be hard to see basically you have a function just does a print statement and basically a pointer to this function is created and so at the assembly level if we look at that on the right hand side we see

the call doesn't happen directly to the function but it happens via its function pointer so with this from an adversarial perspective the program doesn't necessarily care about what's on the other side of that pointer this is known as an indirect function call so essentially when this gets called it will call whatever address it's it's pointing to which is this print statement well what if an adversary could overwrite this pointer with some other sort of malicious address or um allocation of memory etc that would be pretty bad because the program would just end up calling that function so with that there are two mitigations um today that microsoft has implemented for this exact scenario the first is

control flow guard or cfg we'll kind of talk about that first this is a microsoft's implementation of control flow integrity so ensuring that when an indirect function call happens such as the one we've just seen it's calling to a actual target and not something malicious um and so what cfg does it basically inspects these indirect function calls to validate basically if the function the in scope function to make sure that hasn't been overwritten basically with a nefarious address as we just spoke about earlier so the way this is actually implemented is there is a bitmap that's generated at compile time that basically creates a list of all of the functions used in indirect function calls

and these are known as the valid targets and so when a indirect function call happens what happens is the dispatch function for cfg will check that bitmap to say hey is this function we're about to call within that bitmap and if it isn't a crash ensues to protect the programmer user from some sort of nefarious action this is a great start but the issue is that windows applications natively import kernel 32 and nt dll and so these are also a part of that allow list so an adversary won't be able to call to um you know a recently allocated region of memory which they may have just generated with some malicious data in it but they could craft function calls

to other functions within kernel 32 or ntdll or any of those valid targets and so although you've lessened the scope of what an adversary can make a call to it's still possible to overwrite a function pointer and call to something else um but the great thing is that microsoft has addressed this um and so we'll talk about it coming up here right in the next second um extended flow guard or xfg which kind of built upon those principles we just spoke of so xfg is more fine-grained um control flow integrity um so basically it still uses a lot of the same principles as we just talked about with the bitmap but what it also does is it generates a

hash which is made up basically of the function's prototype so whatever the return type is number of parameters type of parameters and a few other things and basically this hash is basically a signature that is representative of a function it's unique to that function depending on how it's prototyped and so that's used as an additional check before um control flow transfer is placed to um whatever is on the other side of that function pointer and so if you want to call a malicious function pointer by overwriting or if you want to call a malicious function by overwriting a function pointer it now not only needs to be in that bitmap that we just spoke of but it need the

function you're calling needs to be prototyped the same way as the expected function so we've gone from calling anything to calling a few less targets to calling even fewer targets so this does a really good job at protecting function indirect function calls and as we can see in the assembly here that in the r10 register which is um box in red that is the hash that's used as a check in this case so we've started with the stack and now we've moved on to primitives such as function pointer overrides so these are all great things memory is randomized memory should either be executable in some cases or writeable but hopefully not both as well as protecting the control flow

of programs but building off of this there's even more modern mitigations which this is what this talk hopes to address um with mitigations maybe you may not be familiar with um such as arbitrary code guard which we'll talk about here in a second which builds on these principles so this is tailored toward browsers as we spoke about earlier browsers are used a lot for initial access and it's microsoft's instrumentation of write xor execute so as the name suggests with acg enabled memory should either be writable or executable but not both and that is a strict enforcement of that so memories is immutable so we know that there are data segments of memory and code segments of memory

this mitigation basically makes sure that code pages don't become writable so an adversary can't write their user supplied data to a code segment to execute it and data pages cannot become code pages so as we spoke about with dep although the data portion of memory is non-executable we saw that we could craft a malicious function call in order to mark that region as executable so we can start executing code from there but with arbitrary code guard it's not possible to do that that data page cannot be turned into a code page to become executable so now that primitive we talked about of executing native code by using return oriented programming is not possible so let's talk about a little more about

how that's manifested so on windows in kernel mode there is a structure known as the e-process structure and basically this is the representation of a process it contains its attributes characteristics etc and there is a member of that structure called mitigation flags and there's a bit mask so if disabled dynamic code is set arbitrary code guard basically will kick in if you're trying to call or change the permissions of memory um and it's ins it's checked through the function mi arbitrary code blocked so this is a really good uh mitigation um but the issue is it's tailored toward browsers and so because of that browsers modern browsers uh use something called just time compilation and basically because javascript is

interpreted um it can induce some performance um problems and so if a piece of javascript is constantly getting called and called and called the um just in time engine will actually compile that into machine code and map it into what's known as the render process which is what you see with your browser so by nature just in time compilers are mapping read write execute memory into a browser and so with edge now being chromium based there there are a few issues here um with that but that's not to say arbitrate code guard won't have full support in the future that's not something that i can really speak to um but this is something that is a known

um issue and something that is going to be taken into consideration in the future and here's a blog from the microsoft edge um folks basically outlining some of the issues that i've just spoken of and some some great solutions uh if you need arbitrary code guard in the browser so now that we've talked about uh user mode exploitation as we mentioned exploit chains consist of initial access through the browser and then you need some sort of exploit to break out of that browser sandbox in order to move around on a system move laterally etc and so we'll talk about a kernel specific exploit mitigation here um so this infers an adversary first of all i should say what it is um

supervisor mode execution prevention um this assumes an adversary has already existing access to a machine and so that that's a perfect perfectly valid scenario for a user using an initial access exploit and then trying to escalate privileges um and so what will happen is if there's some kind of vulnerability within a driver that's loaded on windows what an adversary tries to do a lot of the time is find some type of function pointer to do what we spoke about earlier with control flow hijacking and so they'll find that function pointer and they'll allocate some sort of shell code or data in user mode where they can control it because they can that's where they're operating

and what they do is they try to overwrite that function pointer with the memory address of the data that they've just placed in user mode and because that code is then going to be executed in context of the kernel the shell code will get ran with administrative privilege which can result in code execution as system basically giving an adversary full admin privileges and so this is exactly what snap tries to mitigate so basically from ring zero uh basically if you're trying to execute code from ring zero and you're reaching out to a ring three or user mode page table entry basically um that you can't do that you can't just arbitrarily execute code in context of ring zero which is the

kernel into uh user mode or ring three um so this is enforced through those page table entries we spoke of earlier um so getting deeper into that page table entries as i mentioned earlier are responsible for enforcing various properties or permissions of memory so if we take a look at the screenshot here on the furthest right hand side we can see it says pte at and then there's an address that's highlighted if we look at the letters in the bottom right hand corner of that screenshot we can see that there are a few letters k w and v this means this is a kernel mode page it's writable and it's valid so what smep is actually doing at a bit

of a lower level if it's looking at that page table entry to see if that kernel mode bit is set and if it is we know that that's a kernel mode page and we can execute code from there if it's a u or user mode that we don't want to execute um so these page table entries obviously are managed um from kernel mode um and building off of that the base of the page table entries are randomized so when i say base of the page table entries basically this is an array that can be indexed so an array is a base offset mechanism so the base of this array basically is randomized and as i mentioned they're technically

stored in an array so if you wanted to locate a page table entry for a given memory page basically you would divide the address that you would like to find the page table the corresponding page table entry by the size of a memory page which on windows is usually 0x 1000. um you would then multiply this result by the size of the pte or page table entry which on a 64-bit system would be 8 bytes and then you would add that base that we just spoke of in order to get the proper index into the array um the thing is where there's randomization there needs to be some sort of way in order to find that base

um since it's dynamic and so there are a few functions out there that have this but one of the exported functions in the kernel is called mi get pte address which basically just programmatically does what we just spoke of so why am i speaking about all this one of the ways an adversary bypasses map basically is allocating some shell code as we saw earlier in user mode they will then use some kernel mode primitives in order to calculate the page table entry which corresponds to that memory address of their shell code and since it's a user mode page they will corrupt that bit which we spoke of earlier that smith looks at in order to

mark the page as a kernel mode page so even though the user mode address is there and it's not a kernel mode address the memory manager still recognizes that as a kernel mode page but one thing you have to take into consideration and one it's actually another mitigation it's page table randomization is if you want to do that to index that array you need some sort of other primitive in order to read that base of the page table entries um so i kind of gave some context hopefully to um some of the exploit mitigations that are out there and so now i kind of wanted to use a case study and a recent exploit to kind of put that

to the test and hopefully get away from a lot of these acronyms and jargon um so this cbe here basically is an exploit in dell's bios kernel mode driver and this vulnerability is a classic right what wear vulnerability so an adversary has a way to write arbitrary contents from user mode into the kernel in a controlled manner you can control where that's going to go so this is great from an attacking perspective i should mention as well you can arbitrarily read memory as well so you have a read primitive and you have a right primitive so we can leak sensitive contents we can write into sensitive contents and so with this this is the the goal

for an adversary um so there are multiple avenues for exploitation here um and so one one of the things i wanted to outline is um we spoke a lot about depth and paid table entries um let's use that with this exploit in order to bypass those controls um so the first question we need to ask ourselves is we know we can write some data into the kernel and that means we probably will have some primitive to execute um so first question what do we want to execute um so most adversaries when you're executing native code in the kernel for an exploit you want to elevate your privileges to that of the administrator so i spoke of the e-process object

earlier on windows which manages a lot of properties for a given process and this structure has a member known as the token which basically is responsible for enforcing the privileges of a given process and so what we want our payload to do is copy that token from a process that is already privileged which is the system process on windows which has administrative privilege and executes a lot of kernel mode threads um and we want to copy that to our unprivileged exploiting process essentially and when we do this it will escalate our process to administrative privilege i only included this slide because i intend on distributing it afterwards so if you want to take a look more into the

actual assembly that's behind it um feel free to but basically what this does is it parses the e-process object and it loops to find that system process locates the token and copies that token to the exploiting process which is unprivileged so now that we know what we want to execute what it does uh we then can use an arbitrary write primitive in order to write that payload to kernel mode memory um so in this case i targeted the drivers.data section so as we spoke of earlier depth places boundaries around code and data segments of memory so a data segment although you cannot execute code from there you can still write the contents of your code to that segment

and we proved this through the page table entry of this given page which says it's a kernel mode page and it's writing and so the goal now once we are able to write our code to the data page basically what we need to do is place all the things we spoke about earlier to bypass a few controls into play so basically what we start out with is using the read primitive in order to locate the base of the page table entry array as we see in the screenshot on the furthest left hand side and then in step two in the top right hand of the screenshots we calculate the page table entry that corresponds to the shell code that we just wrote and

we can use those same calculations that that exported function might pt address does and then in the screenshot right below that we can see that once we put all that together we were able to obtain the base of the page table entry through the read primitive and then we were able to successfully calculate the page table entry that corresponds to the code we want to execute and we can just verify that through the last screenshot there and then once we do this we want to corrupt that page table entry in order to mark that page as executable because right now it's just readable and writeable we want to execute the code from there and so we can use bitwise and

in order to clear that bit as we can see in the screenshot here and in the bottom uh right we can see that after corrupting uh the page table entry this page is now kernel mode writable and executable um that's great but now we need some way to force the system to execute our code so we have fully read write execute code in the kernel but we need to execute it somehow one of the most classic techniques that is used to do this is performing control flow hijacking to overwrite a function pointer in the hal dispatch table the hardware abstraction layer on windows so we kind of spoke about this earlier with corrupting function pointers this

is a something an adversary likes to do because it can result in arbitrary code execution through an indirect function call so basically we use the right primitive to overwrite the function pointer at how dispatch table plus 0x8 and i know what you're thinking why such a random address well we'll talk about it in this screenshot or in this code here basically there's a user mode function called nt query interval profile which basically will perform a transition into kernel mode um in order and it will actually call that function pointer from the dispatch table this is not any security vulnerability um or anything in fact a lot of functions on windows do this you have a user mode

function that needs to allocate some resource or do something and it uses um system calls basically on windows that transition to kernel mode to do that so for those who aren't who are familiar with system calls on linux it's not just specific to linux it does exist on windows as well um so now we'll give a quick demo since it's my first talk at b-sides i'm not confident enough to do a live demo but here's kind of why this is attractive is we do it who am i and we're an unprivileged user we put everything together and then we have system privileges so although this is kind of an arteris process to put all these pieces together

it's just point and shoot at that point and you have code execution in context of the kernel so just to summarize this exploit we just have shown relies on two things um we hijacked the control flow of the system and we also corrupted a page table entry in order to mark the page as fully read write execute where our shell code was in order to execute it and so these steps are common with most remote kernel exploits as well so i won't go into a full demo here but one of my peers and friends chompi1337 on twitter she wrote a proof of concept for a recent um smb exploit called smb ghost and this is a

remote kernel exploit in the smb protocol basically through a what's known as a pool overflow vulnerability class resulted in a read write primitive and she did the exact same thing she found some structure in windows called k user shared data which is targeted by adversaries at some points because it has a static address address in context of ntos kernel which is the windows kernel uh the traditional windows kernel and it contains a code cave that's writable so just like we did with our exploit we found some kind of memory region we could reliably find we were able to write the contents of our show code there and so looking at her exploit it does the same thing so she locates k user

shared data the corresponding page table entry and corrupts a page table entry to mark it as read write and execute and this is one of those exploits where the smv protocol is handled in a kernel so if when we talk about you know the the big thing now is ransomware and whatnot um if you're deploying ransomware to one single system where you want admin privileges over a system this would be the export you would do with it's remote it's unauthenticated and it's kernel code execution um but thankfully these exact scenarios are where two mitigations if for nothing else i view these as some of the most important mitigations to date um where they come into play are known

as virtualization based security vbs and hypervisor protected code integrity so getting into those this kind of brings us into the last portion of the talk about the practicality and future binary exploitation because we have a lot of these traditional mitigations thus far which were pretty novel at the time but these are a whole new level of complexity and do a lot of great things and as we can see in the subtitle a never-ending cat and mouse game is exploit development um so virtualization-based security we'll talk about what that is um it's available on compatible hardware after this version of windows and it can be enabled by default um after 20h1 with proper hardware and on windows 11 it's enabled by

default um and so basically with this um enabled uh the system runs on top of the hyper-v hypervisor essentially but more often than not uh many people in this room if you may not even heard of this mitigation um before it's it's quite new um it may be disabled in most organizations because there are a lot of things you need to make sure that are compatible with it we'll go through a checklist here at the end but it's extremely powerful again when it comes to the approach that vulnerability researchers are going to take with exploitation in the future so there's a great book out there it's called windows internals you may be familiar with it um

i don't know if this is something you would read cover to cover but it's a great reference and they kind of speak about um virtualization-based security and the basic architecture of it i highly recommend you check it out so vbs what does it do so basically we create another security boundary through what's known as virtual trust levels and so virtual trust level zero essentially is your traditional resources your traditional user mode and kernel mode which we just exploited here recently um in the talk and then you have vtl1 a virtual trust level one which is the secure kernel um and we'll kind of get a little bit deeper into this and so basically this this boundary this instrumentation of

virtual trust levels prevents resources from one virtual trust level from accessing resources in another so even the most privileged code that runs in the traditional windows kernel that code cannot make changes even to the user mode in virtual trust level one and what's great is that um with the complexity added you get a lot of great features so in kernel mode and vto1 that actually manages a lot of the sensitive resources of virtual trust level zero such as the page table entries we just spoke about um and the reason for this is is because vto1 is a more trusted security boundary and this right here is the basis for hypervisor protected code integrity which requires vbs to be enabled in

order to run so this is my favorite part about vbs hypervisor protected code integrity and this is basically arbitrary code guard in the kernel and so it enforces these things called enhanced page cables or epts and what this is basically is it's an immutable bit that's placed on the traditional kernels page table entries so in vtl0 uh the kernel that we just shown for exploitation um and so what that actually does is it enforces vtl1 which is the secure boundary it enforces its view of the memory and so if we remember what arbitrary code guard is um it's right xor execute so there's not going to be any regions of memory that are writable and executable at the same time

and so even with a primitive as we've just shown in kernel mode in vtl0 where the user was operating for our exploit if you try to do page table entry corruption it will actually not blue screen of death but the thing is vto1's view of the memory is enforced so even though vtl0 sees that as you know oh we've marked this page's read write execute it will not actually be read write execute and this means our page table entry corruption um perimeters that we show should fail and then the second added benefit of hvci being enabled um you may have recalled earlier with the function pointer override from our kernel exploit why wasn't control flow

guard there in order to protect that well with hvci you have full enforcement of kernel mode control flow guard so taking a step back from a second from a higher level if you have a cfg bitmap which is used to contain all of the valid function calls that you can make if that's in kernel mode and a user has a primitive to corrupt some code in kernel mode an adversary could just mark the whole bitmap and place everything as a valid call target and call to any function so with kernel control flow guard you need a tr you need a higher boundary in order to prevent that and that's exactly what vbs and hbci are there for

basically the kcfg bitmap is now protected by this mitigation um and so you have full enforcement of control flow integrity within the kernel with these mitigations enabled um and so this isn't on the slide but um even with um our kernel control flow guard um if you don't have vbs and enabled it's still the the routines are still there and it actually performs a few bitwise tests to make sure that there's not a user mode address being called kind of what we spoke about earlier with smap but for full instrumentation of it you you need vbs and hbci and i would be doing a disservice if i just spoke about these mitigations and didn't show some ways to enable them

that can be done through the windows security app also through gpo and the registry as well again i would also be doing a disservice if i just said every situation enable hvci there are a few things obviously if you have arbitrary code guard and kernel through hvci essentially that's what it is um you have writable memory or executable memory so if you have a driver that's allocating read write execute memory natively not through an exploit that's probably not a good thing and you're probably not going to be able to enable that mitigation so there is a checklist that microsoft has put out these are a few things on there i've included the link in the slides um and so you can

definitely check those out um so nearing the end most exploitation ends with some cool or exploitation talk ends with like a cool novel exploit um that bypasses all these mitigations and uh does do all these cool things but i actually wanted to end today's talk by showing how enabling those mitigations can thwart some of those powerful even the most powerful exploit primitives that we've shown here with kernel code execution so another demo memory integrity is what hpci is called on windows and when we rerun our exploit the machine blue screens so it protects the system from executing any um malicious code and so the bug check that was actually here was the kernel security check

failure that was the reason for the crash and this was basically kernel control flow guard protecting the system against our control flow hijacking because hbc and i excuse me hpci is enabled meaning kernel control flow guard is completely enabled and as i mentioned earlier corrupting the ptes won't necessarily cause a bug check um in context of vtl0 but it's prevented in any case it's not possible in terms of the primitives we've shown here to corrupt the page table entries of ntos kernel when hpci is enabled so if i could just summarize everything up with this talk if it's something that you didn't find useful if i could just summarize everything here of why i think

you should find this useful is exploit mitigations do a lot behind the scenes so all of these things a lot of them are enabled by default on windows operating systems and you may not even know they're there and they're doing a lot for you behind the scenes some of the most powerful mitigations like we've shown hvci and vbs they may not be enabled by default on your system so definitely turn those on if you can because they're fantastic there are also many other mitigations i cannot stress that enough this is not everything that export developers vulnerability research have to deal with one of the ones that i wanted to at least speak on slightly um is intel

control flow enforcement technology or cet with rock earlier we were reusing code in order to maliciously craft function calls and with rob at a more technical level why adversaries want to control the stack with rob is return oriented infers each instruction ends with the return instruction what return actually does is it takes the stack pointer and loads it into the instruction pointer for execution so every time you execute a wrap gadget it will go back to the stack pick up the next gadget execute it and that's the mechanism it uses to keep executing from the stack well um cet basically thwarts rob um it creates what's known as a shadow stack and it inspects the stack's integrity

essentially to make sure that there isn't any stat corruption going on which kills rob which greatly raises the bar for exploitation we also have mem gc which is browser specific app container code integrity guard which is another great one paired with arbitrary code guard you have hyperguard isolated heap kernel patch protection and many others as well and so by enabling these mitigations when possible and just rolling as i mentioned earlier control flow guard is a compile time control so rolling stuff like that where applicable into the software development development life cycle is going to greatly significant going to significantly reduce the attack service and just break many of the public exploits that are available today

uh and with that there are some qr codes for giving feedback on either my talk uh the conference or anything else but with that that's the end so if anybody has any questions i'm more than happy to try to address those but thank you for having me [Applause]