WEBVTT

00:00.000 --> 00:04.160
Hi there, my name is Bill and thanks for coming out to my talk.

00:05.120 --> 00:07.260
So, I'm 19 years old.

00:07.260 --> 00:11.340
I'm a rising sophomore at the Rochester Institute of Technology.

00:11.340 --> 00:13.440
I love Windows internals.

00:13.440 --> 00:20.920
I'm mostly self-taught with some guidance and I have a strong game hacking background, which is why I'm so interested in the Windows kernel.

00:21.440 --> 00:23.340
So, what is this talk about?

00:23.340 --> 00:42.020
Well, in this talk we'll be going over loading a rootkit, communicating with a rootkit, abusing legitimate network communications in order to communicate with our malware, an example rootkit I made and the design choices behind it, executing commands from kernel,

00:42.020 --> 00:46.000
and tricks to cover up the file system trace of your rootkit.

00:46.900 --> 00:52.120
So, when you hear me say rootkit, I'm going to be referring to kernel level rootkits for Windows.

00:52.560 --> 00:54.840
So, why would you want to use a rootkit?

00:54.840 --> 00:56.560
Well, there's a lot of reasons.

00:56.560 --> 00:59.980
Kernel drivers have significant access to the machine.

01:00.080 --> 01:05.280
Unlike in user mode, you pretty much have the same, you have the access to anything and everything.

01:06.220 --> 01:10.580
Kernel drivers have the same privilege level as a typical kernel antivirus.

01:10.600 --> 01:14.720
Generally speaking, you share the same access to the same resources.

01:15.300 --> 01:19.540
There are less mitigations and security solutions targeting kernel malware.

01:19.540 --> 01:23.820
If you can load kernel code, there's a lot you can do to cover up your tracks.

01:24.700 --> 01:29.420
Antivirus often have less visibility into the operations performed by kernel drivers.

01:29.860 --> 01:37.380
This is because a lot of antivirus rely on user mode hooks in order to gain visibility into potentially suspicious operations.

01:37.380 --> 01:46.420
In kernel, however, you can't directly hook the NTOS kernel functions due to patch guard, reducing the visibility antivirus has significantly.

01:46.980 --> 01:52.620
And finally, kernel drivers are treated with a significant amount of trust by antivirus.

01:52.620 --> 01:54.880
Let's take a look at an example of that.

01:55.820 --> 02:05.480
So whether you're running a consumer antivirus or corporate EDR, chances are whatever solution you run treats kernel drivers with a significant amount of trust.

02:05.500 --> 02:12.680
Here I have two examples from Malwarebytes and Carbon Black, specifically their pre-processed thread callback functions.

02:12.740 --> 02:17.780
These functions are called whenever a handle is created to a process or a thread.

02:17.780 --> 02:28.820
In the case of Malwarebytes, they check to see if the process ID is less than 8, and if the operation is for a kernel handle, then it will not continue processing that handle creation.

02:28.940 --> 02:39.180
In the case of Carbon Black, if your handle is for a kernel handle, or if the caller is not from user mode, it will not process that handle being created.

02:40.060 --> 02:43.040
Let's talk a little bit about loading a rootkit.

02:43.500 --> 02:46.680
So the first option you have is to abuse legitimate drivers.

02:47.020 --> 02:51.800
There are a lot of publicly available vulnerable drivers out there.

02:51.800 --> 02:56.800
And with some reversing knowledge, finding your own zero day in one of these drivers can be trivial.

02:57.580 --> 03:05.520
Examples of vulnerable drivers include Capcom's anti-cheat driver, Intel's NAL driver, and even Microsoft themselves.

03:05.780 --> 03:14.820
Now the reason I put vulnerable and zero day in quotes is because oftentimes these drivers require administrative privileges to communicate with them.

03:14.820 --> 03:26.040
Following Microsoft standards, Ring 3 administrator to Ring 0 is not a security boundary, so technically they're not vulnerabilities, but that doesn't mean we can't abuse them.

03:27.400 --> 03:31.160
Abusing legitimate drivers has quite a few benefits as well.

03:31.160 --> 03:34.540
You only need a few primitives to escalate privileges.

03:34.640 --> 03:37.460
Finding a vulnerable driver is relatively trivial.

03:37.460 --> 03:40.080
A good place to start is OEM drivers.

03:40.080 --> 03:43.820
And it's difficult to detect due to compatibility reasons.

