1. 背景
在某次攻防演练中,OA系统被态势感知系统监测到上传内存马通信,攻击动作为[尝试],起初并未在意,但系统显示经过了几轮尝试后,居然出现了【检测到冰蝎加密webshell通信】的告警,于是迅速将来源IP地址进行了封禁,并进行了漏洞修复。此文记录攻击方在OA系统上传木马,通过木马执行命令的溯源情况。
2. 难点
经过对冰蝎木马的简单研究,该木马使用aes进行加密,默认密钥为rebeyond md5值的前16位,执行原理为加载request中的java字节码。那么存在两个难点,一是获取冰蝎通讯的加密密钥,二是对java字节码进行批量还原,获取攻击方执行的攻击命令。
3. 初步尝试
将态势感知中的告警信息导出后,尝试对请求body中的base64编码后的payload进行解密,解密密钥先用默认的
e45e329feb5d925b (rebeyond md5值的前16位),再通过cyber chef的运算后,宣告失败。
随机选了一个(导出数据中的第一个)攻击载荷(请求body)通过cyberchef解密,发现解密后的数据并非java字节码模式,大概率解密失败:
TseXyXgv92jRryJcxINK5eNNE+GIpnvTIWiVnoie/NviaJpIyJEF64vSHMJOAu781UZD9NSf62nXWPztC/BCv7/h0hVruHgO7RRdAQEUsy8P5yzdNxpn7vtuo9Jyx6AB94yAL3YpPSwlefcgJA1o7+Vf1pUIsdoVxJwFZebVuE7v1yDDvczfqYVIzlPqRLndnayiL8PsQ5aOGPldCJ1m43DzuCr8FTLzxKGc6+B/aCp3mi8Ct6tl/1jW7eLUGubSjz6dEBYJ3ybDZa4iz+9K/KZRFaYUoDFWTrumLB2Vwf/ZHbHl5TE2YfJ0mL7lnwumTn5oqqZuNq6A/2gyRi8GpZEI1eNPiHK7zsMlz4RCMPqj712a6ftgTM4wjnvcyUQVFToExaytVDLfjfQf.....省略1万字....
4. 寻找解密key
4.1突破口
经过多次尝试后,发现该条告警虽然为【尝试】状态,但可能已经成功上传木马,通过态势感知将告警的请求body导出,进行研究分析。
请求长度达到了可怕的17322字节,看参数看的眼花缭乱怎么办?善用cyberchef
managerMethod=getConditionValue&arguments=%5B%7B%7D%2C%22%7D%3Bimport+static+com.seeyon.ctp.util.Base64.%2A%3Bimport+st.......省略......classdata=.....
通过Cyberchef的split分割模块,通过&符号进行分割,并且在第二个选项中多加入几个换行,可以看到一共有三个参数,分别是managerMethod、arguments、classdata ,其中第二个和第三个中有大量的base64编码字符串。
4.2峰回路转
再次对arguments中的base64编码的字符串进行还原,可以验证classdata字段是java字节码,并且执行的动作为将类实例化后,执行equals方法。
4.3柳暗花明
对classdata中的数据进行base64解码,解码后内容如下,可以看到非常明显的java字节码的特征,保存为dowload.class:
将download.class拖入idea,进行反编译,查看java源代码:
为了方便跟踪代码,将代码拷贝一份,新建一个类,粘贴,idea就能愉快的进行语法分析、代码追踪了。
看到有个函数中有很长的base64编码的字符串:
跟踪后来到这里,代码核心原理就是base64解码后,通过gzip解压缩,然后加载类,并且实例化:
那么好,就手动把这段base64字符串还原为一个类,在该类上写个main函数,调用一下继续保存为class文件即可:
将该类保存为了loader.class,再次反编译,可以看到pass解密密钥的字符串,在后续代码中也看到相关调用的地方:
通过对代码逻辑的简单分析,该段代码通过判断请求头部是否包含Xxx字段,并且等于ccc,如果存在,则执行命令控制代码,执行逻辑是从request中读取完整内容,base64解码后完成java类的实例化,并且开始执行equasl方法。从态势感知抓取到的后续通讯请求中也可以看出头部字段带有Xxx:ccc 头部字段。
4.4初步成果
通过cyberchef,用获取到的key,完美解密请求body中的加密信息。
5. 下一步
既然要溯源,那就要知道攻击方到底做了什么操作,态势感知系统导出的日志多达1千多条,每个手工解密自然不现实。那就继续编码实现。
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.List;
import java.util.stream.Collectors;
public class Decrypt extends ClassLoader {
public static byte[] decrypt(String payload) throws InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException {
String pass = "47bce5c74f589f48";
Cipher c = Cipher.getInstance("AES");
c.init(Cipher.DECRYPT_MODE, new SecretKeySpec(pass.getBytes(), "AES"));
byte[] decode = Base64.getDecoder().decode(payload);
return c.doFinal(decode);
}
public static void writeFile(String filepath, byte[] data) throws IOException {
Files.write(Paths.get(filepath), data, StandardOpenOption.CREATE);
}
public Class defineClass(byte[] b) {
return super.defineClass(b, 0, b.length);
}
public static void main(String[] args) throws Exception {
List<String> rawData = Files.readAllLines(Paths.get("....日志的文件路径.csv"), Charset.forName("GB2312"));
List<String> collect = rawData.stream().map(s -> {
String[] split = s.split(",");
if (split.length > 8) {
return split[8].trim();// 在csv文件中的第8个字段
}
return s;
}).collect(Collectors.toList());
for (int index = 0; index < collect.size(); index++) {
try {
byte[] decrypt = decrypt(collect.get(index));
Class clazz = new Decrypt().defineClass(decrypt);
StringBuilder sb = new StringBuilder();
for (Field field : clazz.getFields()) {
// 下面几个if视情 进行过滤
if (field.getName().equals("type")) {
continue;
}
if (field.getName().equals("whatever")) {
continue;
}
if (field.get(clazz) == null) {
continue;
}
sb.append(field.getName());
sb.append(":");
sb.append(field.get(clazz));
sb.append(" ");
}
if(!sb.toString().trim().isEmpty()){
System.out.println(sb.toString());
}
} catch (Error error) {
} catch (Exception ex) {
}
}
}
}
通过java代码,读取态势感知日志,base64 decode、aes解密后,将字节数组定义为java类,通过java类反射获取各个字段的值,过滤掉字段为null或者为空的部分请求后,发现了除读取了一些文件之外,还创建了一些文件:
6. 后记
至此,分析完成。后期看了几篇分析冰蝎源代码的文章,对payload的整体还原分析可以有更加直观的认识。