[00:00.000 --> 00:04.560] It will just be a minute more as we get self-paced running and things like that in our presentation. [00:06.300 --> 00:10.700] The presentation was originally developed for an hour and 30 minutes. [00:10.800 --> 00:16.180] However, this time slot is an hour and 50, I mean 50 minutes and we've already filled like 7. [00:16.700 --> 00:22.040] Therefore, we may run a little bit late if we need to leave early. [00:23.900 --> 00:28.520] However, I will go through the first 20 slides in about 2 minutes [00:28.520 --> 00:31.480] because we don't have time to get them in. [00:31.520 --> 00:36.860] And Sherry does the interesting slides, so I will introduce her soon. [00:37.000 --> 00:40.400] My name is Jamie Butler. I'm from Rootkid.com. [00:40.400 --> 00:42.080] This is Sherry Sparks. [00:42.140 --> 00:47.780] And we're developing a project called Shadow Walker and it's to hide rootkits. [00:48.300 --> 00:53.580] You can find a description of this talk probably later on Rootkid.com. [00:53.580 --> 00:55.840] It's in the source code. I believe Sherry has proposed it. [00:55.840 --> 00:58.580] Also, it will be in FRAG 63. [00:59.200 --> 01:04.160] If it hasn't appeared online already, probably in the next week or two it will be there. [01:04.180 --> 01:10.260] If you don't understand much about rootkits or you are new to the Windows world of rootkits, [01:10.260 --> 01:12.020] you can go to Rootkid.com. [01:12.020 --> 01:16.300] We post what's there for free and there's a lot of source code for free. [01:16.640 --> 01:19.120] Nothing there is for free. [01:19.120 --> 01:26.580] Or you can apply for a rootkit supporting the Windows kernel by Greg Kogel and myself. [01:27.100 --> 01:28.980] So we'll get started with Shadow Walker. [01:32.820 --> 01:34.540] So a quick flow through. [01:34.620 --> 01:38.100] Rootkits, they don't propagate themselves. They're not like a virus. [01:38.100 --> 01:39.760] They're just here to provide stuff. [01:39.860 --> 01:43.960] So the attacker gets on the box and they try to maintain presence. [01:44.040 --> 01:48.160] They don't want to keep using their zero day to come back all the time to the machine. [01:48.160 --> 01:49.920] So they install a rootkit. [01:50.060 --> 01:56.440] A rootkit is there to hide their presence and also to give them future access at a later date. [01:56.760 --> 02:03.840] Rootkits can also be used for law enforcement for things like a sneak and peek or a properly executed search warrant. [02:04.640 --> 02:11.320] There's two levels of protection within the Windows and Linux operating system. [02:11.320 --> 02:14.660] There's basically two rings, ring 0 and ring 3. [02:14.660 --> 02:20.260] Ring 3 is user land processes. They are unprivileged. [02:20.260 --> 02:21.960] Ring 0 is kernel land. [02:22.420 --> 02:27.760] Once you're in ring 0, your kernel rootkit can do whatever you want. [02:28.820 --> 02:39.980] Operating system provides basically an intermediary between upper level user processes and things that they want to query the operating system for. [02:39.980 --> 02:42.420] So I want to open a port. [02:42.420 --> 02:44.340] The operating system does that for you. [02:44.340 --> 02:47.360] And then hands it back to the user land process. [02:47.360 --> 02:53.860] They also provide an interface between user land processes and the lower hardware. [02:54.180 --> 03:01.320] So if you have a ring 0 kernel rootkit, you are just like the operating system, you are a man in the middle. [03:03.700 --> 03:09.800] Some of the different OS components that a rootkit may want to attack would be the IOManager. [03:09.800 --> 03:11.680] You can install keyloggers. [03:11.820 --> 03:16.920] Sharing has a project on rootkit.com that's a keyboard sniffer. [03:17.400 --> 03:20.040] And you can also attack the file system. [03:20.060 --> 03:26.700] You can return file filter driver and interface between every single access of the hard drive. [03:26.700 --> 03:34.560] The object manager has things like processes and threads that it uses to keep bookkeeping. [03:34.620 --> 03:40.400] And you can attack the object manager in order to hide your processes and threads. [03:42.420 --> 03:44.900] That's enough of that configuration manager. [03:44.900 --> 03:46.640] You can use that to hide registries. [03:46.760 --> 03:49.500] So first generation of rootkits, what were they? [03:49.500 --> 03:55.320] These were simple infringement replacement programs on the hard drive. [03:57.060 --> 04:04.360] They may include the Unix login program, LS, ES, so on and so forth. [04:05.040 --> 04:07.160] Second generation rootkits. [04:07.160 --> 04:09.720] These started to be more sophisticated. [04:10.220 --> 04:16.740] These modified static tables within the kernel are within the upper levels of the operating system themselves. [04:16.740 --> 04:25.600] So you can replace the import address table of a PE file and alter the execution path of any command. [04:25.600 --> 04:36.900] You can also alter the system call table within the kernel and ring zero and change what function actually gets executed within the kernel when the user requests it. [04:38.840 --> 04:41.560] Third generation rootkits. [04:41.560 --> 04:48.260] We term these... they use DCOM, direct kernel object manipulation. [04:48.540 --> 04:53.940] These actually don't hook anything because hooks are a bit easier to detect. [04:53.940 --> 04:58.380] Third generation actually manipulate the data structures in memory. [04:58.380 --> 05:04.900] So there's no code... you don't alter the code at all, you just alter the data structures themselves. [05:05.000 --> 05:07.260] This is almost undetectable. [05:07.260 --> 05:10.880] Data structures come and go very frequently and they're constantly changing. [05:10.880 --> 05:12.880] That's why they're in the data structure. [05:14.900 --> 05:17.460] This is very hard to detect. [05:17.460 --> 05:20.960] And we'll talk about how you can use this to hide processes and threads if you wanted. [05:21.080 --> 05:27.980] Or you can use it to hide drivers, escalate process privileges using tokens, etc. [05:29.060 --> 05:37.200] So real quick, about two years ago at one of the black ads, and all the slides are online, you can see process hiding with DCOM. [05:37.200 --> 05:40.080] I also mentioned a little bit last year when I talked about Python. [05:40.960 --> 05:47.460] What the kernel uses to keep track of all the processes is this list of active processes. [05:47.880 --> 05:53.220] Every e-process block you see there represents an individual process in the system. [05:53.500 --> 05:56.760] This individual process could be your little hacker program. [05:56.960 --> 06:00.860] It's doing things and you don't want the system administrator to know you're there. [06:01.580 --> 06:06.880] If you simply unlink... at the bottom of the slide you see that there's a series of links. [06:06.880 --> 06:13.760] If you simply unlink a process that you want to have hidden, it will disappear when you try to list it out. [06:14.100 --> 06:17.900] So when you run task manager, etc., it won't be there. [06:20.420 --> 06:24.740] Now, current rootkit detection methods, there's basically four types. [06:24.740 --> 06:26.680] You don't have time to go into all these. [06:26.820 --> 06:31.640] But just quickly, behavioral, integrity, signature-based, and difference-based. [06:32.560 --> 06:36.720] Some of the AB products are basically an example of signature-based. [06:36.740 --> 06:44.820] Integrity checkers, such as tripwire, check some of the disk files so that if you replace it with a fresh one, you'll be found. [06:44.960 --> 06:47.440] Difference-based approaches is kind of a new thing. [06:48.080 --> 06:51.980] Microsoft's doing it. F-Secure's doing it. Sysinternals. [06:52.320 --> 06:58.200] What this is, is it does some kind of low-level scan within the kernel by following structures directly. [06:58.200 --> 07:02.540] It tries to get a list of things like files and processes, registry keys, etc. [07:02.840 --> 07:05.060] Then it does an upper-level API call. [07:05.060 --> 07:07.500] If there's a difference, it knows something's up. [07:10.580 --> 07:11.880] Behavioral detection. [07:12.420 --> 07:15.800] In this, you try to detect the presence, the effects of rootkit. [07:15.800 --> 07:22.920] So you might want to look for alteration in the execution path, or what is actually executed within the program. [07:22.920 --> 07:25.600] Some of the system calls, or the ordering, etc. [07:26.080 --> 07:28.780] We don't have time to go into more of that. [07:28.800 --> 07:31.300] Integrity checking, exactly. It's a CRC. [07:31.380 --> 07:33.340] This could be on-disk or in-memory. [07:33.380 --> 07:35.960] However, hardly anyone is doing it in-memory. [07:37.260 --> 07:45.380] Signature-based scan memory or on-disk for a particular set of bytes that represents the mal code. [07:47.440 --> 07:52.180] So, we have a really powerful way to hide rootkit, or hide processes, etc. [07:52.180 --> 07:55.100] within the kernel by modifying all these data structures. [07:55.840 --> 08:02.780] However, if you look for our driver, because we are in the kernel, so we have a kernel-level driver, [08:02.780 --> 08:08.340] you can just do a simple scan, like a signature scan, for the presence of the malware. [08:08.860 --> 08:11.020] And that's not very good. [08:11.360 --> 08:16.220] So what we want to do is to try to shake it up, so even if you scan memory, [08:16.220 --> 08:21.520] you can't detect all that decomp stuff that's in here in the fu-rootkit. [08:22.520 --> 08:26.720] You want to make it so you can't detect our rootkit within the kernel. [08:27.140 --> 08:31.300] So, this is a problem that viruses have faced for a long time, [08:31.300 --> 08:34.360] and they use things like polymorphism and stuff to get around it. [08:34.640 --> 08:42.100] However, in the kernel, it gets more problematic to do really good polymorphism code. [08:42.100 --> 08:50.280] Also, no one's doing it other than a holy father and his hacker defender. [08:50.480 --> 08:52.680] And you have to get him to handle that. [08:53.020 --> 08:55.840] And I think it's like 40 heroes in the county does it. [08:56.380 --> 09:03.460] So, really quickly, we're going to walk through how we deploy this in memory and share it with people. [09:09.910 --> 09:11.270] Thank you, Jamie. [09:11.270 --> 09:16.530] I'm going to be discussing the technical implementation of ShadowWalker, [09:16.530 --> 09:19.370] which is our rootkit hiding technology. [09:19.690 --> 09:25.130] Like Jamie said, polymorphism has not really been seen a lot in rootkits so far, [09:25.130 --> 09:28.350] but it's not really an optimal approach for a rootkit. [09:29.130 --> 09:31.330] Viruses need to hide their code. [09:31.610 --> 09:37.890] Rootkits not only need to hide their code, but they also need to hide changes to operating system components. [09:38.870 --> 09:41.890] Polymorphism is not necessarily an optimal solution for that [09:41.890 --> 09:49.250] when you consider that you have the memory-based equivalence of, say, tripwire or other types of integrity checkers. [09:49.870 --> 09:55.470] So, basically, what we wanted to do is we wanted to figure out a way to hide in memory [09:55.470 --> 10:01.150] such that we actually hook the memory of the system without actually changing the code at all. [10:01.730 --> 10:04.730] And that gave rise to ShadowWalker, [10:04.730 --> 10:10.090] which we're coining kind of as a prototype for the fourth generation of rootkit technology. [10:11.570 --> 10:16.930] Basically, the alternative to polymorphism is virtual memory subversion. [10:18.550 --> 10:22.890] Basically, what we're going to be showing you today is a proof-of-concept demonstration [10:22.890 --> 10:27.830] that a rootkit is capable of transparently controlling the view of memory [10:27.830 --> 10:33.030] seen by user applications, kernel drivers, and the operating system itself. [10:34.730 --> 10:39.950] Now, the cool thing about this technology is the fact that there's a minimal performance impact. [10:40.270 --> 10:42.750] Because we exploit some features in the hardware, [10:43.570 --> 10:50.570] that means that basically our memory hook engine hiding a rootkit is virtually undetectable. [10:50.570 --> 10:53.910] It has no slowdown or system performance hit. [10:53.910 --> 10:58.610] Obviously, this makes this type of technology attractive to not only a rootkit, [10:58.610 --> 11:03.210] but also to maybe a virus, or a worm, or spyware applications. [11:06.250 --> 11:09.930] Just a few implications of virtual memory subversion. [11:10.030 --> 11:16.690] For a long time, rootkit scanners relied upon the integrity of the operating system API. [11:16.910 --> 11:20.870] Like Jamie mentioned, ever since the second generation of rootkits, [11:20.870 --> 11:22.870] rootkits have been hooking the API, [11:23.270 --> 11:28.770] and upper-level processes can no longer trust the results returned to them by the API, [11:28.770 --> 11:34.030] either the user-level API or the kernel-level API, depending upon where the rootkit is hooking. [11:34.030 --> 11:38.250] So basically, rootkit scanners have gotten a little bit smarter. [11:38.250 --> 11:44.650] They're starting to realize that they can't rely upon operating system APIs anymore. [11:44.650 --> 11:48.870] But even those rootkit scanners that don't rely upon operating system APIs, [11:48.870 --> 11:53.230] they still rely upon the integrity of the virtual memory system. [11:53.810 --> 11:58.810] So therefore, when a security scanner does a read of a certain memory address, [11:58.810 --> 12:02.250] it expects to see the data that's actually stored at that address. [12:02.250 --> 12:09.250] What we're proposing is actually to return some data that is, in fact, not actually stored at that address. [12:09.530 --> 12:12.950] So basically, there are two implications here. [12:13.010 --> 12:15.670] If we can control a scanner's memory reads, [12:15.670 --> 12:23.730] then we can fool signature scanners and potentially make a known rootkit or virus code immune to an in-memory signature scan. [12:23.730 --> 12:28.470] Because basically, the scanner will access the range of memory where the rootkit code is stored, [12:28.470 --> 12:36.350] and it will be returned data which is a clean copy or does not have the rootkit code. [12:36.570 --> 12:39.310] We can also fool integrity checkers. [12:39.310 --> 12:45.550] Those kind of checkers, like Jamie Butler, presented a talk on a tool called Vice, [12:45.550 --> 12:48.330] which basically attempts to detect hooks. [12:48.330 --> 12:54.270] Hooks typically work by replacing the beginning of the function prolog in an API [12:54.270 --> 13:00.010] with a jump instruction to a stub, which is the rootkit code. [13:00.010 --> 13:10.510] So basically, under normal situations, you would not expect to see the first instruction in an API to be a jump instruction. [13:10.510 --> 13:16.090] The normal code would be such stuff as stack setup code, allocation of local variables. [13:16.390 --> 13:22.430] So basically, Jamie uses heuristics like this to determine if functions are hooked. [13:22.430 --> 13:30.250] Basically, the implications for this is that if we are subverting that particular function in memory, [13:30.250 --> 13:37.710] the scanner would perform a read, and it would receive a clean copy of that API function code, [13:37.710 --> 13:41.690] yet the actual code that it's executing is actually the hook code. [13:44.440 --> 13:50.520] Since we're going to be talking about the subversion of virtual memory, we're going to need to do a little bit of a review. [13:51.100 --> 13:56.540] This is stuff that is normally covered in a college-level operating systems class, [13:56.540 --> 14:01.100] but I know that for most people, we probably need to review some of this stuff. [14:01.460 --> 14:05.600] First thing we're going to talk about is virtual address space layout, [14:05.600 --> 14:08.660] and then we're going to talk about some of the concepts of virtual memory. [14:08.660 --> 14:14.240] We're going to go over the ideas of paging and segmentation, page tables and PTEs, [14:14.240 --> 14:20.140] how virtual-to-physical address translation actually occurs, what the page fault handler does, [14:20.140 --> 14:23.580] and then some performance issues associated with paging, [14:23.580 --> 14:28.420] and how those are actually mitigated using a hardware solution, or the translation look-aside buffer. [14:28.880 --> 14:36.120] Finally, we'll talk about different memory access types, and some little eccentricities with the x86 architecture. [14:38.240 --> 14:47.060] So basically what you have here is a layout of the two most common layouts for the Windows virtual address space. [14:47.060 --> 14:52.060] The first one breaks the virtual address space up into two main components. [14:52.060 --> 14:56.820] Like Jamie said, we have two modes of operation under Windows. [14:56.820 --> 15:03.380] We have a privileged mode, which is basically kernel-in, and we have a non-privileged mode, which is basically user-in. [15:03.380 --> 15:06.880] The memory address space is arranged similarly to that. [15:07.240 --> 15:11.600] The lower two gigabytes correspond to user-in address space. [15:11.600 --> 15:16.520] That's going to contain your application code, DLL code. [15:16.520 --> 15:20.940] The upper two gigabytes is going to be where the operating system kernel is. [15:21.220 --> 15:28.280] In the upper two gigabytes, you're going to have N2S kernel, PAL, hardware abstraction layer, boot drivers. [15:28.280 --> 15:35.880] You're going to have key operating system structures, the process objects that Jamie was talking about, [15:35.880 --> 15:42.640] process page tables, and then non-page pool and cache memory. [15:43.080 --> 15:51.020] Alternatively, we have a second address space layout, which has been used on some of the Server 2003 systems, [15:51.020 --> 15:55.340] which actually allows you to expand the user address space up to three gigabytes. [15:56.460 --> 16:01.560] By far the most common layout is going to be the one where it's split in half. [16:03.680 --> 16:07.640] So, basically, what is the main idea behind virtual memory? [16:07.800 --> 16:13.300] Basically, the idea is we want to separate the virtual from the physical address spaces, [16:13.300 --> 16:16.320] or separate the virtual and physical address spaces. [16:17.420 --> 16:22.580] By that, what I mean is the virtual address space is the size of it. [16:22.580 --> 16:26.880] It's going to be defined by the width of your memory bus. [16:26.880 --> 16:34.120] So, if you have a 32-bit system, that means that you have the ability to address 2 to the 32, [16:34.120 --> 16:38.260] or approximately four gigabytes of continuous memory locations. [16:38.680 --> 16:43.520] So, therefore, your virtual address space spans from zero up to four gigabytes. [16:44.000 --> 16:49.520] In contrast, most of us don't really have four gigabytes of RAM installed on our system unless we're really lucky. [16:49.760 --> 16:56.100] So, therefore, we can say that our physical address space is constrained by the amount of RAM on our system. [16:56.100 --> 17:03.720] And the amount of RAM is most likely going to be less than the four gigabyte limit for the virtual address space. [17:04.240 --> 17:12.460] So, basically, virtual memory concerns managing these virtual and physical address spaces by dividing them into fixed-size blocks. [17:12.740 --> 17:16.420] If those blocks are all the same size, we have a paging architecture. [17:16.420 --> 17:20.340] If those blocks may be variable sizes, it's a segmentation architecture. [17:20.920 --> 17:25.460] The x86 architecture is actually a combination of segmentation and paging. [17:25.460 --> 17:33.200] However, for the purposes of our talk, we're focusing upon the paging architecture, because that's the level that we're going to subvert. [17:33.840 --> 17:42.260] As far as how this maps, there's actually mapping information that actually maps a virtual block to a physical block. [17:42.260 --> 17:49.220] And the OS is what actually maintains this mapping information and determines which virtual blocks map to which physical blocks. [17:49.860 --> 17:54.240] Like I said before, the virtual address space may be larger than the physical address space. [17:54.240 --> 18:01.500] And also because the OS is managing this mapping, virtually contiguous memory blocks do not need to be physically contiguous. [18:03.400 --> 18:07.920] This actually shows an illustration of those two points on the previous slide. [18:07.920 --> 18:11.860] As you can see, we have our virtual address space divided up into blocks. [18:11.880 --> 18:15.340] Under a paging architecture, we call these blocks pages. [18:15.700 --> 18:19.120] The physical address space is smaller than the virtual address space. [18:19.120 --> 18:22.180] It's also divided into fixed-size blocks. [18:22.180 --> 18:24.120] This is termed frames. [18:26.060 --> 18:33.380] Basically, as you can see, the virtually contiguous blocks do not necessarily have to be physically contiguous. [18:35.560 --> 18:41.300] So, what actually maintains this mapping information that the OS uses? [18:41.340 --> 18:44.490] This mapping information is contained in page tables. [18:44.880 --> 18:51.000] And page tables consist of entries called PTEs, or page table entries. [18:51.000 --> 18:55.260] Page table entries contain at least two pieces of useful information. [18:55.680 --> 19:00.160] They contain status information, and they contain mapping information. [19:00.320 --> 19:08.500] The page frame number bits are the bits that actually describe where that page actually maps to in physical memory. [19:08.820 --> 19:11.860] There are also a bunch of status bits, as you can see. [19:11.860 --> 19:16.120] A few of the most important ones would be the V there for valid. [19:16.120 --> 19:22.060] That's going to determine if the page is actually resident in memory, or if it's been paged out to the disk. [19:22.340 --> 19:30.940] You have the writable bit, which is going to be concerned with protection, whether or not that page is read-only, or whether it's going to be writable as well. [19:31.220 --> 19:35.160] You also have some bits, the cache-disabled, the dirty bit. [19:35.460 --> 19:42.320] These are bits that the operating system uses in its virtual memory management for page swapping and such. [19:42.320 --> 19:45.780] And then the last bit of interest is going to be the global bit. [19:45.780 --> 19:55.260] The global bit is interesting because it determines whether or not this particular mapping information will be flushed from the hardware cache on a context switch. [19:55.460 --> 19:58.060] This actually becomes important later on. [20:00.980 --> 20:07.040] So, basically, here's an illustration of the big picture, just to kind of summarize all these ideas. [20:07.040 --> 20:14.180] We have our virtual address space layout divided into user space and kernel space. [20:14.180 --> 20:19.390] We have our pages, which are the divisions of the virtual address space. [20:19.940 --> 20:25.580] We have process page tables located in the kernel memory section. [20:25.580 --> 20:31.330] Process page tables contain PTEs, which contain status information and mapping information, [20:31.600 --> 20:36.680] which will basically say where those pages are located out in physical memory. [20:39.820 --> 20:47.580] So, how do you go from an address to actually figuring out where that particular page is in the page table? [20:47.580 --> 20:54.760] Basically, virtual addresses can encode the information necessary to index into page tables. [20:55.060 --> 20:58.740] Page tables may actually be single-level or multi-level. [20:58.800 --> 21:02.500] The x86 maintains a two-level paging architecture. [21:03.700 --> 21:07.460] Basically, we divide the virtual address into two pieces. [21:07.460 --> 21:13.300] We're going to divide it into the virtual page number, which is going to contain the page table indexing information, [21:13.300 --> 21:21.300] and we divide it into the byte index, which is going to contain an offset from the physical frame in physical memory. [21:21.640 --> 21:26.360] So, basically, the virtual page number, in this case, since we have a two-level architecture, [21:26.360 --> 21:33.480] basically what that means is not only do we have a page table, we also have a page directory. [21:33.480 --> 21:38.640] And, basically, the page directory is a table of pointers to page tables. [21:38.940 --> 21:42.500] And the page tables, of course, mean pointers to physical frames. [21:43.060 --> 21:48.400] This actually is used to... a two-level scheme is used to actually save memory, [21:48.400 --> 21:52.360] since the page directory is going to have to be resident. [21:52.580 --> 21:58.060] So, basically, in this case, our virtual page number is actually divided into two pieces. [21:58.060 --> 22:03.920] We have to be able to figure out the index into the page directory so we can locate the correct page table, [22:03.920 --> 22:09.360] and then we have to figure out the index into the page table so we can locate the correct page frame. [22:09.380 --> 22:16.960] So, basically, under x86, the upper ten bits in the virtual address are going to provide your index into the page directory, [22:16.960 --> 22:20.760] while the middle ten bits are going to provide your index into the page table. [22:23.620 --> 22:28.840] This slide gets a little bit complicated, but I'm going to give it a go here to try to explain it. [22:28.980 --> 22:33.720] Basically, there's a number of steps involved in virtual-to-physical address translation. [22:33.760 --> 22:35.740] This is performed by the hardware. [22:36.440 --> 22:42.740] The first step is going to be to locate the page directory, the base address, the page directory and memory. [22:42.940 --> 22:47.020] The page directory base is actually located in the k-process block. [22:47.020 --> 22:52.120] The physical address is also located in CR3, which is the control register on the processor. [22:53.380 --> 22:57.420] So, basically, the first step is to locate the base of the page directory. [22:58.080 --> 23:05.480] The second step is going to be to locate the actual entry in the page directory that contains the pointer to the page table that we need. [23:06.080 --> 23:10.180] So, basically, we're going to extract the upper ten bits from the virtual address [23:10.180 --> 23:16.960] and use that to add to the page directory base to obtain the entry in the page directory table. [23:18.220 --> 23:28.160] Now, the contents of this page directory entry is basically the pointer to a page table, or the base physical address of the page table and memory. [23:29.040 --> 23:37.480] So, now at this point, we have the base address of the page table, and now we're trying to get to the PTE, or the entry in the page table. [23:37.480 --> 23:40.860] So, to get to the page table entry, now we do the same type of thing. [23:40.860 --> 23:47.640] We take the middle ten bits of the virtual address and use that to add to the base address of the page table, [23:47.640 --> 23:55.060] and we now get the actual page table entry, which is going to contain the pointer to the page frame in physical memory. [23:55.960 --> 24:03.180] So, now at this point, we have a pointer to the page frame, and we're actually looking for some byte within that frame. [24:03.180 --> 24:12.440] So, it's finally at this point that the actual byte index comes in, and we add that to the base address of the physical frame to actually resolve the full physical address. [24:19.450 --> 24:27.950] Like we said earlier, the physical memory may be smaller than the amount of virtually addressable memory you have, [24:27.950 --> 24:34.030] so therefore the OS may need to move some pages from main memory out to the disk to satisfy current memory demands. [24:34.790 --> 24:43.910] Basically, when the OS does this, it marks the PTE for the page in question as invalid, writes it out to the disk, [24:43.910 --> 24:48.410] and the next time that page is accessed, it's going to generate a page fault. [24:48.770 --> 24:54.770] A page fault is basically just an interrupt that is going to cause a vector to an interrupt service routine, [24:54.770 --> 24:57.270] which in this case is going to be the page fault handler. [24:58.110 --> 25:01.510] There are actually several conditions where a page fault can occur. [25:01.510 --> 25:07.490] The most common condition is going to be the case where the page table entry is marked invalid, [25:07.490 --> 25:12.250] and that page has been swapped out to the disk and needs to be brought in. [25:12.470 --> 25:17.770] There's a little caveat to that, because not only does the PTE need to be marked invalid, [25:17.770 --> 25:24.050] that entry cannot be present inside the TLB, or the hardware cache of virtual to physical mappings. [25:24.050 --> 25:26.630] This becomes very important later on. [25:27.250 --> 25:32.150] The second situation where you can have a memory protection violation is going to be, [25:33.170 --> 25:36.830] basically, user mode code attempting to access kernel memory, [25:36.830 --> 25:41.190] or perhaps an attempt to write to memory that's marked as read-only. [25:43.530 --> 25:48.370] For all of you guys that I lost on that previous slide of virtual to physical translation, [25:48.370 --> 25:51.590] I have a little bit clearer one that shows the whole page fault path. [26:06.160 --> 26:09.520] Okay. First thing, you have a memory access. [26:10.160 --> 26:16.000] So the first thing we do is we need to look it up in the page directory to resolve the actual page table. [26:16.320 --> 26:21.200] In this case, it's present. So we can move on and go out to the page table. [26:21.220 --> 26:24.980] Now, in the page table, we're trying to look up actually where the physical frame is. [26:25.180 --> 26:30.140] In this case, that particular PTE has been marked invalid, and a page fault is generated. [26:31.300 --> 26:33.900] This causes the page fault handler to be invoked. [26:33.900 --> 26:39.740] The page fault handler now is responsible for issuing the disk.io to go out to the page file [26:39.740 --> 26:43.560] and load that frame back into main memory. [26:43.740 --> 26:48.500] Once it's loaded back into memory, it now marks the PTE as valid. [26:52.180 --> 26:57.260] As you can see, some people would say that paging is not such a good idea. [26:57.440 --> 27:01.100] This is because there's this de-performance hint associated with paging. [27:01.100 --> 27:08.080] Basically, we take one memory reference and we basically turn it into three memory accesses. [27:08.080 --> 27:11.000] So we're cutting our performance by a factor of three. [27:11.360 --> 27:15.340] Basically, the first thing we have to access is the page directory. That's one memory reference. [27:15.340 --> 27:18.520] We have to access the page table. There's another memory reference. [27:18.620 --> 27:22.780] Now we have to actually go out and get the byte in the correct physical address. [27:22.780 --> 27:24.540] There's our third memory reference. [27:24.540 --> 27:29.720] So actually, in the best case, every single memory access requires three memory accesses [27:29.720 --> 27:32.720] to resolve the virtual to physical address. [27:32.940 --> 27:39.640] In the worst case, not only do we need three memory accesses, now we might potentially need two disk.ios. [27:39.680 --> 27:44.600] Because since we have a two-level paging scheme, our page tables may be non-present [27:44.600 --> 27:49.960] and our actual page frames may be non-present as well. [27:49.960 --> 27:53.220] So therefore, you may have up to two additional disk.ios. [27:53.220 --> 27:58.180] So as you can see, paging introduces a speed performance hit. [27:58.180 --> 28:04.500] Hardware designers found this to be unacceptable and they developed a solution for this to help mitigate this problem. [28:04.500 --> 28:07.620] Their solution was the translation look-aside buffer. [28:07.840 --> 28:14.440] The TLV is basically a high-speed hardware cache, generally implemented in associative memory, [28:14.740 --> 28:20.000] that holds the most frequently used virtual to physical mappings. [28:20.000 --> 28:27.260] So basically, the TLV caches the information held in the PTEs for certain pages that are used frequently. [28:28.820 --> 28:33.000] The TLV is actually a lot smaller than main memory, [28:33.000 --> 28:39.620] which means that it's possible that an entry in the TLV could actually be evicted [28:39.620 --> 28:46.840] if another page that maps to that TLV entry needs to be loaded into the TLV. [28:46.840 --> 28:51.140] So basically, the TLV is actually first on the memory access path. [28:51.140 --> 28:53.500] It sits in front of the page table path. [28:54.220 --> 28:58.020] So therefore, on the memory access, the TLV is actually searched first, [28:58.020 --> 29:00.700] before we ever even go out to the page tables. [29:01.260 --> 29:06.720] Since it is high-speed associative memory, that means that a reference found in the TLV [29:06.720 --> 29:14.960] can be resolved much, much faster than it can be resolved by even going out and doing a single memory reference in main memory. [29:14.960 --> 29:19.400] If that reference is actually found in the TLV, we say it was picked. [29:19.420 --> 29:22.380] If it was not found, we say that we had a miss. [29:22.940 --> 29:27.040] The x86 actually uses a split TLV architecture. [29:27.200 --> 29:30.100] And what I mean by that is there's actually two TLVs. [29:30.100 --> 29:34.320] There's one that holds translations for code, that's the iTLV, [29:34.320 --> 29:38.480] and there's another one that holds translations for data, that's the dTLV. [29:39.000 --> 29:42.500] I should note that modern TLVs have extremely high hit rates, [29:42.500 --> 29:46.760] which means that most translations are actually resolved via the TLV cache, [29:46.760 --> 29:50.380] as opposed to actually having to go out to the page tables, [29:50.380 --> 29:53.160] which significantly increases performance. [29:54.820 --> 30:00.380] This is just a simple example of a TLV search. [30:00.860 --> 30:04.580] Basically, the TLV is a cache. It consists of two parts. [30:04.580 --> 30:08.720] You have a tag, which in this case is going to be the virtual page number, [30:08.720 --> 30:12.480] and then you have the corresponding frame that maps to that virtual page number [30:12.480 --> 30:16.620] in the data section of the cache line. [30:16.700 --> 30:19.720] Basically, when a memory reference occurs, [30:20.420 --> 30:23.480] the TLV tags are going to be searched in parallel [30:23.480 --> 30:26.960] to see if they can find a match on that virtual page number. [30:27.020 --> 30:31.820] If they can, the data, or the corresponding physical frame mapping, [30:31.820 --> 30:33.300] is going to be returned. [30:33.300 --> 30:38.560] In this case, we had a TLV hit on virtual page 17, [30:38.560 --> 30:40.840] and its corresponding frame is 84. [30:43.040 --> 30:50.920] So, basically, here I have an illustration of the memory access path that includes a TLV. [30:51.820 --> 30:57.500] We're also going to stick with the x86 architecture, which actually has a split TLV. [30:57.500 --> 31:04.100] So that means that on a memory access, code accesses are going to be checked in the iTLV. [31:04.900 --> 31:08.960] So, in this case, we have a hit on the iTLV. [31:08.960 --> 31:12.340] We now have the corresponding frame, which is 132, [31:12.340 --> 31:15.320] and we can go directly out to physical memory. [31:15.600 --> 31:18.600] In this case, as you can see, since we had a TLV hit, [31:18.600 --> 31:21.880] we were able to completely bypass the page tables. [31:24.180 --> 31:27.220] It may be possible that we don't have a TLV hit. [31:27.220 --> 31:28.540] We could get a miss. [31:28.660 --> 31:32.920] And what's even worse than a miss, now that we have to go out to the page tables, [31:32.920 --> 31:37.140] now we might actually get a page fault, which will kill performance even further. [31:37.420 --> 31:40.640] So, in this case, our memory access is a code access. [31:41.000 --> 31:45.940] It goes out to the iTLV, searches it, it's not there, so now we have a miss. [31:46.520 --> 31:48.960] So, now we have to go out to the page tables. [31:49.240 --> 31:54.380] In this case, we go out to the page tables, and we find something we need is not present. [31:54.720 --> 31:56.540] So, we have a page fault. [31:56.540 --> 31:59.980] Now the OS page fault handler has to be invoked. [32:00.000 --> 32:03.540] It has to go out to the disk, bring that frame into memory, [32:03.540 --> 32:11.900] and finally mark that PTE as being present before the access can be completely resolved. [32:14.420 --> 32:18.580] So, basically, we have three memory access types. [32:18.580 --> 32:20.040] Everyone's familiar with these. [32:20.040 --> 32:22.520] We have read, write, and execute. [32:22.520 --> 32:30.080] Unfortunately, a little caveat to the x86 architecture is we actually only have two memory access types. [32:30.080 --> 32:33.980] We actually have read-execute and read-write-execute. [32:33.980 --> 32:38.380] By that, I mean the execute access is implied to all memory. [32:39.160 --> 32:41.500] This poses a slight problem. [32:41.500 --> 32:47.380] In some cases, we might like to actually differentiate between execute access and read-write access. [32:48.220 --> 32:55.580] One common place where this might be the case would be the implementation of non-executable memory. [32:55.580 --> 33:00.000] This is used as a buffer overflow protection scheme. [33:00.280 --> 33:05.060] Basically, you have your stack space, which is typically readable and writable. [33:05.060 --> 33:06.920] I mean, you store data on the stack. [33:06.920 --> 33:12.980] But normally, the stack does not contain executing code unless a buffer overflow attack is in progress. [33:12.980 --> 33:20.160] So, therefore, heuristically, if we were able to detect an execute access over a read-write access on the stack, [33:20.160 --> 33:27.320] we could say if we detect an execute, that heuristically, there's a good probability that we're in progress with buffer overflow attack. [33:27.960 --> 33:36.300] Now, the little caveat here is, like we said, x86 doesn't let us distinguish between read-write and execute. [33:36.560 --> 33:41.400] So, basically, we have not had hardware support up to this point. [33:41.400 --> 33:43.040] We do now have some hardware support. [33:43.040 --> 33:46.600] But previously, there was a project called PAX. [33:46.600 --> 33:54.600] Basically, PAX implemented read-write-no-execute or non-executable memory semantics using strictly software support. [33:54.600 --> 33:58.760] This was before there was any hardware support for non-executable memory. [33:59.120 --> 34:06.040] Windows XP Service Pack 2 and Server 2003 Service Pack 1 now also implement software support [34:06.040 --> 34:11.000] in the form of what Microsoft calls Data Execution Prevention or DEP. [34:11.460 --> 34:20.220] As a side note, hardware support has been added for non-executable memory onto 64-bit processors as well as the Pentium 4. [34:23.300 --> 34:35.520] Well, in the case of a rootkit, it's also kind of advantageous to be able to distinguish between executable and read-write access to a range of memory. [34:35.520 --> 34:44.120] Basically, we're going to take an offensive stance on the PAX technology, which, it's been around for a while. [34:44.120 --> 34:45.980] This technique's been around for a while. [34:45.980 --> 34:52.180] But so far, it hasn't really been shown how you could invert that technique for malicious purposes. [34:52.560 --> 34:55.480] So, basically, we want to hide code. [34:55.500 --> 34:58.620] So, we want to differentiate between read-write-and-execute. [34:58.620 --> 35:10.880] So, the idea is, if we have a read access of the code section of our rootkit driver, for example, that's going to be a very strong heuristic that a scanner's trying to locate us. [35:11.480 --> 35:16.680] So, basically, what we would want to do in that case is we want our rootkit code to run. [35:16.680 --> 35:31.320] But if a scanner tries to read it, we want to basically return an image of that memory that does not contain the rootkit code, or contains something clean, in the case of a modification to the operating system. [35:31.480 --> 35:37.840] So, basically, what we're proposing here is we're going to do an implementation that is the inverse of PAX. [35:37.840 --> 35:44.480] That is, it's an implementation of execute, allowed, but diverted read-write memory semantics. [35:47.220 --> 35:54.620] We're going to use the PAX technique that PAX discovered to implement non-executable memory. [35:54.620 --> 35:57.100] But, in this case, we're going to use it to hide a rootkit. [35:57.280 --> 36:03.880] So, like I said, the technique is not totally new, but the application is somewhat new. [36:04.280 --> 36:07.380] So, basically, there's three implementation issues here. [36:07.700 --> 36:11.280] First, we need a way to filter, execute, and read-write accesses. [36:11.280 --> 36:12.360] Step one. [36:12.360 --> 36:16.980] Second, we need a way to fake the read-write accesses once we detect them. [36:17.240 --> 36:22.020] Obviously, we don't want the scanner to receive the correct data when it reads our rootkit memory. [36:22.280 --> 36:26.280] Lastly, we need to ensure that performance is not adversely affected. [36:26.780 --> 36:36.300] Obviously, this would not be a viable solution if, when we install our rootkit, the system administrator notices that his machine is suddenly running really, really slow. [36:38.420 --> 36:47.480] So, to solve the first problem, basically, we said earlier that if we mark the PTE as invalid, [36:47.480 --> 36:53.020] the next memory access that occurs to that page is going to generate a page fault. [36:53.260 --> 37:00.600] So, basically, that means that we can trap memory accesses by marking their page table entries as invalid, [37:00.600 --> 37:05.820] and basically hooking the page fault handler and replacing the operating system's page fault handler. [37:06.460 --> 37:11.880] In the actual page fault handler, we have access to some interesting information. [37:12.140 --> 37:14.460] We have the saved instruction pointer. [37:14.460 --> 37:18.700] We also have the virtual address where the fault occurred. [37:18.860 --> 37:25.820] So, therefore, if our instruction pointer equals the address of where the fault occurred, we can say that this was an execute access. [37:25.860 --> 37:28.820] Otherwise, it's going to be a read-write access. [37:29.440 --> 37:39.900] There's one important thing to note here, and that is the fact that our memory hook for the rootkit has to work safely with the operating system's memory management, [37:39.900 --> 37:43.260] because the operating system's resolving page faults all the time. [37:43.260 --> 37:49.760] So, that means, in practical terms, that if we're going to mark pages not present to capture memory accesses, [37:49.760 --> 37:57.660] then we also must be able to distinguish whether those memory accesses are a result of someone trying to access the hidden page, [37:57.660 --> 38:01.220] or a result of normal operating system paging activity. [38:01.380 --> 38:05.660] In order to satisfy that, we actually impose two constraints. [38:05.920 --> 38:10.880] The first constraint could be that pages that are hidden are required to be a non-paged memory. [38:10.880 --> 38:15.060] That means there's no possibility that the OS could page that file out to the disk. [38:15.640 --> 38:22.160] The second constraint could be, if you're using pageable memory, that you use an operating system API to lock it down. [38:24.620 --> 38:34.520] So, once you're able to detect execute from read-write, well, now comes the issue of, well, how are we actually going to fake the read-write accesses? [38:34.660 --> 38:42.720] So, basically, this is the technique that was shown in PAX, that it used to implement the inverse of what we're going to talk about, [38:42.720 --> 38:48.660] and that is, we're going to desynchronize the hardware caches, the TLBs. [38:48.660 --> 38:54.540] Like I said, the x86 has two of them. It has one for code, which is searched on execute. [38:54.540 --> 38:57.280] It has one for data, which is searched on read-write. [38:57.500 --> 39:09.000] So, therefore, a memory access, under normal situation, the ITLB is going to contain virtual page number and corresponding physical frame, [39:09.000 --> 39:11.500] and the DTLB is going to contain the same thing. [39:11.500 --> 39:16.220] So, in this case, for virtual page number 12, we see it maps to frame 2. [39:16.220 --> 39:19.440] For the DTLB, it also maps to frame 2. [39:19.440 --> 39:25.440] Under normal situations, read-write and execute, they all map to the same physical frame. [39:27.400 --> 39:30.300] However, what if we could desynchronize them? [39:30.300 --> 39:37.820] What if we could have the ITLB to actually map off to a different physical frame than the DTLB? [39:37.820 --> 39:45.960] So, basically, the idea here is that the ITLB contains a pointer to a frame which contains the rootkit code, [39:46.360 --> 39:53.060] and the DTLB contains a pointer to the frame which either has a clean copy of something or some random garbage, [39:53.060 --> 39:55.200] which is not the rootkit code. [39:56.160 --> 40:01.600] So, basically, since all these translations are performed at the hardware level, [40:01.600 --> 40:08.960] this is going to be transparent to the application program, to the kernel driver, or even to the operating system. [40:09.460 --> 40:16.860] So, basically, once we've decided that we're going to desynchronize the TLBs, the question is, well, how do you actually do this? [40:18.240 --> 40:21.060] TLBs are almost entirely hardware controlled. [40:21.400 --> 40:28.420] However, there is a little bit of support for software control, which, in this case, happens to be enough support to implement this. [40:28.420 --> 40:32.400] First of all, we have the capability to flush the TLB. [40:32.860 --> 40:37.780] Basically, we can flush the entire TLB by reloading register CR3. [40:37.780 --> 40:40.680] This normally occurs on a context switch. [40:40.700 --> 40:47.780] On a context switch, typically, CR3 will be reloaded, which ends up flushing the entire TLB, [40:47.780 --> 40:51.140] except for global entries, which will remain in the TLB. [40:51.140 --> 40:58.940] This is mainly a performance enhancement for operating system components, which will remain resident at all times. [40:59.780 --> 41:06.920] Also, we also have a method of not only flushing the entire TLB, but of flushing an individual entry in the TLB. [41:06.940 --> 41:09.840] That would be the invalidate page instruction. [41:10.800 --> 41:15.400] So, now that we've established that we can flush the TLB, how about loading it? [41:16.300 --> 41:26.640] It is possible, actually, to load the TLBs separately, which means that actually executing a data access loads the DTLB, but not the ITLB. [41:27.040 --> 41:32.520] Conversely, executing a call loads the ITLB, but not the DTLB. [41:32.520 --> 41:38.740] So, therefore, it is possible to discretely load the two TLBs separately and desynchronize them. [41:40.760 --> 41:46.420] So, basically, our proof-of-concept implementation consists of two main components. [41:46.420 --> 41:53.480] We have the memory hook engine, which has a hook installation component, which sets up the hook, [41:53.480 --> 42:04.100] and then a custom page fault handler, which basically is responsible for filtering execute read-write accesses and correctly manually loading up the right TLB. [42:04.100 --> 42:09.320] Also, our second component is going to be a modified FU rootkit. [42:09.340 --> 42:16.700] So, basically, we've modified Jamie's rootkit to work in collusion with our ShadowWalker. [42:19.220 --> 42:24.880] So, basically, on memory hook installation, there are several steps that have to be performed. [42:24.960 --> 42:30.560] First of all, since we're going to mark pages not present, we need to install a new page fault handler. [42:31.180 --> 42:38.800] Once we've done that, on a page fault, we're going to need to be able to determine, relatively quickly, [42:38.800 --> 42:49.120] if that fault is due to one of our hidden pages being accessed, or if it's due to the actual operating system performing normal paging activity. [42:49.120 --> 42:55.320] So, in order to do that, we just insert that page into a simple hash table, so we can perform a quick lookup. [42:55.900 --> 42:58.460] Next, we mark the page not present. [42:59.580 --> 43:06.600] And then the last step is really the most important here, because we said that the TLB is first on the memory access path, [43:06.600 --> 43:12.360] which means that a page fault is generated, one, when the PTD is marked present, but like I said earlier, [43:12.360 --> 43:15.140] two, when that entry is not present in the TLB. [43:15.140 --> 43:19.480] So, therefore, we perform an explicit flush of that page that we're hiding. [43:19.500 --> 43:24.820] This means that on the next memory access, we will be able to generate a page fault. [43:25.420 --> 43:28.840] If you could please hold your questions to end, we'll have time for questions. [43:32.910 --> 43:41.150] So, basically, the custom page fault handler's main responsibility is to filter, read, write, and execute accesses for hooked pages. [43:41.630 --> 43:46.830] If it determines that the page is not a hooked page, meaning it's just a normal operating system page fault, [43:46.830 --> 43:49.390] it passes it down to the OS handler. [43:50.070 --> 43:57.830] If it is a page fault due to a hidden page, it's either going to manually load the ITLB or manually load the DTLB, [43:57.830 --> 43:59.850] depending upon the type of access. [44:00.630 --> 44:04.390] Most memory references will be resolved via the TLB path. [44:04.390 --> 44:07.610] So, intuitively, you would think that you would be generating a lot of page faults, [44:07.610 --> 44:09.750] it would take a big hit in performance. [44:09.750 --> 44:13.890] Not the case, because we're using the hardware caching mechanism. [44:13.890 --> 44:16.170] We're just simply synchronizing it. [44:16.650 --> 44:22.070] So, basically, we will still have page faults, but page faults are not as many as you would think, [44:22.070 --> 44:26.230] since the TLB path will handle most of the memory references. [44:26.770 --> 44:32.250] Page faults on the hooked pages will, of course, occur on the first execute or data access to the page. [44:32.250 --> 44:36.590] Remember, we had to flush it, flush the TLB, when we instantiated the hook. [44:36.830 --> 44:39.970] It will occur on the TLB cache line eviction. [44:39.970 --> 44:46.990] So, it's possible that the mapping for that hooked page got evicted from the TLB. [44:46.990 --> 44:51.470] Well, the next time that a memory access occurs, it will now generate a page fault, [44:51.470 --> 44:55.210] which the page fault handler will now reload the correct mapping into the TLB, [44:55.210 --> 44:57.090] and that memory access will go through. [44:58.370 --> 45:03.710] Likewise, the last case would be on an explicit TLB flush context switch, [45:03.710 --> 45:06.750] if the global bit has not been enabled. [45:09.090 --> 45:15.430] So, basically, here we have some really rudimentary page fault handler pseudocode for what's going on. [45:15.990 --> 45:20.570] So, basically, we are only hiding kernel pages right now. [45:20.570 --> 45:23.930] So, we're going to disregard user land accesses. [45:23.930 --> 45:27.050] So, in the page fault handler, we want this to be as efficient as possible, [45:27.050 --> 45:30.310] so we don't want to tie things up if we're not interested in handling this fault. [45:30.330 --> 45:33.990] So, the first thing we do is we just check if the processor mode is in user mode. [45:33.990 --> 45:37.110] If it is, we pass it down. We're not dealing with user mode. [45:37.270 --> 45:43.490] If the faulting address is a user page, we're also going to pass it down, because we don't hook user pages. [45:43.990 --> 45:50.470] In contrast, if the fault is from a hidden page, now we're going to check if it's a read-write or an execute. [45:51.010 --> 45:55.570] If it's an execute, we're going to jump over to load the ITLB. [45:55.690 --> 45:59.950] If it's a data access, we're going to jump over to the load DTLB code. [46:00.610 --> 46:08.030] Basically, in the load ITLB, what we're going to do is we're going to basically replace that physical frame information in the PTE [46:08.030 --> 46:15.630] with the mapping for the actual physical frame that contains the rootkit code, because we want it to execute. [46:15.890 --> 46:21.950] We're going to temporarily mark that PTE as present to allow the memory access to go through. [46:22.030 --> 46:26.530] We're going to perform a call into the page, which is going to load up the ITLB. [46:26.530 --> 46:32.670] I should note here, I forgot to put this on the previous page, that we do have to perform a call. [46:32.790 --> 46:40.850] Basically, that means that we need to find an empty byte in our given page that we can insert a return-off code to. [46:41.310 --> 46:46.530] In practice, if you're going to type sections, say in a driver, those are PE sections, [46:47.110 --> 46:52.810] so therefore there's going to be some alignment, so therefore there's normally some empty space in there. [46:52.810 --> 46:56.850] So you shouldn't really have trouble finding one byte that you can just patch a return into. [46:57.390 --> 47:03.930] So therefore, basically we just make a call to this point in the page where the return has been patched, [47:03.930 --> 47:10.590] and it returns control back to the pagefault handler, and in process, loads up the ITLB. [47:10.890 --> 47:17.310] Finally, the PTE is marked as non-present again, so we can catch subsequent accesses. [47:17.310 --> 47:23.110] We also replace the frame back with the original innocent-looking frame, [47:23.110 --> 47:28.890] and we go ahead and we return back to the faulting application, or driver as the case may be, [47:28.890 --> 47:33.550] without passing down to the OS. The OS doesn't need to have any knowledge of this default. [47:34.170 --> 47:38.610] If it's a DTLB load, basically the steps are a little bit simpler, [47:38.610 --> 47:43.930] because by default, we're going to maintain the clean mapping in the PTE. [47:43.930 --> 47:50.690] So therefore, we just mark it present, perform a read access, and then mark it non-present again. [47:50.950 --> 47:54.210] And then, of course, return without passing down to the OS. [47:56.830 --> 48:02.970] So, basically all this information so far concerns how you would hide executable code. [48:08.800 --> 48:13.460] Okay, we're running out of time, so I'm going to go ahead and skip this section on data hiding. [48:13.460 --> 48:18.260] This slide should be available probably on rootkit.com within the next week or so. [48:20.320 --> 48:24.120] So basically, I'm going to move ahead and we're just going to talk a little bit about the rootkit here. [48:24.560 --> 48:30.640] The rootkit is, of course, the object that we are implementing this process on to actually hide it. [48:30.780 --> 48:35.080] It's proof of concept. It doesn't really do anything malicious. It just hides processes. [48:35.700 --> 48:41.520] It runs as a system thread. It has no dependence upon user land initialization, [48:41.520 --> 48:45.500] which means it has no symbolic link or no functional device in the driver. [48:45.920 --> 48:50.620] And also, basically, we envision this rootkit as being an in-memory rootkit, [48:50.620 --> 48:54.720] meaning that ideally it would be installed via a kernel-level exploit. [48:54.940 --> 49:00.140] Obviously, if this rootkit driver is sitting on the disk, we open up a whole new can of worms for detection [49:00.140 --> 49:04.060] and basically invalidate all these faulty things that we're trying to do. [49:04.140 --> 49:06.880] So basically, we never want it to reside on the disk. [49:06.880 --> 49:13.580] This is a perfectly reasonable thing if we consider attacking, say, a server system, which is infrequently rebooted. [49:13.580 --> 49:19.820] Chances are high that this rootkit will sit in memory, and by the time we come back, it'll still be there. [49:22.280 --> 49:28.660] So, lastly, the impact on system performance. Like we said, modern TLBs have high hit rates. [49:28.800 --> 49:34.020] That means most translations will be resolved via the TLB and not the page fault path, [49:34.020 --> 49:39.020] which means that we're going to have very little performance hit. [49:39.040 --> 49:48.820] I can't speak from a performance metric point of view here, because we haven't really done any rigorous testing on performance, [49:48.820 --> 49:57.940] but I can say that, subjectively speaking, hiding Jamie's rootkit with this ShadowWalker memory hook engine, [49:57.940 --> 50:01.960] there is no subjective, noticeable performance impact. [50:02.560 --> 50:07.940] Clearly, the performance impact will probably vary depending upon the number of pages you're having to hide. [50:08.060 --> 50:12.760] In this case, hiding a kernel driver, how many pages do you really have? [50:12.760 --> 50:19.600] You have four or five sections in a PE, portable executable, so chances are you only have four or five pages. [50:19.600 --> 50:23.600] If they're page-aligned, four or five pages is really negligible. [50:24.220 --> 50:29.180] So, for small numbers of pages, we haven't observed any performance impact at all. [50:31.200 --> 50:37.120] As this is not really a weaponized attack tool, there are a number of known limitations. [50:37.660 --> 50:41.240] Jamie and I have done this basically on our spare time. [50:41.320 --> 50:47.320] So, basically, we've not implemented a fully productized or weaponized version. [50:47.480 --> 50:50.260] So, first of all, there's no PAE support. [50:50.260 --> 50:55.620] PAE basically extends addressability from 32 to 36 bits. [50:55.620 --> 50:57.340] We don't support that yet. [50:57.340 --> 51:00.600] There's no hyper-threading or multi-processor support. [51:00.600 --> 51:07.680] There's clear issues with regards to synchronization problems involved with porting this up to a multi-processor system. [51:07.680 --> 51:09.500] We've not addressed those yet. [51:09.600 --> 51:13.840] Currently, we only hide 4K-sized kernel pages. [51:14.000 --> 51:17.680] We are theoretically capable of hiding 4MB pages. [51:17.680 --> 51:22.320] The x86 architecture actually has two page sizes, 4K and 4MB. [51:22.320 --> 51:29.660] The main point of interest with the 4MB pages are going to be hiding of NTOS kernel, which is sitting on a 4MB page. [51:29.660 --> 51:36.020] Obviously, an interest to a rootkit developer who wants to install hooks into the operating system kernel. [51:36.080 --> 51:38.340] Currently, we can't hide NTOS kernel. [51:38.340 --> 51:42.400] There's some technical difficulties with that, which we're working to resolve. [51:42.580 --> 51:50.160] But it's quite effective for hiding drivers and other drivers, such as maybe Indus, which sit in the 4K page range. [51:50.160 --> 51:53.900] So we could hide the hooks in the network drivers, for example. [51:55.800 --> 51:58.980] As far as detection, I'm going to go over this real, real brief. [51:58.980 --> 52:01.140] I think we're really running out of time here. [52:01.480 --> 52:09.020] First of all, like we said earlier, we have to make the constraint that we have non-paged memory, [52:09.020 --> 52:15.460] that we're marking non-present, so we can differentiate our page faults from OS page faults. [52:15.460 --> 52:20.320] Obviously, non-present pages in non-paged memory are heuristically abnormal. [52:20.340 --> 52:24.800] If that's sufficient to indicate that a rootkit's installed, highly debatable. [52:24.800 --> 52:26.400] But it's still abnormal. [52:27.000 --> 52:31.260] Obviously, the page fault handler code itself cannot be marked not present, [52:31.260 --> 52:36.080] because that would imply that we're marking the page containing the page fault handler as not present. [52:36.080 --> 52:40.040] We would end up with lots of recursive problems of recursive re-entry. [52:40.040 --> 52:45.520] Therefore, the solution for hiding the page fault handler, we have to fall back to more tried-and-true approaches. [52:45.760 --> 52:49.540] It's small, it's written in assembly, there's no APIs, [52:49.540 --> 52:53.800] so polymorphism seems like a fairly reasonable solution to that problem. [52:54.380 --> 52:57.560] Likewise, it's also difficult to conceal hooks on the IDT. [52:57.560 --> 53:01.580] Currently, this proof of concept makes no effort to conceal the IDT hooks. [53:01.660 --> 53:05.340] Yes, this rootkit is very easily detected right now because of this, [53:05.340 --> 53:09.600] but that was not really our goal. We're just showing a proof of concept. [53:11.000 --> 53:17.420] Right now, we're really running short, so I'm going to proceed to the demo and skip the last couple slides. [53:27.090 --> 53:32.090] I know we're late, so if anyone has any other engagements, feel free to head on out, [53:32.090 --> 53:36.870] but I am going to go ahead and perform the demo for anyone that's interested. [53:39.330 --> 53:41.650] I'm going to do a GUI version of this demo. [53:41.650 --> 53:47.470] I have a hardcore Softice version if anyone wants to see after the talk. [53:51.660 --> 53:54.380] Basically, the first thing I'm going to do is... [53:54.380 --> 53:58.760] Right now, I'm using Driver Monitor. It's a tool with the Softice driver suite. [53:58.760 --> 54:01.000] It allows you to load a kernel driver. [54:01.640 --> 54:06.160] The first thing I'm going to do is I'm going to go ahead and load up Jamie's rootkit. [54:14.150 --> 54:16.590] Our driver has started successfully. [54:16.590 --> 54:19.830] Now, if we enter Softice... [54:23.490 --> 54:27.950] we can get the base address of where this driver is loaded in memory. [54:43.470 --> 54:47.430] This is the virtual address of where this driver is loaded in memory. [54:47.430 --> 54:50.810] We're going to go ahead and dump it. Dump the memory at that address. [54:51.350 --> 54:56.370] For all of you guys that use hex dumps all the time, this should look pretty familiar. [54:56.370 --> 55:02.030] This 4D5A, that's simply the MZ signature inside the PE header. [55:02.390 --> 55:07.450] Right now, what we're seeing is the PE header of this rootkit driver loaded in memory. [55:08.430 --> 55:15.350] At this point, I'm going to go ahead and load up the memory hook engine, ShadowWalker. [55:28.400 --> 55:30.860] It started up successfully. [55:32.300 --> 55:35.600] Now, we're going to go ahead and perform another memory dump. [55:37.780 --> 55:40.180] Now, it's gone. [55:47.740 --> 55:52.800] So, we'll take questions offline. We're leaving the room. We're being evicted. [55:52.960 --> 55:56.840] We have swag books. We'd love to hear from you. Thanks a lot.