03:43.940 --> 03:49.040
For example, let's say a driver had a vulnerable component that could potentially be exploited.

03:49.100 --> 04:06.080
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?

04:06.080 --> 04:08.060
It becomes a tricky problem.

04:08.580 --> 04:12.060
There are some drawbacks for this method as well though.

04:12.060 --> 04:16.060
The biggest issue I've had with this method is compatibility.

04:16.180 --> 04:23.900
Oftentimes the primitives you get might not be a lot and even then can have stability issues such as race conditions.

04:23.900 --> 04:31.480
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.

04:31.480 --> 04:33.280
That's probably the worst case scenario.

04:33.280 --> 04:37.960
It would be a pretty big indicator of compromise and you're probably going to get caught.

04:37.960 --> 04:40.660
This is why I generally stay away from this method.

04:42.640 --> 04:46.700
Now another method is to just buy a code signing certificate.

04:46.720 --> 04:54.720
This is completely valid for some red teams especially for targeted attacks and primarily because you're not going to have any stability issues.

04:54.720 --> 05:00.480
It's the normal way of getting a driver signed is just to buy your own certificate under your company's name.

05:00.860 --> 05:05.300
But it will reveal your identity and also it can be blacklisted.

05:05.300 --> 05:09.020
Now this blacklisting doesn't happen so much nowadays.

05:09.020 --> 05:14.080
This is something that I know is being worked on by antivirus vendors and even Microsoft themselves.

05:14.500 --> 05:20.040
It's not widely spread yet so you're probably not going to see too much blacklisting happening nowadays.

05:21.300 --> 05:24.780
Another option is just to use someone else's certificate.

05:24.780 --> 05:29.380
You'd be surprised at the number of publicly available leaked certificates online.

05:29.380 --> 05:33.320
A good place to start if you're looking for one is game hacking forums.

05:33.580 --> 05:40.900
Now leaked certificates have almost all of the benefits of just buying your own certificate without the anonymization.

05:41.400 --> 05:46.060
But the leaked certificate you use can be blacklisted in the future.

05:46.140 --> 05:49.360
It goes back to the same point about buying your own certificate.

05:49.560 --> 06:05.820
And if the certificate was issued after July 29th 2015, you can't use it on secure boot machines running Windows 10 1607 or higher unless it is an EV certificate, which is probably not going to happen.

06:07.120 --> 06:13.500
In most cases, Windows doesn't even care if the certificate appears to be expired or even revoked.

06:13.560 --> 06:22.460
This is because what you see in the digital signature section of a driver is not what the kernel checks when determining whether or not to load a driver.

06:22.500 --> 06:32.000
So this view you see here when it says a certificate was explicitly revoked, that's what WinVerify Truster is returning, not necessarily what the kernel cares about.

06:32.440 --> 06:38.600
So if you do come across a certificate that's expired or revoked, chances are you can still use it for signing a driver.

06:40.000 --> 06:45.800
Now if you don't want to use one of the publicly available certificates out there, you can also try to find your own.

06:45.800 --> 06:51.300
One method I found interesting was to use OpenS3Buckets to search for private keys.

06:51.360 --> 07:05.720
I used a site called GreyhatWarfare which searches these OpenS3Buckets 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.

07:06.600 --> 07:11.540
And the best part about this method is that it's undetected by the bulk of antivirus.

07:11.540 --> 07:27.840
Now I'd 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.

07:28.920 --> 07:31.660
Let's talk about communicating with a rootkit.

07:31.780 --> 07:37.100
A tried and true method of communicating with your malware is to just call home to a C2.

07:37.360 --> 07:56.820
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 advanced network inspection that can try to catch some of these methods that attempt to blend in with the noise.

07:59.180 --> 08:04.640
Some malware takes the route that the C2 connects to the victim directly to control it.

08:04.640 --> 08:17.380
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 exclusively.

08:18.760 --> 08:24.900
A more advanced method I've seen is to hook into an application's network communications directly.

08:24.900 --> 08:39.960
Now this is 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 going to have different ports exposed, different services running, and so if that one port or service isn't exposed,

08:39.960 --> 08:43.560
you can't use that line of communication, which isn't very reliable.

08:44.960 --> 09:04.460
So what I wanted when choosing a communication method was limited detection vectors, flexibility for a variety of environments, while making the assumptions that some services will be exposed, which is especially true in corporate environments that have active domain services running.

