1 00:00:00,000 --> 00:00:04,160 Hi there, my name is Bill and thanks for coming out to my talk. 2 00:00:05,120 --> 00:00:07,260 So, I'm 19 years old. 3 00:00:07,260 --> 00:00:11,340 I'm a rising sophomore at the Rochester Institute of Technology. 4 00:00:11,340 --> 00:00:13,440 I love Windows internals. 5 00:00:13,440 --> 00: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. 6 00:00:21,440 --> 00:00:23,340 So, what is this talk about? 7 00:00:23,340 --> 00: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, 8 00:00:42,020 --> 00:00:46,000 and tricks to cover up the file system trace of your rootkit. 9 00:00:46,900 --> 00:00:52,120 So, when you hear me say rootkit, I'm going to be referring to kernel level rootkits for Windows. 10 00:00:52,560 --> 00:00:54,840 So, why would you want to use a rootkit? 11 00:00:54,840 --> 00:00:56,560 Well, there's a lot of reasons. 12 00:00:56,560 --> 00:00:59,980 Kernel drivers have significant access to the machine. 13 00:01:00,080 --> 00:01:05,280 Unlike in user mode, you pretty much have the same, you have the access to anything and everything. 14 00:01:06,220 --> 00:01:10,580 Kernel drivers have the same privilege level as a typical kernel antivirus. 15 00:01:10,600 --> 00:01:14,720 Generally speaking, you share the same access to the same resources. 16 00:01:15,300 --> 00:01:19,540 There are less mitigations and security solutions targeting kernel malware. 17 00:01:19,540 --> 00:01:23,820 If you can load kernel code, there's a lot you can do to cover up your tracks. 18 00:01:24,700 --> 00:01:29,420 Antivirus often have less visibility into the operations performed by kernel drivers. 19 00:01:29,860 --> 00:01:37,380 This is because a lot of antivirus rely on user mode hooks in order to gain visibility into potentially suspicious operations. 20 00:01:37,380 --> 00: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. 21 00:01:46,980 --> 00:01:52,620 And finally, kernel drivers are treated with a significant amount of trust by antivirus. 22 00:01:52,620 --> 00:01:54,880 Let's take a look at an example of that. 23 00:01:55,820 --> 00: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. 24 00:02:05,500 --> 00:02:12,680 Here I have two examples from Malwarebytes and Carbon Black, specifically their pre-processed thread callback functions. 25 00:02:12,740 --> 00:02:17,780 These functions are called whenever a handle is created to a process or a thread. 26 00:02:17,780 --> 00: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. 27 00:02:28,940 --> 00: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. 28 00:02:40,060 --> 00:02:43,040 Let's talk a little bit about loading a rootkit. 29 00:02:43,500 --> 00:02:46,680 So the first option you have is to abuse legitimate drivers. 30 00:02:47,020 --> 00:02:51,800 There are a lot of publicly available vulnerable drivers out there. 31 00:02:51,800 --> 00:02:56,800 And with some reversing knowledge, finding your own zero day in one of these drivers can be trivial. 32 00:02:57,580 --> 00:03:05,520 Examples of vulnerable drivers include Capcom's anti-cheat driver, Intel's NAL driver, and even Microsoft themselves. 33 00:03:05,780 --> 00: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. 34 00:03:14,820 --> 00: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. 35 00:03:27,400 --> 00:03:31,160 Abusing legitimate drivers has quite a few benefits as well. 36 00:03:31,160 --> 00:03:34,540 You only need a few primitives to escalate privileges. 37 00:03:34,640 --> 00:03:37,460 Finding a vulnerable driver is relatively trivial. 38 00:03:37,460 --> 00:03:40,080 A good place to start is OEM drivers. 39 00:03:40,080 --> 00:03:43,820 And it's difficult to detect due to compatibility reasons. 40 00:03:43,940 --> 00:03:49,040 For example, let's say a driver had a vulnerable component that could potentially be exploited. 41 00:03:49,100 --> 00: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? 42 00:04:06,080 --> 00:04:08,060 It becomes a tricky problem. 43 00:04:08,580 --> 00:04:12,060 There are some drawbacks for this method as well though. 44 00:04:12,060 --> 00:04:16,060 The biggest issue I've had with this method is compatibility. 45 00:04:16,180 --> 00:04:23,900 Oftentimes the primitives you get might not be a lot and even then can have stability issues such as race conditions. 46 00:04:23,900 --> 00: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. 47 00:04:31,480 --> 00:04:33,280 That's probably the worst case scenario. 48 00:04:33,280 --> 00:04:37,960 It would be a pretty big indicator of compromise and you're probably going to get caught. 49 00:04:37,960 --> 00:04:40,660 This is why I generally stay away from this method. 50 00:04:42,640 --> 00:04:46,700 Now another method is to just buy a code signing certificate. 51 00:04:46,720 --> 00: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. 52 00:04:54,720 --> 00: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. 53 00:05:00,860 --> 00:05:05,300 But it will reveal your identity and also it can be blacklisted. 54 00:05:05,300 --> 00:05:09,020 Now this blacklisting doesn't happen so much nowadays. 55 00:05:09,020 --> 00:05:14,080 This is something that I know is being worked on by antivirus vendors and even Microsoft themselves. 56 00:05:14,500 --> 00:05:20,040 It's not widely spread yet so you're probably not going to see too much blacklisting happening nowadays. 57 00:05:21,300 --> 00:05:24,780 Another option is just to use someone else's certificate. 58 00:05:24,780 --> 00:05:29,380 You'd be surprised at the number of publicly available leaked certificates online. 59 00:05:29,380 --> 00:05:33,320 A good place to start if you're looking for one is game hacking forums. 60 00:05:33,580 --> 00:05:40,900 Now leaked certificates have almost all of the benefits of just buying your own certificate without the anonymization. 61 00:05:41,400 --> 00:05:46,060 But the leaked certificate you use can be blacklisted in the future. 62 00:05:46,140 --> 00:05:49,360 It goes back to the same point about buying your own certificate. 63 00:05:49,560 --> 00: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. 64 00:06:07,120 --> 00:06:13,500 In most cases, Windows doesn't even care if the certificate appears to be expired or even revoked. 65 00:06:13,560 --> 00: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. 66 00:06:22,500 --> 00: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. 67 00:06:32,440 --> 00: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. 68 00:06:40,000 --> 00: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. 69 00:06:45,800 --> 00:06:51,300 One method I found interesting was to use OpenS3Buckets to search for private keys. 70 00:06:51,360 --> 00: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. 71 00:07:06,600 --> 00:07:11,540 And the best part about this method is that it's undetected by the bulk of antivirus. 72 00:07:11,540 --> 00: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. 73 00:07:28,920 --> 00:07:31,660 Let's talk about communicating with a rootkit. 74 00:07:31,780 --> 00:07:37,100 A tried and true method of communicating with your malware is to just call home to a C2. 75 00:07:37,360 --> 00: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. 76 00:07:59,180 --> 00:08:04,640 Some malware takes the route that the C2 connects to the victim directly to control it. 77 00:08:04,640 --> 00: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. 78 00:08:18,760 --> 00:08:24,900 A more advanced method I've seen is to hook into an application's network communications directly. 79 00:08:24,900 --> 00: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, 80 00:08:39,960 --> 00:08:43,560 you can't use that line of communication, which isn't very reliable. 81 00:08:44,960 --> 00: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. 82 00:09:04,460 --> 00: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. 83 00:09:13,640 --> 00:09:20,500 Now application-specific hooking was perfect for my needs, except for the flexibility. 84 00:09:20,560 --> 00:09:27,460 Is there any way we could change application-specific hooking to where it isn't dependent on any single application? 85 00:09:27,460 --> 00:09:30,580 Well, it turns out there might be a way. 86 00:09:30,580 --> 00:09:38,460 So what if instead of hooking one application directly, we hooked network communication similar to tools such as Wireshark? 87 00:09:38,460 --> 00: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. 88 00:09:45,540 --> 00:09:52,000 And then what we did is on our C2, we created a custom packet that had a magic constant value. 89 00:09:52,000 --> 00:09:57,340 And right after that magic constant, there was information we want to pass to our malware. 90 00:09:57,380 --> 00: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. 91 00:10:16,480 --> 00: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. 92 00:10:25,440 --> 00:10:30,810 Let's talk about how we would actually do this, and specifically hooking to user mode network stack. 93 00:10:30,810 --> 00:10:36,610 So a significant amount of services on Windows rely in user mode. 94 00:10:36,610 --> 00:10:39,970 And how can we globally intercept this traffic? 95 00:10:39,970 --> 00:10:49,030 Well, networking relating to Winsock is handled by afd.sys, otherwise known as the ancillary function driver for Winsock. 96 00:10:49,030 --> 00:10:56,860 Reversing a few functions inside of mswsock.dll revealed that a bulk of the communication was performed through ioctals. 97 00:10:56,860 --> 00:11:02,780 If we could somehow intercept these ioctals, we could snoop in on the data being received. 98 00:11:03,200 --> 00:11:06,340 So how do ERPs know where to go? 99 00:11:06,340 --> 00:11:11,120 So how does a kernel determine what function to call of what driver? 100 00:11:11,140 --> 00:11:19,340 Well, first it'll obtain the device object associated with the file object by calling ioget related device object. 101 00:11:19,340 --> 00:11:25,760 Now for our purposes, this will just be retrieving the device object member of the file object structure. 102 00:11:25,760 --> 00: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. 103 00:11:35,980 --> 00: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. 104 00:11:46,940 --> 00: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. 105 00:12:00,520 --> 00: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. 106 00:12:14,220 --> 00: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. 107 00:12:29,380 --> 00:12:34,380 Another option is just to perform a code hook directly on the dispatch function itself. 108 00:12:35,200 --> 00:12:39,260 So when picking a method , there's a few questions you want to ask yourself. 109 00:12:39,260 --> 00:12:41,960 How many detection vectors am I exposed to? 110 00:12:41,960 --> 00:12:47,600 How usable is the method, both from a compatibility and stability perspective? 111 00:12:47,660 --> 00:12:51,580 And how expensive would it be to detect the method? 112 00:12:51,640 --> 00:12:57,480 Well, for hooking a driver object in memory, you're going to be exposing yourself to memory artifacts. 113 00:12:57,620 --> 00:13:04,360 And from a usability perspective, you're going to be quite stable, mostly because driver objects are well documented. 114 00:13:05,100 --> 00: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. 115 00:13:18,320 --> 00: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. 116 00:13:31,260 --> 00: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. 117 00:13:38,000 --> 00:13:41,600 But unless the function is exported, you're going to need to find it yourself. 118 00:13:41,840 --> 00: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. 119 00:13:53,080 --> 00:13:57,700 And also, not all drivers are going to be compatible with this method due to patch guard. 120 00:13:57,700 --> 00:14:00,560 And this is also HVCI incompatible. 121 00:14:00,560 --> 00:14:03,140 Now, how expensive would it be to detect? 122 00:14:03,140 --> 00:14:05,000 Well, that can vary. 123 00:14:05,000 --> 00:14:08,840 So there's quite simple ways of detecting hooking that are varying inexpensive. 124 00:14:08,900 --> 00:14:12,360 There's other methods that can get pretty expensive. 125 00:14:12,520 --> 00: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. 126 00:14:22,480 --> 00:14:24,080 That's pretty inexpensive. 127 00:14:24,320 --> 00: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. 128 00:14:44,460 --> 00:14:52,700 Now, I wanted a method that was undocumented, stable, yet relatively expensive to detect. 129 00:14:52,700 --> 00:14:59,320 So what if instead of hooking the original driver object, we hook the file object structure instead? 130 00:14:59,400 --> 00: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. 131 00:15:11,440 --> 00:15:17,000 And for our purposes, this is the device object member of the file object structure. 132 00:15:17,020 --> 00:15:23,640 Now, what is stopping us from overriding this device object pointer inside of the file object with our own device? 133 00:15:23,640 --> 00:15:26,100 Well, it turns out absolutely nothing. 134 00:15:26,640 --> 00: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. 135 00:15:37,920 --> 00:15:43,860 And then we can replace the device object pointer inside of the file object with our own device. 136 00:15:43,960 --> 00:15:46,340 So let's talk about how we would do this. 137 00:15:46,340 --> 00:15:52,580 Well, first, we need to find file objects that are to the device AFD device object. 138 00:15:52,580 --> 00:15:55,200 But how can we actually find these objects? 139 00:15:55,280 --> 00: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. 140 00:16:05,360 --> 00: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. 141 00:16:21,180 --> 00: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. 142 00:16:33,420 --> 00:16:38,180 So then we can see, okay, this file object is associated with the AFD device. 143 00:16:38,180 --> 00:16:45,060 Now, before we can overwrite the device object member of the file object, we need to do some preparation first. 144 00:16:45,060 --> 00:16:48,100 And specifically, we need to create our fake objects. 145 00:16:48,100 --> 00:16:53,400 Fortunately, the kernel exports the function we can use to create our own objects. 146 00:16:53,620 --> 00: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. 147 00:17:06,280 --> 00:17:12,040 Now with our fake objects created, we're almost ready to set the device object of the file object. 148 00:17:12,040 --> 00:17:15,320 But first, we need to hook our driver object. 149 00:17:15,320 --> 00:17:19,640 And the way we can do this is by using the standard major function hook method. 150 00:17:19,640 --> 00:17:26,560 Except remember, we're performing this on our own copy of the driver object, not what a normal antivirus could retrieve. 151 00:17:26,560 --> 00:17:28,760 So it's actually relatively safe. 152 00:17:30,180 --> 00: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. 153 00:17:40,000 --> 00: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. 154 00:17:53,500 --> 00: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. 155 00:18:05,620 --> 00:18:11,000 So now that we've actually hooked the file object, there's not much work left. 156 00:18:11,020 --> 00:18:17,780 Inside of our dispatch hook, we need to check to see if the major function code being called is hooked. 157 00:18:17,780 --> 00: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. 158 00:18:24,860 --> 00: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. 159 00:18:36,160 --> 00:18:39,000 This is to prevent issues during teardown. 160 00:18:40,340 --> 00:18:44,980 So from a detection perspective, we're going to be exposing ourselves to memory artifacts. 161 00:18:45,260 --> 00: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. 162 00:18:54,080 --> 00:18:57,500 And finally, how expensive is it to detect the method? 163 00:18:57,500 --> 00:19:03,300 It's going to be pretty expensive because an antivirus would need to replicate our hooking procedure. 164 00:19:03,420 --> 00: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. 165 00:19:12,740 --> 00:19:16,100 So now let's talk about how to spec Darukin. 166 00:19:16,100 --> 00:19:20,180 Darukin I wrote abuses the user mode network stack. 167 00:19:20,420 --> 00:19:26,040 So now using our file object hook, we can intercept ERPs to the AFD driver. 168 00:19:26,040 --> 00:19:32,640 This will allow us to inspect all user mode traffic and send and receive our own data over any socket. 169 00:19:32,880 --> 00:19:38,840 To review our existing plan, we're going to hook user mode communication similar to tools such as Wireshark. 170 00:19:38,840 --> 00: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. 171 00:19:48,200 --> 00:19:56,540 And this way, our C2 can then communicate with our malware through any port, any service that is exposed. 172 00:19:56,860 --> 00:20:05,520 Now, how can we actually retrieve the contents of the packets that are received when the WCA receive function is called? 173 00:20:05,520 --> 00:20:11,260 Well, when you call that function, it's going to send an IOCTL called IOCTL AFD receive. 174 00:20:11,260 --> 00:20:16,060 And specifically, it'll pass the AFD receive info structure in the input buffer. 175 00:20:16,120 --> 00: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. 176 00:20:25,520 --> 00:20:33,400 And these buffers actually contain the bytes that are received from the packet, which we can use then to look into. 177 00:20:33,740 --> 00:20:37,780 So let's talk a little bit more about how the Spectre Rookit was designed. 178 00:20:37,860 --> 00: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. 179 00:20:47,760 --> 00: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. 180 00:20:56,040 --> 00: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. 181 00:21:09,460 --> 00:21:13,920 After the base packet structure, you have an optional custom structure. 182 00:21:13,960 --> 00:21:18,680 Now this, again, custom structure is optional, so it might not be there in the first place. 183 00:21:18,680 --> 00:21:22,720 But this will vary depending on the operation being requested. 184 00:21:22,960 --> 00:21:27,740 And after this optional custom structure, you can have any data you'd like. 185 00:21:27,800 --> 00: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. 186 00:21:35,980 --> 00: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. 187 00:21:45,340 --> 00:21:48,960 And then you can, after that packet, send any packet you'd like. 188 00:21:49,080 --> 00: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. 189 00:21:58,040 --> 00:22:01,680 So what happens when the Specter Rootkit receives a packet? 190 00:22:01,680 --> 00:22:03,220 Well, it's pretty simple. 191 00:22:03,220 --> 00:22:06,420 First, it'll search the buffers for the magic constant. 192 00:22:06,440 --> 00:22:11,220 If the buffer contains the magic, it'll go ahead into the processing stage. 193 00:22:11,320 --> 00:22:16,540 And if the buffer does not contain the magic, then it'll just ignore the packet. 194 00:22:16,540 --> 00:22:21,180 Now, before dispatching the packet, there's a few steps we need to take. 195 00:22:21,420 --> 00: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. 196 00:22:30,080 --> 00: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. 197 00:22:37,940 --> 00:22:40,160 And finally, we'll dispatch the packet. 198 00:22:42,140 --> 00:22:49,300 Now, before we go any further, let's talk about the concept of packet handlers inside of the Specter Rootkit. 199 00:22:49,340 --> 00:22:56,960 So the Specter Rootkit contains this general packet handler class that exposes a virtual process packet function. 200 00:22:57,040 --> 00:23:05,140 Now, this base packet handler class has a default constructor that receives a pointer to the current packet dispatcher instance. 201 00:23:05,140 --> 00:23:10,040 And the process packet function receives a pointer to the packet itself. 202 00:23:10,040 --> 00:23:12,230 We'll talk more about the dispatcher later. 203 00:23:13,320 --> 00:23:20,440 Now, an example of a packet handler included with the Specter Rootkit is the ping packet handler. 204 00:23:20,440 --> 00:23:22,920 This handler is quite simple. 205 00:23:23,160 --> 00:23:27,780 It is just used to determine if a port slash machine is infected. 206 00:23:27,780 --> 00:23:32,540 All it is, is a bare bone magic and then base packet structure. 207 00:23:32,540 --> 00:23:36,840 And this base packet structures has its type or operation set to ping. 208 00:23:36,840 --> 00: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. 209 00:23:49,540 --> 00:23:59,580 Now, once a packet is completely populated, the packet dispatcher will allocate a packet handler depending on the requested operation. 210 00:23:59,580 --> 00:24:03,840 Then it'll call that packet handler's process packet function. 211 00:24:03,840 --> 00:24:07,080 And finally, it'll free the packet handler. 212 00:24:07,080 --> 00: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. 213 00:24:21,760 --> 00: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. 214 00:24:35,060 --> 00:24:37,800 It'll recognize that there's a magic constant there. 215 00:24:37,800 --> 00:24:43,640 It'll then fill out the base packet and the optional custom structure for that packet. 216 00:24:43,640 --> 00: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. 217 00:24:49,340 --> 00:24:53,580 Then, during the dispatching phase, it'll allocate the ping packet handler. 218 00:24:53,580 --> 00:24:59,160 It'll call the ping packet handler process packet function, and then it'll free it. 219 00:24:59,160 --> 00: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. 220 00:25:13,860 --> 00: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. 221 00:25:22,020 --> 00: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. 222 00:25:33,200 --> 00: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. 223 00:25:50,340 --> 00: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. 224 00:25:58,720 --> 00:26:02,420 Then, it'll send this XOR packet to the Specter Rootkit. 225 00:26:03,900 --> 00: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. 226 00:26:13,300 --> 00:26:21,280 And then, it'll take that XOR content and call the dispatch function recursively to dispatch that brand new packet. 227 00:26:21,380 --> 00: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. 228 00:26:39,660 --> 00:26:46,040 So next, let's talk about executing commands, a common feature seen in a ton of Windows malware. 229 00:26:46,680 --> 00: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. 230 00:26:58,900 --> 00:27:04,340 So, in user mode, the first thing we need to do is create an unnamed pipe. 231 00:27:04,480 --> 00:27:09,600 We'll use this pipe in order to obtain the output of our process. 232 00:27:09,660 --> 00:27:17,480 Then, we'll set the startup info structure, and specifically the standard out and standard error handles, to our names pipe. 233 00:27:17,580 --> 00: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. 234 00:27:26,020 --> 00: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. 235 00:27:34,360 --> 00:27:37,900 And then we can wait for it to exit using wait for single object. 236 00:27:37,900 --> 00: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. 237 00:27:46,100 --> 00:27:49,680 Now, let's talk about how we would do this from kernel mode. 238 00:27:49,680 --> 00: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. 239 00:28:00,460 --> 00:28:05,860 Instead, you have to call the NT or ZW variants of functions. 240 00:28:06,100 --> 00:28:12,960 And kernel 32 DLL inside of user mode also calls these functions, but it acts as a simplified layer. 241 00:28:13,480 --> 00:28:16,960 So first, we need to actually create our pipe. 242 00:28:16,960 --> 00:28:21,700 And the way we can do this is by replicating what the kernel 32 DLL does itself. 243 00:28:21,700 --> 00: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. 244 00:28:42,200 --> 00: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. 245 00:28:49,160 --> 00:28:55,560 It'll then call NT open file on that read handle in order to open a write handle as well. 246 00:28:57,080 --> 00:29:04,460 So now that we've re implemented the create pipe function in kernel mode, we need to create the actual process. 247 00:29:04,460 --> 00:29:10,500 And we'll use the same function ZW create user process that kernel base uses itself. 248 00:29:10,900 --> 00:29:17,940 Now, we're going to need to replicate the entire process of entire process that kernel base does itself. 249 00:29:17,940 --> 00:29:21,160 And so we'll need to first pass in an attribute list. 250 00:29:21,160 --> 00: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. 251 00:29:31,440 --> 00:29:37,000 Next, we have to fill out an RTL process parameters structure for the process. 252 00:29:37,100 --> 00: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. 253 00:29:45,820 --> 00: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. 254 00:29:55,200 --> 00:30:03,000 From there, all it takes is a call to ZW create user process in order to start the command prompt process. 255 00:30:03,080 --> 00: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. 256 00:30:12,840 --> 00:30:16,400 Now let's talk about what you can do to hide your rootkit. 257 00:30:16,980 --> 00:30:19,120 So introduction to many filters. 258 00:30:19,120 --> 00:30:25,000 So many filters driver allow you to attach to volumes and intercept certain file operations. 259 00:30:25,000 --> 00:30:31,080 This is performed by registering your mini filter with the filter manager driver. 260 00:30:31,080 --> 00:30:37,200 So this reference from Microsoft documentation shows an example of user requesting file IO. 261 00:30:37,200 --> 00:30:41,480 First, the IO manager forwards the request to the file system. 262 00:30:41,600 --> 00: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. 263 00:31:00,400 --> 00:31:06,320 So many filters essentially allow you to edit file operations before they actually happen. 264 00:31:07,280 --> 00:31:13,040 And many filters can be used to mask the presence of our rootkit on the file system. 265 00:31:13,040 --> 00:31:18,800 For example, a mini filter can redirect all access to a certain file to another file. 266 00:31:18,860 --> 00:31:24,240 We can use this to redirect access to our rootkit file to another legitimate driver file. 267 00:31:24,580 --> 00:31:29,260 Now again, going back to picking a method, there's a few questions you want to ask yourself. 268 00:31:29,300 --> 00:31:31,940 How many detection vectors am I exposed to? 269 00:31:31,940 --> 00:31:36,420 How usable is the method from both a stability and compatibility perspective? 270 00:31:36,500 --> 00:31:39,500 And how expensive would it be to detect the method? 271 00:31:39,600 --> 00: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. 272 00:31:48,040 --> 00:31:51,660 So here are the requirements for the function fltRegisterFilter. 273 00:31:51,820 --> 00:31:57,220 You're going to need to create an instances registry key under your service key. 274 00:31:57,240 --> 00:32:03,320 And under that instances key, you need to create an instance name key, which can be whatever name you'd like. 275 00:32:03,320 --> 00:32:06,580 This is going to be the name for your filter instance. 276 00:32:06,580 --> 00: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. 277 00:32:17,800 --> 00:32:23,740 Then under your instance name key, you need to add the altitude and flags values. 278 00:32:23,940 --> 00:32:30,300 And the altitude is pretty much what the ordering is that many filters get called. 279 00:32:30,300 --> 00:32:38,620 The higher your altitude is, the first in line you are for your mini filter to get called. 280 00:32:39,900 --> 00:32:42,740 So how many detection vectors are you exposed to? 281 00:32:42,740 --> 00:32:46,100 You're going to be exposing yourself to registry and memory artifacts. 282 00:32:46,300 --> 00:32:47,880 How usable is the method? 283 00:32:47,880 --> 00: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. 284 00:32:56,240 --> 00: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. 285 00:33:15,700 --> 00:33:19,980 So another option is to just hook an existing mini filter. 286 00:33:19,980 --> 00:33:22,000 And there's a couple of routes you can take. 287 00:33:22,000 --> 00:33:27,360 First, you can just do a basic code hook on an existing mini filters function. 288 00:33:27,360 --> 00:33:36,380 You can overwrite the FLT registration structure, which is passed into FLT register filter before the victim driver calls it. 289 00:33:36,380 --> 00:33:45,620 Or you can edit a existing mini filter instance through DCOM to replace the function that gets called with your own . 290 00:33:46,060 --> 00:33:53,620 Now one of the easiest ways to intercept callbacks to an existing mini filter is just again a code hook. 291 00:33:53,620 --> 00: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. 292 00:34:02,560 --> 00: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. 293 00:34:14,280 --> 00:34:18,320 Not all drivers are compatible with this method due to patch card. 294 00:34:18,380 --> 00:34:21,660 And finally, this is also HVCI incompatible. 295 00:34:21,940 --> 00: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. 296 00:34:40,100 --> 00: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. 297 00:34:55,160 --> 00: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. 298 00:35:11,080 --> 00:35:17,720 Now the callback nodes array index is directly associated with the operation that is being filtered. 299 00:35:17,720 --> 00: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. 300 00:35:28,120 --> 00: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. 301 00:35:37,380 --> 00:35:43,720 Now this is because the FLT registration structure will contain the original function pointer. 302 00:35:44,080 --> 00: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. 303 00:35:56,540 --> 00:36:02,880 Now from a detection vectors perspective, you're going to be exposing yourself to primarily memory artifacts. 304 00:36:02,880 --> 00:36:11,260 And from a stability perspective, the only concern you should have is that the FLT instance structure itself is undocumented. 305 00:36:11,260 --> 00: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. 306 00:36:21,100 --> 00:36:23,380 Now how expensive would it be to detect the method? 307 00:36:23,380 --> 00: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. 308 00:36:33,680 --> 00:36:37,800 So let's say you want to protect a certain file, such as our rootkit file. 309 00:36:37,800 --> 00:36:40,680 What's an example of redirecting access to it? 310 00:36:40,680 --> 00:36:46,740 Well first, I'm assuming that you hooked a minifilter's pre-create operation callback. 311 00:36:46,740 --> 00: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. 312 00:36:57,160 --> 00: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. 313 00:37:10,320 --> 00: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. 314 00:37:26,620 --> 00: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. 315 00:37:43,020 --> 00:37:44,860 Okay, wrapping up. 316 00:37:44,860 --> 00:37:51,680 I'd like to give thanks to Alex Ionescu, a longtime mentor who's very experienced with Windows internals. 317 00:37:51,940 --> 00:37:59,800 The React OS is an amazing reference for Windows internals, especially for undocumented functions, functions, and structures. 318 00:37:59,800 --> 00:38:05,560 And thanks to Nemanja Mlazmajic and Vlad Ionescu for helping me review this presentation. 319 00:38:06,040 --> 00:38:07,880 Thanks for sticking around. 320 00:38:07,880 --> 00:38:13,920 You'll be able to ask questions in my DEF CON Q&A session, which will be later today. 321 00:38:14,040 --> 00:38:17,360 Make sure to follow me on Twitter and look at my blog. 322 00:38:17,360 --> 00:38:23,080 And you can check out the Spectre rootkit, assuming everything went well, at the URL below. 323 00:38:24,220 --> 00:38:26,040 Thanks for coming out to my talk again. 324 00:38:26,040 --> 00:38:26,920 Have a great one.