一、序言
根据加壳数据在解壳程序Dex文件所处的位置,我提出了两种Android Dex加壳技术实现方案,本片博文将对方案1代码实现进行讲解。博友可以根据方案1的代码实现原理对方案2自行实现。
在方案1的代码实现过程中,各种不同的问题接踵出现,最初的方案也在不同问题的出现、解决过程中不断的得到调整、优化。
本文的代码实现了对整个APK包的加壳处理。加壳程序不会对源程序有任何的影响。
二、代码实现
本程序基于Android2.3代码实现,因为牵扯到系统代码的反射修改,本程序不保证在其它android版本正常工作,博友可以根据实现原理,自行实现对其它Android版本的兼容性开发。
1、 加壳程序流程及代码实现
1、加密源程序APK为解壳数据
2、把解壳数据写入解壳程序DEX文件末尾,并在文件尾部添加解壳数据的大小。
3、修改解壳程序DEX头中checksum、signature 和file_size头信息。
代码实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 |
package com.android.dexshell; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.zip.Adler32; public class DexShellTool { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub try { File payloadSrcFile = new File("g:/payload.apk"); File unShellDexFile = new File("g:/unshell.dex"); byte[] payloadArray = encrpt(readFileBytes(payloadSrcFile)); byte[] unShellDexArray = readFileBytes(unShellDexFile); int payloadLen = payloadArray.length; int unShellDexLen = unShellDexArray.length; int totalLen = payloadLen + unShellDexLen +4; byte[] newdex = new byte[totalLen]; //添加解壳代码 System.arraycopy(unShellDexArray, 0, newdex, 0, unShellDexLen); //添加加密后的解壳数据 System.arraycopy(payloadArray, 0, newdex, unShellDexLen, payloadLen); //添加解壳数据长度 System.arraycopy(intToByte(payloadLen), 0, newdex, totalLen-4, 4); //修改DEX file size文件头 fixFileSizeHeader(newdex); //修改DEX SHA1 文件头 fixSHA1Header(newdex); //修改DEX CheckSum文件头 fixCheckSumHeader(newdex); String str = "g:/classes.dex"; File file = new File(str); if (!file.exists()) { file.createNewFile(); } FileOutputStream localFileOutputStream = new FileOutputStream(str); localFileOutputStream.write(newdex); localFileOutputStream.flush(); localFileOutputStream.close(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } //直接返回数据,读者可以添加自己加密方法 private static byte[] encrpt(byte[] srcdata){ return srcdata; } private static void fixCheckSumHeader(byte[] dexBytes) { Adler32 adler = new Adler32(); adler.update(dexBytes, 12, dexBytes.length - 12); long value = adler.getValue(); int va = (int) value; byte[] newcs = intToByte(va); byte[] recs = new byte[4]; for (int i = 0; i < 4; i++) { recs[i] = newcs[newcs.length - 1 - i]; System.out.println(Integer.toHexString(newcs[i])); } System.arraycopy(recs, 0, dexBytes, 8, 4); System.out.println(Long.toHexString(value)); System.out.println(); } public static byte[] intToByte(int number) { byte[] b = new byte[4]; for (int i = 3; i >= 0; i--) { b[i] = (byte) (number % 256); number >>= 8; } return b; } private static void fixSHA1Header(byte[] dexBytes) throws NoSuchAlgorithmException { MessageDigest md = MessageDigest.getInstance("SHA-1"); md.update(dexBytes, 32, dexBytes.length - 32); byte[] newdt = md.digest(); System.arraycopy(newdt, 0, dexBytes, 12, 20); String hexstr = ""; for (int i = 0; i < newdt.length; i++) { hexstr += Integer.toString((newdt[i] & 0xff) + 0x100, 16) .substring(1); } System.out.println(hexstr); } private static void fixFileSizeHeader(byte[] dexBytes) { byte[] newfs = intToByte(dexBytes.length); System.out.println(Integer.toHexString(dexBytes.length)); byte[] refs = new byte[4]; for (int i = 0; i < 4; i++) { refs[i] = newfs[newfs.length - 1 - i]; System.out.println(Integer.toHexString(newfs[i])); } System.arraycopy(refs, 0, dexBytes, 32, 4); } private static byte[] readFileBytes(File file) throws IOException { byte[] arrayOfByte = new byte[1024]; ByteArrayOutputStream localByteArrayOutputStream = new ByteArrayOutputStream(); FileInputStream fis = new FileInputStream(file); while (true) { int i = fis.read(arrayOfByte); if (i != -1) { localByteArrayOutputStream.write(arrayOfByte, 0, i); } else { return localByteArrayOutputStream.toByteArray(); } } } } |
2、 解壳程序流程及代码实现
在解壳程序的开发过程中需要解决如下几个关键的技术问题:
(1)解壳代码如何能够第一时间执行?
Android程序由不同的组件构成,系统在有需要的时候启动程序组件。因此解壳程序必须在Android系统启动组件之前运行,完成对解壳数 据的解壳及APK文件的动态加载,否则会使程序出现加载类失败的异常。
Android开发者都知道Applicaiton做为整个应用的上下文,会被系统第一时间调用,这也是应用开发者程序代码的第一执行点。因此通过对 AndroidMainfest.xml的application的配置可以实现解壳代码第一时间运行。
1 2 3 4 5 |
<application android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" android:name="<span style="color: rgb(255, 0, 0);"><em><strong>com.android.dexunshell.ProxyApplication</strong></em></span>" > </application> |
(2)如何替换回源程序原有的Application?
当在AndroidMainfest.xml文件配置为解壳代码的Application时。源程序原有的Applicaiton将被替换,为了不影响源程序代码逻辑,我们需要 在解壳代码运行完成后,替换回源程序原有的Application对象。我们通过在AndroidMainfest.xml文件中配置原有Applicaiton类信息来达到我们 的目的。解壳程序要在运行完毕后通过创建配置的Application对象,并通过反射修改回原Application。
1 2 3 4 5 6 |
<application android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" android:name="<em><strong><span style="color: rgb(255, 0, 0);">com.android.dexunshell.ProxyApplication</span></strong></em>" > <span style="color: rgb(255, 0, 0);"><em><strong><meta-data android:name="APPLICATION_CLASS_NAME" android:value="com.***.Application"/></strong></em></span> </application> |
(3)如何通过DexClassLoader实现对apk代码的动态加载。
我们知道DexClassLoader加载的类是没有组件生命周期的,也就是说即使DexClassLoader通过对APK的动态加载完成了对组件类的加载, 当系统启动该组件时,还会出现加载类失败的异常。为什么组件类被动态加载入虚拟机,但系统却出现加载类失败呢?
通过查看Android源代码我们知道组件类的加载是由另一个ClassLoader来完成的,DexClassLoader和系统组件ClassLoader并不存在关 系,系统组件ClassLoader当然找不到由DexClassLoader加载的类,如果把系统组件ClassLoader的parent修改成DexClassLoader,我们就可 以实现对apk代码的动态加载。
(4)如何使解壳后的APK资源文件被代码动态引用。
代码默认引用的资源文件在最外层的解壳程序中,因此我们要增加系统的资源加载路径来实现对借壳后APK文件资源的加载。
解壳实现代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 |
package com.android.dexunshell; import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import dalvik.system.DexClassLoader; import android.app.Application; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.os.Bundle; public class ProxyApplication extends Application { private static final String appkey = "APPLICATION_CLASS_NAME"; private String apkFileName; private String odexPath; private String libPath; protected void attachBaseContext(Context base) { super.attachBaseContext(base); try { File odex = this.getDir("payload_odex", MODE_PRIVATE); File libs = this.getDir("payload_lib", MODE_PRIVATE); odexPath = odex.getAbsolutePath(); libPath = libs.getAbsolutePath(); apkFileName = odex.getAbsolutePath() + "/payload.apk"; File dexFile = new File(apkFileName); if (!dexFile.exists()) dexFile.createNewFile(); // 读取程序classes.dex文件 byte[] dexdata = this.readDexFileFromApk(); // 分离出解壳后的apk文件已用于动态加载 this.splitPayLoadFromDex(dexdata); // 配置动态加载环境 Object currentActivityThread = RefInvoke.invokeStaticMethod( "android.app.ActivityThread", "currentActivityThread", new Class[] {}, new Object[] {}); String packageName = this.getPackageName(); HashMap mPackages = (HashMap) RefInvoke.getFieldOjbect( "android.app.ActivityThread", currentActivityThread, "mPackages"); WeakReference wr = (WeakReference) mPackages.get(packageName); DexClassLoader dLoader = new DexClassLoader(apkFileName, odexPath, libPath, (ClassLoader) RefInvoke.getFieldOjbect( "android.app.LoadedApk", wr.get(), "mClassLoader")); RefInvoke.setFieldOjbect("android.app.LoadedApk", "mClassLoader", wr.get(), dLoader); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } public void onCreate() { { // 如果源应用配置有Appliction对象,则替换为源应用Applicaiton,以便不影响源程序逻辑。 String appClassName = null; try { ApplicationInfo ai = this.getPackageManager() .getApplicationInfo(this.getPackageName(), PackageManager.GET_META_DATA); Bundle bundle = ai.metaData; if (bundle != null && bundle.containsKey("APPLICATION_CLASS_NAME")) { appClassName = bundle.getString("APPLICATION_CLASS_NAME"); } else { return; } } catch (NameNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } Object currentActivityThread = RefInvoke.invokeStaticMethod( "android.app.ActivityThread", "currentActivityThread", new Class[] {}, new Object[] {}); Object mBoundApplication = RefInvoke.getFieldOjbect( "android.app.ActivityThread", currentActivityThread, "mBoundApplication"); Object loadedApkInfo = RefInvoke.getFieldOjbect( "android.app.ActivityThread$AppBindData", mBoundApplication, "info"); RefInvoke.setFieldOjbect("android.app.LoadedApk", "mApplication", loadedApkInfo, null); Object oldApplication = RefInvoke.getFieldOjbect( "android.app.ActivityThread", currentActivityThread, "mInitialApplication"); ArrayList<Application> mAllApplications = (ArrayList<Application>) RefInvoke .getFieldOjbect("android.app.ActivityThread", currentActivityThread, "mAllApplications"); mAllApplications.remove(oldApplication); ApplicationInfo appinfo_In_LoadedApk = (ApplicationInfo) RefInvoke .getFieldOjbect("android.app.LoadedApk", loadedApkInfo, "mApplicationInfo"); ApplicationInfo appinfo_In_AppBindData = (ApplicationInfo) RefInvoke .getFieldOjbect("android.app.ActivityThread$AppBindData", mBoundApplication, "appInfo"); appinfo_In_LoadedApk.className = appClassName; appinfo_In_AppBindData.className = appClassName; Application app = (Application) RefInvoke.invokeMethod( "android.app.LoadedApk", "makeApplication", loadedApkInfo, new Class[] { boolean.class, Instrumentation.class }, new Object[] { false, null }); RefInvoke.setFieldOjbect("android.app.ActivityThread", "mInitialApplication", currentActivityThread, app); HashMap mProviderMap = (HashMap) RefInvoke.getFieldOjbect( "android.app.ActivityThread", currentActivityThread, "mProviderMap"); Iterator it = mProviderMap.values().iterator(); while (it.hasNext()) { Object providerClientRecord = it.next(); Object localProvider = RefInvoke.getFieldOjbect( "android.app.ActivityThread$ProviderClientRecord", providerClientRecord, "mLocalProvider"); RefInvoke.setFieldOjbect("android.content.ContentProvider", "mContext", localProvider, app); } app.onCreate(); } } private void splitPayLoadFromDex(byte[] data) throws IOException { byte[] apkdata = decrypt(data); int ablen = apkdata.length; byte[] dexlen = new byte[4]; System.arraycopy(apkdata, ablen - 4, dexlen, 0, 4); ByteArrayInputStream bais = new ByteArrayInputStream(dexlen); DataInputStream in = new DataInputStream(bais); int readInt = in.readInt(); System.out.println(Integer.toHexString(readInt)); byte[] newdex = new byte[readInt]; System.arraycopy(apkdata, ablen - 4 - readInt, newdex, 0, readInt); File file = new File(apkFileName); try { FileOutputStream localFileOutputStream = new FileOutputStream(file); localFileOutputStream.write(newdex); localFileOutputStream.close(); } catch (IOException localIOException) { throw new RuntimeException(localIOException); } ZipInputStream localZipInputStream = new ZipInputStream( new BufferedInputStream(new FileInputStream(file))); while (true) { ZipEntry localZipEntry = localZipInputStream.getNextEntry(); if (localZipEntry == null) { localZipInputStream.close(); break; } String name = localZipEntry.getName(); if (name.startsWith("lib/") && name.endsWith(".so")) { File storeFile = new File(libPath + "/" + name.substring(name.lastIndexOf('/'))); storeFile.createNewFile(); FileOutputStream fos = new FileOutputStream(storeFile); byte[] arrayOfByte = new byte[1024]; while (true) { int i = localZipInputStream.read(arrayOfByte); if (i == -1) break; fos.write(arrayOfByte, 0, i); } fos.flush(); fos.close(); } localZipInputStream.closeEntry(); } localZipInputStream.close(); } private byte[] readDexFileFromApk() throws IOException { ByteArrayOutputStream dexByteArrayOutputStream = new ByteArrayOutputStream(); ZipInputStream localZipInputStream = new ZipInputStream( new BufferedInputStream(new FileInputStream( this.getApplicationInfo().sourceDir))); while (true) { ZipEntry localZipEntry = localZipInputStream.getNextEntry(); if (localZipEntry == null) { localZipInputStream.close(); break; } if (localZipEntry.getName().equals("classes.dex")) { byte[] arrayOfByte = new byte[1024]; while (true) { int i = localZipInputStream.read(arrayOfByte); if (i == -1) break; dexByteArrayOutputStream.write(arrayOfByte, 0, i); } } localZipInputStream.closeEntry(); } localZipInputStream.close(); return dexByteArrayOutputStream.toByteArray(); } // //直接返回数据,读者可以添加自己解密方法 private byte[] decrypt(byte[] data) { return data; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 |
package com.android.dexunshell; import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import dalvik.system.DexClassLoader; import android.app.Application; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.os.Bundle; public class ProxyApplication extends Application { <span style="white-space:pre"> </span>private static final String appkey = "APPLICATION_CLASS_NAME"; <span style="white-space:pre"> </span>private String apkFileName; <span style="white-space:pre"> </span>private String odexPath; <span style="white-space:pre"> </span>private String libPath; <span style="white-space:pre"> </span>protected void attachBaseContext(Context base) { <span style="white-space:pre"> </span>super.attachBaseContext(base); <span style="white-space:pre"> </span>try { <span style="white-space:pre"> </span>File odex = this.getDir("payload_odex", MODE_PRIVATE); <span style="white-space:pre"> </span>File libs = this.getDir("payload_lib", MODE_PRIVATE); <span style="white-space:pre"> </span>odexPath = odex.getAbsolutePath(); <span style="white-space:pre"> </span>libPath = libs.getAbsolutePath(); <span style="white-space:pre"> </span>apkFileName = odex.getAbsolutePath() + "/payload.apk"; <span style="white-space:pre"> </span>File dexFile = new File(apkFileName); <span style="white-space:pre"> </span>if (!dexFile.exists()) <span style="white-space:pre"> </span>dexFile.createNewFile(); <span style="white-space:pre"> </span>// 读取程序classes.dex文件 <span style="white-space:pre"> </span>byte[] dexdata = this.readDexFileFromApk(); <span style="white-space:pre"> </span>// 分离出解壳后的apk文件已用于动态加载 <span style="white-space:pre"> </span>this.splitPayLoadFromDex(dexdata); <span style="white-space:pre"> </span>// 配置动态加载环境 <span style="white-space:pre"> </span>Object currentActivityThread = RefInvoke.invokeStaticMethod( <span style="white-space:pre"> </span>"android.app.ActivityThread", "currentActivityThread", <span style="white-space:pre"> </span>new Class[] {}, new Object[] {}); <span style="white-space:pre"> </span>String packageName = this.getPackageName(); <span style="white-space:pre"> </span>HashMap mPackages = (HashMap) RefInvoke.getFieldOjbect( <span style="white-space:pre"> </span>"android.app.ActivityThread", currentActivityThread, <span style="white-space:pre"> </span>"mPackages"); <span style="white-space:pre"> </span>WeakReference wr = (WeakReference) mPackages.get(packageName); <span style="white-space:pre"> </span>DexClassLoader dLoader = new DexClassLoader(apkFileName, odexPath, <span style="white-space:pre"> </span>libPath, (ClassLoader) RefInvoke.getFieldOjbect( <span style="white-space:pre"> </span>"android.app.LoadedApk", wr.get(), "mClassLoader")); <span style="white-space:pre"> </span>RefInvoke.setFieldOjbect("android.app.LoadedApk", "mClassLoader", <span style="white-space:pre"> </span>wr.get(), dLoader); <span style="white-space:pre"> </span>} catch (Exception e) { <span style="white-space:pre"> </span>// TODO Auto-generated catch block <span style="white-space:pre"> </span>e.printStackTrace(); <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>public void onCreate() { <span style="white-space:pre"> </span>{ <span style="white-space:pre"> </span>// 如果源应用配置有Appliction对象,则替换为源应用Applicaiton,以便不影响源程序逻辑。 <span style="white-space:pre"> </span>String appClassName = null; <span style="white-space:pre"> </span>try { <span style="white-space:pre"> </span>ApplicationInfo ai = this.getPackageManager() <span style="white-space:pre"> </span>.getApplicationInfo(this.getPackageName(), <span style="white-space:pre"> </span>PackageManager.GET_META_DATA); <span style="white-space:pre"> </span>Bundle bundle = ai.metaData; <span style="white-space:pre"> </span>if (bundle != null <span style="white-space:pre"> </span>&& bundle.containsKey("APPLICATION_CLASS_NAME")) { <span style="white-space:pre"> </span>appClassName = bundle.getString("APPLICATION_CLASS_NAME"); <span style="white-space:pre"> </span>} else { <span style="white-space:pre"> </span>return; <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>} catch (NameNotFoundException e) { <span style="white-space:pre"> </span>// TODO Auto-generated catch block <span style="white-space:pre"> </span>e.printStackTrace(); <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>Object currentActivityThread = RefInvoke.invokeStaticMethod( <span style="white-space:pre"> </span>"android.app.ActivityThread", "currentActivityThread", <span style="white-space:pre"> </span>new Class[] {}, new Object[] {}); <span style="white-space:pre"> </span>Object mBoundApplication = RefInvoke.getFieldOjbect( <span style="white-space:pre"> </span>"android.app.ActivityThread", currentActivityThread, <span style="white-space:pre"> </span>"mBoundApplication"); <span style="white-space:pre"> </span>Object loadedApkInfo = RefInvoke.getFieldOjbect( <span style="white-space:pre"> </span>"android.app.ActivityThread$AppBindData", <span style="white-space:pre"> </span>mBoundApplication, "info"); <span style="white-space:pre"> </span>RefInvoke.setFieldOjbect("android.app.LoadedApk", "mApplication", <span style="white-space:pre"> </span>loadedApkInfo, null); <span style="white-space:pre"> </span>Object oldApplication = RefInvoke.getFieldOjbect( <span style="white-space:pre"> </span>"android.app.ActivityThread", currentActivityThread, <span style="white-space:pre"> </span>"mInitialApplication"); <span style="white-space:pre"> </span>ArrayList<Application> mAllApplications = (ArrayList<Application>) RefInvoke <span style="white-space:pre"> </span>.getFieldOjbect("android.app.ActivityThread", <span style="white-space:pre"> </span>currentActivityThread, "mAllApplications"); <span style="white-space:pre"> </span>mAllApplications.remove(oldApplication); <span style="white-space:pre"> </span>ApplicationInfo appinfo_In_LoadedApk = (ApplicationInfo) RefInvoke <span style="white-space:pre"> </span>.getFieldOjbect("android.app.LoadedApk", loadedApkInfo, <span style="white-space:pre"> </span>"mApplicationInfo"); <span style="white-space:pre"> </span>ApplicationInfo appinfo_In_AppBindData = (ApplicationInfo) RefInvoke <span style="white-space:pre"> </span>.getFieldOjbect("android.app.ActivityThread$AppBindData", <span style="white-space:pre"> </span>mBoundApplication, "appInfo"); <span style="white-space:pre"> </span>appinfo_In_LoadedApk.className = appClassName; <span style="white-space:pre"> </span>appinfo_In_AppBindData.className = appClassName; <span style="white-space:pre"> </span>Application app = (Application) RefInvoke.invokeMethod( <span style="white-space:pre"> </span>"android.app.LoadedApk", "makeApplication", loadedApkInfo, <span style="white-space:pre"> </span>new Class[] { boolean.class, Instrumentation.class }, <span style="white-space:pre"> </span>new Object[] { false, null }); <span style="white-space:pre"> </span>RefInvoke.setFieldOjbect("android.app.ActivityThread", <span style="white-space:pre"> </span>"mInitialApplication", currentActivityThread, app); <span style="white-space:pre"> </span>HashMap mProviderMap = (HashMap) RefInvoke.getFieldOjbect( <span style="white-space:pre"> </span>"android.app.ActivityThread", currentActivityThread, <span style="white-space:pre"> </span>"mProviderMap"); <span style="white-space:pre"> </span>Iterator it = mProviderMap.values().iterator(); <span style="white-space:pre"> </span>while (it.hasNext()) { <span style="white-space:pre"> </span>Object providerClientRecord = it.next(); <span style="white-space:pre"> </span>Object localProvider = RefInvoke.getFieldOjbect( <span style="white-space:pre"> </span>"android.app.ActivityThread$ProviderClientRecord", <span style="white-space:pre"> </span>providerClientRecord, "mLocalProvider"); <span style="white-space:pre"> </span>RefInvoke.setFieldOjbect("android.content.ContentProvider", <span style="white-space:pre"> </span>"mContext", localProvider, app); <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>app.onCreate(); <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>private void splitPayLoadFromDex(byte[] data) throws IOException { <span style="white-space:pre"> </span>byte[] apkdata = decrypt(data); <span style="white-space:pre"> </span>int ablen = apkdata.length; <span style="white-space:pre"> </span>byte[] dexlen = new byte[4]; <span style="white-space:pre"> </span>System.arraycopy(apkdata, ablen - 4, dexlen, 0, 4); <span style="white-space:pre"> </span>ByteArrayInputStream bais = new ByteArrayInputStream(dexlen); <span style="white-space:pre"> </span>DataInputStream in = new DataInputStream(bais); <span style="white-space:pre"> </span>int readInt = in.readInt(); <span style="white-space:pre"> </span>System.out.println(Integer.toHexString(readInt)); <span style="white-space:pre"> </span>byte[] newdex = new byte[readInt]; <span style="white-space:pre"> </span>System.arraycopy(apkdata, ablen - 4 - readInt, newdex, 0, readInt); <span style="white-space:pre"> </span>File file = new File(apkFileName); <span style="white-space:pre"> </span>try { <span style="white-space:pre"> </span>FileOutputStream localFileOutputStream = new FileOutputStream(file); <span style="white-space:pre"> </span>localFileOutputStream.write(newdex); <span style="white-space:pre"> </span>localFileOutputStream.close(); <span style="white-space:pre"> </span>} catch (IOException localIOException) { <span style="white-space:pre"> </span>throw new RuntimeException(localIOException); <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>ZipInputStream localZipInputStream = new ZipInputStream( <span style="white-space:pre"> </span>new BufferedInputStream(new FileInputStream(file))); <span style="white-space:pre"> </span>while (true) { <span style="white-space:pre"> </span>ZipEntry localZipEntry = localZipInputStream.getNextEntry(); <span style="white-space:pre"> </span>if (localZipEntry == null) { <span style="white-space:pre"> </span>localZipInputStream.close(); <span style="white-space:pre"> </span>break; <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>String name = localZipEntry.getName(); <span style="white-space:pre"> </span>if (name.startsWith("lib/") && name.endsWith(".so")) { <span style="white-space:pre"> </span>File storeFile = new File(libPath + "/" <span style="white-space:pre"> </span>+ name.substring(name.lastIndexOf('/'))); <span style="white-space:pre"> </span>storeFile.createNewFile(); <span style="white-space:pre"> </span>FileOutputStream fos = new FileOutputStream(storeFile); <span style="white-space:pre"> </span>byte[] arrayOfByte = new byte[1024]; <span style="white-space:pre"> </span>while (true) { <span style="white-space:pre"> </span>int i = localZipInputStream.read(arrayOfByte); <span style="white-space:pre"> </span>if (i == -1) <span style="white-space:pre"> </span>break; <span style="white-space:pre"> </span>fos.write(arrayOfByte, 0, i); <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>fos.flush(); <span style="white-space:pre"> </span>fos.close(); <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>localZipInputStream.closeEntry(); <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>localZipInputStream.close(); <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>private byte[] readDexFileFromApk() throws IOException { <span style="white-space:pre"> </span>ByteArrayOutputStream dexByteArrayOutputStream = new ByteArrayOutputStream(); <span style="white-space:pre"> </span>ZipInputStream localZipInputStream = new ZipInputStream( <span style="white-space:pre"> </span>new BufferedInputStream(new FileInputStream( <span style="white-space:pre"> </span>this.getApplicationInfo().sourceDir))); <span style="white-space:pre"> </span>while (true) { <span style="white-space:pre"> </span>ZipEntry localZipEntry = localZipInputStream.getNextEntry(); <span style="white-space:pre"> </span>if (localZipEntry == null) { <span style="white-space:pre"> </span>localZipInputStream.close(); <span style="white-space:pre"> </span>break; <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>if (localZipEntry.getName().equals("classes.dex")) { <span style="white-space:pre"> </span>byte[] arrayOfByte = new byte[1024]; <span style="white-space:pre"> </span>while (true) { <span style="white-space:pre"> </span>int i = localZipInputStream.read(arrayOfByte); <span style="white-space:pre"> </span>if (i == -1) <span style="white-space:pre"> </span>break; <span style="white-space:pre"> </span>dexByteArrayOutputStream.write(arrayOfByte, 0, i); <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>localZipInputStream.closeEntry(); <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>localZipInputStream.close(); <span style="white-space:pre"> </span>return dexByteArrayOutputStream.toByteArray(); <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>// //直接返回数据,读者可以添加自己解密方法 <span style="white-space:pre"> </span>private byte[] decrypt(byte[] data) { <span style="white-space:pre"> </span>return data; <span style="white-space:pre"> </span>} |
RefInvoke为反射调用工具类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 |
package com.android.dexunshell; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class RefInvoke { public static Object invokeStaticMethod(String class_name, String method_name, Class[] pareTyple, Object[] pareVaules){ try { Class obj_class = Class.forName(class_name); Method method = obj_class.getMethod(method_name,pareTyple); return method.invoke(null, pareVaules); } catch (SecurityException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalArgumentException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (NoSuchMethodException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (InvocationTargetException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } return null; } public static Object invokeMethod(String class_name, String method_name, Object obj ,Class[] pareTyple, Object[] pareVaules){ try { Class obj_class = Class.forName(class_name); Method method = obj_class.getMethod(method_name,pareTyple); return method.invoke(obj, pareVaules); } catch (SecurityException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalArgumentException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (NoSuchMethodException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (InvocationTargetException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } return null; } public static Object getFieldOjbect(String class_name,Object obj, String filedName){ try { Class obj_class = Class.forName(class_name); Field field = obj_class.getDeclaredField(filedName); field.setAccessible(true); return field.get(obj); } catch (SecurityException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (NoSuchFieldException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalArgumentException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } return null; } public static Object getStaticFieldOjbect(String class_name, String filedName){ try { Class obj_class = Class.forName(class_name); Field field = obj_class.getDeclaredField(filedName); field.setAccessible(true); return field.get(null); } catch (SecurityException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (NoSuchFieldException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalArgumentException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } return null; } public static void setFieldOjbect(String classname, String filedName, Object obj, Object filedVaule){ try { Class obj_class = Class.forName(classname); Field field = obj_class.getDeclaredField(filedName); field.setAccessible(true); field.set(obj, filedVaule); } catch (SecurityException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (NoSuchFieldException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalArgumentException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public static void setStaticOjbect(String class_name, String filedName, Object filedVaule){ try { Class obj_class = Class.forName(class_name); Field field = obj_class.getDeclaredField(filedName); field.setAccessible(true); field.set(null, filedVaule); } catch (SecurityException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (NoSuchFieldException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalArgumentException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } |
三、总结
本文代码基本实现了APK文件的加壳及脱壳原理,该代码作为实验代码还有诸多地方需要改进。比如:
1、加壳数据的加密算法的添加。
2、脱壳代码由java语言实现,可通过C代码的实现对脱壳逻辑进行保护,以达到更好的反逆向分析效果。