09:04.460 --> 09:13.360
But inbound and outbound access may be monitored, so it's going to have to be a method that has a low detection vector opportunity.

09:13.640 --> 09:20.500
Now application-specific hooking was perfect for my needs, except for the flexibility.

09:20.560 --> 09:27.460
Is there any way we could change application-specific hooking to where it isn't dependent on any single application?

09:27.460 --> 09:30.580
Well, it turns out there might be a way.

09:30.580 --> 09:38.460
So what if instead of hooking one application directly, we hooked network communication similar to tools such as Wireshark?

09:38.460 --> 09:45.340
So this means that we would hook every packet that reaches any port on the system, and we're able to inspect it.

09:45.540 --> 09:52.000
And then what we did is on our C2, we created a custom packet that had a magic constant value.

09:52.000 --> 09:57.340
And right after that magic constant, there was information we want to pass to our malware.

09:57.380 --> 10:16.440
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.

10:16.480 --> 10:25.280
So using this method, we could communicate from our C2 to our malware by abusing any legitimate port on the victim machine.

10:25.440 --> 10:30.810
Let's talk about how we would actually do this, and specifically hooking to user mode network stack.

10:30.810 --> 10:36.610
So a significant amount of services on Windows rely in user mode.

10:36.610 --> 10:39.970
And how can we globally intercept this traffic?

10:39.970 --> 10:49.030
Well, networking relating to Winsock is handled by afd.sys, otherwise known as the ancillary function driver for Winsock.

10:49.030 --> 10:56.860
Reversing a few functions inside of mswsock.dll revealed that a bulk of the communication was performed through ioctals.

10:56.860 --> 11:02.780
If we could somehow intercept these ioctals, we could snoop in on the data being received.

11:03.200 --> 11:06.340
So how do ERPs know where to go?

11:06.340 --> 11:11.120
So how does a kernel determine what function to call of what driver?

11:11.140 --> 11:19.340
Well, first it'll obtain the device object associated with the file object by calling ioget related device object.

11:19.340 --> 11:25.760
Now for our purposes, this will just be retrieving the device object member of the file object structure.

11:25.760 --> 11:35.920
If the driver associated with the device supports FastIO, it'll dispatch the request using the FastIO dispatch table, part of the driver object structure.

11:35.980 --> 11:45.740
If the driver does not support FastIO, then it'll allocate and fill out an ERP and forward that ERP to the driver by calling iocalldriver.

11:46.940 --> 12:00.520
Now there's a few standard methods of intercepting ERPs, and the first method is to replace the major functions table or array that is part of the driver object structure.

12:00.520 --> 12:14.220
Now this major function array contains pointers to dispatch functions, and the index for this array directly corresponds to the major function code, which is relevant for that dispatch function.

12:14.220 --> 12:29.220
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 ERPs for that major function to our driver instead.

12:29.380 --> 12:34.380
Another option is just to perform a code hook directly on the dispatch function itself.

12:35.200 --> 12:39.260
So when picking a method , there's a few questions you want to ask yourself.

12:39.260 --> 12:41.960
How many detection vectors am I exposed to?

12:41.960 --> 12:47.600
How usable is the method, both from a compatibility and stability perspective?

12:47.660 --> 12:51.580
And how expensive would it be to detect the method?

12:51.640 --> 12:57.480
Well, for hooking a driver object in memory, you're going to be exposing yourself to memory artifacts.

12:57.620 --> 13:04.360
And from a usability perspective, you're going to be quite stable, mostly because driver objects are well documented.

13:05.100 --> 13:18.200
And from a detectionist perspective, though, it wouldn't be crazy difficult for antivirus to detect because there's only going to be a handful of driver objects out there because there's only a handful of drivers loaded at once.

13:18.320 --> 13:29.440
And all an antivirus would need to do is enumerate these driver objects and check the major functions array for signs that the dispatch function is outside of the driver's bounds.

13:31.260 --> 13:37.580
For hooking a driver's dispatch function directly with a code hook, you're going to be exposing yourself to memory artifacts.

13:38.000 --> 13:41.600
But unless the function is exported, you're going to need to find it yourself.

13:41.840 --> 13:53.020
And there's some a couple of ways to do this, but it's something to especially consider if the underlying driver file might change between Windows operating system versions, which aft.sys does.

13:53.080 --> 13:57.700
And also, not all drivers are going to be compatible with this method due to patch guard.

