关于App加固问题

Uni-App上架避坑指南:如何用360加固宝一键解决应用加固问题 本文详细介绍了Uni-App应用上架过程中如何配置隐私政策、声明第三方SDK以及使用360加固宝进行应用加固,重点解析了各应用市场的特殊要求与常见审核驳回原因,提供了一套完整的避坑指南与解决方案,帮助开发者高效完成应用上架。 阅读详情

一、前言

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

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

public class MyApplication extends Application{

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

}

就是打印一下onCreate方法。


MainActivity.java

  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. }  
package com.example.forceapkobj;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.TextView;

public class MainActivity extends Activity {

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

        TextView content = new TextView(this);
        content.setText("I am Source Apk");
        content.setOnClickListener(new OnClickListener(){
            @Override
            public void onClick(View arg0) {
                Intent intent = new Intent(MainActivity.this, SubActivity.class);
                startActivity(intent);
            }});
        setContentView(content);

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

    }

}
也是打印一下内容。


2、加壳程序项目:DexShellTools


加壳程序其实就是一个Java工程,因为我们从上面的分析可以看到,他的工作就是加密源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. }  
package com.example.reforceapk;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.zip.Adler32;


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

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

            FileOutputStream localFileOutputStream = new FileOutputStream(str);
            localFileOutputStream.write(newdex);
            localFileOutputStream.flush();
            localFileOutputStream.close();


        } catch (Exception e) {
            e.printStackTrace();
        }
    }

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

    /**
     * 修改dex头,CheckSum 校验码
     * @param dexBytes
     */
    private static void fixCheckSumHeader(byte[] dexBytes) {
        Adler32 adler = new Adler32();
        adler.update(dexBytes, 12, dexBytes.length - 12);//从12到文件末尾计算校验码
        long value = adler.getValue();
        int va = (int) value;
        byte[] newcs = intToByte(va);
        //高位在前,低位在前掉个个
        byte[] recs = new byte[4];
        for (int i = 0; i < 4; i++) {
            recs[i] = newcs[newcs.length - 1 - i];
            System.out.println(Integer.toHexString(newcs[i]));
        }
        System.arraycopy(recs, 0, dexBytes, 8, 4);//效验码赋值(8-11)
        System.out.println(Long.toHexString(value));
        System.out.println();
    }


    /**
     * int 转byte[]
     * @param number
     * @return
     */
    public static byte[] intToByte(int number) {
        byte[] b = new byte[4];
        for (int i = 3; i >= 0; i--) {
            b[i] = (byte) (number % 256);
            number >>= 8;
        }
        return b;
    }

    /**
     * 修改dex头 sha1值
     * @param dexBytes
     * @throws NoSuchAlgorithmException
     */
    private static void fixSHA1Header(byte[] dexBytes)
            throws NoSuchAlgorithmException {
        MessageDigest md = MessageDigest.getInstance("SHA-1");
        md.update(dexBytes, 32, dexBytes.length - 32);//从32为到结束计算sha--1
        byte[] newdt = md.digest();
        System.arraycopy(newdt, 0, dexBytes, 12, 20);//修改sha-1值(12-31)
        //输出sha-1值,可有可无
        String hexstr = "";
        for (int i = 0; i < newdt.length; i++) {
            hexstr += Integer.toString((newdt[i] & 0xff) + 0x100, 16)
                    .substring(1);
        }
        System.out.println(hexstr);
    }

    /**
     * 修改dex头 file_size值
     * @param dexBytes
     */
    private static void fixFileSizeHeader(byte[] dexBytes) {
        //新文件长度
        byte[] newfs = intToByte(dexBytes.length);
        System.out.println(Integer.toHexString(dexBytes.length));
        byte[] refs = new byte[4];
        //高位在前,低位在前掉个个
        for (int i = 0; i < 4; i++) {
            refs[i] = newfs[newfs.length - 1 - i];
            System.out.println(Integer.toHexString(newfs[i]));
        }
        System.arraycopy(refs, 0, dexBytes, 32, 4);//修改(32-35)
    }


    /**
     * 以二进制读出文件内容
     * @param file
     * @return
     * @throws IOException
     */
    private static byte[] readFileBytes(File file) throws IOException {
        byte[] arrayOfByte = new byte[1024];
        ByteArrayOutputStream localByteArrayOutputStream = new ByteArrayOutputStream();
        FileInputStream fis = new FileInputStream(file);
        while (true) {
            int i = fis.read(arrayOfByte);
            if (i != -1) {
                localByteArrayOutputStream.write(arrayOfByte, 0, i);
            } else {
                return localByteArrayOutputStream.toByteArray();
            }
        }
    }
}


下面来分析一下:

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

1>、加密源程序Apk文件

  1. byte[] payloadArray = encrpt(readFileBytes(payloadSrcFile));//以二进制形式读出apk,并进行加密处理//对源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. }  
//直接返回数据,读者可以添加自己加密方法
private static byte[] encrpt(byte[] srcdata){
    for(int i = 0;i<srcdata.length;i++){
        srcdata[i] = (byte)(0xFF ^ srcdata[i]);
    }
    return srcdata;
}
对每个字节进行异或一下即可。

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


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

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

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

  1. //添加解壳数据长度  
  2. System.arraycopy(intToByte(payloadLen), 0, newdex, totalLen-4, 4);//最后4为长度  
