JVMTI应用分析,针对 TBCedit 的类加载器的JVMTI实现
Author:vhly[FR]
dAtE:2009/06/30
记得之前论坛里面有人问 tbcredit 中的类文件的问题,前两天比较闲,就抽出几天详细看了一下。
TBC 是一个刷xxx信誉的软件,给予Eclipse实现,或者说是类似于Eclipse的外观样式。
首先查看一下整体的软件目录结构
TBC
jre
bin
lib -- 类库目录,主要是在这个地方
Privoxy --- 未知
resource 资源
.....
run.bat
直接运行 run.bat,会调用 main.jar中的类文件,进行软件更新操作,更新的内容都保存在
lib目录中,同时,经过反编译发现,更新成功之后,程序会解析 lib/update.xml,通过其中的
设置,进行真正软件的调用,配置数据如下
<command><![CDATA[javaw,-agentlib:./lib/run,-Djava.library.path=.\lib,-jar,.\lib\com.xxxtaobao.tbcredit.jar]]></command>
这部分的内容,会组合成 javaw -agentlib:./lib/run -Djava.library.path=.\lib -jar .\lib\com.xxxtaobao.tbcredit.jar
就是调用了 xxx.jar文件,执行相应的内容而已。
直接找到 com.xxx.xxx.jar 这个文件,打开,找到Main-Class ,使用DJ Decompiler 进行反编译
结果,DJ 崩溃了,一般这种情况,就是类文件经过加密了。
于是将一个.class 解压出来,使用HEdit打开,发现原来的 CA FE BA BE 不见了,现在变成了
AA BB CC DD,这种,经过检查所有的class 都是这个开头,那么可以认为,这几个数值
就是某个格式的Magic
分析了半天,还是没有发现应该如何解密。由于以前做过Java Profiler的使用,那些东西基本上
都是提供一个 agentlib的,看看上面的指令,才想起来,原来是用了这种方式
那么就要针对 lib/run.dll 作分析了。
分析run.dll的步骤
1、使用IDA 进行汇编代码分析,发现 run.dll 中有几个导出 Agent_Onload Agent_OnUnload
以及 doXor, doExchange4Bits, encryptBuffer等等的方法,
2、当时并不了解JVMTI,只是看着 onload,觉得应该看一看,后来就分析代码,结果是一头雾水,
因为没有真实运行,很多的数据不知道什么功能。
3、动态调试部分,适用OllyICE进行调试,调试由于采用库的形式,因此,只能够调试java.exe,同时指定命令行参数,这样应该可以装载到run.dll中。
使用OllyICE 装载Java 之后指定参数,重新加载。
1)由于使用了agentlib,因此在启动之前在LoadLibraryA指令上下断点,此处的断点主要是
定位jvm.dll的装载,因为实际 agent的加载等操作,都在 jvm.dll中完成
2)进入jvm.dll空间,再次下LoadLibraryA的端点,当执行第二次的时候,会停止在 装载 agentlib部分
如图
此时一旦装载了 run.dll,那么就进入到实际的调试过程中了
3)查看run.dll中的方法
Agent_OnLoad, Agent_OnUnload, doExchange4Bits, doXor,encryptBuffer,
getEncryptMarks, getEncryptMarkSize, getHeaderSize,getLogined, login,
4)在Agent_OnLoad中下断点,单步查看执行流程
__stdcall Agent_OnLoad(x, x, x)
.text:10001000 public _Agent_OnLoad@12
.text:10001000 _Agent_OnLoad@12 proc near
.text:10001000
.text:10001000 var_A4 = dword ptr -0A4h
.text:10001000 Dst = byte ptr -94h
.text:10001000 var_84 = dword ptr -84h
.text:10001000 var_8 = dword ptr -8
.text:10001000 var_4 = dword ptr -4
.text:10001000 arg_0 = dword ptr 8
.text:10001000 arg_4 = dword ptr 0Ch
.text:10001000
.text:10001000 push ebp
.text:10001001 mov ebp, esp
.text:10001003 sub esp, 0A4h
.text:10001009 push 30010021h
.text:1000100E push offset dword_10008B60
.text:10001013 mov eax, [ebp+arg_0]
.text:10001016 push eax
.text:10001017 mov ecx, [ebp+arg_0]
.text:1000101A mov edx, [ecx]
.text:1000101C call dword ptr [edx+18h] ; GetEnv
.text:1000101F mov [ebp+var_8], eax
.text:10001022 cmp [ebp+var_8], 0
.text:10001026 jz short loc_10001046
.text:10001028 mov eax, [ebp+var_8]
.text:1000102B push eax
.text:1000102C push offset Format ; "ERROR: Unable to create jvmtiEnv, GetEn"...
.text:10001031 push offset File ; File
.text:10001036 call _fprintf
.text:1000103B add esp, 0Ch
.text:1000103E or eax, 0FFFFFFFFh
.text:10001041 jmp loc_100011C1
.text:10001046 ; ---------------------------------------------------------------------------
.text:10001046
.text:10001046 loc_10001046: ; CODE XREF: Agent_OnLoad(x,x,x)+26j
.text:10001046 lea ecx, [ebp+var_A4]
.text:1000104C push ecx
.text:1000104D mov edx, dword_10008B60
.text:10001053 push edx
.text:10001054 mov eax, dword_10008B60
.text:10001059 mov ecx, [eax]
.text:1000105B call dword ptr [ecx+160h] ; GetCapabilities
.text:10001061 mov [ebp+var_4], eax
.text:10001064 cmp [ebp+var_4], 0
.text:10001068 jz short loc_10001080
.text:1000106A mov edx, [ebp+var_4]
.text:1000106D push edx
.text:1000106E push offset aErrorGetcapabi ; "ERROR: GetCapabilities failed, error=%d"...
.text:10001073 push offset File ; File
.text:10001078 call _fprintf
.text:1000107D add esp, 0Ch
.text:10001080
.text:10001080 loc_10001080: ; CODE XREF: Agent_OnLoad(x,x,x)+68j
.text:10001080 mov eax, [ebp+var_A4]
.text:10001086 or eax, 4000000h
.text:1000108B mov [ebp+var_A4], eax
.text:10001091 lea ecx, [ebp+var_A4]
.text:10001097 push ecx
.text:10001098 mov edx, dword_10008B60
.text:1000109E push edx
.text:1000109F mov eax, dword_10008B60
.text:100010A4 mov ecx, [eax]
.text:100010A6 call dword ptr [ecx+234h] ; add JVMTI
.text:100010AC mov [ebp+var_4], eax
.text:100010AF cmp [ebp+var_4], 0
.text:100010B3 jz short loc_100010D5
.text:100010B5 mov edx, [ebp+var_4]
.text:100010B8 push edx
.text:100010B9 push offset aErrorUnableToA ; "ERROR: Unable to add necessary JVMTI ca"...
.text:100010BE push offset File ; File
.text:100010C3 call _fprintf
.text:100010C8 add esp, 0Ch
.text:100010CB mov eax, 0FFFFFFFEh
.text:100010D0 jmp loc_100011C1
.text:100010D5 ; ---------------------------------------------------------------------------
.text:100010D5
.text:100010D5 loc_100010D5: ; CODE XREF: Agent_OnLoad(x,x,x)+B3j
.text:100010D5 push 8Ch ; Size
.text:100010DA push 0 ; Val
.text:100010DC lea eax, [ebp+Dst]
.text:100010E2 push eax ; Dst
.text:100010E3 call _memset
.text:100010E8 add esp, 0Ch
.text:100010EB mov [ebp+var_84], offset sub_10001280 注意此处
.text:100010F5 push 8Ch
.text:100010FA lea ecx, [ebp+Dst]
.text:10001100 push ecx
.text:10001101 mov edx, dword_10008B60
.text:10001107 push edx
.text:10001108 mov eax, dword_10008B60
.text:1000110D mov ecx, [eax]
.text:1000110F call dword ptr [ecx+1E4h]
.text:10001115 push 0
.text:10001117 push 37h 注意此处
.text:10001119 push 1
.text:1000111B mov edx, dword_10008B60
.text:10001121 push edx
.text:10001122 mov eax, dword_10008B60
.text:10001127 mov ecx, [eax]
.text:10001129 call dword ptr [ecx+4]
.text:1000112C add esp, 10h
.text:1000112F push 0
.text:10001131 push 36h 注意此处
.text:10001133 push 1
.text:10001135 mov edx, dword_10008B60
.text:1000113B push edx
.text:1000113C mov eax, dword_10008B60
.text:10001141 mov ecx, [eax]
.text:10001143 call dword ptr [ecx+4]
.text:10001146 add esp, 10h
.text:10001149 push offset unk_10008B64
.text:1000114E push offset aLock ; "lock"
.text:10001153 mov edx, dword_10008B60
.text:10001159 push edx
.text:1000115A mov eax, dword_10008B60
.text:1000115F mov ecx, [eax]
.text:10001161 call dword ptr [ecx+78h] ; create raw monitor
.text:10001164 mov [ebp+var_4], eax
.text:10001167 cmp [ebp+var_4], 0
.text:1000116B jz short loc_1000118A
.text:1000116D mov edx, [ebp+var_4]
.text:10001170 push edx
.text:10001171 push offset aErrorUnableT_0 ; "ERROR: Unable to create raw monitor: %d"...
.text:10001176 push offset File ; File
.text:1000117B call _fprintf
.text:10001180 add esp, 0Ch
.text:10001183 mov eax, 0FFFFFFFDh
.text:10001188 jmp short loc_100011C1
.text:1000118A ; ---------------------------------------------------------------------------
.text:1000118A
.text:1000118A loc_1000118A: ; CODE XREF: Agent_OnLoad(x,x,x)+16Bj
.text:1000118A call lib_Init
.text:1000118F test eax, eax
.text:10001191 jz short loc_1000119A
.text:10001193 mov eax, 0FFFFFFFCh
.text:10001198 jmp short loc_100011C1
.text:1000119A ; ---------------------------------------------------------------------------
.text:1000119A
.text:1000119A loc_1000119A: ; CODE XREF: Agent_OnLoad(x,x,x)+191j
.text:1000119A cmp [ebp+arg_4], 0
.text:1000119E jz short loc_100011BF
.text:100011A0 push offset aDebug ; "debug"
.text:100011A5 mov eax, [ebp+arg_4]
.text:100011A8 push eax
.text:100011A9 call unknown_libname_1 ; Microsoft VisualC 2-8/net runtime
.text:100011AE add esp, 8
.text:100011B1 test eax, eax
.text:100011B3 jnz short loc_100011BF
.text:100011B5 mov dword_10008B6C, 1
.text:100011BF
.text:100011BF loc_100011BF: ; CODE XREF: Agent_OnLoad(x,x,x)+19Ej
.text:100011BF ; Agent_OnLoad(x,x,x)+1B3j
.text:100011BF xor eax, eax
.text:100011C1
.text:100011C1 loc_100011C1: ; CODE XREF: Agent_OnLoad(x,x,x)+41j
.text:100011C1 ; Agent_OnLoad(x,x,x)+D0j ...
.text:100011C1 mov esp, ebp
.text:100011C3 pop ebp
.text:100011C4 retn 0Ch
上面就是 Agent_OnLoad的代码,通过ICE执行的时候,单步跟踪,但是此处实际上只是做了
初始化的处理和设置。
经过多次调试,终于找到3处重要位置,即代码中标记 注意此处 的几个部分。
后来发现,这个代码实际上使用了 JVMTI技术,就 google了,以下,结果找到了 在 Java SDK安装的Include中有描述,其中的 0x36 0x37 这两个 是一个标志位,代表了 ClassLoader的实现
经过仔细阅读代码,调试,终于找到 mov [ebp+var_84], offset sub_10001280
这一句,十分重要,应该是设置回调方法,一旦需要进行Class Load的时候,就调用这个方法
基本的JVMTI实现中的 OnLoad方法,大体上是这样的
JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm, char *options, void *reserved)
{
...
MethodTraceAgent* agent = new MethodTraceAgent();
agent->Init(vm);
agent->ParseOptions(options);
agent->AddCapability();
agent->RegisterEvent(); // 此处注册希望监听的事件类型, 0x36 0x37
...
}
5)查找字符串,上面的回调地址就是用字符串定位的
在IDA 中显示字符串,发现 %d. class %s decrypted successfully, class data size: %d bytes. 这一句,看来一切都没有问题,确实是通过JVMTI进行类解密的了
定位代码位置
子程序 sub_10001280
看来和上面定位到的 offset 10001280是一致的,因此在 sub_10001280 处下断点,
继续执行程序
6)分析实际的类加载/解密过程
int __stdcall sub_10001280(int, int, int, int, int, int, int filelength, void *Buf1, int, int)
.text:10001280 sub_10001280 proc near ; DATA XREF: Agent_OnLoad(x,x,x)+EBo
.text:10001280
.text:10001280 var_4 = dword ptr -4
.text:10001280 arg_0 = dword ptr 8
.text:10001280 arg_10 = dword ptr 18h
.text:10001280 filelength = dword ptr 20h
.text:10001280 Buf1 = dword ptr 24h
.text:10001280 arg_20 = dword ptr 28h
.text:10001280 arg_24 = dword ptr 2Ch
.text:10001280
.text:10001280 push ebp ; asdf
.text:10001280 ; dfasdf
.text:10001281 mov ebp, esp
.text:10001283 push ecx
.text:10001284 cmp [ebp+filelength], 1Ch
.text:10001288 jle loc_10001350
.text:1000128E push 4 ; Size
.text:10001290 push offset MAGIC ; Buf2
.text:10001290 ; AA BB CC DD
.text:10001290 ;
.text:10001295 mov eax, [ebp+Buf1]
.text:10001298 push eax ; Buf1
.text:10001299 call _memcmp
.text:1000129E add esp, 0Ch
.text:100012A1 test eax, eax
.text:100012A3 jnz loc_10001350 ; 不是以 AA BB CC DD 开头,直接返回,否则进入解密部分
.text:100012A9 call lib_Init 此方法 进行初始化
.text:100012AE test eax, eax
.text:100012B0 jz short loc_100012B7
.text:100012B2 jmp loc_10001350
.text:100012B7 ; ---------------------------------------------------------------------------
.text:100012B7
.text:100012B7 loc_100012B7: ; CODE XREF: sub_10001280+30j
.text:100012B7 mov ecx, dword_10008B68
.text:100012BD add ecx, 1
.text:100012C0 mov dword_10008B68, ecx
.text:100012C6 mov edx, [ebp+filelength]
.text:100012C9 sub edx, 1Ch
.text:100012CC mov eax, [ebp+arg_20]
.text:100012CF mov [eax], edx
.text:100012D1 mov ecx, [ebp+arg_20]
.text:100012D4 mov edx, [ecx]
.text:100012D6 push edx
.text:100012D7 mov eax, [ebp+arg_0]
.text:100012DA push eax
.text:100012DB call sub_10001360
.text:100012E0 add esp, 8
.text:100012E3 mov ecx, [ebp+arg_24]
.text:100012E6 mov [ecx], eax
.text:100012E8 mov edx, [ebp+arg_20]
.text:100012EB push edx
.text:100012EC mov eax, [ebp+arg_24]
.text:100012EF mov ecx, [eax]
.text:100012F1 push ecx
.text:100012F2 mov edx, [ebp+filelength]
.text:100012F5 push edx
.text:100012F6 mov eax, [ebp+Buf1]
.text:100012F9 push eax
.text:100012FA call sub_10001800 // 针对 文件数据的解密
.text:100012FF add esp, 10h
.text:10001302 mov [ebp+var_4], eax
.text:10001305 cmp [ebp+var_4], 0
.text:10001309 jz short loc_10001325
.text:1000130B mov ecx, [ebp+var_4]
.text:1000130E push ecx
.text:1000130F mov edx, [ebp+arg_10]
.text:10001312 push edx
.text:10001313 push offset aDecryptClassSF ; "Decrypt class: %s fail! Return code: %d"...
.text:10001318 push offset File ; File
.text:1000131D call _fprintf
.text:10001322 add esp, 10h
.text:10001325
.text:10001325 loc_10001325: ; CODE XREF: sub_10001280+89j
.text:10001325 cmp dword_10008B6C, 0
.text:1000132C jz short loc_10001350
.text:1000132E mov eax, [ebp+arg_20]
.text:10001331 mov ecx, [eax]
.text:10001333 push ecx
.text:10001334 mov edx, [ebp+arg_10]
.text:10001337 push edx
.text:10001338 mov eax, dword_10008B68
.text:1000133D push eax
.text:1000133E push offset aD_ClassSDecryp ; "%d. class %s decrypted successfully, cl"...
.text:10001343 push offset File ; File
.text:10001348 call _fprintf
.text:1000134D add esp, 14h
.text:10001350
.text:10001350 loc_10001350: ; CODE XREF: sub_10001280+8j
.text:10001350 ; sub_10001280+23j ...
.text:10001350 mov esp, ebp
.text:10001352 pop ebp
.text:10001353 retn 28h
.text:10001353 sub_10001280 endp
解密字节数据的部分实际上在 .text:100012FA call sub_10001800
部分
6)数据解密部分
10001800 /$ 55 push ebp
10001801 |. 8BEC mov ebp, esp
10001803 |. 83EC 34 sub esp, 34
10001806 |. 833D 748B0010>cmp dword ptr [10008B74], 0
1000180D |. 75 11 jnz short 10001820
1000180F |. 8B45 14 mov eax, dword ptr [ebp+14]
10001812 |. C700 00000000 mov dword ptr [eax], 0
10001818 |. 83C8 FF or eax, FFFFFFFF
1000181B |. E9 B6000000 jmp 100018D6
10001820 |> 837D 0C 1C cmp dword ptr [ebp+C], 1C ; buflen
10001824 |. 7D 13 jge short 10001839
10001826 |. 8B4D 14 mov ecx, dword ptr [ebp+14]
10001829 |. C701 00000000 mov dword ptr [ecx], 0
1000182F |. B8 FEFFFFFF mov eax, -2
10001834 |. E9 9D000000 jmp 100018D6
10001839 |> 6A 18 push 18
1000183B |. 8B55 08 mov edx, dword ptr [ebp+8] ; buf
1000183E |. 83C2 04 add edx, 4
10001841 |. 52 push edx
10001842 |. 8D45 E8 lea eax, dword ptr [ebp-18]
10001845 |. 50 push eax
10001846 |. E8 75020000 call 10001AC0 ; _memcpy(dest,buf,0x18)
1000184B |. 83C4 0C add esp, 0C
1000184E |. 6A 18 push 18
10001850 |. 8D4D E8 lea ecx, dword ptr [ebp-18]
10001853 |. 51 push ecx
10001854 |. E8 17FDFFFF call doExchange4Bits ; doExchange4Bits(dst,18)
10001859 |. 83C4 08 add esp, 8
1000185C |. 8D55 D0 lea edx, dword ptr [ebp-30]
1000185F |. 52 push edx
10001860 |. 6A 18 push 18
10001862 |. 8D45 E8 lea eax, dword ptr [ebp-18] ; dest
10001865 |. 50 push eax
10001866 |. 6A 04 push 4
10001868 |. 68 14820010 push 10008214 ; 87 76 65 54 head key
1000186D |. E8 8EFCFFFF call doXor
10001872 |. 83C4 14 add esp, 14
10001875 |. 8B4D 08 mov ecx, dword ptr [ebp+8] ; buf
10001878 |. 83C1 1C add ecx, 1C
1000187B |. 894D CC mov dword ptr [ebp-34], ecx
1000187E |. 8B55 0C mov edx, dword ptr [ebp+C] ; buflen
10001881 |. 83EA 1C sub edx, 1C
10001884 |. 8B45 14 mov eax, dword ptr [ebp+14] ; arg_C
10001887 |. 8910 mov dword ptr [eax], edx ; edx 剩余的长度
10001889 |. 8B4D 10 mov ecx, dword ptr [ebp+10] ; arg_8 result
1000188C |. 51 push ecx
1000188D |. 8B55 14 mov edx, dword ptr [ebp+14]
10001890 |. 8B02 mov eax, dword ptr [edx]
10001892 |. 50 push eax ; 剩余文件长度
10001893 |. 8B4D CC mov ecx, dword ptr [ebp-34] ; 剩余的文件数据
10001896 |. 51 push ecx
10001897 |. 6A 18 push 18
10001899 |. 8D55 D0 lea edx, dword ptr [ebp-30] ; 经过doXor 之后的 header部分
1000189C |. 52 push edx
1000189D |. E8 5EFCFFFF call doXor
100018A2 |. 83C4 14 add esp, 14
100018A5 |. 8B45 14 mov eax, dword ptr [ebp+14]
100018A8 |. 8B08 mov ecx, dword ptr [eax]
100018AA |. 51 push ecx ; 剩余文件长度
100018AB |. 8B55 10 mov edx, dword ptr [ebp+10]
100018AE |. 52 push edx
100018AF |. E8 BCFCFFFF call doExchange4Bits
100018B4 |. 83C4 08 add esp, 8
100018B7 |. 8B45 10 mov eax, dword ptr [ebp+10] ; result2
100018BA |. 50 push eax
100018BB |. 8B4D 14 mov ecx, dword ptr [ebp+14]
100018BE |. 8B11 mov edx, dword ptr [ecx]
100018C0 |. 52 push edx ; 剩余长度
100018C1 |. 8B45 10 mov eax, dword ptr [ebp+10]
100018C4 |. 50 push eax
100018C5 |. 6A 10 push 10
100018C7 |. 68 04820010 push 10008204
100018CC |. E8 2FFCFFFF call doXor
100018D1 |. 83C4 14 add esp, 14
100018D4 |. 33C0 xor eax, eax
100018D6 |> 8BE5 mov esp, ebp
100018D8 |. 5D pop ebp
100018D9 \. C3 retn
7)上述汇编代码是在看着郁闷,因此,用Java实现一套完整的类文件解密方法
自己太懒了,用代码说话
// 类解密入口,fbuf 为实际的文件数据
private static byte[] decrypt(byte[] fbuf) {
boolean bOK = true;
byte[] ret = null;
int flen = fbuf.length;
// 0x1C 为加密后的文件的头部分
if (flen < 0x1c) {
bOK = false;
}
int off = 0;
// 检查 AA BB CC DD
for (; off < 4; off++) {
if (fbuf[off] != magic[off]) {
bOK = false;
break;
}
}
if (bOK) {
// TODO 解密操作
int hlen = 0x18; // 0x1c - 4 = 0x18 刨除去得 4 字节 magic
ByteArrayOutputStream bout = null;
byte[] header = null;
try {
bout = new ByteArrayOutputStream();
int headerEnd = hlen + off;
for (; off < headerEnd; off++) {
bout.write(fbuf[off]);
}
header = bout.toByteArray();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (bout != null) {
try {
bout.close();
} catch (IOException e) {
e.printStackTrace();
}
bout = null;
}
}
if (header != null) {
// TODO 此处继续执行
doExchange4Bits(header); // 针对头部的数据进行处理,生成整个类
// 文件的解密密钥
byte[] clsKey = doXor(header_key, header); // 计算后的密钥
byte[] cfbuf = null;
try {
bout = new ByteArrayOutputStream();
// TODO 由于 off 一直在变化 因此使用 off 作为偏移变量
for (; off < flen; off++) {
bout.write(fbuf[off]);
}
cfbuf = bout.toByteArray();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (bout != null) {
try {
bout.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
if (cfbuf != null) {
// TODO 解密文件 使用密钥解密类文件的剩余部分
byte[] dcbuf = doXor(clsKey, cfbuf); // XOR 操作
doExchange4Bits(dcbuf); // 高低4位交换
ret = doXor(cls_key, dcbuf); // 再次进行XOR
}
}
}
return ret; // 生成真正的 Class 文件内容,又看到了 亲切的
// CAFE BABE了
}
private static byte[] doXor(byte[] key, byte[] buf) {
byte[] ret = null;
if (key != null && buf != null) {
int klen = key.length;
int blen = buf.length;
int j = 0;
ret = new byte[blen];
for (int i = 0; i < blen; i++) {
ret[i] = (byte) (buf[i] ^ key[j]);
j++;
if (j >= klen) {
j = 0;
}
}
}
return ret;
}
private static void doExchange4Bits(byte[] buf) {
if (buf != null) {
int len = buf.length;
int h, l, ret;
for (int i = 0; i < len; i++) {
l = (buf[i] & 0x0f);
h = (buf[i] & 0x0f0);
ret = ((l << 4) + (h >> 4));
buf[i] = (byte) ret;
}
}
}
8)总结:
主要针对 Agent_OnLoad的设置,来查找相应的事件监听,根据事件,进行类文件解密操作,
通过指定 agentlib来进行类加载实现。
通过编写代码,进行jar文件中所有class文件的解密,解密后的文件比解密之前少了 0x1C个字节
类文件又能够反编译了,
不过使用了混淆器,进行了字符串加密,不过,已经是不安全了。
Have a Good Day!
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
上传的附件: