记一次逆向分析解密还原Class文件
前言
前阵子我的一位朋友发来一份代码让我帮忙看看。具体就是所有的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文件的更多相关文章
- 某Android手游的lua源码逆向分析与还原
近日分析某一款Android上面的手游,反编译后再起asset目录下可以看到加密过的脚本,lib目录下发现lua的so 初步怀疑其使用lua脚本实现的 解密函数定位 动态跟踪解密函数流程 静态分析解密 ...
- 还原win10任务管理器的内存dump功能之——程序逆向分析(待完成)
逆向分析工作基本完成,笔记待完成.
- IM通信协议逆向分析、Wireshark自定义数据包格式解析插件编程学习
相关学习资料 http://hi.baidu.com/hucyuansheng/item/bf2bfddefd1ee70ad68ed04d http://en.wikipedia.org/wiki/I ...
- C++反汇编与逆向分析技术揭秘
C++反汇编-继承和多重继承 学无止尽,积土成山,积水成渊-<C++反汇编与逆向分析技术揭秘> 读书笔记 一.单类继承 在父类中声明为私有的成员,子类对象无法直接访问,但是在子类对象的 ...
- TI(德州仪器) TMS320C674x逆向分析之一
一.声明 作者并不懂嵌入式开发,整个逆向流程都是根据自身逆向经验,一步一步摸索出来,有什么错误请批评指正,或者有更好的方法请不吝赐教.个人写作水平有限,文中会尽量把过程写清楚,有问题或是写的不清楚的地 ...
- 一文了解安卓APP逆向分析与保护机制
"知物由学"是网易云易盾打造的一个品牌栏目,词语出自汉·王充<论衡·实知>.人,能力有高下之分,学习才知道事物的道理,而后才有智慧,不去求问就不会知道."知物 ...
- PC逆向之代码还原技术,第六讲汇编中除法代码还原以及原理第二讲,被除数是正数 除数非2的幂
目录 一丶简介 二丶代码还原讲解 1.被除数无符号 除数非2的幂 2.被除数无符号 除数为特例7 三丶代码还原总结 一丶简介 上一篇博客说的除2的幂. 如果被除数是有符号的,那么会进行调整,并使用位操 ...
- SG Input 软件安全分析之逆向分析
前言 通过本文介绍怎么对一个 windows 程序进行安全分析.分析的软件版本为 2018-10-9 , 所有相关文件的链接 链接:https://pan.baidu.com/s/1l6BuuL-HP ...
- [Android Security] Smali和逆向分析
copy : https://blog.csdn.net/u012573920/article/details/44034397 1.Smali简介 Smali是Dalvik的寄存器语言,它与Java ...
- 逆向分析-IDA动态调试WanaCrypt0r的wcry.exe程序
0x00 前言 2017年5月12日全球爆发大规模蠕虫勒索软件WanaCrypt0r感染事件,各大厂商对该软件做了深入分析,但针对初学者的分析教程还比较少,复现过程需要解决的问题有很多,而且没有文章具 ...
随机推荐
- 如何通过关键词搜索API接口获取1688的商品详情
如果你是一位电商运营者或者是想要进行1688平台产品调研的人员,你可能需要借助API接口来获取你所需要的信息.在这篇文章中,我们将会讨论如何通过关键词搜索API接口获取1688的商品详情. 第一步:获 ...
- Go类型全解:常量与变量大全!
本篇文章深入探讨了 Go 语言中类型确定值.类型不确定值以及对应类型转换的知识点,后续充分解析了常量与变量及其高级用法,并举出丰富的案例. 关注公众号[TechLeadCloud],分享互联网架构.云 ...
- VisionPro学习笔记(3)——BeadInspectTool
如果需要了解其他图像处理的文章,请移步小编的GitHub地址 传送门:请点击我 如果点击有误:https://github.com/LeBron-Jian/ComputerVisionPractice ...
- mpi转以太网连接300PLC实现以太网通信配置方法
西门子S7300PLC连接MPI-ETH-XD1.0实现以太网通信配置方法 产品简介 兴达易控MPI-ETH-XD1.0用于西门子S7-200/SMART S7-200/S7-300/S7-400/西 ...
- 关于初次new springboot项目
如果是新手初学,然后做springboot项目报各种错,改来改去最终都无法出现successful字样. 请先检查,maven环境是否配好. maven环境决定你下载依赖的速度,以及能否下载成功. m ...
- MySQL 高级(进阶) SQL 语句
MySQL 高级(进阶) SQL 语句 use gy; create table location (Region char(20),Store_Name char(20)); insert into ...
- TopCoder 15903 EllysNim
TopCoder 15903 EllysNim(https://vjudge.net/problem/TopCoder-15903) \(n\)看似有点东西,实际上就只是一个贪心... 设\(i\)表 ...
- 比赛总结:Japan Registry Services (JPRS) Programming Contest 2023 (AtCoder Beginner Contest 324)
比赛:Japan Registry Services (JPRS) Programming Contest 2023 (AtCoder Beginner Contest 324) A-same 1.常 ...
- 低代码平台探讨-MetaStore元数据缓存
背景及需求 之前提到我们模型驱动的实现选择的是解释型,需要模型的元数据信息,在接到请求后动态处理逻辑. 此外,应用的通用能力中还包括:页面dsl查询,菜单查询等. 而且后期加入触发器,用户自定义api ...
- Arithmetic Progression 题解
Arithmetic Progression 题目大意 存在一个打乱了顺序的等差数列 \(a\),你可以询问不超过 \(60\) 次,每次可以以以下两种方式之一进行询问: 查询 \(a\) 中是否有严 ...