13:57.700 --> 14:00.560
And this is also HVCI incompatible.

14:00.560 --> 14:03.140
Now, how expensive would it be to detect?

14:03.140 --> 14:05.000
Well, that can vary.

14:05.000 --> 14:08.840
So there's quite simple ways of detecting hooking that are varying inexpensive.

14:08.900 --> 14:12.360
There's other methods that can get pretty expensive.

14:12.520 --> 14:22.480
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 to check for tampering.

14:22.480 --> 14:24.080
That's pretty inexpensive.

14:24.320 --> 14:42.380
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 disk and what is on the actual what's in memory.

14:44.460 --> 14:52.700
Now, I wanted a method that was undocumented, stable, yet relatively expensive to detect.

14:52.700 --> 14:59.320
So what if instead of hooking the original driver object, we hook the file object structure instead?

14:59.400 --> 15:11.440
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 IO get related device object.

15:11.440 --> 15:17.000
And for our purposes, this is the device object member of the file object structure.

15:17.020 --> 15:23.640
Now, what is stopping us from overriding this device object pointer inside of the file object with our own device?

15:23.640 --> 15:26.100
Well, it turns out absolutely nothing.

15:26.640 --> 15:37.920
So what we can do is we can create our own driver and device objects, patch our copy of the driver object using a common method such as replacing the major function table.

15:37.920 --> 15:43.860
And then we can replace the device object pointer inside of the file object with our own device.

15:43.960 --> 15:46.340
So let's talk about how we would do this.

15:46.340 --> 15:52.580
Well, first, we need to find file objects that are to the device AFD device object.

15:52.580 --> 15:55.200
But how can we actually find these objects?

15:55.280 --> 16:05.360
Well, the Windows NTOS kernel exposes this great function called ZW query system information, which allows you to query a lot of different information about the system.

16:05.360 --> 16:20.960
And one of the classes you can query for is called system handle information, 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.

16:21.180 --> 16:33.300
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.

16:33.420 --> 16:38.180
So then we can see, okay, this file object is associated with the AFD device.

16:38.180 --> 16:45.060
Now, before we can overwrite the device object member of the file object, we need to do some preparation first.

16:45.060 --> 16:48.100
And specifically, we need to create our fake objects.

16:48.100 --> 16:53.400
Fortunately, the kernel exports the function we can use to create our own objects.

16:53.620 --> 17:05.520
We can call obcreate object passing in an IO driver object type and IO device object type respectively, and then copy over the existing object data using a function like mem copy.

17:06.280 --> 17:12.040
Now with our fake objects created, we're almost ready to set the device object of the file object.

17:12.040 --> 17:15.320
But first, we need to hook our driver object.

17:15.320 --> 17:19.640
And the way we can do this is by using the standard major function hook method.

17:19.640 --> 17:26.560
Except remember, we're performing this on our own copy of the driver object, not what a normal antivirus could retrieve.

17:26.560 --> 17:28.760
So it's actually relatively safe.

17:30.180 --> 17:39.180
Now to prevent race conditions between our hook function and the device object member, we need to replace it using an interlocked exchange.

17:40.000 --> 17:53.500
Now, one thing to remember here is that if when you replace the file objects device object, you can have a normal the Windows kernel call that file object at any time.

17:53.500 --> 18:04.960
So you're going to want to use an interlocked exchange in order to make sure the device object you use in your hook is set at the same time the device object is replaced inside of the file object.

18:05.620 --> 18:11.000
So now that we've actually hooked the file object, there's not much work left.

18:11.020 --> 18:17.780
Inside of our dispatch hook, we need to check to see if the major function code being called is hooked.

18:17.780 --> 18:24.780
And if so, we need to pass the original dispatch function, the original device object, and the ERP to our hook function.

18:24.860 --> 18:36.160
Now the trick here is also if we receive the major function code cleanup, we need to replace the device object member of the file object with the original device object.

18:36.160 --> 18:39.000
This is to prevent issues during teardown.

18:40.340 --> 18:44.980
So from a detection perspective, we're going to be exposing ourselves to memory artifacts.

18:45.260 --> 18:53.780
And from a usability perspective, most of the functions we're going to be using for this is semi-documented and unlikely to change significantly.

18:54.080 --> 18:57.500
And finally, how expensive is it to detect the method?

18:57.500 --> 19:03.300
It's going to be pretty expensive because an antivirus would need to replicate our hooking procedure.

