android apk 加固

安卓apk360加固 一、配置AS 官网: https://jiagu.360.cn/#/global/index 1:下载后,把这个jiagu文件放到项目根目录下 2:然后在创建 360protect.gradle文件 /** -login 首次使用必须先登录 <360用户名> <password> <登录密码> -importsign <k 阅读详情

一、前言

今天又到周末了,憋了好久又要出博客了,今天来介绍一下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.youkuaiyun.com/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

 

 
  1. package com.example.forceapkobj;

  2.  
  3. import android.app.Application;

  4. import android.util.Log;

  5.  
  6. public class MyApplication extends Application{

  7.  
  8. @Override

  9. public void onCreate() {

  10. super.onCreate();

  11. Log.i("demo", "source apk onCreate:"+this);

  12. }

  13.  
  14. }

 

就是打印一下onCreate方法。

 

MainActivity.java

 

 
  1. package com.example.forceapkobj;

  2.  
  3. import android.app.Activity;

  4. import android.content.Intent;

  5. import android.os.Bundle;

  6. import android.util.Log;

  7. import android.view.View;

  8. import android.view.View.OnClickListener;

  9. import android.widget.TextView;

  10.  
  11. public class MainActivity extends Activity {

  12.  
  13. @Override

  14. protected void onCreate(Bundle savedInstanceState) {

  15. super.onCreate(savedInstanceState);

  16.  
  17. TextView content = new TextView(this);

  18. content.setText("I am Source Apk");

  19. content.setOnClickListener(new OnClickListener(){

  20. @Override

  21. public void onClick(View arg0) {

  22. Intent intent = new Intent(MainActivity.this, SubActivity.class);

  23. startActivity(intent);

  24. }});

  25. setContentView(content);

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

  28.  
  29. }

  30.  
  31. }

也是打印一下内容。

 

 

2、加壳程序项目:DexShellTools

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

看一下代码:

 

 
  1. package com.example.reforceapk;

  2.  
  3. import java.io.ByteArrayOutputStream;

  4. import java.io.File;

  5. import java.io.FileInputStream;

  6. import java.io.FileOutputStream;

  7. import java.io.IOException;

  8. import java.security.MessageDigest;

  9. import java.security.NoSuchAlgorithmException;

  10. import java.util.zip.Adler32;

  11.  
  12.  
  13. public class mymain {

  14. /**

  15. * @param args

  16. */

  17. public static void main(String[] args) {

  18. // TODO Auto-generated method stub

  19. try {

  20. File payloadSrcFile = new File("force/ForceApkObj.apk"); //需要加壳的程序

  21. System.out.println("apk size:"+payloadSrcFile.length());

  22. File unShellDexFile = new File("force/ForceApkObj.dex"); //解客dex

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

  24. byte[] unShellDexArray = readFileBytes(unShellDexFile);//以二进制形式读出dex

  25. int payloadLen = payloadArray.length;

  26. int unShellDexLen = unShellDexArray.length;

  27. int totalLen = payloadLen + unShellDexLen +4;//多出4字节是存放长度的。

  28. byte[] newdex = new byte[totalLen]; // 申请了新的长度

  29. //添加解壳代码

  30. System.arraycopy(unShellDexArray, 0, newdex, 0, unShellDexLen);//先拷贝dex内容

  31. //添加加密后的解壳数据

  32. System.arraycopy(payloadArray, 0, newdex, unShellDexLen, payloadLen);//再在dex内容后面拷贝apk的内容

  33. //添加解壳数据长度

  34. System.arraycopy(intToByte(payloadLen), 0, newdex, totalLen-4, 4);//最后4为长度

  35. //修改DEX file size文件头

  36. fixFileSizeHeader(newdex);

  37. //修改DEX SHA1 文件头

  38. fixSHA1Header(newdex);

  39. //修改DEX CheckSum文件头

  40. fixCheckSumHeader(newdex);

  41.  
  42. String str = "force/classes.dex";

  43. File file = new File(str);

  44. if (!file.exists()) {

  45. file.createNewFile();

  46. }

  47.  
  48. FileOutputStream localFileOutputStream = new FileOutputStream(str);

  49. localFileOutputStream.write(newdex);

  50. localFileOutputStream.flush();

  51. localFileOutputStream.close();

  52.  
  53.  
  54. } catch (Exception e) {

  55. e.printStackTrace();

  56. }

  57. }

  58.  
  59. //直接返回数据,读者可以添加自己加密方法

  60. private static byte[] encrpt(byte[] srcdata){

  61. for(int i = 0;i<srcdata.length;i++){

  62. srcdata[i] = (byte)(0xFF ^ srcdata[i]);

  63. }

  64. return srcdata;

  65. }

  66.  
  67. /**

  68. * 修改dex头,CheckSum 校验码

  69. * @param dexBytes

  70. */

  71. private static void fixCheckSumHeader(byte[] dexBytes) {

  72. Adler32 adler = new Adler32();

  73. adler.update(dexBytes, 12, dexBytes.length - 12);//从12到文件末尾计算校验码

  74. long value = adler.getValue();

  75. int va = (int) value;

  76. byte[] newcs = intToByte(va);

  77. //高位在前,低位在前掉个个

  78. byte[] recs = new byte[4];

  79. for (int i = 0; i < 4; i++) {

  80. recs[i] = newcs[newcs.length - 1 - i];

  81. System.out.println(Integer.toHexString(newcs[i]));

  82. }

  83. System.arraycopy(recs, 0, dexBytes, 8, 4);//效验码赋值(8-11)

  84. System.out.println(Long.toHexString(value));

  85. System.out.println();

  86. }

  87.  
  88.  
  89. /**

  90. * int 转byte[]

  91. * @param number

  92. * @return

  93. */

  94. public static byte[] intToByte(int number) {

  95. byte[] b = new byte[4];

  96. for (int i = 3; i >= 0; i--) {

  97. b[i] = (byte) (number % 256);

  98. number >>= 8;

  99. }

  100. return b;

  101. }

  102.  
  103. /**

  104. * 修改dex头 sha1值

  105. * @param dexBytes

  106. * @throws NoSuchAlgorithmException

  107. */

  108. private static void fixSHA1Header(byte[] dexBytes)

  109. throws NoSuchAlgorithmException {

  110. MessageDigest md = MessageDigest.getInstance("SHA-1");

  111. md.update(dexBytes, 32, dexBytes.length - 32);//从32为到结束计算sha--1

  112. byte[] newdt = md.digest();

  113. System.arraycopy(newdt, 0, dexBytes, 12, 20);//修改sha-1值(12-31)

  114. //输出sha-1值,可有可无

  115. String hexstr = "";

  116. for (int i = 0; i < newdt.length; i++) {

  117. hexstr += Integer.toString((newdt[i] & 0xff) + 0x100, 16)

  118. .substring(1);

  119. }

  120. System.out.println(hexstr);

  121. }

  122.  
  123. /**

  124. * 修改dex头 file_size值

  125. * @param dexBytes

  126. */

  127. private static void fixFileSizeHeader(byte[] dexBytes) {

  128. //新文件长度

  129. byte[] newfs = intToByte(dexBytes.length);

  130. System.out.println(Integer.toHexString(dexBytes.length));

  131. byte[] refs = new byte[4];

  132. //高位在前,低位在前掉个个

  133. for (int i = 0; i < 4; i++) {

  134. refs[i] = newfs[newfs.length - 1 - i];

  135. System.out.println(Integer.toHexString(newfs[i]));

  136. }

  137. System.arraycopy(refs, 0, dexBytes, 32, 4);//修改(32-35)

  138. }

  139.  
  140.  
  141. /**

  142. * 以二进制读出文件内容

  143. * @param file

  144. * @return

  145. * @throws IOException

  146. */

  147. private static byte[] readFileBytes(File file) throws IOException {

  148. byte[] arrayOfByte = new byte[1024];

  149. ByteArrayOutputStream localByteArrayOutputStream = new ByteArrayOutputStream();

  150. FileInputStream fis = new FileInputStream(file);

  151. while (true) {

  152. int i = fis.read(arrayOfByte);

  153. if (i != -1) {

  154. localByteArrayOutputStream.write(arrayOfByte, 0, i);

  155. } else {

  156. return localByteArrayOutputStream.toByteArray();

  157. }

  158. }

  159. }

  160. }

 

