ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

apk加壳去壳

2021-06-18 20:01:46  阅读:215  来源: 互联网

标签:dex APK apk import new byte 加壳


1.1原理

(1)基础原理

在这里插入图片描述
在整个加固过程中,我们会涉及到四个文件(其实最终要实现加固,需要涉
及的文件不止四个,这里我们仅包含了主要文件):

  • originalAPK.apk:APK 文件,也就是我们要加固的 APP
  • shellDex.dex:dex 文件,该文件的程序功能是将 originalAPK.apk 的密文
    进行解密,并将 originalAPK.apk 中的 dex 文件进行加载,启动被加固的 APP。
    该 dex 文件来自于一个 APK 文件,我们称为 shellAPK.apk。注意,在
    shellAPK.apk 中,shellDex.dex 文件的名字为 classes.dex。这里,我们为了区
    别,将其改名为 shellDex.dex。
  • addShell.java:java 程序,用来加密 originalAPK.apk,并将
    originalAPK.apk 密文与 shellDex.dex 文件合并,生成 classes.dex 文件,并将该
    文件加入到 shellAPK.apk 中替换原来的 classes.dex 文件。
  • classes.dex:dex 文件,最终到用户手里的 shellAPK.apk 中的 dex 文件,
    该文件本质上包含 shellAPK.apk 中原 classes.dex 文件和 originalAPK.apk 的密
    文。

一般加固流程
1.生成 originalAPK.apk
2.编写 addShell.java 程序(注:虽然这里没有所要操作的 shellDex.dex 文件,
但其文件格式都已知晓,不会影响程序的实现)
3.编写 shellAPK.apk,并从中提取 classes.dex 文件改名为 shellDex.dex(注:
虽然这里 originalAPK.apk 都没有被加密,也没有与 shellDex.dex 合并,但解密
算法和文件格式都已知晓,不会影响程序的实现)
4.运行 addShell.java 程序,生成 classes.dex
5.修改相关文件,如 Manifest 文件,将修改后的文件和第四步生成的
classes.dex 文件替换 shellAPK.apk 中相应的文件。最终获得的 shellAPK.apk 就
是我们加固后的 originalAPK.apk。

(2)DEX 头内容

在这里插入图片描述
需要关注的字段:

  • checksum 文件校验码 ,使用 alder32 算法校验文件除去 maigc ,
    checksum 外余下的所有文件区域 ,用于检查文件错误 。
  • signature 使用 SHA-1 算法 hash 除去 magic ,checksum 和 signature 外余
    下的所有文件区域 ,用于唯一识别本文件 。
    ·- ileSize Dex 文件的大小 。
    在文件的最后,我们需要标注被加密的 apk 的大小,因此需要增加 4 个字
    节。

简单来说,我们将数据拼接后,还需要修改这三个字段,以保证文件正常运
行。最后,这个新 DEX 的结构大概如下
在这里插入图片描述
因此拼接步骤如下:
• 获取脱壳 DEX (二进制数据)
• 计算新 DEX 的大小(脱壳 DEX 的大小 + 加密 APK 的大小 + 4)
• 依次拼接脱壳 DEX、加密 APK、加密 APK 的大小,得到新 DEX
• 修改新 DEX 头的三个字段
• 输出新 DEX

1.2操作过程

(1)源apk

首先新建一个HelloWorld
MainAcitivity

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Log.d("helloPack", "I am coming");
    }
}

(2)加密和拼接工具(函数)

使用 Android Studio 的 Java
Moudle 来编辑 Java 函数。
再明确一下功能:对源 APK 进行加密、拼接到脱壳 DEX,得到新 DEX。

1、新建 Java Moudle

新建工程,默认模板即可,然后右键 app,New -> Moudle -> Java Library:
在这里插入图片描述

此时,会生成一个 lib 文件夹和一个默认的 MyClass.java,我们可以在里面编辑 Java 代码。

