美文网首页
加壳VS脱壳(第一代)

加壳VS脱壳(第一代)

作者: M_天河 | 来源:发表于2020-09-01 09:47 被阅读0次

    原理:
    首先有下面三个工程
    1、源程序项目(需要加密的Apk)
    2、脱壳项目(解密源Apk和加载Apk)
    3、对源Apk进行加密和脱壳项目的Dex的合并
    以byte数组格式读取源apk,并按自己设计的算法进行加密,然后直接拼接到脱壳apk的dex文件后面,然后按照标准dex文件的格式对拼接后的dex进行修改文件头中的文件大小位、SHA-1校验位、sum校验位,

    1. checksum:文件校验码 ,使用alder32 算法校验文件除去 maigc ,checksum 外余下的所有文件区域 ,用于检查文件错误 。
    2. signature:使用 SHA-1 算法 hash 除去 magic ,checksum 和 signature 外余下的所有文件区域 ,用于唯一识别本文件 。
    3. file_size:Dex 文件的大小

    然后在脱壳apk中重写Application方法,在方法运行之初去读取dex文件按照指定拼接位置读取出加密apk文件,然后解密出源apk文件,再通过动态加载技术将当前apk的dexClassLoader替换为源apk的dexClassLoader,

    来看下具体代码:

    一,源apk(随便写一个应用)

    应用只有一个MainActivity和MyApplication



    AndroidManifest.xml,这里没什么需要注意的,正常来写就行了

    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.smm.forceapkobj">
    
        <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"
            android:name="com.smm.forceapkobj.MyApplication">
            <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,也没啥需要注意的,需要的话打个Log就行了

    package com.smm.forceapkobj;
    
    import android.support.v7.app.AppCompatActivity;
    import android.app.Activity;
    import android.content.Intent;
    import android.os.Bundle;
    import android.util.Log;
    import android.view.View;
    import android.view.View.OnClickListener;
    import android.widget.TextView;
    import android.widget.Toast;
    
    public class MainActivity extends Activity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            Log.i("测试记录","进入到了源程序MainActivityOnCreate");
            super.onCreate(savedInstanceState);
    
            TextView content = new TextView(this);
            content.setText("I am Source Apk");
            content.setOnClickListener(new OnClickListener(){
                @Override
                public void onClick(View arg0){
                    Toast myToast = Toast.makeText(getApplicationContext(),"this is MainActivity!!!",Toast.LENGTH_SHORT);
                    myToast.show();
                }
            });
            setContentView(content);
        }
    }
    

    MyApplication.java

    package com.smm.forceapkobj;
    
    import android.app.Application;
    import android.util.Log;
    
    public class MyApplication extends Application{
    
        @Override
        public void onCreate(){
            Log.i("测试记录","进入到了源程序Application");
            super.onCreate();
            Log.i("测试记录","source apk onCreate:" + this);
        }
    }
    
    

    二,编写脱壳程序

    结构也很简单,主要这里不要有其他组件,如果有MainActivity的话执行完了源程序的Application之后就会执行这里的MainActivity。


    AndroidManifest.xml,这里除了要注册ProxyApplication之外还要注册源程序中的组件,另外还要用<meta-data>标签获取源程序中的Application对象。
    标签如下:<meta-data android:name="APPLICATION_CLASS_NAME" android:value="com.smm.forceapkobj.MyApplication"/>,后面会在ProxyApplication的onCreate代码中看到具体获取方式。
    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.smm.reforceapk">
    
        <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"
            android:name="com.smm.reforceapk.ProxyApplication">
            <meta-data android:name="APPLICATION_CLASS_NAME" android:value="com.smm.forceapkobj.MyApplication"/>
            <activity android:name="com.smm.forceapkobj.MainActivity">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
    
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
        </application>
    
    </manifest>
    

    ProxyApplication.java

    package com.smm.reforceapk;
    
    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.os.Debug;
    import android.util.ArrayMap;
    import android.util.Log;
    import dalvik.system.DexClassLoader;
    
    import java.io.BufferedInputStream;
    import java.io.ByteArrayInputStream;
    import java.io.ByteArrayOutputStream;
    import java.io.DataInputStream;
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.lang.ref.WeakReference;
    import java.lang.reflect.Method;
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.Iterator;
    import java.util.zip.ZipEntry;
    import java.util.zip.ZipInputStream;
    
    public class ProxyApplication extends Application{
        private static final String appkey = "APPLICATION_CLASS_NAME";
        private String apkFileName;
        private String odexPath;
        private String libPath;
    
        @Override
        protected void attachBaseContext(Context base){
            //Debug.waitForDebugger();
            super.attachBaseContext(base);
            try{
                //从dex中读取apk并解密,还原出源apk,payload.apk
                File odex = this.getDir("payload_odex",MODE_PRIVATE);
                File libs = this.getDir("payload_lib",MODE_PRIVATE);
                odexPath = odex.getAbsolutePath();
                libPath = libs.getAbsolutePath();
                apkFileName = odex.getAbsolutePath() + "/payload.apk";
                File dexFile = new File(apkFileName);
                Log.i("demo","apk size:" + dexFile.length());
                if(!dexFile.exists()) {
                    dexFile.createNewFile();
                    byte[] dexdata = this.readDexFileFromApk();
                    this.splitPayLoadFromDex(dexdata);
                }
                //配置动态加载环境
                //获取主线程对象ActivityTread
                Object currentActivityTread = RefInvoke.invokeStaticMethod("android.app.ActivityThread","currentActivityThread",new Class[]{},new Object[]{});
                String packageName = this.getPackageName();
                //当前app的包名传递给mPachages来获得当前app的弱引用,从而找到当前app的类加载器mClassLoader
                ArrayMap mPackages = (ArrayMap)RefInvoke.getFieldObject("android.app.ActivityThread",currentActivityTread,"mPackages");
                WeakReference wr = (WeakReference)mPackages.get(packageName);
                //创建被加壳apk的DexClassLoader对象,加载apk内的类和native代码
                DexClassLoader dLoader = new DexClassLoader(apkFileName,odexPath,libPath,
                        (ClassLoader)RefInvoke.getFieldObject("android.app.LoadedApk",wr.get(),"mClassLoader"));
                //把当前进程的DexClassLoader设置成被加壳apk的DexClassLoader
                RefInvoke.setFieldObject("android.app.LoadedApk","mClassLoader",wr.get(),dLoader);
                Log.i("demo","classloader:" + dLoader);
            }catch (Exception e){
                Log.i("demo","error:" + Log.getStackTraceString(e));
                e.printStackTrace();
            }
        }
    
        @Override
        public void onCreate(){
            String appClassName = null;
            try{
                //获取am文件中的meta标签,通过检测是否包含```APPLICATION_CLASS_NAME```字符来判断是否获取源apk的Application对象
                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");
                }else{
                    Log.i("demo","have no application class name");
                    return;
                }
            }catch(NameNotFoundException e){
                Log.i("demo","error:" + Log.getStackTraceString(e));
                e.printStackTrace();
            }
            //调用该Application
            Object currentActivityThread = RefInvoke.invokeStaticMethod("android.app.ActivityThread","currentActivityThread",
                    new Class[]{},new Object[]{});
            Object mBoundApplication = RefInvoke.getFieldObject("android.app.ActivityThread",currentActivityThread,"mBoundApplication");
            Object loadedApkInfo = RefInvoke.getFieldObject("android.app.ActivityThread$AppBindData",mBoundApplication,"info");
            //把当前进程的Application设置为null
            RefInvoke.setFieldObject("android.app.LoadedApk","mApplication",loadedApkInfo,null);
            Object oldApplication = RefInvoke.getFieldObject("android.app.ActivityThread",currentActivityThread,"mInitialApplication");
            ArrayList<Application> mAllApplications = (ArrayList<Application>)RefInvoke.getFieldObject("android.app.ActivityThread",
                    currentActivityThread,"mAllApplications");
            mAllApplications.remove(oldApplication);
    
            ApplicationInfo appinfo_In_LoadedApk = (ApplicationInfo)RefInvoke.getFieldObject("android.app.LoadedApk",loadedApkInfo,"mApplicationInfo");
            ApplicationInfo appinfo_In_AppBindData = (ApplicationInfo)RefInvoke.getFieldObject("android.app.ActivityThread$AppBindData",
                    mBoundApplication,"appInfo");
            //将源apk的application对象名赋给LoadedApk
            appinfo_In_LoadedApk.className = appClassName;
            appinfo_In_AppBindData.className = appClassName;
            Application app = (Application)RefInvoke.invokeMethod("android.app.LoadedApk","makeApplication",loadedApkInfo,
                    new Class[]{boolean.class,Instrumentation.class},
                    new Object[]{false,null});//执行makeApplication(false,null)
            RefInvoke.setFieldObject("android.app.ActivityThread","mInitialApplication",currentActivityThread,app);
            ArrayMap mProviderMap = (ArrayMap)RefInvoke.getFieldObject("android.app.ActivityThread",currentActivityThread,"mProviderMap");
            Iterator it = mProviderMap.values().iterator();
            while(it.hasNext()){
                Object providerClientRecord = it.next();
                Object localProvider = RefInvoke.getFieldObject("android.app.ActivityThread$ProviderClientRecord",
                        providerClientRecord,"mLocalProvider");
                RefInvoke.setFieldObject("android.content.ContentProvider","mContext",localProvider,app);
            }
            Log.i("demo","app:" + app);
            app.onCreate();
        }
    
        private byte[] readDexFileFromApk() throws IOException{
            ByteArrayOutputStream dexByteArrayOutputStream = new ByteArrayOutputStream();
            ZipInputStream localZipInputStream = new ZipInputStream(
                    new BufferedInputStream(new FileInputStream(
                            this.getApplicationInfo().sourceDir
                    ))
            );
            while(true){
                ZipEntry localZipEntry = localZipInputStream.getNextEntry();
                if(localZipEntry == null){
                    localZipInputStream.close();
                    break;
                }
                if(localZipEntry.getName().equals("classes.dex")){
                    byte[] arrayOfByte = new byte[1024];
                    while(true){
                        int i = localZipInputStream.read(arrayOfByte);
                        if(i == -1)
                            break;
                        dexByteArrayOutputStream.write(arrayOfByte,0,i);
                    }
                }
                localZipInputStream.closeEntry();
            }
            localZipInputStream.close();
            return dexByteArrayOutputStream.toByteArray();
        }
    
        private void splitPayLoadFromDex(byte[] apkdata)throws IOException{
            int ablen = apkdata.length;
            byte[] dexlen = new byte[4];
            System.arraycopy(apkdata,ablen - 4, dexlen,0,4);
            ByteArrayInputStream bais = new ByteArrayInputStream(dexlen);
            DataInputStream in = new DataInputStream(bais);
            int readInt = in.readInt();
            System.out.println(Integer.toHexString(readInt));
            byte[] newdex = new byte[readInt];
            System.arraycopy(apkdata,ablen - 4 - readInt,newdex,0,readInt);
    
            newdex = decrypt(newdex);
            //写入apk文件
            File file = new File(apkFileName);
            try{
                FileOutputStream localFileOutputStream = new FileOutputStream(file);
                localFileOutputStream.write(newdex);
                localFileOutputStream.close();
            }catch(IOException localIOException){
                throw new RuntimeException(localIOException);
            }
    
            //分析被加壳的apk文件
            ZipInputStream localZipInputStream = new ZipInputStream(
                    new BufferedInputStream(new FileInputStream(file))
            );
            while(true){
                ZipEntry localZipEntry = localZipInputStream.getNextEntry();
                if(localZipEntry == null){
                    localZipInputStream.close();
                    break;
                }
                String name = localZipEntry.getName();
                if(name.startsWith("lib/") && name.endsWith(".so")){
                    File storeFile = new File(libPath + "/" + name.substring(name.lastIndexOf("/")));
                    storeFile.createNewFile();
                    FileOutputStream fos = new FileOutputStream(storeFile);
                    byte[] arrayOfByte = new byte[1024];
                    while(true){
                        int i = localZipInputStream.read(arrayOfByte);
                        if(i == -1)
                            break;
                        fos.write(arrayOfByte,0,i);
                    }
                    fos.flush();
                    fos.close();
                }
                localZipInputStream.closeEntry();
            }
            localZipInputStream.close();
        }
    
        private byte[] decrypt(byte[] srcdata){
            for(int i = 0;i < srcdata.length;i++){
                srcdata[i] = (byte)(0xFF ^srcdata[i]);
            }
            return srcdata;
        }
    }
    

    RefInvoke.java,反射类,主要通过java.lang.reflect.Methodinvoke方法定义了几类调用和赋值方放。

    package com.smm.reforceapk;
    
    import java.lang.reflect.Field;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    
    public class RefInvoke {
        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){
                e.printStackTrace();
            }catch(IllegalArgumentException e){
                e.printStackTrace();
            }catch(IllegalAccessException e){
                e.printStackTrace();
            }catch(NoSuchMethodException e){
                e.printStackTrace();
            }catch(InvocationTargetException e){
                e.printStackTrace();
            }catch(ClassNotFoundException e){
                e.printStackTrace();
            }
            return null;
        }
    
        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){
                e.printStackTrace();
            }catch(IllegalArgumentException e){
                e.printStackTrace();
            }catch(IllegalAccessException e){
                e.printStackTrace();
            }catch(NoSuchMethodException e){
                e.printStackTrace();
            }catch(InvocationTargetException e){
                e.printStackTrace();
            }catch(ClassNotFoundException e){
                e.printStackTrace();
            }
            return null;
        }
    
        public static Object getFieldObject(String class_name,Object obj,String fieldName){
            try{
                Class obj_class = Class.forName(class_name);
                Field field = obj_class.getDeclaredField(fieldName);
                field.setAccessible(true);
                return field.get(obj);
            }catch(SecurityException e){
                e.printStackTrace();
            }catch(IllegalArgumentException e){
                e.printStackTrace();
            }catch(NoSuchFieldException e){
                e.printStackTrace();
            }catch(IllegalAccessException e){
                e.printStackTrace();
            }catch(ClassNotFoundException e){
                e.printStackTrace();
            }
            return null;
        }
    
        public static Object getStaticFileObject(String class_name,String fieldName){
            try{
                Class obj_class = Class.forName(class_name);
                Field field = obj_class.getDeclaredField(fieldName);
                field.setAccessible(true);
                return field.get(null);
            }catch(SecurityException e){
                e.printStackTrace();
            }catch(NoSuchFieldException e){
                e.printStackTrace();
            }catch(IllegalArgumentException e){
                e.printStackTrace();
            }catch(IllegalAccessException e){
                e.printStackTrace();
            }catch(ClassNotFoundException e){
                e.printStackTrace();
            }
            return null;
        }
    
        public static void setFieldObject(String classname,String fieldName,Object obj,Object fieldVaule){
            try{
                Class obj_class = Class.forName(classname);
                Field field = obj_class.getDeclaredField(fieldName);
                field.setAccessible(true);
                field.set(obj,fieldVaule);
            }catch(SecurityException e){
                e.printStackTrace();
            }catch(NoSuchFieldException e){
                e.printStackTrace();
            }catch(IllegalArgumentException e){
                e.printStackTrace();
            }catch(IllegalAccessException e){
                e.printStackTrace();
            }catch(ClassNotFoundException e){
                e.printStackTrace();
            }
        }
    
        public static void setStaticObject(String class_name,String fieldName,Object fieldVaule){
            try{
                Class obj_class = Class.forName(class_name);
                Field field = obj_class.getDeclaredField(fieldName);
                field.setAccessible(true);
                field.set(null,fieldVaule);
            }catch(SecurityException e){
                e.printStackTrace();
            }catch(NoSuchFieldException e){
                e.printStackTrace();
            }catch(IllegalArgumentException e){
                e.printStackTrace();
            }catch(IllegalAccessException e){
                e.printStackTrace();
            }catch(ClassNotFoundException e){
                e.printStackTrace();
            }
        }
    }
    

    然后通过buildAPK编译应用,注意此时编译出的apk是不能运行的。

    三,加壳程序

    加壳程序是个java工程,原理很简单如下:

    package shellApk;
    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 DexShellTool {
        public static void main(String[] args) {
            try {
                File payloadSrcFile = new File("D:\\shellProject\\p1\\force.apk");
                System.out.println("apk size:" + payloadSrcFile.length());
                File unShellDexFile = new File("D:\\shellProject\\p1\\reforce.dex");
                byte[] payloadArray = encrypt(readFileBytes(payloadSrcFile));
                byte[] unShellDexArray = readFileBytes(unShellDexFile);
                int payloadLen = payloadArray.length;
                System.out.println("payloadLen:" + payloadLen);
                int unShellDexLen = unShellDexArray.length;
                System.out.println("unShellLen:" + unShellDexLen);
                int totalLen = payloadLen + unShellDexLen + 4;
                byte[] newdex = new byte[totalLen];
                
                System.arraycopy(unShellDexArray,0,newdex,0,unShellDexLen);
                System.arraycopy(payloadArray, 0, newdex, unShellDexLen, payloadLen);
                System.arraycopy(intToByte(payloadLen), 0, newdex, totalLen - 4, 4);
                
                fixFileSizeHeader(newdex);
                fixFileSHA1Header(newdex);
                fixCheckSumHeader(newdex);
                
                String str = "D:\\shellProject\\p1\\classes.dex";
                File file = new File(str);
                if(!file.exists()) {
                    file.createNewFile();
                }
                
                FileOutputStream localFileOutputStream = new FileOutputStream(str);
                localFileOutputStream.write(newdex);
                localFileOutputStream.flush();
                localFileOutputStream.close();
            }catch(Exception e) {
                e.printStackTrace();
            }
        }
        
        private static void fixFileSizeHeader(byte[] dexBytes) {
            byte[] newfs = intToByte(dexBytes.length);
            System.out.println(Integer.toHexString(dexBytes.length));
            byte[] refs = new byte[4];
            for(int i = 0;i < 4;i++) {
                refs[i] = newfs[newfs.length - 1 - i];
                System.out.println(Integer.toHexString(newfs[i]));
            }
            System.arraycopy(refs, 0, dexBytes, 32, 4);
        }
        
        private static void fixFileSHA1Header(byte[] dexBytes)throws NoSuchAlgorithmException {
            MessageDigest md = MessageDigest.getInstance("SHA-1");
            md.update(dexBytes, 32, dexBytes.length - 32);
            byte[] newdt = md.digest();
            System.arraycopy(newdt, 0, dexBytes, 12, 20);
            String hexstr = "";
            for(int i = 0;i < newdt.length;i++) {
                hexstr += Integer.toString((newdt[i] & 0xff) + 0x100,16).substring(1);
            }
            System.out.println(hexstr);
            
        }
        
        private static void fixCheckSumHeader(byte[] dexBytes) {
            Adler32 adler = new Adler32();
            adler.update(dexBytes,12,dexBytes.length - 12);
            long value = adler.getValue();
            int va = (int)value;
            byte[] newcs = intToByte(va);
            System.out.println("newcs Len:" + newcs.length);
            byte[] recs = new byte[4];
            for(int i = 0;i < 4;i++) {
                recs[i] = newcs[newcs.length - 1 - i];
                System.out.println(Integer.toHexString(newcs[i]));
            }
            System.arraycopy(recs,0,dexBytes,8,4);
            System.out.println(Long.toHexString(value));
            System.out.println();
            
        }
            
        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;
        }
        
        private static byte[] encrypt(byte[] srcdata) {
            for(int i = 0;i < srcdata.length;i++) {
                srcdata[i] = (byte)(0xFF ^ srcdata[i]);
            }
            return srcdata;
        }
        
        private static byte[] readFileBytes(File file)throws IOException{
            byte[] arrayOfByte = new byte[1024];
            ByteArrayOutputStream localByteArrayOutputStream = new ByteArrayOutputStream();
            FileInputStream fis = new FileInputStream(file);
            while(true) {
                int i = fis.read(arrayOfByte);
                if(i != -1) {
                    localByteArrayOutputStream.write(arrayOfByte,0,i);
                }else {
                    return localByteArrayOutputStream.toByteArray();
                }
            }
        }
    }
    

    其中的force.apk是源apk,reforce.dex是脱壳apk的dex文件,运行后生成一个新的classes.dex,用新得到的dex替换掉reforce.apk的dex文件,然后重签名即可。
    最后运行界面可见就是源apk的运行界面;


    最后运行界面

    Tips:
    过程中遇到的几个坑:
    1,7-zip对reforce.apk解压失败,提示文件格式错误,改用winRAR后正常,原因暂时未知。
    2,应用按流程从reforce.apk中的ProxyApplication中运行到源apk中的MyApplication中后又返回到的reforce.apk中的MainActivity,原因是脱壳程序不能有MainActivity,删除后恢复。
    3,自签名和用Androidkiller签名的apk往4.4的手机上安装时出现错误Failure [INSTALL_PARSE_FAILED_NO_CERTIFICATES]Failure [INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION],原因是重打包时要删掉原来的cert文件,删掉后再用自签名工具签名即可。

    参考https://github.com/l123456789jy/Lazy/blob/master/lazylibrary/src/main/java/com/github/lazylibrary/util/AntiEmulatorUtiles.java
    https://blog.csdn.net/androidsecurity/article/details/8809542
    http://www.520monkey.com/archives/553

    相关文章

      网友评论

          本文标题:加壳VS脱壳(第一代)

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