//添加解壳数据长度
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);  
//修改DEX file size文件头
fixFileSizeHeader(newdex);
//修改DEX SHA1 文件头
fixSHA1Header(newdex);
//修改DEX CheckSum文件头
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.ActivityThreadAppBindData"</span><span>,&nbsp;&nbsp;</span></span></li><li class=""><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mBoundApplication,&nbsp;<span class="string">"info"</span><span>);&nbsp;&nbsp;</span></span></li><li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">//把当前进程的mApplication&nbsp;设置成了null</span><span>&nbsp;&nbsp;</span></span></li><li class=""><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;RefInvoke.setFieldOjbect(<span class="string">"android.app.LoadedApk"</span><span>,&nbsp;</span><span class="string">"mApplication"</span><span>,&nbsp;&nbsp;</span></span></li><li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;loadedApkInfo,&nbsp;<span class="keyword">null</span><span>);&nbsp;&nbsp;</span></span></li><li class=""><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Object&nbsp;oldApplication&nbsp;=&nbsp;RefInvoke.getFieldOjbect(&nbsp;&nbsp;</span></li><li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="string">"android.app.ActivityThread"</span><span>,&nbsp;currentActivityThread,&nbsp;&nbsp;</span></span></li><li class=""><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="string">"mInitialApplication"</span><span>);&nbsp;&nbsp;</span></span></li><li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">//http://www.codeceo.com/article/android-context.html</span><span>&nbsp;&nbsp;</span></span></li><li class=""><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ArrayList&lt;Application&gt;&nbsp;mAllApplications&nbsp;=&nbsp;(ArrayList&lt;Application&gt;)&nbsp;RefInvoke&nbsp;&nbsp;</span></li><li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.getFieldOjbect(<span class="string">"android.app.ActivityThread"</span><span>,&nbsp;&nbsp;</span></span></li><li class=""><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;currentActivityThread,&nbsp;<span class="string">"mAllApplications"</span><span>);&nbsp;&nbsp;</span></span></li><li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mAllApplications.remove(oldApplication);<span class="comment">//删除oldApplication</span><span>&nbsp;&nbsp;</span></span></li><li class=""><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></li><li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ApplicationInfo&nbsp;appinfo_In_LoadedApk&nbsp;=&nbsp;(ApplicationInfo)&nbsp;RefInvoke&nbsp;&nbsp;</span></li><li class=""><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.getFieldOjbect(<span class="string">"android.app.LoadedApk"</span><span>,&nbsp;loadedApkInfo,&nbsp;&nbsp;</span></span></li><li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="string">"mApplicationInfo"</span><span>);&nbsp;&nbsp;</span></span></li><li class=""><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ApplicationInfo&nbsp;appinfo_In_AppBindData&nbsp;=&nbsp;(ApplicationInfo)&nbsp;RefInvoke&nbsp;&nbsp;</span></li><li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.getFieldOjbect(<span class="string">"android.app.ActivityThreadAppBindData”,  
  128.                             mBoundApplication, ”appInfo”);  
  129.             appinfo_In_LoadedApk.className = appClassName;  
  130.             appinfo_In_AppBindData.className = appClassName;  
  131.             Application app = (Application) RefInvoke.invokeMethod(  
  132.                     ”android.app.LoadedApk”, “makeApplication”, loadedApkInfo,  
  133.                     new Class[] { boolean.class, Instrumentation.class },  
  134.                     new Object[] { false, null });//执行 makeApplication(false,null)  
  135.             RefInvoke.setFieldOjbect(”android.app.ActivityThread”,  
  136.                     ”mInitialApplication”, currentActivityThread, app);  
  137.   
  138.   
  139.             ArrayMap mProviderMap = (ArrayMap) RefInvoke.getFieldOjbect(  
  140.                     ”android.app.ActivityThread”, currentActivityThread,  
  141.                     ”mProviderMap”);  
  142.             Iterator it = mProviderMap.values().iterator();  
  143.             while (it.hasNext()) {  
  144.                 Object providerClientRecord = it.next();  
  145.                 Object localProvider = RefInvoke.getFieldOjbect(  
  146.                         ”android.app.ActivityThread$ProviderClientRecord”,  
  147.                         providerClientRecord, ”mLocalProvider”);  
  148.                 RefInvoke.setFieldOjbect(”android.content.ContentProvider”,  
  149.                         ”mContext”, localProvider, app);  
  150.             }  
  151.               
  152.             Log.i(”demo”, “app:”+app);  
  153.               
  154.             app.onCreate();  
  155.         }  
  156.     }  
  157.   
  158.     /** 
  159.      * 释放被加壳的apk文件,so文件 
  160.      * @param data 
  161.      * @throws IOException 
  162.      */  
  163.     private void splitPayLoadFromDex(byte[] apkdata) throws IOException {  
  164.         int ablen = apkdata.length;  
  165.         //取被加壳apk的长度   这里的长度取值,对应加壳时长度的赋值都可以做些简化  
  166.         byte[] dexlen = new byte[4];  
  167.         System.arraycopy(apkdata, ablen - 4, dexlen, 0, 4);  
  168.         ByteArrayInputStream bais = new ByteArrayInputStream(dexlen);  
  169.         DataInputStream in = new DataInputStream(bais);  
  170.         int readInt = in.readInt();  
  171.         System.out.println(Integer.toHexString(readInt));  
  172.         byte[] newdex = new byte[readInt];  
  173.         //把被加壳apk内容拷贝到newdex中  
  174.         System.arraycopy(apkdata, ablen - 4 - readInt, newdex, 0, readInt);  
  175.         //这里应该加上对于apk的解密操作,若加壳是加密处理的话  
  176.         //?  
  177.           
  178.         //对源程序Apk进行解密  
  179.         newdex = decrypt(newdex);  
  180.           
  181.         //写入apk文件     
  182.         File file = new File(apkFileName);  
  183.         try {  
  184.             FileOutputStream localFileOutputStream = new FileOutputStream(file);  
  185.             localFileOutputStream.write(newdex);  
  186.             localFileOutputStream.close();  
  187.         } catch (IOException localIOException) {  
  188.             throw new RuntimeException(localIOException);  
  189.         }  
  190.           
  191.         //分析被加壳的apk文件  
  192.         ZipInputStream localZipInputStream = new ZipInputStream(  
  193.                 new BufferedInputStream(new FileInputStream(file)));  
  194.         while (true) {  
  195.             ZipEntry localZipEntry = localZipInputStream.getNextEntry();//不了解这个是否也遍历子目录,看样子应该是遍历的  
  196.             if (localZipEntry == null) {  
  197.                 localZipInputStream.close();  
  198.                 break;  
  199.             }  
  200.             //取出被加壳apk用到的so文件,放到 libPath中(data/data/包名/payload_lib)  
  201.             String name = localZipEntry.getName();  
  202.             if (name.startsWith(“lib/”) && name.endsWith(“.so”)) {  
  203.                 File storeFile = new File(libPath + “/”  
  204.                         + name.substring(name.lastIndexOf(’/’)));  
  205.                 storeFile.createNewFile();  
  206.                 FileOutputStream fos = new FileOutputStream(storeFile);  
  207.                 byte[] arrayOfByte = new byte[1024];  
  208.                 while (true) {  
  209.                     int i = localZipInputStream.read(arrayOfByte);  
  210.                     if (i == -1)  
  211.                         break;  
  212.                     fos.write(arrayOfByte, 0, i);  
  213.                 }  
  214.                 fos.flush();  
  215.                 fos.close();  
  216.             }  
  217.             localZipInputStream.closeEntry();  
  218.         }  
  219.         localZipInputStream.close();  
  220.   
  221.   
  222.     }  
  223.   
  224.     /** 
  225.      * 从apk包里面获取dex文件内容(byte) 
  226.      * @return 
  227.      * @throws IOException 
  228.      */  
  229.     private byte[] readDexFileFromApk() throws IOException {  
  230.         ByteArrayOutputStream dexByteArrayOutputStream = new ByteArrayOutputStream();  
  231.         ZipInputStream localZipInputStream = new ZipInputStream(  
  232.                 new BufferedInputStream(new FileInputStream(  
  233.                         this.getApplicationInfo().sourceDir)));  
  234.         while (true) {  
  235.             ZipEntry localZipEntry = localZipInputStream.getNextEntry();  
  236.             if (localZipEntry == null) {  
  237.                 localZipInputStream.close();  
  238.                 break;  
  239.             }  
  240.             if (localZipEntry.getName().equals(“classes.dex”)) {  
  241.                 byte[] arrayOfByte = new byte[1024];  
  242.                 while (true) {  
  243.                     int i = localZipInputStream.read(arrayOfByte);  
  244.                     if (i == -1)  
  245.                         break;  
  246.                     dexByteArrayOutputStream.write(arrayOfByte, 0, i);  
  247.                 }  
  248.             }  
  249.             localZipInputStream.closeEntry();  
  250.         }  
  251.         localZipInputStream.close();  
  252.         return dexByteArrayOutputStream.toByteArray();  
  253.     }  
  254.   
  255.   
  256.     // //直接返回数据,读者可以添加自己解密方法  
  257.     private byte[] decrypt(byte[] srcdata) {  
  258.         for(int i=0;i<srcdata.length;i++){  
  259.             srcdata[i] = (byte)(0xFF ^ srcdata[i]);  
  260.         }  
  261.         return srcdata;  
  262.     }  
  263.       
  264.       
  265.     //以下是加载资源  
  266.     protected AssetManager mAssetManager;//资源管理器    
  267.     protected Resources mResources;//资源    
  268.     protected Theme mTheme;//主题    
  269.       
  270.     protected void loadResources(String dexPath) {    
  271.         try {    
  272.             AssetManager assetManager = AssetManager.class.newInstance();    
  273.             Method addAssetPath = assetManager.getClass().getMethod(”addAssetPath”, String.class);    
  274.             addAssetPath.invoke(assetManager, dexPath);    
  275.             mAssetManager = assetManager;    
  276.         } catch (Exception e) {    
  277.             Log.i(”inject”, “loadResource error:”+Log.getStackTraceString(e));  
  278.             e.printStackTrace();    
  279.         }    
  280.         Resources superRes = super.getResources();    
  281.         superRes.getDisplayMetrics();    
  282.         superRes.getConfiguration();    
  283.         mResources = new Resources(mAssetManager, superRes.getDisplayMetrics(),superRes.getConfiguration());    
  284.         mTheme = mResources.newTheme();    
  285.         mTheme.setTo(super.getTheme());  
  286.     }    
  287.       
  288.     @Override    
  289.     public AssetManager getAssets() {    
  290.         return mAssetManager == null ? super.getAssets() : mAssetManager;    
  291.     }    
  292.       
  293.     @Override    
  294.     public Resources getResources() {    
  295.         return mResources == null ? super.getResources() : mResources;    
  296.     }    
  297.       
  298.     @Override    
  299.     public Theme getTheme() {    
  300.         return mTheme == null ? super.getTheme() : mTheme;    
  301.     }   
  302.       
  303. }  
