美文网首页
Android最初的加固

Android最初的加固

作者: Sharkchilli | 来源:发表于2020-07-07 16:39 被阅读0次

前言

这次我们对Android的一些基本加固做一个探讨。本文将设计加固和Android的apk动态加载,我们重点放在加固上。有时间可以以后对apk动态加载做探讨。

原理解析

image.png

加壳过程需要三个项目:
1.需要加密的源APK:TargetApk
2.壳程序Apk(负责解密Apk工作):UnDexApk
3.加壳项目(用于加密TargetApk):DexTools

主要加壳过程:
得到解壳apk的dex文件,使用加密算法对源apk进行加密。将源apk合并到解壳apk的dex后面。最后将解壳apk的dex替换为合并后的dex文件就完成了。壳程序在运行时会将dex文件中的apk重新解密并加载起来。这样就完成了一个简单的加固。

Dex
因为涉及到dex的合并所有我们必须先了解一下DEX文件格式,以下有一篇对DEX文件格式介绍的文章我这里就不再赘述
Dex文件格式详解

这里主要注意一下Dex文件头部信息,因为这里需要修改三个地方

image.png

上图中红色标记的部分就是需要修改的地方.

  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结构如下


image.png

最后来看一下代码

代码

TargetApk
这里我们需要一个Application类(为了验证Application的动态加载)

TargetApplication.java

package com.shark.app;

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

public class TargetApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        Log.i("TargetApplication", "source apk onCreate2:" + this);
    }
}

AndroidManifest.xml

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

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:name="com.shark.app.TargetApplication"
        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>

MainActivity.java

package com.shark.targetapk;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.util.Log;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.i("MainActivity","onCreate Ok!");
        setContentView(R.layout.activity_main);
    }
}

源apk非常简单就是在Application和MainActivity中打印了日志,以便我们后续观察

DexTools
其项目结构如下

image.png
DexTools.java
package com.shark.demo;

/**
 * 加壳程序
 */


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

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;

import com.shark.tools.SharkTool;

public class DexTools {

    /**
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        try {
    
            File payloadSrcFile = new File("force/app-debug.apk");   //需要加壳的程序
            File unDecryptDexFile = new File("force/Decrypt.dex");  //解壳dex
            byte[] payloadArray = encrptData(readFileContent(payloadSrcFile));//加密OriginalAPK.apk
            byte[] unDecryptDexArray = readFileContent(unDecryptDexFile);//以二进制形式读出dex
            int payloadLen = payloadArray.length;
            int unDecryptDexLen = unDecryptDexArray.length;
            int totalLen = payloadLen + unDecryptDexLen +4;//4字节存放长度。
            byte[] newdex = new byte[totalLen]; // 申请了新的长度
            //添加解壳代码 public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
            System.arraycopy(unDecryptDexArray, 0, newdex, 0, unDecryptDexLen);//先拷贝Decryptdex内容
            //添加加密后的解壳数据
            System.out.println("unDecryptDexLen:"+unDecryptDexLen);
            System.out.println("payloadLen:"+payloadLen);
            System.arraycopy(payloadArray, 0, newdex, unDecryptDexLen, payloadLen);//再在dex内容后面拷贝加密之后的Original apk的内容
            //添加解壳数据长度到newdex最后4个字节
            System.arraycopy(intToByte(payloadLen), 0, newdex, totalLen-4, 4);//最后4个字节为长度playload长度
            //修改DEX file size文件头
            updateFileSizeHeader(newdex);//dex中32到35的位置为文件长度
            //修改DEX SHA1 文件头
            updateSHA1Header(newdex);//dex中12到31位置,32到结束参与SHA1计算
            //修改DEX CheckSum文件头
            updateCheckSumHeader(newdex);//dex中8到11位置,12到文件结束计算checksum

            String str = "force/classes.dex";
            File file = new File(str);
            if (!file.exists()) {
                file.createNewFile();
            }
            
            FileOutputStream fileOutputStream = new FileOutputStream(str);
            fileOutputStream.write(newdex);
            fileOutputStream.flush();
            fileOutputStream.close();
            System.out.println("done!");


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

        /**
         * 修改dex头,CheckSum 校验码
         * @param dexBytes
         */
        private static void updateCheckSumHeader(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);
            
            for (int i = 0; i < 2; i++) {
                byte tmp = newcs[i];
                newcs[i]= newcs[newcs.length-1-i];
                newcs[newcs.length-1-i]=tmp;
            }
            
