00:00:00.033-->00:00:05.539 >>My name is Morten Schenk. Uh I come from a small consulting firm in Denmark and I do a lot 00:00:05.539-->00:00:10.544 of. [Inaudible yelling from audience] Better? Great. I do a lot of uh blogging about 00:00:13.280-->00:00:18.285 exploitation both use mode and Kernel mode. Um you can read on the blog and so look it up. So 00:00:20.754-->00:00:25.592 this talk is uh primarily about how to leverage these vulnerabilities all from low 00:00:25.592-->00:00:30.597 integrity endpoint inside a sandbox. So you pay lot of eh hex and uh C code here. And some 00:00:34.301-->00:00:39.640 o days. First we'll go through some uh brief history on kernel exploitation to try to get 00:00:39.640-->00:00:44.411 everyone up to date. Then uh look at the core common mitigations that have been put 00:00:44.411-->00:00:48.148 into place in the latest versions of windows ten. Let's see how we can actually overcome 00:00:48.148-->00:00:53.153 them. So write-what-where vulnerability. The best case of this vulnerability class is that 00:00:56.390-->00:00:59.693 if you co- you can write a controlled value at a controlled address somewhere in the kernel. 00:00:59.693-->00:01:05.766 A more commonly found bug will most likely write a non-controlled or 00:01:05.766-->00:01:12.139 semi-controlled value at a controlled address in the kernel. So once that's possible 00:01:12.139-->00:01:17.344 we have to leverage this right? And so we have to get kern- kernel code execution. The most 00:01:17.344-->00:01:22.516 important part about this is we have t know where to write. So that's uh one of the problems. 00:01:22.516-->00:01:26.320 This techniques shown here are for write-what-where vulnerabilities but they can be 00:01:26.320-->00:01:31.325 used for all other vulnerabilities as well, like uh pu- kula- pu- pool overflows or 00:01:33.660-->00:01:38.665 or use after freeze. Yeah so looking at uh Windows 7. Uh was actually quite easier back then. 00:01:42.402-->00:01:48.208 Um first of all you could use uh just allocate just the code directly to the NonPagedPool, 00:01:48.208-->00:01:53.513 'cause it was executable. Then use the uh built in APIs of the operating system just to uh 00:01:53.513-->00:01:58.852 return the address of the location. Then use the [indiscernible] to overwrite uh 00:01:58.852-->00:02:03.323 a function address like in HalDispatchTable and then just call that get your code executed 00:02:03.323-->00:02:08.495 in the kernel. Even easier you can actually allocate uh user-mode memory and then 00:02:08.495-->00:02:15.202 allocate eh eh execute that from the kernel as well. So it's very easy back then. Going forward 00:02:15.202-->00:02:21.608 Microsoft to make quite a lot of improvements for security wise. Uh first of all the uh API to 00:02:21.608-->00:02:26.380 call to uh get the kernel addresses have been blocked from a sandbox so not possible not to 00:02:26.380-->00:02:32.886 get these allocation addresses anymore. Furthermore, uh most built in APIs uh use the 00:02:32.886-->00:02:37.224 NonPagedPoolNx as a standard now. Which means that the locations are not allocated in 00:02:37.224-->00:02:41.662 executable pool memory. So even if we knew where they were, we couldn't execute code off there. 00:02:41.662-->00:02:48.235 And finally uh user-mode is uh supervisor mode execution prevention has been uh 00:02:48.235-->00:02:53.473 implemented as well. Which means if we try to execute code in user mode from the kernel, we'll 00:02:53.473-->00:02:59.046 get blocked as well. So the technique of windows seven don't really work anymore. So what we 00:02:59.046-->00:03:03.450 need is we need a primitive. We provide primitive just like we know from the uh browser 00:03:03.450-->00:03:08.688 exponents, just in the kernel. Some of the most known ones are the uh bitmap primitive and the 00:03:08.688-->00:03:15.362 window primitive and these have existed for some years. Um. The idea is uh for the bitmap 00:03:15.362-->00:03:19.032 primitive that you can then get the address of a bitmap through uh a table called a 00:03:19.032-->00:03:24.704 gdisharedhandletable. Once you have the location of of a bitmap you can use the vulnerabilities 00:03:24.704-->00:03:30.010 to override the size of it. Once you overwrite the size, make sure to have two bitmaps uh 00:03:30.010-->00:03:34.214 after each other. Then use the first one to overwrite the second one and overwrite the 00:03:34.214-->00:03:39.219 pointer to the data area and use the second to read and write into memory. Certainly we can 00:03:41.354-->00:03:45.358 use a window primitive, kind of the same thing. We leak the address of the window, this time 00:03:45.358-->00:03:51.031 using another table called the user handle table. Overwrite the size of uh what's called the 00:03:51.031-->00:03:56.036 extra bytes. And then use the uh um set window long pointer parameter for API to uh allocate 00:03:58.038-->00:04:01.808 his ex uh change his extra bytes. And since we over writing over written the size of the 00:04:01.808-->00:04:07.581 extra bytes, we can actually change um the pointer for the string string im- string end 00:04:07.581-->00:04:12.586 pointer of the window. And this allows us to read or write again into our memory using use mode 00:04:18.625-->00:04:25.298 APIs. We got in user mode uh me- kernel execution, uh sorry use- pe- user mode page is execution 00:04:25.298-->00:04:31.938 uh we have to bypass uh SMEP or uh get the uh dep in the kernel disabled. The common used method 00:04:31.938-->00:04:37.444 is uh overwriting the page table entries using the write primitive. So we find the uh 00:04:37.444-->00:04:42.315 address of the page table entry for the code we want to execute and simply either flip the uh NX 00:04:42.315-->00:04:47.320 bit or change use mode page into a kernel mode page. Then we can execute it again. Sometime a uh 00:04:51.358-->00:04:55.162 KASLR Bypass in the kernel is needed as well for some uh x points especially when you want 00:04:55.162-->00:05:01.101 to run actually run the uh shell code. Um there are two known techniques. First of all the Hal 00:05:01.101-->00:05:06.106 Heap uh has been static uh in a man- for many years. So there is a pointer there. So uh you know 00:05:09.442-->00:05:14.447 whether its pointing to the NT kernel a static address. Just like the SIDT uh instruction can 00:05:16.483-->00:05:23.156 be used. You get an address back from uh disrupted table and then that there the nto kernel 00:05:23.156-->00:05:28.161 pointer. Then you use a read primitive to leak this pointer out and find the base address. 00:05:28.161-->00:05:33.800 By simply going taking the NT pointer, going backwards in the driver to find the API header. 00:05:33.800-->00:05:38.071 This is kind of like where kernel load exploitation was before uh the version of the 00:05:38.071-->00:05:44.911 anniversary last year. In the anniversary update, Microsoft did quite a few changes we'll go 00:05:44.911-->00:05:51.818 through here. See how try to mitigate other ways of doing the kernel exploitation. First of 00:05:51.818-->00:05:55.956 all the randomized the page table entries which means we can't just flip the bit because 00:05:55.956-->00:06:00.727 we don't know where it is anymore. They removed all the kernel addresses of the bitmaps 00:06:00.727-->00:06:04.898 and other objects from the uh gdi shared user handle table. Which means we don't know where 00:06:04.898-->00:06:11.671 to write anymore. So we can use bitmap primitive. And also the SIDT command, uh has been 00:06:11.671-->00:06:16.676 mitigate if we running in eh um we're using Hyper-V then it simply it gets uh a disclosed 00:06:18.979-->00:06:25.752 value so we don't know where the NT pointer is. So let's uh break some of this stuff. Additionally 00:06:25.752-->00:06:31.524 also these mitigations which weren't really uh public, uh what it did is that they uh made 00:06:31.524-->00:06:36.363 sure that the string importer for window object has to point inside the desktop heap. 'Cause 00:06:36.363-->00:06:41.134 that's where the actual gets allocated. So we try to read write outside the desktop heap 00:06:41.134-->00:06:46.139 by overwriting the pointer. We crash. So this allows breaks the unit primitive. So let's see if 00:06:51.044-->00:06:57.517 these mitigations actually work. Let's look at the bitmap first. So the bitmaps stored somewhere 00:06:57.517-->00:07:01.454 in memory. It's actually stored in what's called large paged pool. This takes place if the 00:07:01.454-->00:07:07.961 bitmap is at least uh thirt- thirty thousand uh bytes long. Um or more. Of course large 00:07:07.961-->00:07:11.665 paged pool is randomized in reboot so we don't know where it is beforehand. So we need some 00:07:11.665-->00:07:16.469 kind of kernel information leak to find it. Luckily if you look at the TEB we have the field in 00:07:16.469-->00:07:21.341 win thirty-two thread info, it actually includes a portal into the kernel. It doesn't point a 00:07:21.341-->00:07:26.346 large paged pool, but does point close to it. So we could do, we should try to stabilize pool. 00:07:29.182-->00:07:35.121 First allocate a few very large bitmap objects. Once this is done, we can add a static offset 00:07:35.121-->00:07:40.126 to the pointer we find. We actually pointing inside of bitmaps. Sadly we only pointing 00:07:42.696-->00:07:46.299 inside the data of the bitmaps. There's not really, we cannot really use it from our cloud. 00:07:46.299-->00:07:52.572 But what we can do, is we delete the second of these bitmap objects. And try to allocate 00:07:52.572-->00:07:58.111 around ten thousand new bitmap objects. Of a s- of a page each. We'll find this point actually 00:07:58.111-->00:08:03.316 does point into new bitmaps. This is a way to find the bitmaps again. Since we know 00:08:03.316-->00:08:08.221 what the bitmaps hit, you know we can just overwrite the size and again use two consecutive 00:08:08.221-->00:08:15.128 bitmaps to actually get uh a primitive back. So even though uh Microsoft removed the uh 00:08:15.128-->00:08:20.133 addresses from the uh the table, it's not really needed. And you see here I did the uh simulator 00:08:22.836-->00:08:28.007 write what where just by overwriting the length and then [indiscernible] execution with 00:08:28.007-->00:08:33.079 the code it's possible to read out the uh the the constant of the kernel and also write to the 00:08:33.079-->00:08:38.084 kernel. So looking at the uh window primitive, well we're not allowed to really write outside 00:08:42.555-->00:08:47.527 the desktop heap and this is due to a new function called the desktop verify heap pointer. Uh 00:08:47.527-->00:08:51.197 and every time we try to actually use the uh string importer by either reading or 00:08:51.197-->00:08:56.202 writing for it, well we have to be - is validate it through this function. So it takes the base 00:08:58.338-->00:09:03.209 address of the desktop heap and the size of it and checks it inside these two. Well what we 00:09:03.209-->00:09:08.214 notice is that the pointer comes from an object a tes- tech- tag desktop object. And there's 00:09:10.483-->00:09:15.588 actually no validation performed in this object pointer. And the pointer is hidden for your 00:09:15.588-->00:09:20.493 object. Our window object. Which means if we can find this pointer and replace it, we 00:09:20.493-->00:09:27.367 control the verification address. So what we do is we use uh delete this which is known 00:09:27.367-->00:09:34.040 from the user handle table and just leak the address of it. And then web just when we overwrite 00:09:34.040-->00:09:40.413 the uh string importer we also overwrite the pointer for the uh test tag desktop object because 00:09:40.413-->00:09:44.884 its the header as well. Then when we try to read or write well we control where the 00:09:44.884-->00:09:51.157 desktop heap is. So verification succeeds everywhere. Again we can see here trying to simulate 00:09:51.157-->00:09:56.930 write what where and just like before we can read and write an er- to the kernel. So yeah, 00:09:56.930-->00:10:01.935 that's what pu- what was put in anniversary update, but let's talk what's created these 00:10:05.271-->00:10:10.276 updates. Let's see what passed on there. 'Cause we de- did additional mitigations. What we 00:10:13.346-->00:10:17.617 find in the especially for the window object is that the user handle table is used to uh 00:10:17.617-->00:10:21.855 disclose addresses of the windows objects has been changed. So before we saw a lot 00:10:21.855-->00:10:27.060 of kernel addresses there, which were all the objects, they're removed now. So we cannot oh- 00:10:27.060-->00:10:29.996 don't know where the objects is so we don't know where the windows are anymore. 00:10:29.996-->00:10:35.001 Additionally the uh the field called cli- client data, which is the offset from he user mode 00:10:37.904-->00:10:42.642 map desktop heap to the actual desktop heap has been has been removed as well. So there's not 00:10:42.642-->00:10:48.948 really anyway here of finding the uh windows objects anymore. Additionally we use the set 00:10:48.948-->00:10:54.554 window long p- uh pointer API to uh make sure we e- brought the extra bytes to the kernel and 00:10:54.554-->00:10:59.559 overwrote the string importer. The new up update here, we see that uh when from this action 00:11:01.794-->00:11:05.465 the content which was [indiscernible] if you're interested, is no longer written 00:11:05.465-->00:11:09.769 to kernel mode. It's written to user mode. So even if we actually knew where it was and 00:11:09.769-->00:11:13.540 we overwrote the length of it, we would try to actually overwrite it, it doesn't. It 00:11:13.540-->00:11:19.145 would write in user mode instead. This of course breaks uh the primitive doesn't work 00:11:19.145-->00:11:24.417 anymore. Um some different additional changes to that is that the size of the bitmap 00:11:24.417-->00:11:29.756 object header uh has increased. This doesn't ch- uh break the primitive, but we need to uh 00:11:29.756-->00:11:35.695 make sure that we uh change the the size so that allocation alignment still works. And now 00:11:35.695-->00:11:40.700 the HAL Heap is also randomized so we don't need where the NT pointer is anymore. So actually 00:11:43.169-->00:11:46.773 the try to do a lot of stuff to break the window primitive. Let's see if that actually 00:11:46.773-->00:11:51.778 works. We set uh set the client delta is now gone, if you inspect memory, we find that 00:11:54.080-->00:11:59.118 it's been replaced by user-mode pointer and if check what's there the user-mode pointer. We 00:11:59.118-->00:12:04.057 actually find the desktop heap. The kernel desktop heap to user mode to make uh lookups faster. 00:12:07.026-->00:12:11.164 But that's also make sure that we actually have the kernel addresses. The user handle table 00:12:11.164-->00:12:15.702 was nice because it was a metadata, so it was fast to do a lookup. But we don't have that 00:12:15.702-->00:12:21.507 anymore, but we have the actual data. So we do instead is we just manually search for it. So 00:12:21.507-->00:12:26.512 we ta- take the uh the um uh the handle value, we search through desktop heap until we actually 00:12:28.915-->00:12:33.920 find it. Once we find it, we know where its at. So we're still able to leak the address. 00:12:36.689-->00:12:39.959 But there's additional pr- problem right? Even though we knew the address, or the size of 00:12:39.959-->00:12:45.798 extra bytes we couldn't use it anymore. Because we just write in user mode memory. Looking at 00:12:45.798-->00:12:50.269 how that's working we find that the size of extra bytes is actually defined when we 00:12:50.269-->00:12:54.273 registered the windows class. Well we register the windows class to set different 00:12:54.273-->00:12:59.278 parameters as well. We also set the parameter for an object and even better, it also has an API 00:13:04.884-->00:13:09.288 to uh set these extra bytes. And its not the same. This one is called set class long pointer. 00:13:09.288-->00:13:14.794 And low and behold even though Microsoft tired to mitigate it, they only changed one of the 00:13:14.794-->00:13:19.799 APIs. Didn't change a second one. So the extra bytes from the tagCLS was still placed in the 00:13:22.869-->00:13:28.007 kernel. Which means we can allocate a tagCLS before the window object. We can use extra 00:13:28.007-->00:13:33.579 bytes from the tagCLS object to overwrite the string and pointer of the window object. And this 00:13:33.579-->00:13:38.584 way we have a read write primitive back again. It's not easy to mitigate them. So this 00:13:46.626-->00:13:51.764 is clear even though in kernels update it did a lot of changes we still have our primitives. 00:13:51.764-->00:13:56.536 But they lo- list a lot of stuff right? To make sure the kernel ASLR is better and looking in 00:13:56.536-->00:14:01.307 memory almost all the kernel memories are randomized. The only place I know of that isn't, 00:14:01.307-->00:14:08.147 is the uh the user shared data uh structure for the kernel. But its not executable then there's 00:14:08.147-->00:14:14.520 no point is there? So its not really interesting. The HAL Heap and is randomized. SIDT 00:14:14.520-->00:14:19.525 mitigated, so we need some new way to leak the NT kernel pointer. My idea is that uh 00:14:22.128-->00:14:28.401 perhaps you can finally leak this primitive related. What we first need, we need uh two 00:14:28.401-->00:14:33.406 bypasses. We have two primitives and I wanted to be ac- be the nt kernel pointer we leak. So to uh 00:14:36.075-->00:14:41.414 try and pursue this idea. I start about reactos. Reactos is the open source 00:14:41.414-->00:14:46.519 pre-implementation of Windows xp. With a reverse engineering of all structures. Which means 00:14:46.519-->00:14:50.656 that undocumented kernel structures are found there even though they're for windows XP 00:14:50.656-->00:14:55.661 and thirty-two bit. That might give some hints. So looking at the data structure for bitmap or 00:14:57.830-->00:15:04.003 surface optic as its really called in the kernel. Uh we find a field uh called H DEV. An 00:15:04.003-->00:15:10.076 explanation for that H DEV is pointer to a null object called PD DEV object. Inspecting 00:15:10.076-->00:15:15.214 further we find the REACTOS has documented this pic as well. Actually contains a lot of 00:15:15.214-->00:15:20.553 function pointer, which means these function pointers, point to same kind of kernel forever. 00:15:20.553-->00:15:25.558 We see again gives us our kernel um bypass. KASLR bypass. We've got shake on bitmap object. We 00:15:29.862-->00:15:34.667 find the H DEV field is empty. There's nothing there, which means we cannot find this, we 00:15:34.667-->00:15:41.507 cannot find the integral anyway. Luckily, the bitmap we created using [indiscernible] bit by API 00:15:41.507-->00:15:47.280 isn't the only bitmap. There are several other APIs to create bitmaps with. One of them is the 00:15:47.280-->00:15:53.920 uh create compatible bitmap. And trying to use it, this API shows that we actually do populate the 00:15:53.920-->00:16:00.827 H DEV field now. And while this is populated, we can verify in the dbarker that it does contain 00:16:00.827-->00:16:05.832 a pointer to driver. It's not the NT kernel driver, but is a driver. So how do we do this in 00:16:08.167-->00:16:14.006 export? Well what we do is we uh know where the first bitmap is at. We found the datas so we go 00:16:14.006-->00:16:20.847 to office two thousand, we free this bitmap then we re-allocate it with the compatible bitmap. 00:16:20.847-->00:16:24.250 We just spray a couple hundred of these to make sure that one of them is re-allocated in the 00:16:24.250-->00:16:29.255 same spot. Once that is done we just read out the NT pointer for the code driver. You use, the 00:16:33.893-->00:16:38.064 reason I took this driver so this function in us it says this function actually contains a 00:16:38.064-->00:16:44.570 call of two B instantly into the kernel. So from this its very easy just to read out NT kernel 00:16:44.570-->00:16:49.175 pointer and then again usually uh you read primitive to find the base address of the NT 00:16:49.175-->00:16:54.180 driver. So this way we have a generic bypass to find the NT kernel, using the bitmap 00:16:58.251-->00:17:04.924 primitive. Looking at the window primitive, well its also the argument REACTOS. And the header 00:17:04.924-->00:17:09.729 structure for window update is kind of convoluted. Its a lot of structure nested in each other. 00:17:09.729-->00:17:13.499 Well if we follow the chain, we find that a lot of different header structures. Uh end up 00:17:13.499-->00:17:20.273 pointing at the uh the uh sort of the uh end thread. Of the kernel process. And this for its 00:17:20.273-->00:17:25.278 for its very interesting, because it contains a pointer to the NT kernel. So again e just 00:17:27.446-->00:17:34.020 use a read primitive and the location of the uh window ob- window object to read out NT 00:17:34.020-->00:17:40.126 point- NT kernel pointer and then find the base address, by looking for the PE header. So in 00:17:40.126-->00:17:45.131 this way we actually have a we generic way to find the uh NT kernel pointer NT base address 00:17:47.500-->00:17:52.872 no matter which primitive we use. As I said I'm going to be talking some extra stuff here. 00:17:52.872-->00:17:57.877 So uh actually while I take this research I found a couple other uh bypasses. Um one of them is a 00:18:00.780-->00:18:06.886 primitive independent. Um which comes from the TEB. As I said the win thirty-two thread info 00:18:06.886-->00:18:11.590 field we use that to disclose the address of the bitmaps. But there's more than that because 00:18:11.590-->00:18:16.762 it's actually thread info pointer. And a thread info pointer points to the E thread. 00:18:16.762-->00:18:21.567 As you saw before, the E thread contains the NT pointer. So in this way we just create the NT 00:18:21.567-->00:18:25.571 pointer without actually allocating any kind of objects if they have a reader primitive. 00:18:25.571-->00:18:30.576 So this would work with any other primitive as well. Its from our firewall. If we need to 00:18:32.812-->00:18:39.251 know where the the uh bitmap primitives are, instead of using the uh the TEB, we can also the 00:18:39.251-->00:18:45.057 the desktop heap. It actually contains on the the um windows update we can find point there, 00:18:45.057-->00:18:50.062 use the same setting offset from and we get the address of the bitmaps again. This was uh a 00:18:54.266-->00:19:00.139 largely fixed update after the submission of dark, but it still works on the anniversary update, 00:19:00.139-->00:19:05.311 using the uh thread local storage pointer. You can also disclose uh an address into the 00:19:05.311-->00:19:10.316 kernel, which can be used to find the address of the bitmaps. And finally also mitigating my 00:19:13.052-->00:19:17.423 previous update, but still working an anniversary update uh instead of allocating, actually 00:19:17.423-->00:19:21.994 allocating the window object and using the head of that, we can use the DCE infrastructure 00:19:21.994-->00:19:27.533 directly by uh searching through that and actually finding uh prior to NT kernel. But simply 00:19:27.533-->00:19:32.538 looking through structures on simply land a cor- correct Windows object. So this way 00:19:34.940-->00:19:39.945 we're sure we have a way to bypass KASLR even if one of them is fixed. Although its always 00:19:44.517-->00:19:48.721 cool to bypass kernel KASLR, what do we need for? Well we need to do something and what we 00:19:48.721-->00:19:53.059 did before is we used to read our primitive to overwrite the page table entries. So the pages 00:19:53.059-->00:19:57.997 were either executable uh in the kernel or we flipped the bits so it was uh usable age would turn 00:19:57.997-->00:20:03.803 into a kernel page. The reason we can do that was before this the uh page change ta- the page 00:20:03.803-->00:20:09.842 table entries were u starting at a static base address. And we just calculate the address of 00:20:09.842-->00:20:15.281 the uh page table entries for any address. But now since its randomized we cannot do that 00:20:15.281-->00:20:20.653 anymore. So we need to try to de-randomize it. And my thought is that even though its 00:20:20.653-->00:20:25.758 randomized the kernel must use the lot. So it must have APIs for this. These APIs must work 00:20:25.758-->00:20:32.698 even as randomization. So I looked up and I found couple different APIs which uh I used 00:20:32.698-->00:20:37.369 and the most simple one I found is called the migetpteaddress and it's simply used to 00:20:37.369-->00:20:42.374 translate between uh an address and the um and the PTE. Looking at it an item we can start to 00:20:46.145-->00:20:52.485 analyze, we find that on the left side is it has a static address. So it's not compiled 00:20:52.485-->00:20:57.957 into it its gets uh changed on run time because we can see on the right side, that uh at run 00:20:57.957-->00:21:03.496 time the address is different. That also means that when the drivers running in memory we 00:21:03.496-->00:21:08.501 have a way actually of finding the base address of the page table entries. So what we do is 00:21:11.303-->00:21:16.609 we find this this uh function and from that we read the base um randomized base address. And 00:21:16.609-->00:21:21.046 how do we find this function? Well one way is of course since we have the base address of the 00:21:21.046-->00:21:26.385 uh kernel uh the NT kernel, we just add a static offset. The problem with this is it doesn't 00:21:26.385-->00:21:30.322 work across patches right? Every time there's a patch, the offset will change and we need to fix 00:21:30.322-->00:21:35.594 it. So better way would tot would to do this dynamically. Since we have a read primitive, 00:21:35.594-->00:21:41.033 I would like to look this up. Uh what we do is we just dump the uh content of the NT kernel, 00:21:41.033-->00:21:46.806 then search through it using hashing function. As it turns out, the hashing function just 00:21:46.806-->00:21:52.278 asks the first QWORDS of the function is collision free. So we just calculate hash 00:21:52.278-->00:21:58.684 beforehand and then it should be doing the execution of the the uh next point, dump use the uh 00:21:58.684-->00:22:04.089 read primitive to dump the content, go through it until we find it. And doing this finds it 00:22:04.089-->00:22:10.596 across different versions. From that we can just read it out since it's at offset thirteen. 00:22:10.596-->00:22:14.300 We read out the base address and then we can use the old formula again with the updated address. 00:22:14.300-->00:22:21.273 And from this we simply find it uh simply find the uh page table entry and we're back to where we 00:22:21.273-->00:22:27.580 were before. We just allocate this time, this case uh of shell code directly in kernel memory. 00:22:27.580-->00:22:31.784 Flip the NX bit using page table entries on the write primitive and then we can call the shell 00:22:31.784-->00:22:37.656 code like before through by overwriting the HalDispatchTable uh and then invoking it. 00:22:37.656-->00:22:43.095 Similarly we could also just allocate it in user g- user memory and flip the the uh the 00:22:43.095-->00:22:49.068 bits so it becomes a kernel page instead and execute it. Let's will work. So this is like what 00:22:49.068-->00:22:53.572 we did uh before. So even though they implemented a lot of changes, lot of mitigations, 00:22:53.572-->00:22:59.845 well we can bypass them all. And just a recap steps here, we use vulnerability to create a read 00:22:59.845-->00:23:05.217 write primitive. From that we leaked a base address of the NT kernel using either primitives. 00:23:05.217-->00:23:11.557 We loca- locate the address of this uh function. From that we get the randomized base address 00:23:11.557-->00:23:17.329 of the PTE tables, we can then calculate the PTE of shellcode address. Co- uh copy a shell 00:23:17.329-->00:23:22.334 code to the right page, then overwrite the overwrite the PTE of the shell code and run it. So 00:23:27.840-->00:23:31.744 yeah. This is just like we did before. There's not really uh thing that changes how to do 00:23:31.744-->00:23:37.283 kernel exploitation now. It's the same thing. But I thought, I like the old days in windows 00:23:37.283-->00:23:42.488 seven where we could allocate executable kernel mode uh memory. Just execute it without 00:23:42.488-->00:23:46.892 flipping any kind of uh bits of the page table entries. Is that possible in windows ten? That 00:23:46.892-->00:23:52.898 would be awesome. So I started looking into that. And uh the first thing you come across is 00:23:52.898-->00:23:57.069 how does kernel actually allocate pool memory? Well it uses an API called 00:23:57.069-->00:24:03.108 exallocatepoolwithtag and this uh API takes some ar- some uh arguments. One of them is the 00:24:03.108-->00:24:10.082 pool type. And even though the uh new standard is the uh nonpaged pool NX. The old one is 00:24:10.082-->00:24:16.689 still uh support is also now called the nonpagedpoolexecute. So we would invoke this API with 00:24:16.689-->00:24:22.428 the correct pool type. We would allocate executable pool memory and this API also returns the 00:24:22.428-->00:24:29.001 address of that pool memory. The other problem is of course this API is a kernel mode API. We can 00:24:29.001-->00:24:34.006 call it directly from userland. So what we can do is we could of course override the um the Hal 00:24:36.608-->00:24:41.580 dispatch table, this API could call it. The problem is we need control of the arguments. And 00:24:41.580-->00:24:46.118 when we invoke the uh functions through the Hal dispatch table, we can only specify two 00:24:46.118-->00:24:50.055 arguments and they have to have specific values for it to actually work. So this system 00:24:50.055-->00:24:55.060 call doesn't work. We need a different one. Looking around I found the variation [inaudible] 00:24:59.331-->00:25:03.435 ones less NTGdiDdDDICreateAllocation. And trying to follow that in the 00:25:03.435-->00:25:10.209 kernel, we find it jumps through all the uh bits by bit two K kernel drivers. Until it ends up 00:25:10.209-->00:25:15.614 being called through a function table. So it's a very thin trampoline, nothing gets touched 00:25:15.614-->00:25:21.453 in the way there and looking this function table, we find that this calls into a different 00:25:21.453-->00:25:26.291 driver and best of all that the arguments are not modified from the time that our system call in 00:25:26.291-->00:25:32.564 user land so the execution of the uh function through the uh function table. Additionally, 00:25:32.564-->00:25:36.869 prototype of this function, the user mode function, is that it returns QWORD. Which could be an 00:25:36.869-->00:25:42.141 address. So it fits all our requirements, to actually to call it, to to call the the uh 00:25:42.141-->00:25:47.546 allocate pool attack. The only thing missing is that we uh we should be able to actually write 00:25:47.546-->00:25:52.384 that address there. In inspecting the function table we find that its writable. So we 00:25:52.384-->00:25:58.724 can just patch it using our write primitive and overwrite it with uh allocate pool attack 00:25:58.724-->00:26:04.496 API. But of course we need to find it and since it's a function table, it contains 00:26:04.496-->00:26:07.599 pointers we don't know beforehand. So we cannot use a hashing function to find it 00:26:07.599-->00:26:13.906 directly. So we need to find a function which uses this function able. Um and one 00:26:13.906-->00:26:19.745 function I found which works is the dive occlusion state change notify. Uh and it's quite simple 00:26:19.745-->00:26:24.450 function, simple header and calls into the uh function table. The other problem is it's 00:26:24.450-->00:26:30.856 located in the win thirty-two K dot full dot sys driver. That we don't have the address of that. 00:26:30.856-->00:26:35.761 So we turn one problem into another. We need to find that kernel driver. One way to do 00:26:35.761-->00:26:41.333 that is using the uh P- PS loaded module list. Its a uh length list which contains all 00:26:41.333-->00:26:45.771 the kernel drivers currently loaded um and looking through the structure we find that the 00:26:45.771-->00:26:51.043 uh name of one driver is located offset sixty. And the base address is that driver lo- 00:26:51.043-->00:26:56.482 located offset thirty. So again we can use a read primitive to read through this list looking 00:26:56.482-->00:27:00.986 all the names until we actually find the correct uh driver name and just read out the base 00:27:00.986-->00:27:05.958 address. So now we have the base address. But again we turn the problem into a different one. 00:27:05.958-->00:27:11.129 Now we need to find the Ps Loaded Module List. And this is a load length list we can use a 00:27:11.129-->00:27:16.335 hashing function because we don't bu- the the values of it. So again we need to find a 00:27:16.335-->00:27:20.939 function that uses this. One function is the uh keCapturePersistentThreadState. 00:27:20.939-->00:27:27.379 Which is located in NT kernel. And we have the NT kernel base address, right? So we can use 00:27:27.379-->00:27:32.184 then a hashing function find this function from them, get the base address so we from that get 00:27:32.184-->00:27:36.989 the uh Ps Loaded Module List from that get the base address of the win thirty-two k full dot 00:27:36.989-->00:27:41.860 sys. From that get the drive occlusion state change notify and from that get the function 00:27:41.860-->00:27:46.865 table. Luckily all of this takes less than a second when running, so it's not a problem. Once we 00:27:50.269-->00:27:56.408 got that we simply overwrite offset sixty-eight with the uh the uh allocate uh pool with tag 00:27:56.408-->00:28:03.248 API and then call it. Allocating pool memory and returns allocating pool memory then we 00:28:03.248-->00:28:08.520 use a write primitive to copy the shellcode into it and execute it by overwriting the uh 00:28:08.520-->00:28:14.526 function table with the allocated pool memory. This is like just like the days of win- 00:28:14.526-->00:28:20.499 almost like win- days of windows seven. Allocate executable pool memory and execute it. This has 00:28:20.499-->00:28:25.671 a few more steps so it's not more efficient in anyway, but it's a different way. So one, 00:28:25.671-->00:28:30.676 one method gets fixed, doesn't work, we can use another way, another one. Yeah. So let's try 00:28:33.045-->00:28:38.050 to see it in action. So we have a windows ten here. I'm running in uh low mandatory level. So uh 00:28:51.096-->00:28:56.101 just like inside a sandbox. So we try and run it. Does the uh pool spray and wants us to 00:28:58.971-->00:29:03.909 simulate the write what where, so let's do that. From the kernel reporter. And we see here 00:29:11.416-->00:29:17.856 that it's we get the address here and it contains the uh the length of the bitmap, just as we 00:29:17.856-->00:29:22.861 want it. So uh just simulate the write what where. So I increase the length here and then 00:29:30.669-->00:29:35.674 execute. We go back. We haven't had a crash. But we got a system shell. [applause] And as you can 00:29:46.785-->00:29:51.490 see from the time I actually ended the uh simulated write what where the execution was 00:29:51.490-->00:29:56.495 yeah this in a second. So even though all these lookups don't take any time in reality. So in 00:30:08.306-->00:30:13.545 a summary, even though there are a bit lot of mitigations in the uh the kernel versions of uh 00:30:13.545-->00:30:17.749 windows seven latest versions of windows seven uh windows seven. None of the old techniques 00:30:17.749-->00:30:23.388 really broke it. We can revive them, we can bring them back. So read write primitive work. Page 00:30:23.388-->00:30:28.694 table entry uh overwrite work. We can actually lead the NT kernel in new ways. Which work- 00:30:28.694-->00:30:34.366 didn't work before, but we can leak it. And we can actually also now allocate executable po- 00:30:34.366-->00:30:39.371 uh kernel pool memory. The code for this is already on uh GitHub. Um so you can you can 00:30:41.773-->00:30:47.012 get it there if you want to play with it. Uh we'll just say that of course I didn't find out all 00:30:47.012-->00:30:51.783 of this, uh this was pre-research here. Which I want to credit people for. Yeah. 00:30:51.783-->00:30:56.788 That's it. Thank you for listening to me. [applause]