package com.example.reforceapk;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import android.app.Application;
import android.app.Instrumentation;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.os.Bundle;
import android.util.ArrayMap;
import android.util.Log;
import dalvik.system.DexClassLoader;

public class ProxyApplication extends Application{
    private static final String appkey = "APPLICATION_CLASS_NAME";
    private String apkFileName;
    private String odexPath;
    private String libPath;

    //这是context 赋值
    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        try {
            //创建两个文件夹payload_odex,payload_lib 私有的,可写的文件目录
            File odex = this.getDir("payload_odex", MODE_PRIVATE);
            File libs = this.getDir("payload_lib", MODE_PRIVATE);
            odexPath = odex.getAbsolutePath();
            libPath = libs.getAbsolutePath();
            apkFileName = odex.getAbsolutePath() + "/payload.apk";
            File dexFile = new File(apkFileName);
            Log.i("demo", "apk size:"+dexFile.length());
            if (!dexFile.exists())
            {
                dexFile.createNewFile();  //在payload_odex文件夹内,创建payload.apk
                // 读取程序classes.dex文件
                byte[] dexdata = this.readDexFileFromApk();

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

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

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


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

    @Override
    public void onCreate() {
        {
            //loadResources(apkFileName);

            Log.i("demo", "onCreate");
            // 如果源应用配置有Appliction对象,则替换为源应用Applicaiton,以便不影响源程序逻辑。
            String appClassName = null;
            try {
                ApplicationInfo ai = this.getPackageManager()
                        .getApplicationInfo(this.getPackageName(),
                                PackageManager.GET_META_DATA);
                Bundle bundle = ai.metaData;
                if (bundle != null && bundle.containsKey("APPLICATION_CLASS_NAME")) {
                    appClassName = bundle.getString("APPLICATION_CLASS_NAME");//className 是配置在xml文件中的。
                } else {
                    Log.i("demo", "have no application class name");
                    return;
                }
            } catch (NameNotFoundException e) {
                Log.i("demo", "error:"+Log.getStackTraceString(e));
                e.printStackTrace();
            }
            //有值的话调用该Applicaiton
            Object currentActivityThread = RefInvoke.invokeStaticMethod(
                    "android.app.ActivityThread", "currentActivityThread",
                    new Class[] {}, new Object[] {});
            Object mBoundApplication = RefInvoke.getFieldOjbect(
                    "android.app.ActivityThread", currentActivityThread,
                    "mBoundApplication");
            Object loadedApkInfo = RefInvoke.getFieldOjbect(
                    "android.app.ActivityThread$AppBindData",
                    mBoundApplication, "info");
            //把当前进程的mApplication 设置成了null
            RefInvoke.setFieldOjbect("android.app.LoadedApk", "mApplication",
                    loadedApkInfo, null);
            Object oldApplication = RefInvoke.getFieldOjbect(
                    "android.app.ActivityThread", currentActivityThread,
                    "mInitialApplication");
            //http://www.codeceo.com/article/android-context.html
            ArrayList<Application> mAllApplications = (ArrayList<Application>) RefInvoke
                    .getFieldOjbect("android.app.ActivityThread",
                            currentActivityThread, "mAllApplications");
            mAllApplications.remove(oldApplication);//删除oldApplication

            ApplicationInfo appinfo_In_LoadedApk = (ApplicationInfo) RefInvoke
                    .getFieldOjbect("android.app.LoadedApk", loadedApkInfo,
                            "mApplicationInfo");
            ApplicationInfo appinfo_In_AppBindData = (ApplicationInfo) RefInvoke
                    .getFieldOjbect("android.app.ActivityThread$AppBindData",
                            mBoundApplication, "appInfo");
            appinfo_In_LoadedApk.className = appClassName;
            appinfo_In_AppBindData.className = appClassName;
            Application app = (Application) RefInvoke.invokeMethod(
                    "android.app.LoadedApk", "makeApplication", loadedApkInfo,
                    new Class[] { boolean.class, Instrumentation.class },
                    new Object[] { false, null });//执行 makeApplication(false,null)
            RefInvoke.setFieldOjbect("android.app.ActivityThread",
                    "mInitialApplication", currentActivityThread, app);


            ArrayMap mProviderMap = (ArrayMap) RefInvoke.getFieldOjbect(
                    "android.app.ActivityThread", currentActivityThread,
                    "mProviderMap");
            Iterator it = mProviderMap.values().iterator();
            while (it.hasNext()) {
                Object providerClientRecord = it.next();
                Object localProvider = RefInvoke.getFieldOjbect(
                        "android.app.ActivityThread$ProviderClientRecord",
                        providerClientRecord, "mLocalProvider");
                RefInvoke.setFieldOjbect("android.content.ContentProvider",
                        "mContext", localProvider, app);
            }

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

            app.onCreate();
        }
    }

    /**
     * 释放被加壳的apk文件,so文件
     * @param data
     * @throws IOException
     */
    private void splitPayLoadFromDex(byte[] apkdata) throws IOException {
        int ablen = apkdata.length;
        //取被加壳apk的长度   这里的长度取值,对应加壳时长度的赋值都可以做些简化
        byte[] dexlen = new byte[4];
        System.arraycopy(apkdata, ablen - 4, dexlen, 0, 4);
        ByteArrayInputStream bais = new ByteArrayInputStream(dexlen);
        DataInputStream in = new DataInputStream(bais);
        int readInt = in.readInt();
        System.out.println(Integer.toHexString(readInt));
        byte[] newdex = new byte[readInt];
        //把被加壳apk内容拷贝到newdex中
        System.arraycopy(apkdata, ablen - 4 - readInt, newdex, 0, readInt);
        //这里应该加上对于apk的解密操作,若加壳是加密处理的话
        //?

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

        //写入apk文件   
        File file = new File(apkFileName);
        try {
            FileOutputStream localFileOutputStream = new FileOutputStream(file);
            localFileOutputStream.write(newdex);
            localFileOutputStream.close();
        } catch (IOException localIOException) {
            throw new RuntimeException(localIOException);
        }

        //分析被加壳的apk文件
        ZipInputStream localZipInputStream = new ZipInputStream(
                new BufferedInputStream(new FileInputStream(file)));
        while (true) {
            ZipEntry localZipEntry = localZipInputStream.getNextEntry();//不了解这个是否也遍历子目录,看样子应该是遍历的
            if (localZipEntry == null) {
                localZipInputStream.close();
                break;
            }
            //取出被加壳apk用到的so文件,放到 libPath中(data/data/包名/payload_lib)
            String name = localZipEntry.getName();
            if (name.startsWith("lib/") && name.endsWith(".so")) {
                File storeFile = new File(libPath + "/"
                        + name.substring(name.lastIndexOf('/')));
                storeFile.createNewFile();
                FileOutputStream fos = new FileOutputStream(storeFile);
                byte[] arrayOfByte = new byte[1024];
                while (true) {
                    int i = localZipInputStream.read(arrayOfByte);
                    if (i == -1)
                        break;
                    fos.write(arrayOfByte, 0, i);
                }
                fos.flush();
                fos.close();
            }
            localZipInputStream.closeEntry();
        }
        localZipInputStream.close();


    }

    /**
     * 从apk包里面获取dex文件内容(byte)
     * @return
     * @throws IOException
     */
    private byte[] readDexFileFromApk() throws IOException {
        ByteArrayOutputStream dexByteArrayOutputStream = new ByteArrayOutputStream();
        ZipInputStream localZipInputStream = new ZipInputStream(
                new BufferedInputStream(new FileInputStream(
                        this.getApplicationInfo().sourceDir)));
        while (true) {
            ZipEntry localZipEntry = localZipInputStream.getNextEntry();
            if (localZipEntry == null) {
                localZipInputStream.close();
                break;
            }
            if (localZipEntry.getName().equals("classes.dex")) {
                byte[] arrayOfByte = new byte[1024];
                while (true) {
                    int i = localZipInputStream.read(arrayOfByte);
                    if (i == -1)
                        break;
                    dexByteArrayOutputStream.write(arrayOfByte, 0, i);
                }
            }
            localZipInputStream.closeEntry();
        }
        localZipInputStream.close();
        return dexByteArrayOutputStream.toByteArray();
    }


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


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

    protected void loadResources(String dexPath) {  
        try {  
            AssetManager assetManager = AssetManager.class.newInstance();  
            Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);  
            addAssetPath.invoke(assetManager, dexPath);  
            mAssetManager = assetManager;  
        } catch (Exception e) {  
            Log.i("inject", "loadResource error:"+Log.getStackTraceString(e));
            e.printStackTrace();  
        }  
        Resources superRes = super.getResources();  
        superRes.getDisplayMetrics();  
        superRes.getConfiguration();  
        mResources = new Resources(mAssetManager, superRes.getDisplayMetrics(),superRes.getConfiguration());  
        mTheme = mResources.newTheme();  
        mTheme.setTo(super.getTheme());
    }  

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

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

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

}

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

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

  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. }  