在这里插入图片描述
这里测试一下:

public class MyClass {
 public static void main(String[] args) {
 System.out.println("hello Java");
 }
}

右键 MyClass,或者点击对应文件旁边的按钮,选择 Run"MyClass.main()":
在这里插入图片描述

2、 加密和拼接

package com.example.lib;

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 MyClass {
    public static void main(String[] args){
        try {
            // 获取源APK(项目根目录下)
            File srcApkFile = new File("force/orignal.apk");
            System.out.println("apk path: " + srcApkFile.getAbsolutePath());
            System.out.println("apk size: " + srcApkFile.length());
            // 加密并返回数据
            byte[] enSrcApkArray = encrypt(readFileBytes(srcApkFile));
            // 脱壳DEX, 以二进制形式读出DEX
            File unShellDexFile = new File("force/classes.dex");
            System.out.println("unShellDexFile path: " + unShellDexFile.getAbsolutePath());
            System.out.println("unShellDexFile size: " + unShellDexFile.length());
            byte[] unShellDexArray = readFileBytes(unShellDexFile);

            // 将脱壳DEX长度和加密APK长度相加并加上存放加密APK大小的四位得到总长度
            int unShellDexLen = unShellDexArray.length;
            int enSrcApkLen = enSrcApkArray.length;
            // 多出的四位存放加密APK长度
            int totalLen = unShellDexLen + enSrcApkLen + 4;

            // 依次将脱壳DEX,加密APK,加密APK大小,拼接出新的DEX
            byte[] newDex = new byte[totalLen];
            // 复制加壳数据
            System.arraycopy(unShellDexArray, 0, newDex, 0, unShellDexLen);
            // 复制加密APK数据
            System.arraycopy(enSrcApkArray, 0, newDex, unShellDexLen, enSrcApkLen);
            // 赋值加密APK大小
            System.arraycopy(intToByte(enSrcApkLen), 0, newDex, totalLen - 4, 4);

            // 修改DEX file size 文件头
            fixFileSizeHeader(newDex);
            // 修改DEX SHA1 文件头
            fixSHA1Header(newDex);
            // 修改DEX CheckNum文件头
            fixCheckSumHeader(newDex);

            // 新DEX文件名
            String str = "force/classesShell.dex";
            File file = new File(str);
            if (!file.exists()) {
                if (!file.createNewFile()) {
                    System.out.println("File create error");
                    return;
                }
            }
            // 导出文件
            FileOutputStream fos = new FileOutputStream(str);
            fos.write(newDex);
            fos.flush();
            fos.close();
        } catch (IOException | NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
    }

    /**
     * 修改DEX头,checkSum校验码
     * @param dexBytes 要修改的二进制数据
     */
    private static void fixCheckSumHeader(byte[] dexBytes) {
        Adler32 adler = new Adler32();
        // 从12到文件末尾计算校验码
        adler.update(dexBytes, 12, dexBytes.length - 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("fixCheckSumHeader:" + Integer.toHexString(newCs[i]));
        }
        // 校验码赋值(8-11)
        System.arraycopy(reCs, 0, dexBytes, 8, 4);
        System.out.println("fixCheckSumHeader:" + Long.toHexString(value));
    }

    /**
     * 修改DEX头, sha1值
     * @param dexBytes 要修改的二进制数组
     */
    private static void fixSHA1Header(byte[] dexBytes) throws NoSuchAlgorithmException {
        MessageDigest md = MessageDigest.getInstance("SHA-1");
        // 从32位到结束计算sha-1
        md.update(dexBytes, 32, dexBytes.length - 32);
        byte[] newDt = md.digest();
        // 修改sha-1值(12-21)
        System.arraycopy(newDt, 0, dexBytes, 12, 20);
        // 输出sha-1值
        StringBuilder hexStr = new StringBuilder();
        for (byte aNewDt : newDt) {
            hexStr.append(Integer.toString((aNewDt & 0xFF) + 0x100, 16).substring(1));
        }
        System.out.println("fixSHA1Header:" + hexStr.toString());
    }

    /**
     * 修改DEX头, file_size值
     * @param dexBytes 二进制数据
     */
    private static void fixFileSizeHeader(byte[] dexBytes) {
        // 新文件长度
        byte[] newFs = intToByte(dexBytes.length);
        System.out.println("fixFileSizeHeader: " + 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("fixFileSizeHeader: " + Integer.toHexString(newFs[i]));
        }
        // 修改32-35
        System.arraycopy(reFs, 0, dexBytes, 32, 4);
    }

    /**
     * int 转 byte[]
     * @param number 整型
     * @return 返回字节数组
     */
    private 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;
    }



    /**
     * 读出文件的二进制数据
     * @param file 文件
     * @return 二进制数据
     */
    private static byte[] readFileBytes(File file) throws IOException {
        byte[] bytes = new byte[1024];
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        FileInputStream fis = new FileInputStream(file);
        while (true) {
            int len = fis.read(bytes);
            if (-1 == len) break;
            baos.write(bytes, 0, len);
        }
        byte[] byteArray = baos.toByteArray();
        fis.close();
        baos.close();
        return byteArray;
    }

    /**
     * 加密二进制数据
     * @param srcData 源数据
     * @return 加密后的数据
     */
    private static byte[] encrypt(byte[] srcData) {
        for (int i = 0; i < srcData.length; i++) {
            srcData[i] ^= 0xFF;
        }
        return srcData;
    }
}

