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
Modularity
Even if a Linux kernel is compiled without loadable
module support, linking against the running kernel
is simple
Self-modification
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)
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 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.
... 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)
Infected loadable kernel modules (LKMs)
init_module first loads its own code, then the actual module
http://www.phrack.org/issues.html?issue=61&id=10
The Interrupt Descriptor Table (int 0x80 and others, [entry.S])
Obtain control on recovery from faults
http://www.phrack.org/issues.html?issue=59&id=4
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
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)
Debuggers and monitors used API to examine memory
NtReadVirtualMemory()
... 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