//这是context 赋值
@Override
protected void attachBaseContext(Context base) {
    super.attachBaseContext(base);
    try {
        //创建两个文件夹payload_odex,payload_lib 私有的,可写的文件目录
        File odex = this.getDir("payload_odex", MODE_PRIVATE);
        File libs = this.getDir("payload_lib", MODE_PRIVATE);
        odexPath = odex.getAbsolutePath();
        libPath = libs.getAbsolutePath();
        apkFileName = odex.getAbsolutePath() + "/payload.apk";
        File dexFile = new File(apkFileName);
        Log.i("demo", "apk size:"+dexFile.length());
        if (!dexFile.exists())
        {
            dexFile.createNewFile();  //在payload_odex文件夹内,创建payload.apk
            // 读取程序classes.dex文件
            byte[] dexdata = this.readDexFileFromApk();

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

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

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


    } catch (Exception e) {
        Log.i("demo", "error:"+Log.getStackTraceString(e));
        e.printStackTrace();
    }
}
这里需要注意的一个问题,就是我们需要找到一个时机,就是在脱壳程序还没有运行起来的时候,来加载源程序的Apk,执行他的onCreate方法,那么这个时机不能太晚,不然的话,就是运行脱壳程序,而不是源程序了。查看源码我们知道。Application中有一个方法:attachBaseContext这个方法,他在Application的onCreate方法执行前就会执行了,那么我们的工作就需要在这里进行

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

  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. }  