19:03.420 --> 19:12.300
And so they would have to enumerate every file object to detect if the device object has been tampered with, which is also add some complications.

19:12.740 --> 19:16.100
So now let's talk about how to spec Darukin.

19:16.100 --> 19:20.180
Darukin I wrote abuses the user mode network stack.

19:20.420 --> 19:26.040
So now using our file object hook, we can intercept ERPs to the AFD driver.

19:26.040 --> 19:32.640
This will allow us to inspect all user mode traffic and send and receive our own data over any socket.

19:32.880 --> 19:38.840
To review our existing plan, we're going to hook user mode communication similar to tools such as Wireshark.

19:38.840 --> 19:48.160
And then we're going to use our C2 to place a special indicator inside of a packet we sent to any port on the victim machine.

19:48.200 --> 19:56.540
And this way, our C2 can then communicate with our malware through any port, any service that is exposed.

19:56.860 --> 20:05.520
Now, how can we actually retrieve the contents of the packets that are received when the WCA receive function is called?

20:05.520 --> 20:11.260
Well, when you call that function, it's going to send an IOCTL called IOCTL AFD receive.

20:11.260 --> 20:16.060
And specifically, it'll pass the AFD receive info structure in the input buffer.

20:16.120 --> 20:25.380
Now this buffer, now this structure contains some flags, but what we really care about is the WCA buffers, which is essentially an array of arrays.

20:25.520 --> 20:33.400
And these buffers actually contain the bytes that are received from the packet, which we can use then to look into.

20:33.740 --> 20:37.780
So let's talk a little bit more about how the Spectre Rookit was designed.

20:37.860 --> 20:47.760
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.

20:47.760 --> 20:55.800
And after any prepended data you'd like, you can place a magic constant, which will act as a reference point for the malware.

20:56.040 --> 21:09.320
And after the magic constant, you can add a 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.

21:09.460 --> 21:13.920
After the base packet structure, you have an optional custom structure.

21:13.960 --> 21:18.680
Now this, again, custom structure is optional, so it might not be there in the first place.

21:18.680 --> 21:22.720
But this will vary depending on the operation being requested.

21:22.960 --> 21:27.740
And after this optional custom structure, you can have any data you'd like.

21:27.800 --> 21:35.980
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.

21:35.980 --> 21:45.300
And then you can send a packet that has any prepended data or any appended data with the magic content inside, malicious packet inside.

21:45.340 --> 21:48.960
And then you can, after that packet, send any packet you'd like.

21:49.080 --> 21:57.280
So the key, what I was trying to get at here, is that the Specter Rootkit design allows for quite a bit of flexibility in how you structure your packets.

21:58.040 --> 22:01.680
So what happens when the Specter Rootkit receives a packet?

22:01.680 --> 22:03.220
Well, it's pretty simple.

22:03.220 --> 22:06.420
First, it'll search the buffers for the magic constant.

22:06.440 --> 22:11.220
If the buffer contains the magic, it'll go ahead into the processing stage.

22:11.320 --> 22:16.540
And if the buffer does not contain the magic, then it'll just ignore the packet.

22:16.540 --> 22:21.180
Now, before dispatching the packet, there's a few steps we need to take.

22:21.420 --> 22:30.080
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.

22:30.080 --> 22:37.880
In any case, if we do not have enough bytes for either the base packet or the optional custom structure, we'll receive the rest.

22:37.940 --> 22:40.160
And finally, we'll dispatch the packet.

22:42.140 --> 22:49.300
Now, before we go any further, let's talk about the concept of packet handlers inside of the Specter Rootkit.

22:49.340 --> 22:56.960
So the Specter Rootkit contains this general packet handler class that exposes a virtual process packet function.

22:57.040 --> 23:05.140
Now, this base packet handler class has a default constructor that receives a pointer to the current packet dispatcher instance.

23:05.140 --> 23:10.040
And the process packet function receives a pointer to the packet itself.

23:10.040 --> 23:12.230
We'll talk more about the dispatcher later.

23:13.320 --> 23:20.440
Now, an example of a packet handler included with the Specter Rootkit is the ping packet handler.

23:20.440 --> 23:22.920
This handler is quite simple.

23:23.160 --> 23:27.780
It is just used to determine if a port slash machine is infected.

23:27.780 --> 23:32.540
All it is, is a bare bone magic and then base packet structure.

23:32.540 --> 23:36.840
And this base packet structures has its type or operation set to ping.

