linux进程隐藏

Linux 下进程隐藏手法大体上分为两种,一种是基于用户态隐藏;一种是直接操控内核进行隐藏。

替换源程序

检测源程序的hash

伪造进程名

a) 修改argv[0]

1
memset((void *)argv[0], '\0', strlen(argv[0]));
2
strcpy(argv[0], "-");

检测/proc/[pid]/cmdline中argv[0]和/proc/[pid]/stat或者/proc/[pid]/status所对应的程序名是否一致

b) 使用prctl

1
#include <stdio.h>
2
#include <sys/prctl.h>
3
4
int main(int argc, char *argv[], char *envp[])
5
{
6
    prctl(PR_SET_NAME, "fuckyou\0",NULL,NULL,NULL);
7
    sleep(10000);
8
}

修改了/proc/[pid]/status/proc/[pid]/stat, /proc/[pid]/cmdline没变,所以ps,top看到的进程名还是没有改变。

c)使用exec

1
exec -a fake_name <程序> &

挂载覆盖

mount --bind /tmp/empty /proc/[pid]

检测/proc/mounts文件,解析是否有挂载到/proc/[pid]的文件夹

https://www.freebuf.com/articles/network/140535.html

Hook系统调用

  • 修改内核调用,比如getdents 的源码

  • 修改libc库中readdir 函数的源码

  • 利用环境变量LD_PRELOAD 或者配置ld.so.preload文件 以使的恶意的动态库先于系统标准库加载,以达到架空系统标准库中相关函数的目的,最终实现对特定进程的隐藏

检测:

  1. 查看内核版本编译的时间?
  2. 监控libc文件
  3. 监控环境变量LD_PRELOAD和/etc/ld.so.preload (如果是手工排查可用strace或者sysdig

内核调用

获取sys_call_table几种方法:
1)暴力破解

1
for (start = (unsigned long *)0xc0000000; start < (unsigned long *)0xffffffff; start++)
2
  if (start[__NR_close] == (unsigned long)sys_close){
3
    return start;
4
  }

2)通过/dev/kmem获取IDT address

3) 通过dump获取绝对地址
模拟出一个call *sys_call_table(,%eax,4),然后看其机器码,然后在system_call的附近基于这个特征进行寻找
4) 通过kprobe方式动态获取kallsyms_lookup_name,然后利用kallsyms_lookup_name获取sys_call_table的地址。kallsyms_lookup_name()可以用于获取内核导出符号表中的符号地址,而sys_call_table的地址也存在于内核导出符号表中,我么可以使用kallsyms_lookup_name()获取到sys_call_table的基地址。
5)int 80中断
https://www.cnblogs.com/LittleHann/p/3879961.html
https://www.cnblogs.com/LittleHann/p/3854977.html#_lab2_3_6

检测:

原始的系统调用地址在内核编译阶段被指定,不会更改,通过比较原始的系统调用地址和当前内核态中的系统调用地址我们就可以发现系统调用有没有被更改。原始的系统调用地址在编译阶段被写入两个文件:

  • System.map: 该文件包含所有的符号地址,系统调用也包含在内

  • vmlinux-2.4.x: 系统初始化时首先被读入内存的内核映像文件

0x1: 获取原始内核系统调用函数地址

/boot/System.map-3.10.0-327.el7.x86_64

0x2: 获取当前内核系统调用地址

cat /proc/kallsyms

0x3: 对比区别

从里面枚举sys_call_table的function point地址
/boot/System.map-3.10.0-327.el7.x86_64cat /proc/kallsyms | grep sys_fork进行diff对比
cat System.map-4.4.0-53-generic | grep sys_fork

ubuntu对sys_socket相关的系统调用会进行内核地址重定位,因此需要对检测结果进行一个误报过滤,看是否所有的函数的gap都相同,如果相同,则说明是系统自己的

Linux Hook技术

Ring3中Hook技术

1)通过LD_PRELOAD劫持so文件

