前言

前阵子我的一位朋友发来一份代码让我帮忙看看。具体就是所有的jsp文件内容和大小都一样,漏洞挖掘无从下手。经过分析发现所有的Class都使用了自定义的加密工具加密,经过逆向分析,顺利解密,因而有了此文。

初步分析

文件内容如下所示:

其他文件亦如是:

接着在tomcat work目录找到了编译后的class文件:

但是没办法直接反编译,查看头信息发现都一样:

因此猜测一种可能性是Java层面实现的类加载器,类加载的时候进行动态解密操作。于是自己写了一个jsp文件上传到目标环境,首先访问一下这个jsp文件,让JVM加载至内存中。然后我们调用Class.forName再去加载该类,获取到该类的java.lang.Class对象实例,然后调用getClassLoader获取该类的加载器:

<%
try {
out.println(Class.forName("org.apache.jsp.test_html").getClassLoader());
} catch(Throwable th) {
th.printStackTrace(out);
}
%>

结果获取到的内容为tomcat实现的WebAppClassLoader,回溯父类加载器也没有发现自定义的实现。于是计划取巧,使用Arthas之类的工具attach到目标的JVM,去内存dump加载过的Class。然后发现无法attach,估计是目标JVM版本过低,为JDK 1.5。因而继续取巧,用动态调试调试,在ClassLoader#defineClass方法下断点争取将byte[]直接dump出来,可是也没有成功。

峰回路转

过了几天,后来回头重新去做分析。在tomcat启动脚本中发现了如下的参数:

嗯,果然是自定义加载器,可人家是通过Java agent实现的,而且是实现JVMTI接口,并未在应用层使用Java代码去实现,而是直接用C++实现接口。具体的实现就在这个dll中:

经过对java agent的简单学习,了解相关参数和实现后,在ida中将相关的结构体还原代码如上图所示。这里有个ida使用技巧,分析C/C++代码最重要就是要了解关键的结构体功能,这相当于了解Java中类的定义和相关方法的含义。而JVMTI的SDK在JDK的安装目录中是开源的,我们可以用ida导入本地结构体的功能批量导入。导入的时候根据header文件的加载顺序依次复制到一个文件中,否则会有很多依赖缺失导致的报错。最终的头文件结构如下:

jni_md.h -> jni.h -> jvmti.h

然后需要将前边的include指令导入操作删除掉,导入ida:

顺利的话,将会提示如上图所示:

相关的错误信息也会在message窗口输出。可以用来定位错误。

接着开始我们的逆向之旅,经过一些分析之后还原出来的伪代码如图所示:

在第22行,这里一定要把数据的显示格式修改为hex,如上图,我们可以看到这里判断了Class文件的魔术头,因而猜测这里就是解密的操作了,我们跟进解密函数的具体实现(第29行 decryptClassBytes函数):

代码的实现很简单,根据随机数种子设置srand函数。然后循环调用rand()函数去和读取到的byte进行异或操作。最终返回异或的指针。这里一度让我十分困惑,因为根据我对随机数的认识,至少这里应该会把随机数也存放在某个地方,这样将来解密才能正确执行。因此我自己写了一些代码来观察随机数的生成:

#include <stdio.h>
#include <stdlib.h> int main()
{
srand(0x96F07);
int i = rand();
printf("rand number = %x\n", i); int c = rand();
printf("rand number = %d\n", c); int n = rand();
printf("rand number = %d\n", n); int k = rand();
printf("rand number = %d\n", k); return 0;
}

找了好几个在线运行代码的站点,发现最终生成的结果居然是完全一样的!查询该函数之后发现了这样的一句话:

初始化随机种子,会提供一个种子,这个种子会对应一个随机数,如果使用相同的种子后面的 rand() 函数会出现一样的随机数。

结合前阵子JumpServer出现过的随机数问题,让我再次认识到这个问题的居然是这种方式!

因此我们的解密操作就很简单了:

#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h> int main() {
const int BUFFER_SIZE = 1;
srand(0x96F07);
char *src_file = "D:\\cms\\tomcat\\work\\Catalina\\localhost\\oa\\org\\apache\\jsp\\test_html.class";
char *dst_file = "C:\\Users\\Administrator\\CLionProjects\\decrypt\\decrypt.class"; FILE *p_src = fopen(src_file, "rb");
if (p_src == NULL) {
printf("src_file open failed");
return 0;
}
FILE *p_dst = fopen(dst_file, "wb");
if (p_dst == NULL) {
printf("dst_file open failed");
return 0;
}
// 判断文件大小 , 该结构体接收文件大小结果
struct stat st = {0};
stat(src_file, &st);
// 计算缓冲区文件大小
int buffer_size = st.st_size;
if ( buffer_size > BUFFER_SIZE ) {
buffer_size = BUFFER_SIZE;
}
char *buffer = malloc(buffer_size);
char output[1]; while ( !feof(p_src) ) {
int res = fread(buffer, 1, buffer_size, p_src);
*output = *buffer ^ rand();
fwrite(output, 1, res, p_dst);
}
// 释放缓冲区内存
free(buffer);
fclose(p_src);
fclose(p_dst);
printf("Copy Success");
return 0;
}

以上代码并没有成功,后来我用这个代码加密一个未加密过的class文件,发现和目标文件差了1个字节。也就是说解密操作是越过第一个字节开始的,在如上代码基础上,越过第一个字节即可:

#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h> int main() {
const int BUFFER_SIZE = 1;
srand(0x96F07);
char *src_file = "D:\\cms\\tomcat\\work\\Catalina\\localhost\\oa\\org\\apache\\jsp\\test_html.class";
char *dst_file = "C:\\Users\\Administrator\\CLionProjects\\decrypt\\decrypt.class"; FILE *p_src = fopen(src_file, "rb");
if (p_src == NULL) {
printf("src_file open failed");
return 0;
}
FILE *p_dst = fopen(dst_file, "wb");
if (p_dst == NULL) {
printf("dst_file open failed");
return 0;
}
// 判断文件大小 , 该结构体接收文件大小结果
struct stat st = {0};
stat(src_file, &st);
// 计算缓冲区文件大小
int buffer_size = st.st_size;
if ( buffer_size > BUFFER_SIZE ) {
buffer_size = BUFFER_SIZE;
}
char *buffer = malloc(buffer_size);
char output[1]; // 跳过最初的1字节
if (fseek(p_src, 1, SEEK_SET) != 0) {
printf("fseek error!\n");
fclose(p_src);
return 1;
} while ( !feof(p_src) ) {
int res = fread(buffer, 1, buffer_size, p_src);
*output = *buffer ^ rand();
fwrite(output, 1, res, p_dst);
}
// 释放缓冲区内存
free(buffer);
fclose(p_src);
fclose(p_dst);
printf("Copy Success");
return 0;
}

解密:

记一次逆向分析解密还原Class文件的更多相关文章

  1. 某Android手游的lua源码逆向分析与还原

    近日分析某一款Android上面的手游,反编译后再起asset目录下可以看到加密过的脚本,lib目录下发现lua的so 初步怀疑其使用lua脚本实现的 解密函数定位 动态跟踪解密函数流程 静态分析解密 ...

  2. 还原win10任务管理器的内存dump功能之——程序逆向分析(待完成)

    逆向分析工作基本完成,笔记待完成.

  3. IM通信协议逆向分析、Wireshark自定义数据包格式解析插件编程学习

    相关学习资料 http://hi.baidu.com/hucyuansheng/item/bf2bfddefd1ee70ad68ed04d http://en.wikipedia.org/wiki/I ...

  4. C++反汇编与逆向分析技术揭秘

    C++反汇编-继承和多重继承   学无止尽,积土成山,积水成渊-<C++反汇编与逆向分析技术揭秘> 读书笔记 一.单类继承 在父类中声明为私有的成员,子类对象无法直接访问,但是在子类对象的 ...

  5. TI(德州仪器) TMS320C674x逆向分析之一

    一.声明 作者并不懂嵌入式开发,整个逆向流程都是根据自身逆向经验,一步一步摸索出来,有什么错误请批评指正,或者有更好的方法请不吝赐教.个人写作水平有限,文中会尽量把过程写清楚,有问题或是写的不清楚的地 ...

  6. 一文了解安卓APP逆向分析与保护机制

    "知物由学"是网易云易盾打造的一个品牌栏目,词语出自汉·王充<论衡·实知>.人,能力有高下之分,学习才知道事物的道理,而后才有智慧,不去求问就不会知道."知物 ...

  7. PC逆向之代码还原技术,第六讲汇编中除法代码还原以及原理第二讲,被除数是正数 除数非2的幂

    目录 一丶简介 二丶代码还原讲解 1.被除数无符号 除数非2的幂 2.被除数无符号 除数为特例7 三丶代码还原总结 一丶简介 上一篇博客说的除2的幂. 如果被除数是有符号的,那么会进行调整,并使用位操 ...

  8. SG Input 软件安全分析之逆向分析

    前言 通过本文介绍怎么对一个 windows 程序进行安全分析.分析的软件版本为 2018-10-9 , 所有相关文件的链接 链接:https://pan.baidu.com/s/1l6BuuL-HP ...

  9. [Android Security] Smali和逆向分析

    copy : https://blog.csdn.net/u012573920/article/details/44034397 1.Smali简介 Smali是Dalvik的寄存器语言,它与Java ...

  10. 逆向分析-IDA动态调试WanaCrypt0r的wcry.exe程序

    0x00 前言 2017年5月12日全球爆发大规模蠕虫勒索软件WanaCrypt0r感染事件,各大厂商对该软件做了深入分析,但针对初学者的分析教程还比较少,复现过程需要解决的问题有很多,而且没有文章具 ...