23:36.840 --> 23:48.120
And when the Specter Rootkit, or specifically the ping packet handler, receives a packet, all it will do is send back an empty base packet with the operation set to ping.

23:49.540 --> 23:59.580
Now, once a packet is completely populated, the packet dispatcher will allocate a packet handler depending on the requested operation.

23:59.580 --> 24:03.840
Then it'll call that packet handler's process packet function.

24:03.840 --> 24:07.080
And finally, it'll free the packet handler.

24:07.080 --> 24:21.520
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.

24:21.760 --> 24:35.060
Now, before we get into how this dispatching works and recursively dispatching, to give you an example of how the flow of a ping packet goes, is first the Specter Rootkit receives that packet.

24:35.060 --> 24:37.800
It'll recognize that there's a magic constant there.

24:37.800 --> 24:43.640
It'll then fill out the base packet and the optional custom structure for that packet.

24:43.640 --> 24:49.340
Now, since it's a ping packet, there's no optional custom structure, and it'll only make sure to fill out the base packet.

24:49.340 --> 24:53.580
Then, during the dispatching phase, it'll allocate the ping packet handler.

24:53.580 --> 24:59.160
It'll call the ping packet handler process packet function, and then it'll free it.

24:59.160 --> 25:13.320
Now, the process packet function, the ping packet handler, will send back just a simple base packet containing the ping operation, which will indicate to the C2 that this port is infected with the Specter Rootkit.

25:13.860 --> 25:21.860
So, the best way I can explain the recursive nature of the packet dispatcher is through another example called the XOR packet handler.

25:22.020 --> 25:33.020
Now, the XOR packet handler takes in an optional custom structure called the XOR packet structure, and this XOR packet structure has the XOR key and a XOR content array.

25:33.200 --> 25:50.280
Now, what happens is that if the C2 wants to 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 the XOR content array.

25:50.340 --> 25:58.680
Then, it'll generate a random byte key, which it'll use to perform a XOR operation on every byte of the XOR content array.

25:58.720 --> 26:02.420
Then, it'll send this XOR packet to the Specter Rootkit.

26:03.900 --> 26:13.300
When the XOR packet handler receives this packet, it'll use the XOR key in order to de-obfuscate the XOR content array.

26:13.300 --> 26:21.280
And then, it'll take that XOR content and call the dispatch function recursively to dispatch that brand new packet.

26:21.380 --> 26:37.360
Essentially, this model of the Specter 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 XOR obfuscation.

26:39.660 --> 26:46.040
So next, let's talk about executing commands, a common feature seen in a ton of Windows malware.

26:46.680 --> 26:57.940
Now, 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.

26:58.900 --> 27:04.340
So, in user mode, the first thing we need to do is create an unnamed pipe.

27:04.480 --> 27:09.600
We'll use this pipe in order to obtain the output of our process.

27:09.660 --> 27:17.480
Then, we'll set the startup info structure, and specifically the standard out and standard error handles, to our names pipe.

27:17.580 --> 27:25.780
And we'll set here, we can also set window flags, such as hide to window, so that the victim doesn't see the process being created.

27:26.020 --> 27:34.360
Next, after the startup info structure is populated, we can forward this to create process, to actually create the command prompt process.

27:34.360 --> 27:37.900
And then we can wait for it to exit using wait for single object.

27:37.900 --> 27:45.520
And finally, we can read the output of the command by simply calling read file on the unnamed pipe we created before.

27:46.100 --> 27:49.680
Now, let's talk about how we would do this from kernel mode.

27:49.680 --> 28:00.460
Something important to remember is inside of a kernel driver, you don't have access to many of the same function you have access to in user mode, because the kernel 32 DLL doesn't exist in kernel mode.

28:00.460 --> 28:05.860
Instead, you have to call the NT or ZW variants of functions.

28:06.100 --> 28:12.960
And kernel 32 DLL inside of user mode also calls these functions, but it acts as a simplified layer.

28:13.480 --> 28:16.960
So first, we need to actually create our pipe.

28:16.960 --> 28:21.700
And the way we can do this is by replicating what the kernel 32 DLL does itself.

28:21.700 --> 28:42.040
So what create pipe does is first, it'll open the names pipe device, if it hasn't done it before, then it'll use this names pipe device and set the root directory object attribute to the handle to the names pipe device, then it'll call NT create names by file to actually create that pipe.

