APP加固系统分析心得

本分析还是基本上以AndroidNativeEmU模拟器分析日志为主。不过为了保证函数可以正常运行,部分数据来自真实环境,并还原到原偏移地址中,保证模拟器正常执行。本次分析还首次在模拟器中实现art模式下的Dex加载过程,不用到真实环境中去dump dex了。


第一、对自身模块的保护:
    
APP加固系统都自身模块的保护并没有像360加固保护系统那么强大和复杂,就是使用了code段加密,然后中so的init_array中进行解密,然后抹除so头,防止内存dump的方式。到JNI_OnLoad的时候代码段已经解密完成,这个时候dump下代码,然后把原始的头还原回去基本上就能用来分析了,如果要用模拟器调试,还得patch掉解密和抹除so头的代码。

APP加固系统分析心得

下面来看具体的代码实现。

Calling Init for: samples/appjiagu/libappprotect-up.so
Calling Init function: cbff8d85//这个就是init_array中的第一个函数,解密函数。

把so头0x1000字节清零:
Executing syscall mprotect(cbfab000, 00001000, 00000003) at 0xcbffd165call mprotect:0xcbfab000  ç基地址,即so头地址======================= Registers =======================  R0=0xcbfab034  R1=0x0  R2=0x1  R3=0x1R4=0x7ffce8  R5=0x7ffce8  R6=0x7ffe90  R7=0x7ffff8  R8=0x0R9=0x0  R10=0x0  R11=0x0  R12=0x7ffce0  SP=0x7ffce0LR=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, pc0xcbffd614:    4018   adds   r0, r0, r1


解密代码段中加密的代码:
Executing syscall mprotect(cbfae6f1, 00042C22, 00000007) at 0xcbffab7f

APP加固系统分析心得

解密代码段中加密的代码:
Executing syscall mprotect(cbfae6f1, 00042C22, 00000007) at 0xcbffab7f

APP加固系统分析心得

到这里:

APP加固系统分析心得

都是被加密的代码。
======================= Registers =======================  R0=0x47  R1=0xcbfae6f1  R2=0x7ffe60  R3=0x7ffe58R4=0x7ffcf8  R5=0x7ffdd8  R6=0x7ffe90  R7=0x7ffff8  R8=0x0R9=0x0  R10=0x0  R11=0x0  R12=0xffff1c30  SP=0x7ffce0LR=0xcbffe2f3  PC=0xcbffca94======================= Disassembly =====================0xcbffca94:    0870   strb   r0, [r1] < --R1=0xcbfae6f10xcbffca96:    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, #0xc0xcbfae9c8:    89b0   sub    sp, #0x240xcbfae9ca:    6e46   mov    r6, sp0xcbfae9cc:    3162   str    r1, [r6, #0x20]>dump 0xcbfab000CBFAB000: 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头被清除了。

APP加固系统分析心得

修复so方法:

先dump 数据,然后用010Editor 把原始的头贴回去。

APP加固系统分析心得

由于代码已经被解出来,所以后面不能再被解密了,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 0xcbfc15e7malloc addr:0x2022000Called dlsym(0xcbbdf000, _exit) at 0xcbfb1669symbol:_exit addr->: 0xcbc2780ccall malllos size:0x9 at 0xcbfc166fmalloc addr:0x2023000Called 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 calledJNIEnv->RegisterNatives(1, 0x007fff58, 4) was calledRegister 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加固的技术特点:

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..........N00000050: 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....C00000070: 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-->0x20cdb112020-11-11 17:39:40,907    INFO            androidemu.native.hooks | addr -->0xcbfb6f9b2020-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->242020-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/appprot021435E9: 65 63 74 31 2E 6A 61 72                           ect1.jar2020-11-19 11:23:30,452   DEBUG           androidemu.native.memory | call malllos size:0x7e62dc at 0xcbfd291b2020-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);    }  }

APP加固系统分析心得

这个时候的dex结构如上图所示,下面开始解这些部分。首先用签名的hash生成的key解密前0x1000字节:

APP加固系统分析心得

这个解压出来的数据前0x1000个字节是密文的,后面需要用APP签名的hash值来解密。