            System.arraycopy(newcs, 0, dexBytes, 8, 4);//效验码赋值(8-11)
        
        }

        /**
         * 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 updateSHA1Header(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)
        }

        /**
         * 修改dex头 file_size值
         * @param dexBytes
         */
        private static void updateFileSizeHeader(byte[] dexBytes) {
            //新文件长度
            byte[] newfs = intToByte(dexBytes.length);
        
            //高位低位交换
            for (int i = 0; i < 2; i++) {
                byte tmp = newfs[i];
                newfs[i]=newfs[newfs.length-1-i];
                newfs[newfs.length-1-i]=tmp;
                
            }
            System.arraycopy(newfs, 0, dexBytes, 32, 4);//修改(32-35)
        }


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

}

加壳程序其实就是一个Java工程,因为我们从上面的分析可以看到,他的工作就是加密源Apk,然后将其写入到脱壳Dex文件中,修改文件头,得到一个新的Dex文件即可。其他的看代码和注释应该都能看懂,就是按照上面的加壳理论去做的.

UnDexApk

脱壳项目的工作:
1.通过反射置换android.app.ActivityThread 中的mClassLoader为加载解密出APK的DexClassLoader,该DexClassLoader一方面加载了源程序、另一方面以原mClassLoader为父节点,这就保证了即加载了源程序又没有放弃原先加载的资源与系统代码。

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

整体的流程图:


image.png

ProxyApplication.java

package com.shark.application;