下面来分析一下:

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

1>、加密源程序Apk文件

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

加密算法很简单:

 

 
  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;

  7. }

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

 

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

 

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

 
  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的长度

 
  1. //添加解壳数据长度

  2. System.arraycopy(intToByte(payloadLen), 0, newdex, totalLen-4, 4);//最后4为长度


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

 
  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.youkuaiyun.com/jiangwei0910410003/article/details/48104455

如何得到系统加载Apk的类加载器,然后我们怎么将加载进来的Apk运行起来等问题都在这篇文章中说到了。

 

2>、找到源程序的Application,通过反射建立并运行。

这里需要注意的是,我们现在是加载一个完整的Apk,让他运行起来,那么我们知道一个Apk运行的时候都是有一个Application对象的,这个也是一个程序运行之后的全局类。所以我们必须找到解密之后的源Apk的Application类,运行的他的onCreate方法,这样源Apk才开始他的运行生命周期。这里我们如何得到源Apk的Application的类呢?这个我们后面会说道。使用meta标签进行设置。

 

下面来看一下整体的流程图:

 

所以我们看到这里还需要一个核心的技术就是动态加载。关于动态加载技术,不了解的同学可以看这篇文章:

http://blog.youkuaiyun.com/jiangwei0910410003/article/details/48104581

 

