记一次逆向分析解密还原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感染事件,各大厂商对该软件做了深入分析,但针对初学者的分析教程还比较少,复现过程需要解决的问题有很多,而且没有文章具 ...
随机推荐
- springboot整合nacos和dubbo
0. 源码 源码: gitee 1. 版本 java: 1.8.0_281 nacos: 2.1.2 2. 创建项目 创建一个简单的springboot或者maven项目, 或者代码库(gitee/g ...
- 【Windows】KMS 激活命令记录
目录 KMS 服务器激活 Office.Visio 推荐使用 office tool plus 部署并配置 KMS 激活 什么是 KMS? KMS 正版与否的区别 总结 KMS 服务器激活 利用 KM ...
- 吃透单调栈(2)——解两道Hard题:接雨水、柱状图中最大的矩形问题
怎么想到要用单调栈的? 这类题目的数据通常是一维数组,要寻找任一个元素的右边或者左边第一个比自己大或者小的元素的位置(寻找边界),此时我们就要想到可以用单调栈了. 42. 接雨水 这道题就是要求解每一 ...
- VR国标
<软件基本要求与测试方法>
- Git命令详细使用指南
Git命令详细使用指南 Git是一种广泛使用的版本控制系统,它可以帮助开发人员跟踪变更.协作项目和有效管理代码仓库.无论你是初学者还是有经验的用户,理解各种Git命令对于高效的代码管理至关重要. 安装 ...
- lattice crosslink开发板mipi核心板csi测试dsi屏lif md6000 fpga
1. 概述 CrossLink开发板,是用Lattice的芯片CrossLink 家族系列的,LIF-MD6000-6JM80I.该芯片用于桥接视频接口功能,自带2路MIPI硬核的功能,4 LANE ...
- KRPANO资源分析工具下载VR-FACTORY全景图
示:目前分析工具中的全景图下载功能将被极速全景图下载大师替代,相比分析工具,极速全景图下载大师支持更多的网站(包括各类KRPano全景网站,和百度街景) 详细可以查看如下的链接: 极速全景图下载大师官 ...
- 领域驱动模型DDD(四)——Eventuate Tram Saga源码讲解
前言 虽然本人一直抱怨<微服务架构设计模式>中DDD模式下采用的Eventuate Tram Saga不算简单易用,但是为了更加深入了解原文作者的设计思路,还是花了点时间去阅读源码,并且为 ...
- Vue-基本语法及事件绑定
一.基本语法 v-bind绑定: 双大括号不能在 HTML attributes 中使用.想要响应式地绑定一个 attribute,应该使用 v-bind 指令 代码展示: <div id=&q ...
- Solution -「洛谷 P5072」「YunoOI 2015」盼君勿忘
Description Link. 无修支持查询:查询一个区间 \([l,r]\) 中所有子序列分别去重后的和 \(\bmod\ p\) Solution 这是数据结构一百题的第50题(一半了哦)的纪 ...