Logo TIB AV-Portal Logo TIB AV-Portal

Demystifying Modern Windows Rootkits

Video in TIB AV-Portal: Demystifying Modern Windows Rootkits

Formal Metadata

Title
Demystifying Modern Windows Rootkits
Title of Series
Author
License
CC Attribution 3.0 Unported:
You are free to use, adapt and copy, distribute and transmit the work or content in adapted or unchanged form for any legal purpose as long as the work is attributed to the author in the manner specified by the author or licensor.
Identifiers
Publisher
Release Date
2020
Language
English

Content Metadata

Subject Area
Abstract
This talk will demystify the process of writing a rootkit, moving past theory and instead walking the audience through the process of going from a driver that says "Hello World" to a driver that abuses never-before-seen hooking methods to control the user-mode network stack. Analysis includes common patterns seen in malware and the drawbacks that come with malware in kernel-mode rather than user-mode. We'll walk through writing a rootkit from scratch, discussing how to load a rootkit, how to communicate with a rootkit, and how to hide a rootkit. With every method, we'll look into the drawbacks ranging from usability to detection vectors. The best part? We'll do this all under the radar, evading PatchGuard and anti-virus.
constantly choice programming complex randomization multithreaded administrations open subsets perspective image DNS Arrays sign configuration different case Security exception systems classes screens certification load digital construction infinity bits instance signatures data management means processes communication buffer orders reading Recursive point registry files varieties patched Firewall drivers number Forum root level boundaries structure Booting default standards information Content workgroup volume directories lines applications system call Location Software environment case enumeration functions drivers Games table intercept discrepancy Windows breadth code exiting time views sheaf sets domain primitives instance functions registration Part stacks Kapsel certification ease of use exclusion mathematics malicious code component-based hook memory Operationen socket file system flag model stable Weak generate indicators private key entire Types real vector masking input website disk Right procedure structure Results since surjections modes filters current track flow modes functionality app services machine major surgeries attributes versions naturally operations Jump conditions modules noise stacks incident drivers leaks Antiviruses Indexable Pointer kernel logic root Blog objects Routing operating system
- Hi there, My name is Bill, and thanks for coming out to my talk. So I'm 19 years old, I'm a rising sophomore at the Rochester Institute of Technology. I love Windows Internals. I'm mostly self-taught with some guidance and I've a strong game hacking background, which is why I'm so interested into Windows kernel. So what is this talk about? Well, in this talk will be going over loading a rootkit, communicating with a rootkit, abusing legitimate network communications in order to communicate with our malware. And example rootkit I made and design choices behind it. Executing commands from kernel, and tricks to coverup the file system trace of your rootkit. So when you hear me say, "Rootkit" I'm referring to kernel level rootkits for Windows. So why would you wanna use a rootkit? Well, there's a lot of reasons. Kernel drivers have significant access to the machine. Unlike in user mode, you pretty much have the same... You have the access to anything and everything. Kernel drivers have the same privilege level as a typical kernel anti-virus. Generally speaking, you share the same access to the same resources. There are less mitigations in security solutions targeting kernel malware. If you can load kernel code, there's a lot you can do to cover up your tracks. Antivirus often have less visibility into the operations performed by kernel drivers. This is because a lot of antivirus rely on user mode hooks in order to gain visibility into potentially suspicious operations. In kernel, however you can't directly hook the anti-IRPS kernel functions due to PatchGuard, reducing the visibility antivirus IRPS significantly. And finally, kernel drivers are treated with a significant amount of trust by antivirus. Let's take a look at an example of that. So whether you're running a consumer antivirus or a corporate EDR, chances are whatever solution you run, treats kernel drivers with a significant amount of trust. Here I have two examples from Malwarebytes in Carbon Black, specifically they're preprocessed thread callback functions. These functions that are called whenever a handle is created to a process or a thread. In the case of Malwarebytes, they check to see if the process ID is less than eight, and if the operation is for a kernel handle, then it will not continue processing that handle creation. In the case of Carbon Black, if you handle is for a kernel handle, or if the caller is not from user mode, it will not process that handle being created. Let's talk a little bit about loading a rootkit. So the first option you have is to abuse the legitimate drivers. There are a lot of publicly available vulnerable drivers out there. And with some reversing knowledge, finding your own zero day in one of these drivers can be trivial. Examples of vulnerable drivers include Capcom's Anti-Cheat driver Intel's NAL Driver and Microsoft themselves. Now, the reason I put vulnerable and zero-day in quotes, is because oftentimes these drivers require administrative privileges to communicate with them. Following Microsoft standards ring thread administrator to ring zero is not a security boundary. So technically they're not vulnerabilities, but that doesn't mean we can't abuse them. Abusing legitimate drivers has quite a few benefits as well. You only need a few primitives to escalate privileges. Finding a vulnerable driver is relatively trivial, a good place to start is OEM drivers. And it's difficult to detect due to compatibility reasons. For example, let's say a driver had a vulnerable component that could potentially be exploited. The problem antivirus face is that if the legitimate application also uses that vulnerable component, how do you detect a malicious program requesting this vulnerable component, versus the legitimate application requesting that vulnerable component? That becomes a tricky problem. There are some drawbacks for this method as well though. The biggest issue I've had with this method is compatibility. Oftentimes the primitives you get might not be a lot and even then can have stability issues such as race conditions. Now, this is a significant threat if you're writing red team tooling, because you do not want your malware to blue screen a victim, that's probably the worst case scenario. It would be a pretty big indicator of compromise and you're probably gonna get caught. And this is why I generally stay away from this method. Now another method is to just buy a code signing certificate. This is completely valid for some red teams, especially for targeted attacks. And primarily because you're not gonna have any stability issues, it's the normal way of getting a driver's signed is just to buy your own certificate under your company's name. But it will reveal your identity, and also it can be blacklisted. Now this blacklisting doesn't happen so much nowadays. This is something that I know is being worked on by antivirus vendors and even Microsoft themselves. It's not widely spread there yet, so you're probably not gonna see too much blacklisting happening nowadays. Another option is just to use someone else's certificate. You'd be surprised at the number of publicly available leak certificates online. A good place to start if you're looking for one is game hacking forums. Now at leak certificates have almost all of the benefits of just buying your own certificate without deanonymization. But the leak certificate you use can be blacklisted in the future. It goes back to the same point about buying your own certificate. And if the certificate was issued after July 29th, 2015, you can't use it on secure boot machines, running Windows 10, 16 or seven or higher, unless it is an EV certificate, which is probably not gonna happen. In most cases, Windows doesn't even care if the certificate appears to be expired or even revoked. This is because what you see in a digital signature section of a driver is not what the kernel checks, when determining whether or not to load a driver. So this view you see here when it says certificate was explicitly revoked, that's when Warfare thrusters returning not necessarily what the kernel cares about. So if you do come across a certificate that's expired or revoked, chances are, you can still use it for signing a driver. Now, if you don't wanna use one of the publicly available certificates out there, you can also try to find your own. One method I found interesting was to use open s3 buckets to search for private keys. I used a site called Grayhat Warfare which searches these open s3 buckets, and I was able to find over 6,000 results for the PFX and P12 extensions, which is definitely a place to look at if you're trying to get your own certificate. And the best part about this method is that it's undetected by the bulk of antivirus. Now, I don't understand if antivirus had trouble detecting a certificate that was released a month ago because that's recent, but some of these certificates have been out there for years and yet even the most next generation cutting edge solutions fail to detect this basic threat. Let's talk about communicating with a rootkit. A tried and true method of communicating with your malware is to just call home to C2. Now firewalls can block or flag these requests that go to suspicious IPs or ports, and even for more complex methods, such as DNS exfiltration. There are some solutions being developed, such as an Advanced Network Inspection that can try to catch in these methods that attempt to blend in with the noise. Some malware takes route that the C2 connects to the victim directly to control it. Now, this is extremely easy to set up, but at the same time, it's extremely easy for a firewall to block this. And it's very difficult to blend in with the noise, given that you're using one port exclusive. A more advanced method I've seen is to hook into an applications network communications directly. Now, this is a very hard to detect, especially if you're mimicking a legitimate protocol, but it's not very flexible because in a lot of environments, you're gonna have different ports exposed, different services running. And so if that one port or service isn't exposed, you can't use that line of communication, which isn't very reliable. So what I wanted when choosing a communication method was limited detection vectors, flexibility for a variety of environments, while the assumptions that some services will be exposed, which is especially true in corporate environments that have active domain services running. But inbound and outbound access may be monitored. So it's gonna have to be a method that has a low detection, vector opportunity. Now application specific hooking was perfect for my needs, except for the flexibility. Is there any way we could change application specific hooking to where it isn't dependent on any single application? Well, it turns out there might be away. So what if instead of hooking one application directly, we hooked network communication, similar to tools such as Wireshark. So this means that we would hook every packet that reaches any port on the system and we're able to inspect it. And then what we did is on our C2, we created a custom packet that had a magic constant value. And right after that magic constant, there was information we wanna pass to our malware. So what we could do is we could send this malicious packet to any port on the victim machine. And then the victim machine running our malware would check every packet incoming and see, "Hey, there's a packet with a magic constant." And we'll be able to use it as a reference point to extract information from our C2. So using this method, we could communicate from our C2 to our malware by abusing any legitimate port on the victim machine. Let's talk about how we would actually do this and specifically hooking to user mode network stack. So a signal inefficient amount of services on Windows lie in user mode. And how can we globally intercept this traffic? Well, networking relating to WinSock is handled by afd.sys otherwise known as the Ancillary Function Driver for WinSock. Reversing a few functions inside of mswsock.dll revealed that a bulk of the communication was performed through IOCTLS. If we could somehow intercept these IOCTLS, we could snoop in on the data being received. So how do apps know where to go? So how does a kernel determine what function to call of what driver? Well, first it'll obtain the device object associated with a file object by calling IoGetRelatedDeviceObject. Now for our purposes, this will just be retrieving the device object member of the file object structure. If the driver associated with the device supports fast IO, it'll dispatch the request using the fast IO dispatch table, part of the driver object structure. If the driver does not support fast IO, then it'll allocate and fill out an IRP and forward that IRP to the driver by calling IOCallDriver. Now there's a few standard methods of intercepting IRPS. And the first method to replace the major functions table array, that is part of the driver object structure. Now this major function array contains pointers to dispatch functions and to index for this array directly corresponds to the major function code, which is relevant for that dispatch function. So for example, what we can do is if we wanted to hook a certain major function code, we could replace that index in the array with a pointer to our own function, which would redirect IRPS for that major function to our driver instead. Another option is just to perform a code hook directly on the dispatch function itself. So when picking a method, there's few questions you wanna ask yourself. How many detection vectors am I exposed to? How usable is the method, both from a compatibility and stability perspective? And how expensive would it be to detect the method? Well for hooking a driver object in memory, you're gonna be exposing yourself to memory artifacts. And from a usability perspective, you're gonna be quite stable mostly because driver objects are well-documented. And from a detection perspective, though, it wouldn't be crazy difficult for a antivirus to detect because there's only gonna be a handful of driver objects out there because there's only a handful of drivers loaded at once. And all in antivirus need to do is enumerate these driver objects and check the major functions array for signs that the dispatch function is outside of the drivers bounds, Broking a drivers dispatch function directly with a code hook, you're gonna be exposing yourself to memory artifacts, but unless the function is exported, you're gonna need to find a yourself, which the... And there's a couple of ways to do this, but it's just something to especially consider if the underlying driver file might change between Windows Operating System versions, which afd.sys does. And also not all drivers are gonna be compatible with this method due to PatchGuard, and this is also HVCI incompatible. Now, how expensive would it be to detect? Well, that can vary. So there's quite simple ways of detecting hooking that are varying inexpensive and there's other methods that can get pretty expensive. Now one inexpensive method could be, if they know that you're hooking this one function in this one driver, they could check the bytes of that driver function then check for tampering. That's pretty inexpensive. But another method is if the antivirus can enumerate every driver loaded and specifically the executable sections of that driver module to check for differences between what's on disc and what is on the actual... What's in memory. Now I wanted a method that was undocumented, stable yet relatively expensive to detect. So what if instead of hooking the original driver object, we hooked the file logic structure instead? Well, if you recall to one of our previous slides, the way that the kernel determines what device is associated with a file object is by calling IoGetRelatedDeviceObject. And for our purposes, this is the device object member of the file object structure. Now, what is stopping us from overriding this device object pointer inside of the file object with our own device? Well, it turns out absolutely nothing. So what we can do is we can create our own driver and device objects, patch our copy of the device object using a common method such as replacing the major function table. And then we can replace the DeviceObject pointer inside of the file object with our own device. So let's talk about how we would do this. Well, first we need to find file objects that are to the \Device\AFD device object, but how can we actually find these objects? Well, the Windows NT kernel exposes this great function called ZwQuerySystemInformation, which allows you to query a lot of different information about the system. And one of the classes you can query for is called SystemHandleInformation, which allows us to enumerate every handle opened on the system, including the process ID that the handle is associated with and the pointer to the kernel object associated with that handle. Now, if we open the AFD device ourselves, we can easily determine if a file object is for the AFD device, by comparing the device object member with the previously opened AFD device. So then we can see, okay, this file object is associated with the AFD device. Now, before we can overwrite the DeviceObject member, of the file object, we need to do some preparation first and specifically, we need to create our fake objects. Fortunately, the kernel exports the function we can use to create our own objects. We can call ObCreateObject passing in an IODriverObjectType and IODeviceObjectType respectively, and then copy over the existing object data using a function like memcpy. Now with our fake objects created, we're almost ready to set the DeviceObject of the file object. But first we need to hook our driver object. And the way we can do this is by using the standard major function Hook Method, except remember, we're performing this on our own copy of the driver object, not what a normal antivirus could retrieve, so it's actually relatively safe. Now to prevent race conditions between our hook function and the DeviceObject member, we need to replace it using an interlock exchange. Now, one thing to remember here is that if when you replaced a file objects, device object, you can have a try at normal, the Windows kernel call that file object at any time. So you're gonna wanna use an interlock exchange in order to make sure the device object you use in your hook, is sent at the same time to device object is replaced inside of a file object. So now that we've actually hooked the file object, there's not much work left. Inside of our dispatch hook, we need to check to see if the major function code being called is hooked, and if so, we need to pass the original dispatch function, the original device object and the IRP to our hook function. Now, the trick here is also if we received a major function code cleanup, we need to replace the DeviceObject member of the file object with the original DeviceObject. This is to prevent issues during tear down. So from a detection perspective, we're gonna be exposing ourselves to memory artifacts and from a usability perspective, most of the functions we're gonna be using for this is semi documented and unlikely to change significantly. And finally, of how expensive is it to detect the method? It's gonna be pretty expensive because an antivirus would need to replicate our hooking procedure, and so they would have to enumerate every file object to detect if the device object has been tampered with, which is also adds some complications. So now let's talk about how to Spectre Rootkit, the rootkit I wrote, abuses the user-mode network stack. So now we're using our file object hook, we can intercept IRPS to the AFD driver. This will allow us to inspect all user-mode traffic and send and receive our own data over any socket. To review our existing plan, we're gonna hook user-mode communication, similar to tool such as Wireshark, and then we're gonna use our C2 to place a special indicator inside of a packet we sent to any port on the victim machine. And this way our C2 can then communicate with our malware through any port, any service that is exposed. Now, how can we actually retrieve the contents of the packets that are received when the WSA receive function is called? Well, when you call that function, it's going to send an IOCTL called IOCTL AFD RECV, and specifically it'll pass the AFD receive info structure in the input buffer. Now this buff... Now this structure contains some flags, but what we really care about is the WSA buffers, which is essentially an array of a race. And these buffers actually contain the bytes that are received from the packet, which we can use then to look into. So let's talk a little bit more about how to Spectre Rootkit was designed. Starting with our packet structure, you can prepend any data you'd like, as long as it doesn't contain the magic constant at the start of your packet. And after, any prepended data you'd like you can place a magic constant, which will act as a reference point for the malware. And after magic constant you can add base packet structure, which will have basic information. As you can see on the right about the packet length and the type of operation being requested. After the base packet structure, you have an optional custom structure. Now this, again, custom structure is optional, so it might not be there in the first place, but this will vary depending on the operation being requested. And after this optional custom structure, you can have any data you'd like. Now this model allows for quite a bit of flexibility because you can first send any packets you'd like even one that doesn't contain the magic. And then you can send a packet that has any prepended data any pended data with the magic content inside, malicious packet inside. And then you can, after that packet, send any packet you'd like. So to key what I was trying to get at here is that the Spectre Rootkit design allows for quite a bit of flexibility in how you structure your packets. So what happens when the Specter Rootkit receives a packet? Well, it's pretty simple. First it'll search the buffers for the magic constant. If the buffer contains the magic, it'll go ahead into the processing stage. And if demand... If the buffer does not contain the magic, then it'll just ignore the packet. Now before dispatching the packet, there's a few steps we need to take. First, if we have enough bytes to fill out a base packet structure, we'll try to fill out a custom structure as well, using the bytes we already received. In any case, if we do not have enough bytes for either to base packet or optional custom structure, we'll receive the rest. And finally, we'll dispatch to pack it. Now, before we go any further, let's talk about the concept of packet handlers inside of the Spectre Rootkit. So the Spectre Rootkit contains this general packet handler class that exposes a virtual process packet function. Now this base packet handler class has a default constructor that receives a pointer to the current packet dispatcher incidents. And the process packet function receives a pointer to the packet itself. We'll talk more about the dispatcher later. Now, an example of a packet handler included with the Spectre Rootkit is that PingPacketHandler. This handler is quite simple. It is simple... It is just used to determine if a port/machine is infected. All it is, is a bare bone magic and then base packet structure. And this base packet structure is, has its type or operation Set the Ping. And when the Spectre Rootkit or specifically the PingPacketHandler receive as a packet, all it will do is send back an empty base packet with the operation Set the Ping. Now, once a packet is completely populated, the packet dispatcher will allocate a packet handler depending on their requested operation. Then it'll call that packet handlers process packet function. And finally, it'll packet handler. Now the reason the packet dispatcher model is really nice is because by passing a pointer to itself, to any packet handler, any packet handler can then recursively dispatch a brand new packet. Now, before we get into how this dispatching works and recursively dispatching, to give you an example of how the flow of a PingPacket goes is first the Spectre Rootkit receives that packet, it will recognize that there's a magic constant there, it'll then fill out the base packet and the optional custom structure for that packet. Now, since it's a PingPacket, there's no optional custom structure, it'll only make sure to fill out the base packet. Then during the dispatching phase, it'll allocate the PingPacketHandler, it'll call the PingPacketHandler, ProcessPacketunction, and then it will free it. Now the ProcessPacket function, the PingPacketHandler will send back just a simple base packet containing the ping operation, which will indicate to the C2, that this port is infected with this Spectre Rootkit. So the best way I can explain the recursive nature of the packet dispatcher, is through another example called a XorPacketHandler. Now XorPacketHandler takes in an optional custom structure called XOR PACKET structure. And this XOR PACKET has the XorKey and a XorContent array. Now, what happens is that if the C2 wants to sent, request a certain operation, but it doesn't want to send the exact same packet as it did previously, it can take the packet, the base packet structure it wants to send over and actually put it into XorContent array. Then it'll generate a random byte key, which we'll use to perform a XOR operation on every byte of these XorContent array. Then it'll send this XOR PACKET to Spectre Rootkit. When the XorPacketHandler receives this packet, it'll use the XorKey in order to deobfuscate the XorContent array. And then it'll take that XorContent and call to dispatch function recursively to dispatch that brand new packet. Essentially this model of the Spectre Rootkit allows you to create infinite layers of encapsulation or layers of obfuscation allowing you to create variants, even if you're requesting the same operation by applying a layer of XorFuscation. So next let's talk about executing commands, a common future seen in a ton of Windows malware. And before we get into starting a process from a kernel driver, we need to understand how do you actually do that from user mode in order to see what functions we have to re-implement in the kernel. So in user mode, the first thing we needed to do is create an unnamed pipe. We'll use this pipe in order to obtain the output of our process. Then we'll set the startup info structure and specifically 3D standard out and standard air handles to our names pipe. And we'll say, here, we can also set window flags such as hide the window so that the victim doesn't see the process bringing created. Next, after the startup info structure is populated, we can forward this to create process, to actually create the command prompt process. And then we can wait for it to exit using WaitForSingleObject. And finally, we can read the output of the command by simply calling read file on the unnamed pipe we created before. Now, let's talk about how we would do this from kernel mode. Something important to remember is inside of the kernel driver, you don't have access to many of the same function you have access to in user mode, because the kernel32.dll doesn't exist in kernel mode. Instead you have to call the NT or ZW variants of functions. And kernel32.dLL inside of user mode also calls these functions, but it acts as a simplified layer. So, first we need to actually create our pipe. And the way we can do this is by replicating what the Kernel32.dll does itself. So what CreatPipe does is first it'll open the name's pipe device, if it hasn't done it before. Then it'll use the name pipe device and set the root directory object attribute to the handle, to the named pipe device. Then it'll call anti-create name type file to actually create that pipe. And here it'll only create a read handle to pass in as a handle strictly used for reading from the pipe. It'll then call NT open file on that read handle in order to open a right handle as well. So now that we've re implemented the CreatePipe function in kernel mode, we need to create the actual process. And we'll use the same function ZwCreateUserProcess that kernel base uses itself. Now we're gonna need to replicate the entire process of... Entire process that kernel based does itself. And so we'll need to first pass an attribute list. And the only attribute we need to really pass, is PsAttributeImageName attribute, which will specify the image file name for the new process. Next, we have to fill out an RTL USER PROCESS PARAMETER structure for the process. In this structure we need to set our window flags and the output handles to our pipes. Similar to what we did with the UserModeStartupInfo structure. But we also need to specify the current directory, the command line arguments, the process image path, and a default desktop name. From there, all it takes is a call to ZwCreateUserProcess in order to start the command prompt process. Once the process has exited, we can easily read the output of the command by calling ZwReadFile on to read handle we obtained for the pipe. Now let's talk about what you can do to hide your rootkit. So Introduction to Mini-Filters. So mini-filters driver allow you to attach to volumes and intercept certain file operations. This is performed by registering your mini-filter with the Filter Manager driver. So this reference from Microsoft documentation shows an example of user requesting file IO. First, the IO manager forwards the request to the file system. While this request is being forwarded, the Filter Manager driver intercepts this request and calls the registered mini-filters that I've registered with it in order to example, modify the request being performed before being sent to the file system itself. So mini-filters essentially allow you to edit file operations before they actually happen. And mini-filters can be used to mask the presence of our rootkit onto file system. For example, a mini-filter can redirect all access to a certain file to another file. We can use this to redirect access to our rootkit file to another legitimate driver file. Now, again, going back to picking a method, there's a few questions you wanna ask yourself. How many detection vectors am I exposed to? How usable is the method from both a stability and compatibility perspective? And how expensive would it be to detect the method? Well, the easiest way to abuse the functionality of a mini-filter is to follow the documented procedure and just become a mini-filter yourself. So here are the requirements for the function of a FtlRegisteFilter. You're gonna need to create a instances registry key under your service key. And under that instance's key, you need to create a instance name key, which can be whatever name you'd like. This is going to be the name for your filter instance. Then under the instances key, you need to add a default instance value, which is a string and is set to the instance name you created in step two. Then under your instance name key, you need to add the altitude and flags values and the altitude is pretty much what your... What the ordering is that mini-filters had called. The higher your altitude is the more first in line you are for your mini-filter to get called. So how many detections directors are you exposed to? You're gonna be exposing yourself to registry and memory artifacts. How usable is the method? Well, you don't really have concerns from a stability or usability perspective because this is just how legitimate drivers register as a mini-filter. But at the same time, it's pretty easy to detect this method because besides the registry artifacts, an antivirus could easily enumerate registered filters and their instances through documented functions, such as FltEnumerateFilters or FltEnumerateInstances. So another option is to just a hook in existing mini-filter. And there's a couple of routes you can take. First, you can just do a basic code hook on an existing mini-filters function. You can overwrite the FLT REGISTRATION structure, which is passed into FltRegisterFilter before the victim driver calls it. Or you can edit a existing mini-filter instance, through DKOM to replace a function that gets called with your own. Now, one of the easiest ways to intercept callbacks to an existing mini-filter is just again, a code hook. Now this can be as simple as a jump hook, but it comes with quite a few drawbacks. Similar to what we saw when we were talking about intersecting IRPS. You're gonna be exposing yourself to memory artifacts, and unless the function is exported, you're gonna need to find yourself, which can be difficult if the underlying image changes between operating system versions. Not all drivers are compatible with this method due to PatchGuard. And finally, this is also HVCI incompatible. Now, similar to what we saw with hooking IRPS, it's gonna be potentially inexpensive. Because of a antivirus knows you're hooking a specific function in a driver, it can easily check the bytes of that driver function to determine if there's any patching performed. Now, another way of obtaining access to a mini-filter, existing mini-filters through DEKOM. Now what we can do is you can enumerate filters in instances as I mentioned before, by calling the functions, FltEnumerateFilters and FltEnumarateInstances. Now the function that gets called for a certain filtered operation for a mini-filter is specified in an array called CallbackNodes, which is part of the FilterInstance structure, which you can find by using the previously mentioned functions. Now the CallBackNodes array index is directly associated with the operation that is being filtered. If you find a CallbackNode array entry for the operation you want to target, you can replace the pre-operation and post-operation pointers to your own function. Now, one note here is that you will also want to replace the FltRegistration structure, that is part of the FltFiltering structure. Now this is because the FltRegistration structure will contain the original function pointer. And what antivirus could then do is check to see if there's any discrepancies between the CallbackNodes array and the FLT registration structure part of the filter instance, sorry, the filter itself. Now from a detection vectors perspective, you're gonna be exposing yourself to primarily memory artifacts and from a stability perspective, the only concern you should have is that the FLT INSTANCE structure itself is undocumented. Now finding an FLT INSTANCE is easy through document functions, but the structure at the FLT INSTANCE structure itself may change across operating system versions. Now, how expensive would it be to detect the method? Well, it would be inexpensive. Begin all an antivirus would need to do is occasionally enumerate registered filters and their instances, and the CallbackNodes array. So let's say you wanna protect a certain file such as our rootkit file, what's an example of redirecting access to it? Well, first I'm assuming that you hooked a mini-filter's pre-create operation callback. Inside of that precreate operation callback, you can use FltGetFileNameInformation in order to get a normalized path to file being accessed. Then if the path contains a protective file, such as the path to our rootkit, you can replace the file path being accessed by calling IO replaced file logic name on that file object. Then you need to set the status being returned to StatusReparse and return a FltPreoperationComplete so that the file system will then redirect access to the legitimate driver file. So for example, you could use this to change a file access to your rootkit file to another legitimate driver that might be in the same directory, so that when a user of a program inspects it, it'll actually be looking at the contents of a completely different file. Okay. Wrapping up. I'd like to give, thanks to Alex Ionescu a longtime mentor who is very experienced with the Windows Internals. The ReactOS is an amazing reference for Windows Internals, especially for undocumented functions and structures. And thanks to Nemanja Mulasmajic and Vlad Ionescu for helping me review this presentation. Thanks for sticking around. You'll be able to ask questions in my DEFCON Q&A session, which will be later today. Make sure to follow me on Twitter and look at my blog and you can check out the Spectra Rootkit assuming everything went well at the URL below. Thanks for coming out to my talk again, have a great one.
Feedback