下面来看一下代码:

 

 
  1. package com.example.reforceapk;

  2.  
  3. import java.io.BufferedInputStream;

  4. import java.io.ByteArrayInputStream;

  5. import java.io.ByteArrayOutputStream;

  6. import java.io.DataInputStream;

  7. import java.io.File;

  8. import java.io.FileInputStream;

  9. import java.io.FileOutputStream;

  10. import java.io.IOException;

  11. import java.lang.ref.WeakReference;

  12. import java.lang.reflect.Method;

  13. import java.util.ArrayList;

  14. import java.util.HashMap;

  15. import java.util.Iterator;

  16. import java.util.zip.ZipEntry;

  17. import java.util.zip.ZipInputStream;

  18.  
  19. import android.app.Application;

  20. import android.app.Instrumentation;

  21. import android.content.Context;

  22. import android.content.pm.ApplicationInfo;

  23. import android.content.pm.PackageManager;

  24. import android.content.pm.PackageManager.NameNotFoundException;

  25. import android.content.res.AssetManager;

  26. import android.content.res.Resources;

  27. import android.content.res.Resources.Theme;

  28. import android.os.Bundle;

  29. import android.util.ArrayMap;

  30. import android.util.Log;

  31. import dalvik.system.DexClassLoader;

  32.  
  33. public class ProxyApplication extends Application{

  34. private static final String appkey = "APPLICATION_CLASS_NAME";

  35. private String apkFileName;

  36. private String odexPath;

  37. private String libPath;

  38.  
  39. //这是context 赋值

  40. @Override

  41. protected void attachBaseContext(Context base) {

  42. super.attachBaseContext(base);

  43. try {

  44. //创建两个文件夹payload_odex,payload_lib 私有的,可写的文件目录

  45. File odex = this.getDir("payload_odex", MODE_PRIVATE);

  46. File libs = this.getDir("payload_lib", MODE_PRIVATE);

  47. odexPath = odex.getAbsolutePath();

  48. libPath = libs.getAbsolutePath();

  49. apkFileName = odex.getAbsolutePath() + "/payload.apk";

  50. File dexFile = new File(apkFileName);

  51. Log.i("demo", "apk size:"+dexFile.length());

  52. if (!dexFile.exists())

  53. {

  54. dexFile.createNewFile(); //在payload_odex文件夹内,创建payload.apk

  55. // 读取程序classes.dex文件

  56. byte[] dexdata = this.readDexFileFromApk();

  57.  
  58. // 分离出解壳后的apk文件已用于动态加载

  59. this.splitPayLoadFromDex(dexdata);

  60. }

  61. // 配置动态加载环境

  62. Object currentActivityThread = RefInvoke.invokeStaticMethod(

  63. "android.app.ActivityThread", "currentActivityThread",

  64. new Class[] {}, new Object[] {});//获取主线程对象 http://blog.youkuaiyun.com/myarrow/article/details/14223493

  65. String packageName = this.getPackageName();//当前apk的包名

  66. //下面两句不是太理解

  67. ArrayMap mPackages = (ArrayMap) RefInvoke.getFieldOjbect(

  68. "android.app.ActivityThread", currentActivityThread,

  69. "mPackages");

  70. WeakReference wr = (WeakReference) mPackages.get(packageName);

  71. //创建被加壳apk的DexClassLoader对象 加载apk内的类和本地代码(c/c++代码)

  72. DexClassLoader dLoader = new DexClassLoader(apkFileName, odexPath,

  73. libPath, (ClassLoader) RefInvoke.getFieldOjbect(

  74. "android.app.LoadedApk", wr.get(), "mClassLoader"));

  75. //base.getClassLoader(); 是不是就等同于 (ClassLoader) RefInvoke.getFieldOjbect()? 有空验证下//?

  76. //把当前进程的DexClassLoader 设置成了被加壳apk的DexClassLoader ----有点c++中进程环境的意思~~

  77. RefInvoke.setFieldOjbect("android.app.LoadedApk", "mClassLoader",

  78. wr.get(), dLoader);

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

  81.  
  82. try{

  83. Object actObj = dLoader.loadClass("com.example.forceapkobj.MainActivity");

  84. Log.i("demo", "actObj:"+actObj);

  85. }catch(Exception e){

  86. Log.i("demo", "activity:"+Log.getStackTraceString(e));

  87. }

  88.  
  89.  
  90. } catch (Exception e) {

  91. Log.i("demo", "error:"+Log.getStackTraceString(e));

  92. e.printStackTrace();

  93. }

  94. }

  95.  
  96. @Override

  97. public void onCreate() {

  98. {

  99. //loadResources(apkFileName);

  100.  
  101. Log.i("demo", "onCreate");

  102. // 如果源应用配置有Appliction对象,则替换为源应用Applicaiton,以便不影响源程序逻辑。

  103. String appClassName = null;

  104. try {

  105. ApplicationInfo ai = this.getPackageManager()

  106. .getApplicationInfo(this.getPackageName(),

  107. PackageManager.GET_META_DATA);

  108. Bundle bundle = ai.metaData;

  109. if (bundle != null && bundle.containsKey("APPLICATION_CLASS_NAME")) {

  110. appClassName = bundle.getString("APPLICATION_CLASS_NAME");//className 是配置在xml文件中的。

  111. } else {

  112. Log.i("demo", "have no application class name");

  113. return;

  114. }

  115. } catch (NameNotFoundException e) {

  116. Log.i("demo", "error:"+Log.getStackTraceString(e));

  117. e.printStackTrace();

  118. }

  119. //有值的话调用该Applicaiton

  120. Object currentActivityThread = RefInvoke.invokeStaticMethod(

  121. "android.app.ActivityThread", "currentActivityThread",

  122. new Class[] {}, new Object[] {});

  123. Object mBoundApplication = RefInvoke.getFieldOjbect(

  124. "android.app.ActivityThread", currentActivityThread,

  125. "mBoundApplication");

  126. Object loadedApkInfo = RefInvoke.getFieldOjbect(

  127. "android.app.ActivityThread$AppBindData",

  128. mBoundApplication, "info");

  129. //把当前进程的mApplication 设置成了null

  130. RefInvoke.setFieldOjbect("android.app.LoadedApk", "mApplication",

  131. loadedApkInfo, null);

  132. Object oldApplication = RefInvoke.getFieldOjbect(

  133. "android.app.ActivityThread", currentActivityThread,

  134. "mInitialApplication");

  135. //http://www.codeceo.com/article/android-context.html

  136. ArrayList<Application> mAllApplications = (ArrayList<Application>) RefInvoke

  137. .getFieldOjbect("android.app.ActivityThread",

  138. currentActivityThread, "mAllApplications");

  139. mAllApplications.remove(oldApplication);//删除oldApplication

  140.  
  141. ApplicationInfo appinfo_In_LoadedApk = (ApplicationInfo) RefInvoke

  142. .getFieldOjbect("android.app.LoadedApk", loadedApkInfo,

  143. "mApplicationInfo");

  144. ApplicationInfo appinfo_In_AppBindData = (ApplicationInfo) RefInvoke

  145. .getFieldOjbect("android.app.ActivityThread$AppBindData",

  146. mBoundApplication, "appInfo");

  147. appinfo_In_LoadedApk.className = appClassName;

  148. appinfo_In_AppBindData.className = appClassName;

  149. Application app = (Application) RefInvoke.invokeMethod(

  150. "android.app.LoadedApk", "makeApplication", loadedApkInfo,

  151. new Class[] { boolean.class, Instrumentation.class },

  152. new Object[] { false, null });//执行 makeApplication(false,null)

  153. RefInvoke.setFieldOjbect("android.app.ActivityThread",

  154. "mInitialApplication", currentActivityThread, app);

  155.  
  156.  
  157. ArrayMap mProviderMap = (ArrayMap) RefInvoke.getFieldOjbect(

  158. "android.app.ActivityThread", currentActivityThread,

  159. "mProviderMap");

  160. Iterator it = mProviderMap.values().iterator();

  161. while (it.hasNext()) {

  162. Object providerClientRecord = it.next();

  163. Object localProvider = RefInvoke.getFieldOjbect(

  164. "android.app.ActivityThread$ProviderClientRecord",

  165. providerClientRecord, "mLocalProvider");

  166. RefInvoke.setFieldOjbect("android.content.ContentProvider",

  167. "mContext", localProvider, app);

  168. }

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

  171.  
  172. app.onCreate();

  173. }

  174. }

  175.  
  176. /**

  177. * 释放被加壳的apk文件,so文件

  178. * @param data

  179. * @throws IOException

  180. */

  181. private void splitPayLoadFromDex(byte[] apkdata) throws IOException {

  182. int ablen = apkdata.length;

  183. //取被加壳apk的长度 这里的长度取值,对应加壳时长度的赋值都可以做些简化

  184. byte[] dexlen = new byte[4];

  185. System.arraycopy(apkdata, ablen - 4, dexlen, 0, 4);

  186. ByteArrayInputStream bais = new ByteArrayInputStream(dexlen);

  187. DataInputStream in = new DataInputStream(bais);

  188. int readInt = in.readInt();

  189. System.out.println(Integer.toHexString(readInt));

  190. byte[] newdex = new byte[readInt];

  191. //把被加壳apk内容拷贝到newdex中

  192. System.arraycopy(apkdata, ablen - 4 - readInt, newdex, 0, readInt);

  193. //这里应该加上对于apk的解密操作,若加壳是加密处理的话

  194. //?

  195.  
  196. //对源程序Apk进行解密

  197. newdex = decrypt(newdex);

  198.  
  199. //写入apk文件

  200. File file = new File(apkFileName);

  201. try {

  202. FileOutputStream localFileOutputStream = new FileOutputStream(file);

  203. localFileOutputStream.write(newdex);

  204. localFileOutputStream.close();

  205. } catch (IOException localIOException) {

  206. throw new RuntimeException(localIOException);

  207. }

  208.  
  209. //分析被加壳的apk文件

  210. ZipInputStream localZipInputStream = new ZipInputStream(

  211. new BufferedInputStream(new FileInputStream(file)));

  212. while (true) {

  213. ZipEntry localZipEntry = localZipInputStream.getNextEntry();//不了解这个是否也遍历子目录,看样子应该是遍历的

  214. if (localZipEntry == null) {

  215. localZipInputStream.close();

  216. break;

  217. }

  218. //取出被加壳apk用到的so文件,放到 libPath中(data/data/包名/payload_lib)

  219. String name = localZipEntry.getName();

  220. if (name.startsWith("lib/") && name.endsWith(".so")) {

  221. File storeFile = new File(libPath + "/"

  222. + name.substring(name.lastIndexOf('/')));

  223. storeFile.createNewFile();

  224. FileOutputStream fos = new FileOutputStream(storeFile);

  225. byte[] arrayOfByte = new byte[1024];

  226. while (true) {

  227. int i = localZipInputStream.read(arrayOfByte);

  228. if (i == -1)

  229. break;

  230. fos.write(arrayOfByte, 0, i);

  231. }

  232. fos.flush();

  233. fos.close();

  234. }

  235. localZipInputStream.closeEntry();

  236. }

  237. localZipInputStream.close();

  238.  
  239.  
  240. }

  241.  
  242. /**

  243. * 从apk包里面获取dex文件内容(byte)

  244. * @return

  245. * @throws IOException

  246. */

  247. private byte[] readDexFileFromApk() throws IOException {

  248. ByteArrayOutputStream dexByteArrayOutputStream = new ByteArrayOutputStream();

  249. ZipInputStream localZipInputStream = new ZipInputStream(

  250. new BufferedInputStream(new FileInputStream(

  251. this.getApplicationInfo().sourceDir)));

  252. while (true) {

  253. ZipEntry localZipEntry = localZipInputStream.getNextEntry();

  254. if (localZipEntry == null) {

  255. localZipInputStream.close();

  256. break;

  257. }

  258. if (localZipEntry.getName().equals("classes.dex")) {

  259. byte[] arrayOfByte = new byte[1024];

  260. while (true) {

  261. int i = localZipInputStream.read(arrayOfByte);

  262. if (i == -1)

  263. break;

  264. dexByteArrayOutputStream.write(arrayOfByte, 0, i);

  265. }

  266. }

  267. localZipInputStream.closeEntry();

  268. }

  269. localZipInputStream.close();

  270. return dexByteArrayOutputStream.toByteArray();

  271. }

  272.  
  273.  
  274. // //直接返回数据,读者可以添加自己解密方法

  275. private byte[] decrypt(byte[] srcdata) {

  276. for(int i=0;i<srcdata.length;i++){

  277. srcdata[i] = (byte)(0xFF ^ srcdata[i]);

  278. }

  279. return srcdata;

  280. }

  281.  
  282.  
  283. //以下是加载资源

  284. protected AssetManager mAssetManager;//资源管理器

  285. protected Resources mResources;//资源

  286. protected Theme mTheme;//主题

  287.  
  288. protected void loadResources(String dexPath) {

  289. try {

  290. AssetManager assetManager = AssetManager.class.newInstance();

  291. Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);

  292. addAssetPath.invoke(assetManager, dexPath);

  293. mAssetManager = assetManager;

  294. } catch (Exception e) {

  295. Log.i("inject", "loadResource error:"+Log.getStackTraceString(e));

  296. e.printStackTrace();

  297. }

  298. Resources superRes = super.getResources();

  299. superRes.getDisplayMetrics();

  300. superRes.getConfiguration();

  301. mResources = new Resources(mAssetManager, superRes.getDisplayMetrics(),superRes.getConfiguration());

  302. mTheme = mResources.newTheme();

  303. mTheme.setTo(super.getTheme());

  304. }

  305.  
  306. @Override

  307. public AssetManager getAssets() {

  308. return mAssetManager == null ? super.getAssets() : mAssetManager;

  309. }

  310.  
  311. @Override

  312. public Resources getResources() {

  313. return mResources == null ? super.getResources() : mResources;

  314. }

  315.  
  316. @Override

  317. public Theme getTheme() {

  318. return mTheme == null ? super.getTheme() : mTheme;

  319. }

  320.  
  321. }


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

 

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

 
  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.  
  20. // 分离出解壳后的apk文件已用于动态加载

  21. this.splitPayLoadFromDex(dexdata);

  22. }

  23. // 配置动态加载环境

  24. Object currentActivityThread = RefInvoke.invokeStaticMethod(

  25. "android.app.ActivityThread", "currentActivityThread",

  26. new Class[] {}, new Object[] {});//获取主线程对象 http://blog.youkuaiyun.com/myarrow/article/details/14223493

  27. String packageName = this.getPackageName();//当前apk的包名

  28. //下面两句不是太理解

  29. ArrayMap mPackages = (ArrayMap) RefInvoke.getFieldOjbect(

  30. "android.app.ActivityThread", currentActivityThread,

  31. "mPackages");

  32. WeakReference wr = (WeakReference) mPackages.get(packageName);

  33. //创建被加壳apk的DexClassLoader对象 加载apk内的类和本地代码(c/c++代码)

  34. DexClassLoader dLoader = new DexClassLoader(apkFileName, odexPath,

  35. libPath, (ClassLoader) RefInvoke.getFieldOjbect(

  36. "android.app.LoadedApk", wr.get(), "mClassLoader"));

  37. //base.getClassLoader(); 是不是就等同于 (ClassLoader) RefInvoke.getFieldOjbect()? 有空验证下//?

  38. //把当前进程的DexClassLoader 设置成了被加壳apk的DexClassLoader ----有点c++中进程环境的意思~~

  39. RefInvoke.setFieldOjbect("android.app.LoadedApk", "mClassLoader",

  40. wr.get(), dLoader);

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

  43.  
  44. try{

  45. Object actObj = dLoader.loadClass("com.example.forceapkobj.MainActivity");

  46. Log.i("demo", "actObj:"+actObj);

  47. }catch(Exception e){

  48. Log.i("demo", "activity:"+Log.getStackTraceString(e));

  49. }

  50.  
  51.  
  52. } catch (Exception e) {

  53. Log.i("demo", "error:"+Log.getStackTraceString(e));

  54. e.printStackTrace();

  55. }

  56. }

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

 

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

 
  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. {

  11. dexFile.createNewFile(); //在payload_odex文件夹内,创建payload.apk

  12. // 读取程序classes.dex文件

  13. byte[] dexdata = this.readDexFileFromApk();

  14.  
  15. // 分离出解壳后的apk文件已用于动态加载

  16. this.splitPayLoadFromDex(dexdata);

  17. }

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

 

A) 从Apk中获取到Dex文件

 

 
  1. /**

  2. * 从apk包里面获取dex文件内容(byte)

  3. * @return

  4. * @throws IOException

  5. */

  6. private byte[] readDexFileFromApk() throws IOException {

  7. ByteArrayOutputStream dexByteArrayOutputStream = new ByteArrayOutputStream();

  8. ZipInputStream localZipInputStream = new ZipInputStream(

  9. new BufferedInputStream(new FileInputStream(

  10. this.getApplicationInfo().sourceDir)));

  11. while (true) {

  12. ZipEntry localZipEntry = localZipInputStream.getNextEntry();

  13. if (localZipEntry == null) {

  14. localZipInputStream.close();

  15. break;

  16. }

  17. if (localZipEntry.getName().equals("classes.dex")) {

  18. byte[] arrayOfByte = new byte[1024];

  19. while (true) {

  20. int i = localZipInputStream.read(arrayOfByte);

  21. if (i == -1)

  22. break;

  23. dexByteArrayOutputStream.write(arrayOfByte, 0, i);

  24. }

  25. }

  26. localZipInputStream.closeEntry();

  27. }

  28. localZipInputStream.close();

  29. return dexByteArrayOutputStream.toByteArray();

  30. }

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

 

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

 
  1. /**

  2. * 释放被加壳的apk文件,so文件

  3. * @param data

  4. * @throws IOException

  5. */

  6. private void splitPayLoadFromDex(byte[] apkdata) throws IOException {

  7. int ablen = apkdata.length;

  8. //取被加壳apk的长度 这里的长度取值,对应加壳时长度的赋值都可以做些简化

  9. byte[] dexlen = new byte[4];

  10. System.arraycopy(apkdata, ablen - 4, dexlen, 0, 4);

  11. ByteArrayInputStream bais = new ByteArrayInputStream(dexlen);

  12. DataInputStream in = new DataInputStream(bais);

  13. int readInt = in.readInt();

  14. System.out.println(Integer.toHexString(readInt));

  15. byte[] newdex = new byte[readInt];

  16. //把被加壳apk内容拷贝到newdex中

  17. System.arraycopy(apkdata, ablen - 4 - readInt, newdex, 0, readInt);

  18. //这里应该加上对于apk的解密操作,若加壳是加密处理的话

  19. //?

  20.  
  21. //对源程序Apk进行解密

  22. newdex = decrypt(newdex);

  23.  
  24. //写入apk文件

  25. File file = new File(apkFileName);

  26. try {

  27. FileOutputStream localFileOutputStream = new FileOutputStream(file);

  28. localFileOutputStream.write(newdex);

  29. localFileOutputStream.close();

  30. } catch (IOException localIOException) {

  31. throw new RuntimeException(localIOException);

  32. }

  33.  
  34. //分析被加壳的apk文件

  35. ZipInputStream localZipInputStream = new ZipInputStream(

  36. new BufferedInputStream(new FileInputStream(file)));

  37. while (true) {

  38. ZipEntry localZipEntry = localZipInputStream.getNextEntry();//不了解这个是否也遍历子目录,看样子应该是遍历的

  39. if (localZipEntry == null) {

  40. localZipInputStream.close();

  41. break;

  42. }

  43. //取出被加壳apk用到的so文件,放到 libPath中(data/data/包名/payload_lib)

  44. String name = localZipEntry.getName();

  45. if (name.startsWith("lib/") && name.endsWith(".so")) {

  46. File storeFile = new File(libPath + "/"

  47. + name.substring(name.lastIndexOf('/')));

  48. storeFile.createNewFile();

  49. FileOutputStream fos = new FileOutputStream(storeFile);

  50. byte[] arrayOfByte = new byte[1024];

  51. while (true) {

  52. int i = localZipInputStream.read(arrayOfByte);

  53. if (i == -1)

  54. break;

  55. fos.write(arrayOfByte, 0, i);

  56. }

  57. fos.flush();

  58. fos.close();

  59. }

  60. localZipInputStream.closeEntry();

  61. }

  62. localZipInputStream.close();

  63.  
  64.  
  65. }

 

 

C) 解密源程序Apk

 
  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;

  7. }

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

 

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

 
  1. //配置动态加载环境

  2. Object currentActivityThread = RefInvoke.invokeStaticMethod(

  3. "android.app.ActivityThread", "currentActivityThread",

  4. new Class[] {}, new Object[] {});//获取主线程对象 http://blog.youkuaiyun.com/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.  
  20. Log.i("demo","classloader:"+dLoader);

  21.  
  22. try{

  23. Object actObj = dLoader.loadClass("com.example.forceapkobj.MainActivity");

  24. Log.i("demo", "actObj:"+actObj);

  25. }catch(Exception e){

  26. Log.i("demo", "activity:"+Log.getStackTraceString(e));

  27. }

 

 

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

 
  1. @Override

  2. public void onCreate() {

  3. {

  4. //loadResources(apkFileName);

  5.  
  6. Log.i("demo", "onCreate");

  7. // 如果源应用配置有Appliction对象,则替换为源应用Applicaiton,以便不影响源程序逻辑。

  8. String appClassName = null;

  9. try {

  10. ApplicationInfo ai = this.getPackageManager()

  11. .getApplicationInfo(this.getPackageName(),

  12. PackageManager.GET_META_DATA);

  13. Bundle bundle = ai.metaData;

  14. if (bundle != null && bundle.containsKey("APPLICATION_CLASS_NAME")) {

  15. appClassName = bundle.getString("APPLICATION_CLASS_NAME");//className 是配置在xml文件中的。

  16. } else {

  17. Log.i("demo", "have no application class name");

  18. return;

  19. }

  20. } catch (NameNotFoundException e) {

  21. Log.i("demo", "error:"+Log.getStackTraceString(e));

  22. e.printStackTrace();

  23. }

  24. //有值的话调用该Applicaiton

  25. Object currentActivityThread = RefInvoke.invokeStaticMethod(

  26. "android.app.ActivityThread", "currentActivityThread",

  27. new Class[] {}, new Object[] {});

  28. Object mBoundApplication = RefInvoke.getFieldOjbect(

  29. "android.app.ActivityThread", currentActivityThread,

  30. "mBoundApplication");

  31. Object loadedApkInfo = RefInvoke.getFieldOjbect(

  32. "android.app.ActivityThread$AppBindData",

  33. mBoundApplication, "info");

  34. //把当前进程的mApplication 设置成了null

  35. RefInvoke.setFieldOjbect("android.app.LoadedApk", "mApplication",

  36. loadedApkInfo, null);

  37. Object oldApplication = RefInvoke.getFieldOjbect(

  38. "android.app.ActivityThread", currentActivityThread,

  39. "mInitialApplication");

  40. //http://www.codeceo.com/article/android-context.html

  41. ArrayList<Application> mAllApplications = (ArrayList<Application>) RefInvoke

  42. .getFieldOjbect("android.app.ActivityThread",

  43. currentActivityThread, "mAllApplications");

  44. mAllApplications.remove(oldApplication);//删除oldApplication

  45.  
  46. ApplicationInfo appinfo_In_LoadedApk = (ApplicationInfo) RefInvoke

  47. .getFieldOjbect("android.app.LoadedApk", loadedApkInfo,

  48. "mApplicationInfo");

  49. ApplicationInfo appinfo_In_AppBindData = (ApplicationInfo) RefInvoke

  50. .getFieldOjbect("android.app.ActivityThread$AppBindData",

  51. mBoundApplication, "appInfo");

  52. appinfo_In_LoadedApk.className = appClassName;

  53. appinfo_In_AppBindData.className = appClassName;

  54. Application app = (Application) RefInvoke.invokeMethod(

  55. "android.app.LoadedApk", "makeApplication", loadedApkInfo,

  56. new Class[] { boolean.class, Instrumentation.class },

  57. new Object[] { false, null });//执行 makeApplication(false,null)

  58. RefInvoke.setFieldOjbect("android.app.ActivityThread",

  59. "mInitialApplication", currentActivityThread, app);

  60.  
  61.  
  62. ArrayMap mProviderMap = (ArrayMap) RefInvoke.getFieldOjbect(

  63. "android.app.ActivityThread", currentActivityThread,

  64. "mProviderMap");

  65. Iterator it = mProviderMap.values().iterator();

  66. while (it.hasNext()) {

  67. Object providerClientRecord = it.next();

  68. Object localProvider = RefInvoke.getFieldOjbect(

  69. "android.app.ActivityThread$ProviderClientRecord",

  70. providerClientRecord, "mLocalProvider");

  71. RefInvoke.setFieldOjbect("android.content.ContentProvider",

  72. "mContext", localProvider, app);

  73. }

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

  76.  
  77. app.onCreate();

  78. }

  79. }

