推广 热搜: page  使用  音视频  个数  搜索引擎  选择  企业  百度  可以  父亲 

Android中的Apk的加固(加壳)原理解析和实现(转)

   日期:2025-01-01     作者:bc8a0    caijiyuan   评论:0    移动:http://ww.kub2b.com/mobile/news/18466.html
核心提示:一、前言今天又到周末了,憋了好久又要出博客了,今天来介绍一下Android中的如何对Apk进行加固的原理。现阶段。我们

一、前言

今天又到周末了,憋了好久又要出博客了,今天来介绍一下Android中的如何对Apk进行加固的原理。现阶段。我们知道Android中的反编译工作越来越让人操作熟练,我们辛苦的开发出一个apk,结果被人反编译了,那心情真心不舒服。虽然我们混淆,做到native层,但是这都是治标不治本。反编译的技术在更新,那么保护Apk的技术就不能停止。现在网上有很多Apk加固的第三方平台,最有名的应当属于:爱加密和梆梆加固了。其实加固有些人认为很高深的技术,其实不然,说的简单点就是对源Apk进行加密,然后在套上一层壳即可,当然这里还有一些细节需要处理,这就是本文需要介绍的内容了。

二、原理解析

下面就来看一下Android中加壳的原理

我们在加固的过程中需要三个对象

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

主要来看一下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也是一样,也是唯一识别文件的算法。还有就是需要修改dex文件的大小。

不过这里还需要一个操作,就是标注一下我们加密的Apk的大小,因为我们在脱壳的时候,需要知道Apk的大小,才能正确的得到Apk。那么这个值放到哪呢?这个值直接放到文件的末尾就可以了。

所以总结一下我们需要做:修改Dex的三个文件头,将源Apk的大小追加到壳dex的末尾就可以了。

我们修改之后得到新的Dex文件样式如下

那么我们知道原理了,下面就是代码实现了。所以这里有三个工程

1、源程序项目(需要加密的Apk)

2、脱壳项目(解密源Apk和加载Apk)

3、对源Apk进行加密和脱壳项目的Dex的合并

三、项目案例

下面先来看一下源程序

1、需要加密的源程序Apk项目ForceApkObj

需要一个Application类,这个到后面说为什么需要

MyApplication.java

