Why look at rootkits?

 They solve an interesting software engineering problem

    Maximum effect from minimum code changes

    Maximum code reuse

 They highlight both strengths and weaknesses of OS design


       Even if a Linux kernel is compiled without loadable module support, linking against the running kernel is simple


       The same mechanism that allows driver loading and stacking allows rootkitting.

 They demonstrate architecture-specific systems programming tricks

    IDT modifications

    NDIS driver hooks

    DTLB and ITLB (ShadowWalker by Butler and Sparks)

Ye olde syscall hooking

Based on the "dispatch table" design, an old OS staple

 Windows "system service table"

    maps system call number to actual function address

(from analysis of the Sony DRM rootkit by Mark Russinovich, http://www.sysinternals.com/blog/2005/10/sony-rootkits-and-digital-rights.html)

 Linux sys_call_table (int 0x80)

    [entry.S#L242] [entry.S#L575] -- the system call dispatcher routine

    [knark.c] [rootme.c] http://althing.cs.dartmouth.edu/secref/resources/kernel/rootkits/knark-2.4.3-release/src/ -- start reading from init_module, this function gets executed by the kernel after insmod or modprobe make the corresponding system call and the module gets loaded into the kernel space and linked.

 grep 'T sys_' /boot/System.map shows kernel symbols for syscalls. The Kstat tool and others check these against known good addresses.

 More examples, better design: [adore/]

 More resources: http://althing.cs.dartmouth.edu/secref/resources/kernel/

The return of the Hook (1)

The ease with which sys_call_table modifications can be found made it obsolete. However, this is by far not the only dispatch table!

 Phrack 59:5 http://www.phrack.org/issues.html?issue=59&id=5 showed four extra levels for redirecting execution, on the path from filename to actual binary:

sys_execve -> do_exec -> open_exec -> load_*_binary ...

"classic" -> "obvious" -> "waiter" -> "nexus"

[stories/classic.c] [stories/obvious.c] [stories/waiter.c] [stories/nexus.c]

	 * This structure defines the functions that are
	 * used to load the binary formats that linux
	 * accepts.
	struct linux_binfmt {
		struct linux_binfmt * next;
		struct module *module;
		int (*load_binary)(struct linux_binprm *, \
				struct pt_regs * regs);
		int (*load_shlib)(struct file *);
		int (*core_dump)(long signr, struct pt_regs * regs, \
				struct file * file);
		unsigned long min_coredump;	/* minimal dump size */

...and change the load_binary handler. Or core_dump, for more perplexity.

 intercept execution in dynamic linker, during process setup, by the pattern of mmaps [stories/lord.c]

 # cat /proc/23767/maps
 08048000-080b0000 r-xp 00000000 03:09 12         /bin/bash
 080b0000-080b4000 rw-p 00067000 03:09 12         /bin/bash
 080b4000-08106000 rwxp 00000000 00:00 0
 40000000-40015000 r-xp 00000000 03:09 32657      /lib/ld-2.2.2.so
 40015000-40016000 rw-p 00014000 03:09 32657      /lib/ld-2.2.2.so
 40016000-40017000 r--p 00000000 03:0a 49835      /usr/share/locale/en/LC_IDENTIFICATION
 4001a000-4001b000 r--p 00000000 03:0a 49836      /usr/share/locale/en/LC_NAME

 No need to keep the executable in the filesystem.

The return of the Hook (2)

... or try another set of tables, in Virtual File System layer (VFS). Eliminates the need for syscall pointer modifications.

When a file is read:

sys_read [read_write.c#L312] -> vfs_read [read_write.c#L222]

  ... if (file->f_op->read)
          ret = file->f_op->read(file, buf, count, pos);

So: [adore-ng/adore-ng.c]

Further developments: disk drivers (stackable in Windows)

More points of injection

 Infected loadable kernel modules (LKMs)

    init_module first loads its own code, then the actual module


 The Interrupt Descriptor Table (int 0x80 and others, [entry.S])

    Obtain control on recovery from faults


    Protective patches are also possible: [Kfence.c]

 GRUB, the boot loader (or LILO)

    Fake filesystem contents, map files http://www.phrack.org/issues.html?issue=63&id=10

 Injection into kernels that do not support LKMs

    Write access to /dev/kmem is enough, linking is standard ELF

    Silvio Cesare, http://vx.netlux.org/lib/vsc07.html and several Phrack papers

Back in Windows land

 Syscalls numbers change, only userland API are fixed (kernel32.dll, ntdll.dll)

    makes dynamically linked API hijacking more useful (the Import Address Table, IAT) [butler-hoglund-dc12.ppt] (slides 6--16)

 Different thread/process model affects process hiding concerns

    Scheduling is thread-based, reporting is process-based. Process list is fair game? [butler-hoglund-dc12.ppt] (slides 30,34) "Fu" rootkit, "DKOM"

 Different kernel self-modification conventions

    dummy preamble instructions for easier hooking! (mov edx, edx)

   8b ff   mov edi, edi 
   55      push ebp 
   8b ec   mov ebp, esp

Why? Because the following is exactly 5 bytes:

   e9 xx xx xx xx  jmp xx.xx.xx.xx

    but: very limited in 64bit version http://www.microsoft.com/whdc/driver/kernel/64bitPatching.mspx

 Filter drivers

    Device drivers support stacking in any order.

    Both rootkits and antiviruses can use this (e.g., Sony CD-ROM filter)

Hide-and-seek in Windows land

 Debuggers and monitors used API to examine memory


    ... but it can be hooked to show pristine picture (no detour)

    "Hacker Defender" http://hxdef.czweb.org/. On Windows hooking: http://hxdef.czweb.org/knowhow.php

    Livekd (sysinternals) introduces its own kernel driver, takes a snapshot of kernel memory

More details: Joanna Rutkowska, http://invisiblethings.org, "Rootkits Detection on Windows Systems", "System Virginity Verifier"


 Rootkits must conceal their own code

    Make the scanner see things that are not there

    Subvert virtual memory by desynchronizing ITLB and DTLB

(Images from http://www.securityfocus.com/print/infocus/1851, more details there)

    Presented at BlackHat and Defcon 2005: http://www.blackhat.com/presentations/bh-usa-04/bh-us-04-butler/bh-us-04-butler.pdf