直接在脱壳的Application中的onCreate方法中进行就可以了。这里我们还可以看到是通过AndroidManifest.xml中的meta标签获取源程序Apk中的Application对象的。

 

下面来看一下AndoridManifest.xml文件中的内容:

在这里我们定义了源程序Apk的Application类名。

 

项目下载:http://download.youkuaiyun.com/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.youkuaiyun.com/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的执行流程的了解

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

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

 

七、综合概述

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

1、加壳程序

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

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

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

 

2、脱壳程序

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

语言:Android项目(Java)

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

 

八、总结

Android中的Apk反编译可能是每个开发都会经历的事,但是在反编译的过程中,对于源程序的开发者来说那是不公平的,那么Apk加固也是应运而生,但是即使是这样,我们也还是做不到那么的安全,现在网上也是有很多文章在解析梆梆加固的原理了。而且有人破解成功了,那么加固还不是怎么安全。最后一句话:逆向和加固是一个永不停息的战争。

--------------------- 作者:尼古拉斯_赵四 来源:优快云 原文:https://blog.youkuaiyun.com/jiangwei0910410003/article/details/48415225?utm_source=copy 版权声明:本文为博主原创文章,转载请附上博文链接!

Android动态方式破解apk终极篇(加固apk破解方式) 一、前言 今天总算迎来了破解系列的最后一篇文章了,之前的两篇文章分别为: 第一篇:如何使用Eclipse动态调试smali源码 第二篇:如何使用IDA动态调试SO文件 现在要说的就是最后一篇了,如何应对Android中一些加固apk安全防护,在之前的两篇破解文章中,我们可以看到一个是针对于Java层的破解,一个是针对于native层的破解,还没有涉及到apk的加固,那么今天就要来介绍一下... 阅读详情

相关推荐

Android apk 加固混淆的作用之解决apk报毒

对apk就是加固的好处,可以提高apk的安全性,提高apk被逆向分析破解的门槛,同时通过加固保护可以提高过安全合规的检测。现在市面上的加固产品的还是比较多的,并且各个加固技术产品都有其各自优缺点,但是加固产品的所采用技术去有很多共性的地方。随着加固技术的快速发展和攻防对抗技术的快速迭代,而虚拟机源码保护技术作为当前主流的加固技术,相信在未来很长一段时间,它会一直占据主导且领先的地位,因为通过对源码的虚拟化保护,从而能够为apk提供足够强度的保护。操作的对象是项目打包成的apk文件。三、加固混淆技术的发展。

yesuiyuluo78430的博客 3225

【Android 安全】Android 应用 APK 加固总结 ( 加固原理 | 应用加固完整的实现方案 | 源码资源 )

一、 APK 加固原理、 1、 Android 应用反编译、 2、 ProGuard 混淆、 3、 多 dex 加载原理、 4、 代理 Application 开发、 5、Java 工具开发、 6、Application 替换、

让 学习 成为一种 习惯 ( 韩曙亮 の 技术博客 ) 1万+

Android应用APK加固实践指南

代码混淆是一种常见的软件保护技术,目的是对程序代码进行变形处理,使得代码难以被逆向工程分析和理解。在移动应用开发中,尤其是在APK加固过程中,代码混淆扮演着至关重要的角色。通过混淆,开发者能够保护应用程序的算法逻辑、商业逻辑以及数据结构,避免潜在的竞争对手或恶意攻击者轻易获取到这些关键信息。APK加固通过混淆技术提高了应用的安全防护水平,增强了逆向工程攻击的难度,从而延长了应用的生命周期和保护了企业的商业利益。虽然Android提供了预定义的权限集,但开发者也可以创建自定义权限。

weixin_42612804的博客 1869

Android加固平台推荐(五星)

目前我使用的是360加固来用来加固,然后加固完成,使用的是爱加密来加密。这样的组合还不错。现在的360加固的加密要收费,之前是免费的加固与加密一起,现在要开vip才可加密,但是加固还是老样子免费。喜欢就喜欢吧,喜欢没有理由。

zxz_zxz_zxz的博客 2万+

Win10环境借助DockerDesktop部署Open web UI集成DeepSeek

Win10环境借助DockerDesktop部署Open web UI集成DeepSeek,并借助DuckDuckGo实现联网查询

虎鲸不是鱼 1700

当GO.db安装失败时,生物信息学新手应该知道的5个底层逻辑

本文为生物信息学新手提供了解决GO.db安装失败的5个关键底层逻辑,包括网络连接与镜像源设置、版本兼容性检查、依赖关系管理、SQLite存储机制理解以及调试技巧。特别针对Bioconductor的GO.db包安装问题,详细介绍了如何通过切换国内镜像、管理版本冲突和解决依赖问题来成功安装,帮助用户高效克服常见安装障碍。

weixin_29062823的博客 407

Android Apk加固原理解析

