Undermining the Linux Kernel: Malicious Code Injec:on via /dev/mem Anthony Lineberry anthony.lineberry@gmail.com Black Hat Europe 2009
Overview • What is a rootkit? • Why is protec:on difficult? • Current protec:on mechanisms/bypasses • Injec:on via /dev/mem • Fun things to do once you’re in • Proposed solu:ons
Part I Rootkit?
What is a rootkit? • Way to maintain access (regain “root” aVer successful exploita:on) • Hide files, processes, etc • Control ac:vity – File I/O – Network • Keystroke Logger
Types of rootkits • User‐Land (Ring 3) – Trojaned Binaries (oldest trick in the book) • Binary patching • Source code modifica:on – Process Injec:on/Thread Injec:on • PTRACE_ATTACH, SIGNAL injec:on – Does not affect stability of system
Types of rootkits • Kernel‐Land (Ring 0) – Kernel Modules/Drivers – Hot Patching memory directly! (we’ll get to that ;)
Part II Why are rootkits hard to defend against?
Why so hard? • Can control most everything in the system – System Calls cant be trusted – Network traffic – Can possibly detect if you are trying to detect it
Why so hard? • Most modern rootkits live in the kernel • Kernel is God – Imprac:cal to check EVERYTHING inside kernel • Speed hits – Built in security can be circumvented by more kernel code (if an afacker can get code in, game over)
Part III Current Rootkit Defense
Current Defense • Checking Tables in kernel (sys_call_table, IDT, etc) – Compares tables against known good – Can be bypassed by crea:ng duplicate table to use rather than modifying the main table – Typical security cat and mouse game
Current Defense • Hashes/Code Signing – In kernel • Hash cri:cal sec:ons of code • Require signed kernel modules – In userland • Hashes of system binaries – Tripwire, etc • Signed binaries • File System Integrity
Current Defense • Non‐Modularity – Main suggested end all way to stop kernel space rootkits (obviously this is a fail) – /dev/kmem was previously used in a similar fashion, but read/write access has since been closed off in kernel mainline
Part IV Code Injec:on via /dev/mem
What is /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 • Who needs this? – X Server (Video Memory & Control Registers) – DOSEmu
Hijacking the kernel Kernel addressing is virtual. How do we translate to physical addresses?
Address Transla:on • Find a Page Table Directory (stored in cr3 register) – Pros: • Guaranteed to be able to locate any physical page • Mi:gates page alloca:on randomiza:on situa:ons • Allows us to find physical pages of process user space
Address Transla:on • Find a Page Table Directory (stored in cr3 register) – Cons: • Finding one is easier said than done • Heuris:c could be developed for loca:ng PTD in task struct, but there are easier ways.
Address Transla:on • 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
Address Transla:on 0x40000000 GDT Base Address + 0xC0100000 Kernel Virtual Address = 0x00100000 Physical Address
Address Transla:on • Obviously over thinking that… • No need to wrap around 32bit address, just subtract. – 0xC0100000 – 0xC0000000 = 0x100000 • If page alloca:on randomiza:on existed, this trick would not be possible
Hijacking the kernel #define KERN_START 0xC0000000 int read_virt(unsigned long addr, void *buf, unsigned int len) { if(addr < KERN_START) return -1; /* addr is now physical address */ addr -= KERN_START; lseek(memfd, addr, SEEK_START); return read(memfd, buf, len); }
Useful structures • Determine offset to important structures – IDT – sys_call_table – kmalloc() • Where are they?
IDT • Interrupt Descriptor Table (IDT) – Table of interrupt handlers/call gates – 0x80’th handler entry = Syscall Interrupt • What can we do with it? – Replace Interrupt Handlers • Hardware: Network Cards, Disks, etc • SoVware: System Calls,
IDTR • IDTR holds structure with address of IDT – Get/Set IDTR with LIDT/SIDT assembly instruc:ons – Unlike LIDT instruc:on, SIDT is not protected and can be executed from user space to get IDT address. – Wont work in most VM’s • Hypervisors return bogus IDT address
IDTR IDTR Structure Base Address (4 btyes) Limit (2 bytes) struct { uint32_t base; uint16_t limit; } idtr; __asm__(“sidt %0” : “=m”(idtr));
IDT Entry IDT Entry (8 bytes) 0 16 31 Low 16bits of Handler Address Code Segment Selector Flags High 16bits of Handler Address
IDT IDT idtr.base
IDT IDT idtr.base idtr.base + (0x80 * 8) Entry for Syscall Interrupt
IDT IDT idtr.base idtr.base + (0x80 * 8) Entry for Syscall Interrupt system_call()
System Calls • system_call() – Main entry point for system calls • sys_call_table – Array of func:on pointers – sys_read(), sys_write(), etc
System Calls • Syscall Number stored in EAX register call ptr 0x????????(eax,4) – 0x???????? Is the address of sys_call_table • Opcode for instruc:on: FF 14 85 ?? ?? ?? ?? – Read in memory at system_call(), search for byte sequence “\xFF\x14\x85”. Next 4 following bytes are address of sys_call_table!
Hijacking the kernel • Now we can: – Find IDT – Find system_call() handler func:on – Use simple heuris:c to find address of sys_call_table • What now? – Overwrite system calls with our own code!
Hijacking the kernel • Where do we put our code? – Kernel Memory Pool • Traverse malloc headers looking for free blocks • Not atomic opera:on, cant guarantee we’ll beat kernel – Certain “guard pages” in kernel – Allocate space in the kernel • We can locate __kmalloc() inside the kernel and call that
Hijacking the kernel • Finding __kmalloc() – Use heuris:cs push GFP_KERNEL push SIZE call __kmalloc – Find kernel symbol table • Search for “\0__kmalloc\0” in memory • Find reference to address of above sequence then subtract 4 bytes from loca:on
Hijacking the kernel • How can we allocate kernel memory from userspace? – Locate address of __kmalloc() in kernel space – Overwrite a system call with code to call __kmalloc() – Call system call – Someone else could poten:ally call the same system call and cause system instability
Func:on Clobbering sys_uname() Backup Buffer sys_call_table __NR_uname __kmalloc stub push $0xD0 ;GFP_KERNEL push $0x1000 ; 4k mov 0xc0123456, %ecx call %ecx ret
Func:on Clobbering sys_uname() Backup Buffer sys_call_table 100 bytes __NR_uname __kmalloc stub push $0xD0 ;GFP_KERNEL push $0x1000 ; 4k mov 0xc0123456, %ecx call %ecx ret
Func:on Clobbering sys_uname() Backup Buffer sys_call_table 100 bytes __NR_uname __kmalloc stub push $0xD0 ;GFP_KERNEL push $0x1000 ; 4k mov 0xc0123456, %ecx call %ecx ret
Func:on Clobbering sys_uname() Backup Buffer sys_call_table 100 bytes __kmalloc stub __NR_uname
Hijacking the kernel • Call sys_uname() unsigned long kernel_buf; __asm__(“mov $122, %%eax \n” “int $0x80 \n” “mov %%eax, %0 ” : “=r”(kernel_buf)); • Address of buffer allocated in kernel space returned by syscall in EAX register
Part V Fun things to do inside the kernel
Hijacking the kernel • Recap: – read/write anywhere in memory with /dev/mem – sys_call_table – Kernel alloca:on capabili:es – Time to have fun!
Hijacking the kernel • What can we do? – Use our kernel buffers we allocated to store raw executable code. – Overwrite func:on pointers in kernel with address of our allocated buffers • sys_call_table entries, page fault handler code – Setup code to use Debug registers to “hook” system call table
Hijacking the kernel • What can we do with our injected code? – Anything most other rootkits can do. • Hide files, processes, etc • Control network ac:vity • Limita:ons – All injected code must usually be handwrifen assembly – Some structures/func:ons can be difficult to locate in memory
Part V Solu:ons/Mi:ga:on
Solu:ons • Why does a legi:mate user process need access to read anything from above 16k in physical memory? – SELinux has created a patch to address this problem (RHEL and Fedora kernels are safe) – Modifies mem driver to disallow lseeks past 16k
Solu:ons Mainline kernel has addressed this as of 2.6.26!
Solu:ons Mainline kernel has addressed this as of 2.6.26! Sort of…
Solu:ons • Added func:ons in kernel – range_is_alloc() • Checks each page in range of address space being accessed – devmem_is_allowed() • Called by range_is_allowed() • Checks if address is within first 256 pages (1MB)
Recommend
More recommend