LD_PRELOAD的加载顺序由/etc/ld.so.preload确定

绕过方式:

  • 通过静态编码绕过LD_PRELOAD机制监控

    通过静态链接方式编译so模块:gcc -o test test.c -static
    在静态链接的模式下,程序不会去搜索系统中的so文件(不同是系统默认的、还是第三方加入的),所以也就不会调用到Hook SO模块。

  • 通过内联汇编的方式绕过LD_PRELOAD机制监控

    使用内嵌汇编的形式直接通过syscall指令使用系统调用功能,同样也不会调用到Glibc提供的API。asm("movq $2, %%rax\n\t syscal:"=a"(ret));

2)通过ptrace注入

ptrace 可以让目标 pid 进程成为当前进程的子进程, 进而可以访问目标进程的内存空间, 寄存器, 并且可以向目标内存空间写内容. 需要了解 ptrace 函数的几个关键宏。参考链接 https://linux.die.net/man/2/ptrace :

PTRACE_ATTACH 挂载目标pid

PTRACE_CONT让子程序继续运行

PTRACE_PEEKTEXT 读取内容

PTRACE_POKETEXT 写入内容

  1. 用ptrace函数attach上目标进程
  2. 让目标进程的执行流程跳转到mmap函数来分配一小段内存空间
  3. 把一段机器码拷贝到目标进程中刚分配的内存中去
  4. 最后让目标进程的执行流程跳转到注入的代码执行

开启ptrace保护
img

利用工具地址:

https://jmpews.github.io/2016/12/27/pwn/linux%E8%BF%9B%E7%A8%8B%E5%8A%A8%E6%80%81so%E6%B3%A8%E5%85%A5/

RING0 hook技术

1) Kernel Inline Hook
传统的kernel inline hook技术就是修改内核函数的opcode,通过写入jmp或push ret等指令跳转到新的内核函数中,从而达到劫持的目的。

2)劫持sys_call_table

3)利用Linux内核机制kprobe机制(kprobes, jprobe和kretprobe)进行系统调用Hook
kprobe简介:https://www.cnblogs.com/LittleHann/p/3854977.html#_lab2_2_1

4)LSM hook
LSM框架自身并不提供任何安全策略,它只是为安全模型提供了一致性接口。它使得各种不同的安全模型以内核模块的方式得到实现,而且不需改动内核源代码以及重新编译内核。目前基于LSM框架实现的最主要的安全模块主要有:https://www.cnblogs.com/LittleHann/p/3854977.html#_lab2_3_4
5)VDSO hook
https://www.cnblogs.com/LittleHann/p/3879961.html
6)LKM
https://www.freebuf.com/articles/system/54263.html
https://github.com/croemheld/lkm-rootkit/tree/master/src

Rootkit检测

什么是rootkit

Rootkit是一种特殊的恶意软件,功能是在安全目标上隐藏自身及指定的文件、进程和网络链接等信息,通常rootkit和木马、后门等恶意程序结合使用。

现在的操作系统,包括windows和Linux都是采用2层级别进行访问控制:即R0层和R3层。操作系统和驱动运行于R0层,一旦驱动加载成功,就与操作系统具有同样的权限,如果此驱动是恶意的,那么内核Rookit就植入了。用户态Rookit以普通应用程序(可能是应用层进程,或者模块,或者shellcode)形态运行在R3层。

检测方案

1) 从rootkit造成的效果审计角度去检测。

比如:rootkit会使得自身或者指定的进程端口等隐藏掉,无法通过常用工具查看到;

2) 静态文件特征检测方式;

采用特征扫描方式,首先,要找到rootkit文件在哪?大部分情况,rootkit文件/进程/驱动会隐藏起来,通过一般的查询工具查找不到。其次,找到后,进行特征扫描,特征库要及时更新,这里存在匹配覆盖率低的可能。

3) 动态行为分析检测方式。

