This document describes techniques for creating rootkits on Linux x86 systems. It discusses obtaining the system call table, hooking system calls through various methods like direct modification of the table, inline hooking of system call code, and patching the system call handler. It also presents the idea of abusing debug registers to generate exceptions and intercept system calls. The goal is to conceal running processes, files, and other system data from detection.
3. Index
Rootkit In Brief
๎
Rootkit based on LKM
๎
How to get sys_call_table
๎
Simple sys_call_table hook
๎
Inline hook
๎
Patching system_call
๎
Abuse Debug Registers
๎
Real Rootkit
๎
Rootkit based non-LKM
๎
Using /dev/kmem and kmalloc
๎
Using /dev/mem and kmalloc
๎
4. Rootkits In Brief
โA rootkit is a set of software tools intended to
๎
conceal running processes, files or system data
from the operating systemโฆ Rootkits often
modify parts of the operating system or install
themselves as drivers or kernel modules. โ
Rootkit, Trojans, Virus, Malware?
๎
Now, they often bind together, be called malware.
๎
5. Rootkits' Category
UserSpace Rootkit
๎
Run in user space
๎
Modify some files,libs,config files, and so on.
๎
KernelSpace Rootkit
๎
Run in kernel space
๎
Modify kernel structures, hook system calls at the
๎
lowest level
6. Rootkits' Common Function
Hide Process
๎
Hide File
๎
Hide Network Connection
๎
Back Door
๎
Key Logger
๎
7. Rootkits' Key words
Hijack
๎
Hook
๎
System call
๎
sys_call_table
๎
sysenter
๎
IDT
๎
Debug Register
๎
8. Rootkits based on LKM
How to get sys_call_table
๎
Simple sys_call_table hook
๎
Inline hook
๎
Patching system_call
๎
Abuse Debug Registers
๎
Real Rootkit
๎
10. How to get sys_call_table
Historically, LKM-based rootkits used the
๎
โsys_call_table[]โ symbol to perform hooks on the
system calls
sys_call_table[__NR_open] = (void *) my_func_ptr;
๎
However, since sys_call_table[] is not an exported
๎
symbol anymore, this code isnโt valid
We need another way to find โsys_call_table[]โ
๎
11. How to get sys_call_table
The function โsystem_callโ makes a direct
๎
access to โsys_call_table[]โ
(arch/i386/kernel/entry.S:240)
call *sys_call_table(,%eax,4)
๎
In x86 machine code, this translates to:
๎
0xff 0x14 0x85 <addr4> <addr3> <addr2> <addr1>
๎
Where the 4 โaddrโ bytes form the address of
๎
โsys_call_table[]โ
12. How to get sys_call_table
Problem: โsystem_callโ is not exported too
๎
Itโs not, but we can discover where it is!
๎
โsystem_callโ is set as a trap gate of the system
๎
(arch/i386/kernel/traps.c:1195):
set_system_gate(SYSCALL_VECTOR,&system_call);
๎
In x86, this means that its address is stored inside the
๎
Interrupt Descriptor Table (IDT)
The IDT location can be known via the IDT register
๎
(IDTR)
And the IDTR, finally, can be retrieved by the SIDT
๎
(Store IDT) instruction
13. How to get sys_call_table
Steps to get sys_call_table
๎
Get the IDTR using SIDT
๎
Extract the IDT address from the IDTR
๎
Get the address of โsystem_callโ from the 0x80th entry of the
๎
IDT
Search โsystem_callโ for our code fingerprint
๎
We should have the address of โsys_call_table[]โ by now,
๎
have fun!
23. Detect simple sys_call_table hook
and inline hook
Detections
๎
Saves the addresses of every syscall
๎
Saves the checksums of the first 31 bytes of every
๎
syscallโs code
Saves the checksums of these data themselves
๎
Now you canโt change the addresses in the
๎
system call table
Also canโt patch the system calls with jmpโs to
๎
your hooks
More Tricks......
๎
24. Patching system_call
How to hook all syscalls, without modify
๎
sys_call_table and IDT? You can modify int
0x80's handler(system_call), and manage the
system calls directly.
25. system_call
---- arch/i386/kernel/entry.S ----
# system call handler stub
ENTRY(system_call)
pushl %eax # save orig_eax
SAVE_ALL
GET_THREAD_INFO(%ebp)
cmpl $(nr_syscalls), %eax ---> Those two instrutions will replaced by
jae syscall_badsys ---> Our Own jump
# system call tracing in operation
testb $_TIF_SYSCALL_TRACE,TI_FLAGS(%ebp)
jnz syscall_trace_entry
syscall_call:
call *sys_call_table(,%eax,4)
movl %eax,EAX(%esp) # store the return value
....
---- eof ----
30. Detect system_call hook
Trick 1: Copy the system call table and patch
๎
the proper bytes in โsystem_callโ with the new
address
This can be avoided by having St. Michael making
๎
checksums of โsystem_callโ code too
Trick 2: Copy โsystem_callโ code, apply Trick 1
๎
on it, and modified the 0x80th ID in the IDT with
the new address
This can be avoided by having St. Michael storing
๎
the address of โsystem_callโ too
31. Abuse Debug Registers
DBRs 0-3: contain the linear address of a
๎
breakpoint. A debug exception (# DB) is
generated when the case in an attempt to
access at the breakpoint
DBR 6: lists the conditions that were present
๎
when debugging or breakpoint exception was
generated
DBR 7: Specifies forms of access that will result
๎
in the debug exception to reaching breakpoint
33. Evil Ideas of abusing debug
Registers
Based on the above far this allows us to
๎
generate a #DB when the cpu try to run code
into any memory location at our choice, even a
kernel space
Evil Ideas
๎
Set breakpoint on system_call(in 0x80's handler)
๎
Set breakpoint on sysenter_entry(sysenter handler)
๎
OMG, every syscall will be hooked!
๎
34. The #DB also be managed through IDT
๎
ENTRY(debug)
๎
pushl $0
pushl $SYMBOL_NAME(do_debug)
๎
jmp error_code
fastcall void do_debug(struct *pt_regs,int errorcode)
๎
At the moment we can then divert any
๎
flow execution kernel to do_debug,
without changing a single bit of text
segment!
35. Some breakpoint code
/* DR2 2nd watch on the syscall_table entry for this syscall */
dr2 = sys_table_global + (unsigned int)regs->eax * sizeof(void *);
/* set dr2 read watch on syscall_table */
__asm__ __volatile__ ( quot;movl %0,%%dr2 ntquot;
:
: quot;rquot; (dr2) );
/* get dr6 */
__asm__ __volatile__ ( quot;movl %%dr6,%0 ntquot;
: quot;=rquot; (status) );
/* enable exact breakpoint detection LE/GE */
s_control |= TRAP_GLOBAL_DR2;
s_control |= TRAP_LE;
s_control |= TRAP_GE;
s_control |= DR_RW_READ << DR2_RW;
s_control |= 3 << DR2_LEN;
/* set new control .. gives up syscall handler to avoid races */
__asm__ __volatile__ ( quot;movl %0,%%dr6 ntquot;
quot;movl %1,%%dr7 ntquot;
:
: quot;rquot; (status), quot;rquot; (s_control) );
36. Hook do_debug
When hardware breakpoints appear, kernel will
๎
call do_debug(). BUT orignal do_debug() not
set eip to our evil func. So we must hook
do_debug() ourself.
It means that INT 1 of IDT, will be set
๎
hacked_do_debug() insead of do_debug().
37. Find do_debug
KPROBE_ENTRY(debug)
RING0_INT_FRAME
cmpl $sysenter_entry,(%esp) <- find sysenter_entry here too!
jne debug_stack_correct
FIX_STACK(12, debug_stack_correct, debug_esp_fix_insn)
debug_stack_correct:
pushl $-1 # mark this as an int
CFI_ADJUST_CFA_OFFSET 4
SAVE_ALL
xorl %edx,%edx # error code 0
movl %esp,%eax # pt_regs pointer
call do_debug <- PATCH ME!
jmp ret_from_exception
CFI_ENDPROC
KPROBE_END(debug)
39. Debug register hook
From hacked_do_debug can then have access
๎
to the value of eip representing the return
address on the person who triggered the
breakpoint, which is the kernel in our case, so
you can change at will the flow of execution
after the procedure!
Changing eip can send a running our routine
๎
that once made the 'dirty work' take care to
restore the original flow of execution
regs->eip = (unsigned int)hook_table[regs->eax];
41. hacked_do_debug
hacked_do_debug():
/* get dr6 */
__asm__ __volatile__ ( quot;movl %%dr6,%0 ntquot; : quot;=rquot; (status) );
......
/* check for trap on dr2 */
if (status & DR_TRAP2)
{
trap = 2;
status &= ~DR_TRAP2;
}
......
if ((regs->eax >= 0 && regs->eax < NR_syscalls) && hook_table[regs->eax])
{
/* double check .. verify eip matches original */
unsigned int verify_hook = (unsigned int)sys_p[regs->eax];
if (regs->eip == verify_hook)
{
// ่ฟ้่ฎพ็ฝฎไธไธๆญฅ็ๆง่กๅฝๆฐ
regs->eip = (unsigned int)hook_table[regs->eax];
DEBUGLOG((quot;*** hooked __NR_%d at %X to %Xnquot;, regs->eax, verify_hook,
(unsigned int)hook_table[regs->eax]));
}
}
......
42. More Evil
We modified do_debug(), if someone check the
๎
address of do_debug(), will find the rootkit.
Wait, if we set another hardware breakpoint on
๎
do_debug()'s address. It means that we can
return someone the wrong address of
do_debug(), and can not be detected.
Debug Regiser Save us again :-)
๎
43. Real Rootkit
Strace system call
๎
Hook system call
๎
Hide rootkit self
๎
45. Hook system call
Have be discussed Previously
๎
Simple sys_call_table hook
๎
Inline hook
๎
Patching system_call
๎
Abuse Debug Registers
๎
46. Hide module & network
struct module *m = &__this_module;
/* Delete from the module list*/
if (m->init == init_module)
list_del(&m->list);
struct proc_dir_entry *tcp = proc_net->subdir->next;
/* redefine tcp4_seq_show() */
while (strcmp(tcp->name, quot;tcpquot;) && (tcp != proc_net->subdir))
tcp = tcp->next;
/* Hide TCP Connection Information in /proc/net/tcp */
if (tcp != proc_net->subdir)
{
orig_tcp4_seq_show = ((struct tcp_seq_afinfo *)(tcp->data))->seq_show;
((struct tcp_seq_afinfo *)(tcp->data))->seq_show = hacked_tcp4_seq_show;
}
47. Rootkits based on non-LKM
Access kernel resource from userspace
๎
through some infrastructure of Linux, Mostly
based on /dev/kmem and /dev/mem.
Famous rootkit
๎
suckit
๎
49. Using kmalloc based non-LKM
code injection
The addresses of the syscall table and of the function kmalloc()
๎
within the kernel are found by searching kernel memory for
certain patterns.
The function kmalloc() is internal to the kernel and needed to
๎
reserve space in kernel memory. The address of kmalloc() is
put into an unused entry of the syscall table.
kmalloc() is executed as a system call and memory in the
๎
kernel is allocated.
The rootkit is written to the freshly reserved space in the kernel.
๎
The address of the rootkit is put into the unused entry of the
๎
syscall table, overwriting the address of kmalloc().
The rootkit is called as a system call and finally running in
๎
kernel mode.
50. Steps of suckit 1.3a
Sidt
๎
read/mmap /dev/kmem
๎
Search fingerprint opcode
๎
Set kmalloc into sys_call_table
๎
Insert rootkit opcode into kernel(Patching)
๎
Hook......
๎
51. Detection of rootkit based
/dev/kmem
Most of distributions have disabled /dev/kmem
๎
feature.
Device Drivers => Character Devices =>
๎
/dev/kmem virtual device support
BUT Debian still has this hole ;-)
๎
Detection:
๎
Using the same steps to check sys_call_table
๎
Using LKM to check sys_call_table
๎
52. /dev/mem
/dev/mem
๎
Driver interface to physically addressable memory.
๎
lseek() to offset in โfileโ = offset in physical mem
๎
EG: Offset 0x100000 = Physical Address 0x100000
๎
Reads/Writes like a regular character device
๎
/dev/mem is same with /dev/kmem
๎
Same techniques with Virt -> Phys address
๎
translation
53. Address Translation
Higher half GDT loading concept applies
๎
Bootloader trick to use Virtual Addresses along
๎
with GDT in unprotected mode to resolve
physical addresses.
Kernel usually loaded at 0x100000 (1MB) in
๎
physical memory
Mapped to 0xC0100000 (3GB+1MB) Virtually
๎
55. /dev/mem's address translation
code
#define KERN_START 0xC0000000
int iskernaddr(unsigned long addr)
{
/*is address valid?*/
if(addr<KERN_START)
return -1;
else
return 0 ;
}
/*read from kernel virtual address*/
int read_virt(unsigned long addr, void *buf, unsigned int len)
{
if( iskernaddr(addr)<0 )
return -1;
addr = addr - KERN_START;
lseek(mem_fd, addr, SEEK_SET);
return read(mem_fd, buf, len);
}
56. Steps of rootkit using /dev/mem
and kmalloc
Sidt
๎
Read /dev/mem [address translation]
๎
Search fingerprint opcode
๎
Set kmalloc into sys_call_table
๎
Insert rootkit opcode into kernel(Patching)
๎
Hook......
๎
57. Detection of rootkit based
/dev/mem
SELinux has created a patch to address this
๎
problem (RHEL and Fedora kernels are safe)
Mainline kernel addressed this from 2.6.26
๎
Kernel Hacking => Filter access to /dev/mem
๎
Detection:
๎
Using the same steps to check sys_call_table
๎
Using LKM to check sys_call_table
๎
59. Reference
LKM Rootkits on Linux x86 v2.6:
๎
http://www.enye-sec.org/textos/lkm.rootkits.en.linux.x86.v2.6.txt
Mistifying the debugger, ultimate stealthness
๎
http://www.phrack.com/issues.html?issue=65&id=8
Advances in attacking linux kernel
๎
http://darkangel.antifork.org/publications/Advances%20in%20attacking%20linux%
Kernel-Land Rootkits
๎
http://www.h2hc.com.br/repositorio/2006/Kernel-Land%20Rootkits.pps
Intelยฎ 64 and IA-32 Architectures Software Developer's Manuals
๎
www.intel.com/products/processor/manuals/
Developing Your Own OS On IBM PC
๎
http://docs.huihoo.com/gnu_linux/own_os/index.htm
Handling Interrupt Descriptor Table for fun and profit
๎
http://www.phrack.org/issues.html?issue=60&id=6
Execution path analysis: finding kernel based rootkits
๎
http://www.phrack.org/issues.html?issue=59&id=10
Malicious Code Injection via /dev/mem
๎