汉王e脸通智慧园区管理平台最新版jar包解密浅析


简介

目前最新版本为 V2.1.0.3 版本的 iface.server-1.0.jar 和 iface.common-1.0.jar 这两个核心jar包已经是加密过的了(其实从v2.0.1.2就加密了,本文也是在v2.0.1.2就已经完成分析),想要代码审计就必须获取解密后的jar包或者解密后的class文件,众所周知,Java代码最终都是运行在jvm里,且在jvm里是明文的,最简单获取代码就是直接dump jvm 里与 iface 相关的class文件即可。对于没有加密版本之前的相关漏洞汇总:e脸通智慧园区管理平台漏洞汇总

正文

默认使用反编译工具如IDEA、JD-GUI 等显示 // INTERNAL ERROR // 表明,jar包不是常规class文件打包

使用 hexdump 查看加密的jar包 class 文件如下

➜  iface.common-1.0 file AppConst.class 
AppConst.class: RAR archive data
➜  iface.common-1.0 hexdump -C AppConst.class
00000000  52 61 72 21 1a 07 00 cf  90 73 00 00 0d 00 00 00  |Rar!.....s......|
00000010  aa bb cc dd ee ff c3 d3  4e 3f be d5 7e bc 5b ac  |........N?..~.[.|
00000020  4c c6 b7 99 f2 70 f0 2b  0c d3 38 f8 d2 a7 3f e5  |L....p.+..8...?.|
00000030  42 1b 74 2d 4a 5c 02 60  f5 78 62 92 ee fa 00 00  |B.t-J\.`.xb.....|
00000040  02 dc 00 00 02 e0 6c 07  51 e2 09 cc 3d df 60 46  |......l.Q...=.`F|
00000050  35 81 c9 2c d1 32 15 f8  68 f0 2e ac 93 65 5c c2  |5..,.2..h....e\.|
00000060  14 72 e0 90 cd 0c fe 95  d4 49 6a eb 3d 38 24 76  |.r.......Ij.=8$v|
00000070  34 82 93 6d 51 2f 95 03  52 e0 68 9b 13 00 57 9e  |4..mQ/..R.h...W.|
00000080  24 aa 04 d9 e0 f3 3d 4a  ba 57 59 d9 e5 37 7f dc  |$.....=J.WY..7..|
00000090  85 45 0e ef c5 7f 9d 69  f0 b0 4b 73 fe 9c aa ae  |.E.....i..Ks....|
000000a0  96 49 19 4f 5c 82 3b 63  a2 12 97 99 68 08 22 f9  |.I.O\.;c....h.".|
000000b0  03 ab a2 e2 1e 79 25 c0  5b 3f df 92 d0 20 63 db  |.....y%.[?... c.|
000000c0  f2 09 85 e4 25 16 eb 5a  98 f8 49 d1 aa 66 e2 33  |....%..Z..I..f.3|

file 命令识别到的 AppConst.class 表示是rar压缩文件,且通过hexdump 得到的 6152 2172 即 rar! 字节码文件幻数 52 61 72 21 1a 07 00 (RAR 版本 1.5 到 4.x 的字节码文件幻数(RAR 4 及之前版本)),RAR 版本 5.x 的字节码文件幻数是 52 61 72 21 1A 07 01 00 比旧版本多了一个字节(第7字节变成了0x01)。

但是此文件实际不是rar文件!不能解压的,我比较懒,不喜欢去分析如何加载如何解密后在jvm运行的,直接dump!一步到位!

dump class

dump jvm的calss文件,对于老鸟来说很简单,有很多工具如

  • jcmd:JDK 自带的命令行工具,功能丰富
  • jmap:可以dump堆快照,间接分析 class 信息(通过工具(如 Eclipse MAT)分析堆快照,找到 class 对象并导出。)
  • BTrace:动态追踪工具,可以在运行时hook类加载器,dump加载的class字节码,自定义脚本,拦截类加载过程,写出对应的字节码文件
  • Bytecode Outline(IDE 插件):直接查看并导出 class 文件,但需要加载 class 文件或者 jar,不是动态 dump
  • 使用JVMTI agent(自定义native agent):编写 JVMTI (Java Virtual Machine Tool Interface) agent,hook类加载事件,保存字节码,比较复杂但灵活,适合深入调试
  • VisualVM + Profiler 插件:VisualVM 自带一些内存和类分析插件,有些插件支持保存加载的class字节码
  • Byteman:用于动态插桩,也可以dump类字节码
  • Java Instrumentation API:用 Java agent 方式,在类加载时拦截并保存字节码
  • 使用 arthas 等 attach 工具

我这里直接使用 Java agent 直接 hook !

dump 指定 className 为 com/hanvon/iface 的jvm calss,代码如下

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;

public class DumpAgent {
    public static void premain(String agentArgs, Instrumentation inst) {
        System.out.println("[*] DumpAgent started...");
        inst.addTransformer(new ClassFileTransformer() {
            @Override
            public byte[] transform(ClassLoader loader,
                                    String className,
                                    Class<?> classBeingRedefined,
                                    ProtectionDomain protectionDomain,
                                    byte[] classfileBuffer) {
                if (className != null && classfileBuffer != null) {
                    try {
                        // 避免 dump JVM 内部或代理类(如 sun/, java/, javax/, etc)
                        if (className.startsWith("com/hanvon/iface")) {
                            String filePath = "C:\\\\temp\\\\dump_classes\\\\" + className + ".class";
                            File f = new File(filePath);
                            f.getParentFile().mkdirs();
                            try (FileOutputStream fos = new FileOutputStream(f)) {
                                fos.write(classfileBuffer);
                                System.out.println("[+] Dumped: " + className);
                            }
                        }
                    } catch (Throwable t) {
                        System.err.println("[-] Error dumping class: " + className);
                        t.printStackTrace();
                    }
                }
                return classfileBuffer;
            }
        });
    }
}
Manifest-Version: 1.0
Premain-Class: DumpAgent
Can-Redefine-Classes: true
Can-Retransform-Classes: true

使用如下命令编译成jar文件

因为 EFaceGo 的Java运行版本是1.8版本

所以你的java -version 以及 javac -version 命令的结果需要是1.8版本,不然加载会报错

javac -source 1.8 -target 1.8 DumpAgent.java && jar cfm dumpagent.jar META-INF/MANIFEST.MF *.class

编译后目录结构如下

├── DumpAgent$1.class
├── DumpAgent.class
├── DumpAgent.java
├── META-INF
│   └── MANIFEST.MF
└── dumpagent.jar

2 directories, 5 files

然后将 dumpagent.jar 复制到EFaceGo的安装目录 C:\EFaceGo\Tomcat8\webapps\manage\WEB-INF\lib ,根据你的安装目录修改路径。

设置jvm参数

打开 C:\EFaceGo\Tomcat8\bin\tomcat8w.exe ,找到 【Java】 选项卡

在 【Java Options】 部分添加如下内容

-javaagent:C:\EFaceGo\Tomcat8\webapps\manage\WEB-INF\lib\dumpagent.jar
-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005

如果dump出的class还是加密的,尝试切换位置,放置在最后

然后点击 【应用】

然后在服务里重启 Apache Tomcat 8.5 Tomcat8 服务

或者你在命令行里使用 net stop tomcat8 && net start tomcat8 命令进行重启。

注意观察 C:\EFaceGo\Tomcat8\logs 下面的 tomcat8-stdout.2025-xx-xx.log 日志,出现如下内容

2025-xx-xx xx:xx:xx Apache Commons Daemon procrun stdout initialized.
[*] DumpAgent started...
Listening for transport dt_socket at address: 5005
[+] Dumped: com/hanvon/iface/web/listener/WebAppEventListener
[+] Dumped: com/hanvon/iface/web/filter/HttpServletRequestReplacedFilter
[+] Dumped: com/hanvon/iface/utils/TheApp
[+] Dumped: com/hanvon/iface/utils/RecordProcessor
[+] Dumped: com/hanvon/iface/utils/Utils
[+] Dumped: com/hanvon/iface/types/HistoryTableInfo
[+] Dumped: com/hanvon/iface/web/controller/utils/operationLog/OperationLog
[+] Dumped: com/hanvon/iface/utils/Encrypter
[+] Dumped: com/hanvon/iface/utils/SM4Utils
[+] Dumped: com/hanvon/iface/utils/StrUtil

就代表 agent hook成功,进入 C:\TEMP\dump_classes 目录查看dump 的class文件即可

压缩后将其解压到 EFaceGo 的 manage class目录下即可开始审计啦!

注:采用这种方式可能dump不全!建议加载agent后等两分钟等全部加载了,刷新页面后再去保存calss文件。

后面有空了再讲讲如何解密这种jar文件。

加上 -verbose:class 启动参数,在日志文件 tomcat8-stdout.2025-xx-xx.log 看哪些类是由哪个路径加载

[Opened C:\EFaceGo\Java\jre1.8.0_181\lib\rt.jar]
[Loaded java.lang.Object from C:\EFaceGo\Java\jre1.8.0_181\lib\rt.jar]
[Loaded java.io.Serializable from C:\EFaceGo\Java\jre1.8.0_181\lib\rt.jar]
[Loaded java.lang.Comparable from C:\EFaceGo\Java\jre1.8.0_181\lib\rt.jar]
[Loaded java.lang.CharSequence from C:\EFaceGo\Java\jre1.8.0_181\lib\rt.jar]
[Loaded java.lang.String from C:\EFaceGo\Java\jre1.8.0_181\lib\rt.jar]
[Loaded java.lang.reflect.AnnotatedElement from C:\EFaceGo\Java\jre1.8.0_181\lib\rt.jar]
[Loaded java.lang.reflect.GenericDeclaration from C:\EFaceGo\Java\jre1.8.0_181\lib\rt.jar]
[Loaded java.lang.reflect.Type from C:\EFaceGo\Java\jre1.8.0_181\lib\rt.jar]
[Loaded java.lang.Class from C:\EFaceGo\Java\jre1.8.0_181\lib\rt.jar]
[Loaded java.lang.Cloneable from C:\EFaceGo\Java\jre1.8.0_181\lib\rt.jar]
[Loaded java.lang.ClassLoader from C:\EFaceGo\Java\jre1.8.0_181\lib\rt.jar]
[Loaded java.lang.System from C:\EFaceGo\Java\jre1.8.0_181\lib\rt.jar]
[Loaded java.lang.Throwable from C:\EFaceGo\Java\jre1.8.0_181\lib\rt.jar]
[Loaded java.lang.Error from C:\EFaceGo\Java\jre1.8.0_181\lib\rt.jar]
[Loaded java.lang.ThreadDeath from C:\EFaceGo\Java\jre1.8.0_181\lib\rt.jar]
......

上述 dump 下面的class 文件还是加密的(加载顺序有关,调整后正常dumpclass文件,或者是因为在web页面尝试了导入授权,激活了解密class,之前只是刷新,没有触发加载这些类即没有解密,或者多等待一段时间后,写入解密后的class,反正会覆写),放在最后,特别是放在 -agentpath 后面

还在jvm参数里发现了

-agentpath:C:\EFaceGo\Tomcat8\bin\libhwcheck.dll

是一个 native agent,可能被用来控制或加密/解密 JVM 类的加载过程。

二者区别如下

项目 -javaagent -agentpath
类型 Java 语言写的 agent(.jar) Native C/C++ 写的 JVMTI agent(.dll / .so)
接口 使用 java.lang.instrument.Instrumentation 使用 JVMTI(Java Virtual Machine Tool Interface)
常见文件类型 .jar .dll, .so, .dylib
启动调用 premain(String args, Instrumentation inst)(或 agentmain) Agent_OnLoad(JavaVM vm, char options, void *reserved)
功能能力 插桩类、方法字节码修改 JVM 更底层能力:类加载 hook、线程监控、加解密控制、反调试等
加载时机 启动时、attach 后 通常只能启动时加载(attach 支持但更复杂)

但是 libhwcheck.dll 大概率是硬件加密狗检测或硬件绑定校验的Agent,并非class加解密

通过进入后台,各个功能点点 dump 的class更完整(不点也没事,大部分主要class都还是能dump下来的

编写解密工具

通过翻找观察tomcat目录文件时间戳变化,发现tomcat的lib目录下的一个jar包 tomcat-coyote.jar 时间明显和其他不一样:

其他jar包都是 2023年11月10号的,唯独它是2024年3月6号的,多半有猫腻!

直接下载一个官方版本的 tomcat-coyote.jar 和其进行对比

tomcat版本信息在启动时会打印在控制台,也会写入 logs/catalina.日期.log 里

直接去 https://mvnrepository.com/artifact/org.apache.tomcat/tomcat-coyote/8.5.96 下载一个 tomcat-coyote-8.5.96.jar 文件,对比如下

两个jar包就一处不一样 org/apache/tomcat/util/bcel/classfile/ClassParser.class

这个 ClassParser.class 文件是 Tomcat 中用于解析 .class 字节码文件结构的类,属于 BCEL(Byte Code Engineering Library)的一部分,主要用于读取和分析 Java 字节码。

然后将这两个jar里的这个 ClassParser.class 直接用解压软件提取出来,尝试查看这个不同的class

可以看到 EFACEGO 里面自带的这个 tomcat-coyote.jar 文件幻数明显不是常见Java calss 文件的幻数 ca fe ba be

那我们修改下前面的agent 来 dump 这个class,dump后的class用 jd-gui 查看下有啥猫腻

魔改版本增加的 decode 方法,顾名思义 那就是解码!

看下魔改的主要变动内容如下

private InputStream decode(InputStream is) throws Exception {
    int size = is.available();
    is.mark(size + 1);
    if (size < ENCRYPT_HEAD.length + ENCRYPT_TAIL.length + 24 + 16 + 8)
      return is; 
    byte[] head = new byte[ENCRYPT_HEAD.length];
    if (is.read(head) != ENCRYPT_HEAD.length) {
      is.reset();
      return is;
    } 
    for (int i = 0; i < ENCRYPT_HEAD.length; i++) {
      if (head[i] != ENCRYPT_HEAD[i]) {
        is.reset();
        return is;
      } 
    } 
    byte[] key = new byte[24];
    if (is.read(key) != 24)
      throw new IOException("Tomcat ClassParser failed parse file[0]"); 
    byte[] iv = new byte[16];
    if (is.read(iv) != 16)
      throw new IOException("Tomcat ClassParser failed parse file[1]"); 
    byte[] buffer = new byte[8];
    if (is.read(buffer) != 8)
      throw new IOException("Tomcat ClassParser failed parse file[2]"); 
    int sourceLen = buffer[0] << 24 & 0xFF000000 | buffer[1] << 16 & 0xFF0000 | buffer[2] << 8 & 0xFF00 | buffer[3] & 0xFF;
    int dataLen = buffer[4] << 24 & 0xFF000000 | buffer[5] << 16 & 0xFF0000 | buffer[6] << 8 & 0xFF00 | buffer[7] & 0xFF;
    if (sourceLen <= 0 || dataLen <= 0 || size != ENCRYPT_HEAD.length + ENCRYPT_TAIL.length + 24 + 16 + 8 + dataLen)
      throw new IOException("Tomcat ClassParser failed parse file[3]"); 
    byte[] data = new byte[dataLen];
    if (is.read(data) != dataLen)
      throw new IOException("Tomcat ClassParser failed parse file[4]"); 
    SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
    IvParameterSpec ivSpec = new IvParameterSpec(iv);
    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    cipher.init(2, keySpec, ivSpec);
    byte[] decrypted = cipher.doFinal(data);
    if (decrypted == null || decrypted.length != sourceLen)
      throw new IOException("Tomcat ClassParser failed parse file[5]"); 
    byte code = (byte)(sourceLen & 0xFF);
    for (int j = 0; j < sourceLen; j++)
      decrypted[j] = (byte)(decrypted[j] ^ code); 
    return new ByteArrayInputStream(decrypted);
  }

  private static final byte[] ENCRYPT_HEAD = new byte[] { 
      82, 97, 114, 33, 26, 7, 0, -49, -112, 115, 
      0, 0, 13, 0, 0, 0, -86, -69, -52, -35, 
      -18, -1 };

  private static final byte[] ENCRYPT_TAIL = new byte[] { -105, -60, 61, 123, 0, 64, 7, 0 };

这不就是我们心心念念的解密逻辑吗!其主要处理逻辑如下

  • 如果不满足加密数据结构长度,否则直接放回。
  • 读取头部标记,与 ENCRYPT_HEAD 比较,若不匹配,也说明不是加密格式,返回原始流。
  • 读取密钥、IV 和长度信息,若读取失败,抛出 IOException,如果数据长度不对,也抛出异常。
  • 读取密文数据
  • 执行 AES 解密(AES 使用 CBC 模式,带填充,key 和 iv 是前面读取的,2 表示 Cipher.DECRYPT_MODE)
  • 验证长度并异或还原(使用 (sourceLen & 0xFF) 得到一个字节做异或)
  • 返回解密结果

加密格式结构如下

顺序 字节数 说明
1 22 ENCRYPT_HEAD 魔术头
2 24 AES 密钥
3 16 AES IV 向量
4 8 解密后长度 + 加密数据长度
5 N 加密数据
6 8 ENCRYPT_TAIL 魔术尾(未验证!此段代码未读取尾部,只通过 size 检查存在)

知道了加密方式后,就可以写一个解密工具直接将整个jar包进行解密,下载方式在最后

成功解密

参考


手机扫码阅读

普华Powerpms OfficeService.aspx 金格组件payload分析+SQL注入漏洞

红帆ioffice PgcaUserLogin.aspx SQL 注入漏洞

评 论
avatar
1
可以给我个具体下载的链接吗 师傅?万分感谢
9 个月前 回复
avatar
Mrxn
@1:https://www.hwzy99.com/download
9 个月前 回复
avatar
1
安装包可以分享一下吗??
9 个月前 回复
avatar
Mrxn
@1:官网就可以下载
9 个月前 回复