1、前言
今天又到週末了,憋了很久又要出博客了,今天來介紹一下Android中的如何對Apk進行加固的原理。現階段。咱們知道Android中的反編譯工做愈來愈讓人操做熟練,咱們辛苦的開發出一個apk,結果被人反編譯了,那心情真心不舒服。雖然咱們混淆,作到native層,可是這都是治標不治本。反編譯的技術在更新,那麼保護Apk的技術就不能中止。如今網上有不少Apk加固的第三方平臺,最有名的應當屬於:愛加密和梆梆加固了。其實加固有些人認爲很高深的技術,其實否則,說的簡單點就是對源Apk進行加密,而後在套上一層殼便可,固然這裏還有一些細節須要處理,這就是本文須要介紹的內容了。html
2、原理解析
下面就來看一下Android中加殼的原理:java
咱們在加固的過程當中須要三個對象:android
一、須要加密的Apk(源Apk)c++
二、殼程序Apk(負責解密Apk工做)算法
三、加密工具(將源Apk進行加密和殼Dex合併成新的Dex)安全
主要步驟:數據結構
咱們拿到須要加密的Apk和本身的殼程序Apk,而後用加密算法對源Apk進行加密在將殼Apk進行合併獲得新的Dex文件,最後替換殼程序中的dex文件便可,獲得新的Apk,那麼這個新的Apk咱們也叫做脫殼程序Apk.他已經不是一個完整意義上的Apk程序了,他的主要工做是:負責解密源Apk.而後加載Apk,讓其正常運行起來。app
在這個過程當中咱們可能須要瞭解的一個知識是:如何將源Apk和殼Apk進行合併成新的Dexide
這裏就須要瞭解Dex文件的格式了。下面就來簡單介紹一下Dex文件的格式工具
具體Dex文件格式的詳細介紹能夠查看這個文件:http://download.csdn.net/detail/jiangwei0910410003/9102599
主要來看一下Dex文件的頭部信息,其實Dex文件和Class文件的格式分析原理都是同樣的,他們都是有固定的格式,咱們知道如今反編譯的一些工具:
一、jd-gui:能夠查看jar中的類,其實他就是解析class文件,只要瞭解class文件的格式就能夠
二、dex2jar:將dex文件轉化成jar,原理也是同樣的,只要知道Dex文件的格式,可以解析出dex文件中的類信息就能夠了
固然咱們在分析這個文件的時候,最重要的仍是頭部信息,應該他是一個文件的開始部分,也是索引部分,內部信息很重要。
咱們今天只要關注上面紅色標記的三個部分:
1) checksum
文件校驗碼 ,使用alder32 算法校驗文件除去 maigc ,checksum 外餘下的全部文件區域 ,用於檢查文件錯誤 。
2) signature
使用 SHA-1 算法 hash 除去 magic ,checksum 和 signature 外餘下的全部文件區域 ,用於惟一識別本文件 。
3) file_size
Dex 文件的大小 。
爲何說咱們只須要關注這三個字段呢?
由於咱們須要將一個文件(加密以後的源Apk)寫入到Dex中,那麼咱們確定須要修改文件校驗碼(checksum).由於他是檢查文件是否有錯誤。那麼signature也是同樣,也是惟一識別文件的算法。還有就是須要修改dex文件的大小。
不過這裏還須要一個操做,就是標註一下咱們加密的Apk的大小,由於咱們在脫殼的時候,須要知道Apk的大小,才能正確的獲得Apk。那麼這個值放到哪呢?這個值直接放到文件的末尾就能夠了。
因此總結一下咱們須要作:修改Dex的三個文件頭,將源Apk的大小追加到殼dex的末尾就能夠了。
咱們修改以後獲得新的Dex文件樣式以下:
那麼咱們知道原理了,下面就是代碼實現了。因此這裏有三個工程:
一、源程序項目(須要加密的Apk)
二、脫殼項目(解密源Apk和加載Apk)
三、對源Apk進行加密和脫殼項目的Dex的合併
3、項目案例
下面先來看一下源程序
一、須要加密的源程序Apk項目:ForceApkObj
須要一個Application類,這個到後面說爲何須要:
MyApplication.Java
- package com.example.forceapkobj;
-
- import android.app.Application;
- import android.util.Log;
-
- public class MyApplication extends Application{
-
- @Override
- public void onCreate() {
- super.onCreate();
- Log.i("demo", "source apk onCreate:"+this);
- }
-
- }
就是打印一下onCreate方法。
MainActivity.java
- package com.example.forceapkobj;
-
- import android.app.Activity;
- import android.content.Intent;
- import android.os.Bundle;
- import android.util.Log;
- import android.view.View;
- import android.view.View.OnClickListener;
- import android.widget.TextView;
-
- public class MainActivity extends Activity {
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- TextView content = new TextView(this);
- content.setText("I am Source Apk");
- content.setOnClickListener(new OnClickListener(){
- @Override
- public void onClick(View arg0) {
- Intent intent = new Intent(MainActivity.this, SubActivity.class);
- startActivity(intent);
- }});
- setContentView(content);
-
- Log.i("demo", "app:"+getApplicationContext());
-
- }
-
- }
也是打印一下內容。
二、加殼程序項目:DexShellTools
加殼程序其實就是一個Java工程,由於咱們從上面的分析能夠看到,他的工做就是加密源Apk,而後將其寫入到脫殼Dex文件中,修改文件頭,獲得一個新的Dex文件便可。
看一下代碼:
- package com.example.reforceapk;
-
- 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 mymain {
-
- public static void main(String[] args) {
-
- try {
- File payloadSrcFile = new File("force/ForceApkObj.apk");
- System.out.println("apk size:"+payloadSrcFile.length());
- File unShellDexFile = new File("force/ForceApkObj.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);
-
- fixFileSizeHeader(newdex);
-
- fixSHA1Header(newdex);
-
- fixCheckSumHeader(newdex);
-
- String str = "force/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) {
- e.printStackTrace();
- }
- }
-
-
- private static byte[] encrpt(byte[] srcdata){
- for(int i = 0;i<srcdata.length;i++){
- srcdata[i] = (byte)(0xFF ^ srcdata[i]);
- }
- 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();
- }
- }
- }
- }
下面來分析一下:
紅色部分其實就是最核心的工做:
1>、加密源程序Apk文件
- byte[] payloadArray = encrpt(readFileBytes(payloadSrcFile));
加密算法很簡單:
- private static byte[] encrpt(byte[] srcdata){
- for(int i = 0;i<srcdata.length;i++){
- srcdata[i] = (byte)(0xFF ^ srcdata[i]);
- }
- return srcdata;
- }
對每一個字節進行異或一下便可。
(說明:這裏是爲了簡單,因此就用了很簡單的加密算法了,其實爲了增長破解難度,咱們應該使用更高效的加密算法,同事最好將加密操做放到native層去作)
2>、合併文件:將加密以後的Apk和原脫殼Dex進行合併
- 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);
3>、在文件的末尾追加源程序Apk的長度
- System.arraycopy(intToByte(payloadLen), 0, newdex, totalLen-4, 4);
4>、修改新Dex文件的文件頭信息:file_size; sha1; check_sum
- fixFileSizeHeader(newdex);
- fixSHA1Header(newdex);
- fixCheckSumHeader(newdex);
具體修改能夠參照以前說的文件頭格式,修改指定位置的字節值便可。
這裏咱們還須要兩個輸入文件:
1>、源Apk文件:ForceApkObj.apk
2>、脫殼程序的Dex文件:ForceApkObj.dex
那麼第一個文件咱們都知道,就是上面的源程序編譯以後的Apk文件,那麼第二個文件咱們怎麼獲得呢?這個就是咱們要講到的第三個項目:脫殼程序項目,他是一個Android項目,咱們在編譯以後,可以獲得他的classes.dex文件,而後修改一下名稱就可。
三、脫殼項目:ReforceApk
在講解這個項目以前,咱們先來了解一下這個脫殼項目的工做:
1>、經過反射置換android.app.ActivityThread 中的mClassLoader爲加載解密出APK的DexClassLoader,該DexClassLoader一方面加載了源程序、另外一方面以原mClassLoader爲父節點,這就保證了即加載了源程序又沒有放棄原先加載的資源與系統代碼。
關於這部份內容,不瞭解的同窗能夠看一下ActivityThread.java的源碼:
或者直接看一下這篇文章:
http://blog.csdn.NET/jiangwei0910410003/article/details/48104455
如何獲得系統加載Apk的類加載器,而後咱們怎麼將加載進來的Apk運行起來等問題都在這篇文章中說到了。
2>、找到源程序的Application,經過反射創建並運行。
這裏須要注意的是,咱們如今是加載一個完整的Apk,讓他運行起來,那麼咱們知道一個Apk運行的時候都是有一個Application對象的,這個也是一個程序運行以後的全局類。因此咱們必須找到解密以後的源Apk的Application類,運行的他的onCreate方法,這樣源Apk纔開始他的運行生命週期。這裏咱們如何獲得源Apk的Application的類呢?這個咱們後面會說道。使用meta標籤進行設置。
下面來看一下總體的流程圖:
因此咱們看到這裏還須要一個核心的技術就是動態加載。關於動態加載技術,不瞭解的同窗能夠看這篇文章:
http://blog.csdn.net/jiangwei0910410003/article/details/48104581
下面來看一下代碼:
- package com.example.reforceapk;
-
- 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.lang.reflect.Method;
- import java.util.ArrayList;
- import java.util.HashMap;
- import java.util.Iterator;
- import java.util.zip.ZipEntry;
- import java.util.zip.ZipInputStream;
-
- import android.app.Application;
- import android.app.Instrumentation;
- import android.content.Context;
- import android.content.pm.ApplicationInfo;
- import android.content.pm.PackageManager;
- import android.content.pm.PackageManager.NameNotFoundException;
- import android.content.res.AssetManager;
- import android.content.res.Resources;
- import android.content.res.Resources.Theme;
- import android.os.Bundle;
- import android.util.ArrayMap;
- import android.util.Log;
- import dalvik.system.DexClassLoader;
-
- public class ProxyApplication extends Application{
- private static final String appkey = "APPLICATION_CLASS_NAME";
- private String apkFileName;
- private String odexPath;
- private String libPath;
-
-
- @Override
- 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);
- Log.i("demo", "apk size:"+dexFile.length());
- if (!dexFile.exists())
- {
- dexFile.createNewFile();
-
- byte[] dexdata = this.readDexFileFromApk();
-
-
- this.splitPayLoadFromDex(dexdata);
- }
-
- Object currentActivityThread = RefInvoke.invokeStaticMethod(
- "android.app.ActivityThread", "currentActivityThread",
- new Class[] {}, new Object[] {});
- String packageName = this.getPackageName();
-
- ArrayMap mPackages = (ArrayMap) 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);
-
- Log.i("demo","classloader:"+dLoader);
-
- try{
- Object actObj = dLoader.loadClass("com.example.forceapkobj.MainActivity");
- Log.i("demo", "actObj:"+actObj);
- }catch(Exception e){
- Log.i("demo", "activity:"+Log.getStackTraceString(e));
- }
-
-
- } catch (Exception e) {
- Log.i("demo", "error:"+Log.getStackTraceString(e));
- e.printStackTrace();
- }
- }
-
- @Override
- public void onCreate() {
- {
-
-
- Log.i("demo", "onCreate");
-
- 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 {
- Log.i("demo", "have no application class name");
- return;
- }
- } catch (NameNotFoundException e) {
- Log.i("demo", "error:"+Log.getStackTraceString(e));
- 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);
-
-
- ArrayMap mProviderMap = (ArrayMap) 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);
- }
-
- Log.i("demo", "app:"+app);
-
- app.onCreate();
- }
- }
-
-
- private void splitPayLoadFromDex(byte[] apkdata) throws IOException {
- 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);
-
-
-
-
- newdex = decrypt(newdex);
-
-
- 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[] srcdata) {
- for(int i=0;i<srcdata.length;i++){
- srcdata[i] = (byte)(0xFF ^ srcdata[i]);
- }
- return srcdata;
- }
-
-
-
- protected AssetManager mAssetManager;
- protected Resources mResources;
- protected Theme mTheme;
-
- protected void loadResources(String dexPath) {
- try {
- AssetManager assetManager = AssetManager.class.newInstance();
- Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
- addAssetPath.invoke(assetManager, dexPath);
- mAssetManager = assetManager;
- } catch (Exception e) {
- Log.i("inject", "loadResource error:"+Log.getStackTraceString(e));
- e.printStackTrace();
- }
- Resources superRes = super.getResources();
- superRes.getDisplayMetrics();
- superRes.getConfiguration();
- mResources = new Resources(mAssetManager, superRes.getDisplayMetrics(),superRes.getConfiguration());
- mTheme = mResources.newTheme();
- mTheme.setTo(super.getTheme());
- }
-
- @Override
- public AssetManager getAssets() {
- return mAssetManager == null ? super.getAssets() : mAssetManager;
- }
-
- @Override
- public Resources getResources() {
- return mResources == null ? super.getResources() : mResources;
- }
-
- @Override
- public Theme getTheme() {
- return mTheme == null ? super.getTheme() : mTheme;
- }
-
- }
首先咱們來看一下具體步驟的代碼實現:
1>、獲得脫殼Apk中的dex文件,而後從這個文件中獲得源程序Apk.進行解密,而後加載
- @Override
- 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);
- Log.i("demo", "apk size:"+dexFile.length());
- if (!dexFile.exists())
- {
- dexFile.createNewFile();
-
- byte[] dexdata = this.readDexFileFromApk();
-
-
- this.splitPayLoadFromDex(dexdata);
- }
-
- Object currentActivityThread = RefInvoke.invokeStaticMethod(
- "android.app.ActivityThread", "currentActivityThread",
- new Class[] {}, new Object[] {});
- String packageName = this.getPackageName();
-
- ArrayMap mPackages = (ArrayMap) 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);
-
- Log.i("demo","classloader:"+dLoader);
-
- try{
- Object actObj = dLoader.loadClass("com.example.forceapkobj.MainActivity");
- Log.i("demo", "actObj:"+actObj);
- }catch(Exception e){
- Log.i("demo", "activity:"+Log.getStackTraceString(e));
- }
-
-
- } catch (Exception e) {
- Log.i("demo", "error:"+Log.getStackTraceString(e));
- e.printStackTrace();
- }
- }
這裏須要注意的一個問題,就是咱們須要找到一個時機,就是在脫殼程序尚未運行起來的時候,來加載源程序的Apk,執行他的onCreate方法,那麼這個時機不能太晚,否則的話,就是運行脫殼程序,而不是源程序了。查看源碼咱們知道。Application中有一個方法:attachBaseContext這個方法,他在Application的onCreate方法執行前就會執行了,那麼咱們的工做就須要在這裏進行
1)、從脫殼程序Apk中找到源程序Apk,而且進行解密操做
- 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);
- Log.i("demo", "apk size:"+dexFile.length());
- if (!dexFile.exists())
- {
- dexFile.createNewFile();
-
- byte[] dexdata = this.readDexFileFromApk();
-
-
- this.splitPayLoadFromDex(dexdata);
- }
這個脫殼解密操做必定要和咱們以前的加殼以及加密操做對應,否則就會出現Dex加載錯誤問題
A) 從Apk中獲取到Dex文件
- 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();
- }
其實就是解壓Apk文件,直接獲得dex文件便可
B) 從脫殼Dex中獲得源Apk文件
- private void splitPayLoadFromDex(byte[] apkdata) throws IOException {
- 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);
-
-
-
-
- newdex = decrypt(newdex);
-
-
- 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();
-
-
- }
C) 解密源程序Apk
- private byte[] decrypt(byte[] srcdata) {
- for(int i=0;i<srcdata.length;i++){
- srcdata[i] = (byte)(0xFF ^ srcdata[i]);
- }
- return srcdata;
- }
這個解密算法和加密算法是一致的
2>、加載解密以後的源程序Apk
- Object currentActivityThread = RefInvoke.invokeStaticMethod(
- "android.app.ActivityThread", "currentActivityThread",
- new Class[] {}, new Object[] {});
- String packageName = this.getPackageName();
- ArrayMap mPackages = (ArrayMap) 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);
-
- Log.i("demo","classloader:"+dLoader);
-
- try{
- Object actObj = dLoader.loadClass("com.example.forceapkobj.MainActivity");
- Log.i("demo", "actObj:"+actObj);
- }catch(Exception e){
- Log.i("demo", "activity:"+Log.getStackTraceString(e));
- }
2)、找到源程序的Application程序,讓其運行
- @Override
- public void onCreate() {
- {
-
-
- Log.i("demo", "onCreate");
-
- 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 {
- Log.i("demo", "have no application class name");
- return;
- }
- } catch (NameNotFoundException e) {
- Log.i("demo", "error:"+Log.getStackTraceString(e));
- 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);
-
-
- ArrayMap mProviderMap = (ArrayMap) 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);
- }
-
- Log.i("demo", "app:"+app);
-
- app.onCreate();
- }
- }
直接在脫殼的Application中的onCreate方法中進行就能夠了。這裏咱們還能夠看到是經過AndroidManifest.xml中的meta標籤獲取源程序Apk中的Application對象的。
下面來看一下AndoridManifest.xml文件中的內容:
在這裏咱們定義了源程序Apk的Application類名。
項目下載:http://download.csdn.net/detail/jiangwei0910410003/9102741
4、運行程序
那麼到這裏咱們就介紹完了,這三個項目的內容,下面就來看看如何運行吧:
運行步驟:
第一步:獲得源程序Apk文件和脫殼程序的Dex文件
運行源程序和脫殼程序項目,以後獲得這兩個文件(記得將classes.dex文件更名ForceApkObj.dex),而後使用加殼程序進行加殼:
這裏的ForceApkObj.apk文件和ForceApkObj.dex文件是輸入文件,輸出的是classes.dex文件。
第二步:替換脫殼程序中的classes.dex文件
咱們在第一步中獲得加殼以後的classes.dex文件以後,而且咱們在第一步運行脫殼項目的時候獲得一個ReforceApk.apk文件,這時候咱們使用解壓縮軟件進行替換:
第三步:咱們在第二步的時候獲得替換以後的ReforceApk.apk文件,這個文件由於被修改了,因此咱們須要重新對他簽名,否則運行也是報錯的。
工具下載:http://download.csdn.net/detail/jiangwei0910410003/9102767
下載以後的工具須要用ReforeceApk.apk文件替換ReforceApk_des.apk文件,而後運行run.bat就能夠獲得簽名以後的文件了。
run.bat文件的命令以下:
cd C:\Users\i\Desktop\forceapks
jarsigner -verbose -keystore forceapk -storepass 123456 -keypass 123456 -sigfile CERT -digestalg SHA1 -sigalg MD5withRSA -signedjar ReforceApk_des.apk ReforceApk.apk jiangwei
del ReforceApk.apk
這裏最主要的命令就是中間的一條簽名的命令,關於命令的參數說明以下:
jarsigner -verbose -keystore 簽名文件 -storepass 密碼 -keypass alias的密碼 -sigfile CERT -digestalg SHA1 -sigalg MD5withRSA 簽名後的文件 簽名前的apk alias名稱
eg:
jarsigner -verbose -keystore forceapk -storepass 123456 -keypass 123456 -sigfile CERT -digestalg SHA1 -sigalg MD5withRSA -signedjar ReforceApk_des.apk ReforceApk_src.apk jiangwei
簽名文件的密碼:123456
alais的密碼:123456
因此這裏咱們在獲得ReforceApk.apk文件的時候,須要簽名,關於Eclipse中如何簽名一個Apk的話,這裏就很少說了,本身google一下吧:
那麼經過上面的三個步驟以後咱們獲得一個簽名以後的最終文件:ReforceApk_des.apk
咱們安裝這個Apk,而後運行,效果以下:
看到運行結果的那一瞬間,咱們是多麼的開心,多麼的有成就感,可是這個過程當中遇到的問題,是可想而知的。
咱們這個時候再去反編譯一下源程序Apk(這個文件是咱們脫殼出來的payload.apk,看ReforeceApk中的代碼,就知道他的位置了)
發現dex文件格式是不正確的。說明咱們的加固是成功的。
5、遇到的問題
一、研究的過程當中遇到簽名不正確的地方,開始的時候,直接替換dex文件以後,就直接運行了Apk,可是老是提示簽名不正確。
二、運行的過程當中說找不到源程序中的Activity,這個問題其實我在動態加載的那篇文章中說道了,咱們須要在脫殼程序中的AndroidManifest.xml中什麼一下源程序中的Activiity:
6、技術要點
一、對Dex文件格式的瞭解
二、動態加載技術的深刻掌握
三、Application的執行流程的瞭解
四、如何從Apk中獲得Dex文件
五、如何重新簽名一個Apk程序
7、綜合概述
咱們經過上面的過程能夠看到,關於Apk加固的工做仍是挺複雜的,涉及到的東西也挺多的,下面就在來總結一下吧:
一、加殼程序
任務:對源程序Apk進行加密,合併脫殼程序的Dex文件 ,而後輸入一個加殼以後的Dex文件
語言:任何語言均可以,不限於Java語言
技術點:對Dex文件格式的解析
二、脫殼程序
任務:獲取源程序Apk,進行解密,而後動態加載進來,運行程序
語言:Android項目(Java)
技術點:如何從Apk中獲取Dex文件,動態加載Apk,使用反射運行Application
8、總結
Android中的Apk反編譯多是每一個開發都會經歷的事,可是在反編譯的過程當中,對於源程序的開發者來講那是不公平的,那麼Apk加固也是應運而生,可是即便是這樣,咱們也仍是作不到那麼的安全,如今網上也是有不少文章在解析梆梆加固的原理了。並且有人破解成功了,那麼加固還不是怎麼安全。最後一句話:逆向和加固是一個永不停息的戰爭。
from:https://www.cnblogs.com/yanzheng216/articles/6831689.html