apk加固_apk加固之类抽取分析与修复 - CSDN
精华内容
参与话题
  • 转载本文需注明出处:微信公众号EAWorld,违者必究。引言:在安卓开发中,打包发布是开发的最后一个环节,apk是整个项目的源码和资源的结合体;对于懂点反编译原理的人可以...
        

    640?wx_fmt=jpeg

    转载本文需注明出处:微信公众号EAWorld,违者必究。


    引言:


    在安卓开发中,打包发布是开发的最后一个环节,apk是整个项目的源码和资源的结合体;对于懂点反编译原理的人可以轻松编译出apk的源码资源,并且可以修改资源代码、重新打包编译,轻轻松松变成自己的apk或者修改其中一部分窃取用户信息。

    代码被反编译对于apk的开发者和使用者而言十分苦恼。apk加固、防止反编译此时显得尤为重要。虽然有好多给apk加固的第三方,可能并不需要自己做apk加固,但是了解apk加固原理还是很有必要的。本文主要向大家介绍apk加固原理和简单实现。


    目录:


    一、apk常见加固方式

    二、apk加固原理

    三、apk加固实现

    四、apk该方式加固后缺陷


    一、apk常见加固方式


    (1)代码层级加密--代码混淆


    代码混淆是一种常用的加密方式。本质是把工程中原来的有具体含义的类名、变量名、方法名,修改成让人看不懂的名字。常见的代码混淆工具proguard(有兴趣的可以自己看一下该工具:http://t.cn/ELjgHdi)。该加密方式只是对工程提供了最小的保护,并不是说不能逆向破解;只是说难度增加,需要耐心。


    (2) Dex文件加密


    dex是Android工程中的代码资源文件,通过dex可以反编译出java代码。dex的加壳是常见的加密方式。通过对dex文件加密拼接加壳,可以有效的对工程代码进行保护。apk工程在安装成功后,app启动时会有dex解密的过程,然后重新加载解密后的dex文件。


    第二种加密方式也就是本文要为大家分享的加密方式。基本原理是在jni层, 使用DexClassLoader动态加载技术完成对加密classex.dex的动态加载,dex文件可以附属在assert或raw目录。


    二、apk加固原理


    (1)apk文件结构


    解压一个apk包,可以看到如下目录结构:


    assets:存放工程资源(图片、本地html等)文件的目录


    Lib:存放ndk编译出来的so文件(so:C/C++编译出的文件)


    META-INF

    该目录下存放的是签名信息,用来保证apk包的完整性和系统的安全性:

    CERT.RSA:保存着该应用程序的证书和授权信息

    CERT.SF:保存着SHA-1信息资源列表

    MANIFEST.MF:清单信息


    res:存放资源(布局xml、布局xml引用图片等)文件的目录


    AndroidManifest.xml:清单文件,它描述了应用的名字、版本、权限、注册的服务等信息


    classes.dex:java源码编译经过编译后生成的dalvik字节码文件,主要在Dalvik虚拟机上运行的主要代码部分


    resources.arsc:编译后的二进制资源文件


    META-INF文件主要是跟签名有关的文件,保证了apk的完整性和安全性。apk每次重新签名需要删除该文件夹。

    需要大家主要关注的是classes.dex文件:因为apk加固主要是对dex文件进行的加密。


     (2)Dex文件结构


    640?wx_fmt=png

    Java源文件生成Dex文件的映射关系


    dex文件可以理解为由java文件编译生产的,直观表现就是dex文件可以编译出java源码;


    dex文件的作用是记录整个工程(通常是一个Android工程)的所有类文件的信息;


    dex文件是从class文件演变而来的,class文件存在冗余信息,dex文件则去掉了冗余,并且整合了整个工程的类信息。


    640?wx_fmt=png

    Dex文件结构


    文件头header包含了dex文件的信息,也是大家需要关注的部分。因为下面的操作中会有dex文件的修改操作,而判断是否是正确的dex文件是由header部分决定的。


    下面看一下header部分的信息:


    struct DexFile { 	DexHeader     header; 	DexStringId     StringIds[StringIdsSize]; 	DexTypeId      TypeIds[typeIdsSize];	DexProtoId     ProtoIds[protoIdsSize]; 	DexFieldId      FieldIds[fieldIdsSize]; 	DexMethodId   MethodIds[methodIdsSize]; 	DexClassDef    Data[]; 	DexLink        LinkData; }

    Dex文件header部分结构体信息


    Header部分有需要注意的三个字段:checksum字段、signature字段、filesize字段。


    checksum字段: checksum是校验码字段,校验dex数据的完整性

    signature字段: signature是SHA-1签名字段,dex数据完整性校验

    signature字段:保存classes.dex文件总长度


    之所以关注这三个字段,是因为后面会有对dex的重新拼接。dex拼接后要修改这三个字段,字段修改正确后才可以保证dex的正确加载。


     (3)Dex文件整体加固原理


    640?wx_fmt=png


    上图对象解析:


    源apk:需要加密的apk程序,源dex来自于源apk

    壳程序:Android工程,提供壳dex,壳dex主要作为工程入口,解密出源dex,映射到源dex等操作

    加密程序:java工程,主要是做对源dex加密且和壳dex合并成新dex的操作


     (4)整个工程加载原理


    APP启动——>自定义Application中attachBaseContext()方法——>自定义Application工程onCreate()方法——>源Application


    1、自定义Application来自于壳程序的dex,加密合成的新dex前半部分就是壳程序的dex,这部分是没任何问题,可以正常加载。该Application中attachBaseContext方法会做解密操作,解密出源dex并放置在固定目录下,添加dex的加载映射;映射到源dex目录。


    2、自定义Application工程onCreate()方法添加源dex加载的入口;即源dex的application和mainActivity。


    3、程序正常启动;源dex被正确加载。


    三、apk加固实现


    准备:


    SourceProject

    需要加密源程序,自定义application为:

    com. targetapk

    MyApplication,主activity为:

    com. targetapk.MainActivity


    jiaguApk:java工程,dex加密合并操作


    shellApk:android工程,提供壳dex;自定义Application设定为:

    org.hackcode.ProxyApplication


    SourceProject是简单的Android工程demo,编译生成生成被壳加密的sourceProject.apk

    jiaguApk主要作用是加密源sourceProject.apk中dex文件;然后拼接到壳dex后面生成新的dex

    shellApk是壳工程,主要是作为加密后apk的伪入口,加密后的apk工程会先加载壳工程提供Application:org.hackcode.ProxyApplication,解密、映射等操作


    整体加密步骤:


    1.反编译目标app(sourceProject.apk),得到sourceProject文件;sourceProject.apk来源于SourceProject工程


    2.修改sourceProject文件中的manifest文件,将自定义Application设定为“org.hackcode.ProxyApplication”


    3.拿到sourceProject文件中的dex文件,压缩为TargetApk.zip


    4.反编译壳apk:apktool.bat d shellApk.apk,得到shellApk文件


    5.拿到shellApk文件下的classes.dex和TargetApk.zip,加密合成新的classes.dex文件


    6.新合成的class.dex替换sourceProject文件中的class.dex


    7.删除sourceProject文件中的META-INF文件,重压缩成zip文件


    8.重新签名


    步骤2主要是是为了加载壳dex中的代码;正确解析dex。

    步骤6主要是jiaguApk工程的工作。


    jiaguApk关键代码


    640?wx_fmt=png


    以上代码主要作用是源程序dex压缩成TargetApk.zip后加密,加密后拼接在壳dex的后面,然后生成新的dex文件,dex文件修改头部参数,保证dex文件正确读取。


    此时生成的新的dex文件前部分是能正确被运行的壳dex的代码。


    shellApk壳工程关键代码


    640?wx_fmt=png


    attachBaseContext方法:

    主要作用是程序apk中dex拷贝、解密出源dex放在data/data目录下;设置dex加载目录映射。


    640?wx_fmt=png


    onCreate:

    主要作用替换源应用dex的application,使解密出的源dex正确加载;保证源入口正确,保证项目正确加载。


    以上就是apk加壳实现整个过程。实现起来基本没什么问题,需要注意的点是源工程有多dex问题:源程序中有多个dex的时候,多个dex同时一块压缩成TargetApk.zip,然后其他步骤不变。亲测没问题!


    四、apk该方式加固后缺陷


    (1)首次打开加载慢的问题。加固后的工程首次打开会有延时,延时时间会跟源工程的dex大小有关。就拿普元客户端来说,里面有两个dex,总共有8M左右;加固后首次打开会慢3s左右;而且以后每次打开会有2s左右的延时。


    (2)安全性问题。大家会发现源dex文件还是会落地,在工程的data/data目录下面,越狱的手机可以直接看到。拿到dex资源,一样可以通过反编译拿到java代码。网上有不落地的方法,尝试一直没有成功;如果哪位大神有可行方法的话,欢迎分享交流。


    精选提问:


    问1:能讲讲.so加固吗?


    .so反编译比较困难,比dex的反编译困难。一般做法是抽取Android工程的关键java代码转为c/c++, 然后生成so文件。so加密我只是了解了一下破坏.so部分头文件的方式加固。


    问2:加固里面的VMP(虚拟机)是什么?


    答:VMP(虚拟软件保护技术)大概思路就是自定义一套虚拟机指令和对应的解释器,并将标准的指令转换成自己的指令,然后由解释器将自己的指令给对应的解释器。


    推荐阅读

    基于RN+微应用打造多业务支撑的企业官方App

    企业全面移动化实践指南

    浅谈BDD下的自动化测试框架


    640?wx_fmt=png关于作者张存征,普元移动高级研发工程师,普元移动产品研发团队成员之一,拥有首钢集团、邮政集团等移动平台项目实施研发经验。


    640?wx_fmt=jpeg关于EAWorld:微服务,DevOps,数据治理,移动架构原创技术分享。长按二维码关注!


    课程预告! 12月7日(周五)下午14:30普元开发工程师李中文为大家分享《微服务架构下的服务调用与鉴权》,在此公众回复“YG+微信号”马上入群并完成报名!


    640?wx_fmt=jpeg


    展开全文
  • 一、前言今天又到周末了,憋了好久又要出博客了,今天来介绍一下Android中的如何对Apk进行加固的原理。现阶段。我们知道Android中的反编译工作越来越让...现在网上有很多Apk加固的第三方平台,最有名的应当属于:爱...

    一、前言

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

    二、原理解析

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


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

    1、需要加密的Apk(源Apk)

    2、壳程序Apk(负责解密Apk工作)

    3、加密工具(将源Apk进行加密和壳Dex合并成新的Dex)

    主要步骤:

    我们拿到需要加密的Apk和自己的壳程序Apk,然后用加密算法对源Apk进行加密在将壳Apk进行合并得到新的Dex文件,最后替换壳程序中的dex文件即可,得到新的Apk,那么这个新的Apk我们也叫作脱壳程序Apk.他已经不是一个完整意义上的Apk程序了,他的主要工作是:负责解密源Apk.然后加载Apk,让其正常运行起来。

    在这个过程中我们可能需要了解的一个知识是:如何将源Apk和壳Apk进行合并成新的Dex

    这里就需要了解Dex文件的格式了。下面就来简单介绍一下Dex文件的格式

    具体Dex文件格式的详细介绍可以查看这个文件:http://download.csdn.net/detail/jiangwei0910410003/9102599

    主要来看一下Dex文件的头部信息,其实Dex文件和Class文件的格式分析原理都是一样的,他们都是有固定的格式,我们知道现在反编译的一些工具:

    1、jd-gui:可以查看jar中的类,其实他就是解析class文件,只要了解class文件的格式就可以

    2、dex2jar:将dex文件转化成jar,原理也是一样的,只要知道Dex文件的格式,能够解析出dex文件中的类信息就可以了

    当然我们在分析这个文件的时候,最重要的还是头部信息,应该他是一个文件的开始部分,也是索引部分,内部信息很重要。


    我们今天只要关注上面红色标记的三个部分:

    1) checksum

    文件校验码 ,使用alder32 算法校验文件除去 maigc ,checksum 外余下的所有文件区域 ,用于检查文件错误 。

    2) signature

    使用 SHA-1 算法 hash 除去 magic ,checksum 和 signature 外余下的所有文件区域 ,用于唯一识别本文件 。

    3) file_size

    Dex 文件的大小 。

    为什么说我们只需要关注这三个字段呢?

    因为我们需要将一个文件(加密之后的源Apk)写入到Dex中,那么我们肯定需要修改文件校验码(checksum).因为他是检查文件是否有错误。那么signature也是一样,也是唯一识别文件的算法。还有就是需要修改dex文件的大小。

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

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

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


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

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

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

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

    三、项目案例

    下面先来看一下源程序

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


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

    MyApplication.java

    [java]view plaincopy

    packagecom.example.forceapkobj;

    importandroid.app.Application;

    importandroid.util.Log;

    publicclassMyApplicationextendsApplication{

    @Override

    publicvoidonCreate() {

    super.onCreate();

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

    }

    }

    就是打印一下onCreate方法。

    MainActivity.java

    [java]view plaincopy

    packagecom.example.forceapkobj;

    importandroid.app.Activity;

    importandroid.content.Intent;

    importandroid.os.Bundle;

    importandroid.util.Log;

    importandroid.view.View;

    importandroid.view.View.OnClickListener;

    importandroid.widget.TextView;

    publicclassMainActivityextendsActivity {

    @Override

    protectedvoidonCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    TextView content =newTextView(this);

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

    content.setOnClickListener(newOnClickListener(){

    @Override

    publicvoidonClick(View arg0) {

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

    startActivity(intent);

    }});

    setContentView(content);

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

    }

    }

    也是打印一下内容。

    2、加壳程序项目:DexShellTools


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

    看一下代码:

    [java]view plaincopy

    packagecom.example.reforceapk;

    importjava.io.ByteArrayOutputStream;

    importjava.io.File;

    importjava.io.FileInputStream;

    importjava.io.FileOutputStream;

    importjava.io.IOException;

    importjava.security.MessageDigest;

    importjava.security.NoSuchAlgorithmException;

    importjava.util.zip.Adler32;

    publicclassmymain {

    /**

    * @param args

    */

    publicstaticvoidmain(String[] args) {

    // TODO Auto-generated method stub

    try{

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

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

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

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

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

    intpayloadLen = payloadArray.length;

    intunShellDexLen = unShellDexArray.length;

    inttotalLen = payloadLen + unShellDexLen +4;//多出4字节是存放长度的。

    byte[] newdex =newbyte[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 =newFile(str);

    if(!file.exists()) {

    file.createNewFile();

    }

    FileOutputStream localFileOutputStream =newFileOutputStream(str);

    localFileOutputStream.write(newdex);

    localFileOutputStream.flush();

    localFileOutputStream.close();

    }catch(Exception e) {

    e.printStackTrace();

    }

    }

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

    privatestaticbyte[] encrpt(byte[] srcdata){

    for(inti =0;i

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

    }

    returnsrcdata;

    }

    /**

    * 修改dex头,CheckSum 校验码

    * @param dexBytes

    */

    privatestaticvoidfixCheckSumHeader(byte[] dexBytes) {

    Adler32 adler =newAdler32();

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

    longvalue = adler.getValue();

    intva = (int) value;

    byte[] newcs = intToByte(va);

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

    byte[] recs =newbyte[4];

    for(inti =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

    */

    publicstaticbyte[] intToByte(intnumber) {

    byte[] b =newbyte[4];

    for(inti =3; i >=0; i--) {

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

    number >>=8;

    }

    returnb;

    }

    /**

    * 修改dex头 sha1值

    * @param dexBytes

    * @throws NoSuchAlgorithmException

    */

    privatestaticvoidfixSHA1Header(byte[] dexBytes)

    throwsNoSuchAlgorithmException {

    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(inti =0; i < newdt.length; i++) {

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

    .substring(1);

    }

    System.out.println(hexstr);

    }

    /**

    * 修改dex头 file_size值

    * @param dexBytes

    */

    privatestaticvoidfixFileSizeHeader(byte[] dexBytes) {

    //新文件长度

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

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

    byte[] refs =newbyte[4];

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

    for(inti =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

    */

    privatestaticbyte[] readFileBytes(File file)throwsIOException {

    byte[] arrayOfByte =newbyte[1024];

    ByteArrayOutputStream localByteArrayOutputStream =newByteArrayOutputStream();

    FileInputStream fis =newFileInputStream(file);

    while(true) {

    inti = fis.read(arrayOfByte);

    if(i != -1) {

    localByteArrayOutputStream.write(arrayOfByte,0, i);

    }else{

    returnlocalByteArrayOutputStream.toByteArray();

    }

    }

    }

    }

    下面来分析一下:


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

    1>、加密源程序Apk文件

    [java]view plaincopy

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

    加密算法很简单:

    [java]view plaincopy

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

    privatestaticbyte[] encrpt(byte[] srcdata){

    for(inti =0;i

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

    }

    returnsrcdata;

    }

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

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

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

    [java]view plaincopy

    intpayloadLen = payloadArray.length;

    intunShellDexLen = unShellDexArray.length;

    inttotalLen = payloadLen + unShellDexLen +4;//多出4字节是存放长度的。

    byte[] newdex =newbyte[totalLen];// 申请了新的长度

    //添加解壳代码

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

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

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

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

    [java]view plaincopy

    //添加解壳数据长度

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

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

    [java]view plaincopy

    //修改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.csdn.net/jiangwei0910410003/article/details/48104455

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

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

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

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


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

    http://blog.csdn.net/jiangwei0910410003/article/details/48104581

    下面来看一下代码:

    [java]view plaincopy

    packagecom.example.reforceapk;

    importjava.io.BufferedInputStream;

    importjava.io.ByteArrayInputStream;

    importjava.io.ByteArrayOutputStream;

    importjava.io.DataInputStream;

    importjava.io.File;

    importjava.io.FileInputStream;

    importjava.io.FileOutputStream;

    importjava.io.IOException;

    importjava.lang.ref.WeakReference;

    importjava.lang.reflect.Method;

    importjava.util.ArrayList;

    importjava.util.HashMap;

    importjava.util.Iterator;

    importjava.util.zip.ZipEntry;

    importjava.util.zip.ZipInputStream;

    importandroid.app.Application;

    importandroid.app.Instrumentation;

    importandroid.content.Context;

    importandroid.content.pm.ApplicationInfo;

    importandroid.content.pm.PackageManager;

    importandroid.content.pm.PackageManager.NameNotFoundException;

    importandroid.content.res.AssetManager;

    importandroid.content.res.Resources;

    importandroid.content.res.Resources.Theme;

    importandroid.os.Bundle;

    importandroid.util.ArrayMap;

    importandroid.util.Log;

    importdalvik.system.DexClassLoader;

    publicclassProxyApplicationextendsApplication{

    privatestaticfinalString appkey ="APPLICATION_CLASS_NAME";

    privateString apkFileName;

    privateString odexPath;

    privateString libPath;

    //这是context 赋值

    @Override

    protectedvoidattachBaseContext(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 =newFile(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",

    newClass[] {},newObject[] {});//获取主线程对象 http://blog.csdn.net/myarrow/article/details/14223493

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

    //下面两句不是太理解

    ArrayMap mPackages = (ArrayMap) RefInvoke.getFieldOjbect(

    "android.app.ActivityThread", currentActivityThread,

    "mPackages");

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

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

    DexClassLoader dLoader =newDexClassLoader(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

    publicvoidonCreate() {

    {

    //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",

    newClass[] {},newObject[] {});

    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 mAllApplications = (ArrayList) 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,

    newClass[] {boolean.class, Instrumentation.class},

    newObject[] {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

    */

    privatevoidsplitPayLoadFromDex(byte[] apkdata)throwsIOException {

    intablen = apkdata.length;

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

    byte[] dexlen =newbyte[4];

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

    ByteArrayInputStream bais =newByteArrayInputStream(dexlen);

    DataInputStream in =newDataInputStream(bais);

    intreadInt = in.readInt();

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

    byte[] newdex =newbyte[readInt];

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

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

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

    //?

    //对源程序Apk进行解密

    newdex = decrypt(newdex);

    //写入apk文件

    File file =newFile(apkFileName);

    try{

    FileOutputStream localFileOutputStream =newFileOutputStream(file);

    localFileOutputStream.write(newdex);

    localFileOutputStream.close();

    }catch(IOException localIOException) {

    thrownewRuntimeException(localIOException);

    }

    //分析被加壳的apk文件

    ZipInputStream localZipInputStream =newZipInputStream(

    newBufferedInputStream(newFileInputStream(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 =newFile(libPath +"/"

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

    storeFile.createNewFile();

    FileOutputStream fos =newFileOutputStream(storeFile);

    byte[] arrayOfByte =newbyte[1024];

    while(true) {

    inti = 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

    */

    privatebyte[] readDexFileFromApk()throwsIOException {

    ByteArrayOutputStream dexByteArrayOutputStream =newByteArrayOutputStream();

    ZipInputStream localZipInputStream =newZipInputStream(

    newBufferedInputStream(newFileInputStream(

    this.getApplicationInfo().sourceDir)));

    while(true) {

    ZipEntry localZipEntry = localZipInputStream.getNextEntry();

    if(localZipEntry ==null) {

    localZipInputStream.close();

    break;

    }

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

    byte[] arrayOfByte =newbyte[1024];

    while(true) {

    inti = localZipInputStream.read(arrayOfByte);

    if(i == -1)

    break;

    dexByteArrayOutputStream.write(arrayOfByte,0, i);

    }

    }

    localZipInputStream.closeEntry();

    }

    localZipInputStream.close();

    returndexByteArrayOutputStream.toByteArray();

    }

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

    privatebyte[] decrypt(byte[] srcdata) {

    for(inti=0;i

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

    }

    returnsrcdata;

    }

    //以下是加载资源

    protectedAssetManager mAssetManager;//资源管理器

    protectedResources mResources;//资源

    protectedTheme mTheme;//主题

    protectedvoidloadResources(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 =newResources(mAssetManager, superRes.getDisplayMetrics(),superRes.getConfiguration());

    mTheme = mResources.newTheme();

    mTheme.setTo(super.getTheme());

    }

    @Override

    publicAssetManager getAssets() {

    returnmAssetManager ==null?super.getAssets() : mAssetManager;

    }

    @Override

    publicResources getResources() {

    returnmResources ==null?super.getResources() : mResources;

    }

    @Override

    publicTheme getTheme() {

    returnmTheme ==null?super.getTheme() : mTheme;

    }

    }

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

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

    [java]view plaincopy

    //这是context 赋值

    @Override

    protectedvoidattachBaseContext(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 =newFile(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",

    newClass[] {},newObject[] {});//获取主线程对象 http://blog.csdn.net/myarrow/article/details/14223493

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

    //下面两句不是太理解

    ArrayMap mPackages = (ArrayMap) RefInvoke.getFieldOjbect(

    "android.app.ActivityThread", currentActivityThread,

    "mPackages");

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

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

    DexClassLoader dLoader =newDexClassLoader(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,并且进行解密操作

    [java]view plaincopy

    //创建两个文件夹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 =newFile(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文件

    [java]view plaincopy

    /**

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

    * @return

    * @throws IOException

    */

    privatebyte[] readDexFileFromApk()throwsIOException {

    ByteArrayOutputStream dexByteArrayOutputStream =newByteArrayOutputStream();

    ZipInputStream localZipInputStream =newZipInputStream(

    newBufferedInputStream(newFileInputStream(

    this.getApplicationInfo().sourceDir)));

    while(true) {

    ZipEntry localZipEntry = localZipInputStream.getNextEntry();

    if(localZipEntry ==null) {

    localZipInputStream.close();

    break;

    }

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

    byte[] arrayOfByte =newbyte[1024];

    while(true) {

    inti = localZipInputStream.read(arrayOfByte);

    if(i == -1)

    break;

    dexByteArrayOutputStream.write(arrayOfByte,0, i);

    }

    }

    localZipInputStream.closeEntry();

    }

    localZipInputStream.close();

    returndexByteArrayOutputStream.toByteArray();

    }

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

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

    [java]view plaincopy

    /**

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

    * @param data

    * @throws IOException

    */

    privatevoidsplitPayLoadFromDex(byte[] apkdata)throwsIOException {

    intablen = apkdata.length;

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

    byte[] dexlen =newbyte[4];

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

    ByteArrayInputStream bais =newByteArrayInputStream(dexlen);

    DataInputStream in =newDataInputStream(bais);

    intreadInt = in.readInt();

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

    byte[] newdex =newbyte[readInt];

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

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

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

    //?

    //对源程序Apk进行解密

    newdex = decrypt(newdex);

    //写入apk文件

    File file =newFile(apkFileName);

    try{

    FileOutputStream localFileOutputStream =newFileOutputStream(file);

    localFileOutputStream.write(newdex);

    localFileOutputStream.close();

    }catch(IOException localIOException) {

    thrownewRuntimeException(localIOException);

    }

    //分析被加壳的apk文件

    ZipInputStream localZipInputStream =newZipInputStream(

    newBufferedInputStream(newFileInputStream(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 =newFile(libPath +"/"

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

    storeFile.createNewFile();

    FileOutputStream fos =newFileOutputStream(storeFile);

    byte[] arrayOfByte =newbyte[1024];

    while(true) {

    inti = localZipInputStream.read(arrayOfByte);

    if(i == -1)

    break;

    fos.write(arrayOfByte,0, i);

    }

    fos.flush();

    fos.close();

    }

    localZipInputStream.closeEntry();

    }

    localZipInputStream.close();

    }

    C) 解密源程序Apk

    [java]view plaincopy

    直接返回数据,读者可以添加自己解密方法

    privatebyte[] decrypt(byte[] srcdata) {

    for(inti=0;i

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

    }

    returnsrcdata;

    }

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

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

    [java]view plaincopy

    //配置动态加载环境

    Object currentActivityThread = RefInvoke.invokeStaticMethod(

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

    newClass[] {},newObject[] {});//获取主线程对象 http://blog.csdn.net/myarrow/article/details/14223493

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

    //下面两句不是太理解

    ArrayMap mPackages = (ArrayMap) RefInvoke.getFieldOjbect(

    "android.app.ActivityThread", currentActivityThread,

    "mPackages");

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

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

    DexClassLoader dLoader =newDexClassLoader(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程序,让其运行

    [java]view plaincopy

    @Override

    publicvoidonCreate() {

    {

    //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",

    newClass[] {},newObject[] {});

    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 mAllApplications = (ArrayList) 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,

    newClass[] {boolean.class, Instrumentation.class},

    newObject[] {false,null});//执行 makeApplication(false,null)

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

    "mInitialApplication", currentActivityThread, app);

    ArrayMap mProviderMap = (ArrayMap) RefInvoke.getFieldOjbect(

    "android.app.ActivityThread", currentActivityThread,

    "mProviderMap");

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

    while(it.hasNext()) {

    Object providerClientRecord = it.next();

    Object localProvider = RefInvoke.getFieldOjbect(

    "android.app.ActivityThread$ProviderClientRecord",

    providerClientRecord,"mLocalProvider");

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

    "mContext", localProvider, app);

    }

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

    app.onCreate();

    }

    }

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

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


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

    项目下载:http://download.csdn.net/detail/jiangwei0910410003/9102741

    四、运行程序

    那么到这里我们就介绍完了,这三个项目的内容,下面就来看看如何运行吧:

    运行步骤:

    第一步:得到源程序Apk文件和脱壳程序的Dex文件



    运行源程序和脱壳程序项目,之后得到这两个文件(记得将classes.dex文件改名ForceApkObj.dex),然后使用加壳程序进行加壳:


    这里的ForceApkObj.apk文件和ForceApkObj.dex文件是输入文件,输出的是classes.dex文件。

    第二步:替换脱壳程序中的classes.dex文件

    我们在第一步中得到加壳之后的classes.dex文件之后,并且我们在第一步运行脱壳项目的时候得到一个ReforceApk.apk文件,这时候我们使用解压缩软件进行替换:


    第三步:我们在第二步的时候得到替换之后的ReforceApk.apk文件,这个文件因为被修改了,所以我们需要从新对他签名,不然运行也是报错的。


    工具下载:http://download.csdn.net/detail/jiangwei0910410003/9102767

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

    展开全文
  • 正文:分享4个加固品台 这边开始我的分享:腾讯乐固平台官网:http://console.cloud.tencent.com/legu/myapplication/index360加固平台官网:http://jiagu.360.cn/#/global/index网易 易盾平台官网:...

    正文:分享4个加固品台    这边开始我的分享:
    腾讯乐固平台官网:http://console.cloud.tencent.com/legu/myapplication/index

     

    360加固平台官网:http://jiagu.360.cn/#/global/index
     
    网易 易盾平台官网:http://dun.163.com/
     
    百度加固:http://app.baidu.com/user/register

     

    转载:https://www.52pojie.cn/thread-813237-1-1.html

    展开全文
  • APK 加固

    2018-11-11 12:49:31
    加固方法 gradle默认签名 生成签名用的keystore,集成bugly https://blog.csdn.net/seven_newbie/article/details/72454644 生成签名的apk ####[配置自动打包的签名信息]- ...

    加固方法

    在这里插入图片描述

    ####[配置自动打包的签名信息]- (https://blog.csdn.net/wedfrend/article/details/58044935)

    • 完成后在项目的build文件夹下可以看到生成的apk
      在这里插入图片描述

    在百度地图,支付宝支付的时候,为了让测试和正式发布的apk一样,那么要配置同样的key,否则掉不起来三方

    展开全文
  • apk加固(乐固)

    千次阅读 2019-05-28 10:32:32
    特地去用了一下apk加固。 目前加固工具挺多的,我听到的有腾讯的乐固和360安全加固。 这次选腾讯的乐固。记录下步骤,怕自己忘了。 步骤: 1.因为乐固是在腾讯云上(以前我以为腾讯云只是提供服务器功能,看了下...
  • apk进行加固

    2019-06-28 14:38:30
    最近app打包上架,我们公司使用的是免费的360加固宝 1:http://jiagu.360.cn/#/global/download 首先,得先去上面的这个网址去下载我们要用的这个加固宝 2:下载好了,当然就需要去注册(个人或者公司的)账号和...
  • Android中Apk加固代码实现

    千次阅读 2019-06-14 09:30:55
    前言:上一篇博客已经把Apk加固的思路详细的介绍过了,也开始创建了一个空的demo进行,然后在项目中添加一个代理module(解密,和系统源码交互功能)和tools工具加密Java library 的module ,这里开始接着把整个过程...
  • Apk加固原理解析

    千次阅读 2017-11-14 11:25:52
    为了更好的防止Apk被反编译,将Apk加密后打入宿主Apk包中,宿主启动时,解密Apk然后启动Apk。二、工作原理 通过反射的方法替换ClassLoader及资源路径,使得启动的都是加固Apk。三、启动过程分析 加固Apk...
  • Android APK加固(加壳)工具

    万次阅读 2016-12-07 09:28:31
    之前一篇文章Android proguard代码混淆,我们讲解了如何实现...现在有很多Apk加固的第三方平台,譬如爱加密,360加固,梆梆加密等,但是这些平台都是收费的。今天我们给大家介绍一个免费的APK加固工具APK Protect。测试
  • android apk 加固

    2018-10-10 14:33:13
    一、前言 今天又到周末了,憋了好久又要出博客了,今天来介绍一下Android中的如何对Apk进行加固的原理。现阶段。我们知道Android中的反编译工作越来越让人...现在网上有很多Apk加固的第三方平台,最有名的应当属于...
  • APK加固Demo

    2020-07-24 23:32:33
    Android中对Apk加固原理解析的最好例子,通过一个例子来讲解Android中加固Apk的原理
  • Android APK加固完善篇

    千次阅读 2017-07-05 11:27:33
    ...时隔半年,困扰的问题始终是需要解决的,之前也算是没时间弄,今天因为有人在此提起这个问题,那么就不能不解决了,这里写一篇文章记录一下吧。...就是关于之前的一个话题:Android中apk加固技术实现 关于
  • Apk加固

    2016-04-20 15:46:54
    Android中的Apk加固(加壳)原理解析和实现 http://blog.csdn.net/jiangwei0910410003/article/details/48415225 apk加壳demo,对包含布局xml和图片资源文件的apk加壳 ...
  • Apk加固方案

    2020-08-30 18:19:09
    特点:梆梆助手,是梆梆安全面向广大开发者提供的桌面级(PC端)APP加固辅助工具,具有提交便捷、响应迅速、传输稳定等特点,同时支持断点续传、可视化任务管理、多任务处理、自动一体化加固、自定义签名导出形式等...
  • apk加固之360加固

    2017-04-13 15:32:00
    360加固可选加固后不签名,也可选择加固后自动签名, 360有加固的视图操作, 选择简单的视图操作,上传签名文件自动签名, 最后(可选多渠道打包)打包上架。给大家推荐一个好用的文章,简单易懂。
  • 一、Apk加固厂商特征(apk包含以下一个或多个文件) 爱加密 libexec.so, libexecmain.so,ijiami.dat 娜迦 libchaosvmp.so , libddog.solibfdog.so 梆梆 libsecexe.so, libsecmain.so 梆梆企业版 ...
  • 国内apk加固的破解方法

    千次阅读 2015-12-29 19:27:56
    国内apk加固的破解方法 原文链接:http://blog.csdn.net/pxb1988/article/details/17167795 By Bob Pan 国内的apk加固技术都使用了将原有的dex隐藏, 在运行时解压, 并且通过修改app的类加载器的方式实现...
  • 一、前 言Android Apk加固的发展已经有一段时间了,相对来说本篇博客要记录的Android加壳的实现思路是4年的东西了,已经被老鸟玩烂了,Android加固的安全厂商也不会采用这么粗犷的方式来进行Android Apk的加固处理。...
  • APK加固(梆梆助手)

    千次阅读 2019-10-24 16:59:12
    前言:朋友在使用梆梆时出现Apk加固后安装失败的现象,所以自己写篇小白文 1.进入梆梆官网(注册) 2.下载梆梆助手(点击加固工具) 本人电脑是Windows系统(下载) 3.安装后,登录账号,导入APK 4.等-----------...
  • Android逆向工程之apk加固后反编译AndroidManifest.xml Android编译生成的apk经过加固后,AndroidManifest.xml无法直接查看,直接打开是一堆乱码。在一些情况下, 又特别需要知道加固后的包中某个值最终形态,比如多...
1 2 3 4 5 ... 20
收藏数 4,325
精华内容 1,730
关键字:

apk加固