//创建两个文件夹payload_odex,payload_lib 私有的,可写的文件目录
File odex = this.getDir("payload_odex", MODE_PRIVATE);
File libs = this.getDir("payload_lib", MODE_PRIVATE);
odexPath = odex.getAbsolutePath();
libPath = libs.getAbsolutePath();
apkFileName = odex.getAbsolutePath() + "/payload.apk";
File dexFile = new File(apkFileName);
Log.i("demo", "apk size:"+dexFile.length());
if (!dexFile.exists())
{
    dexFile.createNewFile();  //在payload_odex文件夹内,创建payload.apk
    // 读取程序classes.dex文件
    byte[] dexdata = this.readDexFileFromApk();

    // 分离出解壳后的apk文件已用于动态加载
    this.splitPayLoadFromDex(dexdata);
}
这个脱壳解密操作一定要和我们之前的加壳以及加密操作对应,不然就会出现Dex加载错误问题

A) 从Apk中获取到Dex文件

  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文件内容(byte)
 * @return
 * @throws IOException
 */
private byte[] readDexFileFromApk() throws IOException {
    ByteArrayOutputStream dexByteArrayOutputStream = new ByteArrayOutputStream();
    ZipInputStream localZipInputStream = new ZipInputStream(
            new BufferedInputStream(new FileInputStream(
                    this.getApplicationInfo().sourceDir)));
    while (true) {
        ZipEntry localZipEntry = localZipInputStream.getNextEntry();
        if (localZipEntry == null) {
            localZipInputStream.close();
            break;
        }
        if (localZipEntry.getName().equals("classes.dex")) {
            byte[] arrayOfByte = new byte[1024];
            while (true) {
                int i = localZipInputStream.read(arrayOfByte);
                if (i == -1)
                    break;
                dexByteArrayOutputStream.write(arrayOfByte, 0, i);
            }
        }
        localZipInputStream.closeEntry();
    }
    localZipInputStream.close();
    return dexByteArrayOutputStream.toByteArray();
}
其实就是解压Apk文件,直接得到dex文件即可


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

  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. }  
/**
 * 释放被加壳的apk文件,so文件
 * @param data
 * @throws IOException
 */
private void splitPayLoadFromDex(byte[] apkdata) throws IOException {
    int ablen = apkdata.length;
    //取被加壳apk的长度   这里的长度取值,对应加壳时长度的赋值都可以做些简化
    byte[] dexlen = new byte[4];
    System.arraycopy(apkdata, ablen - 4, dexlen, 0, 4);
    ByteArrayInputStream bais = new ByteArrayInputStream(dexlen);
    DataInputStream in = new DataInputStream(bais);
    int readInt = in.readInt();
    System.out.println(Integer.toHexString(readInt));
    byte[] newdex = new byte[readInt];
    //把被加壳apk内容拷贝到newdex中
    System.arraycopy(apkdata, ablen - 4 - readInt, newdex, 0, readInt);
    //这里应该加上对于apk的解密操作,若加壳是加密处理的话
    //?

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

    //写入apk文件   
    File file = new File(apkFileName);
    try {
        FileOutputStream localFileOutputStream = new FileOutputStream(file);
        localFileOutputStream.write(newdex);
        localFileOutputStream.close();
    } catch (IOException localIOException) {
        throw new RuntimeException(localIOException);
    }

    //分析被加壳的apk文件
    ZipInputStream localZipInputStream = new ZipInputStream(
            new BufferedInputStream(new FileInputStream(file)));
    while (true) {
        ZipEntry localZipEntry = localZipInputStream.getNextEntry();//不了解这个是否也遍历子目录,看样子应该是遍历的
        if (localZipEntry == null) {
            localZipInputStream.close();
            break;
        }
        //取出被加壳apk用到的so文件,放到 libPath中(data/data/包名/payload_lib)
        String name = localZipEntry.getName();
        if (name.startsWith("lib/") && name.endsWith(".so")) {
            File storeFile = new File(libPath + "/"
                    + name.substring(name.lastIndexOf('/')));
            storeFile.createNewFile();
            FileOutputStream fos = new FileOutputStream(storeFile);
            byte[] arrayOfByte = new byte[1024];
            while (true) {
                int i = localZipInputStream.read(arrayOfByte);
                if (i == -1)
                    break;
                fos.write(arrayOfByte, 0, i);
            }
            fos.flush();
            fos.close();
        }
        localZipInputStream.closeEntry();
    }
    localZipInputStream.close();


}