项目根目录下新建文件夹 force,将源 APK 放进其中

( 3 )脱壳 DEX

编辑脱壳 APK 的解密部分和内容替换部分,取出其中的 DEX 文件,即脱壳DEX
在这里插入图片描述

( 1 )自定义 Application

首先,自定义 Application,在 AndroidManifest.xml 指定名称为
ProxyApplication,利用 Android Studio 可以自动生成
ProxyApplication.java 文件。同时,将源 APK 的 Activity 注册进来,

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.exampie.helloworld">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

( 2 )attachBaseContext 方法实现

package com.exampie.myapplication;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class RefInvoke {
    /**
     * 反射执行类的静态函数(public)
     *
     * @param className  类名
     * @param methodName 方法名
     * @param pareTypes  函数的参数类型
     * @param pareValues 调用函数时传入的参数
     * @return
     */
    public static Object invokeStaticMethod(String className, String methodName, Class[] pareTypes, Object[] pareValues) {
        try {
            Class objClass = Class.forName(className);
            Method method = objClass.getMethod(methodName, pareTypes);
            return method.invoke(null, pareValues);
        } catch (ClassNotFoundException | NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 反射执行的函数(public)
     *
     * @param className  类名
     * @param methodName 方法名
     * @param obj        对象
     * @param pareTypes  参数类型
     * @param pareValues 调用方法传入的参数
     * @return
     */
    public static Object invokeMethod(String className, String methodName, Object obj, Class[] pareTypes, Object[] pareValues) {
        try {
            Class objClass = Class.forName(className);
            Method method = objClass.getMethod(methodName, pareTypes);
            return method.invoke(obj, pareValues);
        } catch (ClassNotFoundException | NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }
    /**
     * 反射得到类的属性(包括私有和保护)
     *
     * @param className 类名
     * @param obj       对象
     * @param fieldName 属性名
     * @return
     */
    public static Object getFieldObject(String className, Object obj, String fieldName) {
        try {
            Class objClass = Class.forName(className);
            Field field = objClass.getDeclaredField(fieldName);
            field.setAccessible(true);
            return field.get(obj);
        } catch (ClassNotFoundException | IllegalAccessException | NoSuchFieldException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 反射得到类的静态属性(包括私有和保护)
     *
     * @param className 类名
     * @param fieldName 属性名
     * @return
     */
    public static Object getStaticFieldObject(String className, String fieldName) {
        try {
            Class objClass = Class.forName(className);
            Field field = objClass.getDeclaredField(fieldName);
            field.setAccessible(true);
            return field.get(null);
        } catch (ClassNotFoundException | IllegalAccessException | NoSuchFieldException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 设置类的属性(包括私有和保护)
     *
     * @param className  类名
     * @param fieldName  属性名
     * @param obj        对象
     * @param fieldValue 字段值
     */
    public static void setFieldObject(String className, String fieldName, Object obj, Object fieldValue) {
        try {
            Class objClass = Class.forName(className);
            Field field = objClass.getDeclaredField(fieldName);
            field.setAccessible(true);
            field.set(obj, fieldValue);
        } catch (ClassNotFoundException | IllegalAccessException | NoSuchFieldException e) {
            e.printStackTrace();
        }
    }

    /**
     * 设置类的静态属性(包括私有和保护)
     *
     * @param className  类名
     * @param fieldName  属性名
     * @param fieldValue 属性值
     */
    public static void setStaticObject(String className, String fieldName, String fieldValue) {
        try {
            Class objClass = Class.forName(className);
            Field field = objClass.getDeclaredField(fieldName);
            field.setAccessible(true);
            field.set(null, fieldValue);
        } catch (ClassNotFoundException | IllegalAccessException | NoSuchFieldException e) {
            e.printStackTrace();
        }
    }
}

( 3 )onCreate 方法实现

在 ProxyApplication.java :

package com.exampie.myapplication;

import android.app.Application;
import android.app.Instrumentation;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.util.ArrayMap;
import android.util.Log;


import androidx.annotation.RequiresApi;

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.util.ArrayList;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import dalvik.system.DexClassLoader;

public class ProxyApplication extends Application {
    private static final String TAG = ProxyApplication.class.getSimpleName();
    /**
     * APP_KEY获取Activity入口
     */
    private static final String APP_KEY = "APPLICATION_CLASS_NAME";
    /**ActivityThread包名*/
    private static final String CLASS_NAME_ACTIVITY_THREAD = "android.app.ActivityThread";
    /**LoadedApk包名*/
    private static final String CLASS_NAME_LOADED_APK = "android.app.LoadedApk";
    /**
     * 源Apk路径
     */
    private String mSrcApkFilePath;
    /**
     * odex路径
     */
    private String mOdexPath;
    /**
     * lib路径
     */
    private String mLibPath;

    /**
     * 最先执行的方法
     */
    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        Log.d(TAG, "attachBaseContext: --------onCreate");

        try {
            // 创建payload_odex和payload_lib文件夹,payload_odex中放置源apk即源dex,payload_lib放置so文件
            File odex = this.getDir("payload_odex", MODE_PRIVATE);
            File libs = this.getDir("payload_lib", MODE_PRIVATE);
            // 用于存放源apk释放出来的dex
            mOdexPath = odex.getAbsolutePath();
            // 用于存放源apk用到的so文件
            mLibPath = libs.getAbsolutePath();
            // 用于存放解密后的apk即源apk
            mSrcApkFilePath = mOdexPath + "/payload.apk";

            File srcApkFile = new File(mSrcApkFilePath);
            Log.d(TAG, "attachBaseContext: apk size: " + srcApkFile.length());

            // 第一次加载
            if (!srcApkFile.exists()) {
                Log.d(TAG, "attachBaseContext: isFirstLoading");
                // 拿到源apk的dex文件
                byte[] dexData = this.readDexFileFromApk();
                // 取出解密后的apk放置在/payload.apk,及其so文件放置在payload_lib下
                this.splitPayLoadFromDex(dexData);
            }

            // 配置动态加载环境
            // 反射获取主线程对象,并从中获取所有已加载的package信息,找到当前LoadApk的弱引用
            // 获取主线程对象
            Object currentActivityThread = RefInvoke.invokeStaticMethod(
                    CLASS_NAME_ACTIVITY_THREAD, "currentActivityThread",
                    new Class[]{}, new Object[]{}
            );
            // 获取当前报名
            String packageName = this.getPackageName();
            // 获取已加载的所有包
            ArrayMap mPackages = (ArrayMap) RefInvoke.getFieldObject(
                    CLASS_NAME_ACTIVITY_THREAD, currentActivityThread,
                    "mPackages"
            );
            // 获取LoadApk的弱引用
            WeakReference wr = (WeakReference) mPackages.get(packageName);
            Log.d(TAG,"获取LoadApk的弱引用");
            // 创建一个新的DexClassLoader用于加载源Apk
            // 传入apk路径,dex释放路径,so路径,及父节点的DexClassLoader使其遵循双亲委托模型
            // 反射获取属性ClassLoader
            Object mClassLoader = RefInvoke.getFieldObject(
                    CLASS_NAME_LOADED_APK, wr.get(), "mClassLoader"
            );
            // 定义新的DexClassLoader对象,指定apk路径,odex路径,lib路径
            DexClassLoader dLoader = new DexClassLoader(
                    mSrcApkFilePath, mOdexPath, mLibPath, (ClassLoader) mClassLoader
            );
            // getClassLoader()等同于 (ClassLoader) RefInvoke.getFieldOjbect()
            // 但是为了替换掉父节点我们需要通过反射来获取并修改其值
            Log.d(TAG, "attachBaseContext: 父ClassLoader: " + mClassLoader);

            // 将父节点DexClassLoader替换
            RefInvoke.setFieldObject(
                    CLASS_NAME_LOADED_APK,
                    "mClassLoader",
                    wr.get(),
                    dLoader
            );

            Log.d(TAG, "attachBaseContext: 子ClassLoader: " + dLoader);

            // 测试
            try {
                // 尝试加载源apk的MainActivity
                Object actObj = dLoader.loadClass("com.example.apkreinforcement.MainActivity");
                Log.d(TAG, "attachBaseContext: SrcApk_MainActivity: " + actObj);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
                Log.d(TAG, "attachBaseContext: LoadSrcActivityErr: " + Log.getStackTraceString(e));
            }

        } catch (IOException e) {
            e.printStackTrace();
            Log.d(TAG, "attachBaseContext: error: " + Log.getStackTraceString(e));
        }

    }

    /**
     * 从DEX中分割出资源
     * @param dexData dex资源
     */
    private void splitPayLoadFromDex(byte[] dexData) throws IOException {
        // 获取dex数据长度
        int len = dexData.length;
        // 存储被加壳apk的长度
        byte[] dexLen = new byte[4];
        // 获取最后4个字节数据
        System.arraycopy(dexData, len - 4, dexLen, 0, 4);
        ByteArrayInputStream bais = new ByteArrayInputStream(dexLen);
        DataInputStream in = new DataInputStream(bais);
        // 获取被加密apk的长度
        int readInt = in.readInt();
        // 打印被加密apk的长度
        Log.d(TAG, "splitPayLoadFromDex: Integer.toHexString(readInt): " + Integer.toHexString(readInt));

        // 取出apk
        byte[] enSrcApk = new byte[readInt];
        // 将被加密apk内容复制到二进制数组中
        System.arraycopy(dexData, len - 4 - readInt, enSrcApk, 0, readInt);

        // 对源apk解密
        byte[] srcApk = decrypt(enSrcApk);
        Log.d(TAG,"原APK解密成功");

        // 写入源apk文件
        File file = new File(mSrcApkFilePath);
        try {
            if(file.createNewFile()){
                FileOutputStream fos = new FileOutputStream(file);
                fos.write(srcApk);
                fos.close();
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        // 分析源apk文件
        ZipInputStream zis = new ZipInputStream(
                new BufferedInputStream(
                        new FileInputStream(file)
                )
        );

        // 遍历压缩包
        while (true) {
            ZipEntry entry = zis.getNextEntry();
            // 判断是否有内容
            if (null == entry) {
                zis.close();
                break;
            }

            // 依次取出被加壳的apk用到的so文件,放到libPath中(data/data/包名/paytload_lib)
            String name = entry.getName();
            if (name.startsWith("lib/") && name.endsWith(".so")) {
                // 存储文件
                File storeFile = new File(
                        mLibPath + "/" + name.substring(name.lastIndexOf('/'))
                );
                storeFile.createNewFile();
                FileOutputStream fos = new FileOutputStream(storeFile);
                byte[] bytes = new byte[1024];
                while (true) {
                    int length = zis.read(bytes);
                    if (-1 == length) break;
                    fos.write(bytes);
                }
                fos.flush();
                fos.close();
            }
            zis.closeEntry();
        }
        zis.close();
    }

    /**
     * 解密二进制
     * @param srcData 二进制数
     * @return 解密后的二进制数据
     */
    private byte[] decrypt(byte[] srcData) {
        for (int i = 0; i < srcData.length; i++) {
            srcData[i] ^= 0xFF;
        }
        return srcData;
    }

    /**
     * 从新APK中获取加密APK的DEX文件
     * @return dex字节数组
     */
    private byte[] readDexFileFromApk() throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ZipInputStream zis = new ZipInputStream(
                new BufferedInputStream(
                        new FileInputStream(this.getApplicationInfo().sourceDir)
                )
        );
        // 遍历压缩包
        while (true) {
            ZipEntry entry = zis.getNextEntry();
            if (null == entry) {
                zis.close();
                break;
            }
            // 获取dex文件
            if ("classes.dex".equals(entry.getName())) {
                byte[] bytes = new byte[1024];
                while (true) {
                    int len = zis.read(bytes);
                    if (len == -1) break;
                    baos.write(bytes, 0, len);
                }
                Log.d(TAG,"classes.dex读取成功");
            }
            zis.closeEntry();
        }
        zis.close();
        return baos.toByteArray();
    }

    // 后续实现
    @RequiresApi(api = Build.VERSION_CODES.KITKAT)
    @Override
    public void onCreate() {
        super.onCreate();
        Log.d(TAG, "onCreate: ---------------");

        // 获取配置在清单文件的源apk的Application路径
        String appClassName = null;
        try {
            // 创建应用信息对象
            ApplicationInfo ai = this.getPackageManager().getApplicationInfo(this.getPackageName(), PackageManager.GET_META_DATA);
            // 获取metaData数据
            Bundle bundle = ai.metaData;
            if (null != bundle && bundle.containsKey(APP_KEY)) {
                appClassName = bundle.getString(APP_KEY);
            } else {
                Log.d(TAG, "onCreate: have no application class name");
                return;
            }
        } catch (PackageManager.NameNotFoundException e) {
            Log.d(TAG, "onCreate: error: " + Log.getStackTraceString(e));
            e.printStackTrace();
        }

        // 获取当前Activity线程
        Object currentActivityThread = RefInvoke.invokeStaticMethod(CLASS_NAME_ACTIVITY_THREAD,
                "currentActivityThread", new Class[]{}, new Object[]{});
        // 获取绑定的应用
        Object mBoundApplication = RefInvoke.getFieldObject(CLASS_NAME_ACTIVITY_THREAD,
                currentActivityThread, "mBoundApplication");
        // 获取加载apk的信息
        Object loadedApkInfo = RefInvoke.getFieldObject(
                CLASS_NAME_ACTIVITY_THREAD + "$AppBindData",
                mBoundApplication, "info"
        );
        // 将LoadedApk中的ApplicationInfo设置为null
        RefInvoke.setFieldObject(CLASS_NAME_LOADED_APK, "mApplication", loadedApkInfo, null);
        // 获取currentActivityThread中注册的Application
        Object oldApplication = RefInvoke.getFieldObject(
                CLASS_NAME_ACTIVITY_THREAD, currentActivityThread, "mInitialApplication"
        );
        // 获取ActivityThread中所有已注册的Application, 并将当前壳Apk的Application从中移除
        ArrayList<Application> mAllApplications = (ArrayList<Application>) RefInvoke.getFieldObject(
                CLASS_NAME_ACTIVITY_THREAD, currentActivityThread, "mAllApplications"
        );
        mAllApplications.remove(oldApplication);
        // 从loadedApk中获取应用信息
        ApplicationInfo appInfoInLoadedApk = (ApplicationInfo) RefInvoke.getFieldObject(
                CLASS_NAME_LOADED_APK, loadedApkInfo, "mApplicationInfo"
        );
        // 从AppBindData中获取应用信息
        ApplicationInfo appInfoInAppBindData = (ApplicationInfo) RefInvoke.getFieldObject(
                CLASS_NAME_ACTIVITY_THREAD + "$AppBindData", mBoundApplication, "appInfo"
        );
        // 替换原来的Application
        appInfoInLoadedApk.className = appClassName;
        appInfoInAppBindData.className = appClassName;

        // 注册Application
        Application app = (Application) RefInvoke.invokeMethod(
                CLASS_NAME_LOADED_APK, "makeApplication", loadedApkInfo,
                new Class[]{boolean.class, Instrumentation.class},
                new Object[]{false, null}
        );
        // 替换ActivityThread中的Application
        RefInvoke.setFieldObject(CLASS_NAME_ACTIVITY_THREAD, "mInitialApplication",
                currentActivityThread, app);
        ArrayMap mProviderMap = (ArrayMap) RefInvoke.getFieldObject(
                CLASS_NAME_ACTIVITY_THREAD, currentActivityThread, "mProviderMap"
        );

        // 遍历
        for (Object providerClientRecord : mProviderMap.values()) {
            Object localProvider = RefInvoke.getFieldObject(
                    CLASS_NAME_ACTIVITY_THREAD + "$ProviderClientRecord",
                    providerClientRecord, "mLocalProvider"
            );
            RefInvoke.setFieldObject("android.content.ContentProvider", "mContext",
                    localProvider, app);
        }

        Log.d(TAG, "onCreate: SrcApp: " + app);
        // 调用新的Application
        app.onCreate();
    }

}

至此脱壳目录如下
在这里插入图片描述

( 4 )新 DEX

使用加密和拼接工具(函数)对源 APK 进行加密、拼接到脱壳 DEX,得到新DEX
这一步开始,实际就是测试部分。

1、打包源 APK,这里是 helloWorld.apk。
在这里插入图片描述

2、打包脱壳 APK,这里是 默认.apk,使用解压软件打开,将其中的 classes.dex,即脱壳 DEX,拷贝出来:
在这里插入图片描述
在这里插入图片描述
运行加密和拼接函数,即 MyClass.main(),此时会生成 classesShell.dex:

在这里插入图片描述

( 5 )新 APK

对新 DEX 打包,生成新 APK
使用新 DEX 替换掉apk 里的 classes.dex
在这里插入图片描述
为 apk 被修改,所以要对其重新签名。同样打开新 APK,将其 METAIMF 文件夹删除,然后使用 jarsigner 重新签名:

jarsigner -verbose -keystore IS.keystore xx.apk IS1601

弹出日志
在这里插入图片描述
可见已经加载出了源 APK 内容。
在这里插入图片描述
该源 APK 可以直接安装运行,前提是给予相关权限。
再观察一下 AndroidKiller 逆向情况,并无 MainActivity,源 APK 内容隐藏
成功
在这里插入图片描述

标签:dex,APK,apk,import,new,byte,加壳
来源: https://blog.csdn.net/Cloud_Player/article/details/118018442

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有