[java] view plaincopy

  1. package com.example.forceapkobj; 

  2. import android.app.Application; 
  3. import android.util.Log; 

  4. public class MyApplication extends Application{ 

  5. @Override
  6. public void onCreate() { 
  7. super.onCreate(); 
  8.         Log.i("demo", "source apk onCreate:"+this); 
  9.     } 

就是打印一下onCreate方法。

MainActivity.java

[java] view plaincopy

  1. package com.example.forceapkobj; 

  2. import android.app.Activity; 
  3. import android.content.Intent; 
  4. import android.os.Bundle; 
  5. import android.util.Log; 
  6. import android.view.View; 
  7. import android.view.View.OnClickListener; 
  8. import android.widget.TextView; 

  9. public class MainActivity extends Activity { 

  10. @Override
  11. protected void onCreate(Bundle savedInstanceState) { 
  12. super.onCreate(savedInstanceState); 

  13.         TextView content = new TextView(this); 
  14.         content.setText("I am Source Apk"); 
  15.         content.setonClickListener(new onClickListener(){ 
  16. @Override
  17. public void onClick(View arg0) { 
  18.                 Intent intent = new Intent(MainActivity.this, SubActivity.class); 
  19.                 startActivity(intent); 
  20.             }}); 
  21.         setContentView(content); 

  22.         Log.i("demo", "app:"+getApplicationContext()); 

  23.     } 

也是打印一下内容。

2、加壳程序项目DexShellTools

加壳程序其实就是一个Java工程,因为我们从上面的分析可以看到,他的工作就是加密源Apk,然后将其写入到脱壳Dex文件中,修改文件头,得到一个新的Dex文件即可。

看一下代码

[java] view plaincopy

  1. package com.example.reforceapk; 

  2. import java.io.ByteArrayOutputStream; 
  3. import java.io.File; 
  4. import java.io.FileInputStream; 
  5. import java.io.FileOutputStream; 
  6. import java.io.IOException; 
  7. import java.security.MessageDigest; 
  8. import java.security.NoSuchAlgorithmException; 
  9. import java.util.zip.Adler32; 


  10. public class mymain { 
  11. public static void main(String[] args) { 
  12. // TODO Auto-generated method stub
  13. try { 
  14.             File payloadSrcFile = new File("force/ForceApkObj.apk");   //需要加壳的程序
  15.             System.out.println("apk size:"+payloadSrcFile.length()); 
  16.             File unShellDexFile = new File("force/ForceApkObj.dex");    //解客dex
  17. byte[] payloadArray = encrpt(readFileBytes(payloadSrcFile));//以二进制形式读出apk,并进行加密处理//对源Apk进行加密操作
  18. byte[] unShellDexArray = readFileBytes(unShellDexFile);//以二进制形式读出dex
  19. int payloadLen = payloadArray.length; 
  20. int unShellDexLen = unShellDexArray.length; 
  21. int totalLen = payloadLen + unShellDexLen +4;//多出4字节是存放长度的。
  22. byte[] newdex = new byte[totalLen]; // 申请了新的长度
  23. //添加解壳代码
  24.             System.arraycopy(unShellDexArray, 0, newdex, 0, unShellDexLen);//先拷贝dex内容
  25. //添加加密后的解壳数据
  26.             System.arraycopy(payloadArray, 0, newdex, unShellDexLen, payloadLen);//再在dex内容后面拷贝apk的内容
  27. //添加解壳数据长度
  28.             System.arraycopy(intToByte(payloadLen), 0, newdex, totalLen-4, 4);//最后4为长度
  29. //修改DEX file size文件头
  30.             fixFileSizeHeader(newdex); 
  31. //修改DEX SHA1 文件头
  32.             fixSHA1Header(newdex); 
  33. //修改DEX CheckSum文件头
  34.             fixCheckSumHeader(newdex); 

  35.             String str = "force/classes.dex"; 
  36.             File file = new File(str); 
  37. if (!file.exists()) { 
  38.                 file.createNewFile(); 
  39.             } 

  40.             FileOutputStream localFileOutputStream = new FileOutputStream(str); 
  41.             localFileOutputStream.write(newdex); 
  42.             localFileOutputStream.flush(); 
  43.             localFileOutputStream.close(); 


  44.         } catch (Exception e) { 
  45.             e.printStackTrace(); 
  46.         } 
  47.     } 

  48. //直接返回数据,读者可以添加自己加密方法
  49. private static byte[] encrpt(byte[] srcdata){ 
  50. for(int i = 0;i<srcdata.length;i++){ 
  51.             srcdata[i] = (byte)(0xFF ^ srcdata[i]); 
  52.         } 
  53. return srcdata; 
  54.     } 

  55. private static void fixCheckSumHeader(byte[] dexBytes) { 
  56.         Adler32 adler = new Adler32(); 
  57.         adler.update(dexBytes, 12, dexBytes.length - 12);//从12到文件末尾计算校验码
  58. long value = adler.getValue(); 
  59. int va = (int) value; 
  60. byte[] newcs = intToByte(va); 
  61. //高位在前,低位在前掉个个
  62. byte[] recs = new byte[4]; 
  63. for (int i = 0; i < 4; i++) { 
  64.             recs[i] = newcs[newcs.length - 1 - i]; 
  65.             System.out.println(Integer.toHexString(newcs[i])); 
  66.         } 
  67.         System.arraycopy(recs, 0, dexBytes, 8, 4);//效验码赋值(8-11
  68.         System.out.println(Long.toHexString(value)); 
  69.         System.out.println(); 
  70.     } 


  71. public static byte[] intToByte(int number) { 
  72. byte[] b = new byte[4]; 
  73. for (int i = 3; i >= 0; i--) { 
  74.             b[i] = (byte) (number % 256); 
  75.             number >>= 8; 
  76.         } 
  77. return b; 
  78.     } 

  79. private static void fixSHA1Header(byte[] dexBytes) 
  80. throws NoSuchAlgorithmException { 
  81.         MessageDigest md = MessageDigest.getInstance("SHA-1"); 
  82.         md.update(dexBytes, 32, dexBytes.length - 32);//从32为到结束计算sha--1
  83. byte[] newdt = md.digest(); 
  84.         System.arraycopy(newdt, 0, dexBytes, 12, 20);//修改sha-1值(12-31
  85. //输出sha-1值,可有可无
  86.         String hexstr = ""; 
  87. for (int i = 0; i < newdt.length; i++) { 
  88.             hexstr += Integer.toString((newdt[i] & 0xff) + 0x100, 16) 
  89.                     .substring(1); 
  90.         } 
  91.         System.out.println(hexstr); 
  92.     } 

  93. private static void fixFileSizeHeader(byte[] dexBytes) { 
  94. //新文件长度
  95. byte[] newfs = intToByte(dexBytes.length); 
  96.         System.out.println(Integer.toHexString(dexBytes.length)); 
  97. byte[] refs = new byte[4]; 
  98. //高位在前,低位在前掉个个
  99. for (int i = 0; i < 4; i++) { 
  100.             refs[i] = newfs[newfs.length - 1 - i]; 
  101.             System.out.println(Integer.toHexString(newfs[i])); 
  102.         } 
  103.         System.arraycopy(refs, 0, dexBytes, 32, 4);//修改(32-35
  104.     } 


  105. private static byte[] readFileBytes(File file) throws IOException { 
  106. byte[] arrayOfByte = new byte[1024]; 
  107.         ByteArrayOutputStream localByteArrayOutputStream = new ByteArrayOutputStream(); 
  108.         FileInputStream fis = new FileInputStream(file); 
  109. while (true) { 
  110. int i = fis.read(arrayOfByte); 
  111. if (i != -1) { 
  112.                 localByteArrayOutputStream.write(arrayOfByte, 0, i); 
  113.             } else { 
  114. return localByteArrayOutputStream.toByteArray(); 
  115.             } 
  116.         } 
  117.     } 

红色部分其实就是最核心的工作

1>、加密源程序Apk文件

[java] view plaincopy

  1. byte[] payloadArray = encrpt(readFileBytes(payloadSrcFile));//以二进制形式读出apk,并进行加密处理//对源Apk进行加密操作

加密算法很简单

[java] view plaincopy

  1. //直接返回数据,读者可以添加自己加密方法
  2. private static byte[] encrpt(byte[] srcdata){ 
  3. for(int i = 0;i<srcdata.length;i++){ 
  4.         srcdata[i] = (byte)(0xFF ^ srcdata[i]); 
  5.     } 
  6. return srcdata; 

对每个字节进行异或一下即可。

(说明:这里是为了简单,所以就用了很简单的加密算法了,其实为了增加破解难度,我们应该使用更高效的加密算法,同事最好将加密操作放到native层去做)

2>、合并文件:将加密之后的Apk和原脱壳Dex进行合并

[java] view plaincopy

  1. int payloadLen = payloadArray.length; 
  2. int unShellDexLen = unShellDexArray.length; 
  3. int totalLen = payloadLen + unShellDexLen +4;//多出4字节是存放长度的。
  4. byte[] newdex = new byte[totalLen]; // 申请了新的长度
  5. //添加解壳代码
  6. System.arraycopy(unShellDexArray, 0, newdex, 0, unShellDexLen);//先拷贝dex内容
  7. //添加加密后的解壳数据
  8. System.arraycopy(payloadArray, 0, newdex, unShellDexLen, payloadLen);//再在dex内容后面拷贝apk的内容

3>、在文件的末尾追加源程序Apk的长度

[java] view plaincopy

  1. //添加解壳数据长度
  2. System.arraycopy(intToByte(payloadLen), 0, newdex, totalLen-4, 4);//最后4为长度

4>、修改新Dex文件的文件头信息:file_size; sha1; check_sum

[java] view plaincopy

  1. //修改DEX file size文件头
  2. fixFileSizeHeader(newdex); 
  3. //修改DEX SHA1 文件头
  4. fixSHA1Header(newdex); 
  5. //修改DEX CheckSum文件头
  6. fixCheckSumHeader(newdex); 

具体修改可以参照之前说的文件头格式,修改指定位置的字节值即可。

这里我们还需要两个输入文件

1>、源Apk文件:ForceApkObj.apk

2>、脱壳程序的Dex文件:ForceApkObj.dex

那么第一个文件我们都知道,就是上面的源程序编译之后的Apk文件,那么第二个文件我们怎么得到呢?这个就是我们要讲到的第三个项目:脱壳程序项目,他是一个Android项目,我们在编译之后,能够得到他的classes.dex文件,然后修改一下名称就可。

3、脱壳项目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

下面来看一下代码

[java] view plaincopy

  1. package com.example.reforceapk; 

  2. import java.io.BufferedInputStream; 
  3. import java.io.ByteArrayInputStream; 
  4. import java.io.ByteArrayOutputStream; 
  5. import java.io.DataInputStream; 
  6. import java.io.File; 
  7. import java.io.FileInputStream; 
  8. import java.io.FileOutputStream; 
  9. import java.io.IOException; 
  10. import java.lang.ref.WeakReference; 
  11. import java.lang.reflect.Method; 
  12. import java.util.ArrayList; 
  13. import java.util.HashMap; 
  14. import java.util.Iterator; 
  15. import java.util.zip.ZipEntry; 
  16. import java.util.zip.ZipInputStream; 

  17. import android.app.Application; 
  18. import android.app.Instrumentation; 
  19. import android.content.Context; 
  20. import android.content.pm.ApplicationInfo; 
  21. import android.content.pm.PackageManager; 
  22. import android.content.pm.PackageManager.NameNotFoundException; 
  23. import android.content.res.AssetManager; 
  24. import android.content.res.Resources; 
  25. import android.content.res.Resources.Theme; 
  26. import android.os.Bundle; 
  27. import android.util.ArrayMap; 
  28. import android.util.Log; 
  29. import dalvik.system.DexClassLoader; 

  30. public class ProxyApplication extends Application{ 
  31. private static final String appkey = "APPLICATION_CLASS_NAME"; 
  32. private String apkFileName; 
  33. private String odexPath; 
  34. private String libPath; 

  35. //这是context 赋值
  36. @Override
  37. protected void attachbaseContext(Context base) { 
  38. super.attachbaseContext(base); 
  39. try { 
  40. //创建两个文件夹payload_odex,payload_lib 私有的,可写的文件目录
  41.             File odex = this.getDir("payload_odex", MODE_PRIVATE); 
  42.             File libs = this.getDir("payload_lib", MODE_PRIVATE); 
  43.             odexPath = odex.getAbsolutePath(); 
  44.             libPath = libs.getAbsolutePath(); 
  45.             apkFileName = odex.getAbsolutePath() + "/payload.apk"; 
  46.             File dexFile = new File(apkFileName); 
  47.             Log.i("demo", "apk size:"+dexFile.length()); 
  48. if (!dexFile.exists()) 
  49.             { 
  50.                 dexFile.createNewFile();  //在payload_odex文件夹内,创建payload.apk
  51. // 读取程序classes.dex文件
  52. byte[] dexdata = this.readDexFileFromApk(); 

  53. // 分离出解壳后的apk文件已用于动态加载
  54. this.splitPayLoadFromDex(dexdata); 
  55.             } 
  56. // 配置动态加载环境
  57.             Object currentActivityThread = RefInvoke.invokeStaticMethod( 
  58. "android.app.ActivityThread", "currentActivityThread", 
  59. new Class[] {}, new Object[] {});//获取主线程对象 http://blog.csdn.net/myarrow/article/details/14223493
  60.             String packageName = this.getPackageName();//当前apk的包名
  61. //下面两句不是太理解
  62.             ArrayMap mPackages = (ArrayMap) RefInvoke.getFieldOjbect( 
  63. "android.app.ActivityThread", currentActivityThread, 
  64. "mPackages"); 
  65.             WeakReference wr = (WeakReference) mPackages.get(packageName); 
  66. //创建被加壳apk的DexClassLoader对象  加载apk内的类和本地代码(c/c++代码
  67.             DexClassLoader dLoader = new DexClassLoader(apkFileName, odexPath, 
  68.                     libPath, (ClassLoader) RefInvoke.getFieldOjbect( 
  69. "android.app.LoadedApk", wr.get(), "mClassLoader")); 
  70. //base.getClassLoader(); 是不是就等同于 (ClassLoader) RefInvoke.getFieldOjbect()? 有空验证下//?
  71. //把当前进程的DexClassLoader 设置成了被加壳apk的DexClassLoader  ----有点c++中进程环境的意思~~
  72.             RefInvoke.setFieldOjbect("android.app.LoadedApk", "mClassLoader", 
  73.                     wr.get(), dLoader); 

  74.             Log.i("demo","classloader:"+dLoader); 

  75. try{ 
  76.                 Object actObj = dLoader.loadClass("com.example.forceapkobj.MainActivity"); 
  77.                 Log.i("demo", "actObj:"+actObj); 
  78.             }catch(Exception e){ 
  79.                 Log.i("demo", "activity:"+Log.getStackTraceString(e)); 
  80.             } 


  81.         } catch (Exception e) { 
  82.             Log.i("demo", "error:"+Log.getStackTraceString(e)); 
  83.             e.printStackTrace(); 
  84.         } 
  85.     } 

  86. @Override
  87. public void onCreate() { 
  88.         { 
  89. //loadResources(apkFileName);

  90.             Log.i("demo", "onCreate"); 
  91. // 如果源应用配置有Appliction对象,则替换为源应用Applicaiton,以便不影响源程序逻辑。
  92.             String appClassName = null; 
  93. try { 
  94.                 ApplicationInfo ai = this.getPackageManager() 
  95.                         .getApplicationInfo(this.getPackageName(), 
  96.                                 PackageManager.GET_meta_DATA); 
  97.                 Bundle bundle = ai.metaData; 
  98. if (bundle != null && bundle.containsKey("APPLICATION_CLASS_NAME")) { 
  99.                     appClassName = bundle.getString("APPLICATION_CLASS_NAME");//className 是配置在xml文件中的。
  100.                 } else { 
  101.                     Log.i("demo", "have no application class name"); 
  102. return; 
  103.                 } 
  104.             } catch (NameNotFoundException e) { 
  105.                 Log.i("demo", "error:"+Log.getStackTraceString(e)); 
  106.                 e.printStackTrace(); 
  107.             } 
  108. //有值的话调用该Applicaiton
  109.             Object currentActivityThread = RefInvoke.invokeStaticMethod( 
  110. "android.app.ActivityThread", "currentActivityThread", 
  111. new Class[] {}, new Object[] {}); 
  112.             Object mBoundApplication = RefInvoke.getFieldOjbect( 
  113. "android.app.ActivityThread", currentActivityThread, 
  114. "mBoundApplication"); 
  115.             Object loadedApkInfo = RefInvoke.getFieldOjbect( 
  116. "android.app.ActivityThread$AppBindData", 
  117.                     mBoundApplication, "info"); 
  118. //把当前进程的mApplication 设置成了null
  119.             RefInvoke.setFieldOjbect("android.app.LoadedApk", "mApplication", 
  120.                     loadedApkInfo, null); 
  121.             Object oldApplication = RefInvoke.getFieldOjbect( 
  122. "android.app.ActivityThread", currentActivityThread, 
  123. "mInitialApplication"); 
  124. //http://www.codeceo.com/article/android-context.html
  125.             ArrayList<Application> mAllApplications = (ArrayList<Application>) RefInvoke 
  126.                     .getFieldOjbect("android.app.ActivityThread", 
  127.                             currentActivityThread, "mAllApplications"); 
  128.             mAllApplications.remove(oldApplication);//删除oldApplication

  129.             ApplicationInfo appinfo_In_LoadedApk = (ApplicationInfo) RefInvoke 
  130.                     .getFieldOjbect("android.app.LoadedApk", loadedApkInfo, 
  131. "mApplicationInfo"); 
  132.             ApplicationInfo appinfo_In_AppBindData = (ApplicationInfo) RefInvoke 
  133.                     .getFieldOjbect("android.app.ActivityThread$AppBindData", 
  134.                             mBoundApplication, "appInfo"); 
  135.             appinfo_In_LoadedApk.className = appClassName; 
  136.             appinfo_In_AppBindData.className = appClassName; 
  137.             Application app = (Application) RefInvoke.invokeMethod( 
  138. "android.app.LoadedApk", "makeApplication", loadedApkInfo, 
  139. new Class[] { boolean.class, Instrumentation.class }, 
  140. new Object[] { false, null });//执行 makeApplication(false,null
  141.             RefInvoke.setFieldOjbect("android.app.ActivityThread", 
  142. "mInitialApplication", currentActivityThread, app); 


  143.             ArrayMap mProviderMap = (ArrayMap) RefInvoke.getFieldOjbect( 
  144. "android.app.ActivityThread", currentActivityThread, 
  145. "mProviderMap"); 
  146.             Iterator it = mProviderMap.values().iterator(); 
  147. while (it.hasNext()) { 
  148.                 Object providerClientRecord = it.next(); 
  149.                 Object localProvider = RefInvoke.getFieldOjbect( 
  150. "android.app.ActivityThread$ProviderClientRecord", 
  151.                         providerClientRecord, "mLocalProvider"); 
  152.                 RefInvoke.setFieldOjbect("android.content.ContentProvider", 
  153. "mContext", localProvider, app); 
  154.             } 

  155.             Log.i("demo", "app:"+app); 

  156.             app.onCreate(); 
  157.         } 
  158.     } 

  159. private void splitPayLoadFromDex(byte[] apkdata) throws IOException { 
  160. int ablen = apkdata.length; 
  161. //取被加壳apk的长度   这里的长度取值,对应加壳时长度的赋值都可以做些简化
  162. byte[] dexlen = new byte[4]; 
  163.         System.arraycopy(apkdata, ablen - 4, dexlen, 0, 4); 
  164.         ByteArrayInputStream bais = new ByteArrayInputStream(dexlen); 
  165.         DataInputStream in = new DataInputStream(bais); 
  166. int readInt = in.readInt(); 
  167.         System.out.println(Integer.toHexString(readInt)); 
  168. byte[] newdex = new byte[readInt]; 
  169. //把被加壳apk内容拷贝到newdex中
  170.         System.arraycopy(apkdata, ablen - 4 - readInt, newdex, 0, readInt); 
  171. //这里应该加上对于apk的解密操作,若加壳是加密处理的话
  172. //?

  173. //对源程序Apk进行解密
  174.         newdex = decrypt(newdex); 

  175. //写入apk文件  
  176.         File file = new File(apkFileName); 
  177. try { 
  178.             FileOutputStream localFileOutputStream = new FileOutputStream(file); 
  179.             localFileOutputStream.write(newdex); 
  180.             localFileOutputStream.close(); 
  181.         } catch (IOException localIOException) { 
  182. throw new RuntimeException(localIOException); 
  183.         } 

  184. //分析被加壳的apk文件
  185.         ZipInputStream localZipInputStream = new ZipInputStream( 
  186. new BufferedInputStream(new FileInputStream(file))); 
  187. while (true) { 
  188.             ZipEntry localZipEntry = localZipInputStream.getNextEntry();//不了解这个是否也遍历子目录,看样子应该是遍历的
  189. if (localZipEntry == null) { 
  190.                 localZipInputStream.close(); 
  191. break; 
  192.             } 
  193. //取出被加壳apk用到的so文件,放到 libPath中(data/data/包名/payload_lib)
  194.             String name = localZipEntry.getName(); 
  195. if (name.startsWith("lib/") && name.endsWith(".so")) { 
  196.                 File storeFile = new File(libPath + "/"
  197.                         + name.substring(name.lastIndexOf('/'))); 
  198.                 storeFile.createNewFile(); 
  199.                 FileOutputStream fos = new FileOutputStream(storeFile); 
  200. byte[] arrayOfByte = new byte[1024]; 
  201. while (true) { 
  202. int i = localZipInputStream.read(arrayOfByte); 
  203. if (i == -1) 
  204. break; 
  205.                     fos.write(arrayOfByte, 0, i); 
  206.                 } 
  207.                 fos.flush(); 
  208.                 fos.close(); 
  209.             } 
  210.             localZipInputStream.closeEntry(); 
  211.         } 
  212.         localZipInputStream.close(); 


  213.     } 

  214. private byte[] readDexFileFromApk() throws IOException { 
  215.         ByteArrayOutputStream dexByteArrayOutputStream = new ByteArrayOutputStream(); 
  216.         ZipInputStream localZipInputStream = new ZipInputStream( 
  217. new BufferedInputStream(new FileInputStream( 
  218. this.getApplicationInfo().sourceDir))); 
  219. while (true) { 
  220.             ZipEntry localZipEntry = localZipInputStream.getNextEntry(); 
  221. if (localZipEntry == null) { 
  222.                 localZipInputStream.close(); 
  223. break; 
  224.             } 
  225. if (localZipEntry.getName().equals("classes.dex")) { 
  226. byte[] arrayOfByte = new byte[1024]; 
  227. while (true) { 
  228. int i = localZipInputStream.read(arrayOfByte); 
  229. if (i == -1) 
  230. break; 
  231.                     dexByteArrayOutputStream.write(arrayOfByte, 0, i); 
  232.                 } 
  233.             } 
  234.             localZipInputStream.closeEntry(); 
  235.         } 
  236.         localZipInputStream.close(); 
  237. return dexByteArrayOutputStream.toByteArray(); 
  238.     } 


  239. // //直接返回数据,读者可以添加自己解密方法
  240. private byte[] decrypt(byte[] srcdata) { 
  241. for(int i=0;i<srcdata.length;i++){ 
  242.             srcdata[i] = (byte)(0xFF ^ srcdata[i]); 
  243.         } 
  244. return srcdata; 
  245.     } 


  246. //以下是加载资源
  247. protected AssetManager mAssetManager;//资源管理器 
  248. protected Resources mResources;//资源 
  249. protected Theme mTheme;//主题 

  250. protected void loadResources(String dexPath) {   
  251. try {   
  252.             AssetManager assetManager = AssetManager.class.newInstance();   
  253.             Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);   
  254.             addAssetPath.invoke(assetManager, dexPath);   
  255.             mAssetManager = assetManager;   
  256.         } catch (Exception e) {   
  257.             Log.i("inject", "loadResource error:"+Log.getStackTraceString(e)); 
  258.             e.printStackTrace();   
  259.         }   
  260.         Resources superRes = super.getResources();   
  261.         superRes.getDisplayMetrics();   
  262.         superRes.getConfiguration();   
  263.         mResources = new Resources(mAssetManager, superRes.getDisplayMetrics(),superRes.getConfiguration());   
  264.         mTheme = mResources.newTheme();   
  265.         mTheme.setTo(super.getTheme()); 
  266.     }   

  267. @Override
  268. public AssetManager getAssets() {   
  269. return mAssetManager == null ? super.getAssets() : mAssetManager;   
  270.     }   

  271. @Override
  272. public Resources getResources() {   
  273. return mResources == null ? super.getResources() : mResources;   
  274.     }   

  275. @Override
  276. public Theme getTheme() {   
  277. return mTheme == null ? super.getTheme() : mTheme;   
  278.     }  

首先我们来看一下具体步骤的代码实现

1>、得到脱壳Apk中的dex文件,然后从这个文件中得到源程序Apk.进行解密,然后加载

[java] view plaincopy

  1. //这是context 赋值
  2. @Override
  3. protected void attachbaseContext(Context base) { 
  4. super.attachbaseContext(base); 
  5. try { 
  6. //创建两个文件夹payload_odex,payload_lib 私有的,可写的文件目录
  7.         File odex = this.getDir("payload_odex", MODE_PRIVATE); 
  8.         File libs = this.getDir("payload_lib", MODE_PRIVATE); 
  9.         odexPath = odex.getAbsolutePath(); 
  10.         libPath = libs.getAbsolutePath(); 
  11.         apkFileName = odex.getAbsolutePath() + "/payload.apk"; 
  12.         File dexFile = new File(apkFileName); 
  13.         Log.i("demo", "apk size:"+dexFile.length()); 
  14. if (!dexFile.exists()) 
  15.         { 
  16.             dexFile.createNewFile();  //在payload_odex文件夹内,创建payload.apk
  17. // 读取程序classes.dex文件
  18. byte[] dexdata = this.readDexFileFromApk(); 

  19. // 分离出解壳后的apk文件已用于动态加载
  20. this.splitPayLoadFromDex(dexdata); 
  21.         } 
  22. // 配置动态加载环境
  23.         Object currentActivityThread = RefInvoke.invokeStaticMethod( 
  24. "android.app.ActivityThread", "currentActivityThread", 
  25. new Class[] {}, new Object[] {});//获取主线程对象 http://blog.csdn.net/myarrow/article/details/14223493
  26.         String packageName = this.getPackageName();//当前apk的包名
  27. //下面两句不是太理解
  28.         ArrayMap mPackages = (ArrayMap) RefInvoke.getFieldOjbect( 
  29. "android.app.ActivityThread", currentActivityThread, 
  30. "mPackages"); 
  31.         WeakReference wr = (WeakReference) mPackages.get(packageName); 
  32. //创建被加壳apk的DexClassLoader对象  加载apk内的类和本地代码(c/c++代码
  33.         DexClassLoader dLoader = new DexClassLoader(apkFileName, odexPath, 
  34.                 libPath, (ClassLoader) RefInvoke.getFieldOjbect( 
  35. "android.app.LoadedApk", wr.get(), "mClassLoader")); 
  36. //base.getClassLoader(); 是不是就等同于 (ClassLoader) RefInvoke.getFieldOjbect()? 有空验证下//?
  37. //把当前进程的DexClassLoader 设置成了被加壳apk的DexClassLoader  ----有点c++中进程环境的意思~~
  38.         RefInvoke.setFieldOjbect("android.app.LoadedApk", "mClassLoader", 
  39.                 wr.get(), dLoader); 

  40.         Log.i("demo","classloader:"+dLoader); 

  41. try{ 
  42.             Object actObj = dLoader.loadClass("com.example.forceapkobj.MainActivity"); 
  43.             Log.i("demo", "actObj:"+actObj); 
  44.         }catch(Exception e){ 
  45.             Log.i("demo", "activity:"+Log.getStackTraceString(e)); 
  46.         } 


  47.     } catch (Exception e) { 
  48.         Log.i("demo", "error:"+Log.getStackTraceString(e)); 
  49.         e.printStackTrace(); 
  50.     } 

这里需要注意的一个问题,就是我们需要找到一个时机,就是在脱壳程序还没有运行起来的时候,来加载源程序的Apk,执行他的onCreate方法,那么这个时机不能太晚,不然的话,就是运行脱壳程序,而不是源程序了。查看源码我们知道。Application中有一个方法attachbaseContext这个方法,他在Application的onCreate方法执行前就会执行了,那么我们的工作就需要在这里进行

1)、从脱壳程序Apk中找到源程序Apk,并且进行解密操作

[java] view plaincopy

  1. //创建两个文件夹payload_odex,payload_lib 私有的,可写的文件目录
  2. File odex = this.getDir("payload_odex", MODE_PRIVATE); 
  3. File libs = this.getDir("payload_lib", MODE_PRIVATE); 
  4. odexPath = odex.getAbsolutePath(); 
  5. libPath = libs.getAbsolutePath(); 
  6. apkFileName = odex.getAbsolutePath() + "/payload.apk"; 
  7. File dexFile = new File(apkFileName); 
  8. Log.i("demo", "apk size:"+dexFile.length()); 
  9. if (!dexFile.exists()) 
  10.     dexFile.createNewFile();  //在payload_odex文件夹内,创建payload.apk
  11. // 读取程序classes.dex文件
  12. byte[] dexdata = this.readDexFileFromApk(); 

  13. // 分离出解壳后的apk文件已用于动态加载
  14. this.splitPayLoadFromDex(dexdata); 

这个脱壳解密操作一定要和我们之前的加壳以及加密操作对应,不然就会出现Dex加载错误问题

A) 从Apk中获取到Dex文件

[java] view plaincopy

  1. private byte[] readDexFileFromApk() throws IOException { 
  2.     ByteArrayOutputStream dexByteArrayOutputStream = new ByteArrayOutputStream(); 
  3.     ZipInputStream localZipInputStream = new ZipInputStream( 
  4. new BufferedInputStream(new FileInputStream( 
  5. this.getApplicationInfo().sourceDir))); 
  6. while (true) { 
  7.         ZipEntry localZipEntry = localZipInputStream.getNextEntry(); 
  8. if (localZipEntry == null) { 
  9.             localZipInputStream.close(); 
  10. break; 
  11.         } 
  12. if (localZipEntry.getName().equals("classes.dex")) { 
  13. byte[] arrayOfByte = new byte[1024]; 
  14. while (true) { 
  15. int i = localZipInputStream.read(arrayOfByte); 
  16. if (i == -1) 
  17. break; 
  18.                 dexByteArrayOutputStream.write(arrayOfByte, 0, i); 
  19.             } 
  20.         } 
  21.         localZipInputStream.closeEntry(); 
  22.     } 
  23.     localZipInputStream.close(); 
  24. return dexByteArrayOutputStream.toByteArray(); 

其实就是解压Apk文件,直接得到dex文件即可

B) 从脱壳Dex中得到源Apk文件

[java] view plaincopy

  1. private void splitPayLoadFromDex(byte[] apkdata) throws IOException { 
  2. int ablen = apkdata.length; 
  3. //取被加壳apk的长度   这里的长度取值,对应加壳时长度的赋值都可以做些简化
  4. byte[] dexlen = new byte[4]; 
  5.     System.arraycopy(apkdata, ablen - 4, dexlen, 0, 4); 
  6.     ByteArrayInputStream bais = new ByteArrayInputStream(dexlen); 
  7.     DataInputStream in = new DataInputStream(bais); 
  8. int readInt = in.readInt(); 
  9.     System.out.println(Integer.toHexString(readInt)); 
  10. byte[] newdex = new byte[readInt]; 
  11. //把被加壳apk内容拷贝到newdex中
  12.     System.arraycopy(apkdata, ablen - 4 - readInt, newdex, 0, readInt); 
  13. //这里应该加上对于apk的解密操作,若加壳是加密处理的话
  14. //?

  15. //对源程序Apk进行解密
  16.     newdex = decrypt(newdex); 

  17. //写入apk文件  
  18.     File file = new File(apkFileName); 
  19. try { 
  20.         FileOutputStream localFileOutputStream = new FileOutputStream(file); 
  21.         localFileOutputStream.write(newdex); 
  22.         localFileOutputStream.close(); 
  23.     } catch (IOException localIOException) { 
  24. throw new RuntimeException(localIOException); 
  25.     } 

  26. //分析被加壳的apk文件
  27.     ZipInputStream localZipInputStream = new ZipInputStream( 
  28. new BufferedInputStream(new FileInputStream(file))); 
  29. while (true) { 
  30.         ZipEntry localZipEntry = localZipInputStream.getNextEntry();//不了解这个是否也遍历子目录,看样子应该是遍历的
  31. if (localZipEntry == null) { 
  32.             localZipInputStream.close(); 
  33. break; 
  34.         } 
  35. //取出被加壳apk用到的so文件,放到 libPath中(data/data/包名/payload_lib)
  36.         String name = localZipEntry.getName(); 
  37. if (name.startsWith("lib/") && name.endsWith(".so")) { 
  38.             File storeFile = new File(libPath + "/"
  39.                     + name.substring(name.lastIndexOf('/'))); 
  40.             storeFile.createNewFile(); 
  41.             FileOutputStream fos = new FileOutputStream(storeFile); 
  42. byte[] arrayOfByte = new byte[1024]; 
  43. while (true) { 
  44. int i = localZipInputStream.read(arrayOfByte); 
  45. if (i == -1) 
  46. break; 
  47.                 fos.write(arrayOfByte, 0, i); 
  48.             } 
  49.             fos.flush(); 
  50.             fos.close(); 
  51.         } 
  52.         localZipInputStream.closeEntry(); 
  53.     } 
  54.     localZipInputStream.close(); 


C) 解密源程序Apk

[java] view plaincopy

  1. 直接返回数据,读者可以添加自己解密方法
  2. private byte[] decrypt(byte[] srcdata) { 
  3. for(int i=0;i<srcdata.length;i++){ 
  4.         srcdata[i] = (byte)(0xFF ^ srcdata[i]); 
  5.     } 
  6. return srcdata; 

这个解密算法和加密算法是一致的

2>、加载解密之后的源程序Apk

[java] view plaincopy

  1. //配置动态加载环境
  2. Object currentActivityThread = RefInvoke.invokeStaticMethod( 
  3. "android.app.ActivityThread", "currentActivityThread", 
  4. new Class[] {}, new Object[] {});//获取主线程对象 http://blog.csdn.net/myarrow/article/details/14223493
  5. String packageName = this.getPackageName();//当前apk的包名
  6. //下面两句不是太理解
  7. ArrayMap mPackages = (ArrayMap) RefInvoke.getFieldOjbect( 
  8. "android.app.ActivityThread", currentActivityThread, 
  9. "mPackages"); 
  10. WeakReference wr = (WeakReference) mPackages.get(packageName); 
  11. //创建被加壳apk的DexClassLoader对象  加载apk内的类和本地代码(c/c++代码
  12. DexClassLoader dLoader = new DexClassLoader(apkFileName, odexPath, 
  13.         libPath, (ClassLoader) RefInvoke.getFieldOjbect( 
  14. "android.app.LoadedApk", wr.get(), "mClassLoader")); 
  15. //base.getClassLoader(); 是不是就等同于 (ClassLoader) RefInvoke.getFieldOjbect()? 有空验证下//?
  16. //把当前进程的DexClassLoader 设置成了被加壳apk的DexClassLoader  ----有点c++中进程环境的意思~~
  17. RefInvoke.setFieldOjbect("android.app.LoadedApk", "mClassLoader", 
  18.         wr.get(), dLoader); 

  19. Log.i("demo","classloader:"+dLoader); 

  20. try{ 
  21.     Object actObj = dLoader.loadClass("com.example.forceapkobj.MainActivity"); 
  22.     Log.i("demo", "actObj:"+actObj); 
  23. }catch(Exception e){ 
  24.     Log.i("demo", "activity:"+Log.getStackTraceString(e)); 

2)、找到源程序的Application程序,让其运行

[java] view plaincopy

  1. @Override
  2. public void onCreate() { 
  3.     { 
  4. //loadResources(apkFileName);

  5.         Log.i("demo", "onCreate"); 
  6. // 如果源应用配置有Appliction对象,则替换为源应用Applicaiton,以便不影响源程序逻辑。
  7.         String appClassName = null; 
  8. try { 
  9.             ApplicationInfo ai = this.getPackageManager() 
  10.                     .getApplicationInfo(this.getPackageName(), 
  11.                             PackageManager.GET_meta_DATA); 
  12.             Bundle bundle = ai.metaData; 
  13. if (bundle != null && bundle.containsKey("APPLICATION_CLASS_NAME")) { 
  14.                 appClassName = bundle.getString("APPLICATION_CLASS_NAME");//className 是配置在xml文件中的。
  15.             } else { 
  16.                 Log.i("demo", "have no application class name"); 
  17. return; 
  18.             } 
  19.         } catch (NameNotFoundException e) { 
  20.             Log.i("demo", "error:"+Log.getStackTraceString(e)); 
  21.             e.printStackTrace(); 
  22.         } 
  23. //有值的话调用该Applicaiton
  24.         Object currentActivityThread = RefInvoke.invokeStaticMethod( 
  25. "android.app.ActivityThread", "currentActivityThread", 
  26. new Class[] {}, new Object[] {}); 
  27.         Object mBoundApplication = RefInvoke.getFieldOjbect( 
  28. "android.app.ActivityThread", currentActivityThread, 
  29. "mBoundApplication"); 
  30.         Object loadedApkInfo = RefInvoke.getFieldOjbect( 
  31. "android.app.ActivityThread$AppBindData", 
  32.                 mBoundApplication, "info"); 
  33. //把当前进程的mApplication 设置成了null
  34.         RefInvoke.setFieldOjbect("android.app.LoadedApk", "mApplication", 
  35.                 loadedApkInfo, null); 
  36.         Object oldApplication = RefInvoke.getFieldOjbect( 
  37. "android.app.ActivityThread", currentActivityThread, 
  38. "mInitialApplication"); 
  39. //http://www.codeceo.com/article/android-context.html
  40.         ArrayList<Application> mAllApplications = (ArrayList<Application>) RefInvoke 
  41.                 .getFieldOjbect("android.app.ActivityThread", 
  42.                         currentActivityThread, "mAllApplications"); 
  43.         mAllApplications.remove(oldApplication);//删除oldApplication

  44.         ApplicationInfo appinfo_In_LoadedApk = (ApplicationInfo) RefInvoke 
  45.                 .getFieldOjbect("android.app.LoadedApk", loadedApkInfo, 
  46. "mApplicationInfo"); 
  47.         ApplicationInfo appinfo_In_AppBindData = (ApplicationInfo) RefInvoke 
  48.                 .getFieldOjbect("android.app.ActivityThread$AppBindData", 
  49.                         mBoundApplication, "appInfo"); 
  50.         appinfo_In_LoadedApk.className = appClassName; 
  51.         appinfo_In_AppBindData.className = appClassName; 
  52.         Application app = (Application) RefInvoke.invokeMethod( 
  53. "android.app.LoadedApk", "makeApplication", loadedApkInfo, 
  54. new Class[] { boolean.class, Instrumentation.class }, 
  55. new Object[] { false, null });//执行 makeApplication(false,null
  56.         RefInvoke.setFieldOjbect("android.app.ActivityThread", 
  57. "mInitialApplication", currentActivityThread, app); 


  58.         ArrayMap mProviderMap = (ArrayMap) RefInvoke.getFieldOjbect( 
  59. "android.app.ActivityThread", currentActivityThread, 
  60. "mProviderMap"); 
  61.         Iterator it = mProviderMap.values().iterator(); 
  62. while (it.hasNext()) { 
  63.             Object providerClientRecord = it.next(); 
  64.             Object localProvider = RefInvoke.getFieldOjbect( 
  65. "android.app.ActivityThread$ProviderClientRecord", 
  66.                     providerClientRecord, "mLocalProvider"); 
  67.             RefInvoke.setFieldOjbect("android.content.ContentProvider", 
  68. "mContext", localProvider, app); 
  69.         } 

  70.         Log.i("demo", "app:"+app); 

  71.         app.onCreate(); 
  72.     } 

直接在脱壳的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

下载之后的工具需要用ReforeceApk.apk文件替换ReforceApk_des.apk文件,然后运行run.bat就可以得到签名之后的文件了。

run.bat文件的命令如下

cd C:UsersiDesktopforceapks
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的执行流程的了解

4、如何从Apk中得到Dex文件

5、如何从新签名一个Apk程序

七、综合概述

我们通过上面的过程可以看到,关于Apk加固的工作还是挺复杂的,涉及到的东西也挺多的,下面就在来总结一下吧

1、加壳程序

任务:对源程序Apk进行加密,合并脱壳程序的Dex文件 ,然后输入一个加壳之后的Dex文件

语言:任何语言都可以,不限于Java语言

技术点:对Dex文件格式的解析

2、脱壳程序

任务:获取源程序Apk,进行解密,然后动态加载进来,运行程序

语言:Android项目(Java)

技术点:如何从Apk中获取Dex文件,动态加载Apk,使用反射运行Application

八、总结

本文地址:http://ww.kub2b.com/news/18466.html     企库往 http://ww.kub2b.com/ ,  查看更多

特别提示:本信息由相关用户自行提供,真实性未证实,仅供参考。请谨慎采用,风险自负。

 
 
更多>同类最新文章
0相关评论

文章列表
相关文章
最新动态
推荐图文
最新文章
点击排行
网站首页  |  关于我们  |  联系方式  |  使用协议  |  版权隐私  |  网站地图  |  排名推广  |  广告服务  |  积分换礼  |  网站留言  |  RSS订阅  |  违规举报  |  鄂ICP备2020018471号