哪个网站学习做辅助,中企动力科技股份有限公司贵阳分公司,如何建立公司,淘宝指数在线查询某某某加固系统内核so dump和修复#xff1a;
某某某加固系统采取了内外两层native代码模式#xff0c;外层主要为了保护内层核心代码#xff0c;从分析来看外层模块主要用来反调试#xff0c;释放内层模块#xff0c;维护内存模块的某些运行环境达到防止分离内外模块
某某某加固系统采取了内外两层native代码模式外层主要为了保护内层核心代码从分析来看外层模块主要用来反调试释放内层模块维护内存模块的某些运行环境达到防止分离内外模块另外由于内层模块不是通过系统加载的所以实现了自主的ELF加载这样就实现内层模块的加密处理。这些实现后就可以依赖内层模块保护dex。从而达到系统保护目的。
对于某某某加固系统的反调试网上已经有很多资料了就不再作为重点介绍了。下面主要介绍对内层模块的加载加密保护。这些外面资料不多的内容。
外层解密内层核心模块的解密算法是使用zlib库的uncompress函数实现的不过解密函数解密出来的并不是整个的模块而是被加密了或者说被移除了四个部分的模块包含
program_header_table、.rel.dyn、.rel.plt、Dynamic Segment 。由于是自己的加载系统加载所以这些被move的部分依赖父模块组装防止内存直接dump出解密的内层模块。
下面给出frida dump这些数据的脚本并加以说明
Java.perform(function () {var i 0;var phadd 0;var jmprel 0;var rel 0;var dynadd 0;var buff 0;console.log(begin)var fileclass Java.use(java.io.File);var mysavePath /data/data/ pkg_name /myso;var pathDir fileclass.$new(mysavePath);if (!pathDir.exists()) {pathDir.mkdirs();}console.log(mysavepath:pathDir)Interceptor.attach(Module.getExportByName(libz.so, uncompress), {onEnter: function (args) {if (i 0) {if (args[2] ! null) {var memcpy_add Module.findExportByName(libc.so, memcpy);console.log(memcpy: memcpy_add);Interceptor.attach(memcpy_add, {onEnter: function (args) {console.log(begin:memcpy,len: args[2]);console.log(hexdump(args[1]));if (args[2] 0x100) {// program_header_table 的大小一般是固定的phadd args[0];console.log(hexdump(args[1]))}if (args[2] 0x948) {//.rel.plt 数据;jmprel args[0];console.log(hexdump(args[1]))}if (args[2] 0x4a58) {//.rel.dyn 数据rel args[0];console.log(hexdump(args[1]))}if (args[2] 0xd8) {//Dynamic Segment 数据也是一般固定长度dynadd args[0];console.log(hexdump(args[1]))}if (args[2] 0xbbff4) {//这个就是某某某加固的加载加载基地址就是copy到的空间地址这个数据是经过解密的移除上面部分的模块数据;console.log(hexdump(args[1])); //这个hexdump中可以看到ELF头所以是个标记上面顺序就可以确定了。var new_so_base args[0];console.log(newso_base: args[0])}if (args[2] 0x38fc) {//到这里上面得到被move的四个部分数据也已经解密出来了在这里就可以开始dump了console.log(args[0])var file_path pathDir /mydump_ phadd .dat;console.log(----save begin1-----);var file_handle new File(file_path, wb);console.log(----save begin2-----);if (file_handle) {console.log(----save begin3-----);Memory.protect(ptr(phadd), 0x100, rwx);var libso_buffer ptr(phadd).readByteArray(0x100);file_handle.write(libso_buffer);file_handle.flush();file_handle.close();console.log([dump]: file_path);}var file_path pathDir /mydump_ jmprel .dat;console.log(----save begin1-----);var file_handle new File(file_path, wb);console.log(----save begin2-----);if (file_handle) {console.log(----save begin3-----);Memory.protect(ptr(jmprel), 0x948, rwx);var libso_buffer ptr(jmprel).readByteArray(0x948);file_handle.write(libso_buffer);file_handle.flush();file_handle.close();console.log([dump]: file_path);}var file_path pathDir /mydump_ rel .dat;console.log(----save begin1-----);var file_handle new File(file_path, wb);console.log(----save begin2-----);if (file_handle) {console.log(----save begin3-----);Memory.protect(ptr(rel), 0x4a58, rwx);var libso_buffer ptr(rel).readByteArray(0x4a58);file_handle.write(libso_buffer);file_handle.flush();file_handle.close();console.log([dump]: file_path);}var file_path pathDir /mydump_ dynadd .dat;console.log(----save begin1-----);var file_handle new File(file_path, wb);console.log(----save begin2-----);if (file_handle) {console.log(----save begin3-----);Memory.protect(ptr(dynadd), 0xd8, rwx);var libso_buffer ptr(dynadd).readByteArray(0xd8);file_handle.write(libso_buffer);file_handle.flush();file_handle.close();console.log([dump]: file_path);}}if (args[2] 0xb4) {//console.log(JSON.stringify(this.context))console.log(args[0])var file_path pathDir /mydump_ buff.so;console.log(----save begin1-----);var file_handle new File(file_path, wb);console.log(----save begin2-----);if (file_handle) {console.log(----save begin3-----);Memory.protect(ptr(buff), 0xc0000, rwx);var libso_buffer ptr(buff).readByteArray(0xc0000);file_handle.write(libso_buffer);file_handle.flush();file_handle.close();console.log([dump]: file_path);}}}},onLeave: function (retval) {// simply replace the value to be returned with 0}})})
上面代码只是说明功能扣出来的需要自己整理下可能才能执行。
脚本的原理就是根据某某某加固流程父模块使用uncompress解压后会把解压出来的被偷走的数据重新解密到新的内存地址在memcpy时得到内存地址和长度然后等解密出来后dump数据。
另外是根据数据的大小取相关数据的每个APP可能会不同需要先跑下看看。
需要说明下首先跑下hook uncompress后的memcpy hexdumpmemcpy加载的新地址数据出现ELF头数据的表明加载了。然后向上逆推其他数据这样就能确定每个的数据大小然后更改脚本获取数据并dump下来。
比如本例
begin:memcpy,len:0xbbff4 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF c9085589 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 .ELF............ c9085599 03 00 28 00 01 00 00 00 00 00 00 00 34 00 00 00 ..(.........4... c90855a9 bc fa 0b 00 00 00 00 05 34 00 20 00 08 00 28 00 ........4. ...(. c90855b9 19 00 18 00 11 a6 dd 35 da cf 22 1a 71 b7 8b 08 .......5...q...
由于父模块需要内存偏移修正所以完整的模块需要在后面的一次才能dump。
拿到了需要的so模块数据我们需要修复这个so模块否则ida无法分析用010Editor也会打开失败。下面进入so修复
使用的工具有010Editor和ELFfix
ELFfix 修复原理具体请参考https://bbs.pediy.com/thread-192874.htm
用010Editor打开dump的so模块会提示错误。
我们已我们已经知道被移除了填充杂乱数据的几个关键的部分。所以肯定不能正常加载。当然修复是需要理解下ELF的文件格式和内存加载原理。这样更便于理解为啥需要这样步骤修复。这个各位自己学习学习了。 首先我们需要还原program_header_table 这个是系统解析加载的关键数据用010Editor打开我们刚才dump出来的0x100字节的那个数据 复制并覆盖到dumpso的program_header_table里面 好了到这里我们完成ELFfix需要的关键数据保存这个文件然后可以使用ELFfix工具进行修复了
复制到ELFfix目录下并执行相关的修复命令会得到修复后的文件 dump_new_full.so ß修复后的so
再次用010Editor打开这个修复后的so还是会提示错误。不过打开后section_header_table已经正确了原来的section_header_table是错误的
来说下为啥先要修复program_header_table 节和这个段里面的Dynamic Segment根据ELFfix作者文章里面说明修复是依赖这两个数据进行解析得到ELF section所以必须先还原这两个数据块。
下面开始还原rel数据一个是jmprel一个是rel
我们打开这两个节 到这里内层的so模块被修复还原出来了IDA加载完全没问题了得到这个模块我们就可以用IDA分析调试了。当然也可以脱壳使用了。
注意目前这个ELFfix不支持64位程序修复。
好了有了这个就可以详细的分析某某某内层模块的功能当然也可以patch代码等操作了。
下面来看看某某某加固系统如何来保护内层so功能模块的。比较有特色 Java层偷Native层代码 某某某加固为了保护内层so不能脱离外层so环境做了一些防范措施这种也是通过移除关键部位的二进制代码如果脱离了整个环境那么就会执行错误被移除的二进制代码丢失。 1 2 3 4 def getBytes(self,mu,**kwargs): #采坑这个是某某某偷代码的一种方式把代码先偷走然后再放回来模拟这个的时候参数中没有出现这个值。 str B54FF67474E8BF10 dat bytearray.fromhex(str) return dat
内层在Java层使用getByte这个函数获取被偷走的二进制代码数据填充回so的执行中。
被偷的数据
dump 0x10004b1
010004B1: 00 00 00 00 00 00 00 00 BD 10 B5 4F F6 75 74 E8 ...........O.ut.
010004C1: BF 10 BD 10 B5 4F F6 76 74 E8 BF 10 BD 10 B5 4F .....O.vt......O 来看看这个是啥函数原来是JNIEnv-CallStaticObjectMethodV
这种Java层偷函数二进制执行代码的方式还是比较新颖的。 关键参数放父模块通过导出函数调用
一般模块的导出函数都是给其他模式引用的接口某某某这个导出函数却是调用父模块的接口因为子模块的加载完全是父模块负责的所以这个接口的填充是父模块做的所以如果脱离了父模块这个函数就变成了空的。而且这个函数还是个特别的核心函数来看看 获取key这个函数是获取解密算法rc4的key的如果没有这个解密key后面的dex解密都会失败。所以必须到父模块中执行。
通过查询有两个地方调用 这个就是某某某自己实现的类似乱序处理过的多功能集成函数。 来看看key计算的时候参数是啥 Registers R00x20917ec R10x350 R20x2024006 R30x2024018
R40x2024000 R50x2024006 R60x0 R70xcbcca6e8 R80x350
R90x350 R100x2024018 R110x202c350 R120x80000000 SP0x7ffad8
LR0xcbda5859 PC0xcbdb08fc Disassembly
0xcbdb08fc: blx r7 020917EC: 22 39 52 52 54 52 52 52 42 52 52 52 13 02 02 19 9RRTRRRBRRR....
020917FC: 17 0B 60 30 60 31 6A 61 66 67 33 6A 65 61 64 6A ..01jafg3jeadj
0209180C: 62 34 22 39 52 52 5A 52 52 52 53 52 52 52 01 36 b49RRZRRRSRRR.6
0209181C: 39 17 3C 26 20 2B 63 22 39 52 52 5E 52 52 52 4E 9. c9RR^RRRN
0209182C: 52 52 52 33 31 26 3B 24 3B 26 2B 1C 33 3F 37 31 RRR31;$;.3?71
0209183C: 3D 3F 7C 33 3B 35 28 7C 27 3B 7C 30 33 21 37 7C ?|3;5(|;|03!7| 执行下这个函数得到的key
dump 0x2024006
02024006: 67 5E 7F 35 70 37 78 2E 7D 22 75 27 08 56 4A A1 g^.5p7x.}u.VJ.
上面的参数哪里来的呢
分析发现原来是从原始包里面用libz解压出来的
Executing syscall openat(ffffff9c, 02029000, 00020000, 00000000) at 0xcbc28be4
path:/data/app/xxxxxxxx-1/base.apk 这个解出来就是原始包里面的classes.dex部分 另外一个地方的调用参数如下 Registers R00x7ffbf9 R10x0 R20x2024040 R30x7ffb10
R40xcbc761c8 R50x7ffbf8 R60x202b054 R70xcbde56df R80xcbc761c8
R90x202c34c R100x0 R110x2024000 R120x2024040 SP0x7ffb08
LR0xcbcca6e8 PC0xcbdb0982 Disassembly
0xcbdb0982: blx r2
dump 0x7ffbf9
007FFBF9: 00 DA CB 54 B0 02 02 DF 56 DE CB C8 61 C7 CB 00 ...T....V...a...
007FFC09: 00 00 00 2D 00 00 00 00 60 02 02 00 70 02 02 F0 ...-.......p...
这是个校验调用如果脱离父模块就会死在这个调用里面堆栈会被破坏掉。
当然还有其他类似的这样调用父模块校验 DEX文件保护
某某某加固系统最重要的功能应该就是为了对dex文件的保护了因为一般得到原始的dex后去掉加固模块就可以直接运行。相当于完整脱壳了。为了达到这个目的某某某加固系统对dex的保护特别重要。前面的这些对模块的保护最终的目的也是为了保护dex文件。下面来看看他的保护是怎么样的。
某某某加固系统第一次会把原始包中的classes.dex用libz函数解压出来然后系统会把他重新编译成oat文件这个文件中的dex头被加密处理了。里面的数据部分也被加密处理。在运行的时候加固系统直接解内存中加载的classes.oat文件而不用再次解压原始文件了。这样带来了效率的提升。也保证了没有明文的dex存在磁盘上。
数据的解密用到了rc4算法算法的key刚才已经说过了是从父模块的函数中获取的保证了解密的安全性。
rc4算法部分大家可以网上找资料看看用key生成0x100的解密盒子。然后用这个盒子去解密数据。
nt __fastcall rc4(int result, _BYTE *a2, int a3)
{ int v3; // lr int v4; // r12 int v5; // r5 if ( a3 ) { v3 0; v4 0; do { --a3; v3 (v3 1) % 256; v5 *(unsigned __int8 *)(result v3); v4 (v4 v5) % 256; *(_BYTE *)(result v3) *(_BYTE *)(result v4); *(_BYTE *)(result v4) v5; *a2 ^ *(_BYTE *)(result (unsigned __int8)(*(_BYTE *)(result v3) v5)); } while ( a3 ); } return result;
} 算法核心部分。
下面是跟踪的截图和注释这个循环保证解密oat中的所有dex文件 所有的oat文件中的dex都被解密出来后需要立即保存下来否则某某某加固系统会把dex的头再次删除我们在这个地方先dump下来oat文件。 Dump这个已经解密的oat文件后我们就可以把其中的dex文件都找出来
根据oat文件结构知道dex数据是从0x1000开始的前面是头后面接着就是dex文件从我们刚才跟踪的时候知道这个oat中有三个dex文件。向下找下看看也可以根据刚才R5中的偏移找到开始的位置第一个位置偏移是0x1808这个里面的dex头保存完整
从dex结构我们知道开始的偏移0x20 后面的4个字节就是长度第一个长度为0x60E694把这个数据保存出来然后继续这样找其他的dex
全部找到后我们用解析工具打开看看
这样原始的dex就被我们抓出来了。
Dump dex原理 某某某加固通过hook系统的LoadDexFile 在系统加载dex文件之前把修改部分的字节还原后交给系统使用。所以在这个时候获取的dex文件是正确的。 某某某的VMP保护 某某某加固系统的核心保护就是对dex代码中的onCreate函数进行VMP保护。把原指令用自己实现的指令集替换并在Native层实现这些指令达到替换指令集移除dex代码加密指令数据等。并且在vmp的代码中还可以插入自己的检测代码。非常有特色保护能力也非常强下面就来仔细的研究下某某某vmp的实现原理和具体实现方法。
来看这个app的例子
通过frida hook RegisterNatives脚本,获取加固系统注册Natives方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 if (addrRegisterNatives ! null) { Interceptor.attach(addrRegisterNatives, { onEnter: function (args) { var call_add this.returnAddress; console.log(Register_address:, call_add, offset:, ptr(call_add).sub(new_so_base)); console.log([RegisterNatives] method_count:, args[3]); var methods_ptr ptr(args[2]); var call_addr this.returnAddress; var method_count parseInt(args[3]); for (var i 0; i method_count; i) { var name_ptr Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3)); var sig_ptr Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 Process.pointerSize)); var fnPtr_ptr Memory.readPointer(methods_ptr.add(i * 12 8)); var fuPtr fnPtr_ptr.sub(1); var name Memory.readCString(name_ptr); var sig Memory.readCString(sig_ptr); var module_base new_so_base; JNI_RegisterNatives_array.push(fuPtr); JNI_RegisterNatives_array.push(name); console.log([RegisterNatives] java_class:, class_name, name:, name, sig:, sig, fnPtr:, fuPtr, offset:, ptr(fnPtr_ptr).sub(module_base)); if (name onCreate) { console.log(hexdump(fnPtr_ptr)); var code Instruction.parse(fnPtr_ptr); var next_code code.next; console.log(code.address, :, code); for (var i 0; i 11; i) { var next_c Instruction.parse(next_code); console.log(next_c.address, :, next_c); next_code next_c.next; } var onCreate_fun_ptr next_c.next.add(0xf); var onCreate_fun_addr Memory.readPointer(onCreate_fun_ptr); console.log(onCreate_function_address:,onCreate_fun_addr,offset:,ptr(onCreate_fun_addr).sub(module_base)); }
并获取参数打印如下
这个APP有两个地方被某某某加固使用VMP保护
[RegisterNatives] java_class: com/aigz/ui/base/BBSAppStart name: onCreate sig: (Landroid/os/Bundle;)V fnPtr: 0xd32fb39c
[RegisterNatives] java_class: com/aigz/ui/base/BBSAppStart name: onCreate sig: (Landroid/os/Bundle;)V fnPtr: 0xe857c39c
来到这两个函数的地方看看
Method表头中被定义为Native函数
通过frida的跟踪分析某某某加固系统还隐藏了真实的vmp处理函数地址真正的函数是在入口函数中通过地址跳转过去的可以通过内嵌的capstone引擎反汇编出来得到真实的函数入口地址 0xe857c39c : mov r8, r8
0xe857c39e : sub sp, #0xc
0xe857c3a0 : push {r7}
0xe857c3a2 : push {lr}
0xe857c3a4 : sub sp, #4
0xe857c3a6 : mov ip, r0
0xe857c3a8 : add r0, sp, #0xc
0xe857c3aa : stm r0!, {r1, r2, r3}
0xe857c3ac : add r2, sp, #0xc
0xe857c3ae : mov r0, pc
0xe857c3b0 : mov r1, ip
0xe857c3b2 : str r2, [sp]
onCreate_function_address: 0xd2858985 offset: 0x1c985
这样就定位到真实的函数偏移是0x1c985
通过比较长时间的分析大概的了解了这个函数的参数如下
R0加密函数的key这个key决定了后面的jump table的跳转也决定了后面使用的参数的计算和资源的获取。也就是说某某某的VMP引擎是同一个就是通过这个key识别处理不同的函数。
R1jni_env
R2: 堆栈参数
Vmp函数的大概结构是通过R0 key值获取需要处理的函数资源通过函数的参数值生成后面解密的xor_key这个key还被一个参数值保护失去这个参数值就会出现错误。另外这个key还被inker:__dl_rtld_db_dlactivity 值处理如果被调试inker:__dl_rtld_db_dlactivity值不为0这个参数也是错误的。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 for ( *(_DWORD *)v107 4; ; *(_DWORD *)v107 v92 2 * v76 ) { while ( 1 ) { while ( 1 ) { while ( 1 ) { while ( 1 ) { v106 (v107[4] | (v107[4] 8)) ^ **(unsigned __int16 **)v107; v13 ((int (__fastcall *)(unsigned __int8 *, _DWORD))sub_CC0BDEFE)(v107, (unsigned __int16)v106); v14 v13; if ( v13 0x7E ) break; if ( v13 0xBE ) { if ( v13 0xDD ) { if ( v13 0xEE ) { if ( v13 0xE6 ) { if ( v13 0xEA ) { if ( v13 0xE8 ) { if ( v13 ! 0xE7 ) JUMPOUT
下面详细的解释下这个处理函数的过程 .text.ARM.extab:CC0B042A LDR.W R0, [R9]
.text.ARM.extab:CC0B042E LDRB.W R1, [R9,#4]
.text.ARM.extab:CC0B0432 SUBS R2, R0, R6
.text.ARM.extab:CC0B0434 LDRH R0, [R0]
.text.ARM.extab:CC0B0436 ORR.W R1, R1, R1,LSL#8
.text.ARM.extab:CC0B043A ASRS R4, R2, #1
.text.ARM.extab:CC0B043C EOR.W R8, R1, R0
.text.ARM.extab:CC0B0440 MOV R0, R9
.text.ARM.extab:CC0B0442 UXTH.W R11, R8
.text.ARM.extab:CC0B0446 MOV R1, R11 上面这段就是整个处理的核心部分其中R9中是参数
本次VMP运行参数如下:
02029000: EC 9B E5 D2 95 00 00 00 00 52 C6 E5 50 3B 00 00 .........R..P;..
这个参数的的第一个dword 是被加密函数的加密数据指针第五个字节是xor_key被加密数据需要通过xor 这个xor_key来解密。第三个dword是第二参数指针第二参数包含解密查询指针的低位值和查询表地址。
.text.ARM.extab:CC0B0448 BL sub_CC0BDEFE //模拟opcode查询算法
.text.ARM.extab:CC0B044C MOV R10, R0 //返回值就是opcode_v
.text.ARM.extab:CC0B044E CMP R0, #0x7E ; ~ //后面就是根据这个返回值处理
实现代码如下 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 def Get_Opcode_v(vm_index,table): args_list: 0xd1c0f790 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF 00000000 0c 1e 55 cf 91 00 00 00 00 52 de e4 60 b3 00 00 ..U......R..... 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF e4de5200 08 78 fa ce 20 ef e4 e4 b9 0d 00 00 00 00 00 00 .x.. ........... e4de5210 d0 01 4b e2 00 00 00 00 00 00 00 00 00 00 00 00 ..K............. e4de5220 00 00 00 00 48 27 0e e6 48 27 0e e6 01 00 00 00 ....H..H...... e4de5230 58 67 f9 cb 58 26 0e e6 04 00 00 00 b8 dd a1 e7 Xg..X.......... e4de5240 88 26 0e e6 04 00 00 00 4c 52 de e4 00 00 00 00 .......LR...... e4de5250 00 00 00 00 30 77 c7 d1 20 78 c7 d1 08 00 00 00 ....0w.. x...... e4de5260 40 8c 4f e2 64 00 00 00 80 54 de e4 d8 54 de e4 .O.d....T...T.. var opcode_table Memory.readPointer(buf.add(8)); // var table_args Memory.readByteArray(opcode_table.add(60), 0x10); // console.log(table_args); console.log(hexdump(opcode_table)); opcode_table Memory.readPointer(opcode_table.add(0x60)); console.log(Opcede_table_addr:, opcode_table); #key1 0xb某某某 % 0x64 #查询的高位为这个值 #key1 0x24C4 % 0x64 key1 0x3b50 % 0x64 key1 key1 8 key vm_index % 0x100 index key1 ^ key opcode_v int.from_bytes(ReadFile(table, index, 1), byteorderlittle, signedFalse) return opcode_v
也就是说先把加密函数的两个字节取出来然后xor xor_key 然后用解密后的前两个字节进入查询函数进行查询计算。得到查询的index根据index获取模拟的opcode值。
查询表格每个加密函数都有一个属于自己的表 在上面的查询过程中会使用inker:__dl_rtld_db_dlactivity 值来破坏查询值所以必须清零。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 看一段运行的日志 2020-10-13 14:05:20,625 INFO samples.debug_utils | 本次VMP运行参数如下: 2020-10-13 14:05:20,625 INFO samples.debug_utils | 02029000: EC 9B E5 D2 95 00 00 00 00 52 C6 E5 50 3B 00 00 .........R..P;.. 2020-10-13 14:05:20,628 INFO samples.debug_utils | vmp_opcode_v:0xb5c1 2020-10-13 14:05:20,628 INFO samples.debug_utils | vmp_opcode_v_dat:0x2054 2020-10-13 14:05:20,628 INFO samples.debug_utils | vmp_opcode:0x7a 2020-10-13 14:05:20,629 INFO samples.debug_utils | [0x7a] 2020-10-13 14:05:20,629 INFO samples.debug_utils | opcode run-- 2020-10-13 14:05:20,641 DEBUG androidemu.java.jni_env | JNIEnv-FindClass(androidx/appcompat/app/AppCompatActivity) was called 2020-10-13 14:05:20,643 DEBUG androidemu.java.jni_env | JNIEnv-NewGlobalRef(2) was called 2020-10-13 14:05:20,668 DEBUG androidemu.java.jni_env | JNIEnv-GetMethodId(class __main__.androidx_appcompat_app_AppCompatActivity, onCreate, (Landroid/os/Bundle;)V) was called 2020-10-13 14:05:20,669 INFO androidemu.java.jni_env | find method_id -0xd2000188 2020-10-13 14:05:20,684 DEBUG androidemu.java.jni_env | JNIEnv-DeleteLocalRef(2) was called call remove androidemu.java.jni_ref.jclass object at 0x0000015DF2728908 2020-10-13 14:05:20,687 INFO samples.debug_utils | vmp_opcode_v_ptr:0x3 2020-10-13 14:05:20,687 INFO samples.debug_utils | vmp_opcode_length:0x3 2020-10-13 14:05:20,687 INFO samples.debug_utils | [0x3] 2020-10-13 14:05:20,687 INFO samples.debug_utils | vmp_opcode_v:0x97ea 2020-10-13 14:05:20,687 INFO samples.debug_utils | vmp_opcode_v_dat:0x27f 2020-10-13 14:05:20,687 INFO samples.debug_utils | vmp_opcode:0x12 2020-10-13 14:05:20,688 INFO samples.debug_utils | [0x7a, 0x12] 2020-10-13 14:05:20,688 INFO samples.debug_utils | opcode run-- 2020-10-13 14:05:20,688 DEBUG samples.debug_utils | 0xcbd7b648: 2ada bge #0xcbd7b6a0 2020-10-13 14:05:20,688 DEBUG samples.debug_utils | 0xcbd7b64a: 3f28 cmp r0, #0x3f 2020-10-13 14:05:20,688 DEBUG samples.debug_utils | 0xcbd7b64c: 4cda bge #0xcbd7b6e8 2020-10-13 14:05:20,688 DEBUG samples.debug_utils | 0xcbd7b64e: 1f28 cmp r0, #0x1f 2020-10-13 14:05:20,689 DEBUG samples.debug_utils | 0xcbd7b650: 80f2e080 bge.w #0xcbd7b814 2020-10-13 14:05:20,689 DEBUG samples.debug_utils | 0xcbd7b654: 0f28 cmp r0, #0xf 2020-10-13 14:05:20,689 DEBUG samples.debug_utils | 0xcbd7b656: 80f25f81 bge.w #0xcbd7b918 2020-10-13 14:05:20,689 DEBUG samples.debug_utils | 0xcbd7b918: 1728 cmp r0, #0x17 2020-10-13 14:05:20,689 DEBUG samples.debug_utils | 0xcbd7b91a: 80f24382 bge.w #0xcbd7bda4 2020-10-13 14:05:20,690 DEBUG samples.debug_utils | 0xcbd7b91e: 1328 cmp r0, #0x13 2020-10-13 14:05:20,690 DEBUG samples.debug_utils | 0xcbd7b920: 80f23984 bge.w #0xcbd7c196 2020-10-13 14:05:20,690 DEBUG samples.debug_utils | 0xcbd7b924: 1128 cmp r0, #0x11 2020-10-13 14:05:20,690 DEBUG samples.debug_utils | 0xcbd7b926: 81f2c180 bge.w #0xcbd7caac 2020-10-13 14:05:20,690 DEBUG samples.debug_utils | 0xcbd7caac: 74f4c5a8 bne.w #0xcbd70c3a 2020-10-13 14:05:20,691 DEBUG samples.debug_utils | 0xcbd70c3a: d9f80010 ldr.w r1, [sb] 2020-10-13 14:05:20,691 DEBUG samples.debug_utils | 0xcbd70c3e: 99f80400 ldrb.w r0, [sb, #4] 2020-10-13 14:05:20,691 DEBUG samples.debug_utils | 0xcbd70c42: 2a68 ldr r2, [r5] 2020-10-13 14:05:20,691 DEBUG samples.debug_utils | 0xcbd70c44: 5b4b ldr r3, [pc, #0x16c] 2020-10-13 14:05:20,691 DEBUG samples.debug_utils | 0xcbd70c46: 8f88 ldrh r7, [r1, #4] 2020-10-13 14:05:20,691 DEBUG samples.debug_utils | 0xcbd70c48: 40ea0020 orr.w r0, r0, r0, lsl #8 2020-10-13 14:05:20,692 DEBUG samples.debug_utils | 0xcbd70c4c: 4988 ldrh r1, [r1, #2] 2020-10-13 14:05:20,692 DEBUG samples.debug_utils | 0xcbd70c4e: 7b44 add r3, pc 2020-10-13 14:05:20,692 DEBUG samples.debug_utils | 0xcbd70c50: 4740 eors r7, r0 2020-10-13 14:05:20,692 DEBUG samples.debug_utils | 0xcbd70c52: 4840 eors r0, r1 2020-10-13 14:05:20,692 DEBUG samples.debug_utils | 0xcbd70c54: c8f30721 ubfx r1, r8, #8, #8 2020-10-13 14:05:20,692 DEBUG samples.debug_utils | 0xcbd70c58: c0ea0740 pkhbt r0, r0, r7, lsl #0x10 2020-10-13 14:05:20,692 DEBUG samples.debug_utils | 0xcbd70c5c: 42f82100 str.w r0, [r2, r1, lsl #2] 2020-10-13 14:05:20,693 DEBUG samples.debug_utils | 0xcbd70c60: b0fa80f0 clz r0, r0 2020-10-13 14:05:20,693 DEBUG samples.debug_utils | 0xcbd70c64: 4009 lsrs r0, r0, #5 2020-10-13 14:05:20,693 DEBUG samples.debug_utils | 0xcbd70c66: 53f82000 ldr.w r0, [r3, r0, lsl #2] 2020-10-13 14:05:20,693 DEBUG samples.debug_utils | 0xcbd70c6a: 8746 mov pc, r0 2020-10-13 14:05:20,693 DEBUG samples.debug_utils | 0xcbd70c74: d9f80000 ldr.w r0, [sb] 2020-10-13 14:05:20,693 DEBUG samples.debug_utils | 0xcbd70c78: 0630 adds r0, #6 //opcode len 2020-10-13 14:05:20,694 DEBUG samples.debug_utils | 0xcbd70c7a: 0af0d0bc b.w #0xcbd7b61e 2020-10-13 14:05:20,694 DEBUG samples.debug_utils | 0xcbd7b61e: c9f80000 str.w r0, [sb] 2020-10-13 14:05:20,694 DEBUG samples.debug_utils | 0xcbd7b622: 0f9d ldr r5, [sp, #0x3c] 2020-10-13 14:05:20,694 DEBUG samples.debug_utils | 0xcbd7b624: d9f80000 ldr.w r0, [sb] 2020-10-13 14:05:20,696 INFO samples.debug_utils | vmp_opcode_v_ptr:0x6 2020-10-13 14:05:20,696 INFO samples.debug_utils | vmp_opcode_length:0x3 2020-10-13 14:05:20,697 INFO samples.debug_utils | [0x3, 0x3]
从这个运行日志中可以看到某某某加固有两种处理一种就是对Java class的处理还有一种就是数值处理。
对Java class的处理日志能明确的打印出结果根据结果基本上能知晓函数的作用。而对数值的处理比较麻烦某某某加固的vmp是自己模拟实现的需要跟踪调试反分析出来函数的作用。
另外opcode的长度是某某某加固vmp自己维护的。也就是根据opcode硬编码的所以需要记录下来。
现在来总结下某某某加固vmp的处理方法
1. 用需要加密的onCreate函数的参数常量参数anti参数生成的xor_key加密函数数据。
2. 每个opcode的第一个字节被某某某加固vmp的模拟opcode所替换并被上面的xor_key加密保护。
3. 每个opcode类型自己实现功能。
4. 每个opcode长度在实现代码中自己维护。硬编码实现。
5. 每个函数生成高地位的参数加密并和密文的opcode 计算获得查询index。
6. 每个函数都拥有一张模拟opcode_v查询表。模拟的opcode是通过这个表查询得到的。
也就是说模拟的opcode是两层加密解密出来的。并且都是自己实现具体功能的。 某某某加固VMP代码还原
首先要想还原得有一个官方的opcode文档推荐
Dalvik opcodes 从官方的文档中大致能知道opcode类型和opcode长度定义。
从文档中我们也知道dex的代码分为opcode操作码和后面的操作数组成的。
Opcode 占一个字节。后面都是操作数。
而某某某加固的VMP简单的加密了整个代码就是用xor_key 加密而重点的保护的就是opcode操作码还原代码其实也就是找回opcode操作码替换回去就行。后面的操作数通过xor_key还原出来就可以了。
下面就来重点说说怎么还原操作码的
从上面的分析来看就是两种类型处理Java class 的通过打印的日志基本就清楚是啥功能的。然后通过正常的代码结合官方文档大致就能还原出来。
而对数据的处理比较麻烦因为都是某某某加固系统自己处理的所以需要到每个处理单元里面跟踪分析。
当然最好是在AndroidNativeEm模拟器中运行这样能方便跟踪如果你能用ida动态跟踪也可以。
下面是一个demo加固后的跟踪结果和分析结果 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 2020-10-13 11:21:46,942 INFO samples.debug_utils | 本次VMP运行参数如下: 2020-10-13 11:21:46,942 INFO samples.debug_utils | 02029000: EC 9B E5 D2 95 00 00 00 00 52 C6 E5 50 3B 00 00 .........R..P;.. 2020-10-13 11:21:46,946 INFO samples.debug_utils | vmp_opcode:0x7a 6F invoke-super 2020-10-13 11:21:46,946 INFO samples.debug_utils | [0x7a] 2020-10-13 11:21:46,946 INFO samples.debug_utils | opcode run-- 2020-10-13 11:21:46,987 DEBUG androidemu.java.jni_env | JNIEnv-GetMethodId(class __main__.androidx_appcompat_app_AppCompatActivity, onCreate, (Landroid/os/Bundle;)V) was called 2020-10-13 11:21:46,988 INFO androidemu.java.jni_env | find method_id -0xd2000188 2020-10-13 11:21:47,007 INFO samples.debug_utils | vmp_opcode_length:0x3 2020-10-13 11:21:47,007 INFO samples.debug_utils | [0x3] 2020-10-13 11:21:47,008 INFO samples.debug_utils | vmp_opcode:0x12 14 const vx, lit32 2020-10-13 11:21:47,008 INFO samples.debug_utils | [0x7a, 0x12] 2020-10-13 11:21:47,008 INFO samples.debug_utils | opcode run-- 2020-10-13 11:21:47,012 DEBUG samples.debug_utils | 0xcbd70c3a: d9f80010 ldr.w r1, [sb] data Buff 2020-10-13 11:21:47,012 DEBUG samples.debug_utils | 0xcbd70c3e: 99f80400 ldrb.w r0, [sb, #4] xor key 2020-10-13 11:21:47,012 DEBUG samples.debug_utils | 0xcbd70c42: 2a68 ldr r2, [r5] 2020-10-13 11:21:47,012 DEBUG samples.debug_utils | 0xcbd70c46: 8f88 ldrh r7, [r1, #4] 2020-10-13 11:21:47,013 DEBUG samples.debug_utils | 0xcbd70c48: 40ea0020 orr.w r0, r0, r0, lsl #8 2020-10-13 11:21:47,013 DEBUG samples.debug_utils | 0xcbd70c4c: 4988 ldrh r1, [r1, #2] 2020-10-13 11:21:47,013 DEBUG samples.debug_utils | 0xcbd70c50: 4740 eors r7, r0 解密数据 2020-10-13 11:21:47,013 DEBUG samples.debug_utils | 0xcbd70c52: 4840 eors r0, r1 解密数据 2020-10-13 11:21:47,014 DEBUG samples.debug_utils | 0xcbd70c54: c8f30721 ubfx r1, r8, #8, #8 2020-10-13 11:21:47,014 DEBUG samples.debug_utils | 0xcbd70c58: c0ea0740 pkhbt r0, r0, r7, lsl #0x10 合并寄存器值 取常量给寄存器 2020-10-13 11:21:47,014 DEBUG samples.debug_utils | 0xcbd70c5c: 42f82100 str.w r0, [r2, r1, lsl #2] R00x7f0a001c 2020-10-13 11:21:47,018 INFO samples.debug_utils | vmp_opcode_length:0x3 2020-10-13 11:21:47,018 INFO samples.debug_utils | [0x3, 0x3] 2020-10-13 11:21:47,018 INFO samples.debug_utils | vmp_opcode:0x1 6e invoke-virtual 2020-10-13 11:21:47,018 INFO samples.debug_utils | [0x7a, 0x12, 0x1] 2020-10-13 11:21:47,019 INFO samples.debug_utils | opcode run-- 2020-10-13 11:21:47,032 DEBUG androidemu.java.jni_env | JNIEnv-FindClass(com/example/test/MainActivity) was called 2020-10-13 11:21:47,033 DEBUG androidemu.java.jni_env | JNIEnv-NewGlobalRef(3) was called 2020-10-13 11:21:47,053 DEBUG androidemu.java.jni_env | JNIEnv-GetMethodId(class __main__.com_example_test_MainActivity, setContentView, (I)V) was called 2020-10-13 11:21:47,053 INFO androidemu.java.jni_env | find method_id -0xd2000190 2020-10-13 11:21:53,918 INFO samples.debug_utils | vmp_opcode_length:0x3 2020-10-13 11:21:53,918 INFO samples.debug_utils | [0x3, 0x3, 0x3] 2020-10-13 11:21:53,918 INFO samples.debug_utils | vmp_opcode:0x12 2020-10-13 11:21:53,918 INFO samples.debug_utils | [0x7a, 0x12, 0x1, 0x12] 2020-10-13 11:21:53,918 INFO samples.debug_utils | opcode run-- 2020-10-13 11:21:53,927 INFO samples.debug_utils | vmp_opcode_length:0x3 2020-10-13 11:21:53,927 INFO samples.debug_utils | [0x3, 0x3, 0x3, 0x3] 2020-10-13 11:21:53,927 INFO samples.debug_utils | vmp_opcode:0x1 2020-10-13 11:21:53,927 INFO samples.debug_utils | [0x7a, 0x12, 0x1, 0x12, 0x1] 2020-10-13 11:21:53,927 INFO samples.debug_utils | opcode run-- 2020-10-13 11:21:53,955 DEBUG androidemu.java.jni_env | JNIEnv-GetMethodId(class __main__.com_example_test_MainActivity, findViewById, (I)Landroid/view/View;) was called 2020-10-13 11:21:53,955 INFO androidemu.java.jni_env | find method_id -0xd200018c 2020-10-13 11:21:53,968 DEBUG androidemu.java.jni_env | JNIEnv-CallObjectMethodV(com/example/test/MainActivity, findViewById (I)Landroid/view/View;, 0x2033000) was called 2020-10-13 11:21:53,975 INFO samples.debug_utils | vmp_opcode_length:0x3 2020-10-13 11:21:53,975 INFO samples.debug_utils | [0x3, 0x3, 0x3, 0x3, 0x3] 2020-10-13 11:21:53,975 INFO samples.debug_utils | vmp_opcode:0x21 0x0c move-result-object vx 2020-10-13 11:21:53,975 INFO samples.debug_utils | [0x7a, 0x12, 0x1, 0x12, 0x1, 0x21] 2020-10-13 11:21:53,975 INFO samples.debug_utils | opcode run-- 2020-10-13 11:21:53,978 DEBUG samples.debug_utils | 0xcbd7c99c: 2746 mov r7, r4 2020-10-13 11:21:53,978 DEBUG samples.debug_utils | 0xcbd7c99e: 0a9c ldr r4, [sp, #0x28] 2020-10-13 11:21:53,978 DEBUG samples.debug_utils | 0xcbd7c9a0: 3846 mov r0, r7 2020-10-13 11:21:53,978 DEBUG samples.debug_utils | 0xcbd7c9a2: ab46 mov fp, r5 2020-10-13 11:21:53,978 DEBUG samples.debug_utils | 0xcbd7c9a4: 4ff00008 mov.w r8, #0 2020-10-13 11:21:53,979 DEBUG samples.debug_utils | 0xcbd7c9a8: a268 ldr r2, [r4, #8] 2020-10-13 11:21:53,979 DEBUG samples.debug_utils | 0xcbd7c9aa: e4f731fe bl #0xcbd61610 2020-10-13 11:21:53,979 DEBUG samples.debug_utils | 0xcbd61610: 70b5 push {r4, r5, r6, lr} 2020-10-13 11:21:53,979 DEBUG samples.debug_utils | 0xcbd61612: 0546 mov r5, r0 2020-10-13 11:21:53,979 DEBUG samples.debug_utils | 0xcbd61614: c069 ldr r0, [r0, #0x1c] 2020-10-13 11:21:53,979 DEBUG samples.debug_utils | 0xcbd61616: 1446 mov r4, r2 2020-10-13 11:21:53,980 DEBUG samples.debug_utils | 0xcbd61618: 0e46 mov r6, r1 2020-10-13 11:21:53,980 DEBUG samples.debug_utils | 0xcbd6161a: 50f82100 ldr.w r0, [r0, r1, lsl #2] 2020-10-13 11:21:53,980 DEBUG samples.debug_utils | 0xcbd6161e: 10b1 cbz r0, #0xcbd61626 2020-10-13 11:21:53,980 DEBUG samples.debug_utils | 0xcbd61620: 0068 ldr r0, [r0] 2020-10-13 11:21:53,980 DEBUG samples.debug_utils | 0xcbd61622: a042 cmp r0, r4 2020-10-13 11:21:53,980 DEBUG samples.debug_utils | 0xcbd61624: 13d0 beq #0xcbd6164e 2020-10-13 11:21:53,981 DEBUG samples.debug_utils | 0xcbd6164e: a869 ldr r0, [r5, #0x18] 2020-10-13 11:21:53,981 DEBUG samples.debug_utils | 0xcbd61650: 002c cmp r4, #0 2020-10-13 11:21:53,981 DEBUG samples.debug_utils | 0xcbd61652: 40f82640 str.w r4, [r0, r6, lsl #2] 2020-10-13 11:21:53,981 DEBUG samples.debug_utils | 0xcbd61656: 14d0 beq #0xcbd61682 2020-10-13 11:21:53,981 DEBUG samples.debug_utils | 0xcbd61682: 70bd pop {r4, r5, r6, pc} 2020-10-13 11:21:53,981 DEBUG samples.debug_utils | 0xcbd7c9ae: 84f81080 strb.w r8, [r4, #0x10] 2020-10-13 11:21:53,982 DEBUG samples.debug_utils | 0xcbd7c9b2: 3c46 mov r4, r7 2020-10-13 11:21:53,985 INFO samples.debug_utils | vmp_opcode_length:0x1 2020-10-13 11:21:53,985 INFO samples.debug_utils | [0x3, 0x3, 0x3, 0x3, 0x3, 0x1] 2020-10-13 11:21:53,985 INFO samples.debug_utils | vmp_opcode:0xa6 0x1f check-cast vx, type_id 2020-10-13 11:21:53,985 INFO samples.debug_utils | [0x7a, 0x12, 0x1, 0x12, 0x1, 0x21, 0xa6] 2020-10-13 11:21:53,985 INFO samples.debug_utils | opcode run-- 2020-10-13 11:21:53,993 DEBUG androidemu.java.jni_env | JNIEnv-FindClass(android/widget/TextView) was called 2020-10-13 11:21:54,004 INFO samples.debug_utils | vmp_opcode_length:0x2 2020-10-13 11:21:54,004 INFO samples.debug_utils | [0x3, 0x3, 0x3, 0x3, 0x3, 0x1, 0x2] 2020-10-13 11:21:54,005 INFO samples.debug_utils | vmp_opcode:0x1 2020-10-13 11:21:54,005 INFO samples.debug_utils | [0x7a, 0x12, 0x1, 0x12, 0x1, 0x21, 0xa6, 0x1] 2020-10-13 11:21:54,005 INFO samples.debug_utils | opcode run-- 2020-10-13 11:21:54,030 DEBUG androidemu.java.jni_env | JNIEnv-GetMethodId(class __main__.com_example_test_MainActivity, stringFromJNI, ()Ljava/lang/String;) was called 2020-10-13 11:21:54,030 INFO androidemu.java.jni_env | find method_id -0xd2000194 2020-10-13 11:21:54,041 DEBUG androidemu.java.jni_env | JNIEnv-CallObjectMethodV(com/example/test/MainActivity, stringFromJNI ()Ljava/lang/String;, 0x2035000) was called 2020-10-13 11:21:54,048 INFO samples.debug_utils | vmp_opcode_length:0x3 2020-10-13 11:21:54,048 INFO samples.debug_utils | [0x3, 0x3, 0x3, 0x3, 0x3, 0x1, 0x2, 0x3] 2020-10-13 11:21:54,048 INFO samples.debug_utils | vmp_opcode:0x21 2020-10-13 11:21:54,048 INFO samples.debug_utils | [0x7a, 0x12, 0x1, 0x12, 0x1, 0x21, 0xa6, 0x1, 0x21] 2020-10-13 11:21:54,048 INFO samples.debug_utils | opcode run-- 2020-10-13 11:21:54,065 INFO samples.debug_utils | vmp_opcode_length:0x1 2020-10-13 11:21:54,065 INFO samples.debug_utils | [0x3, 0x3, 0x3, 0x3, 0x3, 0x1, 0x2, 0x3, 0x1] 2020-10-13 11:21:54,065 INFO samples.debug_utils | vmp_opcode:0x1 2020-10-13 11:21:54,065 INFO samples.debug_utils | [0x7a, 0x12, 0x1, 0x12, 0x1, 0x21, 0xa6, 0x1, 0x21, 0x1] 2020-10-13 11:21:54,065 INFO samples.debug_utils | opcode run-- 2020-10-13 11:21:54,094 DEBUG androidemu.java.jni_env | JNIEnv-GetMethodId(class __main__.android_widget_TextView, setText, (Ljava/lang/CharSequence;)V) was called 2020-10-13 11:21:54,094 INFO androidemu.java.jni_env | find method_id -0xd2000170 2020-10-13 11:21:54,114 INFO samples.debug_utils | vmp_opcode_length:0x3 2020-10-13 11:21:54,114 INFO samples.debug_utils | [0x3, 0x3, 0x3, 0x3, 0x3, 0x1, 0x2, 0x3, 0x1, 0x3] 2020-10-13 11:21:54,114 INFO samples.debug_utils | vmp_opcode:0xc5 0x0e return-void 2020-10-13 11:21:54,114 INFO samples.debug_utils | [0x7a, 0x12, 0x1, 0x12, 0x1, 0x21, 0xa6, 0x1, 0x21, 0x1, 0xc5] 2020-10-13 11:21:54,114 INFO samples.debug_utils | opcode run-- 2020-10-13 11:21:54,117 DEBUG samples.debug_utils | 0xcbd7d2a6: 0598 ldr r0, [sp, #0x14] 2020-10-13 11:21:54,117 DEBUG samples.debug_utils | 0xcbd7d2a8: 099a ldr r2, [sp, #0x24] 2020-10-13 11:21:54,117 DEBUG samples.debug_utils | 0xcbd7d2aa: d0e90001 ldrd r0, r1, [r0] 2020-10-13 11:21:54,117 DEBUG samples.debug_utils | 0xcbd7d2ae: c2e90001 strd r0, r1, [r2] 2020-10-13 11:21:54,117 DEBUG samples.debug_utils | 0xcbd7d2b2: 64a8 add r0, sp, #0x190 2020-10-13 11:21:54,117 DEBUG samples.debug_utils | 0xcbd7d2b4: 0021 movs r1, #0 2020-10-13 11:21:54,118 DEBUG samples.debug_utils | 0xcbd7d2b6: 04f026fe bl #0xcbd81f06 2020-10-13 11:21:54,118 DEBUG samples.debug_utils | 0xcbd81f06: 0a46 mov r2, r1 2020-10-13 11:21:54,118 DEBUG samples.debug_utils | 0xcbd81f08: 0168 ldr r1, [r0] 2020-10-13 11:21:54,118 DEBUG samples.debug_utils | 0xcbd81f0a: 0260 str r2, [r0] 2020-10-13 11:21:54,118 DEBUG samples.debug_utils | 0xcbd81f0c: 09b1 cbz r1, #0xcbd81f12 2020-10-13 11:21:54,118 DEBUG samples.debug_utils | 0xcbd81f0e: 00f001b8 b.w #0xcbd81f14 2020-10-13 11:21:54,119 DEBUG samples.debug_utils | 0xcbd81f14: 49b1 cbz r1, #0xcbd81f2a 2020-10-13 11:21:54,119 DEBUG samples.debug_utils | 0xcbd81f16: 10b5 push {r4, lr} 2020-10-13 11:21:54,119 DEBUG samples.debug_utils | 0xcbd81f18: 0846 mov r0, r1 2020-10-13 11:21:54,119 DEBUG samples.debug_utils | 0xcbd81f1a: 0c46 mov r4, r1 2020-10-13 11:21:54,119 DEBUG samples.debug_utils | 0xcbd81f1c: fef752f8 bl #0xcbd7ffc4 2020-10-13 11:21:54,119 DEBUG samples.debug_utils | 0xcbd7ffc4: 10b5 push {r4, lr} 2020-10-13 11:21:54,120 DEBUG samples.debug_utils | 0xcbd7ffc6: 0446 mov r4, r0 2020-10-13 11:21:54,120 DEBUG samples.debug_utils | 0xcbd7ffc8: 007c ldrb r0, [r0, #0x10] 2020-10-13 11:21:54,120 DEBUG samples.debug_utils | 0xcbd7ffca: 38b1 cbz r0, #0xcbd7ffdc 2020-10-13 11:21:54,120 DEBUG samples.debug_utils | 0xcbd7ffdc: 0020 movs r0, #0 2020-10-13 11:21:54,120 DEBUG samples.debug_utils | 0xcbd7ffde: 2074 strb r0, [r4, #0x10] 2020-10-13 11:21:54,120 DEBUG samples.debug_utils | 0xcbd7ffe0: 10bd pop {r4, pc} 2020-10-13 11:21:54,121 DEBUG samples.debug_utils | 0xcbd81f20: 2046 mov r0, r4 2020-10-13 11:21:54,121 DEBUG samples.debug_utils | 0xcbd81f22: bde81040 pop.w {r4, lr}
通过上面的日志分析基本上还原出来代码。
opcode_len_dat [3,3,3,3,3,1,2,3,1,3,1]
xor_key 0x9595
opcode_操作码 [0x7a, 0x12, 0x1, 0x12, 0x1, 0x21, 0xa6, 0x1, 0x21, 0x1, 0xc5]
使用的opcode种类 [0x7a, 0x12, 0x1, 0x21, 0xa6, 0xc5] 合计个数 6
恢复的代码 6f20e70d210014021c000a7f6e20513b210014027e00077f6e204f3b21000c021c0200026e10523b01000c006e20fe0b02000e00
用这个还原的代码修复dex文件就可以实现剥离某某某加固还原原程序了。 Dex 二进制修改方法
用IDA打开dex文件搜索class名称找到要修改的方法的地方 看到Native函数定义的数据了吧。比较下正常的public函数定义知道类型应该是0004而不是0284后面跟着的是code地址修改这个数据
直接到IDA的Hex View页中按F2修改 下面修改code offset这个地址是LEB128编码编码介绍
https://berryjam.github.io/2019/09/LEB128(Little-Endian-Base-128)格式介绍/
Andorid系统在Dex文件采用LEB128变长编码格式相对固定长度的编码格式leb128编码存储利用率比较高能让Dex文件尽可能的小。对于存储空间比较紧缺的移动设备这非常有用。就是在字节的第七位插入1计算地址时去掉这个第七位的1再组合。
来的偏移地址0x1b7478处从IDA中也可以看到前面0x10是函数头参数后面就是代码了 保存用IDA重新打开
除了偏移地址发生改变基本上一样。说明还原成功。 某某某加固修正重新打包技术要点如下
1. 首先需要使用自己编写的某某某_jiagu_info 脚本跑出oat文件的dexdump出来oat文件后用winhex把dex头前面的数据去掉就是dex文件。
2.一般dump 多个dex文件需要处理所有的oat文件得到所有的dex
3.一般被加固的apk的原始入口在Manifest xml中已经改变需要找回原来的入口这个尤其重要这个坑踩了好久。这个入口中脚本中会打印出来App_Application_Entry is: 4.由于Java vm并不能运行nop指令一般也不可能出现这个指令所以清除某某某加固的SDK时如果不能进行code对齐中间有nop 时Java vm会执行错误。所以建议保留SDK但是修改SDK代码为返回原值就行。
某某某加固一般有三种SDK插入 主onCreate函数的 stub-Mark() 这个少一般只有一个反编译后直接在smile中可以去掉搜索dex中的mark就能找到地方。 onCreate 的interface11 这个由于是在函数的开头插入所以可以写代码清除并进行code对齐。 需要写代码修复。先用winhex通过特征码搜索所有的调用地址然后导出这个搜索结果给下面修复脚本用就能修复好。
脚本见附件某某某加固dex嵌入SDK修复脚本。 invoke-static/range StubApp-getOrigApplicationContext(Context)Context, v0 .. v0 这种数量巨大一般都是在调用getcontext函数的后面所以建议保留请修改StubApp里面的函数代码直接返回原值即可。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 修改成 .class public final Lcom/stub/StubApp; .super Landroid/app/Application; .source SourceFile # static fields .field private static loadFromLib:Z .field private static needX86Bridge:Z .field public static strEntryApplication:Ljava/lang/String; .end field # direct methods .method public static getOrigApplicationContext(Landroid/content/Context;)Landroid/content/Context; .locals 1 .prologue .line 68 return-object p0 .end method
5. 用dextools工具dump的dex存在问题需要用自己写的某某某_info脚本跑出来的oat修复的dex才可以。 把上面修改后的classes文件和dump的所有其他dex文件一起放到apk包中用apktool工具反编译后修改mark的地方就是去掉就行。然后修改Manifest xml中application 选项中的android:name 为 Python 跑出来的App_Application_Entry 6. 如果存在onCreate VMP 那就复杂需要修复vmp后才能进行上面的反编译修改再打包。vmp的修复最好是用AndroidNativeEmu 模拟器修复。修复过程在某某某加固分析文档中有。参数和数据需要自己到内存中去抓。 附录1 获取某某某加固信息脚本 本脚本用来获取某某某加固系统关键数据包含dump so查询apk的原始入口根据特征码搜索onCreate vmp处理函数等。