C) 解密源程序Apk
  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. }  
////直接返回数据,读者可以添加自己解密方法
private byte[] decrypt(byte[] srcdata) {
    for(int i=0;i<srcdata.length;i++){
        srcdata[i] = (byte)(0xFF ^ srcdata[i]);
    }
    return srcdata;
}
这个解密算法和加密算法是一致的


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

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

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


2)、找到源程序的Application程序,让其运行
  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.ActivityThreadAppBindData"</span><span>,&nbsp;&nbsp;</span></span></li><li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mBoundApplication,&nbsp;<span class="string">"info"</span><span>);&nbsp;&nbsp;</span></span></li><li class=""><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">//把当前进程的mApplication&nbsp;设置成了null</span><span>&nbsp;&nbsp;</span></span></li><li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;RefInvoke.setFieldOjbect(<span class="string">"android.app.LoadedApk"</span><span>,&nbsp;</span><span class="string">"mApplication"</span><span>,&nbsp;&nbsp;</span></span></li><li class=""><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;loadedApkInfo,&nbsp;<span class="keyword">null</span><span>);&nbsp;&nbsp;</span></span></li><li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Object&nbsp;oldApplication&nbsp;=&nbsp;RefInvoke.getFieldOjbect(&nbsp;&nbsp;</span></li><li class=""><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="string">"android.app.ActivityThread"</span><span>,&nbsp;currentActivityThread,&nbsp;&nbsp;</span></span></li><li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="string">"mInitialApplication"</span><span>);&nbsp;&nbsp;</span></span></li><li class=""><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">//http://www.codeceo.com/article/android-context.html</span><span>&nbsp;&nbsp;</span></span></li><li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ArrayList&lt;Application&gt;&nbsp;mAllApplications&nbsp;=&nbsp;(ArrayList&lt;Application&gt;)&nbsp;RefInvoke&nbsp;&nbsp;</span></li><li class=""><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.getFieldOjbect(<span class="string">"android.app.ActivityThread"</span><span>,&nbsp;&nbsp;</span></span></li><li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;currentActivityThread,&nbsp;<span class="string">"mAllApplications"</span><span>);&nbsp;&nbsp;</span></span></li><li class=""><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mAllApplications.remove(oldApplication);<span class="comment">//删除oldApplication</span><span>&nbsp;&nbsp;</span></span></li><li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></li><li class=""><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ApplicationInfo&nbsp;appinfo_In_LoadedApk&nbsp;=&nbsp;(ApplicationInfo)&nbsp;RefInvoke&nbsp;&nbsp;</span></li><li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.getFieldOjbect(<span class="string">"android.app.LoadedApk"</span><span>,&nbsp;loadedApkInfo,&nbsp;&nbsp;</span></span></li><li class=""><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="string">"mApplicationInfo"</span><span>);&nbsp;&nbsp;</span></span></li><li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ApplicationInfo&nbsp;appinfo_In_AppBindData&nbsp;=&nbsp;(ApplicationInfo)&nbsp;RefInvoke&nbsp;&nbsp;</span></li><li class=""><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.getFieldOjbect(<span class="string">"android.app.ActivityThreadAppBindData”,  
  33.                         mBoundApplication, ”appInfo”);  
  34.         appinfo_In_LoadedApk.className = appClassName;  
  35.         appinfo_In_AppBindData.className = appClassName;  
  36.         Application app = (Application) RefInvoke.invokeMethod(  
  37.                 ”android.app.LoadedApk”, “makeApplication”, loadedApkInfo,  
  38.                 new Class[] { boolean.class, Instrumentation.class },  
  39.                 new Object[] { false, null });//执行 makeApplication(false,null)  
  40.         RefInvoke.setFieldOjbect(”android.app.ActivityThread”,  
  41.                 ”mInitialApplication”, currentActivityThread, app);  
  42.   
  43.   
  44.         ArrayMap mProviderMap = (ArrayMap) RefInvoke.getFieldOjbect(  
  45.                 ”android.app.ActivityThread”, currentActivityThread,  
  46.                 ”mProviderMap”);  
  47.         Iterator it = mProviderMap.values().iterator();  
  48.         while (it.hasNext()) {  
  49.             Object providerClientRecord = it.next();  
  50.             Object localProvider = RefInvoke.getFieldOjbect(  
  51.                     ”android.app.ActivityThread$ProviderClientRecord”,  
  52.                     providerClientRecord, ”mLocalProvider”);  
  53.             RefInvoke.setFieldOjbect(”android.content.ContentProvider”,  
  54.                     ”mContext”, localProvider, app);  
  55.         }  
  56.           
  57.         Log.i(”demo”, “app:”+app);  
  58.           
  59.         app.onCreate();  
  60.     }  
  61. }  
