nProtect APPGuard安卓反外挂分析
Posted 逆向与安全
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了nProtect APPGuard安卓反外挂分析相关的知识,希望对你有一定的参考价值。
工具与环境:
IDA7.0
JEB2.2.5
Nexus 5
android 4.4
目录:
一:app简单分析与java层反编译
二: compatible.so反调试与反反调试
三: compatible.so注册jni函数分析
四: stub.so反调试与反反调试
五: stub.so注册jni函数分析
六: Assembly-CSharp.dll解密分析
七: libengine模块分析
八:总结
一:app简单分析与java层反编译
整体图:
1.最近在学习手游保护方面的技术,本文是学习过程中分析某反外挂的一点记录,高手莫要见笑,有不对的地方还请指教,首先简单通过资源目录中文件名做基本了解,
在lib目录中有libmono.so、libunity.so,资源目录中存在(assets\\bin\\Data\\Managed\\Assembly-CSharp.dll),应该是unity 3D编写,通过反编译发现该文件己被加密,在资源目录下armeabi文件夹中还存放着libengine.sox与libstub.sox文件,看名字猜测很可能这两个文件就是反外挂其中的一些模块了,在看看lib目录下只有libcompatible.so模块比较可疑。如下图所示:
当我们用调试器附加游戏程进时会有如下提示:
被发现有调试器附加,下节我们将分析它的反调试机制。
2.通过JEB反编译来看看大致流程,反编译后先找到application类,代码如下图:
主要是加载so模块,so名称字符串被加密了,解密出来后so名称"compatible",将compatible.so放到IDA中反编译发现函数名被混淆了,字符串己加密,如下图:
通过以上简单分析,我们主要关注的重点关注的模块主要有lib目录下的libcompatible.so与资源目录中的libengine.sox与libstub.sox,还有就是发现java层的字符串与函数名都被混淆,so模块中的字符串也函数名也被混淆。
3.拷贝资源,解密libstub.sox并加载 。
在Lcom/inca/security/Core/AppGuardEngine初始函数<init>(Landroid/content/Context;Lcom/inca/security/AppGuard/AppGuardEventListener;Z)V中将判断X86或ARM平台并将对应的\\assets\\appguard中的libengine.sox、libstub.sox、update.dat拷贝到程序安装目录。JEB未能正常反编译出java代码,看smali代码。
1 :1946 2 00001946 const/4 v9, 3 3 00001948 if-ge v6, v9, :1C16 4 :194C 5 0000194C invoke-static Binder->getABI()I 6 00001952 move-result v6 7 00001954 move-object/from16 v0, p0 8 00001958 move-object/from16 v1, p1 9 0000195C invoke-virtual AppGuardEngine->iiIIIiiiIi(Context, I)Z, v0, v1, v6 # 拷贝资源 10 00001962 move-result v6 11 00001964 if-eqz v6, :1BE0 12 :1968 13 00001968 new-instance v6, qb 14 0000196C invoke-static JNISoxProxy->getContext()Context 15 00001972 move-result-object v9 16 00001974 invoke-direct qb-><init>(Context)V, v6, v9 17 0000197A const-string v9, "fChY~_h\\u0004yEr" # libstub.sox 18 0000197E invoke-virtual qb->iiIIIiiiIi([B)V, v6, v8 19 00001984 invoke-static b->iiIIIiiiIi(String)String, v9 20 0000198A move-result-object v9 21 0000198C const-string v10, "j\\u000Fd\\u0015r\\u0013dHu\\t" # libstub.so 22 00001990 invoke-static yb->iiIIIiiiIi(String)String, v10 23 00001996 move-result-object v10 24 00001998 const/4 v11, 0 25 0000199A invoke-virtual qb->iiIIIiiiIi(String, String, [B)Z, v6, v9, v10, v11 # 解密libstub.sox((最终传入so层解密private static native byte[] iIiIIIiIiI(byte[] arg0, int arg1))) 26 000019A0 move-result v6 27 000019A2 if-eqz v6, :1BE0 28 :19A6 29 000019A6 new-instance v6, File 30 000019AA new-instance v9, StringBuilder 31 000019AE invoke-direct StringBuilder-><init>()V, v9 32 000019B4 const/4 v10, 0 33 000019B6 invoke-static JNISoxProxy->getContext()Context 34 000019BC move-result-object v11 35 000019BE invoke-virtual Context->getFilesDir()File, v11 36 000019C4 move-result-object v11 37 000019C6 invoke-virtual File->getAbsolutePath()String, v11 38 000019CC move-result-object v11 39 000019CE invoke-virtual StringBuilder->insert(I, String)StringBuilder, v9, v10, v11 40 000019D4 move-result-object v9 41 000019D6 const-string v10, "%FcHy^\\u007FH$Ye" # /libstub.so 42 000019DA invoke-static b->iiIIIiiiIi(String)String, v10 43 000019E0 move-result-object v10 44 000019E2 invoke-virtual StringBuilder->append(String)StringBuilder, v9, v10 45 000019E8 move-result-object v9 46 000019EA invoke-virtual StringBuilder->toString()String, v9 47 000019F0 move-result-object v9 48 000019F2 invoke-direct File-><init>(String)V, v6, v9 49 000019F8 invoke-virtual File->exists()Z, v6 50 :19FE 51 000019FE move-result v9 52 00001A00 if-eqz v9, :1BE0 53 :1A04 54 00001A04 invoke-virtual File->getAbsolutePath()String, v6 55 00001A0A move-result-object v9 56 00001A0C invoke-static System->load(String)V, v9 # 加载指定路径的SO 57 00001A12 invoke-direct/range AppGuardEngine->lllIIIlllI(Context)V, p0 .. p1 # 调用Native 58 :1A18 59 00001A18 invoke-virtual File->delete()Z, v6 60 00001A1E new-instance v6, File 61 00001A22 new-instance v9, StringBuilder 62 00001A26 invoke-direct StringBuilder-><init>()V, v9 63 00001A2C const/4 v10, 0 64 00001A2E invoke-static JNISoxProxy->getContext()Context 65 00001A34 move-result-object v11 66 00001A36 invoke-virtual Context->getFilesDir()File, v11 67 00001A3C move-result-object v11 68 00001A3E invoke-virtual File->getAbsolutePath()String, v11 69 00001A44 move-result-object v11 70 00001A46 invoke-virtual StringBuilder->insert(I, String)StringBuilder, v9, v10, v11 71 00001A4C move-result-object v9 72 00001A4E const-string v10, ")\\no\\u0004u\\u0012s\\u0004(\\u0015i\\u001E" # /libstub.sox 73 00001A52 invoke-static yb->iiIIIiiiIi(String)String, v10 74 00001A58 move-result-object v10 75 00001A5A invoke-virtual StringBuilder->append(String)StringBuilder, v9, v10 76 00001A60 move-result-object v9 77 00001A62 invoke-virtual StringBuilder->toString()String, v9 78 00001A68 move-result-object v9 79 00001A6A invoke-direct File-><init>(String)V, v6, v9 80 00001A70 invoke-virtual File->delete()Z, v6
4. 解密libstub.sox模块。
解密函数在类com/inca/security/qb中iiIIIiiiIi函数,代码如下:
1 @SuppressLint(value={"SdCardPath"}) public boolean iiIIIiiiIi(String arg25, String arg26, byte[] arg27) throws IOException, InvalidKeyException { 2 Object v18; 3 Object v5_2; 4 long v16_1; 5 Method v8_3; 6 Class v11_2; 7 Object v7_2; 8 Object v4_7; 9 Method v15; // doFinal 10 Method v14_1; // init java.security.Key 11 Object v13_1; // RSA/ECB/PKCS1Padding 12 int v8_1; 13 int v7; 14 FileInputStream v13; 15 Method v4_6; 16 byte[] v7_1; 17 boolean v4_2; 18 Method v5_1; // read 19 byte[] v12; 20 byte[] v11; 21 Object v10; // / 22 Class v9; // java.io.FileInputStream 23 try { 24 v9 = Class.forName(vb.iiIIIiiiIi("&3:3b;#|\\n; 7\\u0005<<\\\'8\\u00018 )3!")); // java.io.FileInputStream 25 Constructor v4_1 = v9.getConstructor(String.class); 26 v10 = v4_1.newInstance(arg25.indexOf(yb.iiIIIiiiIi("I")) == 0 ? arg25 : new StringBuilder().insert(0, this.iiIiiiIIIi).append(arg25).toString()); // / 27 v11 = new byte[16]; 28 v12 = new byte[4]; 29 v5_1 = v9.getMethod(vb.iiIIIiiiIi(" )3("), byte[].class, Integer.TYPE, Integer.TYPE); // read 30 v5_1.invoke(v10, v11, Integer.valueOf(0), Integer.valueOf(3)); 31 if(v11[0] == 83 && v11[1] == 79 && v11[2] == 88) { // 判断开头是否为SOX 32 goto label_82; 33 } 34 35 v4_2 = false; 36 return v4_2; 37 } 38 catch(Exception v4) { 39 goto label_78; 40 } 41 42 label_82: 43 int v4_3 = 3; 44 try { 45 v5_1.invoke(v10, v11, Integer.valueOf(0), Integer.valueOf(2)); 46 if((((short)((((short)v11[0])) | (((short)v11[1])) << 8))) != 1) { 47 return false; 48 } 49 50 v5_1.invoke(v10, v11, Integer.valueOf(0), Integer.valueOf(1)); 51 v5_1.invoke(v10, v11, Integer.valueOf(0), Integer.valueOf(2)); 52 v5_1.invoke(v10, v11, Integer.valueOf(0), Integer.valueOf(2)); 53 v5_1.invoke(v10, v11, Integer.valueOf(0), Integer.valueOf(4)); 54 v5_1.invoke(v10, v11, Integer.valueOf(0), Integer.valueOf(4)); 55 v5_1.invoke(v10, v12, Integer.valueOf(0), Integer.valueOf(4)); 56 v5_1.invoke(v10, v11, Integer.valueOf(0), Integer.valueOf(12)); 57 v5_1.invoke(v10, v11, Integer.valueOf(0), Integer.valueOf(16)); 58 v5_1.invoke(v10, v11, Integer.valueOf(0), Integer.valueOf(16)); 59 byte[] v4_5 = null; 60 if(v11[0] == 0 || v11[1] == 0 || v11[14] == 0 || v11[15] == 0) { 61 v7_1 = v4_5; 62 v4_6 = v5_1; 63 goto label_291; 64 label_276: 65 while(v7 < 64) { 66 v13.read(v11); 67 if(v8_1 == v14) { 68 v4_5 = new byte[16]; 69 System.arraycopy(v11, 0, v4_5, 0, 16); 70 } 71 72 v7 = v8_1 + 1; 73 v8_1 = v7; 74 } 75 76 v13.close(); 77 v7_1 = v4_5; 78 v4_6 = v5_1; 79 } 80 else { 81 File v8 = new File(String.format(yb.iiIIIiiiIi("CuI#\\u0015(\\u0012v\\r"), arg25.substring(0, arg25.lastIndexOf(47)), qb.iiIIIiiiIi(v11))); // %s/%s.tpk 82 if(v8.exists()) { 83 v13 = new FileInputStream(v8); 84 v13.read(v11); 85 int v14 = (Math.abs(v11[0] << 24 | v11[4] << 16 | v11[8] << 8 | v11[12]) + 1) % 64; 86 v7 = 1; 87 v8_1 = 1; 88 goto label_276; 89 } 90 else { 91 return false; 92 } 93 } 94 95 label_291: 96 v4_6.invoke(v10, v11, Integer.valueOf(0), Integer.valueOf(4)); 97 v4_5 = new byte[(v11[3] & 255) << 24 | 0 | (v11[2] & 255) << 16 | (v11[1] & 255) << 8 | v11[0] & 255]; 98 v5_1 = v9.getMethod(vb.iiIIIiiiIi(" )3("), byte[].class); // read 99 v5_1.invoke(v10, v4_5); 100 Class v8_2 = Class.forName(yb.iiIIIiiiIi("\\fg\\u0010g\\u001E(\\u0005t\\u001Fv\\u0012iHE\\u000Fv\\u000Ec\\u0014")); // javax.crypto.Cipher 101 Method v11_1 = v8_2.getMethod(vb.iiIIIiiiIi("+78\\u001B\\"!83\\"1)"), String.class); // getInstance 102 v13_1 = v11_1.invoke(null, yb.iiIIIiiiIi("T5GIC%DIV-E576g\\u0002b\\u000Fh\\u0001")); // RSA/ECB/PKCS1Padding 103 v14_1 = v8_2.getMethod(vb.iiIIIiiiIi(";\\";8"), Integer.TYPE, Class.forName(yb.iiIIIiiiIi("\\fg\\u0010gHu\\u0003e\\u0013t\\u000Fr\\u001F(-c\\u001F"))); // init java.security.Key 104 v14_1.invoke(v13_1, Integer.valueOf(2), this.iIiIIiIiIi); 105 v15 = v8_2.getMethod(vb.iiIIIiiiIi("(=\\n;\\"3 "), byte[].class); // doFinal 106 v4_7 = v15.invoke(v13_1, v4_5); 107 if(v7_1 != null) { 108 v13_1 = v11_1.invoke(null, yb.iiIIIiiiIi("\\\'C5")); // AES 109 v14_1.invoke(v13_1, Integer.valueOf(2), Class.forName(vb.iiIIIiiiIi("&3:34|/ 5\\"8=b!<7/|\\u001F7/ )&\\u000775\\u0001<7/")).getConstructor(byte[].class, String.class).newInstance(v7_1, yb.iiIIIiiiIi("\\\'C5"))); // javax.crypto.spec.SecretKeySpec AES 110 v4_7 = v15.invoke(v13_1, v4_7); 111 } 112 113 v7_2 = null; 114 v7_2 = v11_1.invoke(v7_2, vb.iiIIIiiiIi("\\r\\u0017\\u001F")); 115 v14_1.invoke(v7_2, Integer.valueOf(2), Class.forName(yb.iiIIIiiiIi("\\fg\\u0010g\\u001E(\\u0005t\\u001Fv\\u0012iHu\\u0016c\\u0005(5c\\u0005t\\u0003r-c\\u001FU\\u0016c\\u0005")).getConstructor(byte[].class, String.class).newInstance(v4_7, vb.iiIIIiiiIi("\\r\\u0017\\u001F"))); 116 v11_2 = Class.forName(yb.iiIIIiiiIi("\\fg\\u0010gHo\\t($\\u007F\\u0012c\\\'t\\u0014g\\u001FI\\u0013r\\u0016s\\u0012U\\u0012t\\u0003g\\u000B")); 117 v13_1 = v11_2.getConstructor(null).newInstance(null); 118 byte[] v14_2 = new byte[1024]; 119 v15 = v8_2.getMethod(vb.iiIIIiiiIi("\\\'<6-&)"), byte[].class, Integer.TYPE, Integer.TYPE); 120 Method v16 = v11_2.getMethod(yb.iiIIIiiiIi("\\u0011t\\u000Fr\\u0003"), byte[].class); 121 for(v4_6 = v5_1; true; v4_6 = v5_1) { 122 v4_3 = v4_6.invoke(v10, v14_2).intValue(); 123 if(v4_3 == -1) { 124 break; 125 } 126 127 v16.invoke(v13_1, v15.invoke(v7_2, v14_2, Integer.valueOf(0), Integer.valueOf(v4_3))); 128 } 129 130 v16.invoke(v13_1, v8_2.getMethod(vb.iiIIIiiiIi("(=\\n;\\"3 "), null).invoke(v7_2, null)); 131 v4_7 = v11_2.getMethod(yb.iiIIIiiiIi("\\u0012i$\\u007F\\u0012c\\\'t\\u0014g\\u001F"), null).invoke(v13_1, null); 132 if(arg25.indexOf(vb.iiIIIiiiIi("c")) != 0) { 133 arg26 = new StringBuilder().insert(0, this.iiIiiiIIIi).append(arg26).toString(); 134 } 135 136 Class v7_3 = Class.forName(yb.iiIIIiiiIi("l\\u0007p\\u0007(\\u000FiH@\\u000Fj\\u0003I\\u0013r\\u0016s\\u0012U\\u0012t\\u0003g\\u000B")); 137 v8_3 = v7_3.getMethod(vb.iiIIIiiiIi("; %&)"), byte[].class); 138 v14_1 = v7_3.getMethod(yb.iiIIIiiiIi("\\u0005j\\tu\\u0003"), null); 139 v15 = v7_3.getMethod(vb.iiIIIiiiIi("*>9!$"), null); 140 v7_2 = v7_3.getConstructor(String.class).newInstance(arg26); 141 v16_1 = na.iIIIiiiIII(((byte[])v4_7), 5); 142 v5_2 = null; 143 } 144 catch(Exception v4) { 145 goto label_78; 146 } 147 148 try { 149 v18 = Binder.getReserved1(); 150 if(v18 == null) { 151 goto label_761; 152 } 153 } 154 catch(Exception v4) { 155 goto label_760; 156 } 157 158 try { 159 v5_2 = v18.getClass().getMethod(yb.iiIIIiiiIi("o/o/O\\u000Fo/o/"), byte[].class, Integer.TYPE).invoke(v18, v4_7, Integer.valueOf(((int)v16_1))); 160 } 161 catch(Exception v4) { 162 try { 163 v4.printStackTrace(); 164 goto label_659; 165 label_761: 166 byte[] v5_3 = AppGuardEngine.iiIIIiiiIi(((byte[])v4_7), ((int)v16_1)); // 传入SO层解密 (该Native函数在compatible.so进行动态注册) 167 label_760: 168 } 169 catch(Exception v4) { 170 goto label_760; 171 } 172 } 173 174 label_659: 175 v4_3 = 3; 176 try { 177 int v12_1 = v12[0] & 255 | ((v12[v4_3] & 255) << 24 | 0 | (v12[2] & 255) << 16 | (v12[1] & 255) << 8); 178 Class v16_2 = Class.forName(vb.iiIIIiiiIi("&3:3b\\\'8; |6;<|\\r6 7>a~")); 179 Object v17 = v16_2.getConstructor(null).newInstance(null); 180 v16_2.getMethod(yb.iiIIIiiiIi("s\\u0016b\\u0007r\\u0003"), byte[].class, Integer.TYPE, Integer.TYPE).invoke(v17, v5_2, Integer.valueOf(0), Integer.valueOf(v5_2.length)); 181 v16_1 = v16_2.getMethod(vb.iiIIIiiiIi("5)&\\u001A3 \\\')"), null).invoke(v17, null).longValue(); 182 if(v12_1 != 0 && v12_1 != (((int)v16_1))) { 183 v14_1.invoke(v7_2, null); 184 v4_2 = false; 185 } 186 else { 187 v8_3.invoke(v7_2, v5_2); 188 v15.invoke(v7_2, null); 189 v14_1.invoke(v7_2, null); 190 v9.getMethod(yb.iiIIIiiiIi("\\u0005j\\tu\\u0003"), null).invoke(v10, null); 191 v11_2.getMethod(vb.iiIIIiiiIi("/>#!)"), null).invoke(v13_1, null); 192 v9.getMethod(yb.iiIIIiiiIi("\\u0005j\\tu\\u0003"), null).invoke(v10, null); 193 v4_2 = true; 194 } 195 196 return v4_2; 197 } 198 catch(Exception v4) { 199 label_78: 200 Exception v5_4 = v4; 201 v4_2 = false; 202 v5_4.printStackTrace(); 203 return v4_2; 204 } 205 }
java层AES解密再传入传入so层解密private static native byte[] iIiIIIiIiI(byte[] arg0, int arg1)。
5. 解密libengine.sox模块
在Lcom/inca/security/Core/AppGuardEngine;->iiIIIiiiIi([B)Z生成一个随机数后SHA1后字会串做为解密后的文件名存放在/data/data/包名/files/目录下,解密函数与上一步相同。
生成随机数代码:
1 .method private synthetic iiIIIiiiIi([B)Z 2 .registers 12 3 .annotation build SuppressLint 4 value = { 5 "TrulyRandom" 6 } 7 .end annotation 8 .annotation system Throws 9 value = { 10 AppGuardException 11 } 12 .end annotation 13 00000000 const/4 v9, 2 14 00000002 const/16 v8, -0x007E 15 00000006 const/16 v7, 0x0030 16 0000000A const/4 v2, 0 17 0000000C const/4 v3, 1 18 :E 19 0000000E const-string v1, "U.GW" # SHA1 20 00000012 invoke-static yb->iiIIIiiiIi(String)String, v1 21 00000018 move-result-object v1 22 0000001A invoke-static MessageDigest->getInstance(String)MessageDigest, v1 23 :20 24 00000020 move-result-object v1 25 :22 26 00000022 new-instance v4, SecureRandom 27 00000026 invoke-direct SecureRandom-><init>()V, v4 28 0000002C const/16 v5, 0x0100 29 00000030 new-array v5, v5, [B 30 00000034 const/16 v6, 0x0126 31 00000038 invoke-virtual SecureRandom->nextBytes([B)V, v4, v5 32 0000003E invoke-virtual MessageDigest->update([B)V, v1, v5 33 00000044 invoke-virtual SecureRandom->nextBytes([B)V, v4, v5 34 0000004A invoke-virtual MessageDigest->update([B)V, v1, v5 35 00000050 new-instance v4, BigInteger 36 00000054 invoke-virtual MessageDigest->digest()[B, v1 37 0000005A move-result-object v1 38 0000005C invoke-direct BigInteger-><init>(I, [B)V, v4, v3, v1 39 00000062 const/16 v1, 0x0010 40 00000066 invoke-virtual BigInteger->toString(I)String, v4, v1 # 随机数后SHA1值 41 0000006C move-result-object v1 42 0000006E iput-object v1, p0, AppGuardEngine->IiIIiiiiii_Random_SHA1:String # 解密后文件名 43 //解密并调用 44 00000D7A new-instance v4, qb 45 00000D7E invoke-static JNISoxProxy->getContext()Context 46 00000D84 move-result-object v5 47 00000D86 invoke-direct qb-><init>(Context)V, v4, v5 48 00000D8C const-string v5, "fChOdMcDo\\u0004yEr" # libengine.sox 49 00000D90 invoke-virtual qb->iiIIIiiiIi([B)V, v4, v1 50 00000D96 invoke-static b->iiIIIiiiIi(String)String, v5 51 00000D9C move-result-object v1 52 00000D9E iget-object v5, p0, AppGuardEngine->IiIIiiiiii_Random_SHA1:String 53 00000DA2 const/4 v6, 0 54 00000DA4 invoke-virtual qb->iiIIIiiiIi(String, String, [B)Z, v4, v1, v5, v6 # 解密libengine.sox(最终传入so层解密private static native byte[] iIiIIIiIiI(byte[] arg0, int arg1)) 55 :DAA 56 00000DAA move-object v0, p0 57 :DAC 58 00000DAC invoke-static JNISoxProxy->getContext()Context 59 00000DB2 move-result-object v1 60 00000DB4 new-instance v4, StringBuilder 61 00000DB8 invoke-direct StringBuilder-><init>()V, v4 62 00000DBE invoke-static JNISoxProxy->getContext()Context 63 00000DC4 move-result-object v5 64 00000DC6 invoke-virtual Context->getFilesDir()File, v5 65 00000DCC move-result-object v5 66 00000DCE invoke-virtual File->getAbsolutePath()String, v5 67 00000DD4 move-result-object v5 68 00000DD6 invoke-virtual StringBuilder->insert(I, String)StringBuilder, v4, v2, v5 69 00000DDC move-result-object v4 70 00000DDE const-string v5, "%" # / 71 00000DE2 invoke-static b->iiIIIiiiIi(String)String, v5 72 00000DE8 move-result-object v5 73 00000DEA invoke-virtual StringBuilder->append(String)StringBuilder, v4, v5 74 00000DF0 move-result-object v4 75 00000DF2 iget-object v5, p0, AppGuardEngine->IiIIiiiiii_Random_SHA1:String 76 00000DF6 invoke-virtual StringBuilder->以上是关于nProtect APPGuard安卓反外挂分析的主要内容,如果未能解决你的问题,请参考以下文章
独家开放反外挂能力 WeTest手游安全测试服务登陆腾讯云(内含《龙之谷手游》反外挂实战)