IO操作Dex文件加密,APK加固项目实战

image

0x04 运行壳程序加载原dex文件

通过上面3个步骤,我们了解了dex文件整体加固的流程。那么,当这个加固后的dex文件加载到Dalvik虚拟机中,它又是如何工作的呢?下面我们就来讲解下壳程序在这里面发挥的重要作用。

首先,Dalvik虚拟机会加载我们经过修改的新的classes.dex文件,并最先运行ProxyApplication类。在这个类里面,有2个关键的方法:attachBaseContext和onCreate方法。ProxyApplication显示运行attachBaseContext再运行onCreate方法。

在attachBaseContext方法里,主要做两个工作:

  1. 读取classes.dex文件末尾记录加密dex文件大小的数值,则加密dex文件在新classes.dex文件中的位置为:len(新classes.dex文件) – len(加密dex文件大小)。然后将加密的dex文件读取出来,加密并保存到资源目录下

  2. 然后使用自定义的DexClassLoader加载解密后的原dex文件

在onCreate方法中,主要做两个工作:

  1. 通过反射修改ActivityThread类,并将Application指向原dex文件中的Application

  2. 创建原Application对象,并调用原Application的onCreate方法启动原程序

image

具体的app启动过程可以回看第2章的内容。

2 APK加固实现

====================================================================================

2.1 加固Demo源程序


源程序我们使用NDK开发一个简单的demo,并在assets目录存放一个txt文件读取。这样,我们就可以得到跟10.1.1一样的文件结构,也可以检测下加固后的文件是否影响lib文件的和资源目录下文件的操作。

工程结构如下:

image

MyApplication.java关键代码:

  1. public class MyApplication extends Application{

  2. @Override

  3. public void onCreate() {

  4. // TODO Auto-generated method stub

  5. super.onCreate();

  6. }

  7. @Override

  8. protected void attachBaseContext(Context base) {

  9. // TODO Auto-generated method stub

  10. super.attachBaseContext(base);

  11. }

  12. }

MainActivity.java关键代码:

  1. public class MainActivity extends Activity

  2. {

  3. /** Called when the activity is first created. */

  4. public native void test();

  5. @Override

  6. public void onCreate(Bundle savedInstanceState)

  7. {

  8. super.onCreate(savedInstanceState);

  9. setContentView(R.layout.main);

  10. System.loadLibrary(“demo1”);

  11. test();

  12. Log.v(“demo”, getFromAssets(“test.txt”));

  13. }

  14. public String getFromAssets(String fileName){

  15. String Result=“”;

  16. try {

  17. InputStreamReader inputReader = new InputStreamReader( getResources().getAssets().open(fileName) );

  18. BufferedReader bufReader = new BufferedReader(inputReader);

  19. String line=“”;

  20. while((line = bufReader.readLine()) != null)

  21. Result += line;

  22. } catch (Exception e) {

  23. e.printStackTrace();

  24. }

  25. return Result;

  26. }

  27. }

demo1.cpp关键代码:

  1. #include “com_demo_MainActivity.h”

  2. #include

2.2 加密程序


加密程序是个java工程。

Main.java源码:

  1. public static void main(String[] args) throws Exception {

  2. if(args.length != 2){

  3. System.out.println(“Error! Please input 2 parameters!”);

  4. return ;

  5. }

  6. //参数1:脱壳classes.dex

  7. //参数2:TargetApk.zip

  8. File tuokeDexFile = new File(args[0]);

  9. File targetFile = new File(args[1]);

  10. byte[] tuokeDexFileArray = readFileBytes(tuokeDexFile);

  11. byte[] targetFileArray = encrypt(readFileBytes(targetFile));

  12. int tuokeDexFileLen = tuokeDexFileArray.length;

  13. int targetFileLen = targetFileArray.length;

  14. int totalLen = targetFileLen + tuokeDexFileLen + 4;

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

  16. //添加脱壳classes.dex文件

  17. System.arraycopy(tuokeDexFileArray, 0, newdex, 0, tuokeDexFileLen);

  18. //添加目标TargetApk.zip文件

  19. System.arraycopy(targetFileArray, 0, newdex, tuokeDexFileLen, targetFileLen);

  20. //添加目标zip文件大小

  21. System.arraycopy(intToByte(targetFileLen), 0, newdex, tuokeDexFileLen + targetFileLen, 4);

  22. //修改Dex file size文件头

  23. fixFileSizeHeader(newdex);

  24. //修改Dex SHA1文件头

  25. fixSHA1Header(newdex);

  26. //修改Dex CheckSum文件头

  27. fixCheckSumHeader(newdex);

  28. File file = new File(“classes.dex”);

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

  30. file.createNewFile();

  31. }

  32. FileOutputStream out = new FileOutputStream(file);

  33. out.write(newdex);

  34. out.flush();

  35. out.close();

  36. System.out.println(“Enforce apk successfully!”);

  37. }

修改dex头的fileSize字段

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

  2. throws NoSuchAlgorithmException {

  3. MessageDigest md = MessageDigest.getInstance(“SHA-1”);

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

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

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

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

  8. String hexstr = “”;

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

  10. hexstr += Integer.toString((newdt[i] & 0xff) + 0x100, 16).substring(1);

  11. }

  12. }

修改dex头的checkSum字段

修改dex头的signature字段

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

  2. Adler32 adler = new Adler32();

  3. long value = adler.getValue();

  4. int va = (int) value;

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

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

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

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

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

  10. }

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

  12. }

加密:

  1. private static byte[] encrypt(byte[] srcdata){

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

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

  4. }

  5. return srcdata;

  6. }

读取二进制文件内容

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

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

  3. ByteArrayOutputStream localByteArrayOutputStream = new ByteArrayOutputStream();

  4. FileInputStream fis = new FileInputStream(file);

  5. while (true) {

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

  7. if (i != -1) {

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

  9. } else {

  10. return localByteArrayOutputStream.toByteArray();

  11. }

  12. }

  13. }

int转byte数组

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

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

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

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

  5. number >>= 8;

  6. }

  7. return b;

  8. }

2.3 壳程序


壳程序我们主要分析ProxyApplication类:

0x01 attachBaseContext方法

首先分析attachBaseContext方法:

  1. protected void attachBaseContext(Context base) {

  2. super.attachBaseContext(base);

  3. Log.v(“demo”, “[JiaguApk]=>attachBaseContext() start…”);

  4. try {

  5. File odex = this.getDir(“assets”, MODE_PRIVATE);

  6. String odexPath = odex.getAbsolutePath();

  7. St

    ring targetFilename = odexPath + “/TargetApk.zip”;

  8. File targetApkZipFile = new File(targetFilename);

  9. if(!targetApkZipFile.exists()){

  10. //从classes.dex中提取TargetApk.zip

  11. targetApkZipFile.createNewFile();

  12. byte[] classesDexData = readClassesDexFromApk();

  13. extractTargetZipFileFromDex(classesDexData, targetFilename);

  14. }

  15. Object currentActivityThread = RefInvoke.invokeStaticMethod(“android.app.ActivityThread”, “currentActivityThread”, new Class[] {}, new Object[] {});

  16. String packageName = getPackageName();

  17. Map mPackages = (Map) RefInvoke.getFieldOjbect(“android.app.ActivityThread”, currentActivityThread, “mPackages”);

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

  19. DexClassLoader dLoader = new DexClassLoader(odexPath + “/TargetApk.zip”, odexPath, “/data/data/” + packageName + “/lib”, base.getClassLoader().getParent());

  20. //替換成TargetApk.dex的ClassLoader

  21. RefInvoke.setFieldOjbect(“android.app.LoadedApk”, “mClassLoader”, wr.get(), dLoader);

  22. } catch (Exception e) {

  23. Log.v(“demo”, "[JiaguApk]=>attachBaseContext() " + Log.getStackTraceString(e));

  24. }

  25. Log.v(“demo”, “[JiaguApk]=>attachBaseContext() end…”);

  26. }

在该方法中,我们首先获取加密dex文件,并保存到assets资源目录下,然后进行解密。

由于运行加固后的apk文件时,应用程序使用的是加固后dex文件的类加载器,而不是原dex文件的类加载器。所以,我们需要事先将类加载器替换成员dex文件的类加载器。

这里,我们通过反射获取ActivityThread,通过其mPackages成员获取LoadedApk对象。通过第二章的app启动过程分析,我们知道类加载器的创建是在LoadedApk中完成,这也是为什么我们需要获取LoadedApk对象。通过该对象的mClassLoader成员,我们可以修改该成员指向我们自定义的DexClassLoader,这个类加载器就是原dex中的类加载器。这样我们就可以在后面的步骤中用该类加载器加载原dex文件。

0x02 onCreate方法

  1. public void onCreate() {

  2. try {

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

  4. String appClassName = “com.demo.MyApplication”;

  5. /**

    • 调用静态方法android.app.ActivityThread.currentActivityThread
    • 获取当前activity所在的线程对象
  6. */

  7. Object currentActivityThread = RefInvoke.invokeStaticMethod(

  8. “android.app.ActivityThread”, “currentActivityThread”,

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

  10. /**

    • 获取currentActivityThread中的mBoundApplication属性对象,该对象是一个
    • AppBindData类对象,该类是ActivityThread的一个内部类
  11. */

  12. Object mBoundApplication = RefInvoke.getFieldOjbect(

  13. “android.app.ActivityThread”, currentActivityThread,

  14. “mBoundApplication”);

  15. /**

    • 获取mBoundApplication中的info属性,info 是 LoadedApk类对象
  16. */

  17. Object loadedApkInfo = RefInvoke.getFieldOjbect(

  18. “android.app.ActivityThread$AppBindData”,

  19. mBoundApplication, “info”);

  20. if(null == loadedApkInfo){

  21. Log.v(“demo”, “[JiaguApk]=>onCreate()=>loadedApkInfo is null!!!”);

  22. }else{

  23. Log.v(“demo”, “[JiaguApk]=>onCreate()=>loadedApkInfo:” + loadedApkInfo);

  24. }

  25. /**

    • loadedApkInfo对象的mApplication属性置为null
  26. */

  27. RefInvoke.setFieldOjbect(“android.app.LoadedApk”, “mApplication”,

  28. loadedApkInfo, null);

  29. /**

    • 获取currentActivityThread对象中的mInitialApplication属性

建议

当我们出去找工作,或者准备找工作的时候,我们一定要想,我面试的目标是什么,我自己的技术栈有哪些,近期能掌握的有哪些,我的哪些短板 ,列出来,有计划的去完成,别看前两天掘金一些大佬在驳来驳去 ,他们的观点是他们的,不要因为他们的观点,膨胀了自己,影响自己的学习节奏。基础很大程度决定你自己技术层次的厚度,你再熟练框架也好,也会比你便宜的,性价比高的替代,很现实的问题但也要有危机意识,当我们年级大了,有哪些亮点,与比我们经历更旺盛的年轻小工程师,竞争。

  • 无论你现在水平怎么样一定要 持续学习 没有鸡汤,别人看起来的毫不费力,其实费了很大力,这四个字就是我的建议!!!!!!!!!

  • 准备想说怎么样写简历,想象算了,我觉得,技术就是你最好的简历

  • 我希望每一个努力生活的it工程师,都会得到自己想要的,因为我们很辛苦,我们应得的。

  • 有什么问题想交流,欢迎给我私信,欢迎评论

【附】相关架构及资料

Android高级技术大纲

面试资料整理

内含往期Android高级架构资料、源码、笔记、视频。高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter全方面的Android进阶实践技术

/**

    • loadedApkInfo对象的mApplication属性置为null
  1. */

  2. RefInvoke.setFieldOjbect(“android.app.LoadedApk”, “mApplication”,

  3. loadedApkInfo, null);

  4. /**

    • 获取currentActivityThread对象中的mInitialApplication属性

建议

当我们出去找工作,或者准备找工作的时候,我们一定要想,我面试的目标是什么,我自己的技术栈有哪些,近期能掌握的有哪些,我的哪些短板 ,列出来,有计划的去完成,别看前两天掘金一些大佬在驳来驳去 ,他们的观点是他们的,不要因为他们的观点,膨胀了自己,影响自己的学习节奏。基础很大程度决定你自己技术层次的厚度,你再熟练框架也好,也会比你便宜的,性价比高的替代,很现实的问题但也要有危机意识,当我们年级大了,有哪些亮点,与比我们经历更旺盛的年轻小工程师,竞争。

  • 无论你现在水平怎么样一定要 持续学习 没有鸡汤,别人看起来的毫不费力,其实费了很大力,这四个字就是我的建议!!!!!!!!!

  • 准备想说怎么样写简历,想象算了,我觉得,技术就是你最好的简历

  • 我希望每一个努力生活的it工程师,都会得到自己想要的,因为我们很辛苦,我们应得的。

  • 有什么问题想交流,欢迎给我私信,欢迎评论

【附】相关架构及资料

[外链图片转存中…(img-aJQ0TFua-1716043224309)]

[外链图片转存中…(img-OeDr5jO6-1716043224309)]

内含往期Android高级架构资料、源码、笔记、视频。高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter全方面的Android进阶实践技术

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值