import android.app.Application;

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.Field;
import java.lang.reflect.InvocationTargetException;
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 com.shark.com.shark.tools.RefInvoke;

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;
    private String libFilePath;

    @Override
    public void onCreate() {
        super.onCreate();
        // 如果源应用配置有Appliction对象,则替换为源应用Applicaiton,以便不影响源程序逻辑。
        String appClassName = null;
        try {
            //得到以下信息
            //meta-data android:name="APPLICATION_CLASS_NAME" android:value="com.shark.app.TargetApplication"/>
            ApplicationInfo ai = this.getPackageManager()
                    .getApplicationInfo(this.getPackageName(),
                            PackageManager.GET_META_DATA);
            Bundle bundle = ai.metaData;
            if (bundle != null && bundle.containsKey("APPLICATION_CLASS_NAME")) {
                //源apk的Application
                appClassName = bundle.getString("APPLICATION_CLASS_NAME");//className 是配置在xml文件中的 mfapplication。
            } 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 = invokeStaticMethod(
                "android.app.ActivityThread", "currentActivityThread",
                new Class[]{}, new Object[]{});
        Object mBoundApplication = getFieldOjbect(
                "android.app.ActivityThread", currentActivityThread,
                "mBoundApplication");
        Object loadedApkInfo = getFieldOjbect(
                "android.app.ActivityThread$AppBindData",
                mBoundApplication, "info");
        //把当前进程的mApplication 设置成了null
        setFieldOjbect("android.app.LoadedApk", "mApplication",
                loadedApkInfo, null);
        //得到久oldApplication
        Object oldApplication = getFieldOjbect(
                "android.app.ActivityThread", currentActivityThread,
                "mInitialApplication");
        //http://www.codeceo.com/article/android-context.html
        ArrayList<Application> mAllApplications = (ArrayList<Application>) getFieldOjbect("android.app.ActivityThread",
                currentActivityThread, "mAllApplications");
        mAllApplications.remove(oldApplication);//删除oldApplication

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


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

    //先于onCreate执行
    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        try {
            //创建两个文件夹payload_odex,payload_lib 私有的,可写的文件目录
            ///data/data/com.shark.undexapk/app_payload_odex
            ///data/data/com.shark.undexapk/app_payload_lib
            File odex = this.getDir("payload_odex", MODE_PRIVATE);
            File libs = this.getDir("payload_lib", MODE_PRIVATE);
            odexPath = odex.getAbsolutePath();
            libPath = libs.getAbsolutePath();

            // 创建源apk
            apkFileName = odex.getAbsolutePath() + "/payload.apk";
            File apkFile = new File(apkFileName);

            if (!apkFile.exists()) {
                apkFile.createNewFile();  //在payload_odex文件夹内,创建payload.apk
                // 读取程序classes.dex文件
                byte[] dexdata = this.readDexFileFromApk();//加壳后的classes.dex文件,里面包含originalAPK文件

                // 分离出解壳后的apk文件已用于动态加载
                this.splitPayLoadFromDex(dexdata);//从加壳后的classes.dex文件里释放original apk

            }

            //到此,Original APK已经解密,so等文件也保存了。
            // 配置动态加载环境

            Object currentActivityThread = invokeStaticMethod(
                    "android.app.ActivityThread", "currentActivityThread",
                    new Class[]{}, new Object[]{});//获取主线程对象 http://blog.csdn.net/myarrow/article/details/14223493
            String packageName = this.getPackageName();//当前apk的包名

            //TODO 这里应该是得到所有的包名 可以打印其key值观察
            ArrayMap mPackages = (ArrayMap) 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) getFieldOjbect(
                    "android.app.LoadedApk", wr.get(), "mClassLoader"));

            //把当前进程的DexClassLoader 设置成了被加壳apk的DexClassLoader
            setFieldOjbect("android.app.LoadedApk", "mClassLoader",
                    wr.get(), dLoader);

            try {
                //加载园apk的MainActivity文件
                Object actObj = dLoader.loadClass("com.shark.targetapk.MainActivity");

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

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

    /**
     * 释放被加壳的apk文件,so文件
     *
     * @throws IOException
     */
    private void splitPayLoadFromDex(byte[] decryptdexdata) throws IOException {//从classes.dex中拿到original APK文件,并解密
        int decryptdexlen = decryptdexdata.length;
        //取被加壳apk的长度   这里的长度取值,对应加壳时长度的赋值都可以做些简化
        byte[] orignalapklen = new byte[4];
        System.arraycopy(decryptdexdata, decryptdexlen - 4, orignalapklen, 0, 4);//最后4个字节是加密后的Original apk的长度
        ByteArrayInputStream bais = new ByteArrayInputStream(orignalapklen);
        DataInputStream in = new DataInputStream(bais);
        int readInt = in.readInt();

        byte[] originalAPK = new byte[readInt];
        //把被加壳apk内容拷贝到originalAPK中
        System.arraycopy(decryptdexdata, decryptdexlen - 4 - readInt, originalAPK, 0, readInt);

        //对源程序Apk进行解密
        originalAPK = decryptData(originalAPK);

        //写入apk文件/data/data/com.shark.undexapk/app_payload_odex/payload.apk 即解密后的Original APK
        File file = new File(apkFileName);
        try {
            FileOutputStream localFileOutputStream = new FileOutputStream(file);
            localFileOutputStream.write(originalAPK);
            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/包名/app_payload_lib)
            String name = localZipEntry.getName();
            //如果是以lib/开头并且以.so结尾则需要将送拷贝进外面
            if (name.startsWith("lib/") && name.endsWith(".so")) {
                File storeFile = new File(libPath + "/"
                        + name.substring(name.lastIndexOf('/')));
                //创建so文件
                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();

        //getApplicationInfo().dataDir: 分配给此Application的存放数据的位置。通常是:/data/data/packageName/
        //getApplicationInfo().SourceDir: 安装这个包后的存放位置。 因为APK安装后,archive文件存放在某个目录(一般程序和//root程序位置不同)。
        // 作为读取资源是的位置。此位置通常在/data/app/pakcageName-1/base.apk
        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")) {//找到classes.dex,里面包含着加密的original apk
                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[] decryptData(byte[] srcdata) {
        for (int i = 0; i < srcdata.length; i++) {
            srcdata[i] = (byte) (~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;
    }

    /**
     * 反射执行类的静态函数(public)
     *
     * @param class_name  类名
     * @param method_name 函数名
     * @param pareTyple   函数的参数类型
     * @param pareVaules  调用函数时传入的参数
     * @return
     */
    public static Object invokeStaticMethod(String class_name, String method_name, Class[] pareTyple, Object[] pareVaules) {

        try {
            Class obj_class = Class.forName(class_name);
            Method method = obj_class.getMethod(method_name, pareTyple);
            return method.invoke(null, pareVaules);
        } catch (SecurityException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return null;

    }

    /**
     * 反射执行类的函数(public)
     *
     * @param class_name
     * @param method_name
     * @param obj
     * @param pareTyple
     * @param pareVaules
     * @return
     */
    public static Object invokeMethod(String class_name, String method_name, Object obj, Class[] pareTyple, Object[] pareVaules) {

        try {
            Class obj_class = Class.forName(class_name);
            Method method = obj_class.getMethod(method_name, pareTyple);
            return method.invoke(obj, pareVaules);
        } catch (SecurityException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return null;

    }

    /**
     * 反射得到类的属性(包括私有和保护)
     *
     * @param class_name
     * @param obj
     * @param filedName
     * @return
     */
    public static Object getFieldOjbect(String class_name, Object obj, String filedName) {
        try {
            Class obj_class = Class.forName(class_name);
            Field field = obj_class.getDeclaredField(filedName);
            field.setAccessible(true);
            return field.get(obj);
        } catch (SecurityException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return null;

    }

    /**
     * 反射得到类的静态属性(包括私有和保护)
     *
     * @param class_name
     * @param filedName
     * @return
     */
    public static Object getStaticFieldOjbect(String class_name, String filedName) {

        try {
            Class obj_class = Class.forName(class_name);
            Field field = obj_class.getDeclaredField(filedName);
            field.setAccessible(true);
            return field.get(null);
        } catch (SecurityException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return null;

    }

    /**
     * 设置类的属性(包括私有和保护)
     *
     * @param classname
     * @param filedName
     * @param obj
     * @param filedVaule
     */
    public static void setFieldOjbect(String classname, String filedName, Object obj, Object filedVaule) {
        try {
            Class obj_class = Class.forName(classname);
            Field field = obj_class.getDeclaredField(filedName);
            field.setAccessible(true);
            field.set(obj, filedVaule);
        } catch (SecurityException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    /**
     * 设置类的静态属性(包括私有和保护)
     *
     * @param class_name
     * @param filedName
     * @param filedVaule
     */
    public static void setStaticOjbect(String class_name, String filedName, Object filedVaule) {
        try {
            Class obj_class = Class.forName(class_name);
            Field field = obj_class.getDeclaredField(filedName);
            field.setAccessible(true);
            field.set(null, filedVaule);
        } catch (SecurityException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

MainActivity.java

package com.shark.undexapk;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

AndroidManifest.xml

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

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:name="com.shark.application.ProxyApplication"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <meta-data android:name="APPLICATION_CLASS_NAME" android:value="com.shark.app.TargetApplication"/>
        <activity android:name="com.shark.targetapk.MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

</manifest>

运行

1.现将源程序apk和加壳程序的dex文件拷贝到DexTools项目中,运行项目得到合并后的dex文件


image.png

2.将新的classes.dex拷贝覆盖到壳程序的dex,并且删除签名文件后重新签名


image.png
3.将重新签名的apk丢进虚拟机中,其打印日志如下:
image.png

可以看出壳程序正常解壳,执行了Application和Activity

尾言

其实了解dex文件格式的话这个加固还是非常简单的,关键是动态加载dex,执行Application和Activity这个我们可以下一次做分析,还有这里没有对资源文件进行加壳,资源文件都需要放在壳程序中。

相关文章

  • Android最初的加固

    前言 这次我们对Android的一些基本加固做一个探讨。本文将设计加固和Android的apk动态加载,我们重点放...

  • Android加固方案 之 类方法抽取指令

    前言 以前我们介绍了加密dex文件的加固方案Android最初的加固[https://www.jianshu.co...

  • Android 数据安全

    代码混淆 & apk的加固 常用加固平台:爱加密、360加固、腾讯加固、梆梆加固 Android应用加固原理 网络...

  • Android加壳脱壳

    Android 加固与脱壳 加固与脱壳常用加固 so 文件特征分析 apk 加固动态调试分析 apk 加固-环境搭...

  • frida 得到.init .init_array等函数地址

    前言 移动端Android安全的发展,催生了各种Android加固的诞生,基于ELF文件的特性,很多的加固厂商在进...

  • Android 发版的小工具

    Android加固包签名 我们知道自己的apk在上传市场的时候, 为了更好的包含我们的代码需要加固服务, 加固后的...

  • AndroidLinker与SO加壳技术之上篇

    1. 前言 Android 系统安全愈发重要,像传统pc安全的可执行文件加固一样,应用加固是Android系统安全...

  • android apk 加壳原理与实现

    请参考Android中的Apk的加固(加壳)原理解析和实现Android中的Apk加固原理解析和实现第2篇也是参考...

  • Android逆向编程

    一、加固 APP应用加固 | 几维安全 联系我们--安天 智者安天下 你不知道的Android SDK安全测试 -...

  • 基于御安全APK加固的游戏反外挂方案

    APK加固,Android开发,APP保护,游戏反外挂,应用安全加固 摘要:兴起的手游市场背后隐藏着严峻外挂问题,...

网友评论

      本文标题:Android最初的加固

      本文链接:https://www.haomeiwen.com/subject/jpqgqktx.html