<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                #### 1、需要加密的Apk(源Apk) #### 2、殼程序Apk(負責解密Apk工作) #### 3、加密工具(將源Apk進行加密和殼Dex合并成新的Dex) 主要步驟: #### 我們拿到需要加密的Apk和自己的殼程序Apk,然后用加密算法對源Apk進行加密在將殼Apk進行合并得到新的Dex文件,最后替換殼程序中的dex文件即可,得到新的Apk,那么這個新的Apk我們也叫作脫殼程序Apk.他已經不是一個完整意義上的Apk程序了,他的主要工作是:負責解密源Apk.然后加載Apk,讓其正常運行起來。 在這個過程中我們可能需要了解的一個知識是:??如何將源Apk和殼Apk進行合并成新的Dex 這里就需要了解Dex文件的格式了。下面就來簡單介紹一下Dex文件的格式 具體Dex文件格式的詳細介紹可以查看這個文件:??[http://download.csdn.net/detail/jiangwei0910410003/9102599](http://download.csdn.net/detail/jiangwei0910410003/9102599) 主要來看一下Dex文件的頭部信息,其實Dex文件和Class文件的格式分析原理都是一樣的,他們都是有固定的格式,我們知道現在反編譯的一些工具: #### 1、jd-gui:可以查看jar中的類,其實他就是解析class文件,只要了解class文件的格式就可以 #### 2、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也是一樣,也是唯一識別文件的[算法](http://lib.csdn.net/base/datastructure "算法與數據結構知識庫")。還有就是需要修改dex文件的大小。 #### 不過這里還需要一個操作,就是標注一下我們加密的Apk的大小,因為我們在脫殼的時候,需要知道Apk的大小,才能正確的得到Apk。那么這個值放到哪呢?這個值直接放到文件的末尾就可以了。 所以總結一下我們需要做:修改Dex的三個文件頭,將源Apk的大小追加到殼dex的末尾就可以了。 我們修改之后得到新的Dex文件樣式如下: 那么我們知道原理了,下面就是代碼實現了。所以這里有三個工程: #### 1、源程序項目(需要加密的Apk) #### 2、脫殼項目(解密源Apk和加載Apk) #### 3、對源Apk進行加密和脫殼項目的Dex的合并 ## 三、項目案例? 下面先來看一下源程序 ## 1、需要加密的源程序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()); } } ~~~ 也是打印一下內容。 ## 2、加殼程序項目:DexShellTools? 加殼程序其實就是一個[Java](http://lib.csdn.net/base/javase "Java SE知識庫")工程,因為我們從上面的分析可以看到,他的工作就是加密源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 { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub try { File payloadSrcFile = new File("force/ForceApkObj.apk"); //需要加殼的程序 System.out.println("apk size:"+payloadSrcFile.length()); File unShellDexFile = new File("force/ForceApkObj.dex"); //解客dex byte[] payloadArray = encrpt(readFileBytes(payloadSrcFile));//以二進制形式讀出apk,并進行加密處理//對源Apk進行加密操作 byte[] unShellDexArray = readFileBytes(unShellDexFile);//以二進制形式讀出dex int payloadLen = payloadArray.length; int unShellDexLen = unShellDexArray.length; int totalLen = payloadLen + unShellDexLen +4;//多出4字節是存放長度的。 byte[] newdex = new byte[totalLen]; // 申請了新的長度 //添加解殼代碼 System.arraycopy(unShellDexArray, 0, newdex, 0, unShellDexLen);//先拷貝dex內容 //添加加密后的解殼數據 System.arraycopy(payloadArray, 0, newdex, unShellDexLen, payloadLen);//再在dex內容后面拷貝apk的內容 //添加解殼數據長度 System.arraycopy(intToByte(payloadLen), 0, newdex, totalLen-4, 4);//最后4為長度 //修改DEX file size文件頭 fixFileSizeHeader(newdex); //修改DEX SHA1 文件頭 fixSHA1Header(newdex); //修改DEX CheckSum文件頭 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; } /** * 修改dex頭,CheckSum 校驗碼 * @param dexBytes */ private static void fixCheckSumHeader(byte[] dexBytes) { Adler32 adler = new Adler32(); adler.update(dexBytes, 12, dexBytes.length - 12);//從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);//效驗碼賦值(8-11) System.out.println(Long.toHexString(value)); System.out.println(); } /** * int 轉byte[] * @param number * @return */ 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; } /** * 修改dex頭 sha1值 * @param dexBytes * @throws NoSuchAlgorithmException */ private static void fixSHA1Header(byte[] dexBytes) throws NoSuchAlgorithmException { MessageDigest md = MessageDigest.getInstance("SHA-1"); md.update(dexBytes, 32, dexBytes.length - 32);//從32為到結束計算sha--1 byte[] newdt = md.digest(); System.arraycopy(newdt, 0, dexBytes, 12, 20);//修改sha-1值(12-31) //輸出sha-1值,可有可無 String hexstr = ""; for (int i = 0; i < newdt.length; i++) { hexstr += Integer.toString((newdt[i] & 0xff) + 0x100, 16) .substring(1); } System.out.println(hexstr); } /** * 修改dex頭 file_size值 * @param dexBytes */ 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);//修改(32-35) } /** * 以二進制讀出文件內容 * @param file * @return * @throws IOException */ 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));//以二進制形式讀出apk,并進行加密處理//對源Apk進行加密操作 ~~~ 加密算法很簡單: ~~~ //直接返回數據,讀者可以添加自己加密方法 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;//多出4字節是存放長度的。 byte[] newdex = new byte[totalLen]; // 申請了新的長度 //添加解殼代碼 System.arraycopy(unShellDexArray, 0, newdex, 0, unShellDexLen);//先拷貝dex內容 //添加加密后的解殼數據 System.arraycopy(payloadArray, 0, newdex, unShellDexLen, payloadLen);//再在dex內容后面拷貝apk的內容 ~~~ #### 3>、在文件的末尾追加源程序Apk的長度 ~~~ //添加解殼數據長度 System.arraycopy(intToByte(payloadLen), 0, newdex, totalLen-4, 4);//最后4為長度 ~~~ 4>、修改新Dex文件的文件頭信息:file_size; sha1; check_sum ~~~ //修改DEX file size文件頭 fixFileSizeHeader(newdex); //修改DEX SHA1 文件頭 fixSHA1Header(newdex); //修改DEX CheckSum文件頭 fixCheckSumHeader(newdex); ~~~ 具體修改可以參照之前說的文件頭格式,修改指定位置的字節值即可。 這里我們還需要兩個輸入文件: #### 1>、源Apk文件:ForceApkObj.apk #### 2>、脫殼程序的Dex文件:ForceApkObj.dex 那么第一個文件我們都知道,就是上面的源程序編譯之后的Apk文件,那么第二個文件我們怎么得到呢?這個就是我們要講到的第三個項目:脫殼程序項目,他是一個[Android](http://lib.csdn.net/base/android "Android知識庫")項目,我們在編譯之后,能夠得到他的classes.dex文件,然后修改一下名稱就可。 ## 3、脫殼項目:ReforceApk? 在講解這個項目之前,我們先來了解一下這個脫殼項目的工作: 1>、通過反射置換android.app.ActivityThread 中的mClassLoader為加載解密出APK的DexClassLoader,該DexClassLoader一方面加載了源程序、另一方面以原mClassLoader為父節點,這就保證了即加載了源程序又沒有放棄原先加載的資源與系統代碼。 關于這部分內容,不了解的同學可以看一下ActivityThread.java的源碼: 或者直接看一下這篇文章: [http://blog.csdn.net/jiangwei0910410003/article/details/48104455](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](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; //這是context 賦值 @Override protected void attachBaseContext(Context base) { super.attachBaseContext(base); try { //創建兩個文件夾payload_odex,payload_lib 私有的,可寫的文件目錄 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(); //在payload_odex文件夾內,創建payload.apk // 讀取程序classes.dex文件 byte[] dexdata = this.readDexFileFromApk(); // 分離出解殼后的apk文件已用于動態加載 this.splitPayLoadFromDex(dexdata); } // 配置動態加載環境 Object currentActivityThread = RefInvoke.invokeStaticMethod( "android.app.ActivityThread", "currentActivityThread", new Class[] {}, new Object[] {});//獲取主線程對象 http://blog.csdn.net/myarrow/article/details/14223493 String packageName = this.getPackageName();//當前apk的包名 //下面兩句不是太理解 ArrayMap mPackages = (ArrayMap) RefInvoke.getFieldOjbect( "android.app.ActivityThread", currentActivityThread, "mPackages"); WeakReference wr = (WeakReference) mPackages.get(packageName); //創建被加殼apk的DexClassLoader對象 加載apk內的類和本地代碼(c/c++代碼) DexClassLoader dLoader = new DexClassLoader(apkFileName, odexPath, libPath, (ClassLoader) RefInvoke.getFieldOjbect( "android.app.LoadedApk", wr.get(), "mClassLoader")); //base.getClassLoader(); 是不是就等同于 (ClassLoader) RefInvoke.getFieldOjbect()? 有空驗證下//? //把當前進程的DexClassLoader 設置成了被加殼apk的DexClassLoader ----有點c++中進程環境的意思~~ 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() { { //loadResources(apkFileName); Log.i("demo", "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");//className 是配置在xml文件中的。 } else { Log.i("demo", "have no application class name"); return; } } catch (NameNotFoundException e) { Log.i("demo", "error:"+Log.getStackTraceString(e)); e.printStackTrace(); } //有值的話調用該Applicaiton 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"); //把當前進程的mApplication 設置成了null RefInvoke.setFieldOjbect("android.app.LoadedApk", "mApplication", loadedApkInfo, null); Object oldApplication = RefInvoke.getFieldOjbect( "android.app.ActivityThread", currentActivityThread, "mInitialApplication"); //http://www.codeceo.com/article/android-context.html ArrayList<Application> mAllApplications = (ArrayList<Application>) RefInvoke .getFieldOjbect("android.app.ActivityThread", currentActivityThread, "mAllApplications"); mAllApplications.remove(oldApplication);//刪除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 });//執行 makeApplication(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(); } } /** * 釋放被加殼的apk文件,so文件 * @param data * @throws IOException */ private void splitPayLoadFromDex(byte[] apkdata) throws IOException { int ablen = apkdata.length; //取被加殼apk的長度 這里的長度取值,對應加殼時長度的賦值都可以做些簡化 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]; //把被加殼apk內容拷貝到newdex中 System.arraycopy(apkdata, ablen - 4 - readInt, newdex, 0, readInt); //這里應該加上對于apk的解密操作,若加殼是加密處理的話 //? //對源程序Apk進行解密 newdex = decrypt(newdex); //寫入apk文件 File file = new File(apkFileName); try { FileOutputStream localFileOutputStream = new FileOutputStream(file); localFileOutputStream.write(newdex); localFileOutputStream.close(); } catch (IOException localIOException) { throw new RuntimeException(localIOException); } //分析被加殼的apk文件 ZipInputStream localZipInputStream = new ZipInputStream( new BufferedInputStream(new FileInputStream(file))); while (true) { ZipEntry localZipEntry = localZipInputStream.getNextEntry();//不了解這個是否也遍歷子目錄,看樣子應該是遍歷的 if (localZipEntry == null) { localZipInputStream.close(); break; } //取出被加殼apk用到的so文件,放到 libPath中(data/data/包名/payload_lib) 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(); } /** * 從apk包里面獲取dex文件內容(byte) * @return * @throws IOException */ 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.進行解密,然后加載 ~~~ //這是context 賦值 @Override protected void attachBaseContext(Context base) { super.attachBaseContext(base); try { //創建兩個文件夾payload_odex,payload_lib 私有的,可寫的文件目錄 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(); //在payload_odex文件夾內,創建payload.apk // 讀取程序classes.dex文件 byte[] dexdata = this.readDexFileFromApk(); // 分離出解殼后的apk文件已用于動態加載 this.splitPayLoadFromDex(dexdata); } // 配置動態加載環境 Object currentActivityThread = RefInvoke.invokeStaticMethod( "android.app.ActivityThread", "currentActivityThread", new Class[] {}, new Object[] {});//獲取主線程對象 http://blog.csdn.net/myarrow/article/details/14223493 String packageName = this.getPackageName();//當前apk的包名 //下面兩句不是太理解 ArrayMap mPackages = (ArrayMap) RefInvoke.getFieldOjbect( "android.app.ActivityThread", currentActivityThread, "mPackages"); WeakReference wr = (WeakReference) mPackages.get(packageName); //創建被加殼apk的DexClassLoader對象 加載apk內的類和本地代碼(c/c++代碼) DexClassLoader dLoader = new DexClassLoader(apkFileName, odexPath, libPath, (ClassLoader) RefInvoke.getFieldOjbect( "android.app.LoadedApk", wr.get(), "mClassLoader")); //base.getClassLoader(); 是不是就等同于 (ClassLoader) RefInvoke.getFieldOjbect()? 有空驗證下//? //把當前進程的DexClassLoader 設置成了被加殼apk的DexClassLoader ----有點c++中進程環境的意思~~ 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,并且進行解密操作 ~~~ //創建兩個文件夾payload_odex,payload_lib 私有的,可寫的文件目錄 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(); //在payload_odex文件夾內,創建payload.apk // 讀取程序classes.dex文件 byte[] dexdata = this.readDexFileFromApk(); // 分離出解殼后的apk文件已用于動態加載 this.splitPayLoadFromDex(dexdata); } ~~~ 這個脫殼解密操作一定要和我們之前的加殼以及加密操作對應,不然就會出現Dex加載錯誤問題 A) 從Apk中獲取到Dex文件 ~~~ /** * 從apk包里面獲取dex文件內容(byte) * @return * @throws IOException */ 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文件 ~~~ /** * 釋放被加殼的apk文件,so文件 * @param data * @throws IOException */ private void splitPayLoadFromDex(byte[] apkdata) throws IOException { int ablen = apkdata.length; //取被加殼apk的長度 這里的長度取值,對應加殼時長度的賦值都可以做些簡化 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]; //把被加殼apk內容拷貝到newdex中 System.arraycopy(apkdata, ablen - 4 - readInt, newdex, 0, readInt); //這里應該加上對于apk的解密操作,若加殼是加密處理的話 //? //對源程序Apk進行解密 newdex = decrypt(newdex); //寫入apk文件 File file = new File(apkFileName); try { FileOutputStream localFileOutputStream = new FileOutputStream(file); localFileOutputStream.write(newdex); localFileOutputStream.close(); } catch (IOException localIOException) { throw new RuntimeException(localIOException); } //分析被加殼的apk文件 ZipInputStream localZipInputStream = new ZipInputStream( new BufferedInputStream(new FileInputStream(file))); while (true) { ZipEntry localZipEntry = localZipInputStream.getNextEntry();//不了解這個是否也遍歷子目錄,看樣子應該是遍歷的 if (localZipEntry == null) { localZipInputStream.close(); break; } //取出被加殼apk用到的so文件,放到 libPath中(data/data/包名/payload_lib) 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[] {});//獲取主線程對象 http://blog.csdn.net/myarrow/article/details/14223493 String packageName = this.getPackageName();//當前apk的包名 //下面兩句不是太理解 ArrayMap mPackages = (ArrayMap) RefInvoke.getFieldOjbect( "android.app.ActivityThread", currentActivityThread, "mPackages"); WeakReference wr = (WeakReference) mPackages.get(packageName); //創建被加殼apk的DexClassLoader對象 加載apk內的類和本地代碼(c/c++代碼) DexClassLoader dLoader = new DexClassLoader(apkFileName, odexPath, libPath, (ClassLoader) RefInvoke.getFieldOjbect( "android.app.LoadedApk", wr.get(), "mClassLoader")); //base.getClassLoader(); 是不是就等同于 (ClassLoader) RefInvoke.getFieldOjbect()? 有空驗證下//? //把當前進程的DexClassLoader 設置成了被加殼apk的DexClassLoader ----有點c++中進程環境的意思~~ 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() { { //loadResources(apkFileName); Log.i("demo", "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");//className 是配置在xml文件中的。 } else { Log.i("demo", "have no application class name"); return; } } catch (NameNotFoundException e) { Log.i("demo", "error:"+Log.getStackTraceString(e)); e.printStackTrace(); } //有值的話調用該Applicaiton 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"); //把當前進程的mApplication 設置成了null RefInvoke.setFieldOjbect("android.app.LoadedApk", "mApplication", loadedApkInfo, null); Object oldApplication = RefInvoke.getFieldOjbect( "android.app.ActivityThread", currentActivityThread, "mInitialApplication"); //http://www.codeceo.com/article/android-context.html ArrayList<Application> mAllApplications = (ArrayList<Application>) RefInvoke .getFieldOjbect("android.app.ActivityThread", currentActivityThread, "mAllApplications"); mAllApplications.remove(oldApplication);//刪除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 });//執行 makeApplication(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 ## 四、運行程序 那么到這里我們就介紹完了,這三個項目的內容,下面就來看看如何運行吧: 運行步驟: #### 第一步:得到源程序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](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文件格式是不正確的。說明我們的加固是成功的。 ## 五、遇到的問題 1、研究的過程中遇到簽名不正確的地方,開始的時候,直接替換dex文件之后,就直接運行了Apk,但是總是提示簽名不正確。 2、運行的過程中說找不到源程序中的Activity,這個問題其實我在動態加載的那篇文章中說道了,我們需要在脫殼程序中的AndroidManifest.xml中什么一下源程序中的Activiity: ## 六、技術要點 #### 1、對Dex文件格式的了解 #### 2、動態加載技術的深入掌握 #### 3、Application的執行流程的了解
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看