赞
踩
本分析还是基本上以AndroidNativeEmU模拟器分析日志为主。不过为了保证函数可以正常运行,部分数据来自真实环境,并还原到原偏移地址中,保证模拟器正常执行。本次分析还首次在模拟器中实现art模式下的Dex加载过程,不用到真实环境中去dump dex了。
第一、对自身模块的保护:
APP加固系统都自身模块的保护并没有像360加固保护系统那么强大和复杂,就是使用了code段加密,然后中so的init_array中进行解密,然后抹除so头,防止内存dump的方式。到JNI_OnLoad的时候代码段已经解密完成,这个时候dump下代码,然后把原始的头还原回去基本上就能用来分析了,如果要用模拟器调试,还得patch掉解密和抹除so头的代码。
下面来看具体的代码实现。
Calling Init for: samples/appjiagu/libappprotect-up.so
Calling Init function: cbff8d85//这个就是init_array中的第一个函数,解密函数。
把so头0x1000字节清零:
Executing syscall mprotect(cbfab000, 00001000, 00000003) at 0xcbffd165``call mprotect:0xcbfab000 ç基地址,即so头地址``======================= Registers =======================` `R0=0xcbfab034 R1=0x0 R2=0x1 R3=0x1``R4=0x7ffce8 R5=0x7ffce8 R6=0x7ffe90 R7=0x7ffff8 R8=0x0``R9=0x0 R10=0x0 R11=0x0 R12=0x7ffce0 SP=0x7ffce0``LR=0xcbffd165 PC=0xcbffd60c``======================= Disassembly =====================``0xcbffd60c: 0170 strb r1, [r0] ç开始把so头清零``0xcbffd60e: 6e48 ldr r0, [pc, #0x1b8]``0xcbffd610: 7f49 ldr r1, [pc, #0x1fc]``0xcbffd612: 7944 add r1, pc``0xcbffd614: 4018 adds r0, r0, r1
解密代码段中加密的代码:
Executing syscall mprotect(cbfae6f1, 00042C22, 00000007) at 0xcbffab7f
解密代码段中加密的代码:
Executing syscall mprotect(cbfae6f1, 00042C22, 00000007) at 0xcbffab7f
到这里:
都是被加密的代码。
======================= Registers =======================` `R0=0x47 R1=0xcbfae6f1 R2=0x7ffe60 R3=0x7ffe58``R4=0x7ffcf8 R5=0x7ffdd8 R6=0x7ffe90 R7=0x7ffff8 R8=0x0``R9=0x0 R10=0x0 R11=0x0 R12=0xffff1c30 SP=0x7ffce0``LR=0xcbffe2f3 PC=0xcbffca94``======================= Disassembly =====================``0xcbffca94: 0870 strb r0, [r1] < --R1=0xcbfae6f1``0xcbffca96: 1878 ldrb r0, [r3]``0xcbffca98: 1168 ldr r1, [r2]``0xcbffca9a: 0870 strb r0, [r1]``0xcbffca9c: 1020 movs r0, #0x10
这个函数执行完就可以dump so,然后修复代码了。
JNI_OnLoad:代码已经解密出来了:
0xcbfae9c4: f0b5 push {r4, r5, r6, r7, lr}``0xcbfae9c6: 03af add r7, sp, #0xc``0xcbfae9c8: 89b0 sub sp, #0x24``0xcbfae9ca: 6e46 mov r6, sp``0xcbfae9cc: 3162 str r1, [r6, #0x20]``>dump 0xcbfab000``CBFAB000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................``CBFAB010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................``CBFAB020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................``CBFAB030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
可以看到so头被清除了。
修复so方法:
先dump 数据,然后用010Editor 把原始的头贴回去。
由于代码已经被解出来,所以后面不能再被解密了,patch代码,第一个就是把清除so头的代码除掉。然后把解密代码的写入操作代码也nop掉。这样就保证了可以二次加载这个so进行运行分析。
0xcbffd60c: 0170 strb r1, [r0] ç开始把so头清零 nop掉代码
0xcbffca94: 0870 strb r0, [r1] < --R1=0xcbfae6f1 nop掉
0xcbffca96: 1878 ldrb r0, [r3]
0xcbffca98: 1168 ldr r1, [r2]
0xcbffca9a: 0870 strb r0, [r1] nop掉代码
0xcbffca9c: 1020 movs r0, #0x10
从分析来看,APP加固对自身模块的保护力度不是很强,修复起来也比较容易。
JNI_OnLoad 函数主要就是把libc中的这些函数填到函数列表中,然后通过列表的索引进行调用。这样防止分析代码:
Called dlopen(libc.so)``Loading module 'vfs/system/lib/libc.so'.``call malllos size:0x9 at 0xcbfc15e7``malloc addr:0x2022000``Called dlsym(0xcbbdf000, _exit) at 0xcbfb1669``symbol:_exit addr->: 0xcbc2780c``call malllos size:0x9 at 0xcbfc166f``malloc addr:0x2023000``Called dlsym(0xcbbdf000, exit) at 0xcbfb167f
导入的函数有:
#libc_fun_name = ["_exit","exit","pthread_create","pthread_join","memcpy","malloc","calloc","memset","fopen","fclose","fgets","strtoul","strtoull","strstr","ptrace","mprotect","strlen","sscanf","free","strdup","strcmp","strcasecmp","utime","mkdir","open","close","unlink","stat64","time","snprintf","strchr","strncmp","pthread_detach","pthread_self","opendir","readdir","closedir","mmap","munmap","lseek","fstat","read","select","bsd_signal","fork","prctl","setrlimit","getppid","getpid","waitpid","kill","flock","write","execve","execv","execl","sysconf","__system_property_get","ftruncate","gettid","pread64","pwrite64","pread","pwrite"," ","statvfs"]
后面就是注册加固系统com.app.protect.A 主功能函数:
JNIEnv->FindClass(com/app/protect/A) was called``JNIEnv->RegisterNatives(1, 0x007fff58, 4) was called``Register native ('n001', '(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IZ)V',function->'0xcbfaf829') failed on class com_app_protect_A.``Register native ('n002', '(Landroid/content/Context;)V',function->'0xcbfaf999') failed on class com_app_protect_A.``Register native ('n003', '()[Ljava/lang/String;',function->'0xcbfaf9f9') failed on class com_app_protect_A.``Register native ('n004', '()V',function->'0xcbfafad5') failed on class com_app_protect_A.
这个版本的加固中,注册了四个函数。主要的功能在’n001’这个函数中。包括dex解压,解密,加载,附加数据的处理。Dex_VMP数据初始化。
另外Native so中大量使用字符串加密,防止关键字符串暴露,也是APP加固的技术特点:
第一、
第二、对dex文件的保护:
Android APP加固系统保护的重点就是dex,所以对dex文件的保护是比较重要的。APP加固系统这块也做的比较好,甚至全程没有dex明文文件落地,都是从apk包中在运行时解压解密加载。具体我们来看看流程:
APP加固的JNI_OnLoad中注册了四个Native函数,其中n001函数就是处理dex解压,解密,加载和vmp数据的。
` `protected void attachBaseContext(Context arg8) {` `StubApplication.mContext = arg8;` `StubApplication.mBootStrapApplication = this;` `AppInfo.APKPATH = arg8.getApplicationInfo().sourceDir;` `AppInfo.DATAPATH = StubApplication.getDataFolder(arg8.getApplicationInfo().dataDir);` `if(!Debug.isDebuggerConnected()) {` `StubApplication.loadLibrary();` `A.n001(AppInfo.PKGNAME, AppInfo.APPNAME, AppInfo.APKPATH, AppInfo.DATAPATH, Build.VERSION.SDK_INT, AppInfo.REPORT_CRASH);` `}`` ` `if(AppInfo.APPNAME != null && AppInfo.APPNAME.length() > 0) {` `StubApplication.mRealApplication = MonkeyPatcher.createRealApplication(AppInfo.APPNAME);` `}`` ` `super.attachBaseContext(arg8);` `if(StubApplication.mRealApplication != null) {` `MonkeyPatcher.attachBaseContext(arg8, StubApplication.mRealApplication);` `}``}``
Java层的入口,可以看出来,首先对调试状态进行了检测,如果是调试状态就不会加载so模块,也不会执行dex的解密函数A.n001.防止APP被调试。如果没有被调试则加载完libappprotect.so后进入dex的加载函数,即n001函数。从上面我们知道JNI_OnLoad中注册了这个函数,函数地址是:function->‘0xcbfaf829’,我们就看看这个Native函数的具体功能:
首先获取APP的包信息,从包信息中获取signatures,从apk包的META-INF目录读取签名文件TRANSLAT.RSA,然后从0x3A开始读两个字节的签名数据长度,根据长度读取后面的签名数据。比如这个APP的签名长度是0x235
读取数据如下:
00000040: 30 82 02 31 30 82 01 9A A0 03 02 01 02 02 04 4E 0..10..........N``00000050: 25 42 6B 30 0D 06 09 2A 86 48 86 F7 0D 01 01 05 %Bk0...APP.H......``00000060: 05 00 30 5D 31 0B 30 09 06 03 55 04 06 13 02 43 ..0]1.0...U....C``00000070: 4E 31 10 30 0E 06 03 55 04 08 13 07 62 65 69 6A N1.0...U....beij
然后对这个签名文件取hash:
.text:CBFB71CE 28 4D LDR R5, =(_GLOBAL_OFFSET_TABLE_ - 0xCBFB71D4)``.text:CBFB71D0 7D 44 ADD R5, PC ; _GLOBAL_OFFSET_TABLE_``.text:CBFB71D2 44 19 ADDS R4, R0, R5 ; byte_CC00F664``.text:CBFB71D4 20 46 MOV R0, R4``.text:CBFB71D6 39 46 MOV R1, R7``.text:CBFB71D8 1B F0 E8 FA BL Fun_md5 ; 获取apk 数字签名,然后MD5这个签名,给后面的dex decode 做key``
2020-11-11 17:39:40,906 DEBUG androidemu.native.hooks | call memcpy (len:0x10)``2020-11-11 17:39:40,906 DEBUG androidemu.native.hooks | memcpy_data:0xcc00f664-->0x20cdb11``2020-11-11 17:39:40,907 INFO androidemu.native.hooks | addr -->0xcbfb6f9b``2020-11-11 17:39:40,907 DEBUG androidemu.native.hooks |``CC00F664: 05 86 74 2E 88 A2 E6 A1 9E 99 65 98 EC 33 6B 61 ..t.......e..3ka <== md5``
这个hash值用于后面dex前0x1000字节的解密key。这样设计的目的是在用防止APP被二次打包,打包后的签名已经改变,改变后的这个签名hash值也会改变,然后就不能再解密出正确的dex文件。对dex的保护起到了一定的作用,如果在不能完整恢复原dex代码的情况下,加固保护系统就能起作用。
后面就是从apk包中解压assets目录下所有的jar文件。这个jar文件就是dex的密文,也就是前0x1000个字节没有被解密的dex文件。
2020-11-16 14:23:04,042 INFO androidemu.native.hooks | string-->'assets/appprotect1.jar'``2020-11-16 14:23:04,045 DEBUG androidemu.native.hooks | call memcmp;data1->0x21435d9,data2->0x7ff8d8,len->24``2020-11-16 14:23:04,045 DEBUG androidemu.native.hooks | mem1_data:``2020-11-16 14:23:04,045 DEBUG androidemu.native.hooks |``021435D9: 61 73 73 65 74 73 2F 62 61 69 64 75 70 72 6F 74 assets/appprot``021435E9: 65 63 74 31 2E 6A 61 72 ect1.jar``2020-11-19 11:23:30,452 DEBUG androidemu.native.memory | call malllos size:0x7e62dc at 0xcbfd291b``2020-11-19 11:23:30,455 INFO androidemu.native.memory | malloc addr:0x21db000 ß申请到的空间,后面存放解压数据。size:0x7e62dc 是appprotect1.jar的大小。``
读取这个数据后使用libart模块中的zlib函数进行解压:
if ( !j_inflateInit2_(&v19, 0xFFFFFFF1, "1.2.3", 0x38) )` `{` `if ( j_inflate(&v19, 4) == 1 )` `{` `v10 = v23;` `j_inflateEnd(&v19);` `if ( v10 == v9 )` `{``LABEL_13:` `v6 = 1;` `if ( APP(_DWORD APP)&v16 >= 0x8001u )` `sub_CBFB11A0(v8, 0);` `goto LABEL_16;` `}` `}` `else` `{` `j_inflateEnd(&v19);` `}` `}
这个时候的dex结构如上图所示,下面开始解这些部分。首先用签名的hash生成的key解密前0x1000字节:
这个解压出来的数据前0x1000个字节是密文的,后面需要用APP签名的hash值来解密。
解密算法:
======================= Registers =======================` `R0=0x21db000 R1=0x7ff758 R2=0x20cdc3c R3=0xcbff04f9``R4=0x21db000 R5=0x7ff7b0 R6=0x1000 R7=0xcbff04f9 R8=0x0``R9=0x0 R10=0x0 R11=0x0 R12=0xcc00eef8 SP=0x7ff720``LR=0xcbff0e11 PC=0xcbff0ca8``======================= Disassembly =====================``0xcbff0ca8: b847 blx r7 <=解密函数``0xcbff0caa: 3b46 mov r3, r7``0xcbff0cac: 2868 ldr r0, [r5]``0xcbff0cae: 029a ldr r2, [sp, #8]``0xcbff0cb0: 1168 ldr r1, [r2]``>dump 0x20cdc3c // APP 签名hash生成的解密key``020CDC3C: 4D 23 BC 03 9D 27 9A 95 B2 85 D7 ED 76 C3 3D BA M#...'......v.=.``020CDC4C: 10 D7 1A A0 C0 A0 FE FA 1D E9 14 58 9D C1 CD AE ...........X....``020CDC5C: 52 4B 9B 2D D0 77 E4 5A DD 49 EA A2 80 28 D9 F6 RK.-.w.Z.I...(..``020CDC6C: 7C 0A 2B 97 82 3C 7F 77 0D 3E 0E F8 5D 61 33 54 |.+..<.w.>..]a3T``>dump 0x21db000 // dex 密文数据``021DB000: E3 7A E6 20 86 8C 4B 89 F8 74 A9 AF 65 5B 06 78 .z. ..K..t..e[.x``021DB010: 69 DD 9E C2 C2 68 85 1E A5 A7 35 E0 88 96 E4 4F i....h....5....O``021DB020: 38 16 B1 B7 84 AB 0A E8 7F E1 5D 3C 74 A6 24 FF 8.........]<t.$.```021DB030: 82 CB 1D 61 60 AF 48 8C 9F 50 11 BF C9 72 98 2E ...a`.H..P...r..`
到这里全部解出来了:
======================= Registers =======================` `R0=0x76e69e89 R1=0x195a3f R2=0x7ff758 R3=0xcbff04f9``R4=0x21dc000 R5=0x7ff7b0 R6=0x0 R7=0xcbff04f9 R8=0x0``R9=0x0 R10=0x0 R11=0x0 R12=0xcc00eef8 SP=0x7ff720``LR=0xcbff0cab PC=0xcbff0ce6``======================= Disassembly =====================``0xcbff0ce6: 0b98 ldr r0, [sp, #0x2c]``0xcbff0ce8: 0a99 ldr r1, [sp, #0x28]
这个时候的dex有部分代码被移走了,需要重新解密填充回来:
下面的函数就是解密填充:
======================= Registers =======================` `R0=0x21db000 R1=0x7e62dc R2=0x0 R3=0xcbff04f9``R4=0x0 R5=0x21db000 R6=0x7ff804 R7=0xcbc3064d R8=0x0``R9=0x0 R10=0x0 R11=0x0 R12=0xcc00eef8 SP=0x7ff7d8``LR=0xcbff0cab PC=0xcbfd2954``======================= Disassembly =====================``0xcbfd2954: e8f79efc bl #0xcbfbb294 //解密并填充回被移动走的代码的函数。``2020-11-19 15:40:58,411 DEBUG androidemu.native.hooks | call memcpy (len:0x739dc)``2020-11-19 15:40:58,411 DEBUG androidemu.native.hooks | memcpy_data:0x2944514-->0x28d06ac``2020-11-19 15:40:58,412 INFO androidemu.native.hooks | addr -->0xcbfc1003``2020-11-19 15:40:58,412 DEBUG androidemu.native.hooks |``
密文数据:
02944514: 33 A9 E7 6F C3 9B 4C DD F2 82 6E 56 D5 0D 40 91 3..o..L...nV..@.``02944524: C3 FA A6 2C 82 F1 5A 6A 57 24 C8 F3 68 92 4C A0 ...,..ZjW$..h.L.``02944534: 93 0B C4 13 AF DC 0A A0 B0 FE 26 36 89 D2 1E F1 ..........&6....``02944544: C2 D9 97 1D 30 43 95 04 2B 5B B7 8F 0D 56 9A 75 ....0C..+[...V.u``02944554: 7B 63 AC 1B F4 D8 1C 8E A1 D1 78 1B 8B D3 23 CC {c........x...#.``02944564: AA 69 35 BF 11 61 AA 4F 72 01 ED D5 0E 3B E5 09 .i5..a.Or....;..``
这个数据的解密也是用到APP签名的hash生成的key来解密。所以如果签名改变dex就会解密失败。
【附加数据解密并填充被移走的dex数据】
到这里dex的解压和解密完成,可以dump下来了。
下面是dex加载过程:
用InMemoryDexClassLoader类调用NewObjectV加载dex,InMemoryDexClassLoader内部会使用mmap分配内存存放dex,分配一个新的DexPathList$Element数组,将原来系统的类加载器和刚才的InMemoryDexClassLoader中的classLoader.pathList.dexElements合并成一个数组,然后替换原来系统中的类加载器的dexElements。
这样刚才解压解密还原的dex被加载进内存,而整个过程中没有明文文件落地,避免被copy出来。
.text:CBFB56A6 7C 69 LDR R4, [R7,#0x74+var_60] dex 个数计数器``.text:CBFB56A8 1E F0 DC FB BL sub_CBFD3E64 //获取总个数``.text:CBFB56AC 00 F0 DA F8 BL sub_CBFB5864``.text:CBFB56B0 84 42 CMP R4, R0 //循环解压解密加载所有的dex``.text:CBFB56B2 44 DA BGE loc_CBFB573E``.text:CBFB56B4 FF E7 B loc_CBFB56B6``
加载完成后注册dex vmp 函数:
JNIEnv->FindClass(com/app/protect/A) was called``JNIEnv->RegisterNatives(57, 0x007ff984, 10) was called``Register native ('V', '(ILjava/lang/Object;[Ljava/lang/Object;)V',function->'0xcbfd9ed9') failed on class com_app_protect_A.``Register native ('Z', '(ILjava/lang/Object;[Ljava/lang/Object;)Z',function->'0xcbfd9ed9') failed on class com_app_protect_A.``Register native ('B', '(ILjava/lang/Object;[Ljava/lang/Object;)B',function->'0xcbfd9ed9') failed on class com_app_protect_A.``Register native ('C', '(ILjava/lang/Object;[Ljava/lang/Object;)C',function->'0xcbfd9ed9') failed on class com_app_protect_A.``Register native ('S', '(ILjava/lang/Object;[Ljava/lang/Object;)S',function->'0xcbfd9ed9') failed on class com_app_protect_A.``Register native ('I', '(ILjava/lang/Object;[Ljava/lang/Object;)I',function->'0xcbfd9ed9') failed on class com_app_protect_A.``Register native ('J', '(ILjava/lang/Object;[Ljava/lang/Object;)J',function->'0xcbfd9ed9') failed on class com_app_protect_A.``Register native ('F', '(ILjava/lang/Object;[Ljava/lang/Object;)F',function->'0xcbfd9ed9') failed on class com_app_protect_A.``Register native ('D', '(ILjava/lang/Object;[Ljava/lang/Object;)D',function->'0xcbfd9ed9') failed on class com_app_protect_A.``Register native ('L', '(ILjava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;',function->'0xcbfd9ed9') failed on class com_app_protect_A.
这十个vmp 函数根据类型不同而分别调用。
初始化附加段的vmp数据:
======================= Registers =======================` `R0=0x2100000 R1=0x29be544 R2=0x2c80 R3=0x2100028``R4=0x7ff928 R5=0x7ff940 R6=0x7ff998 R7=0x7ffa08 R8=0x0``R9=0x0 R10=0x0 R11=0x0 R12=0x0 SP=0x7ff920``LR=0xcbfdac4d PC=0xcbfe62a0``======================= Disassembly =====================``0xcbfe62a0: f0b5 push {r4, r5, r6, r7, lr} //初始化vmp函数``0xcbfe62a2: 03af add r7, sp, #0xc``0xcbfe62a4: 93b0 sub sp, #0x4c``0xcbfe62a6: 6e46 mov r6, sp``0xcbfe62a8: 7262 str r2, [r6, #0x24]``>dump 0x29be544 0x2c80 //vmp 数据偏移 和 长度``029BE544: 42 44 30 35 32 37 26 00 00 00 01 00 00 00 26 00 BD0527&.......&.``029BE554: 00 00 00 00 00 00 00 00 00 00 80 00 00 00 2C 00 ..............,.``029BE564: 00 00 80 2B 00 00 02 00 00 00 4C 00 00 00 00 0A ...+......L.....
.text:CBFE7338 ; ---------------------------------------------------------------------------``.text:CBFE7338``.text:CBFE7338 loc_CBFE7338 ; CODE XREF: sub_CBFE62A0+108C↑j``.text:CBFE7338 ; sub_CBFE62A0+1092↑j ...``.text:CBFE7338 01 20 MOVS R0, #1``.text:CBFE733A 30 64 STR R0, [R6,#0x40]` `.text:CBFE733C 24 21 MOVS R1, #0x24 ; '$'``.text:CBFE733E 11 F0 1F F9 BL j_calloc // 申请128个长度为0x24的空间创建索引``
解析这段数据,创建索引。
>dump 0x2104000
02104000: 00 00 00 0A 26 00 00 00 04 00 00 00 01 00 01 00 …&…
02104010: 02 00 02 00 00 00 07 00 86 E5 9B 02 00 00 00 00 …
02104020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 …
02104030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 …
一直到:
>dump 0x219b000
0219B000: 7F 00 00 0A 58 00 00 00 01 00 00 00 01 00 02 00 …X…
0219B010: 02 00 03 00 00 00 20 00 80 10 9C 02 00 00 00 00 … …
0219B020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 …
0219B030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 …
一共00-7F :128个索引表。那就是应该有128个函数被vmp保护了。
CODE:006CCB0C # Source file: SourceFile``CODE:006CCB0C public void com.app.apptranslate.reading.dailyreading.fragment.PunchReadingScoreappFragment.onCreate(``CODE:006CCB0C android.os.Bundle p0)``CODE:006CCB0C this = v4``CODE:006CCB0C p0 = v5``CODE:006CCB0C .line 88``CODE:006CCB0C const v1, 0xA00007F``CODE:006CCB12 .line 89``CODE:006CCB12 const v3, 1``CODE:006CCB18 new-array v0, v3, <t: Object[]>``CODE:006CCB1C const v3, 0``CODE:006CCB22 check-cast p0, <t: Object>``CODE:006CCB26 aput-object p0, v0, v3``CODE:006CCB2A invoke-static {v1, this, v0}, <void A.V(int, ref, ref) imp. @ _def_A_V@VILL>``CODE:006CCB30``CODE:006CCB30 locret:``CODE:006CCB30 return-void``CODE:006CCB30 Method End``CODE:006CCB30 # ---------------------------------------------------------------------------
还真是有这个,厉害!
.text:CBFDACA4 B1 6C LDR R1, [R6,#0x48]``.text:CBFDACA6 13 F0 CD F8 BL sub_CBFEDE44 ; 初始化vmp JAVA类型``.text:CBFDACAA 02 B0 ADD SP, SP, #8``.text:CBFDACAC 01 21 MOVS R1, #1``JNIEnv->FindClass([F) was called``JNIEnv->FindClass([D) was called``JNIEnv->FindClass(java/lang/Boolean) was called``JNIEnv->FindClass(java/lang/Byte) was called``JNIEnv->FindClass(java/lang/Character) was called``JNIEnv->FindClass(java/lang/Short) was called``JNIEnv->FindClass(java/lang/Integer) was called``JNIEnv->FindClass(java/lang/Long) was called``JNIEnv->FindClass(java/lang/Float) was called``JNIEnv->FindClass(java/lang/Double) was called``JNIEnv->FindClass(java/lang/Object) was called``JNIEnv->GetMethodId(<class '__main__.java_lang_Boolean'>, <init>, (Z)V) was called``JNIEnv->GetMethodId(<class '__main__.java_lang_Byte'>, <init>, (B)V) was called``JNIEnv->GetMethodId(<class '__main__.java_lang_Character'>, <init>, (C)V) was called``JNIEnv->GetMethodId(<class '__main__.java_lang_Short'>, <init>, (S)V) was called``JNIEnv->GetMethodId(<class '__main__.java_lang_Integer'>, <init>, (I)V) was called``JNIEnv->GetMethodId(<class '__main__.java_lang_Long'>, <init>, (J)V) was called``JNIEnv->GetMethodId(<class '__main__.java_lang_Float'>, <init>, (F)V) was called``JNIEnv->GetMethodId(<class '__main__.java_lang_Double'>, <init>, (D)V) was called``JNIEnv->GetMethodId(<class '__main__.java_lang_Boolean'>, booleanValue, ()Z) was called``JNIEnv->GetMethodId(<class '__main__.java_lang_Byte'>, byteValue, ()B) was called``JNIEnv->GetMethodId(<class '__main__.java_lang_Character'>, charValue, ()C) was called``JNIEnv->GetMethodId(<class '__main__.java_lang_Short'>, shortValue, ()S) was called``JNIEnv->GetMethodId(<class '__main__.java_lang_Integer'>, intValue, ()I) was called``JNIEnv->GetMethodId(<class '__main__.java_lang_Long'>, longValue, ()J) was called``JNIEnv->GetMethodId(<class '__main__.java_lang_Float'>, floatValue, ()F) was called``JNIEnv->GetMethodId(<class '__main__.java_lang_Double'>, doubleValue, ()D) was called
第三、对dex代码的保护(VMP):
APP加固的vmp保护没有像360加固那样在原代码处加密代码,运行时解密。而是把原始代码移动到附加数据中,使用时根据vmp的代理函数解析参数,查询附加数据块,然后运行vmp代码的方式。具体看下面的代码分析:
com.app.apptranslate.activity. MainActivity.onCreate函数是个被vmp保护的dex函数。` `@Override // com.app.apptranslate.common.base.BaseObserveActivity` `protected void onCreate(Bundle arg5) {` `A.V(0xA000011, this, new Object[]{((Object)arg5)});` `}``
这个函数的参数为0xA000011,也就是0x11号:
======================= Registers =======================` `R0=0xe11f2a00 R1=0x11 R2=0x7ffc70 R3=0xe11f2f00``R4=0xfffffff8 R5=0xcc00ee9c R6=0x7ffc08 R7=0x7ffc60 R8=0x0``R9=0x0 R10=0x0 R11=0x0 R12=0x7ffff8 SP=0x7ffbc0``LR=0xcbfda033 PC=0xcbfde9b2``======================= Disassembly =====================``0xcbfde9b2: 0bf0adfe bl #0xcbfea710``
R0 是附加数据解析出来的vmp部分,R1就是参数号。下面的函数就是通过参数查询资源。
======================= Registers =======================` `R0=0xe3a26f60 R1=0x44 R2=0x7ffc70 R3=0xe11f2f00``R4=0xfffffff8 R5=0xcc00ee9c R6=0x7ffc08 R7=0x7ffc60 R8=0x0``R9=0x0 R10=0x0 R11=0x0 R12=0x7ffff8 SP=0x7ffbc0``LR=0xcbfde9b7 PC=0xcbfde9b6``======================= Disassembly =====================`` ``R0 vmp 数据资源地址:``>dump 0xe3a26f60``E3A26F60: 11 00 00 0A 42 01 00 00 04 00 00 00 01 00 05 00 ....B...........``E3A26F70: 02 00 06 00 00 00 95 00 D7 8B 71 D1 00 00 00 00 ..........q.....``E3A26F80: 00 00 00 00 00 00 00 00 12 00 00 0A 38 00 00 00 ............8...``E3A26F90: 04 00 00 00 01 00 01 00 02 00 02 00 00 00 10 00 ................
也就是n001函数中vmp初始化时候解析的那个128个函数。参数数值对应初始化的索引值。
也就是理论上他支持128种函数vmp。
通过这个索引找到classes.dex中的附加数据偏移地址:
.text:CBFE0328 31 88 LDRH R1, [R6]``======================= Registers =======================` `R0=0xcbfe51a2 R1=0x2056 R2=0xff R3=0xe11f2f00``R4=0x2056 R5=0xe11f2f00 R6=0xd1718bd7 R7=0x0 R8=0x0``R9=0x0 R10=0x0 R11=0x0 R12=0xcc00efc0 SP=0x7ffa98``LR=0xcbfe0315 PC=0xcbfe0348``======================= Disassembly =====================``>dump 0xd1718bd7 //这个就是vmp的code``D1718BD7: 56 20 54 87 54 00 6F 05 FF 40 8B 00 1B 1F 47 10 V T.T.o..@....G.``D1718BE7: 49 DB 00 00 6F 01 F8 A3 54 20 53 DB 10 00 77 00 I...o...T S...w.```D1718BF7: 60 DB 00 00 2E 01 54 30 50 DB 10 02 54 10 5D DB `.....T0P...T.].````D1718C07: 00 00 42 00 77 20 FB 0F 05 00 77 00 60 DB 00 00 ..B.w ....w.`...```
根据dex函数格式,前0x10是函数头:
>dump 0xd1718bc7``D1718BC7: 04 00 00 00 01 00 05 00 02 00 06 00 00 00 95 00 ................``D1718BD7: 56 20 54 87 54 00 6F 05 FF 40 8B 00 1B 1F 47 10 V T.T.o..@....G.``D1718BE7: 49 DB 00 00 6F 01 F8 A3 54 20 53 DB 10 00 77 00 I...o...T S...w.```D1718BF7: 60 DB 00 00 2E 01 54 30 50 DB 10 02 54 10 5D DB `.....T0P...T.].`
再回头看看索引:
>dump 0xe3a26f60``E3A26F60: 11 00 00 0A 42 01 00 00 04 00 00 00 01 00 05 00 ....B...........``E3A26F70: 02 00 06 00 00 00 95 00 D7 8B 71 D1 00 00 00 00 ..........q.....``E3A26F80: 00 00 00 00 00 00 00 00 12 00 00 0A 38 00 00 00 ............8...``E3A26F90: 04 00 00 00 01 00 01 00 02 00 02 00 00 00 10 00 ................
说明前8个字节就是索引的表,后面跟着0x10是函数头,然后就是函数的code偏移地址。
索引表8个字节,其中前4个字节是索引号,后面四个字节是本数据长度。每段数据以\0000结束。
按照这个就可以解析出附加数据中的vmp代码了。
下面执行这个code:
.text:CBFE0328 31 88 LDRH R1, [R6]``.text:CBFE032A FF 22 MOVS R2, #0xFF``.text:CBFE032C 16 92 STR R2, [SP,#0x10C+var_B4]``.text:CBFE032E 08 46 MOV R0, R1``.text:CBFE0330 10 40 ANDS R0, R2``======================= Registers =======================` `R0=0x56 R1=0x2056 R2=0xff R3=0xe11f2f00``R4=0xe11f3304 R5=0xe11f2f00 R6=0xd1718ed3 R7=0x7ffb68 R8=0x0``R9=0x0 R10=0x0 R11=0x0 R12=0xcc00efc0 SP=0x7ffa98``LR=0xcbfe0315 PC=0xcbfe0332``======================= Disassembly =====================``0xcbfe0332: 8000 lsls r0, r0, #2``0xcbfe0334: 1818 adds r0, r3, r0``0xcbfe0336: 0430 adds r0, #4``0xcbfe0338: 0024 movs r4, #0``0xcbfe033a: 1494 str r4, [sp, #0x50]``>dump 0xd1718ed3``D1718ED3: 56 20 54 87 21 00 54 10 B2 80 01 00 42 02 B7 02 V T.!.T.....B...``D1718EE3: 0B 00 54 10 B2 80 01 00 42 02 EC 00 00 04 54 30 ..T.....B.....T0``D1718EF3: 71 13 02 00 86 02 39 00 0C 7F 54 20 C4 80 21 00 q.....9...T ..!.``D1718F03: 77 10 CF CF 01 00 42 02 78 12 6F 4C 77 00 58 E3 w.....B.x.oLw.X.``
解释:
从函数code中取两个字节,然后 and 0xFF就是取一个字节opcode,这个是app加固的vmp的opcode。也就是获取vmp的opcode。继续:
.text:CBFE0332 80 00 LSLS R0, R0, #2``.text:CBFE0334 18 18 ADDS R0, R3, R0``.text:CBFE0336 04 30 ADDS R0, #4``.text:CBFE0338 00 24 MOVS R4, #0``.text:CBFE033A 14 94 STR R4, [SP,#0x10C+var_BC]``.text:CBFE033C 00 68 LDR R0, [R0]``======================= Registers =======================` `R0=0xe11f305c R1=0x2056 R2=0xff R3=0xe11f2f00``R4=0xe11f3304 R5=0xe11f2f00 R6=0xd1718ed3 R7=0x7ffb68 R8=0x0``R9=0x0 R10=0x0 R11=0x0 R12=0xcc00efc0 SP=0x7ffa98``LR=0xcbfe0315 PC=0xcbfe0338``======================= Disassembly =====================``0xcbfe0338: 0024 movs r4, #0``0xcbfe033a: 1494 str r4, [sp, #0x50]``0xcbfe033c: 0068 ldr r0, [r0]``0xcbfe033e: 0d94 str r4, [sp, #0x34]``0xcbfe0340: 0b94 str r4, [sp, #0x2c]``>dump 0xe11f2f00 //跳转表``E11F2F00: 00 2A 1F E1 5C 3C FE CB 9E 46 FE CB 16 05 FE CB .*..\<...F......``E11F2F10: 94 2B FE CB 52 4E FE CB 18 06 FE CB FA 36 FE CB .+..RN.......6..``E11F2F20: D0 52 FE CB AC 44 FE CB AC 04 FE CB BC 20 FE CB .R...D....... ..``E11F2F30: 20 24 FE CB F8 2D FE CB FA 03 FE CB 00 22 FE CB $...-......."..``.text:CBFE9CE2 loc_CBFE9CE2 ; CODE XREF: sub_CBFE96A8+62E↑j``.text:CBFE9CE2 ; sub_CBFE96A8+634↑j ...``.text:CBFE9CE2 28 68 LDR R0, [R5]``.text:CBFE9CE4 19 68 LDR R1, [R3]``.text:CBFE9CE6 80 00 LSLS R0, R0, #2``.text:CBFE9CE8 09 18 ADDS R1, R1, R0``.text:CBFE9CEA 09 68 LDR R1, [R1]``.text:CBFE9CEC 32 69 LDR R2, [R6,#0x10]``.text:CBFE9CEE 10 18 ADDS R0, R2, R0``.text:CBFE9CF0 00 6B LDR R0, [R0,#0x30]``.text:CBFE9CF2 22 68 LDR R2, [R4]``.text:CBFE9CF4 80 00 LSLS R0, R0, #2``.text:CBFE9CF6 10 18 ADDS R0, R2, R0``.text:CBFE9CF8 01 60 STR R1, [R0]``.text:CBFE9CFA 6B 48 LDR R0, =(dword_CC01355C - 0xCC00EE9C)``.text:CBFE9CFC 7A 49 LDR R1, =(_GLOBAL_OFFSET_TABLE_ - 0xCBFE9D02)``.text:CBFE9CFE 79 44 ADD R1, PC ; _GLOBAL_OFFSET_TABLE_``.text:CBFE9D00 40 18 ADDS R0, R0, R1 ; dword_CC01355C``.text:CBFE9D02 02 68 LDR R2, [R0]``.text:CBFE9D04 69 48 LDR R0, =(dword_CC013574 - 0xCC00EE9C)``.text:CBFE9D06 40 18 ADDS R0, R0, R1 ; dword_CC013574``.text:CBFE9D08 00 68 LDR R0, [R0]``.text:CBFE9D0A 51 1E SUBS R1, R2, #1``.text:CBFE9D0C 51 43 MULS R1, R2``.text:CBFE9D0E 01 22 MOVS R2, #1``.text:CBFE9D10 11 42 TST R1, R2``.text:CBFE9D12 04 D0 BEQ loc_CBFE9D1E``.text:CBFE9D14 FF E7 B loc_CBFE9D16``
解释:
通过vmp的opcode 来查询执行代码,首先opcode 左移两位,就是x4 然后加上执行函数表的基地址。
总结下:就是vmp利用vmp的opcode x 4 + 4 然后加上跳转表基地址,得到执行这个opcode的代码入口。完成模拟真实opcode的过程。也就是这个跳转表其实就是真实opcode的映射表。重新来看看这个映射表的生成过程:
模拟器记录如下:
2020-11-24 11:16:53,878 Memory WRITE at 0x7ffa40, data size = 4, pc: cbfe9a6a, data value = 0x0``2020-11-24 11:16:53,885 Memory WRITE at 0xe11f3060, data size = 4, pc: cbfe9cf8, data value = 0xcbfe034a``2020-11-24 11:16:53,888 Memory WRITE at 0x7ffa40, data size = 4, pc: cbfe9e1c, data value = 0x1``2020-11-24 11:16:53,895 Memory WRITE at 0xe11f2fa0, data size = 4, pc: cbfe9cf8, data value = 0xcbfe0360``2020-11-24 11:16:53,898 Memory WRITE at 0x7ffa40, data size = 4, pc: cbfe9e1c, data value = 0x2``2020-11-24 11:16:53,904 Memory WRITE at 0xe11f3074, data size = 4, pc: cbfe9cf8, data value = 0xcbfe0396``2020-11-24 11:16:53,907 Memory WRITE at 0x7ffa40, data size = 4, pc: cbfe9e1c, data value = 0x3``2020-11-24 11:16:53,913 Memory WRITE at 0xe11f32c4, data size = 4, pc: cbfe9cf8, data value = 0xcbfe03ca``2020-11-24 11:16:53,916 Memory WRITE at 0x7ffa40, data size = 4, pc: cbfe9e1c, data value = 0x4``2020-11-24 11:16:53,922 Memory WRITE at 0xe11f2f38, data size = 4, pc: cbfe9cf8, data value = 0xcbfe03fa``2020-11-24 11:16:53,925 Memory WRITE at 0x7ffa40, data size = 4, pc: cbfe9e1c, data value = 0x5``2020-11-24 11:16:53,932 Memory WRITE at 0xe11f2f88, data size = 4, pc: cbfe9cf8, data value = 0xcbfe0438``2020-11-24 11:16:53,935 Memory WRITE at 0x7ffa40, data size = 4, pc: cbfe9e1c, data value = 0x6``2020-11-24 11:16:53,941 Memory WRITE at 0xe11f32ac, data size = 4, pc: cbfe9cf8, data value = 0xcbfe0474``2020-11-24 11:16:53,944 Memory WRITE at 0x7ffa40, data size = 4, pc: cbfe9e1c, data value = 0x7``2020-11-24 11:16:53,951 Memory WRITE at 0xe11f2f28, data size = 4, pc: cbfe9cf8, data value = 0xcbfe04ac``2020-11-24 11:16:53,954 Memory WRITE at 0x7ffa40, data size = 4, pc: cbfe9e1c, data value = 0x8``2020-11-24 11:16:53,960 Memory WRITE at 0xe11f3190, data size = 4, pc: cbfe9cf8, data value = 0xcbfe04e2``……………``2020-11-24 11:16:56,224 Memory WRITE at 0x7ffa40, data size = 4, pc: cbfe9e1c, data value = 0xfb``2020-11-24 11:16:56,230 Memory WRITE at 0xe11f3034, data size = 4, pc: cbfe9cf8, data value = 0xcbfe4e52``2020-11-24 11:16:56,233 Memory WRITE at 0x7ffa40, data size = 4, pc: cbfe9e1c, data value = 0xfc``2020-11-24 11:16:56,239 Memory WRITE at 0xe11f313c, data size = 4, pc: cbfe9cf8, data value = 0xcbfe4e52``2020-11-24 11:16:56,242 Memory WRITE at 0x7ffa40, data size = 4, pc: cbfe9e1c, data value = 0xfd``2020-11-24 11:16:56,249 Memory WRITE at 0xe11f3264, data size = 4, pc: cbfe9cf8, data value = 0xcbfe4e52``2020-11-24 11:16:56,251 Memory WRITE at 0x7ffa40, data size = 4, pc: cbfe9e1c, data value = 0xfe``2020-11-24 11:16:56,258 Memory WRITE at 0xe11f3280, data size = 4, pc: cbfe9cf8, data value = 0xcbfe4e52``2020-11-24 11:16:56,261 Memory WRITE at 0x7ffa40, data size = 4, pc: cbfe9e1c, data value = 0xff``2020-11-24 11:16:56,267 Memory WRITE at 0xe11f2f98, data size = 4, pc: cbfe9cf8, data value = 0xcbfe4e52``
上面的代码组装了这个表,而这个表就是原始opcode跟执行代码的映射表。通过这种方式完成了vmp的opcode到真实opcode的映射。
比如,0xA000011 参数时:
.text:CBFE0328 31 88 LDRH R1, [R6] 获取opcode``.text:CBFE032A FF 22 MOVS R2, #0xFF``.text:CBFE032C 16 92 STR R2, [SP,#0x10C+var_B4]``.text:CBFE032E 08 46 MOV R0, R1``.text:CBFE0330 10 40 ANDS R0, R2``.text:CBFE0332 80 00 LSLS R0, R0, #2``.text:CBFE0334 18 18 ADDS R0, R3, R0 定位表偏移``.text:CBFE0336 04 30 ADDS R0, #4``.text:CBFE0338 00 24 MOVS R4, #0``.text:CBFE033A 14 94 STR R4, [SP,#0x10C+var_BC]``.text:CBFE033C 00 68 LDR R0, [R0] 获取执行代码地址``.text:CBFE033E 0D 94 STR R4, [SP,#0x10C+var_D8]``.text:CBFE0340 0B 94 STR R4, [SP,#0x10C+var_E0]``.text:CBFE0342 06 94 STR R4, [SP,#0x10C+var_F4]``.text:CBFE0344 27 46 MOV R7, R4``.text:CBFE0346 0C 46 MOV R4, R1``.text:CBFE0348 87 46 MOV PC, R0 跳转到执行代码地址``======================= Registers =======================` `R0=0xcbfe51a2 R1=0x2056 R2=0x54 R3=0xe11f2f00``R4=0x2056 R5=0xe11f2f00 R6=0xd1718bd7 R7=0x0 R8=0x0``R9=0x0 R10=0x0 R11=0x0 R12=0xcc00efc0 SP=0x7ffa98``LR=0xcbfe0315 PC=0xcbfe51a6``======================= Disassembly =====================``0xcbfe51a6: 0f20 movs r0, #0xf``0xcbfe51a8: 1040 ands r0, r2``0xcbfe51aa: 002f cmp r7, #0``0xcbfe51ac: 00d0 beq #0xcbfe51b0``0xcbfe51ae: 1046 mov r0, r2``>dump 0xd1718bd7 //code``D1718BD7: 56 20 54 87 54 00 6F 05 FF 40 8B 00 1B 1F 47 10 V T.T.o..@....G.``D1718BE7: 49 DB 00 00 6F 01 F8 A3 54 20 53 DB 10 00 77 00 I...o...T S...w.```D1718BF7: 60 DB 00 00 2E 01 54 30 50 DB 10 02 54 10 5D DB `.....T0P...T.].````D1718C07: 00 00 42 00 77 20 FB 0F 05 00 77 00 60 DB 00 00 ..B.w ....w.`...```
R1为vmp的opcode,为0x56,查询到的执行代码地址为:R0=0xcbfe51a2 ,我们从映射表中查询下这个执行代码映射的是真实opcode值为多少:
2020-11-24 11:16:54,914 Memory WRITE at 0x7ffa40, data size = 4, pc: cbfe9e1c, data value = 0x6f
2020-11-24 11:16:54,921 Memory WRITE at 0xe11f305c, data size = 4, pc: cbfe9cf8, data value = 0xcbfe51a2
从这个记录就可以看到映射的真实opcode为0x6f,查询下这个opcode命令是啥:
这是个invoke-super,也就是调用父类的onCreate,执行下看看结果:
JNIEnv->FindClass``(com/app/apptranslate/common/base/BaseObserveActivity) was called``JNIEnv->GetMethodId``(<class '__main__.com_app_apptranslate_common_base_BaseObserveActivity'>, onCreate, (Landroid/os/Bundle;)V) was called
确实是对类com_app_apptranslate_common_base_BaseObserveActivity的onCreate函数的调用。
【映射表函数代码】
从这段代码也可以看出来,APP加固vmp也是只替换了一个字节的opcode,其他操作数都没有改变,并且vmp的opcode和原始的opcode存在映射表关系。当然这个映射关系是通过opcode的执行代码地址联系起来的。
具体代码这样的:
@Override // com.app.rp.lib.base.BaseFragmentActivity
protected void onCreate(Bundle arg5) {
A.V(0xA00000F, this, new Object\[\]{((Object)arg5)});
}
第四、 VMP_dex代码还原:
通过上面的分析可以知道,APP加固的dex代码vmp保护中原始的代码数据被转移到附加数据中,并建立了vmp_fun_ID与函数体的索引。原始的dex代码位置给加固保护系统的vmp代理函数替代,并传递vmp_fun_ID到Native层处理。
Vmp的Native处理函数根据传递的vmp_fun_ID到索引表中查询函数数据地址。并按照函数头参数初始化函数。然后根据索引表中code的地址开始执行vmp代码。而vmp代码只是替换了一个字节的opcode指令,其他操作数都保持没有改变。而vmp的处理函数初始化时就进行了原始opcode与vmp执行流程代码的映射表,而这个映射表可以跟vmp_fun_ID进行关联。而vmp_fun_ID又跟vmp的代码进行了关联。所以vmp的opcode其实就是跟原始的opcode进行了关联。
根据这个我们就可以还原出原始的dex代码。还需要把dex函数的执行偏移地址修正到被vmp转移的还原出来的dex代码地址。也就是需要解决两个问题:
1、 还原被替换的dex代码中的opcode
还原vmp的dex代码的opcode,需要把原始的opcode与vmp的opcode一一对应起来。而程序是通过vmp的opcode数值进行计算,查询执行代码地址,执行代码流程的。而这个执行代码地址又在原始opcode表中进行了映射。所以可以根据这两个表进行逆查询,找到vmp的opcode与原始opcode的对应关系。我们到内存中获取这两个表就可以了。
2、 修正dex方法索引中offset到还原的代码地址
这个vmp流程里面并没有dex方法索引表与vmp代码的直接关联,所以需要解决dex方法索引中被vmp保护的函数,以及这些函数与调用Native入口函数的对应关系。也就是需要解决vmp_fun_ID与dex方法索引中的方法对应关系,这个在看雪论坛的帖子中是运行时记录dex_method_id与vmp_fun_ID关系,然后手动建立联系表的方式。这样如果运行时没有被执行的就不能被记录下来。也比较复杂。而我决定使用IDA的分析功能建立这个对应表来:
首先我们知道vmp保护的都是onCreate这个函数,所以我们在IDA中过滤下这个函数,效果如下:
然后找到长度为26的所有这类函数:
这样我们就可以把128个vmp的onCreate函数找出来。
然后再根据这个找到方法索引中这些函数的索引地址和对应的vmp_fun_ID,双击上面的这些函数:
我们记录下这些数据,并形成查询表:
这样我们就得到了所有的vmp的vmp_fun_ID和方法索引表中的方法索引地址,以及加固调用入口函数地址。
其实我们只需要vmp_fun_ID和方法索引表方法地址,加固调用入口在我们修正方法索引表的方法代码offse时就没有作用了。
先需要修改dex文件:
1、修改dex struct header_item dex_header 头里面的uint data_size 把附加数据部分加进去。
2、Dex的struct map_list_type dex_map_list 中增加一个节,把vmp的代码段加进去。
1、
3、修改map_list 项数量:
1、
4、APP加固自己解析了Method 的头,需要重建Method 头。
好了,有了这些数据和表我们就可以写代码来还原dex的vmp了。
Python
Python代码如下:
import codecs import leb128 def Get\_uleb128(vmp\_addr,mode): if mode == 1: offset = leb128.u.encode(vmp\_addr) if mode == 2: offset = leb128.u.decode(vmp\_addr) return offset def ReadFile(file, address, len,flag=0): add = int(address) file.seek(add) if flag == 0: data = int.from\_bytes(file.read(len), byteorder="little", signed=False) else: data =file.read(len) return data def main(): """ opcode长度表,按照这个长度表获取每条指令的长度,才能查询到下条指令 """ opcode\_Length = \[2, 2, 4, 4, 4, 4, 4, 2, 4, 4, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4, 6, 4, 4, 6, 10, 4, 4, 4, 4, 4, 2, 4, 4, 2, 4, 4, 6, 6, 6, 2, 2, 4, 4, 6, 6, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 6, 6, 6, 6, 6, 4, 6, 6, 6, 6, 6, 4, 4, 2, 4, 2, 4, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 6, 4, 6, 4, 4, 4, 4, 4, 4, 4, 6, 6, 6, 6, 4, 4, 4, 4\] """ 利用IDA建立的vmp\_fun\_ID与方法索引表的方法索引地址关联表。利用这个表修正方法索引中的offset地址,这个offset地址是uleb128格式的,需要转换下。 """ vmp\_dex\_fun\_idtomethod\_id = \[0xA000001, 0x6F8559, 0xA000003, 0x6F9EF5, 0xA000005, 0x6FA139, 0xA000006, 0x6FAA81, 0xA000002, 0x6F9E75, 0xA000000, 0x6F84D8, 0xA000008, 0x6FB05F, 0xA000007, 0x6FAD9B, 0xA000004, 0x6F9FDD, 0xA000009, 0x72B90C, 0xA00000A, 0x72BEFC, 0xA00000B, 0x72CC31, 0xA00000C, 0x72CC75, 0xA00000D, 0x72CF40, 0xA000010, 0x72D0E3, 0xA000011, 0x72D3C9, 0xA000013, 0x72D592, 0xA000014, 0x72D6E1, 0xA000016, 0x72D78F, 0xA000017, 0x72D806, 0xA000018, 0x72D977, 0xA000019, 0x72DA58, 0xA00001A, 0x72DAD2, 0xA00001B, 0x72DBAE, 0xA00001C, 0x72DD1E, 0xA00001D, 0x72DE8F, 0xA00001E, 0x72DF14, 0xA00001F, 0x72DFC2, 0xA000020, 0x72E0E5, 0xA000021, 0x72E29A, 0xA000022, 0x72E334, 0xA000023, 0x73178C, 0xA00000F, 0x72D06C, 0xA000012, 0x72D466, 0xA000015, 0x72D739, 0xA000025, 0x738125, 0xA000026, 0x73E615, 0xA000027, 0x744157, 0xA000028, 0x744267, 0xA000029, 0x7443EB, 0xA00002A, 0x7445DE, 0xA00002B, 0x74482A, 0xA00002C, 0x7449F1, 0xA00002D, 0x74600C, 0xA00002E, 0x747E40, 0xA00002F, 0x74E453, 0xA000030, 0x74E4C5, 0xA000031, 0x74E541, 0xA000032, 0x74E727, 0xA000033, 0x74E7BB, 0xA000034, 0x74E843, 0xA000035, 0x74E90B, 0xA000036, 0x74E971, 0xA000037, 0x74EA36, 0xA000038, 0x74EAD0, 0xA000039, 0x74EC03, 0xA00003A, 0x74ED09, 0xA00003B, 0x74EDAD, 0xA00003C, 0x74EE7A, 0xA00003D, 0x74EEE9, 0xA00003E, 0x74EF78, 0xA00003F, 0x74F045, 0xA000040, 0x7500A6, 0xA000041, 0x7505F4, 0xA000042, 0x7507A4, 0xA000043, 0x7508C5, 0xA000044, 0x750937, 0xA000045, 0x750981, 0xA000046, 0x750A09, 0xA000047, 0x750A99, 0xA000048, 0x750B6C, 0xA000049, 0x750D70, 0xA00004A, 0x750E71, 0xA00004B, 0x750F22, 0xA00004D, 0x7512BE, 0xA00004E, 0x751306, 0xA00004F, 0x7513F8, 0xA000050, 0x751436, 0xA000051, 0x7514AE, 0xA000052, 0x7517AB, 0xA000053, 0x7518B5, 0xA000054, 0x751961, 0xA000055, 0x751993, 0xA000056, 0x751A4D, 0xA000057, 0x751AA1, 0xA000058, 0x751B27, 0xA000059, 0x751BF1, 0xA00005A, 0x751DC3, 0xA00005B, 0x751E84, 0xA00004C, 0x7510D3, 0xA00005C, 0x7520E2, 0xA00005D, 0x752534, 0xA00005E, 0x7526E1, 0xA00005F, 0x75292C, 0xA000060, 0x752AB6, 0xA000061, 0x753A08, 0xA000062, 0x75694D, 0xA000063, 0x757072, 0xA000064, 0x75B38B, 0xA000065, 0x75B49E, 0xA000066, 0x75B54D, 0xA000067, 0x75B5E0, 0xA000068, 0x75B799, 0xA000069, 0x75C4CC, 0xA00006A, 0x75C5CA, 0xA00006B, 0x75C6FF, 0xA00006C, 0x75CAAE, 0xA00006D, 0x75CB1C, 0xA00006E, 0x75CD6F, 0xA00006F, 0x75CEB2, 0xA000070, 0x75CF32, 0xA000071, 0x75CFE3, 0xA000072, 0x75D02D, 0xA000073, 0x75D04F, 0xA000074, 0x75D0ED, 0xA000075, 0x75D4F8, 0xA000076, 0x75E83D, 0xA000077, 0x75E8B4, 0xA000078, 0x762043, 0xA000079, 0x762284, 0xA00007A, 0x762434, 0xA00007B, 0x763175, 0xA00007C, 0x763233, 0xA00007D, 0x76334C, 0xA00007E, 0x763696, 0xA00007F, 0x765011, 0xA000024, 0x731856, 0xA00000E, 0x72CFA9\] vmp\_dex2\_fun\_idtomethod\_id = \[0xa010000, 0x752ecf, 0xa010001, 0x757ba7, 0xa010002, 0x757cc4, 0xa010003, 0x757e5f, 0xa010004, 0x757f52, 0xa010005, 0x757fb2, 0xa010006, 0x758003, 0xa010007, 0x758056, 0xa010008, 0x7580a7, 0xa010009, 0x758104, 0xa01000a, 0x75815f, 0xa01000b, 0x7581ac, 0xa01000c, 0x75822c, 0xa01000d, 0x7588a8, 0xa01000e, 0x75ab80, 0xa01000f, 0x75b2a0, 0xa010010, 0x75d394, 0xa010011, 0x75d57f, 0xa010012, 0x75f249, 0xa010013, 0x75f8a1, 0xa010014, 0x76479b, 0xa010015, 0x764d50, 0xa010016, 0x764d7a, 0xa010017, 0x76e7b4, 0xa010018, 0x76f81c, 0xa010019, 0x76ff10, 0xa01001a, 0x771356, 0xa01001b, 0x77f579, 0xa01001c, 0x7857ca, 0xa01001d, 0x785886, 0xa01001e, 0x7863be, 0xa01001f, 0x786646, 0xa010020, 0x78a519, 0xa010021, 0x78f2d7, 0xa010022, 0x790310, 0xa010023, 0x7908c5, 0xa010024, 0x790bb0, 0xa010025, 0x790d67, 0xa010026, 0x790d91, 0xa010027, 0x791787, 0xa010028, 0x791a94, 0xa010029, 0x791de6, 0xa01002a, 0x791f58, 0xa01002b, 0x79200e, 0xa01002c, 0x793a62, 0xa01002d, 0x793c5d, 0xa01002e, 0x793d61, 0xa01002f, 0x793e04, 0xa010030, 0x793e84, 0xa010031, 0x795417, 0xa010032, 0x79558c, 0xa010033, 0x795721, 0xa010034, 0x7958a7, 0xa010035, 0x796616, 0xa010036, 0x7966ea, 0xa010037, 0x796f0e, 0xa010038, 0x79728d, 0xa010039, 0x7975e0, 0xa01003a, 0x797620, 0xa01003b, 0x797a50, 0xa01003c, 0x7988fa, 0xa01003d, 0x799ac0, 0xa01003e, 0x79b78c, 0xa01003f, 0x79c924, 0xa010040, 0x79cbb3, 0xa010041, 0x79da68, 0xa010042, 0x79deeb, 0xa010043, 0x79ea97, 0xa010044, 0x79eea4, 0xa010045, 0x7a2cdf, 0xa010046, 0x7a2e2b, 0xa010047, 0x7a2eef, 0xa010048, 0x7a2f6f, 0xa010049, 0x7a306b, 0xa01004a, 0x7a3772, 0xa01004b, 0x7a3a3d, 0xa01004c, 0x7a3aaf, 0xa01004d, 0x7a3b93, 0xa01004e, 0x7a3bfb, 0xa01004f, 0x7a3cbc, 0xa010050, 0x7a3d20, 0xa010051, 0x7a3df5, 0xa010052, 0x7a3e9d, 0xa010053, 0x7a5018, 0xa010054, 0x7a51d6, 0xa010055, 0x7a52c8, 0xa010056, 0x7a548e, 0xa010057, 0x7a5bd3, 0xa010058, 0x7a5d74, 0xa010059, 0x7a5f0c, 0xa01005a, 0x7a605c, 0xa01005b, 0x7a60fa, 0xa01005c, 0x7a627f, 0xa01005d, 0x7a636f, 0xa01005e, 0x7a6953, 0xa01005f, 0x7a6a8d, 0xa010060, 0x7a6bc0, 0xa010061, 0x7a6c6f, 0xa010062, 0x7a6ccb, 0xa010063, 0x7a6ee6, 0xa010064, 0x7a7086, 0xa010065, 0x7a72ea, 0xa010066, 0x7a74fa, 0xa010067, 0x7a76fc, 0xa010068, 0x7a784c, 0xa010069, 0x7a7a60, 0xa01006a, 0x7a7baa, 0xa01006b, 0x7a7c20, 0xa01006c, 0x7a7c94, 0xa01006d, 0x7a7db5, 0xa01006e, 0x7a7df9, 0xa01006f, 0x7a7e43, 0xa010070, 0x7a7f1c, 0xa010071, 0x7a887c, 0xa010072, 0x7aa2b1, 0xa010073, 0x7aa401, 0xa010074, 0x7aa7e8, 0xa010075, 0x7aa961, 0xa010076, 0x7aaa3c, 0xa010077, 0x7aade0, 0xa010078, 0x7aae1a, 0xa010079, 0x7ab0ab, 0xa01007a, 0x7b8bc2, 0xa01007b, 0x7b8c75, 0xa01007c, 0x7bb79e\] vmp\_fun\_idtomethod\_id = \[0xA020000,0x567960,0xA020001,0x56BB4C,0xA020002,0x56C039,0xA020003,0x57FC9F,0xA020004,0x57FD18,0xA020005,0x581D21\] """ 由于APP加固把vmp的dex code作为附加数据段附加值dex文件的尾部,不能被正常加载,且vmp的附加数据中dex code header被修改,code部分也没有对齐,需要恢复时我们需要把code恢复到code段,并且 每段code要对齐,然后需要把MAP段重新放到增加了code的code段后面,就涉及到MAP段在header中的offset修正,以及MAP段中MAP段中的定义。由于增加了code段数据,所以header中uint data\_size 需要把 新增加的code长度加上,header中的文件大小也需要修正,最好包括check值。 Name Start End R W X D L Align Base Type Class AD ds HEADER 00000000 00000070 ? ? ? . L byte 0002 public DATA 32 0002 STR\_IDS 00000070 00034670 ? ? ? . L byte 0003 public DATA 32 0003 TYPES 00034670 0003D030 ? ? ? . L byte 0004 public DATA 32 0004 PROTO 0003D030 0005E780 ? ? ? . L byte 0005 public DATA 32 0005 FIELDS 0005E780 000DE760 ? ? ? . L byte 0006 public DATA 32 0006 METHODS 000DE760 00151000 ? ? ? . L byte 0007 public DATA 32 0007 CLASS\_DEF 00151000 001842A0 ? ? ? . L byte 0008 public DATA 32 0008 STRINGS 001842A0 002EAE18 ? ? ? . L byte 000A public DATA 32 000A CODE 002EAE18 00769088 ? ? ? . L byte 0001 public CODE 32 0001 MAP 00769088 00769164 ? ? ? . L byte 0009 public DATA 32 0009 """ vmp\_dex\_filename = r"AndroidNativeEm/samples/appjiagu/data/classes\_vmp.dex" vmp\_dex1\_filename = r"AndroidNativeEm/samples/appjiagu/data/classes1\_vmp.dex" vmp\_dex1\_filename = r"AndroidNativeEm/samples/appjiagu/data/classes2\_vmp.dex" vm\_data = codecs.open(vmp\_dex1\_filename, "rb") vmp\_data\_buff = vm\_data.read() """ 这段代码是vmp的opcode与原始opcode的对应关系映射表的构造算法,我们利用这段代码直接构造出一个vmp opcode与原始opcode对应的映射表 .text:CBFE9CE2 ; --------------------------------------------------------------------------- .text:CBFE9CE2 .text:CBFE9CE2 loc\_CBFE9CE2 ; CODE XREF: sub\_CBFE96A8+62E↑j .text:CBFE9CE2 ; sub\_CBFE96A8+634↑j ... .text:CBFE9CE2 28 68 LDR R0, \[R5\] ; 原始opcode .text:CBFE9CE4 19 68 LDR R1, \[R3\] ; vmp 执行代码表基地址 .text:CBFE9CE6 80 00 LSLS R0, R0, #2 .text:CBFE9CE8 09 18 ADDS R1, R1, R0 .text:CBFE9CEA 09 68 LDR R1, \[R1\] ; 获取vmp执行代码地址值 .text:CBFE9CEC 32 69 LDR R2, \[R6,#0x10\] ; 方法表基地址 .text:CBFE9CEE 10 18 ADDS R0, R2, R0 .text:CBFE9CF0 00 6B LDR R0, \[R0,#0x30\] ; method\_ID .text:CBFE9CF2 22 68 LDR R2, \[R4\] ; vmp opcode映射表基地址 .text:CBFE9CF4 80 00 LSLS R0, R0, #2 .text:CBFE9CF6 10 18 ADDS R0, R2, R0 .text:CBFE9CF8 01 60 STR R1, \[R0\] ; 写映射表 修改如下: .text:CBFE9CEA 29 68 LDR R1, \[R5\] ;获取原始opcode 写入到表中,后面查询到的就是原始opcode了 获得的数据如下: Offset 0 1 2 3 4 5 6 7 8 9 A B C D E F 00000000 AA 00 00 00 C9 00 00 00 09 00 00 00 6C 00 00 00 ª É l 00000010 E5 00 00 00 11 00 00 00 9C 00 00 00 77 00 00 00 å œ w 00000020 C3 00 00 00 07 00 00 00 56 00 00 00 5D 00 00 00 à V \] 00000030 81 00 00 00 04 00 00 00 59 00 00 00 B0 00 00 00 Y ° 00000040 3D 00 00 00 DF 00 00 00 60 00 00 00 2C 00 00 00 = ß \` , 00000050 67 00 00 00 E0 00 00 00 44 00 00 00 19 00 00 00 g à D 00000060 E7 00 00 00 A2 00 00 00 B7 00 00 00 52 00 00 00 ç ¢ · R 00000070 12 00 00 00 2B 00 00 00 7D 00 00 00 3A 00 00 00 + } : 00000080 E9 00 00 00 05 00 00 00 F5 00 00 00 10 00 00 00 é õ 00000090 7C 00 00 00 FF 00 00 00 42 00 00 00 01 00 00 00 | ÿ B 000000A0 16 00 00 00 46 00 00 00 B8 00 00 00 1D 00 00 00 F ¸ 000000B0 AB 00 00 00 8F 00 00 00 0B 00 00 00 32 00 00 00 « 2 000000C0 D2 00 00 00 8E 00 00 00 A4 00 00 00 53 00 00 00 Ò Ž ¤ S 000000D0 87 00 00 00 4F 00 00 00 25 00 00 00 DB 00 00 00 ‡ O % Û 000000E0 9E 00 00 00 EA 00 00 00 F1 00 00 00 57 00 00 00 ž ê ñ W 000000F0 90 00 00 00 E8 00 00 00 D3 00 00 00 6D 00 00 00 è Ó m 00000100 68 00 00 00 99 00 00 00 0C 00 00 00 C5 00 00 00 h ™ Å 00000110 79 00 00 00 CF 00 00 00 6A 00 00 00 70 00 00 00 y Ï j p 00000120 C7 00 00 00 B1 00 00 00 D8 00 00 00 BD 00 00 00 Ç ± Ø ½ 00000130 FB 00 00 00 A5 00 00 00 9D 00 00 00 41 00 00 00 û ¥ A 00000140 B4 00 00 00 AE 00 00 00 3C 00 00 00 82 00 00 00 ´ ® < ‚ 00000150 6E 00 00 00 A3 00 00 00 6F 00 00 00 00 00 00 00 n £ o 00000160 E6 00 00 00 47 00 00 00 E4 00 00 00 BE 00 00 00 æ G ä ¾ 00000170 02 00 00 00 E1 00 00 00 9B 00 00 00 5F 00 00 00 á › \_ 00000180 B9 00 00 00 0D 00 00 00 17 00 00 00 48 00 00 00 ¹ H 00000190 D5 00 00 00 9F 00 00 00 0A 00 00 00 AC 00 00 00 Õ Ÿ ¬ 000001A0 76 00 00 00 1E 00 00 00 72 00 00 00 4A 00 00 00 v r J 000001B0 C1 00 00 00 F2 00 00 00 CB 00 00 00 1A 00 00 00 Á ò Ë 000001C0 23 00 00 00 7A 00 00 00 DE 00 00 00 F0 00 00 00 # z Þ ð 000001D0 85 00 00 00 89 00 00 00 28 00 00 00 71 00 00 00 … ‰ ( q 000001E0 5B 00 00 00 97 00 00 00 26 00 00 00 B3 00 00 00 \[ — & ³ 000001F0 30 00 00 00 92 00 00 00 4D 00 00 00 C4 00 00 00 0 ’ M Ä 00000200 F9 00 00 00 65 00 00 00 98 00 00 00 73 00 00 00 ù e ˜ s 00000210 DC 00 00 00 5A 00 00 00 14 00 00 00 7B 00 00 00 Ü Z { 00000220 40 00 00 00 3E 00 00 00 C6 00 00 00 22 00 00 00 @ > Æ " 00000230 29 00 00 00 BC 00 00 00 FC 00 00 00 49 00 00 00 ) ¼ ü I 00000240 F8 00 00 00 83 00 00 00 EC 00 00 00 21 00 00 00 ø ƒ ì ! 00000250 FA 00 00 00 B2 00 00 00 BB 00 00 00 36 00 00 00 ú ² » 6 00000260 94 00 00 00 1F 00 00 00 7F 00 00 00 A8 00 00 00 ” ¨ 00000270 50 00 00 00 91 00 00 00 5C 00 00 00 3B 00 00 00 P ‘ \\ ; 00000280 DA 00 00 00 C8 00 00 00 18 00 00 00 08 00 00 00 Ú È 00000290 AF 00 00 00 2F 00 00 00 1C 00 00 00 55 00 00 00 ¯ / U 000002A0 62 00 00 00 35 00 00 00 63 00 00 00 33 00 00 00 b 5 c 3 000002B0 E3 00 00 00 64 00 00 00 EE 00 00 00 CE 00 00 00 ã d î Î 000002C0 0E 00 00 00 2D 00 00 00 8A 00 00 00 88 00 00 00 - Š ˆ 000002D0 A7 00 00 00 95 00 00 00 B5 00 00 00 38 00 00 00 § • µ 8 000002E0 4C 00 00 00 69 00 00 00 5E 00 00 00 54 00 00 00 L i ^ T 000002F0 34 00 00 00 6B 00 00 00 43 00 00 00 75 00 00 00 4 k C u 00000300 B6 00 00 00 2A 00 00 00 F6 00 00 00 93 00 00 00 ¶ \* ö “ 00000310 66 00 00 00 3F 00 00 00 8C 00 00 00 A0 00 00 00 f ? Œ 00000320 D6 00 00 00 D9 00 00 00 96 00 00 00 D0 00 00 00 Ö Ù – Ð 00000330 20 00 00 00 ED 00 00 00 EB 00 00 00 CA 00 00 00 í ë Ê 00000340 A1 00 00 00 BA 00 00 00 61 00 00 00 45 00 00 00 ¡ º a E 00000350 39 00 00 00 78 00 00 00 74 00 00 00 37 00 00 00 9 x t 7 00000360 FD 00 00 00 F7 00 00 00 CD 00 00 00 F3 00 00 00 ý ÷ Í ó 00000370 24 00 00 00 0F 00 00 00 4E 00 00 00 FE 00 00 00 $ N þ 00000380 AD 00 00 00 CC 00 00 00 C2 00 00 00 1B 00 00 00 Ì Â 00000390 E2 00 00 00 F4 00 00 00 84 00 00 00 80 00 00 00 â ô „ € 000003A0 27 00 00 00 58 00 00 00 06 00 00 00 EF 00 00 00 ' X ï 000003B0 13 00 00 00 7E 00 00 00 2E 00 00 00 15 00 00 00 ~ . 000003C0 03 00 00 00 8B 00 00 00 A6 00 00 00 8D 00 00 00 ‹ ¦ 000003D0 9A 00 00 00 D4 00 00 00 D7 00 00 00 4B 00 00 00 š Ô × K 000003E0 A9 00 00 00 BF 00 00 00 D1 00 00 00 51 00 00 00 © ¿ Ñ Q 000003F0 31 00 00 00 C0 00 00 00 86 00 00 00 DD 00 00 00 1 À † Ý """ # opcode 映射表 opcode\_v\_opcode\_table = codecs.open(r"AndroidNativeEm/samples/appjiagu/data/vmp\_opcode\_table", "rb") # 把MAP段前的数据都读进来 # uint map\_off 8138768 34h 4h Fg: Bg: File offset of map list map address data\_ptr = ReadFile(vm\_data,0x34,4) map\_list\_size = ReadFile(vm\_data,data\_ptr,4) code\_ptr = data\_ptr magic\_begin = data\_ptr + (map\_list\_size \* 0xC) +4 # dex\_data = list(vm\_data.read(data\_ptr)) dex\_data = list(ReadFile(vm\_data,0,data\_ptr,1)) # vmp 数据开始地址 # magic = int.from\_bytes((vmp\_data\_buff\[magic\_begin:(magic\_begin+4)\]), byteorder="little", signed=False) magic\_data =bytearray.fromhex("42443035323726") magic = vmp\_data\_buff\[magic\_begin:(magic\_begin+7)\] while magic != magic\_data: magic\_begin += 1 magic = vmp\_data\_buff\[magic\_begin:(magic\_begin+7)\] begin\_addr = magic\_begin + 0x2C # 由于附加数据里面的vmp没有对齐,所以长度不正确,所以需要查询这个索引,才能确保正确的vmp dex数据 # vmp\_methon\_id = 0xA000000 vmp\_fun\_id = ReadFile(vm\_data, begin\_addr, 4) vmp\_len = ReadFile(vm\_data,(magic\_begin + 0x1A),2) for i in range(vmp\_len): # 先读取vmp dex code 长度 code\_len = ReadFile(vm\_data, (begin\_addr + 0x14), 2) if code\_len > 0x13: # 如果code 长度大于 vmp代理入口函数长度就在code段后面增加这个Method code,恢复代码后需要修正Method 索引中的offset for j in range(2): # 先读取vmp dex Method 头的前2个字节 data\_head = ReadFile(vm\_data,(begin\_addr + 6 + j),1) dex\_data.append(data\_head) # 由于APP加固把Method 的header 进行了重新拼装,所以需要重新修正 for j in range(6): # 先读取vmp dex Method 头的前6个字节 data\_head = ReadFile(vm\_data,(begin\_addr + 0xE + j),1) dex\_data.append(data\_head) index\_fun = vmp\_fun\_idtomethod\_id.index(vmp\_fun\_id) + 1 method\_fun = vmp\_fun\_idtomethod\_id\[index\_fun\] print(hex(method\_fun)) vmp\_fun\_ep = ReadFile(vm\_data, method\_fun, 4, 1) # 修正Method fun offset new\_offset = Get\_uleb128(code\_ptr,1) for j in range(4): dex\_data\[method\_fun + j\] = new\_offset\[j\] # 获取vmp 代理入口函数地址 ep\_addr = Get\_uleb128(vmp\_fun\_ep, 2) # 获取debug info address 写入到新的code中 for j in range(4): debug\_info = ReadFile(vm\_data, (ep\_addr + 8 + j), 1) dex\_data.append(debug\_info) # 把vmp Method header 中code length写入新code中 for j in range(2): len\_dat = ReadFile(vm\_data, (begin\_addr + 0x14 + j), 1) dex\_data.append(len\_dat) # 四个字节对齐 for j in range(2): dex\_data.append(0) vmp\_code\_addr = begin\_addr + 0x16 # code 地址 # Method header 中的code length 是按双字节计算的,所以需要乘以2 code\_len = code\_len \* 2 code\_ptr += code\_len +0x10 while code\_len > 0: vmp\_opcode = ReadFile(vm\_data, vmp\_code\_addr, 1) index\_opcode = vmp\_opcode \* 4 opcode = ReadFile(opcode\_v\_opcode\_table, index\_opcode, 1) # 写入新的code中 dex\_data.append(opcode) # 获取这个opcode 的指令长度,然后减去opcode一个字节 opcode\_len = opcode\_Length\[opcode\] - 1 code\_length = opcode\_Length\[opcode\] # 把code 指令写到新的code中 for k in range(opcode\_len): data\_code = ReadFile(vm\_data, (vmp\_code\_addr + 1 + k), 1) dex\_data.append(data\_code) vmp\_code\_addr += code\_length code\_len -= code\_length print("vmp\_opcode:", hex(vmp\_opcode)) print("opcode:", hex(opcode)) # 写两个字节0结束符,防止小于vmp 代理入口函数时没有结束标记问题 for k in range(2): dex\_data.append(0) code\_ptr += 2 begin\_addr = vmp\_code\_addr + code\_length else: # 如果小于等于vmp代理入口长度,就写回到这个代理入口地址,且不需要修正Method 索引的offset index\_fun = vmp\_fun\_idtomethod\_id.index(vmp\_fun\_id) + 1 method\_fun = vmp\_fun\_idtomethod\_id\[index\_fun\] vmp\_fun\_ep =ReadFile(vm\_data, method\_fun, 4,1) # 获取vmp 代理入口函数地址 ep\_addr = Get\_uleb128(vmp\_fun\_ep, 2) for j in range(2): # 先读取vmp dex Method 头的前2个字节 data\_head = ReadFile(vm\_data,(begin\_addr + 6 + j),1) dex\_data\[ep\_addr+j\] = data\_head # 由于APP加固把Method 的header 进行了重新拼装,所以需要重新修正 for j in range(6): # 先读取vmp dex Method 头的前6个字节 data\_head = ReadFile(vm\_data, (begin\_addr + 0xE + j), 1) dex\_data\[ep\_addr + j + 2\] = data\_head # 保存原debug info # 写入code 长度 for j in range(2): data\_head = ReadFile(vm\_data, (begin\_addr + 0x14 + j), 1) dex\_data\[ep\_addr + 0x12 +j\] = data\_head code\_addr = begin\_addr + 0x16 # code 地址 code\_old\_addr = ep\_addr + 0x10 # Method header 中的code length 是按双字节计算的,所以需要乘以2 code\_len = code\_len \* 2 while code\_len > 0: vmp\_opcode = ReadFile(vm\_data, code\_addr, 1) index\_opcode = vmp\_opcode \* 4 opcode = ReadFile(opcode\_v\_opcode\_table, index\_opcode, 1) dex\_data\[code\_old\_addr\] = opcode # 获取这个opcode 的指令长度,然后减去opcode一个字节 opcode\_len = opcode\_Length\[opcode\] - 1 code\_length = opcode\_Length\[opcode\] # 把code 指令写到原始的地方 for k in range(opcode\_len): data\_code = ReadFile(vm\_data, (code\_addr + 1 + k), 1) dex\_data\[code\_old\_addr+ 1 + k\] = data\_code code\_old\_addr = code\_old\_addr + code\_length code\_addr += code\_length code\_len -= code\_length print("vmp\_opcode:", hex(vmp\_opcode)) print("opcode:", hex(opcode)) # 写两个字节0结束符,防止小于vmp 代理入口函数时没有结束标记问题 for k in range(2): dex\_data\[code\_old\_addr\] = 0 begin\_addr = code\_addr + code\_length vmp\_fun\_id\_n = vmp\_fun\_id + 1 vmp\_fun\_id = ReadFile(vm\_data, begin\_addr, 4) vmp\_fun\_end = vmp\_fun\_id\_n % 0x100 if vmp\_fun\_end > (vmp\_len-1): break while vmp\_fun\_id != vmp\_fun\_id\_n: begin\_addr += 1 vmp\_fun\_id = ReadFile(vm\_data, begin\_addr, 4) # data = bytearray(dex\_data) map\_data = list(ReadFile(vm\_data,data\_ptr,0xd8,1)) for i in range(len(map\_data)): dex\_data.append(map\_data\[i\]) # dex\_data.append(map\_data) # 修正MAP段属性 # 修正header中MAP段offset new\_code\_len = code\_ptr - data\_ptr data\_size = ReadFile(vm\_data,0x68,4) + new\_code\_len data\_size\_new = data\_size.to\_bytes(4, byteorder="little", signed=False) for i in range(4): dex\_data\[0x68 + i\] = data\_size\_new\[i\] map\_offset = code\_ptr.to\_bytes(4, byteorder="little", signed=False) for i in range(4): dex\_data.append(map\_offset\[i\]) dex\_data\[0x34 + i\] = map\_offset\[i\] # 修正header 中文件大小 file\_size = len(dex\_data) file\_size\_new = file\_size.to\_bytes(4,byteorder="little", signed=False) for i in range(4): dex\_data\[0x20 + i\] = file\_size\_new\[i\] data = bytearray(dex\_data) bak\_filename = r"AndroidNativeEm/samples/appjiagu/data/classes2\_vmp\_new.dex" f = open(bak\_filename,"wb") if f: f.write(data) f.close() if \_\_name\_\_ == '\_\_main\_\_': main()
这个方向初期比较容易入门一些,掌握一些基本技术,拿起各种现成的工具就可以开黑了。不过,要想从脚本小子变成hei客大神,这个方向越往后,需要学习和掌握的东西就会越来越多,以下是学习网络安全需要走的方向:
# 网络安全学习方法
上面介绍了技术分类和学习路线,这里来谈一下学习方法:
## 视频学习
无论你是去B站或者是油管上面都有很多网络安全的相关视频可以学习,当然如果你还不知道选择那套学习,我这里也整理了一套和上述成长路线图挂钩的视频教程,完整版的视频已经上传至CSDN官方,朋友们如果需要可以点击这个链接免费领取。网络安全重磅福利:入门&进阶全套282G学习资源包免费分享!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。