对APP进行加固,可以有效防止移动应用被破解、盗版、二次打包、注入、反编译等,保障程序的安全性、稳定性。常见的加固方案有很多,本文主要介绍如果通过对dex文件进行加密来达到apk加固的目的;ღ( ´・ᴗ・` )

故不积跬步无以至千里 4399

等时替代模型( Isotemporal Substitution Model)

文献阅读:Associations of sedentary time and physical activity with adverse health conditions: Outcome-wide analyses using isotemporal substitution model(该论文于2022年4月28日发表在柳叶刀子刊 EClinicalMedicine 上)

姚巨龙的博客 1万+

安卓逆向笔记--apk加固

安卓逆向笔记–apk加固 资料来源: 浅谈安卓apk加固原理和实现 Android中的Apk的加固(加壳)原理解析和实现 一、apk常见加固方法 (1)代码层级加密–代码混淆         代码混淆是一种常见的加密方式。本质是把工程中原来具有含义的类名、变量名、方法名,修改成让人看不懂的名字。常见的代码混淆工具pro...

qq_43593334的博客 2722

从FP16到FP8:我是如何让Stable Diffusion 3.5提速40%而不丢画质的

本文分享了StableDiffusion模型优化的实战经验。通过FP8精度转换、分层处理敏感模块,实现显存占用降低35%、推理速度提升40%。文章详细介绍了提升画质的五大技巧、本地/云端/移动端部署方案,以及二次元模型定制等垂直领域应用。同时总结了硬件兼容性、数据质量等七大常见陷阱,并强调持续监控和伦理考量。作者指出未来应关注MoE架构、注意力机制优化等方向,认为技术优化是持续追求性能与质量平衡的过程。

达则兼济天下 1187

⭐️VMwareWorkstation17.0虚拟机搭建WindowsXP虚拟机(完整安装步骤详细图文教程)

Windows XP是微软公司研发的计算机操作系统,于2001年10月25日正式发布。其名字中“XP”的意思来自英文中的“体验”(Experience)。Windows XP使用了Luna图形用户界面和双列开始菜单,提升了系统的安全性和工作性能,提供了WiFi支持,简化了软件的安装和运行,并增加了远程桌面功能。

Python老吕的博客 1万+

【免费】Android App加固,Android应用加固,安卓加固,Apk加固

针对目前移动应用普遍存在的破解、篡改、劫持、盗版、数据窃取、钓鱼欺诈等各类安全风险,通过问顶安全的第七代加固技术,有效防止Android应用的反编译、二次打包、内存注入、动态调试、数据窃取、数据泄露等恶意攻击行为,全面保护Android应用软件安全。深圳问顶安全科技有限公司的“Android应用加固与安全检测平台”,是根据市场上移动应用和系统的安全问题,并结合本公司多年来在该领域的安全技术和经验研发的平台软件,可对Android应用(APK)进行自动化安全检测与防护。

键盘的起始页 1484

一文吃透 OData Deep Insert:从框架原理到 ABAP 实战

本文面向ABAP工程师,解析OData服务中的Deep Insert技术。文章首先阐述Deep Insert的业务背景,说明其适用于父子层级数据必须成组创建的场景(如销售订单与行项目)。随后深入框架实现机制,介绍SAP Gateway如何通过CREATE_DEEP_ENTITY方法处理嵌套请求,并强调开发者的责任边界。通过销售订单案例,文章对比了Deep Insert与$batch的适用场景差异,提供请求负载示例和DPC_EXT实现骨架代码。最后给出MPC_EXT声明深结构的三种套路,以及事务处理和错误处理

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 225

安卓apk加固后,Android11+无法安装

降级targetSdkVersion版本到30以下(我在项目中降低了这个版本后项目报错了,所以放弃这个方法)文件是否已经按照 Android 11(API 级别 30)及更高版本的要求进行了对齐和未压缩。这里我签名的软件使用的是梆梆加固提供的,果然签名过后,在执行上面的命令,控制台输出的就是。到这里,我以为我加固后的APK是不符合要求的,但是没想到控制台输出的结果是。正常打包APK,可以在Android11+上正常安装。根据错误提示,检查一下加固后的APK 文件中的。那么问题就出现在签名后的APK上了。

qq_43493084的博客 1166

45、数据挖掘学术文献推荐

本文是一篇关于数据挖掘领域的重要学术文献推荐,涵盖了经典理论与最新研究成果。内容包括关键研究论文、综述文章、教科书和专著的介绍,以及来自顶级会议和期刊的出版物推荐。此外,还探讨了数据挖掘与其他领域的交叉研究,如机器学习、统计学和数据库系统,并列举了多个实际应用案例。最后,分析了数据挖掘的未来发展趋势,如更加智能化的算法、高效的计算平台及隐私保护技术。无论是初学者还是专业人士,这些文献都将提供宝贵的学习资源和前沿动态。

qsc90123456的博客 229

【产品经理修炼之道】-如何搭建企业级账务系统?

笔者公司的业务模式具有一定的独特性,不同于我们常见的市面上供应链类公司的进销存模式。公司主要为各大银行和金融机构提供信贷领域的风控服务,客户主要通过请求公司的数据产品接口产生费用,可以简单地理解为请求一次接口收费0.5元,一个月若是有1w人请求同一个接口则公司需要收费5000元。

xiaoli8748的专栏 5406

安卓apk加固后重签名

等保检测、apk加固失效 、360加固、腾讯加固

Dayer的博客 1455

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

安卓应用加固壳判断java厂商_Android APK加固(加壳)工具

之前一篇文章Android proguard代码混淆,我们讲解了如何实现APK的代码混淆,让反编译者不那么容易阅读我们的源代码。虽然我们混淆,做到native层,但是这都是治标不治本的。反编译的技术在更新,那么保护Apk的技术就不能停止。现在有很多Apk加固的第三方平台,譬如爱加密,360加固,梆梆加密等,但是这些平台都是收费的。今天我们给大家介绍一个免费的APK加固工具APK Protect。测...

weixin_29149259的博客 1086

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

深度学习工具包 DeepLearningToolbox

深度学习工具包Deprecation notice.-----This toolbox is outdated and no longer maintained.There are much better tools available for deep learning than this toolbox, e.g. [Theano](http://deeplearning.net/software/theano/), [torch](http://torch.ch/) or [tensorflow](http://www.tensorflow.org/)I would suggest you use one of the tools mentioned above rather than use this toolbox.Best, Rasmus.DeepLearnToolbox================A Matlab toolbox for Deep Learning.Deep Learning is a new subfield of machine learning that focuses on learning deep hierarchical models of data.It is inspired by the human brain's apparent deep (layered, hierarchical) architecture.A good overview of the theory of Deep Learning theory is[Learning Deep Architectures for AI](http://www.iro.umontreal.ca/~bengioy/papers/ftml_book.pdf)For a more informal introduction, see the following videos by Geoffrey Hinton and Andrew Ng.* [The Next Generation of Neural Networks](http://www.youtube.com/watch?v=AyzOUbkUf3M) (Hinton, 2007)* [Recent Developments in Deep Learning](http://www.youtube.com/watch?v=VdIURAu1-aU) (Hinton, 2010)* [Unsupervised Feature Learning and Deep Learning](http://www.youtube.com/watch?v=ZmNOAtZIgIk) (Ng, 2011)If you use this toolbox in your research please cite [Prediction as a candidate for learning deep hierarchical models of data](http://www2.imm.dtu.dk/pubdb/views/publication_details.php?id=6284)```@MASTERSTHESIS\{IMM2012-06284, author = "R. B. Palm", title = "Prediction as a candidate for learning deep hierarchical models of data", year = "2012",}```Contact: rasmusbergpalm at gmail dot comDirectories included in the toolbox-----------------------------------`NN/` - A library for Feedforward Backpropagation Neural Networks`CNN/` - A library for Convolutional Neural Networks`DBN/` - A library for Deep Belief Networks`SAE/` - A library for Stacked Auto-Encoders`CAE/` - A library for Convolutional Auto-Encoders`util/` - Utility functions used by the libraries`data/` - Data used by the examples`tests/` - unit tests to verify toolbox is workingFor references on each library check REFS.mdSetup-----1. Download.2. addpath(genpath('DeepLearnToolbox'));Example: Deep Belief Network---------------------```matlabfunction test_example_DBNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit RBM and visualize its weightsrand('state',0)dbn.sizes = [100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);figure; visualize(dbn.rbm{1}.W'); % Visualize the RBM weights%% ex2 train a 100-100 hidden unit DBN and use its weights to initialize a NNrand('state',0)%train dbndbn.sizes = [100 100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);%unfold dbn to nnnn = dbnunfoldtonn(dbn, 10);nn.activation_function = 'sigm';%train nnopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.10, 'Too big error');```Example: Stacked Auto-Encoders---------------------```matlabfunction test_example_SAEload mnist_uint8;train_x = double(train_x)/255;test_x = double(test_x)/255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit SDAE and use it to initialize a FFNN% Setup and train a stacked denoising autoencoder (SDAE)rand('state',0)sae = saesetup([784 100]);sae.ae{1}.activation_function = 'sigm';sae.ae{1}.learningRate = 1;sae.ae{1}.inputZeroMaskedFraction = 0.5;opts.numepochs = 1;opts.batchsize = 100;sae = saetrain(sae, train_x, opts);visualize(sae.ae{1}.W{1}(:,2:end)')% Use the SDAE to initialize a FFNNnn = nnsetup([784 100 10]);nn.activation_function = 'sigm';nn.learningRate = 1;nn.W{1} = sae.ae{1}.W{1};% Train the FFNNopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.16, 'Too big error');```Example: Convolutional Neural Nets---------------------```matlabfunction test_example_CNNload mnist_uint8;train_x = double(reshape(train_x',28,28,60000))/255;test_x = double(reshape(test_x',28,28,10000))/255;train_y = double(train_y');test_y = double(test_y');%% ex1 Train a 6c-2s-12c-2s Convolutional neural network %will run 1 epoch in about 200 second and get around 11% error. %With 100 epochs you'll get around 1.2% errorrand('state',0)cnn.layers = { struct('type', 'i') %input layer struct('type', 'c', 'outputmaps', 6, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %sub sampling layer struct('type', 'c', 'outputmaps', 12, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %subsampling layer};cnn = cnnsetup(cnn, train_x, train_y);opts.alpha = 1;opts.batchsize = 50;opts.numepochs = 1;cnn = cnntrain(cnn, train_x, train_y, opts);[er, bad] = cnntest(cnn, test_x, test_y);%plot mean squared errorfigure; plot(cnn.rL);assert(er<0.12, 'Too big error');```Example: Neural Networks---------------------```matlabfunction test_example_NNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);% normalize[train_x, mu, sigma] = zscore(train_x);test_x = normalize(test_x, mu, sigma);%% ex1 vanilla neural netrand('state',0)nn = nnsetup([784 100 10]);opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samples[nn, L] = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.08, 'Too big error');%% ex2 neural net with L2 weight decayrand('state',0)nn = nnsetup([784 100 10]);nn.weightPenaltyL2 = 1e-4; % L2 weight decayopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex3 neural net with dropoutrand('state',0)nn = nnsetup([784 100 10]);nn.dropoutFraction = 0.5; % Dropout fraction opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex4 neural net with sigmoid activation functionrand('state',0)nn = nnsetup([784 100 10]);nn.activation_function = 'sigm'; % Sigmoid activation functionnn.learningRate = 1; % Sigm require a lower learning rateopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex5 plotting functionalityrand('state',0)nn = nnsetup([784 20 10]);opts.numepochs = 5; % Number of full sweeps through datann.output = 'softmax'; % use softmax outputopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex6 neural net with sigmoid activation and plotting of validation and training error% split training data into training and validation datavx = train_x(1:10000,:);tx = train_x(10001:end,:);vy = train_y(1:10000,:);ty = train_y(10001:end,:);rand('state',0)nn = nnsetup([784 20 10]); nn.output = 'softmax'; % use softmax outputopts.numepochs = 5; % Number of full sweeps through dataopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, tx, ty, opts, vx, vy); % nntrain takes validation set as last two arguments (optionally)[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');```[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/rasmusbergpalm/deeplearntoolbox/trend.png)](https://bitdeli.com/free "Bitdeli Badge")

android 加固作用,浅谈安卓apk加固原理和实现

二、apk加固原理(1)apk文件结构解压一个apk包,可以看到如下目录结构:assets:存放工程资源(图片、本地html等)文件的目录Lib:存放ndk编译出来的so文件(so:C/C++编译出的文件)META-INF:该目录下存放的是签名信息,用来保证apk包的完整性和系统的安全性:CERT.RSA:保存着该应用程序的证书和授权信息CERT.SF:保存着SHA-1信息资源列表MANIFEST...

weixin_34638021的博客 1412

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

【亲测免费】 Android APK加固工具Jiagu安装与配置指南

Android APK加固工具Jiagu安装与配置指南 项目基础介绍与主要编程语言 项目名称: Jiagu 项目地址: https://github.com/Frezrik/Jiagu 主要编程语言: Java, Shell, Batchfile Jiagu是一款专为Android应用程序设计的加固工具,旨在增强APK的安全性。它支持Android 5.0及其以上版本,并且能够适应多dex环境。通...

gitblog_09123的博客 2183

浅谈安卓apk加固原理和实现

转载本文需注明出处:微信公众号EAWorld,违者必究。引言:在安卓开发中,打包发布是开发的最后一个环节,apk是整个项目的源码和资源的结合体;对于懂点反编译原理的人可以...

低代码开发,数据,中间件,企业架构原创技术分享 3211

Android Apk 360加固方法步骤

1、首先 : 采用Android Studio或Eclipse生成 360渠道的 .apk 文件。 2、然后去 360加固网站 下载加固软件。  (1)、点击如图选项:   (2)、根据您的电脑型号选择要下载的加固版本:  3、下载后解压完成如图:  4、点击加固助手打开后我们只需要关注两项就可以了:  5、首先点击配置信息 进入配置选项:  6、点击下面的浏览选项:  7、找到自己项...

qq_34317125的博客 2万+

android资源加固,Android apk加固实现原理

apk加固是每一个app发布之前必须要做的事情;如果一个apk没有加固那么别人就很容易被别人反编译,看到这其中的原码,虽然现在有代码混淆、把业务写到native层,但是这都是治标不治本。反编译的技术在更新,那么保护Apk的技术就不能停止,虽然网上有很多加固的第三方框架,但是这加固的原理还是有必要去了解一下的,其实加固有些人认为很高深的技术,其实不然,说的简单点就是对源Apk的dex进行加密,然后在...

weixin_31939387的博客 794

Android Apk加固厂商特征,Apk加固哪家强?

一、Apk加固厂商特征(apk包含以下一个或多个文件) 爱加密 libexec.so, libexecmain.so,ijiami.dat 娜迦 libchaosvmp.so , libddog.solibfdog.so 梆梆 libsecexe.so, libsecmain.so 梆梆企业版 libDexHelper.so , libDexHelper-x86...

键盘的起始页 6921

Android Apk加固签名

Apk加固签名流程

ggzaigl1的博客 956

安卓APK加密、破解、加固与去弹窗浅谈

在Android应用开发与逆向工程中,APK的加密、破解与加固,是开发者和安全研究人员常遇到的话题。本文不涉及非法用途,仅从学习和安全研究角度,简要介绍相关技术流程和常见工具,用于提升自身代码安全防护意识。

lzj781210的博客 4129
上一篇: android webRTC 译文
下一篇: Java中的23种设计模式详解
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值