💾 Archived View for aphrack.org › issues › phrack65 › 4.gmi captured on 2021-12-04 at 18:04:22. Gemini links have been rewritten to link to archived content
⬅️ Previous capture (2021-12-03)
-=-=-=-=-=-=-
==Phrack Inc.== Volume 0x0c, Issue 0x41, Phile #0x04 of 0x0f |=-----------------------------------------------------------------------=| |=---=[ Stealth hooking : Another way to subvert the Windows kernel ]=---=| |=-----------------------------------------------------------------------=| |=--------------------=[ by mxatone and ivanlef0u ]=---------------------=| |=-----------------------------------------------------------------------=| 1 - Introduction on anti-rookits technologies and bypass 1.1 - Rookits and anti-rootkits techniques 1.2 - About kernel level protections 1.3 - Concept key: use kernel code against itself 2 - Introducing stealth hooking on IDT. 2.1 - How Windows manage hardware interrupts 2.1.1 - Hardware interrupts dispatching on Windows 2.1.2 - Hooking hardware IT like a ninja 2.1.3 - Application 1 : Kernel keylogger 2.1.4 - Application 2 : NDIS incoming packets sniffer 2.2 - Conclusion about stealth hooking on IDT 3 - Owning NonPaged pool using stealth hooking 3.1 - Kernel allocation layout review 3.1.1 - Difference between Paged and NonPaged pool 3.1.2 - NonPaged pool tables 3.1.3 - Allocation and free algorithms 3.2 - Getting code execution abusing allocation code 3.2.1 - Data corruption of MmNonPagedPoolFreeListHead 3.2.2 - Expend it for every size 3.3 - Exploit our position 3.3.1 - Generic stack redirection 3.3.2 - Userland process code injection 4 - Detection 5 - Conclusion 6 - References ---[ 1 - Introduction on anti-rookits technologies and bypass Nowadays rootkits and anti-rootkits are becoming more and more important into the IT security landscape. Loved by some, hated by others, rootkits can be considered as the holy grail of backdoors : stealthy, little, close to hardware, ingenious, vicious... Their control over a computer locally or remotely make them the best choice for an attacker. Anti-rootkits try to detect and eradicate those malicious programs. Rk techniques and complexity are evolving fast and today developing a rk or anti-rk is a very hard mission. This paper deals about rootkits on Windows platform. More precisely about new kind of hijacking techniques that can be applied to the Windows kernel. Readers are assumed to be aware about rootkits techniques on Windows. ----[ 1.1 - Rootkits and anti-rootkits technics A rootkit hijacks an operating system's behavior. In order to achieve this task, it can simply modify the operating system's binaries but that's not very stealthy. Most rk's use hooks on important functions and change theirs results. A basic hook redirects execution flow by changing function start or a function pointer but there is no single way to hook a routine. The most common example is the SSDT (System Service Descriptor Table), this table contains the syscall list which is a set of functions pointers. If you can modify a pointer in this table, you are able to control the behavior of one function. That's an example of how rootkits proceed, obviously there is a lot of critical areas that can be controlled by an attacker. Anti-rootkits try to check those areas, but the task is very hard. Most of the time, anti-rk software makes a comparison between the memory image of the program and its binary on the disk or verify some function pointer tables to see if something has changed. That's how the war between rk-makers and anti-rk-junkies began, trying to find the best way, the best area, for hooking critical operating system features. On Windows those following areas are often used by rootkits : - SSDT (kernel syscalls table) and shadow SSDT (win32k syscall table) are the simplest solution. - MSR (Model Specific Registers) can be modified by a rootkit. On Windows the MSR_SYSENTER_EIP is used by the assembly instructions 'sysenter' to enter into ring0 mode. Hijacking this MSR allow an attacker to control the system. - MajorFunctions are functions used by drivers for I/O processing with others devices, hooking those functions can be useful for a rootkit. - IDT (Interrupt Descriptor Table) is table used by the system for handling exceptions and interruptions. Another kind of techniques has appeared. By accessing to the kernel objects a rootkit can easily change information about processes, threads, loaded modules and other stuff. Those techniques are called DKOM (Direct Kernel Object Manipulation). For example, the Windows kernel maintains a double linked list called PsActiveProcessList (EPROCESS structures) containing information about running processes. Unlink one of them and your process will disappear from process lists like task manager, whereas the process is still running. To block those kernel objects modifications, anti-rk checks other sections. For processes, they used to read the PspCidTable which has a table of PID (Process IDentifier) and TID (Thread IDentifier). A comparison between this table and PsActiveProcessList shows hidden processes. Against those attacks anti-rk tools have to find others sections and tricks to detect altered objects. One of the first paper about Windows stealth was written by Holy Father, "Invisibility on NT boxes" [1]. With this paper came one of the first public implementations of a rootkit with a ring0 driver, Hacker Defender [2], coded by Holy Father and Ratter of the famous VXing mag 29A [3]. This driver was able to elevate process rights using token manipulation. The rest of the rootkit uses user-land hooks to perform files and registry hiding, process infection with dll injection. A good example of a full ring0 rootkit is NT Rootkit of Greg Hoglund [4], this driver uses SSDT hooks to perform stealth operation. It registers a Filter Device Object above the NTFS file system and above the keyboard device for filtering IRP (I/O Request Packets). It also provides a NDIS protocol driver to hide communication on the network. Even if this rk was written for NT 4.0 and Win2K it's a perfect example for beginners. After came more advanced ring0 rk like FU [5], written by Fuzen_op and its improvement FUto published in the famous technical journal Uninformed [6]. Vista improvement on driver verification introduces new rootkits mostly based on hardware features. Like BootRoot [7] and Pixie [8] by Eeye loaded before any protection. Finally Joanna Rutkowska with her Blue Pill [9] used virtualization technology to create layer between the operating system and the hardware. In the wild the rk are used most of the time for lame mail spamming or botnets. They often use old techniques but some of them are interesting like Rustock [10] series or StormWorm [11] and the MBR rootkit [12]. They implement a lot of tricks as ADS (Alternate Data Stream), code obfuscation, anti-debug, anti-VM or polymorphic code. The goal is not only subverting the kernel but also slow down their analysis and make them harder to defeat. Even if the technology used by rootkits are more and more sophisticated, the underground community is still developing POCs to improve current techniques. Unreal [13] and AK992 [14] are both great examples. The first uses an ADS and a NTFS MajorFunctions hooking to hide itself, the second checks IRP completion when sended to disk's devices. You can find plenty examples of rootkit techniques on rootkit.com. Finally, this part would not be complete if we don't speak about anti-rk. The most famous is Rk Unhooker by MP_ART & EP_X0FF and their team UG North. Others anti-rk are DarkSpy [15] by CardMagic, IceSword [16] by pjf and Gmer [17]. ----[ 1.2 - About kernel level protections When we talk about protection, we must notice where the protection takes place into the system. A protection has an advantage on an attack only if it operates from a higher level. Protections like PaX or Exec Shield are efficient because they protecting userland from kernel. Protections like PatchGuard and other HIPS also protect the system integrity but as far as an attacker can find a way to attack those protections at their own level they will be useless. A protection is reliable only if it can't be corrupted by an attacker. Assuming an attacker find a way to inject code into the protection and you can consider that your b0x is dead. That's why PatchGuard isn't so efficient [18]. But we know that disabling or destroying a protection is very noisy. No, the best way is to fly under the radar by working with special objects and events that cannot be checked because of their volatility. In June 2006, Greg Hoglund presented the concept of KOH (Kernel Object Hooking) [19]. A new way of detouring code execution, you don't have to modify static code section but rather you work on dynamic allocated structures/codes like DPC (Deferred Procedure Calls). For protections, it's hard to find and verify those areas due to their instabilities. Others cool objects are IRP. They are the object used by the Windows kernel I/O manager to communicate with devices. Each I/O operation on hardware generates an IRP, sycalls send IRP to a driver through his device. In general a driver owns several devices; one of them is used to communique with the userland by using IOCTL and others devices are managing IRP by filtering them or performing a requested task. IRP are sent to a driver using its MajorFunctions table. This table includes the different functionalities provided by the driver. You can check the result returned by a MajorFunction by installing a completion routine on an IRP. They are very volatile objects; controlling and checking them is very hard. In fact, if you want to check everything you would need to completely redesign operating system architecture. So keep in mind that protection cannot be everywhere at every time and we will demonstrate it in the following parts. ----[ 1.3 - Concept key: use kernel code against itself The idea behind this paper is exploiting kernel code. Exploitation is possible because input defines code behavior. Submitting a crafted input to a vulnerable software can leads into code execution. Dangerous input is of course defined by your target. Kernel space contains more exploitation scenarios because you can change its environment. A rootkit can not change basic inputs as arguments. But it can change the environment around a code. Heap exploitation techniques such as unlinking is a perfect example. By changing a memory block structure, you are able to overwrite 4 bytes. Some techniques can even change next allocated block address [20]. It does work because a program trusts those information. In kernel, you have a total control on the environment. Also completely checking the kernel is bad for performance and totally impossible. Changing code environment has been used successfully for the phide2 rootkit [21] technique. This rootkit can hide threads without hooking Windows scheduler which is impressive. As it relies on code behavior, it needs strong reverse knowledge. It extends this concept into unknown operating system behaviors. Generic protections are based on generic assumptions. Such as checking only driver images for code hooks. These days operating systems design is against those protections and requires advanced software rootkit techniques. ---[ 2 - Introducing stealth hooking on IDT Let's introduce our concept about stealth hooking with an example based on IDT. First we will see what is the IDT and its purpose. Then we will discuss about hardware interrupts and how Windows deals with them. IDT (Interrupt Descriptor Table) is a CPU specific linear table localized in kernel-land. IDT can be read with ring3 privilege level but you must have ring0 privilege if you want to write into it. IDT is composed of 256 entries of KIDTENTRY structures and you can use the Kernel Debugger (KD) included into the Debugging Tools for Windows [22] to see the definition of an IDT entry. kd> dt nt!_KIDTENTRY +0x000 Offset : Uint2B +0x002 Selector : Uint2B +0x004 Access : Uint2B +0x006 ExtendedOffset : Uint2B Here we don't want to (re)explain the architecture of the IDT so we advise you to read Kad's paper published in Phrack 59 about IDT and about how it works [23]. The first 32 entries of IDT are reserved by the CPU for exceptions. Others are use to handle hardware interrupts and special system events. Here is a dump of the first 64 entries of the Windows' IDT. kd> !idt -a Dumping IDT: 00: 804df350 nt!KiTrap00 01: 804df4cb nt!KiTrap01 02: Task Selector = 0x0058 03: 804df89d nt!KiTrap03 04: 804dfa20 nt!KiTrap04 05: 804dfb81 nt!KiTrap05 06: 804dfd02 nt!KiTrap06 07: 804e036a nt!KiTrap07 08: Task Selector = 0x0050 09: 804e078f nt!KiTrap09 0a: 804e08ac nt!KiTrap0A 0b: 804e09e9 nt!KiTrap0B 0c: 804e0c42 nt!KiTrap0C 0d: 804e0f38 nt!KiTrap0D 0e: 804e164f nt!KiTrap0E 0f: 804e197c nt!KiTrap0F 10: 804e1a99 nt!KiTrap10 11: 804e1bce nt!KiTrap11 12: 804e197c nt!KiTrap0F 13: 804e1d34 nt!KiTrap13 14: 804e197c nt!KiTrap0F 15: 804e197c nt!KiTrap0F 16: 804e197c nt!KiTrap0F 17: 804e197c nt!KiTrap0F 18: 804e197c nt!KiTrap0F 19: 804e197c nt!KiTrap0F 1a: 804e197c nt!KiTrap0F 1b: 804e197c nt!KiTrap0F 1c: 804e197c nt!KiTrap0F 1d: 804e197c nt!KiTrap0F 1e: 804e197c nt!KiTrap0F 1f: 804e197c nt!KiTrap0F 20: 00000000 21: 00000000 22: 00000000 23: 00000000 24: 00000000 25: 00000000 26: 00000000 27: 00000000 28: 00000000 29: 00000000 2a: 804deb92 nt!KiGetTickCount 2b: 804dec95 nt!KiCallbackReturn 2c: 804dee34 nt!KiSetLowWaitHighThread 2d: 804df77c nt!KiDebugService 2e: 804de631 nt!KiSystemService 2f: 804e197c nt!KiTrap0F 30: 806f3d48 hal!HalpClockInterrupt 31: 80dd816c i8042prt!I8042KeyboardInterruptService (KINTERRUPT 80dd8130) 32: 804ddd04 nt!KiUnexpectedInterrupt2 33: 80dd3224 serial!SerialCIsrSw (KINTERRUPT 80dd31e8) 34: 804ddd18 nt!KiUnexpectedInterrupt4 35: 804ddd22 nt!KiUnexpectedInterrupt5 36: 804ddd2c nt!KiUnexpectedInterrupt6 37: 804ddd36 nt!KiUnexpectedInterrupt7 38: 806edef0 hal!HalpProfileInterrupt 39: 80f0827c ACPI!ACPIInterruptServiceRoutine (KINTERRUPT 80f08240) 3a: 80dc67cc vmsrvc+0x1C16 (KINTERRUPT 80dc6790) 3b: 80df6414 NDIS!ndisMIsr (KINTERRUPT 80df63d8) 3c: 80de040c i8042prt!I8042MouseInterruptService (KINTERRUPT 80de03d0) 3d: 804ddd72 nt!KiUnexpectedInterrupt13 3e: 80ed78a4 atapi!IdePortInterrupt (KINTERRUPT 80ed7868) 3f: 80f01dd4 atapi!IdePortInterrupt (KINTERRUPT 80f01d98) 40: 804ddd90 nt!KiUnexpectedInterrupt16 [...] This dump represents a typical Windows IDT, you can see the IDT entries index followed by the address of the handler and this name. The first 32 entries are filled by KiTrap* functions that manage exceptions. The rest of the table is left to the system, you can see specials system interrupts like KiSystemService and KiCallbackReturn and handlers used by drivers like I8042KeyboardInterruptService or I8042MouseInterruptService. ----[ 2.1 - How Windows manage hardware interrupts When we talk about interrupts we must introduce the concept of IRQL (Interrupt ReQuest Level). The kernel represents IRQLs internally as a number from 0 through 31 on x86 with higher numbers representing higher priority interrupts. Although the kernel defines the standard set of IRQLs for software interrupts, the HAL (Hardware Abstraction Layer) maps hardware interrupt numbers to the IRQLs. +----------------+ 31 | Highests | \ to | IRQLs | | Clock, system failure. 27 | | / +----------------+ 26 | | \ to | DEVICE_IRQL | | Hardware interrupts. 3 | | / +----------------+ 2 | DISPATCH_LEVEL | Scheduler, DPC. +----------------+ 1 | APC_LEVEL | Used when dispatching APC. +----------------+ 0 | PASSIVE_LEVEL | Threads run at this IRQL. +----------------+ Each processor has its own IRQL. You can have a core running at an IRQL= DISPATCH_LEVEL whereas another is running at PASSIVE_LEVEL. In fact IRQL represents the "mask ability" of the current running code. Interrupts from a source with an IRQL above the current level interrupt the processor, whereas interrupts from sources with IRQLs equal to or below the current level are masked until an executing thread decrease the IRQL. Some system components are not accessible when code is running at IRQL>=DISPATH_LEVEL. Accessing to paged memory (memory which can be swapped on disk) is impossible and lots of kernel functions cannot be used. Hardware interrupts are asynchronous and reached by external peripherals. For example when you hit a key, your keyboard device sends an IRQ (Interrupt ReQuest) routed by the Southbridge [24] on your interrupt controller through the Northbridge [25]. The Southbridge is a chip that can be described like a I/O controller hub. This chip receives all the I/O externals interrupt and send them to the Northbridge. The Northbridge is directly connected to your memory and high speed graphic bus also to your CPU. This chip is also known as the memory controller hub. On most x86 systems we find a chipset called i82489, Advanced Programmable Interrupt Controller (APIC). The APIC is composed by 2 main components, a I/O APIC, one per CPU, and a LAPIC (Local APIC) on each core. I/O APIC uses a routing algorithm to dispatch an interrupt on the best adapted core. According to the principle of locality, I/O APIC will deliver the device interrupt on the core which handled it the previous time [26]. After this LAPIC translates the IRQ to an 8-bits value, an interrupt vector. This interrupt vector represents IDT's entry index associated with the handler. When the core is ready to handle the interrupt, its instruction flow is redirected on the IDT entry. IDT IDT IDT IDT 1 2 3 4 +---+ +---+ +---+ +---+ | | | | | | | | |---| |---| |---| |---| | | | | | | | | |---| |---| |---| |---| | | | | | | | | +---+ +---+ +---+ +---+ | | | | +--------+ +--------+ +--------+ +--------+ | | | | | | | | | core 1 | | core 2 | | core 3 | | core 4 | | | | | | | | | +--------+ +--------+ +--------+ +--------+ | LAPIC | | LAPIC | | LAPIC | | LAPIC | +---+----+ +---+----+ +---+----+ +---+----+ | | | | | | | | <---+--------------+------+-------+-------------+-----> Interrupt | Processor system bus Messages | | | External +------+------+ Interrupts | | ---------------> I/O APIC | | | +-------------+ -----[ 2.3.1 Hardware interrupts dispatching on Windows On Windows, the interrupt handler isn't executed immediately, there is a code template first. This template is implemented in the function KiInterruptTemplate and does two things. First, it saves the current core state in the stack and dispatches code flow to the right "interrupt dispatcher". When a interrupt is raised, after the core status core is saved, code flow is transferred to the interrupt handler as defined in the IDT. In fact each interrupt handler in the IDT points to a KiInterruptTemplate routine [27]. KiInterruptTemplate will call KiInterruptDispatch which performs the following operations : - Acquire the service routine spinlock. - Raise IRQL to DEVICE_IRQL, the IRQL of a given interrupt vector is calculated by subtracting the interrupt vector from 27d. - Call the interrupt handler, an ISR (Interrupt Service Routine). - Lower IRQL. - Release the service routine spinlock. For example, the keyboard device ISR is I8042KeyboardInterruptService. ISR are routines for handling interrupts like top-halves in the linux kernel. According to the WDK (Windows Driver Kit), the ISR must do whatever is appropriate to the device to dismiss the interrupt. Then, it should do only what is necessary to save stage and queue a DPC. It means it interruption management will take place on a lower IRQL than during ISR execution. The I/O processing is done into the DPC. DPC (Deferred Procedure Call) are equivalent of bottom-halves in linux. DPC works at IRQL DISPATCH_LEVEL, lower than the ISR's IRQL. In fact the ISR will queue a DPC to process the entire interrupt at a lower IRQL in order to avoid the core preemption taking too much time. For the keyboard the DPC is I8042KeyboardIsrDpc. Here a figure to sum up the interrupt processing : +-------------------------+ Hardware Interrupt /----> Here we are at | | | | IRQL=DEVICE_LEVEL | | | | The KiInterruptDispatch | /---> IDT ---\ | | routine calls the ISR. | | | | | | | | ISR handles interrupt | +-----------------------+ | | and queue a DPC for | | KiInterruptTemplate ------/ | later processing | +-----------------------+ | | +-------------------------+ KiInterruptDispatch receives one main argument from KiInterruptTemplate, a pointer to an interrupt object stored in the EDI register. Interrupt objects are defined by a KINTERRUPT structure : kd> dt nt!_KINTERRUPT +0x000 Type : Int2B +0x002 Size : Int2B +0x004 InterruptListEntry : _LIST_ENTRY +0x00c ServiceRoutine : Ptr32 unsigned char +0x010 ServiceContext : Ptr32 Void +0x014 SpinLock : Uint4B +0x018 TickCount : Uint4B +0x01c ActualLock : Ptr32 Uint4B +0x020 DispatchAddress : Ptr32 void +0x024 Vector : Uint4B +0x028 Irql : UChar +0x029 SynchronizeIrql : UChar +0x02a FloatingSave : UChar +0x02b Connected : UChar +0x02c Number : Char +0x02d ShareVector : UChar +0x030 Mode : _KINTERRUPT_MODE +0x034 ServiceCount : Uint4B +0x038 DispatchCount : Uint4B +0x03c DispatchCode : [106] Uint4B We retrieve in this structure, the SpinLock and the ServiceRoutine. Notice that SynchronizeIrql contains the IRQL when the ISR will be executed. For each entry in the IDT which handles a hardware interrupt, the KiInterruptTemplate is contained in the DispatchCode table of the KINTERRUPT structure. For the keyboard device we have this KINTERRUPT : kd> dt nt!_KINTERRUPT 80dd8130 +0x000 Type : 22 +0x002 Size : 484 +0x004 InterruptListEntry : _LIST_ENTRY [ 0x80dd8134 - 0x80dd8134 ] +0x00c ServiceRoutine : 0xfa815495 unsigned char ->i8042prt!I8042KeyboardInterruptService+0 +0x010 ServiceContext : 0x80e2ec88 +0x014 SpinLock : 0 +0x018 TickCount : 0xffffffff +0x01c ActualLock : 0x80e2ed48 -> 0 +0x020 DispatchAddress : 0x804da8d8 void nt!KiInterruptDispatch+0 +0x024 Vector : 0x31 +0x028 Irql : 0x1a '' +0x029 SynchronizeIrql : 0x1a '' +0x02a FloatingSave : 0 '' +0x02b Connected : 0x1 '' +0x02c Number : 0 '' +0x02d ShareVector : 0 '' +0x030 Mode : 1 ( Latched ) +0x034 ServiceCount : 0 +0x038 DispatchCount : 0xffffffff +0x03c DispatchCode : [106] 0x56535554 Let's have a look at the beginning of KiInterruptTemplate : nt!KiInterruptTemplate: 804da972 54 push esp 804da973 55 push ebp 804da974 53 push ebx 804da975 56 push esi 804da976 57 push edi 804da977 83ec54 sub esp,54h 804da97a 8bec mov ebp,esp 804da97c 89442444 mov dword ptr [esp+44h],eax 804da980 894c2440 mov dword ptr [esp+40h],ecx 804da984 8954243c mov dword ptr [esp+3Ch],edx 804da988 f744247000000200 test dword ptr [esp+70h],20000h 804da990 0f852a010000 jne nt!V86_kit_a (804daac0) 804da996 66837c246c08 cmp word ptr [esp+6Ch],8 804da99c 7423 je nt!KiInterruptTemplate+0x4f (804da9c1) 804da99e 8c642450 mov word ptr [esp+50h],fs 804da9a2 8c5c2438 mov word ptr [esp+38h],ds 804da9a6 8c442434 mov word ptr [esp+34h],es 804da9aa 8c6c2430 mov word ptr [esp+30h],gs 804da9ae bb30000000 mov ebx,30h 804da9b3 b823000000 mov eax,23h 804da9b8 668ee3 mov fs,bx 804da9bb 668ed8 mov ds,ax 804da9be 668ec0 mov es,ax 804da9c1 648b1d00000000 mov ebx,dword ptr fs:[0] 804da9c8 64c70500000000ffffffff mov dword ptr fs:[0],0FFFFFFFFh 804da9d3 895c244c mov dword ptr [esp+4Ch],ebx 804da9d7 81fc00000100 cmp esp,10000h 804da9dd 0f82b5000000 jb nt!Abios_kit_a (804daa98) 804da9e3 c744246400000000 mov dword ptr [esp+64h],0 804da9eb fc cld 804da9ec 8b5d60 mov ebx,dword ptr [ebp+60h] 804da9ef 8b7d68 mov edi,dword ptr [ebp+68h] 804da9f2 89550c mov dword ptr [ebp+0Ch],edx 804da9f5 c74508000ddbba mov dword ptr [ebp+8],0BADB0D00h 804da9fc 895d00 mov dword ptr [ebp],ebx 804da9ff 897d04 mov dword ptr [ebp+4],edi 804daa02 f60550f0dfffff test byte ptr ds:[0FFDFF050h],0FFh 804daa09 750d jne nt!Dr_kit_a (804daa18) nt!KiInterruptTemplate2ndDispatch: 804daa0b bf00000000 mov edi,0 nt!KiInterruptTemplateObject: 804daa10 e9c3fcffff jmp nt!KeSynchronizeExecution+0x2 (804da6d8) [...] Remember, this code is unique for each KINTERRUPT. We said before that KiInterruptDispatch receives its arguments from the EDI register (a pointer to the KINTERRUPT of the interrupt). In the KiInterruptTemplate we can see this little code : [...] nt!KiInterruptTemplate2ndDispatch: 804daa0b bf00000000 mov edi,0 nt!KiInterruptTemplateObject: 804daa10 e9c3fcffff jmp nt!KeSynchronizeExecution+0x2 (804da6d8) [...] Here we have a mov "edi, 0" and a jmp, but if we look at the KiInterruptTemplate code contained in the keyboard's KINTERRUPT we have : ffb72525 bf5024b7ff mov edi,0FFB72450h ; Keyboard KINTERRUPT ffb7252a e9a9839680 jmp nt!KiInterruptDispatch (804da8d8) Wow, instructions are modified! The kernel will dynamically changes those 2 instructions in the KiInterruptTemplate code. In EDI we find the KINTERRUPT object and the jmp branch on KiInterruptDispatch. Why this implementation ? Because we can easily change the dispatch handler. Even if we often have the KiInterruptDispatch we can find KiFloatingDispatch or KiChainDispatch. KiChainedDispatch is for vectors shared among multiple interrupt objects and KiFloatingDispatch is like KiInterruptDispatch, but it saves the floating core state too. Windows provides APIs for connecting interrupts on IDT. IoConnectInterrupt and IoConnectInterruptEx, according to the WDK : NTSTATUS IoConnectInterrupt( OUT PKINTERRUPT *InterruptObject, IN PKSERVICE_ROUTINE ServiceRoutine, IN PVOID ServiceContext, IN PKSPIN_LOCK SpinLock OPTIONAL, IN ULONG Vector, IN KIRQL Irql, IN KIRQL SynchronizeIrql, IN KINTERRUPT_MODE InterruptMode, IN BOOLEAN ShareVector, IN KAFFINITY ProcessorEnableMask, IN BOOLEAN FloatingSave ); As you can see IoConnectInterrupt returns in the InterruptObject parameter a KINTERRUPT structure, the same that we retrieve in the IDT. Previously you have seen in the KiInterruptTemplate two labels, KiInterruptTemplateObject and KiInterruptTemplate2ndDispatch. Those two labels are used by kernel function to find the two instructions in the KiInterruptTemplateRoutine. KeInitializeInterrupt uses the KiInterruptTemplateObject label to update the "jmp Ki*Dispatch" and the KiConnectVectorAndInterruptObject function uses KiInterruptTemplate2ndDispatch to modify the "mov edi, <&Kinterrupt>". -----[ 2.3.2 Hooking hardware IDT like a ninja Now, think about this. We want to hook the IDT in a stealth way, we know that replacing an entry directly is not the best solution. Anti-rooktits don't check the dynamically allocated KiInterruptTemplate routine. So we can modify this routine as we wish. There are three possible ways : - Redirect the "jmp Ki*Dispatch" on our dispatch routine, we have to code our dispatch routine, not so hard. - Change the kinterrupt address passed in EDI by the instruction "mov edi, <&Kinterrupt>". The new KINTERRUPT will be the same than the previous one, only the ServiceRoutine will be modified by us. - Create our own KiInterruptTemplate, hard ... In this paper, we choosed the simplest way. We change the "mov edi, <&kinterrupt>" by a "mov edi, <&OurKinterrupt>" and we implement our ServiceRoutine. We know that this instruction is followed by a jmp, so with a disassembly engine we can retrieve the instruction before the jmp nt!KiInterruptDispatch and modify it. We must keep in mind, when the ServiceRoutine is running, the interrupt is not handled yet and we are running at DEVICE_IRQL IRQL. This is not a fair situation, because a lot of Windows kernel functions are not accessible. We know, that most ISR queued a DPC, so after the ISR has been executed, the last entry in the current core DPC queue should contain the DPC routine of our interrupt. If we want to access data generated by the interrupt we must proceed like the ISR. Replacing the original ISR by our own ISR handler is very hard, because it depends too much on the hardware device. But we know that the real I/O is done by the DPC, so when KiInterruptTemplate will call our ServiceRoutine, first we call the original ServiceRoutine and we modify the last DPC entry by our. DPC are represented by KDPC structures : kd> dt nt!_KDPC +0x000 Type : Int2B +0x002 Number : UChar +0x003 Importance : UChar +0x004 DpcListEntry : _LIST_ENTRY +0x00c DeferredRoutine : Ptr32 void +0x010 DeferredContext : Ptr32 Void +0x014 SystemArgument1 : Ptr32 Void +0x018 SystemArgument2 : Ptr32 Void +0x01c Lock : Ptr32 Uint4B DPC list can be found in the KPRCB (Kernel Processor Control Region Block) structure of the current processor. KPRCB is preceded by a KPCR (Kernel Processor Control Block) structure which is located at FS:[0x1C] on the current processor. KPRCB is a 0x120 bytes from the beginning of the KPCR structure. dt nt!_KPRCB [...] +0x860 DpcListHead : _LIST_ENTRY +0x868 DpcStack : Ptr32 Void ; DPC arguments +0x86c DpcCount : Uint4B ; DPC core counter +0x870 DpcQueueDepth : Uint4B ; Numbers of DPC in the list +0x874 DpcRoutineActive : Uint4B +0x878 DpcInterruptRequested : Uint4B +0x87c DpcLastCount : Uint4B +0x880 DpcRequestRate : Uint4B +0x884 MaximumDpcQueueDepth : Uint4B +0x888 MinimumDpcRate : Uint4B Now we know how to retrieve the DPC of our interrupt, we can easily change it to our own and handle the data. For the keyboard the DPC is queued by KeInsertQueueDpc in the I8xQueueCurrentKeyboardInput routine called by the keyboard's ISR. kd> dt nt!_KDPC 80e3461c +0x000 Type : 19 ; 19=DpcObject +0x002 Number : 0 '' +0x003 Importance : 0x1 '' +0x004 DpcListEntry : _LIST_ENTRY [ 0xffdff980 - 0x80559684 ] +0x00c DeferredRoutine : 0xfa815650 void i8042prt!I8042KeyboardIsrDpc +0x010 DeferredContext : 0x80e343b8 +0x014 SystemArgument1 : (null) +0x018 SystemArgument2 : (null) +0x01c Lock : 0xffdff9c0 -> 0 Here is the figure of the attack : MyKinterrupt structure +---------------------+ Hardware Interrupt /----> MyServiceRoutine | | | | Calls the original | | | | ISR ------\ \---> IDT ---\ | | And modify the DPC | | | | | queue. | | | | +---------------------+ | +---------------------+ | | | KiInterruptTemplate -----/ Original Kinterrupt | +---------------------+ +---------------------+ | Core | | | +------------+ | ServiceRoutine <-----/ | | | Queues the ISR's DPC| |DpcListHead |--\ +---------------------+ | | | +------------+ | | +-----+ +-----+ +-----+ +-----+ \-> DPC |---->| DPC |---->| DPC |---->| DPC |-->DpcListHead DpcListHead<---| |<----| |<----| |<----| | +-----+ +-----+ +-----+ +-----+ /\ || Last DPC entry Modified after the call to the ServiceRoutine. -----[ 2.3.3 - Application 1 : Kernel keylogger It's time to design a POC. In this sample we will see how to sniff keyboard keystrokes. As you see previously, we are now able to control the DPC generated by an interrupt. For the keyboard we will hijack the I8042KeyboardIsrDpc routine which is set into the DPC's keyboard interruption. With our own DPC handler we will reproduce the behavior of the original routine, unfortunately this kind of routine is hard to write so we ripped some pieces of codes and used reversing techniques (notice the lazy hacker style). In our DPC handler we must call the KeyboardClassServiceCallback [28] routine, this routine is provided by the Kbdclass driver. This callback transfers input data buffer of a device to the class data queue. A function keyboard driver must calls this class service callback in its DPC routine. Here is the KeyboardClassServiceCallback's prototype : VOID KeyboardClassServiceCallback ( IN PDEVICE_OBJECT DeviceObject, IN PKEYBOARD_INPUT_DATA InputDataStart, IN PKEYBOARD_INPUT_DATA InputDataEnd, IN OUT PULONG InputDataConsumed ); Parameters : DeviceObject : Pointer to the class device object. InputDataStart : Pointer to the first keyboard input data packet in the input data buffer of the port device. InputDataEnd : Pointer to the keyboard input data packet that immediately follows the last data packet in the input data buffer of the port device. InputDataConsumed : Pointer to the number of keyboard input data packets that are transferred by the routine. KEYBOARD_INPUT_DATA is defined by : typedef struct _KEYBOARD_INPUT_DATA { USHORT UnitId; USHORT MakeCode; USHORT Flags; USHORT Reserved; ULONG ExtraInformation; } KEYBOARD_INPUT_DATA, *PKEYBOARD_INPUT_DATA; So in our DPC handler we just have to check the MakeCode member of the set of KEYBOARD_INPUT_DATA structures. The MakeCode (or scancode) represents the data sent by the keyboard to the system when you hit or release a key, each key has it's own scancode and the system usually translates the scancode into a character depending on you code page. For example the scancode 19d on classical US keyboard is translated into the keycode 'e'. In order to know if CAPSLOCK is activated we send an IOCTL to the functional keyboard device but we can only send IOCTL at a PASSIVE_LEVEL IRQL. For that we use a system thread which will sent IOCTL with the kernel API IoBuildDeviceIoControlRequest. In fact the scancodes are queued in a list locked by a spinlock and thread synchronized with a semaphore. The thread is listening to incoming keystrokes then converts scancodes into keycodes. Like the kernel keylogger Klog does [29]. -----[ 2.3.4 - Application 2 : NDIS packet sniffer In the same way, an interrupt is raised when your network card receives a packet. When this kind of interrupt is raised NDIS ISR handler (ndisMIsr) routine launches the miniport ISR interrupt handler. The ndisMIsr routine is used as a wrapper for miniport ISR and DPC. You can see in the IDT the following entry : 3b: 80df6414 NDIS!ndisMIsr (KINTERRUPT 80df63d8) It means, your ISR handler is not called directly when an interrupt occurs, it is the ndisMIsr routine. Miniport's ISR is called by ndisMIsr and the miniport DPC is also queued in this routine. The DPC queued is the ndisMDpc routine which wraps your own DPC miniport handler. Finally NDIS wraps all the interrupt process with ndisMIsr and ndisMDpc routines on Windows XP with NDIS 5.1. We don't know if this implementation is still present on Windows Vista with NDIS 6.0. We know we can hijack the ndisMDpc handler by our own handler. With NDIS we will proceed in the same way but we will not hook the MiniportDpc routine but directly hook the ndisMDpc routine. Why? Because we know that ndisMDpc wraps the MiniportDpc routine and in fact MiniportDpc depends too much on the hardware of the miniport device. Each miniport device is represented by an NDIS_MINIPORT_BLOCK [30] structure, in this structure we find a reference to a NDIS_MINIPORT_INTERRUP structure, which looks like : kd> dt ndis!_NDIS_MINIPORT_INTERRUPT +0x000 InterruptObject : Ptr32 _KINTERRUPT +0x004 DpcCountLock : Uint4B +0x008 Reserved : Ptr32 Void +0x00c MiniportIsr : Ptr32 Void +0x010 MiniportDpc : Ptr32 Void +0x014 InterruptDpc : _KDPC +0x034 Miniport : Ptr32 _NDIS_MINIPORT_BLOCK +0x038 DpcCount : UChar +0x039 Filler1 : UChar +0x03c DpcsCompletedEvent : _KEVENT +0x04c SharedInterrupt : UChar +0x04d IsrRequested : UChar If we look at the ndisMDpc routine we notice that only the first parameter is used and this parameter refers to a NDIS_MINIPORT_INTERRUPT structure. The ndisMDpc function will call the MiniportDpc field of this structure. We just have to hijack this pointer by our routine in order to control the incoming packets on the system. The NDIS documentation specifies that a miniport DPC routine must notify the bound protocol driver that an that an array of received packets is available by calling the NdisMIndicateReceivePacket function [31]. VOID NdisMIndicateReceivePacket( IN NDIS_HANDLE MiniportAdapterHandle, IN PPNDIS_PACKET ReceivePackets, IN UINT NumberOfPackets ); In the ndis.h header we have : #define NdisMIndicateReceivePacket(_H, _P, _N) \ { \ (*((PNDIS_MINIPORT_BLOCK)(_H))->PacketIndicateHandler)( \ _H, \ _P, \ _N); \ } So in our MiniportDpc routine we will hihjack the PacketIndicateHandler, which is often the ethFilterDprIndicateReceivePacket routine in the NDIS_MINIPORT_BLOCK structure, in order to filter the incoming packets on the miniport. After we have hijacked this pointer we call the original MiniportDpc routine that will process everything. After that, we restore the PacketIndicateHandler handler in the NDIS_MINIPORT_BLOCK for stealth reasons. To sum up we must : - Hijack the routine into the DPC queued by the ndisMIsr routine. - Now that we have hijacked the ndisMDpc we modify the PacketIndicateHandler into the NDIS_MINIPORT_BLOCK of the miniport. - We call the ndisMDpc routine. It will call the original MiniportDpc handler - The MiniportDpc routine calls the NdisMIndicateReceivePacket macro. Our filter function is called and we do our job. - When the ndisMDpc returns we restore the original PacketIndicateHandler into the NDIS_MINIPORT_BLOCK of the miniport. With this filter, we can monitor or modify the incoming packets. For example, our PacketIndicateHandler hook can search in the incoming packets for a tag, when this tag his found the rootkit triggers a function. ---[ 2.2 - Conclusion about stealth hooking on IDT In this part we have seen how Windows manages his hardware interrupts by using a global template function dedicated to all interrupts. The fact that this template routine his forged for each interrupts is the main point of this attack, with that we can create a fake template routine that cannot be detected directly. The stealth of our attack remains on two points : - We modify only dynamic allocated and forged code - We hijack highly temporal dynamic allocated structures which when running, are always preempting the core. So, even if the scope of our attack is restricted, controlling the hardware is the best way for a rk to reach critical components. Finally, we have just cheated the system with its own features and that's the purpose of a stealth rootkit. --[ 3 - Owning NonPaged pool using stealth hooking Rootkit sophistication depends on how it subverts the kernel. More complex techniques come out as kernel and hardware understanding evolve. Nowadays there is so many ways to subvert the kernel, in consequence protections become harder to defeat. We're going to present a different means to gain control. Next techniques apply this approach to the kernel memory allocator. Our goal is getting execution on every NonPaged allocation without using any hook. It must bypass any hooking verification even those based on code page comparison or hashing. It will be done by modifying data used by the allocator. We just apply the concept of using code against itself. We do believe that this concept can be used on others components and in different ways successfully. We won't try to convince you that this technique is perfect. It evades current protections and detection systems. The more important is that they would need more than a simple modification to prevent and block an attack based on kernel code behavior. ---[ 3.1 - Kernel allocation layout review As every operating system, Windows kernel puts forward some functions in order to allocate or free memory. Virtual memory is organized as block of memory called pages. In Intel x86 architecture, a page size is 4096 bytes and most allocations requests are smaller. Thus, kernel functions like ExAllocatePoolWithTag and ExFreePoolWithTag kept unused memory blocks for next allocations. Internal functions directly interact with hardware each time a page is needed. All those procedures are complex and delicate that's why drivers trust kernel implementation. -----[ 3.1.1 - Difference between Paged and NonPaged pool Kernel system memory is divided in two different kind of pool. It has been separated to distinguish most used memory blocks. The system must know which pages should be resident and which can be temporarily discarded. The page fault handler restores pageable memory only when IRQL is inferior of DPC or DISPATCH level. Paged pool can be paged in or out of the system. A memory block paged out will be saved on the file system and so unused part of paged memory will not be resident in memory. NonPaged pool is present in every IRQL level and then is put-upon for important tasks. The file pagefile.sys contains paged out memory. It was attacked to inject unsigned code into Vista kernel [32]. Some solutions was discussed as disabling kernel memory paging. Joanna Rutkowska defended this solution as more secure than others but with a small physical memory loss. Microsoft just denied raw disk access, which may prove that Paged and NonPaged layout is an important feature of Windows kernel [33]. This article focuses on NonPaged pool layout as PagedPool handling is totally different. NonPaged pool can be more or less considered as following a typical heap implementation. Global information about system pool can be found in Microsoft Windows Internals [34]. -----[ 3.1.2 - NonPaged pool tables The allocation algorithm must be fast allocating on the most used sizes. That why three different tables exist and each one is devoted to a size range. We found this organization in most memory management algorithms. Retrieving memory blocks from hardware takes time. Windows balances between response faster and avoid memory wasting. Response time becomes faster if memory blocks are stored for next allocations. In the other hand, if you keep too much memory, it can penalize memory demands. Each table implements a different way to store memory blocks. We will present each table and where you can find them. The NonPaged lookaside is a per-processor table covering size inferior or equal to 256 bytes. Each processor has a processor control register (PCR) storing data concerning only a single processor like IRQL level, GDT, IDT. Its extension called processor control region (PCRB) contains lookasides tables. Next windbg dump presents NonPaged lookaside table and its structure. kd> !pcr KPCR for Processor 0 at ffdff000: Major 1 Minor 1 NtTib.ExceptionList: 805486b0 NtTib.StackBase: 80548ef0 NtTib.StackLimit: 80546100 NtTib.SubSystemTib: 00000000 NtTib.Version: 00000000 NtTib.UserPointer: 00000000 NtTib.SelfTib: 00000000 SelfPcr: ffdff000 Prcb: ffdff120 Irql: 00000000 IRR: 00000000 IDR: ffffffff InterruptMode: 00000000 IDT: 8003f400 GDT: 8003f000 TSS: 80042000 CurrentThread: 80551920 NextThread: 00000000 IdleThread: 80551920 DpcQueue: 0x80551f80 0x804ff29c kd> dt nt!_KPRCB ffdff120 [...] +0x5a0 PPNPagedLookasideList : [32] +0x000 P : 0x819c6000 _GENERAL_LOOKASIDE +0x004 L : 0x8054dd00 _GENERAL_LOOKASIDE [...] kd> dt nt!_GENERAL_LOOKASIDE +0x000 ListHead : _SLIST_HEADER +0x008 Depth : Uint2B +0x00a MaximumDepth : Uint2B +0x00c TotalAllocates : Uint4B +0x010 AllocateMisses : Uint4B +0x010 AllocateHits : Uint4B +0x014 TotalFrees : Uint4B +0x018 FreeMisses : Uint4B +0x018 FreeHits : Uint4B +0x01c Type : _POOL_TYPE +0x020 Tag : Uint4B +0x024 Size : Uint4B +0x028 Allocate : Ptr32 void* +0x02c Free : Ptr32 void +0x030 ListEntry : _LIST_ENTRY +0x038 LastTotalAllocates : Uint4B +0x03c LastAllocateMisses : Uint4B +0x03c LastAllocateHits : Uint4B +0x040 Future : [2] Uint4B Lookaside tables permit faster block retrieving than typical double linked list. For this optimization lock time is really important and a single linked list is a faster mechanism than software locking. ExInterlockedPopEntrySList function is used to pop an entry from a single linked list using hardware locking instruction "lock". PPNPagedLookasideList is the lookaside table we were talking about. It contains two lookaside lists P and L. Depth field of the GENERAL_LOOKASIDE structure defines how many entries can be in ListHead single list. The system updates regularly the depth using different counters. The update algorithm is based on processor number and is different for P and L. Depth of the P list is updated more frequently than L list as it optimizes performances on very small blocks. The second table depends how many processors are used and how system managed them. Allocation system walk it if size is inferior or equal to 4080 bytes or if lookaside research failed. Even if target table can change, it always has the same POOL_DESCRIPTOR structure. On single processor, a variable called PoolVector is used to retrieve NonPagedPoolDescriptor pointer. On multi processor, the ExpNonPagedPoolDescriptor table has 16 slots containing pool descriptors. Each processor PRCB points on a KNODE structure. A node can be linked on more than one processor and contains a color field used as an index in ExpNonPagedPoolDescriptor. Next figures illustrate this algorithm. PoolVector +------------+ | NonPaged | --------------> NonPagedPoolDescriptor |------------+ | Paged | +------------+ [ Figure 1 - Single processor pool descriptor ] Processor #1 +------------+ | | ExpNonPagedPoolDescriptor | PRCB ------\ +-------------------+ | | | /---------> SLOT #01 | +------------+ | | | SLOT #02 | /---------/ | | SLOT #03 | | KNODE | | SLOT #04 | |---> +------------+ | | SLOT #05 | | | Proc mask | | | SLOT #06 | | | color (01) --/ | SLOT #07 | | | ... | | SLOT #08 | | +------------+ | SLOT #09 | | | SLOT #10 | \---------\ | SLOT #11 | Processor #2 | | SLOT #12 | +------------+ | | SLOT #13 | | | | | SLOT #14 | | PRCB ------/ | SLOT #15 | | | | SLOT #16 | +------------+ +-------------------+ [ Figure 2 - Multiple processor pool descriptor ] A global variable ExpNumberOfNonPagedPools defines if multi processor case is used. It should reflect processor number but it can change between operating system versions. The next dump shows POOL_DESCRIPTOR structure from windbg. kd> dt nt!_POOL_DESCRIPTOR +0x000 PoolType : _POOL_TYPE +0x004 PoolIndex : Uint4B +0x008 RunningAllocs : Uint4B +0x00c RunningDeAllocs : Uint4B +0x010 TotalPages : Uint4B +0x014 TotalBigPages : Uint4B +0x018 Threshold : Uint4B +0x01c LockAddress : Ptr32 Void +0x020 PendingFrees : Ptr32 Void +0x024 PendingFreeDepth : Int4B +0x028 ListHeads : [512] _LIST_ENTRY Queued spinlock synchronization, part of HAL library, is used to restrict concurrency on a pool descriptor. It assures that only one thread and one processor will access and unlink an entry from a pool descriptor. HAL library changes on different architectures and what is a simple IRQL raising on single processor becomes a more complex queued system on multi-processor. For default pool descriptor, general NonPaged queued spinlock is locked (LockQueueNonPagedPoolLock). Else, a custom queued spinlock is created. The third and last table is shared by processors for size superior of 4080 bytes. MmNonPagedPoolFreeListHead is also used when others tables lack memory. It composed by 4 LIST_ENTRY each one representing a page number, except for the last one which holds all superiors pages kept by the system. Access to this table is guarded by general non paged queued spinlock also called LockQueueNonPagedPoolLock. During the free procedure of a smaller block, ExFreePoolWithTag merges it with previous and next free blocks. It can create a block superior or equal to 1 page. In this case, the new block is added in the MmNonPagedPoolFreeListHead table. -----[ 3.1.3 - Allocation and free algorithms Kernel allocation does not change that much between OS versions but its algorithm is as hard as the userland heap one. In this part, we want to illustrate basic behavior between tables during allocation or free procedures. A lot of details have been thrown away such as synchronization mechanisms. Those algorithms will help you for the technique explanation but also understanding the basic elements of kernel allocation. Despite kernel exploitation is not part of this paper, pool overflow is an interesting topic that needs understanding of some part of this algorithm. NonPaged pool allocation algorithm (ExAllocatePoolWithTag): IF [ Size > 4080 bytes ] [ - Call the MiAllocatePoolPages function - Walk MmNonPagedPoolFreeListHead LIST_ENTRY table. - Retrieve memory from hardware if necessary. - Return memory page aligned (without header). ] IF [ Size <= 256 bytes ] [ - Pop entry from PPNPagedLookasideList table. - If something is found return memory block. ] IF [ ExpNumberOfNonPagedPools > 1 ] - PoolDescriptor from ExpNumberOfNonPagedPools and used index comes from PRCB KNODE color. ELSE - PoolDescriptor is PoolVector first entry, designed by symbol as NonPagedPoolDescriptor. FOREACH [ >= Size entry of PoolDescriptor.ListHeads ] [ IF [ Entry is not empty ] [ - Unlink entry and split it if needed - Return memory block ] ] - Call the MiAllocatePoolPages function - Walk MmNonPagedPoolFreeListHead LIST_ENTRY table.. - Split it correctly to the right size - Return new memory block NonPaged pool free algorithm (ExFreePoolWithTag) : IF [ MemoryBlock is page aligned ] [ - Call the MiFreePoolPages function - Determine block type (Paged or NonPaged) - Depending on how many blocks are kept in MmNonPagedPoolFreeListHead, we release it to the hardware. ] ELSE [ - Merge previous and next block if possible IF [ NewMemoryBlock size <= 256 bytes ] [ - Look at PPNPagedLookasideList entry depth and see if we should keep it. - We return if memory block is pushed into lookaside list ] IF [ NewMemoryBlock size <= 4080 bytes ] [ - Use POOL_HEADER PoolIndex variable to determine which PoolDescriptor must be used. - Insert it in the proper LIST_ENTRY array entry - If anything goes well, return ] - Depending on how many blocks are kept in MmNonPagedPoolFreeListHead, we release it to the hardware. ] Paged pool algorithm is very different especially for page aligned blocks. Smaller size management should be not that far from NonPaged but in assembly code we definitely saw that NonPaged and Paged pool are totally separated. Once you know a little more about how NonPaged allocation works, we can now talk about exploitation part. ---[ 3.2 - Getting code execution abusing allocation code Our main goal is getting code execution on every allocation attempts for NonPaged pool only. This result must be done only by changing data used by targeted code. Our purpose is proving that kernel code can serve our interest only by changing typical data environment. Our work is based on a new rootkit developed to gain control over NonPaged allocation. We start with getting code execution for allocation superior or equal to 1 page. As we saw on previous part, it concerns the third and last table. -----[ 3.2.1 - Data corruption of MmNonPagedPoolFreeListHead MmNonPagedPoolFreeListHead conserves page aligned memory blocks to speed up memory allocation. It links held memory block using a LIST_ENTRY structure. This structure is common and use in Windows heap library for example. kd> dt nt!_LIST_ENTRY +0x000 Flink : Ptr32 _LIST_ENTRY +0x004 Blink : Ptr32 _LIST_ENTRY MmNonPagedPoolFreeListHead access is protected by general NonPaged queued spinlock LockQueueNonPagedPoolLock. It assures that only one thread and processor can look and modify this structure. So we need a way to get control over allocation and unlinking procedure seems perfect. We can poison this linked list with a fake entry, with the highest size possible, which unlinking will modify current executed code. At kernel level, you can modify code as data without any protection issues. Unlinking was used when heap exploitation started [20] but modifying code was not possible from userland. As spinlock assures us exclusivity, there is no risk on some race condition. The created "hook" would be dynamic and code restored directly. Page guard protection reverse [18] shows that code is only checked every 5 minutes. Whether a modification is found, real code is just replaced. This method has plenty assets but also a lot of obstacles. Let start by enumerating all those obstacles : - On a basic implementation of unlinking, list become unwalkable. It breaks most utilization of the table. - Pass through page cleaning methods and always be the first block on the list otherwise we could miss some call. - We break code path and sooner or later we must return as if our hijacking has never been there and everything goes fine. - Processor prefetch make self code modification dangerous. Unlinking gives us 4 bytes overwriting to build an opcode and create a redirection. In our case, we influenced current context and a register should point to the unlinked entry. We said should point without choosing a single register because kernel changes between versions or service packs. As soon as we discuss context, we will stay talking about general situations. We choose to make a jmp [reg+XX] which is FF60XX in hex. This technique effectiveness lies on keeping the MmNonPagedPoolFreeListHead walkable. A double linked list, as LIST_ENTRY, is walkable if Flink is correct. Therefore we can choose an address for Flink as 0xXXXX60FF and Blink will point to the code address. The Intel x86 architecture using little endian our address is quite easy to found, we must check opcode offset and discard too close possibilities. Next figure illustrates a poisoned entry. MmNonPagedPoolFreeListHead[i] /------> +--------------------+ | | Flink | ---\ | |--------------------| | | <---- | Blink | | | +--------------------+ | | | ... | | | +--------------------+ | | /-------------------------------/ | | | | Poisoned entry | | +--------------------+ | | | PreviousSize : - | | | +--------------------+ | | | PoolIndex : - | | | +--------------------+ | | | PoolType: NonPaged | | | +--------------------+ | | | BlockSize : i | | | +--------------------+ | | | PoolTag : - | | \---> +--------------------+ | | Flink : 0xYYXX60FF | <--\ | |--------------------| | | X--- | Blink : 0x80YYYYYY | | | +--------------------+ | | | | /-------------------------------/ | | Fake entry (0xYYXX60FF) | | +--------------------+ | | | PreviousSize : - | | | +--------------------+ | | | PoolIndex : - | | | +--------------------+ | | | PoolType: NonPaged | | | +--------------------+ | | | BlockSize : < i | | | +--------------------+ | | | PoolTag : - | | |---> +--------------------+ | | | Flink : 0x80..... | ---\ | | |--------------------| | | \---- | Blink : Poisoned | | | +--------------------+ | \--------------- [...] ------------/ Unlinking instruction : mov [0x80YYYYYY], 0xYYXX60FF New Opcode after unlinking : jmp [reg+XX] (FF 60 XX) [ Figure 3 - Poisoned double linked list ] This figure shows a MmNonPagedPoolFreeListHead entry layout that assures predicted unlinking and then code execution. We must maintain this layout or we will lose our position. NonPaged blocks come from two different virtual memory ranges. The second memory region start is stored in MmNonPagedPoolExpansionStart. A cleaning function is called sometime to free blocks from the expansion NonPaged pool. To avoid this cleaning, we can use a Paged pool block locked. You can lock a memory block with the MmProbeAndLockPages function. This lock makes described memory region as resident. Another more discreet way is to remap a NonPaged block with the function MmMapLockedPagesSpecifyCache. It is more discrete because this mapping would be just before expansion NonPaged pool memory range. Using a locked Paged pool block creates an address totally differently. A quick look at those addresses between NonPaged ones show a clear difference. As virtual memory is very large, it does not take too much time to find an address like 0xYYXX60FF. We will not unlock those pages until our technique is running. To defeat code path issues we differentiate two different states. The first state is when our block is selected. The second state is when our block is unlinked. If we were able to return to the first step with our next fake entry selected, we could continue walking code as normal. We achieve that by using a generic approach. At IRQL equal to DISPATCH_LEVEL, we corrupt a MmNonPagedPoolFreeListHead entry with some invalid pointers. With a hook on the page fault handler we are capable to see first and second stages, restore the right context each time and save context difference between those states. Assembly dump from MiAllocatePoolPages : lea eax, [esi+8] ; Stage #1 esi is selected block and esi+8 its size cmp [eax], ebx ; Check with needed size mov ecx, esi jnb loc_47014B [...] loc_47014B: sub [esi+8], ebx mov eax, [esi+8] shl eax, 0Ch add eax, esi cmp _MmProtectFreedNonPagedPool, 0 ; Protected mode, don't care mov [ebp+arg_4], eax jnz short loc_47016E mov eax, [esi] ; \ Stage #2 mov ecx, [esi+4] ; | Unlinking mov [ecx], eax ; | procedure mov [eax+4], ecx ; / jmp short loc_470174 Now let's see how it works during our test technique with interrupt fault handler (int 0xE) hooked : lea eax, [esi+8] ; Stage #1 - Check with needed size cmp [eax], ebx ; ----> PAGE FAULT esi = 0xAAAAAAAA | eax = esi + 8 ; - We keep EIP and all registers ; - Scan all registers for 0xAAAAAAAA +/- 8 ; and correct the current context. Continue. mov ecx, esi jnb loc_47014B [...] loc_47014B: sub [esi+8], ebx mov eax, [esi+8] shl eax, 0Ch add eax, esi cmp _MmProtectFreedNonPagedPool, 0 ; Protected mode, don't care mov [ebp+arg_4], eax jnz short loc_47016E mov eax, [esi] ; \ Stage #2 - Unlinking procedure mov ecx, [esi+4] ; | mov [ecx], eax ; | ------> PAGE FAULT ecx = 0xBBBBBBBB ; | eax = 0xCCCCCCCC ; | - Keep EIP and sub this context from ; | Stage #1 saved context ; | - Change fault registers and ; | structure pointers. Continue. mov [eax+4], ecx ; / jmp short loc_470174 Fault addresses 0xAAAAAAA, 0xBBBBBBBB and 0xCCCCCCCC must point on invalid addresses to force a caught page fault. This test is made only once and when we still have exclusivity on all processors. The int 0xE (page fault) handler is restored just after. This generic technique permits us to restore a valid context just before selected block size is checked. Once we get code execution, we apply context difference, change the current block register and then return at first stage address. It works well because our two stages are very close, once a selected block size is checked, unlinking is directly made. Given examples were based on a single LIST_ENTRY of the MmNonPagedPoolFreeListHead table but you must poison all entries. If a given entry is empty (except for our fake blocks), the algorithm tries next the entry. It means we will be called more than one time per allocation. We created a mechanism to manage multiple call on a single allocation. If the first entry is empty, the second entry is used and so on. Then we will be called twice or more. By checking current table, we can predict a future code execution on the same allocation and avoid executing payload more than one time per allocation request. Prefetch is a processor feature that retrieves more than a single instruction from memory before it executes them. Some processor use a complex branch prediction algorithm to fetch as much instruction as possible. After some tests, we saw that processors invalidate code cache when a modification occurs in cached memory addresses. Our driver supports a case where code modification could be right after current instruction. To achieve that we created a routine which calculates prefetch cache size and consider it in next parts of our technique. We could also search specifics instructions which clean prefetch cache like a far jump but it can only be used as an option. This technique gives us code execution for NonPaged allocation superior or equal to 1 page. It achieves that with a stealth hook, created by kernel code and cleaned by our routine directly after. It's far from being perfect as those allocations are not used that much. Next part describes how this technique can be extended to gain control over all NonPaged pool allocations. -----[ 3.2.2 - Expend it for every size Others lists can not be hijacked the same way because synchronization mechanisms are not exclusive. Changing some assembly code becomes tricky if it can be executed by more than one thread at a time. Our method is assuring our previous technique execution on any allocation. Once we have control, we can find a way to retore ExAllocatePoolWithTag context with a correct return value. We must do that without recoding a single line of memory allocator. It is possible to create our own allocator but Windows one is great and it will perfectly do the job for us. During allocation, the lookaside list is checked first. It will pop an entry and if this entry is not NULL, use it. This entry comes from GENERAL_LOOKASIDE ListHeader field. This field structure is SLIST_HEADER. kd> dt nt!_SLIST_HEADER . +0x000 Alignment : Uint8B +0x000 Next : +0x000 Next : Ptr32 _SINGLE_LIST_ENTRY +0x004 Depth : Uint2B +0x006 Sequence : Uint2B The ExInterlockedPopEntrySList function pops an entry from a SLIST_HEADER structure. The Next field is a pointer to the next SLIST node (single linked list). The Depth field represents how many entries are kept in the list. ExFreePoolWithTag compare GENERAL_LOOKASIDE optimal depth with current SLIST_HEADER depth. ExAllocatePoolWithTag does not check this field and just looks if some entry can be popped out Next field. To stunt allocation and free procedure on NonPaged lookaside table, we set Next field to NULL and Depth field to 0xFFFF. This state will be preserved and this table will not be used anymore. Our technique expansion relies entirely on subverting how the ExpNonPagedPoolDescriptor table is used. In the previous part, we explained global variable ExpNumberOfNonPagedPools involvement in this process. It is possible to expand number of NonPaged pools and then play with current KNODE color. During allocation, the KNODE color defines which pool descriptor is used. Then during free procedure, PoolIndex field of POOL_HEADER keep pool descriptor color. So we can use this nice feature to our advantage. Default KNODE color on every processors would point on an empty pool descriptors. It will lead to code execution using our base technique. If the function MiAllocatePoolPages return address is not the one use for classical page rounded allocation, we know that a smaller allocation occur. All we have to do is switch PRCB KNODE pointer to a copy with custom color and recall ExAllocatePoolWithTag. Everything related to allocation and block management will be implemented as it needs to be even if it differs between operating system versions. Returned blocks PoolIndex will point to our own pool descriptor and free procedure, which will perfectly work. Lets see how it will look on a single processor. ExpNonPagedPoolDescriptor +-------------------+ | PREVIOUS POOLDESC | <--- Kept for compatibility (0) | EMPTY POOLDESC | <--- Default KNODE->color (1) | -- | | -- | | -- | | -- | | -- | | -- | | -- | | -- | | -- | | -- | | -- | | -- | | -- | | CUSTOM POOLDESC | <--- Used for our allocations (16) +-------------------+ [ Figure 4 - Corrupted ExpNonPagedPoolDescriptor ] [ on single processor ] This setup is just an example and you can manage the arrangement as you want. We could transfer previous blocks from older pool descriptors in our own and then receive free blocks. It is also possible to use multiple pool descriptors and so on. Beware of system pool descriptor recycling as it can leads to strange behavior specially on multi-processor architecture. Once we have our fresh allocated block, we must return at ExAllocatePoolWithTag return address. MiAllocatePoolPages has been called to retrieve a new page and fill the current pool descriptor with it. It's obvious that we can't return normally and let page allocation occurs. On Intel x86 architecture the stack is used to store local variables, arguments and saved registers. The Windows compiler starts by reserving local variable and then pushes each register before its modification. The next figure shows our stack configuration once we have code execution. top +--------------------+ | Our stack elements | Restore assembly example +--------------------+ <------ /---------------\ | | | pop ecx | | Saved registers | | pop ebx | | | | pop esi | +--------------------+ | leave | | | | retn 0Ch | | | \---------------/ | | | | | | | Stack variables | | | | | | | | | | | +--------------------+ [new stack level] | Saved EBP | | +--------------------+ | | Return Address | | +--------------------+ | | | | | Function arguments | | | | | +--------------------+ <--------------/ bottom [ Figure 5 - Stack context after code execution ] [ ~ small blocks case ~ ] The restore assembly part shows correct assembly in current function which perfectly restores the context. It does not correspond of the first series of pop instruction before return. There is an important risk that some register has not been pushed yet. It is possible to deduce the pushed register number by looking at function prologue when stack variables are reserved. In the Windows compiler, it's quite simple and we can easily calculate the pushed register number. A simple disassembly analysis on needed pop register number does the job. It must be done for MiAllocatePoolPages and ExAllocatePoolWithTag. We change the return address stored in the stack and go to the deduced MiAllocatePoolPages address. Last step is setting eax register for the return value. Both functions return a value and preserve eax value. Our analyzer is dynamic and registers each pop and its register. That why we can restore the proper context even if it changes between versions. The Windows compiler is really easy to predict and does not create too strange assembly organization. This technique is theoretically possible on every assembly code that follow stdcall specification. The approach could differ on others compilers. ---[ 3.3 Exploit our position This article present a way of subverting the Windows kernel by modifying only data. No function pointers, no static hooking or others classical technique. It could exempt us of any other explanation. But it would not be complete without some concrete examples. I personally believe that the only limitation here is imagination. -----[ 3.3.1 Generic stack redirection Allocation occurs in so many places that you must rely on known context and functions. Once everything is setup and before releasing exclusivity, some stack redirection database can be created. The first way to do this is calling a handler if stack backtracing reveals a specific function. Stack backtracing shows only return addresses and not which function call it. Debuggers resolve those functions by deep analysis or symbol checking. Implementing those features would take too much time. So it's better to target a specific return address on ExAllocatePoolWithTag stack frame. It will definitely improve check speed. To do that, we indicates to our stack redirection API that we target a specific function. Then launch a normal call or procedure that will lead to our function. Every allocation during this time will show important backtrace stacks. Let say, we target an IRP and we know which function handles it by looking at IRP dispatch table. We also know by reversing that it will allocate a NonPaged block. Launching an I/O request, our API could register some NonPaged call and recognize later. In the wild, it will call the appropriate handler with sub context information. Sometimes getting a context is not enough. The second way stays on same principles but modifies the stack to assure our handler is called once the function end. Efficiency depends on what is your target and how you modifying it. -----[ 3.3.2 Userland process code injection This technique can be also used to inject code in userland to subvert trusted applications. NonPaged allocation occurs a lot in kernel mode and it happens in every process. Some kernel drivers like win32k.sys call userland many times. This call is achieve by the function KeUserModeCallback [35]. It modifies userland stack to switch temporarily for a call in userland. Available functions are limited by a table. Userland injection from kernel should not be resident and only concern known trusted application as browsers. Injection can be done on explorer.exe as well to launch an hidden instance of a trusted program. KeUserModeCallback algorithm can be easily remade or copied then relocated.Redirection table could be subverted to redirect the call. We can also think about exploiting userland calls. It does not make any sense to add checks on those available functions. --[ 4 - Detection This article does not try to convince you that subverting IDT or allocation mechanism using advanced technique is the future. Most detection tools only indicate if a rookit may or may not be in this computer. It has pains identifying which module is responsible. It detects antivirus or firewall as rootkits. A protection layout could detect itself as a rootkit because it does everything a rootkit does and so does not ask it to block or uninstall a rootkit. Rootkit papers demonstrate so many great ways to easily bypass those protections. But we don't see much those techniques in the wild, simply because rootkits don't need them for the moment. Detect software behavior modification could be part of a Verifiable Operating System [36]. It will involve basic checks on known memory structures. Checks integrity of LIST_ENTRY structures and correct them if needed. We can blame rootkit protections as much as we want but detecting rootkits on a closed operating system is almost impossible. Gives more information for kernel components will certainly leads to more sofisticates attacks. In the other hand, it could reduce attack surface. It is specially true on a defence oriented operating system. Next protection improvements should come from the operating system itself. Now that there are hardware improvements for virtualisation, such as hypervisors, there will be extensions to hardware to detect and protect against rootkits. It offers a real control on operating system behavior without advanced research on kernel layout. Some protections techniques that were impossible to implement in Windows environment like PAX, could rely on those hardware features. Our techniques could be detected by registering and monitoring some specific events on the processor. It is possible today to do that but performance issues are important. Our attacks could be blocked using targeted protection such as signatures. An attack is defined as how many times it takes to create a generic protection. In this area, Patchguard is an important improvement. --[ 5 - Conclusion This papers techniques were made to show that elegant software hijacking can still evades most protections and avoid any performance issues or unstable behaviors. Even though, these techniques are hardly reliable and should be considered only as a technical proof of concept. New protections are not efficient enough or present. They do not represent a threat for a rootkit which targets millions of computers. Reversing is an important tool in improving software rootkits techniques. Detecting that a rootkit is present should not be enough. A protection which cannot uninstall a rootkit or prevent infection is useless. Drivers signatures was a good idea as it was designed to stop current infections entries. But infection prevention includes local kernel exploitation. Generic detection of those attacks would need an important improvement in anti-rootkits protections and operating system design. --[ 6 - References [1] Holy Father, Invisibility on NT boxes, How to become unseen on Windows NT (Version: 1.2) http://vx.netlux.org/lib/vhf00.html [2] Holy Father, Hacker Defender https://www.rootkit.com/vault/hf/hxdef100r.zip [3] 29A http://vx.netlux.org/29a [4] Greg Hoglund, NT Rootkit https://www.rootkit.com/vault/hoglund/rk_044.zip [5] fuzen_op, FU http://www.rootkit.com/project.php?id=12 [6] Peter Silberman, C.H.A.O.S, FUto http://uninformed.org/?v=3&a=7 [7] Eeye, Bootroot http://research.eeye.com/html/tools/RT20060801-7.html [8] Eeye, Pixie http://research.eeye.com/html/papers/download/ eEyeDigitalSecurity_Pixie%20Presentation.pdf [9] Joanna Rutkowska and Alexander Tereshkin, Blue Pill project http://bluepillproject.org/ [10] Frank Boldewin, A Journey to the Center of the Rustock.B Rootkit http://www.reconstructer.org/papers/ A%20Journey%20to%20the%20Center%20of%20the%20Rustock.B%20Rootkit.zip [11] Frank Boldewin, Peacomm.C - Cracking the nutshell http://www.reconstructer.org/papers/ Peacomm.C%20-%20Cracking%20the%20nutshell.zip [12] Stealth MBR rootkit http://www2.gmer.net/mbr/ [13] EP_X0FF and MP_ART, Unreal.A, bypassing modern Antirootkits http://www.rootkit.com/newsread.php?newsid=647 [14] AK922 : Bypassing Disk Low Level Scanning to Hide File http://rootkit.com/newsread.php?newsid=783 [15] CardMagic and wowocock, DarkSpy http://www.fyyre.net/~cardmagic/index_en.html [16] pjf, IceSword http://pjf.blogone.net [17] Gmer http://www.gmer.net/index.php [18] Pageguard papers (Uniformed) : - Bypassing PatchGuard on Windows x64 by skape & Skywing http://www.uninformed.org/?v=all&a=14&t=sumry - Subverting PatchGuard Version 2 by Skywing http://www.uninformed.org/?v=all&a=28&t=sumry - PatchGuard Reloaded: A Brief Analysis of PatchGuard Version 3 by Skywing http://www.uninformed.org/?v=all&a=38&t=sumry [19] Greg Hoglund, Kernel Object Hooking Rootkits (KOH Rootkits) http://www.rootkit.com/newsread.php?newsid=501 [20] Windows Heap Overflows - David Litchfield http://www.blackhat.com/presentations/win-usa-04/bh-win-04-litchfield/ bh-win-04-litchfield.ppt [21] Bypassing Klister 0.4 With No Hooks or Running a Controlled Thread Scheduler by 90210 - 29A http://vx.netlux.org/29a/magazines/29a-8.rar [22] Microsoft, Debugging Tools for Windows http://www.microsoft.com/whdc/devtools/debugging/default.mspx [23] Kad, Phrack 59, Handling Interrupt Descriptor Table for fun and profit http://phrack.org/issues.html?issue=59&id=4#article [24] Wikipedia, Southbridge http://en.wikipedia.org/wiki/Southbridge_(computing) [25] Wikipedia, Northbridge http://en.wikipedia.org/wiki/Northbridge_%28computing%29 [26] The NT Insider, Stop Interrupting Me -- Of PICs and APICs http://www.osronline.com/article.cfm?article=211 (login required) [27] Russinovich, Solomon, Microsoft Windows Internals, Fourth Edition Chapter 3. System Mechanisms -> Trap Dispatching [28] MSDN, KeyboardClassServiceCallback http://msdn2.microsoft.com/en-us/library/ms793303.aspx [29] Clandestiny, Klog http://www.rootkit.com/vault/Clandestiny/Klog%201.0.zip [30] Alexander Tereshkin, Rootkits: Attacking Personal Firewalls www.blackhat.com/presentations/bh-usa-06/BH-US-06-Tereshkin.pdf [31] MSDN, NdisMIndicateReceivePacket http://msdn2.microsoft.com/en-us/library/aa448038.aspx [32] Subverting VistaTM Kernel For Fun And Profit by Joanna Rutkowska http://invisiblethings.org/papers/ joanna%20rutkowska%20-%20subverting%20vista%20kernel.ppt [33] Vista RC2 vs. pagefile attack by Joanna Rutkowska http://theinvisiblethings.blogspot.com/2006/10/ vista-rc2-vs-pagefile-attack-and-some.html [34] Russinovich, Solomon, Microsoft Windows Internals, Fourth Edition Chapter 7. Memory Management -> System Memory Pools [35] KeUserModCallback ref - "Ring0 under WinNT/2k/XP" by Ratter - 29A http://www.illmob.org/files/text/29a7/Articles/29A-7.003 [36] Joanna Rutkowska - Towards Verifiable Operating Systems http://theinvisiblethings.blogspot.com/2007/01/ towards-verifiable-operating-systems.htm