基于ADT环境开发的的实现,请参考: Android中的Apk的加固(加壳)原理解析和实现 
类加载和dex文件相关的内容,如:Android动态加载Dex机制解析 

一、什么是加壳?

加壳是在二进制的程序中植入一段代码,在运行的时候优先取得程序的控制权,做一些额外的工作。大多数病毒就是基于此原理。是应用加固的一种手法对原始二进制原文进行加密/隐藏/混淆。
壳最本质的功能就是实现加载器。

  • 未加壳前,系统直接执行原dex,即原apk
  • 加壳后,系统执行 壳代码--> 脱壳得到原dex --> 执行原dex


Apk加壳:就是通过给目标APK加一层保护程序,把需要保护的内容加密、隐藏起来,来防止反编译的一种方法。
加壳的原理: 
Android之Apk加壳_第1张图片


所以我们在加壳过程中需要三个关键对象: 
1、未加密的Apk(即demo.apk) 
2、壳程序Apk(即shell.apk,负责解密apk工作) 
3、加密工具(即java工程。将demo.apk加密和shell.dex合并,得到新的dex)

 

二、下面我们来实现如何加壳:
Step1:打包demo工程:demo.apk
Step2
(先设置解壳/密):打包解壳工程:shell.apk,解压获取:shell.dex
Step3
(开始加壳/密:运行java工程,合并shell.dex和demo.apk,得到:classes.dex
step4
(修正签名、运行):把class.dex放进shell.apk,重新签名得到:shell_demo.apk
shell_demo.apk就是我们想得到的加壳app!


 

Step1:打包demo工程:demo.apk

注意:这里包括后续打包,只能使用同一个签名。
源码:https://github.com/lvxiangan/Shell/tree/master/Demo
功能:获取当前包名,广播监听网络状态变化,Glide框架显示网络图片(网络操作+图片显示)等。
Android之Apk加壳_第2张图片     Android之Apk加壳_第3张图片

关键代码

1、MyApplication

public class MyApplication extends Application {

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


2、AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
    package="demon.demo">

   
   

            android:name=".MyApplication"
        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">
       
           
               

               
           
       
       
   

 

 

 

 

Step2:打包解壳工程:shell.apk,解压获取:shell.dex

这个shell.apk在经过后面替换dex后,就是我们想要得到的东西。

源码:https://github.com/lvxiangan/Shell/tree/master/MyUnshell
工程目录:
Android之Apk加壳_第4张图片


通过解压shell.Apk的方式获取到dex文件,  更名为shell.dex。如图:
Android之Apk加壳_第5张图片

 

关键代码
1、ProxyApplication.java

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.res.AssetManager;import android.content.res.Resources;import android.os.Bundle;import android.util.ArrayMap;import android.util.Log;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.Iterator;import java.util.zip.ZipEntry;import java.util.zip.ZipInputStream;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;    @Override    protected void attachBaseContext(Context base) {        super.attachBaseContext(base);        try {            // 创建两个文件夹payload_odex,payload_lib 私有的,可写的文件目录            File odex = this.getDir("demo_odex", MODE_PRIVATE);            File libs = this.getDir("demo_lib", MODE_PRIVATE);            odexPath = odex.getAbsolutePath();            libPath = libs.getAbsolutePath();            apkFileName = odexPath + "/shelldemo.apk";            File dexFile = new File(apkFileName);            Log.i("demo", "apk size:" + dexFile.length());            if (!dexFile.exists()) {                // 在payload_odex文件夹内,创建payload.apk                dexFile.createNewFile();                // 读取程序classes.dex文件                byte[] dexdata = this.readDexFileFromApk();                // 分离出解壳后的apk文件已用于动态加载                this.splitPayLoadFromDex(dexdata);            }            // 配置动态加载环境 获取主线程对象 http://blog.csdn.net/myarrow/article/details/14223493            Object currentActivityThread = RefInvoke.invokeStaticMethod("android.app.ActivityThread", "currentActivityThread", new Class[]{}, new Object[]{});            String packageName = this.getPackageName();//当前apk的包名            ArrayMap mPackages = (ArrayMap) RefInvoke.getFieldOjbect("android.app.ActivityThread", currentActivityThread, "mPackages");            WeakReference wr = (WeakReference) mPackages.get(packageName);            //创建被加壳apk的DexClassLoader对象 加载apk内的类和本地代码(c/c++代码)            DexClassLoader dLoader = new DexClassLoader(apkFileName, odexPath, libPath, (ClassLoader) RefInvoke.getFieldOjbect("android.app.LoadedApk", wr.get(), "mClassLoader"));            //base.getClassLoader(); 是不是就等同于 (ClassLoader) RefInvoke.getFieldOjbect()? 有空验证下//?            //把当前进程的DexClassLoader 设置成了被加壳apk的DexClassLoader            RefInvoke.setFieldOjbect("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() {        //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 (PackageManager.NameNotFoundException e) {            Log.i("demo", "error:" + Log.getStackTraceString(e));        }        //有值的话调用该Applicaiton        Object currentActivityThread = RefInvoke.invokeStaticMethod("android.app.ActivityThread", "currentActivityThread", new Class[]{}, new Object[]{});        Object mBoundApplication = RefInvoke.getFieldOjbect("android.app.ActivityThread", currentActivityThread, "mBoundApplication");        Object loadedApkInfo = RefInvoke.getFieldOjbect("android.app.ActivityThread$AppBindData", mBoundApplication, "info");        //把当前进程的mApplication 设置成了null        RefInvoke.setFieldOjbect("android.app.LoadedApk", "mApplication", loadedApkInfo, null);        Object oldApplication = RefInvoke.getFieldOjbect("android.app.ActivityThread", currentActivityThread, "mInitialApplication");        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, new Class[]{boolean.class, Instrumentation.class}, new Object[]{false, null});//执行 makeApplication(false,null)        RefInvoke.setFieldOjbect("android.app.ActivityThread", "mInitialApplication", currentActivityThread, app);        ArrayMap mProviderMap = (ArrayMap) RefInvoke.getFieldOjbect("android.app.ActivityThread", currentActivityThread, "mProviderMap");        Iterator it = mProviderMap.values().iterator();        while (it.hasNext()) {            Object providerClientRecord = it.next();            Object localProvider = RefInvoke.getFieldOjbect("android.app.ActivityThread$ProviderClientRecord", providerClientRecord, "mLocalProvider");            RefInvoke.setFieldOjbect("android.content.ContentProvider", "mContext", localProvider, app);        }        Log.i("demo", "app:" + app);        app.onCreate();    }    /**     * 释放被加壳的apk文件,so文件     *     * @param     * @throws IOException     */    private void splitPayLoadFromDex(byte[] apkdata) throws IOException {        int ablen = apkdata.length;        //取被加壳apk的长度 这里的长度取值,对应加壳时长度的赋值都可以做些简化        byte[] dexlen = new byte[4];        System.arraycopy(apkdata, ablen - 4, dexlen, 0, 4);        ByteArrayInputStream bais = new ByteArrayInputStream(dexlen);        DataInputStream in = new DataInputStream(bais);        int readInt = in.readInt();        System.out.println(Integer.toHexString(readInt));        byte[] newdex = new byte[readInt];        //把被加壳apk内容拷贝到newdex中        System.arraycopy(apkdata, ablen - 4 - readInt, newdex, 0, readInt);        //这里应该加上对于apk的解密操作,若加壳是加密处理的话        //?        //对源程序Apk进行解密        newdex = decrypt(newdex);        //写入apk文件        File file = new File(apkFileName);        try {            FileOutputStream localFileOutputStream = new FileOutputStream(file);            localFileOutputStream.write(newdex);            localFileOutputStream.close();        } catch (IOException localIOException) {            throw new RuntimeException(localIOException);        }        //分析被加壳的apk文件        ZipInputStream localZipInputStream = new ZipInputStream(new BufferedInputStream(new FileInputStream(file)));        while (true) {            ZipEntry localZipEntry = localZipInputStream.getNextEntry();//不了解这个是否也遍历子目录,看样子应该是遍历的            if (localZipEntry == null) {                localZipInputStream.close();                break;            }            //取出被加壳apk用到的so文件,放到 libPath中(data/data/包名/payload_lib)            String name = localZipEntry.getName();            if (name.startsWith("lib/") && name.endsWith(".so")) {                File storeFile = new File(libPath + "/" + name.substring(name.lastIndexOf('/')));                storeFile.createNewFile();                FileOutputStream fos = new FileOutputStream(storeFile);                byte[] arrayOfByte = new byte[1024];                while (true) {                    int i = localZipInputStream.read(arrayOfByte);                    if (i == -1)                        break;                    fos.write(arrayOfByte, 0, i);                }                fos.flush();                fos.close();            }            localZipInputStream.closeEntry();        }        localZipInputStream.close();    }    /**     * 从apk包里面获取dex文件内容(byte)     *     * @return     * @throws IOException     */    private byte[] readDexFileFromApk() throws IOException {        ByteArrayOutputStream dexByteArrayOutputStream = new ByteArrayOutputStream();        ZipInputStream localZipInputStream = new ZipInputStream(new BufferedInputStream(new FileInputStream(this.getApplicationInfo().sourceDir)));        while (true) {            ZipEntry localZipEntry = localZipInputStream.getNextEntry();            if (localZipEntry == null) {                localZipInputStream.close();                break;            }            if (localZipEntry.getName().equals("classes.dex")) {                byte[] arrayOfByte = new byte[1024];                while (true) {                    int i = localZipInputStream.read(arrayOfByte);                    if (i == -1)                        break;                    dexByteArrayOutputStream.write(arrayOfByte, 0, i);                }            }            localZipInputStream.closeEntry();        }        localZipInputStream.close();        return dexByteArrayOutputStream.toByteArray();    }    // //直接返回数据,读者可以添加自己解密方法    private byte[] decrypt(byte[] srcdata) {        for (int i = 0; i < srcdata.length; i++) {            srcdata[i] = (byte) (0xFF ^ srcdata[i]);        }        return srcdata;    }    //以下是加载资源    protected AssetManager mAssetManager;//资源管理器    protected Resources mResources;//资源    protected Resources.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 Resources.Theme getTheme() {        return mTheme == null ? super.getTheme() : mTheme;    }}



2.RefInvoke.java

import java.lang.reflect.Field;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;public class RefInvoke {    /**     * 反射执行类的静态函数(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 (Exception e) {            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 (Exception e) {            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 (Exception e) {            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 (Exception e) {            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 (Exception e) {            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 (Exception e) {            e.printStackTrace();        }    }}


3、根据demo的 AndroidManifest.xml ,配置shell工程AndroidManifest.xml
注意:

  • 把demo.apk的AndroidManifest.xml中所有:权限、组件(activity、service、broadcastreceiver) 复制过来,组件必须使用完整的包名。 
  • 使用meta-data配置 demo.apk 的MyApplication,也要使用完整包名。 

注意对比两个配置文件的区别。
解壳工程:
<?xml version="1.0" encoding="utf-8"?>
   
package="demon.myunshell">

   
   

            android:name=".ProxyApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme">
       
            android:name="APPLICATION_CLASS_NAME"
            android:value="demon.demo.MyApplication" />

       
           
               
               
           

       

       
   



demo工程:
<?xml version="1.0" encoding="utf-8"?>
   
package="demon.demo">

   
   

            android:name=".MyApplication"
        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=".MainActivity">
           
               
               
           

       
        android:name=".ImageActivity" />
   


 

Step3:运行java工程,合并shell.dex和demo.apk,得到:classes.dex

源码:https://github.com/lvxiangan/Shell/tree/master/DexShellTool
这是一个java工程,目录结构如下:
Android之Apk加壳_第6张图片

工程下新建force文件夹,将demo.apk,shell.dex复制到里面去,运行如下代码,生成新的dex文件,即classes.dex:

Android之Apk加壳_第7张图片
加密合并成功后的classes.dex,大小几乎等于demo.apk + shell.dex。

 

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

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

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


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

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

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


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

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

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


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

 

step4:把class.dex放进shell.apk,重新签名得到:shell_demo.apk

素材下载:https://github.com/lvxiangan/Shell/tree/master/Tools

解压step2的shell.apk

  • 将step3得到的classes.dex替换classes.dex。 
  • 重新签名

完成后,如下图:
Android之Apk加壳_第8张图片

注意观察classes.dex的大小,判断是否复制成功。

 

开始重新签名:

  • 新建一个Tools文件夹,将前面的签名文件,shell.apk复制进去。 
  • 签名命令太长不好记,我们新建sign.bat文件,添加如下内容,注意使用该命令系统必须配置Java环境变量,可根据自身情况进行修改,方便下次使用:

jarsigner -verbose -keystore DeMon.jks -storepass 123456 -keypass 123456 -sigfile CERT -digestalg SHA1 -sigalg MD5withRSA -signedjar shelldemo.apk shell.apk key

命令说明:
jarsigner -verbose -keystore 签名文件 -storepass 密码  -keypass alias的密码 -sigfile CERT -digestalg SHA1 -sigalg MD5withRSA  签名后的文件 签名前的apk alias名称

  •  
  • 双击运行sign.bat文件,成功签名Tools文件会新增一个shelldemo.apk,会比shell.apk稍大,大概就是生成的签名文件的大小。shelldemo.apk就是成功加壳后的apk,可以安装运行。

正确签名后的内容如下:
Android之Apk加壳_第9张图片

 

 

 

step5:验证效果
Android之Apk加壳_第10张图片  Android之Apk加壳_第11张图片

注意对比demo.apk的效果图,除了标题和包名与不一致外,功能上完全相同,即符合预期。Apk加壳成功!

 

 

 

总结

优点不多述,说说缺点吧: 
1、 Apk体积变大,尤其是res文件成倍增长。 
2、解壳过程容易被反编译,最好用C/C++实现
3、第一次安装启动需要等待加载时间较长,用户体验不好。

 

GitHub地址:
https://github.com/lvxiangan/Shell

改进版:https://github.com/lvxiangan/Android-Shell2
1、解决加壳后运行有两个app的问题、
2、在一个AS工程管理各个模块,打包输出时记得选择切换模块
3、在壳程序实现JNI解密



参考:https://blog.csdn.net/DeMonliuhui/article/details/78269234

更多相关文章

  1. 学习笔记之——Android中的Picasso实现圆形头像、圆角图片工具类
  2. ndroid显示在线图片
  3. Android中读取properties文件2
  4. Android下载 文件(APP) 并且静默安装
  5. Android 根据所给的图片位置获得Thumbnail
  6. Android HttpURLConnection上传图片至Servlet端指定目录
  7. Android文件读写权限
  8. Android当中显示网络图片
  9. Android studio 新建一个空白工程提示:Conflict with dependency

随机推荐

  1. Android(安卓)Telephony Framework相关知
  2. Android(安卓)API中文文档EditText
  3. 命令行建avd
  4. android 谷歌地图准备
  5. ImageView的属性android:scaleType
  6. Android根据输入银行卡号判断属于哪个银
  7. Android(安卓)Studio 初体验
  8. This Android(安卓)SDK requires Android
  9. RelativeLayout用到的一些重要的属性(自
  10. 如何发布你的Android应用程序