作android framework方面的工做將近三年的時間了,如今公司讓作一下android apk安全方面的研究,因而最近就在網上找大量的資料來學習。如今將最近學習成果作一下整理總結。學習的這些成果我會作成一個系列慢慢寫出來與你們分享,共同進步。這篇主要講apk的加殼技術,廢話很少說了直接進入正題。android
1、加殼技術原理程序員
所謂apk的加殼技術和pc exe的加殼原理同樣,就是在程序的外面再包裹上另一段代碼,保護裏面的代碼不被非法修改或反編譯,在程序運行的時候優先取得程序的控制權作一些咱們本身想作的工做。(哈哈,跟病毒的原理差很少)shell
PC exe的加殼原理以下:安全
2、android apk加殼實現微信
要想實現加殼須要解決的技術點以下:app
(1)怎麼第一時間執行咱們的加殼程序?微信公衆平臺
首先根據上面的原理咱們在apk中要想優先取得程序的控制權做爲android apk的開發人員都知道Application會被系統第一時間調用而咱們的程序也會放在這裏執行。ide
(2)怎麼將咱們的加殼程序和原有的android apk文件合併到一塊兒?學習
咱們知道android apk最終會打包生成dex文件,咱們能夠將咱們的程序生成dex文件後,將咱們要進行加殼的apk和咱們dex文件合併成一個文件,而後修改dex文件頭中的checksum、signature 和file_size的信息,而且要附加加殼的apk的長度信息在dex文件中,以便咱們進行解殼保證原來apk的正常運行。加完殼後整個文件的結構以下:this
(3)怎麼將原來的apk正常的運行起來?
按照(2)中的合併方式在當咱們的程序首先運行起來後,逆向讀取dex文件獲取原來的apk文件經過DexClassLoader動態加載。
具體實現以下:
(1)修改原來apk的AndroidMainfest.xml文件,假如原來apk的AndroidMainfest.xml文件內容以下:
1. <application 2. android:icon="@drawable/ic_launcher" 3. android:label="@string/app_name" 4. android:theme="@style/AppTheme" android:name="com.android.MyApplication" > 5. </application>
修改後的內容以下:
1. <application 2. android:icon="@drawable/ic_launcher" 3. android:label="@string/app_name" 4. android:theme="@style/AppTheme" android:name="com.android.shellApplication" > 5. <meta-data android:name="APPLICATION_CLASS_NAME" android:value="com.android.MyApplication"/> 6. </application>
com.android.shellApplication這個就是咱們的程序的的application的名稱,而
<meta-data android:name="APPLICATION_CLASS_NAME" android:value="com.android.MyApplication"/>
是原來的apk的application名稱。
(2)合併文件代碼實現以下:
public class ShellTool { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub try { File payloadSrcFile = new File("payload.apk");//咱們要加殼的apk文件 File unShellDexFile = new File("classes.dex");//咱們的程序生成的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]; //添加咱們程序的dex System.arraycopy(unShellDexArray, 0, newdex, 0, unShellDexLen); //添加要加殼的apk文件 System.arraycopy(payloadArray, 0, newdex, unShellDexLen, payloadLen); //添加apk文件長度 System.arraycopy(intToByte(payloadLen), 0, newdex, totalLen-4, 4); //修改DEX file size文件頭 fixFileSizeHeader(newdex); //修改DEX SHA1 文件頭 fixSHA1Header(newdex); //修改DEX CheckSum文件頭 fixCheckSumHeader(newdex); String str = "outdir/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(); } } } }
(3)在咱們的程序中加載運行原來的apk文件,代碼以下:
public class shellApplication 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; }
根據上面的講述相信你們對apk的加殼技術有了必定的瞭解,下一篇咱們將講解另外一種android apk防止反編譯技術,期待你們的捧場。若是對這篇講的技術有任何疑問及想要得到這篇文章講的技術的工程源碼
歡迎關注我的微信公衆平臺:程序員互動聯盟(coder_online),掃一掃下方二維碼或搜索微信號coder_online便可關注,咱們能夠在線交流。