随机推荐

  1. 记一次 .NET某报关系统 非托管泄露分析

    一:背景 1. 讲故事 前段时间有位朋友找到我,说他的程序内存会出现暴涨,让我看下是怎么事情?而且还告诉我是在 Linux 环境下,说实话在Linux上分析.NET程序难度会很大,难度大的原因在于Li ...

  2. JS中文件相关的知识(一):MIME类型

    不知道有没有同学和我一样,写代码时一遇到文件操作就犯怵,必须要先去把知识补一遍再说:对于Content-Type.responseType.ArrayBuffer.buffer.blob.file等这 ...

  3. arrch架构部署redis,报错: ignore-warnings ARM64-COW-BUG

    arrch架构服务器redis部署完成后,启动报错.做个记录. arrch架构的redis安装包 下载链接:https://pan.baidu.com/s/1TMXNpMvMDWRFD1f5km7Mw ...

  4. python将print的打印内容保存到日志

    将python程序中的所有打印内容都输出到日志文件中,在程序执行完成后,方便查询程序运行过程是否出现异常. 1. 将打印内容输出到日志文件 1.1 代码实现: sys.stdout = open('s ...

  5. Dokcer学习之旅(1)——运行一个简单的容器

    基本概念 镜像 我们都知道,操作系统分为 内核 和 用户空间.对于 Linux 而言,内核启动后,会挂载 root 文件系统为其提供用户空间支持.而 Docker 镜像(Image),就相当于是一个 ...

  6. Strategy Pattern and Polymorphism —— Behavioral Class

    策略模式着重于封装和替换 不同的算法或行为,以便在运行时进行选择. Simple example - Computer and USB interface 现代人对计算机.USB接口还有各种设备之间的 ...

  7. Web应用防火墙--规则防护

    一.什么是Web应用防火墙? Web应用防火墙对网站.APP的业务流量安全及合规性保护,对业务流量的识别恶意特征提取.分析识别出恶意流量并进行处理, 将正常安全的流量回源到业务服务器, 保护网站核心业 ...

  8. 解密Linux中的通用块层:加速存储系统,提升系统性能

    通用块层 通用块层是Linux中的一个重要组件,用于管理不同块设备的统一接口,减少不同块设备的差异带来的影响.它位于文件系统和磁盘驱动之间,类似于Java中的适配器模式,让我们无需关注底层实现,只需提 ...

  9. RocketMQ 消息重试与死信队列

    RocketMQ 消息重试与死信队列 RocketMQ 前面系列文章如下: RocketMQ系列(一) 基本介绍 RocketMQ 系列(二) 环境搭建 RocketMQ 系列(三) 集成 Sprin ...

  10. iOS16新特性:实时活动-在锁屏界面实时更新APP消息

    简介 之前在 <iOS16新特性:灵动岛适配开发与到家业务场景结合的探索实践> 里介绍了iOS16新的特性:实时更新(Live Activity)中灵动岛的适配流程,但其实除了灵动岛的展示 ...