@Override
public void onCreate() {
    {
        //loadResources(apkFileName);

        Log.i("demo", "onCreate");
        // 如果源应用配置有Appliction对象,则替换为源应用Applicaiton,以便不影响源程序逻辑。
        String appClassName = null;
        try {
            ApplicationInfo ai = this.getPackageManager()
                    .getApplicationInfo(this.getPackageName(),
                            PackageManager.GET_META_DATA);
            Bundle bundle = ai.metaData;
            if (bundle != null && bundle.containsKey("APPLICATION_CLASS_NAME")) {
                appClassName = bundle.getString("APPLICATION_CLASS_NAME");//className 是配置在xml文件中的。
            } else {
                Log.i("demo", "have no application class name");
                return;
            }
        } catch (NameNotFoundException e) {
            Log.i("demo", "error:"+Log.getStackTraceString(e));
            e.printStackTrace();
        }
        //有值的话调用该Applicaiton
        Object currentActivityThread = RefInvoke.invokeStaticMethod(
                "android.app.ActivityThread", "currentActivityThread",
                new Class[] {}, new Object[] {});
        Object mBoundApplication = RefInvoke.getFieldOjbect(
                "android.app.ActivityThread", currentActivityThread,
                "mBoundApplication");
        Object loadedApkInfo = RefInvoke.getFieldOjbect(
                "android.app.ActivityThread$AppBindData",
                mBoundApplication, "info");
        //把当前进程的mApplication 设置成了null
        RefInvoke.setFieldOjbect("android.app.LoadedApk", "mApplication",
                loadedApkInfo, null);
        Object oldApplication = RefInvoke.getFieldOjbect(
                "android.app.ActivityThread", currentActivityThread,
                "mInitialApplication");
        //http://www.codeceo.com/article/android-context.html
        ArrayList<Application> mAllApplications = (ArrayList<Application>) RefInvoke
                .getFieldOjbect("android.app.ActivityThread",
                        currentActivityThread, "mAllApplications");
        mAllApplications.remove(oldApplication);//删除oldApplication

        ApplicationInfo appinfo_In_LoadedApk = (ApplicationInfo) RefInvoke
                .getFieldOjbect("android.app.LoadedApk", loadedApkInfo,
                        "mApplicationInfo");
        ApplicationInfo appinfo_In_AppBindData = (ApplicationInfo) RefInvoke
                .getFieldOjbect("android.app.ActivityThread$AppBindData",
                        mBoundApplication, "appInfo");
        appinfo_In_LoadedApk.className = appClassName;
        appinfo_In_AppBindData.className = appClassName;
        Application app = (Application) RefInvoke.invokeMethod(
                "android.app.LoadedApk", "makeApplication", loadedApkInfo,
                new Class[] { boolean.class, Instrumentation.class },
                new Object[] { false, null });//执行 makeApplication(false,null)
        RefInvoke.setFieldOjbect("android.app.ActivityThread",
                "mInitialApplication", currentActivityThread, app);


        ArrayMap mProviderMap = (ArrayMap) RefInvoke.getFieldOjbect(
                "android.app.ActivityThread", currentActivityThread,
                "mProviderMap");
        Iterator it = mProviderMap.values().iterator();
        while (it.hasNext()) {
            Object providerClientRecord = it.next();
            Object localProvider = RefInvoke.getFieldOjbect(
                    "android.app.ActivityThread$ProviderClientRecord",
                    providerClientRecord, "mLocalProvider");
            RefInvoke.setFieldOjbect("android.content.ContentProvider",
                    "mContext", localProvider, app);
        }

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

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

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

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


项目下载:http://download.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加固也是应运而生,但是即使是这样,我们也还是做不到那么的安全,现在网上也是有很多文章在解析梆梆加固的原理了。而且有人破解成功了,那么加固还不是怎么安全。最后一句话:逆向和加固是一个永不停息的战争。


Ubuntu 20.04 LTS下5分钟搞定ONLYOFFICE协作空间社区版(Docker版保姆级教程) 本文提供了一份在Ubuntu 20.04 LTS系统上,通过Docker快速部署ONLYOFFICE协作空间社区版的保姆级教程。内容涵盖从系统准备、Docker环境搭建,到执行自动化安装脚本、完成Web初始化的全流程,并详细说明了部署后的安全配置、数据备份及基本运维要点,帮助用户高效构建私有、安全、可控的团队文档协作平台。 阅读详情

相关推荐

JavaScript性能优化:全链路性能优化策略

JavaScript性能优化是提升Web应用质量的核心要素,直接影响用户体验与业务成果。研究表明,页面加载延迟1秒可能导致转化率下降7%。高效的JavaScript执行能减少主线程阻塞,避免动画卡顿或操作延迟,尤其在移动端低性能设备上更为关键。优化内存管理与减少冗余计算可降低浏览器资源消耗,防止因内存泄漏导致的崩溃,提升应用稳定性。在竞争激烈的互联网环境中,性能优势已成为产品差异化的关键因素,直接影响用户留存与商业价值。因此,性能优化应作为开发全周期的核心实践,而非事后补救措施。

weixin_49707375的博客 1271

几维加固分析(学习加固版本)

这篇帖子起因是前段时间想挂个协和的号,随便拿了个测试机下载好APP后打开卡在启动界面,以为是测试机root之类的东西被检测到后拒绝启动了。 于是准备看看怎么回事,发现这玩意儿使用了几维加固,正准备分析分析,突然发现测试机WiFi没连上,连上网之后就正常了,于是就把它丢在一边没管了。 然后过年在家闲着没事,想起还有这么个东西,正好几维加固以前没有遇到过,于是又在垃圾桶里把它翻出来了。 分析完后,实在是忍不住想吐槽一下,,不知道是不是外包公司没给钱,这加固做得就跟期末大作业水平差不多,全程就像读源码一样,

zhangmiaoping23的专栏 2058

ABAQUS二次开发中创建SET

在ABAQUS中创建加载、边界条件时,需要使用set,创建set时需要使用对象集合对象中的Set函数,这个函数需要提供加入对象集合的列表,但这个对象列表好像并不能自行创建,只能通过ABAQUS各类对象集合中的findAt、getSequenceFromMask、getByBoundingSphere、getByBoundingCylinder或getByBoundingSphere函数得到,但这几个函数在直接使用PYTHON编程时都不太好用。这个函数返回一个字典,如下所示。

hohoyu的专栏 2482

app加固认识

针对apk中的classes.dex文件进行处理,放入特定的文件中,通 过native代码来对其运行时解密使用厂商(娜迦/爱加密/梆梆)对原dex文件整体压缩加密,保存在壳代理的dex文件尾部,加 载到内存中解密运行使用厂商(360)

Python9724的博客 1127

HAL库硬件SPI驱动ST7735 TFT屏幕:从零实现高效刷屏与图形绘制

本文详细介绍了如何使用STM32的HAL库硬件SPI驱动ST7735 TFT屏幕,从硬件连接到SPI配置优化,再到图形绘制与文本显示的实现。重点分享了DMA传输、局部刷新等高级优化技巧,显著提升刷屏效率,帮助开发者快速掌握屏幕驱动技术并应用于实际项目。

pink的博客 741

离合器KP点自学习:从原理到工程实践的全景解析

本文全面解析了离合器KP点自学习的原理与工程实践,详细介绍了KP点的物理本质、控制价值及自学习方法。通过扭矩增量式和转速变化式学习法,结合不同变速箱构型的适配策略,提升离合器控制的精度与效率。文章还探讨了液压系统迟滞补偿和摩擦材料老化应对方案,为工程实践提供可靠保障。

weixin_33717117的博客 287

加固加壳脱壳分析(1)_加固加壳原理和几代壳

什么是加固加壳 对App资源代码进行保护,使其不容易被反编译工具解开。 加固的核心在于保证软件正常运行的同时又能保证源码的安全性。 为什么要加固加壳 若应用不做任何安全防护,极易被病毒植入、广告替换、支付渠道篡改、钓鱼、信息劫持等,严重侵害开发者的利益。 加固的方向有哪些 从对抗静态分析入手 资源文件保护 dex文件保护 AndroidManifest.xml文件的保护 从对抗动态分析入手 反调试 反hook 从对抗重打包入手 Apk签名校验 安装包文件完整性校验 加固具体思路 dex和elf文件是加固的重

HURUWO的技术博客 1983

从登录到应用可见:在 SAP S/4HANA Cloud Private Edition 中创建用户与打通 SAP Fiori 权限的完整思路

在SAP S/4HANA系统中,用户创建远非简单的账号开通,而是涉及认证、授权、数据访问等多维度的复杂过程。用户主数据记录(User Master Record)是系统访问的起点,包含登录方式、角色分配等关键信息。认证解决"你是谁"的问题,授权则决定"你能做什么"。SAP Fiori访问链路涉及Launchpad内容分配、应用启动映射、OData服务权限等多层校验。角色(Role)通过PFCG维护,包含业务目录(Catalog)和空间(Space)等元素,共同构成完整

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

Android应用加固原理

一、前言 Android开发者常常面临的一个问题就是防破解、 防二次打包。现如今安全问题越来越重要,越来越多的Android开发者也开始寻求安全的保护方案。请看一下下面的几张图片: 1.1 u=2065152164,360269629&fm=173&s=D19EA0725C3578880C719942030030F5&w=639&h=342&img...

Android系统攻城狮 4034

OceanBase容量统计:租户、数据库、表大小

🍎 数据库版本:OceanBase V4。

Sebastien23的博客 1403

逆向工具:一文讲述 Ghidra 的详细使用

Ghidra是由NSA开源的一款功能强大的逆向工程框架,支持多平台、多架构和多种文件格式分析。本文详细介绍了Ghidra的核心优势、安装配置方法、主界面功能模块,并通过一个CrackMe示例演示了完整的逆向分析流程。文章还讲解了高级功能如交叉引用、控制流图分析、数据类型重建和脚本自动化等,最后提供了常见文件格式的分析技巧。作为IDA Pro的免费替代方案,Ghidra适合个人学习、教学和中小项目使用。(149字)

数据知道的博客 4344

RSA算法 Android JAVA C#互通

RSA算法属非对称加密算法,在实际使用中,往往客户端使用公钥进行加密传递敏感数据,服务端server使用私钥进行解密,这样防止中间人从网络获取敏感数据的明文。 Android端主要代码如下: 1 package com.example.rsatest; 2 3 import java.io.UnsupportedEncodingException; 4 import...

yuanpan的专栏 757

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

Android App打包加固后的APK无法安装问题解决

java -jar apksigner.jar sign --ks [签名文件路径] --ks-key-alias [alias的别名] --ks-pass pass:[密码] --key-pass pass:[密码] --out [签名后的新安装包路径] [待签名的安装包路径,即是加固后的安装包路径]查了一下资料说啥的都有,最后发现一个靠谱的,是要进行再次签名处理,说是经过加固后可能会破坏掉签名。3、去验证签名后的APK是否签名,重复1的语句,只是APK变成了2签名后的。2、使用如下命令签名。

chinaherolts2008的博客 1127

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

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

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

Android APP加固混淆案例分享(解决风险提示问题)

APP加固是指通过一些加固技术对apk进行加固,防止别人反编译我们的apk获取源码和资源文件,大致原理是:先将java语法翻译成c/c++代码,然后将c/c++代码编译成.so库。

yesuiyuluo78430的博客 1095

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

Android App打包加固后的APK无法安装问题

最近开发的一个应用要上架,正常流程打完包后去加固,由于以前一直用的是360的加固助手,这里开始也是选择用它。使用360加固:问题一、开始出现的问题是说应用未签名无法加固,我明明是签名后打的包,怎么会没签名呢?后面使用命令查看发现是没有进行V1签名,而360加固要求有v1,一查之下发现是Android6.0之前用V1,Android7.0之后用V2,把自己项目的支持最低版本改为23以下,重新打包发360发现可以加固了。问题二、加固完后的APK发现无法安装。

qq_34707150的博客 1869

Android 16 API 36 升级后 APP 加固兼容性问题解析

摘要:Android 16/API 36升级后,APP加固可能因多因素叠加引发兼容性问题,包括targetSdk变更、系统行为调整、第三方SDK适配等。关键解决思路是: 拆解变量,通过原始包与加固包在旧/新API下的四组对照测试(A/B/C/D)定位问题边界; 聚焦核心场景,如窗口适配、权限控制、Native加载及Google Play提交要求; 建立门禁机制,将安装、启动、支付等关键路径纳入发布前强制校验。 建议企业提前规划升级路径,避免2026年Google Play强制要求时的集中风险,并将加固验证融

zzq7797的博客 455

【教程】APP加固的那些小事情

APP加固是保护APP代码逻辑的重要手段,通过隐藏、混淆、加密等操作提高软件的逆向成本,降低被破解的几率,保障开发者和用户利益。本文将介绍APP加固常见失败原因及解决方法,以及处理安装出现问题的情况和资源文件加固策略选择。APP加固是保护应用代码逻辑的重要手段,但在实际操作中可能会遇到各种小问题。通过及时查看加固日志、适当调整策略以及处理安装和卡死情况,可以有效提升加固效果,保障应用安全。建议开发者在进行APP加固时注意以上细节问题,确保应用的稳定性和安全性。

2301_79527128的博客 352

Tinker和Bugly在加固app下热更新的问题

记录一下使用Tinker的坑: 加固方式为360加固,其他加固方式未测试: // 是否启用加固模式,默认为false.(tinker-spport 1.0.7起支持) isProtectedApp = true implementation 'com.tencent.bugly:crashreport_upgrade:1.3.5' implementation 'com.tencent.tink...

weixin_34268169的博客 714
上一篇: eclipse中配置的tomcat 启动正常 但是访问报404错误
下一篇: MySQL 链接eclipse
RayXiong
博客等级 码龄10年 8粉丝 8原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值