首先要锁定目标进程/驱动执行体,才能通过各种方式来捕捉其运行期的行为,再通过行为规则匹配或者大数据分析的方式进行判定。

rootkit 进程隐藏检测

攻击手段

可以通过多种手段实现进程隐藏,比如:

A) 替换掉常见的进程查看工具(比如:ps、top、lsof)等;

B) Hook系统调用,篡改常用的进程查看工具的工作流程,劫持底层调用的函数,在读取/proc/pid目录时,过滤掉需隐藏进程信息。

C) 通过内核模块,拦截proc文件系统的回调函数,过滤掉需隐藏进程信息;

D) 利用环境变量LD_PRELOAD或者配置ld.so.preload文件,以使得恶意的动态库先于系统标准库加载,架空标准库中的相关函数,实现对特定进程隐藏。

检测方法

  • 针对直接替换工具类,通过文件校验即可检测;

  • 对于hook 底层系统函数类,通过系统文件/内存完整性校验即可检测;

  • 新增内核模块类,需要通过基线检查(白名单机制)发现非白名单的内核模块;

  • 检查LD_PRELOAD变量和/etc/ld.so.preload文件

rootkit 应用层隐藏端口检测

攻击手段

与进程隐藏思路类似,通过对Netstat等端口查看工具的调用过程中某个环节进行劫持,从而实现对指定端口信息的隐藏。Netstat是从/proc/net/tcp得到连接信息的。

检测方法

通过libc系统调用函数bind,listen盲测端口,对比差异发现隐藏端口。

具体如下:

1) 从1到65535遍历端口;

2) 创建一个基于TCP协议SOCK_STREAM的socket;

3) 通过bind返回值和错误码探测端口状态;

4) 如果被占用,通过listen错误码是EADDRINUSE确定端口占用;

5) 通过ss或netstat命令过滤tcp协议,查看端口情况;

6) 对比差异,确认差异的端口即为隐藏端口。

7) 对系统上原本合法隐藏的端口信息等建立基线,在第6)中排除。

相比tcp,udp使用SOCK_DGRAM的socket,缺少listen这步,其余检测步骤类似。

rootkit应用层恶意文件检测

检测方法

假设能够拿到rootkit恶意文件样本,通过特征匹配的方式进行静态检测。

实现原理

特征库包括:

  • 文件特征:如特征名、文件大小、目录名、文件和目录的隐藏属性;

  • 字符串特征:如被篡改的二进制文件和系统启动脚本中会包含一些特征字符串;

  • 内核符号表特征:通过加载内核模块而在内核符号表中留下的痕迹字符串等;

统计特征:对一条样本已检测出的所有特征个数进行统计,超过阈值(比如:30%)则认为是有效rootkit,否则视为疑似rootkit;

病毒木马检测

其实和rootkit检测方法差不多,因为可以把木马也属于rookit中的一种。现在很多挖矿马,一方面可以使用静态特征进行判断,也可以可以添加对cup占用的检测,如果单进程占用cup的负载很高,又不是白名单中的进程,则很有可能存在安全风险。

计算cup占用情况的方法:

/proc/stat 存放所有CPU信息

/proc/<pid>/stat存放当个进程的cpu信息

  1. 取一次processCpuTime1和totalCpuTime1;

  2. 间隔一段时间,再取一次processCpuTime2和totalCpuTime2;

  3. pcpu = 100 * (processCpuTime2 – processCpuTime1)/(totalCpuTime2 - totalCpuTime1);

开源的杀毒引擎

开源杀毒引擎ClamAV的病毒库非常强大,主要有

  1. 已知的恶意二进制文件的MD5哈希值
  2. PE(Windows 中可执行文件格式)节的MD5哈希值
  3. 十六进制特征码(shellcode)
  4. 存档元数据特征码
  5. 已知的合法文件的白名单数据库

我们可以将clamav的病毒库转换为yara规则,进行恶意代码识别。也可以利用开源的yara规则,进行木马病毒的检测。