28:42.200 --> 28:49.160
And here, it'll only create a read handle to pass in as a handle strictly used for reading from the pipe.

28:49.160 --> 28:55.560
It'll then call NT open file on that read handle in order to open a write handle as well.

28:57.080 --> 29:04.460
So now that we've re implemented the create pipe function in kernel mode, we need to create the actual process.

29:04.460 --> 29:10.500
And we'll use the same function ZW create user process that kernel base uses itself.

29:10.900 --> 29:17.940
Now, we're going to need to replicate the entire process of entire process that kernel base does itself.

29:17.940 --> 29:21.160
And so we'll need to first pass in an attribute list.

29:21.160 --> 29:30.060
And the only attribute we need really pass is the PS attribute image name attribute, which will specify the image file name for the new process.

29:31.440 --> 29:37.000
Next, we have to fill out an RTL process parameters structure for the process.

29:37.100 --> 29:45.560
In this structure, we need to set our window flags and the output handles to our pipes, similar to what we did with the user mode startup info structure.

29:45.820 --> 29:54.940
But we also need to specify the current directory, the command line arguments, the process image path, and the default desktop name.

29:55.200 --> 30:03.000
From there, all it takes is a call to ZW create user process in order to start the command prompt process.

30:03.080 --> 30:11.800
Once the process has exited, we can easily read the output of the command by calling ZW read file on the read handle we obtained for the pipe.

30:12.840 --> 30:16.400
Now let's talk about what you can do to hide your rootkit.

30:16.980 --> 30:19.120
So introduction to many filters.

30:19.120 --> 30:25.000
So many filters driver allow you to attach to volumes and intercept certain file operations.

30:25.000 --> 30:31.080
This is performed by registering your mini filter with the filter manager driver.

30:31.080 --> 30:37.200
So this reference from Microsoft documentation shows an example of user requesting file IO.

30:37.200 --> 30:41.480
First, the IO manager forwards the request to the file system.

30:41.600 --> 31:00.400
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, for example, modify the request being performed before being sent to the file system itself.

31:00.400 --> 31:06.320
So many filters essentially allow you to edit file operations before they actually happen.

31:07.280 --> 31:13.040
And many filters can be used to mask the presence of our rootkit on the file system.

31:13.040 --> 31:18.800
For example, a mini filter can redirect all access to a certain file to another file.

31:18.860 --> 31:24.240
We can use this to redirect access to our rootkit file to another legitimate driver file.

31:24.580 --> 31:29.260
Now again, going back to picking a method, there's a few questions you want to ask yourself.

31:29.300 --> 31:31.940
How many detection vectors am I exposed to?

31:31.940 --> 31:36.420
How usable is the method from both a stability and compatibility perspective?

31:36.500 --> 31:39.500
And how expensive would it be to detect the method?

31:39.600 --> 31:48.020
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.

31:48.040 --> 31:51.660
So here are the requirements for the function fltRegisterFilter.

31:51.820 --> 31:57.220
You're going to need to create an instances registry key under your service key.

31:57.240 --> 32:03.320
And under that instances key, you need to create an instance name key, which can be whatever name you'd like.

32:03.320 --> 32:06.580
This is going to be the name for your filter instance.

32:06.580 --> 32:17.800
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 2.

32:17.800 --> 32:23.740
Then under your instance name key, you need to add the altitude and flags values.

32:23.940 --> 32:30.300
And the altitude is pretty much what the ordering is that many filters get called.

32:30.300 --> 32:38.620
The higher your altitude is, the first in line you are for your mini filter to get called.

32:39.900 --> 32:42.740
So how many detection vectors are you exposed to?

32:42.740 --> 32:46.100
You're going to be exposing yourself to registry and memory artifacts.

32:46.300 --> 32:47.880
How usable is the method?

32:47.880 --> 32:56.020
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.

32:56.240 --> 33:14.620
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 FLT enumerate filters or FLT enumerate instances.

33:15.700 --> 33:19.980
So another option is to just hook an existing mini filter.

33:19.980 --> 33:22.000
And there's a couple of routes you can take.

33:22.000 --> 33:27.360
First, you can just do a basic code hook on an existing mini filters function.

33:27.360 --> 33:36.380
You can overwrite the FLT registration structure, which is passed into FLT register filter before the victim driver calls it.

33:36.380 --> 33:45.620
Or you can edit a existing mini filter instance through DCOM to replace the function that gets called with your own .