解密算法:
======================= Registers =======================  R0=0x21db000  R1=0x7ff758  R2=0x20cdc3c  R3=0xcbff04f9R4=0x21db000  R5=0x7ff7b0  R6=0x1000  R7=0xcbff04f9  R8=0x0R9=0x0  R10=0x0  R11=0x0  R12=0xcc00eef8  SP=0x7ff720LR=0xcbff0e11  PC=0xcbff0ca8======================= Disassembly =====================0xcbff0ca8: b847   blx    r7    <=解密函数0xcbff0caa: 3b46   mov    r3, r70xcbff0cac: 2868   ldr    r0, [r5]0xcbff0cae: 029a   ldr    r2, [sp, #8]0xcbff0cb0: 1168   ldr    r1, [r2]>dump 0x20cdc3c     // APP 签名hash生成的解密key020CDC3C: 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[.x021DB010: 69 DD 9E C2 C2 68 85 1E  A5 A7 35 E0 88 96 E4 4F  i....h....5....O021DB020: 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=0xcbff04f9R4=0x21dc000  R5=0x7ff7b0  R6=0x0  R7=0xcbff04f9  R8=0x0R9=0x0  R10=0x0  R11=0x0  R12=0xcc00eef8  SP=0x7ff720LR=0xcbff0cab  PC=0xcbff0ce6======================= Disassembly =====================0xcbff0ce6: 0b98   ldr    r0, [sp, #0x2c]0xcbff0ce8: 0a99   ldr    r1, [sp, #0x28]


APP加固系统分析心得

这个时候的dex有部分代码被移走了,需要重新解密填充回来:

APP加固系统分析心得

下面的函数就是解密填充:
======================= Registers =======================  R0=0x21db000  R1=0x7e62dc  R2=0x0  R3=0xcbff04f9R4=0x0  R5=0x21db000  R6=0x7ff804  R7=0xcbc3064d  R8=0x0R9=0x0  R10=0x0  R11=0x0  R12=0xcc00eef8  SP=0x7ff7d8LR=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-->0x28d06ac2020-11-19 15:40:58,412    INFO            androidemu.native.hooks | addr -->0xcbfc10032020-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.u02944554: 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就会解密失败。

APP加固系统分析心得

                                【附加数据解密并填充被移走的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 calledJNIEnv->RegisterNatives(57, 0x007ff984, 10) was calledRegister 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=0x2100028R4=0x7ff928  R5=0x7ff940  R6=0x7ff998  R7=0x7ffa08  R8=0x0R9=0x0  R10=0x0  R11=0x0  R12=0x0  SP=0x7ff920LR=0xcbfdac4d  PC=0xcbfe62a0======================= Disassembly =====================0xcbfe62a0: f0b5   push   {r4, r5, r6, r7, lr}                    //初始化vmp函数0xcbfe62a2: 03af   add    r7, sp, #0xc0xcbfe62a4: 93b0   sub    sp, #0x4c0xcbfe62a6: 6e46   mov    r6, sp0xcbfe62a8: 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.....


APP加固系统分析心得

.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的空间创建索引

解析这段数据,创建索引。

APP加固系统分析心得

>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: SourceFileCODE:006CCB0C public void com.app.apptranslate.reading.dailyreading.fragment.PunchReadingScoreappFragment.onCreate(CODE:006CCB0C       android.os.Bundle p0)CODE:006CCB0C this = v4CODE:006CCB0C p0 = v5CODE:006CCB0C                 .line 88CODE:006CCB0C                 const                           v1, 0xA00007FCODE:006CCB12                 .line 89CODE:006CCB12                 const                           v3, 1CODE:006CCB18                 new-array                       v0, v3, <t: Object[]>CODE:006CCB1C                 const                           v3, 0CODE:006CCB22                 check-cast                      p0, <t: Object>CODE:006CCB26                 aput-object                     p0, v0, v3CODE:006CCB2A                 invoke-static                   {v1, this, v0}, <void A.V(int, ref, ref) imp. @ _def_A_V@VILL>CODE:006CCB30CODE:006CCB30 locret:CODE:006CCB30                 return-voidCODE:006CCB30 Method EndCODE: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, #1JNIEnv->FindClass([F) was calledJNIEnv->FindClass([D) was calledJNIEnv->FindClass(java/lang/Boolean) was calledJNIEnv->FindClass(java/lang/Byte) was calledJNIEnv->FindClass(java/lang/Character) was calledJNIEnv->FindClass(java/lang/Short) was calledJNIEnv->FindClass(java/lang/Integer) was calledJNIEnv->FindClass(java/lang/Long) was calledJNIEnv->FindClass(java/lang/Float) was calledJNIEnv->FindClass(java/lang/Double) was calledJNIEnv->FindClass(java/lang/Object) was calledJNIEnv->GetMethodId(<class '__main__.java_lang_Boolean'>, <init>, (Z)V) was calledJNIEnv->GetMethodId(<class '__main__.java_lang_Byte'>, <init>, (B)V) was calledJNIEnv->GetMethodId(<class '__main__.java_lang_Character'>, <init>, (C)V) was calledJNIEnv->GetMethodId(<class '__main__.java_lang_Short'>, <init>, (S)V) was calledJNIEnv->GetMethodId(<class '__main__.java_lang_Integer'>, <init>, (I)V) was calledJNIEnv->GetMethodId(<class '__main__.java_lang_Long'>, <init>, (J)V) was calledJNIEnv->GetMethodId(<class '__main__.java_lang_Float'>, <init>, (F)V) was calledJNIEnv->GetMethodId(<class '__main__.java_lang_Double'>, <init>, (D)V) was calledJNIEnv->GetMethodId(<class '__main__.java_lang_Boolean'>, booleanValue, ()Z) was calledJNIEnv->GetMethodId(<class '__main__.java_lang_Byte'>, byteValue, ()B) was calledJNIEnv->GetMethodId(<class '__main__.java_lang_Character'>, charValue, ()C) was calledJNIEnv->GetMethodId(<class '__main__.java_lang_Short'>, shortValue, ()S) was calledJNIEnv->GetMethodId(<class '__main__.java_lang_Integer'>, intValue, ()I) was calledJNIEnv->GetMethodId(<class '__main__.java_lang_Long'>, longValue, ()J) was calledJNIEnv->GetMethodId(<class '__main__.java_lang_Float'>, floatValue, ()F) was calledJNIEnv->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=0xe11f2f00R4=0xfffffff8  R5=0xcc00ee9c  R6=0x7ffc08  R7=0x7ffc60  R8=0x0R9=0x0  R10=0x0  R11=0x0  R12=0x7ffff8  SP=0x7ffbc0LR=0xcbfda033  PC=0xcbfde9b2======================= Disassembly =====================0xcbfde9b2:    0bf0adfe bl     #0xcbfea710

R0 是附加数据解析出来的vmp部分,R1就是参数号。下面的函数就是通过参数查询资源。
======================= Registers =======================  R0=0xe3a26f60  R1=0x44  R2=0x7ffc70  R3=0xe11f2f00R4=0xfffffff8  R5=0xcc00ee9c  R6=0x7ffc08  R7=0x7ffc60  R8=0x0R9=0x0  R10=0x0  R11=0x0  R12=0x7ffff8  SP=0x7ffbc0LR=0xcbfde9b7  PC=0xcbfde9b6======================= Disassembly =====================
R0 vmp 数据资源地址:>dump 0xe3a26f60E3A26F60: 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=0xe11f2f00R4=0x2056  R5=0xe11f2f00  R6=0xd1718bd7  R7=0x0  R8=0x0R9=0x0  R10=0x0  R11=0x0  R12=0xcc00efc0  SP=0x7ffa98LR=0xcbfe0315  PC=0xcbfe0348======================= Disassembly =====================>dump 0xd1718bd7   //这个就是vmp的codeD1718BD7: 56 20 54 87 54 00 6F 05  FF 40 8B 00 1B 1F 47 10  V [email protected].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 0xd1718bc7D1718BC7: 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 [email protected].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 0xe3a26f60E3A26F60: 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个字节是索引号,后面四个字节是本数据长度。每段数据以000结束。

按照这个就可以解析出附加数据中的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=0xe11f2f00R4=0xe11f3304  R5=0xe11f2f00  R6=0xd1718ed3  R7=0x7ffb68  R8=0x0R9=0x0  R10=0x0  R11=0x0  R12=0xcc00efc0  SP=0x7ffa98LR=0xcbfe0315  PC=0xcbfe0332======================= Disassembly =====================0xcbfe0332:    8000   lsls   r0, r0, #20xcbfe0334:    1818   adds   r0, r3, r00xcbfe0336:    0430   adds   r0, #40xcbfe0338:    0024   movs   r4, #00xcbfe033a:    1494   str    r4, [sp, #0x50]>dump 0xd1718ed3D1718ED3: 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.....T0D1718EF3: 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=0xe11f2f00R4=0xe11f3304  R5=0xe11f2f00  R6=0xd1718ed3  R7=0x7ffb68  R8=0x0R9=0x0  R10=0x0  R11=0x0  R12=0xcc00efc0  SP=0x7ffa98LR=0xcbfe0315  PC=0xcbfe0338======================= Disassembly =====================0xcbfe0338:    0024   movs   r4, #00xcbfe033a:    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 = 0x02020-11-24 11:16:53,885  Memory WRITE at 0xe11f3060, data size = 4, pc: cbfe9cf8, data value = 0xcbfe034a2020-11-24 11:16:53,888  Memory WRITE at 0x7ffa40, data size = 4, pc: cbfe9e1c, data value = 0x12020-11-24 11:16:53,895  Memory WRITE at 0xe11f2fa0, data size = 4, pc: cbfe9cf8, data value = 0xcbfe03602020-11-24 11:16:53,898  Memory WRITE at 0x7ffa40, data size = 4, pc: cbfe9e1c, data value = 0x22020-11-24 11:16:53,904  Memory WRITE at 0xe11f3074, data size = 4, pc: cbfe9cf8, data value = 0xcbfe03962020-11-24 11:16:53,907  Memory WRITE at 0x7ffa40, data size = 4, pc: cbfe9e1c, data value = 0x32020-11-24 11:16:53,913  Memory WRITE at 0xe11f32c4, data size = 4, pc: cbfe9cf8, data value = 0xcbfe03ca2020-11-24 11:16:53,916  Memory WRITE at 0x7ffa40, data size = 4, pc: cbfe9e1c, data value = 0x42020-11-24 11:16:53,922  Memory WRITE at 0xe11f2f38, data size = 4, pc: cbfe9cf8, data value = 0xcbfe03fa2020-11-24 11:16:53,925  Memory WRITE at 0x7ffa40, data size = 4, pc: cbfe9e1c, data value = 0x52020-11-24 11:16:53,932  Memory WRITE at 0xe11f2f88, data size = 4, pc: cbfe9cf8, data value = 0xcbfe04382020-11-24 11:16:53,935  Memory WRITE at 0x7ffa40, data size = 4, pc: cbfe9e1c, data value = 0x62020-11-24 11:16:53,941  Memory WRITE at 0xe11f32ac, data size = 4, pc: cbfe9cf8, data value = 0xcbfe04742020-11-24 11:16:53,944  Memory WRITE at 0x7ffa40, data size = 4, pc: cbfe9e1c, data value = 0x72020-11-24 11:16:53,951  Memory WRITE at 0xe11f2f28, data size = 4, pc: cbfe9cf8, data value = 0xcbfe04ac2020-11-24 11:16:53,954  Memory WRITE at 0x7ffa40, data size = 4, pc: cbfe9e1c, data value = 0x82020-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 = 0xfb2020-11-24 11:16:56,230  Memory WRITE at 0xe11f3034, data size = 4, pc: cbfe9cf8, data value = 0xcbfe4e522020-11-24 11:16:56,233  Memory WRITE at 0x7ffa40, data size = 4, pc: cbfe9e1c, data value = 0xfc2020-11-24 11:16:56,239  Memory WRITE at 0xe11f313c, data size = 4, pc: cbfe9cf8, data value = 0xcbfe4e522020-11-24 11:16:56,242  Memory WRITE at 0x7ffa40, data size = 4, pc: cbfe9e1c, data value = 0xfd2020-11-24 11:16:56,249  Memory WRITE at 0xe11f3264, data size = 4, pc: cbfe9cf8, data value = 0xcbfe4e522020-11-24 11:16:56,251  Memory WRITE at 0x7ffa40, data size = 4, pc: cbfe9e1c, data value = 0xfe2020-11-24 11:16:56,258  Memory WRITE at 0xe11f3280, data size = 4, pc: cbfe9cf8, data value = 0xcbfe4e522020-11-24 11:16:56,261  Memory WRITE at 0x7ffa40, data size = 4, pc: cbfe9e1c, data value = 0xff2020-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=0xe11f2f00R4=0x2056  R5=0xe11f2f00  R6=0xd1718bd7  R7=0x0  R8=0x0R9=0x0  R10=0x0  R11=0x0  R12=0xcc00efc0  SP=0x7ffa98LR=0xcbfe0315  PC=0xcbfe51a6======================= Disassembly =====================0xcbfe51a6:    0f20   movs   r0, #0xf0xcbfe51a8:    1040   ands   r0, r20xcbfe51aa:    002f   cmp    r7, #00xcbfe51ac:    00d0   beq    #0xcbfe51b00xcbfe51ae:    1046   mov    r0, r2>dump 0xd1718bd7   //codeD1718BD7: 56 20 54 87 54 00 6F 05  FF 40 8B 00 1B 1F 47 10  V [email protected].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命令是啥:

APP加固系统分析心得

这是个invoke-super,也就是调用父类的onCreate,执行下看看结果:
JNIEnv->FindClass(com/app/apptranslate/common/base/BaseObserveActivity) was calledJNIEnv->GetMethodId(<class '__main__.com_app_apptranslate_common_base_BaseObserveActivity'>, onCreate, (Landroid/os/Bundle;)V) was called


确实是对类com_app_apptranslate_common_base_BaseObserveActivity的onCreate函数的调用。

APP加固系统分析心得
                    【映射表函数代码】

从这段代码也可以看出来,APP加固vmp也是只替换了一个字节的opcode,其他操作数都没有改变,并且vmp的opcode和原始的opcode存在映射表关系。当然这个映射关系是通过opcode的执行代码地址联系起来的。

APP加固系统分析心得

具体代码这样的:
    @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中过滤下这个函数,效果如下:

APP加固系统分析心得

然后找到长度为26的所有这类函数:

APP加固系统分析心得

这样我们就可以把128个vmp的onCreate函数找出来。

然后再根据这个找到方法索引中这些函数的索引地址和对应的vmp_fun_ID,双击上面的这些函数:

APP加固系统分析心得

我们记录下这些数据,并形成查询表:

APP加固系统分析心得

这样我们就得到了所有的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的代码段加进去。

APP加固系统分析心得1、  
3、修改map_list 项数量:

APP加固系统分析心得
1、  
4、APP加固自己解析了Method 的头,需要重建Method 头。
APP加固系统分析心得


好了,有了这些数据和表我们就可以写代码来还原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()



APP加固系统分析心得


看雪ID:fxyang

https://bbs.kanxue.com/user-home-981.htm

*本文为看雪论坛精华文章,由 fxyang 原创,转载请注明来自看雪社区

APP加固系统分析心得



# 往期推荐

1、自定义Linker实现分析之路

2、逆向分析VT加持的无畏契约纯内核挂

3、阿里云CTF2024-暴力ENOTYOURWORLD题解

4、Hypervisor From Scratch – 基本概念和配置测试环境、进入 VMX 操作

5、V8漏洞利用之对象伪造漏洞利用模板


APP加固系统分析心得


APP加固系统分析心得

球分享

APP加固系统分析心得

球点赞

APP加固系统分析心得

球在看



APP加固系统分析心得

点击阅读原文查看更多

原文始发于微信公众号(看雪学苑):APP加固系统分析心得

版权声明:admin 发表于 2024年4月30日 下午6:09。
转载请注明:APP加固系统分析心得 | CTF导航

相关文章