33:46.060 --> 33:53.620
Now one of the easiest ways to intercept callbacks to an existing mini filter is just again a code hook.

33:53.620 --> 34:01.980
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 intercepting ERPs.

34:02.560 --> 34:14.040
You're going to be exposing yourself to memory artifacts, and unless the function is exported, you're going to need to find yourself, which can be difficult if the underlying image changes between operating system versions.

34:14.280 --> 34:18.320
Not all drivers are compatible with this method due to patch card.

34:18.380 --> 34:21.660
And finally, this is also HVCI incompatible.

34:21.940 --> 34:38.120
Now similar to what we saw with hooking ERPs, it's going to be potentially inexpensive because if 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.

34:40.100 --> 34:55.160
Now another way of obtaining access to a mini filter, existing mini filters, Now what you can do is you can enumerate filters and instances, as I mentioned before, by calling the functions fltenumeratefilters and fltenumerateinstances.

34:55.160 --> 35:10.520
Now the function that gets called for a certain filtered operation, for a mini filter, is specified in an array called callback nodes, which is part of the filter instance structure, which you can find by using the previously mentioned functions.

35:11.080 --> 35:17.720
Now the callback nodes array index is directly associated with the operation that is being filtered.

35:17.720 --> 35:28.120
If you find a callback node array entry for the operation you want to target, you can replace the pre-operation and post-operation pointers to your own function.

35:28.120 --> 35:37.240
Now one note here is that you will also want to replace the FLT registration structure that is part of the FLT filter structure.

35:37.380 --> 35:43.720
Now this is because the FLT registration structure will contain the original function pointer.

35:44.080 --> 35:55.560
And what an antivirus could then do is check to see if there's any discrepancies between the callback nodes array and the FLT registration structure, part of the filter instance, sorry, the filter itself.

35:56.540 --> 36:02.880
Now from a detection vectors perspective, you're going to be exposing yourself to primarily memory artifacts.

36:02.880 --> 36:11.260
And from a stability perspective, the only concern you should have is that the FLT instance structure itself is undocumented.

36:11.260 --> 36:20.940
Now finding an FLT instance is easy through documented functions, but the structure at the FLT instance structure itself may change across operating system versions.

36:21.100 --> 36:23.380
Now how expensive would it be to detect the method?

36:23.380 --> 36:32.960
Well it would be inexpensive because all an antivirus would need to do is occasionally enumerate registered filters and their instances in the callback nodes array.

36:33.680 --> 36:37.800
So let's say you want to protect a certain file, such as our rootkit file.

36:37.800 --> 36:40.680
What's an example of redirecting access to it?

36:40.680 --> 36:46.740
Well first, I'm assuming that you hooked a minifilter's pre-create operation callback.

36:46.740 --> 36:57.160
Inside of that pre-create operation callback, you can use FLT get file name information in order to get a normalized path to the file being accessed.

36:57.160 --> 37:10.320
Then if the path contains a protected file, such as the path to our rootkit, you can replace the file path being accessed by calling io replace file object name on that file object.

37:10.320 --> 37:26.620
Then you need to set the status being returned to status reparse and return FLT pre-operation complete so that the file system will then redirect access to the legitimate driver file.

37:26.620 --> 37:41.680
So for example, you could use this to change file access to your rootkit file to another legitimate driver that might be in the same directory so that when a user or program inspects it, it'll actually be looking at the contents of a completely different file.

37:43.020 --> 37:44.860
Okay, wrapping up.

37:44.860 --> 37:51.680
I'd like to give thanks to Alex Ionescu, a longtime mentor who's very experienced with Windows internals.

37:51.940 --> 37:59.800
The React OS is an amazing reference for Windows internals, especially for undocumented functions, functions, and structures.

37:59.800 --> 38:05.560
And thanks to Nemanja Mlazmajic and Vlad Ionescu for helping me review this presentation.

38:06.040 --> 38:07.880
Thanks for sticking around.

38:07.880 --> 38:13.920
You'll be able to ask questions in my DEF CON Q&A session, which will be later today.

38:14.040 --> 38:17.360
Make sure to follow me on Twitter and look at my blog.

38:17.360 --> 38:23.080
And you can check out the Spectre rootkit, assuming everything went well, at the URL below.

38:24.220 --> 38:26.040
Thanks for coming out to my talk again.

38:26.040 --> 38:26.920
Have a great one.

