关于安卓APP加固基础的总结

34 篇文章 5 订阅
31 篇文章 1 订阅

安卓加固基础(一)

1.Dex字符串加密

1.1 前序

Android应用当中,很多隐私信息都是以字符串的形式存在的。这些隐私信息是明文,对于软件来说是想当不安全的,如果我们能在打包时对Dex中的字符串加密替换,并在运行时调用解密,这样就能够避免字符串明文存在于Dex中。虽然,无法完全避免被破解,但是加大了逆向提取信息的难度,安全性无疑提高了很多。

1.2 字符串加密方法简介

目前市面上主要存在两种字符串加密方式:
(1)在开发阶段开发者使用加密后的字符串然后手动调用解密,这种方法工程量太大了,缺点很明显。
(2)编译后修改字节码,然后再动态植入加密后的字符串,最后让其自动调用进行解密,这里重点分析的是第二种

1.3 强大的工具字符串加密工具StringFog
1.3.1 环境配置和运行效果

工具:

    (1)AS版本3.5.0
    (2)StringFog配置方法:https://github.com/MegatronKing/StringFog
    (3)反编译工具 jeb3

代码(MainActivity.java):

    package com.example.testdex;
    
    import androidx.appcompat.app.AppCompatActivity;
    
    import android.os.Bundle;
    import android.widget.TextView;
    
    public class MainActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate (savedInstanceState);
            setContentView (R.layout.activity_main);
            String str ="Stringfog";               //这是加密字符串
        }
    }    

jeb3反编译的效果(MainActivity.java):

    package com.example.testdex;
    
    import android.os.Bundle;
    import androidx.appcompat.app.AppCompatActivity;
    
    public class MainActivity extends AppCompatActivity {
        public MainActivity() {
            super();
        }
    
        protected void onCreate(Bundle arg2) {
            super.onCreate(arg2);
            this.setContentView(0x7F09001C);
            StringFog.decrypt("GxEeBQFHMQAV");    //显然"StringFog"被加密了
        }
    }
1.3.2 StringFog加密原理分析

首先看加密的方法:StringFog采用的是base64+xor(异或)算法

    import java.util.Base64;
    public class StringFog {
    	private static byte[] xor(byte[] data, String key) {     //异或算法
    	    int len = data.length;
    	    int lenKey = key.length();
    	    int i = 0;
    	    int j = 0;
    	    while (i < len) {
    	        if (j >= lenKey) {
    	            j = 0;
    	        }
    	        data[i] = (byte) (data[i] ^ key.charAt(j));
    	        i++;
    	        j++;
    	    }
    	    return data;
    	}
    	public static String encode(String data, String key) {
    	    return new String(Base64.getEncoder().encode(xor(data.getBytes(), key)));
    	    //调用base64加密包
    	}
    
    	public static String decode(String data, String key) {
    	    return new String(xor(Base64.getDecoder().decode(data), key));
    	    //调用base64解密包
    	}

按照安卓程序加密的字符串测试:

    	public static void main(String[] args) {
    		String test = encode("Stringfog","Hello World");
    		System.out.println(test);
    	}

输出结果为:
在这里插入图片描述
显然跟jeb3里面看到的一样。

ps:Base64 packge 下载地址:http://commons.apache.org/proper/commons-codec/download_codec.cgi 下载后导入commons-codec-1.14.jar

1.3.2 StringFog加密原理分析
    StringFog实际上是操作了class文件,编译class文件的字节码文件,发现如果都是字符串常量的话,指令都为LDC
    
    ****************************************************************************
    [root@iZbp1dubkpj5g938jakcouZ ~]# javac Hello.java
    [root@iZbp1dubkpj5g938jakcouZ ~]# javap -c Hello
    Compiled from "Hello.java"
    public class Hello {
      public Hello();
        Code:
           0: aload_0
           1: invokespecial #1                  // Method java/lang/Object."<init>":()V
           4: return
    
      public static void main(java.lang.String[]);
        Code:
           0: ldc           #2                  // String This is a String!
           2: astore_1
           3: return
    }

那么我们可以借助asm库拦截方法中的每条LDC指令,然后插入该指令即可

总结:字符串加密方法能够较为有效的阻止他人通过字符串搜素定位代码,但是不能防止Hook,总而言之,这只是一个比较普通的混淆方法

2.资源加密

java代码将png图片加密:

    package cn.zzh;
    
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    
    public class pngEncrypt {
    	public static void main(String[] args){
    	    //调用加密方法
    	    KMD.encrypt("f://1.png");
    	}
    
        //加密后,会在原图片的路径下生成加密后的图片
        public static void encrypt(String filePath){
            byte[] tempbytes = new byte[5000];
            try {
                InputStream in = new FileInputStream(filePath);
                OutputStream out = new FileOutputStream(filePath.subSequence(0, filePath.lastIndexOf("."))+"2.png");
                while (in.read(tempbytes) != -1) {
                    byte a = tempbytes[0];
                    tempbytes[0] = tempbytes[1]; //将第一个字符和第二个字符交换
                    tempbytes[1] = a;
                    out.write(tempbytes);//写文件
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

在这里插入图片描述

使用winhex查看发现:

在这里插入图片描述变成了:
在这里插入图片描述

然后我们只需要在AS中将编写解密代码即可:

下面是一个简单的测试demo:

    (1)建立asserts文件,将加密的12.png文件放进去
    (2)代码编写:
    package com.example.testdex;
    
    import androidx.appcompat.app.AppCompatActivity;
    
    import android.content.Context;
    import android.content.res.AssetManager;
    import android.graphics.Bitmap;
    import android.graphics.BitmapFactory;
    import android.os.Bundle;
    import android.util.Log;
    import android.widget.TextView;
    import android.widget.Toast;
    
    import java.io.IOException;
    import java.io.InputStream;
    
    public class MainActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate (savedInstanceState);
            setContentView (R.layout.activity_main);
            Bitmap bitmap= getImageFromAssets(this,"12.png");//图片资源加密
            if(bitmap != null) {
                //imageView.setImage(ImageSource.bitmap(bitmap));
                Toast.makeText (this, "图片解密成功", Toast.LENGTH_SHORT).show ();
            } else {
               // Log.i(TAG,"图片为空");
                Toast.makeText (this, "图片解密失败", Toast.LENGTH_SHORT).show ();
            }
    
        }
        public Bitmap getImageFromAssets(Context context, String fileName) {
            Bitmap image = null;
            AssetManager am = context.getResources().getAssets();
            try {
                InputStream is = am.open(fileName);
                byte[] buffer = new byte[1500000];//记得要足够大
                is.read(buffer);
                for(int i=0; i<buffer.length; i+= 5000){//和加密相同
                    byte temp = buffer[i];
                    buffer[i] = buffer[i+1];
                    buffer[i+1] = temp;
                }
                image = BitmapFactory.decodeByteArray(buffer, 0, buffer.length);
                if (is!=null){
                    is.close();
                }
    
            } catch (IOException e) {
                e.printStackTrace();
    
            }
            return image;
        }
    
    }

测试结果:
在这里插入图片描述

此时,我们打开apk查看assert文件时依然无法看到有用的文件
在这里插入图片描述

ps:同时我们还可以进行资源路径混淆,详情见:https://github.com/shwenzhang/AndResGuard/blob/master/README.zh-cn.md

3.对抗反编译

3.1类名混淆

现在的反编译工具都太先进了,很多纯粹的对抗反编译技术都不在适应了,
但是我们可以将类名进行混淆:

gradle版本在3.4以下时我们使用proguard-rules.pro进行混淆,达到3.4以上时我们使用R8穿插一些proguard规则进行混淆
下面重点研究的是3.4以上版本的情况:

官方文档:https://developer.android.com/studio/build/shrink-code?hl=zh-cn

通用教程:https://www.jianshu.com/p/65027e18c2fe

混淆规则(如下):

    
    #############################################
    #
    # 对于一些基本指令的添加
    #
    #############################################
    # 代码混淆压缩比,在0~7之间,默认为5,一般不做修改
    -optimizationpasses 5
    
    # 混合时不使用大小写混合,混合后的类名为小写
    -dontusemixedcaseclassnames
    
    # 指定不去忽略非公共库的类
    -dontskipnonpubliclibraryclasses
    
    # 这句话能够使我们的项目混淆后产生映射文件
    # 包含有类名->混淆后类名的映射关系
    -verbose
    
    # 指定不去忽略非公共库的类成员
    -dontskipnonpubliclibraryclassmembers
    
    # 不做预校验,preverify是proguard的四个步骤之一,Android不需要preverify,去掉这一步能够加快混淆速度。
    -dontpreverify
    
    # 保留Annotation不混淆
    -keepattributes *Annotation*,InnerClasses
    
    # 避免混淆泛型
    -keepattributes Signature
    
    # 抛出异常时保留代码行号
    -keepattributes SourceFile,LineNumberTable
    
    # 指定混淆是采用的算法,后面的参数是一个过滤器
    # 这个过滤器是谷歌推荐的算法,一般不做更改
    -optimizations !code/simplification/cast,!field/*,!class/merging/*
    
    
    #############################################
    #
    # Android开发中一些需要保留的公共部分
    #
    #############################################
    
    # 保留我们使用的四大组件,自定义的Application等等这些类不被混淆
    # 因为这些子类都有可能被外部调用
    -keep public class * extends android.app.Activity
    -keep public class * extends android.app.Appliction
    -keep public class * extends android.app.Service
    -keep public class * extends android.content.BroadcastReceiver
    -keep public class * extends android.content.ContentProvider
    -keep public class * extends android.app.backup.BackupAgentHelper
    -keep public class * extends android.preference.Preference
    -keep public class * extends android.view.View
    -keep public class com.android.vending.licensing.ILicensingService
    
    
    # 保留support下的所有类及其内部类
    -keep class android.support.** {*;}
    
    # 保留继承的
    -keep public class * extends android.support.v4.**
    -keep public class * extends android.support.v7.**
    -keep public class * extends android.support.annotation.**
    
    # 保留R下面的资源
    -keep class **.R$* {*;}
    
    # 保留本地native方法不被混淆
    -keepclasseswithmembernames class * {
        native <methods>;
    }
    
    # 保留在Activity中的方法参数是view的方法,
    # 这样以来我们在layout中写的onClick就不会被影响
    -keepclassmembers class * extends android.app.Activity{
        public void *(android.view.View);
    }
    
    # 保留枚举类不被混淆
    -keepclassmembers enum * {
        public static **[] values();
        public static ** valueOf(java.lang.String);
    }
    
    # 保留我们自定义控件(继承自View)不被混淆
    -keep public class * extends android.view.View{
        *** get*();
        void set*(***);
        public <init>(android.content.Context);
        public <init>(android.content.Context, android.util.AttributeSet);
        public <init>(android.content.Context, android.util.AttributeSet, int);
    }
    
    # 保留Parcelable序列化类不被混淆
    -keep class * implements android.os.Parcelable {
        public static final android.os.Parcelable$Creator *;
    }
    
    # 保留Serializable序列化的类不被混淆
    -keepclassmembers class * implements java.io.Serializable {
        static final long serialVersionUID;
        private static final java.io.ObjectStreamField[] serialPersistentFields;
        !static !transient <fields>;
        !private <fields>;
        !private <methods>;
        private void writeObject(java.io.ObjectOutputStream);
        private void readObject(java.io.ObjectInputStream);
        java.lang.Object writeReplace();
        java.lang.Object readResolve();
    }
    
    # 对于带有回调函数的onXXEvent、**On*Listener的,不能被混淆
    -keepclassmembers class * {
        void *(**On*Event);
        void *(**On*Listener);
    }
    
    # webView处理,项目中没有使用到webView忽略即可
    -keepclassmembers class fqcn.of.javascript.interface.for.webview {
        public *;
    }
    -keepclassmembers class * extends android.webkit.webViewClient {
        public void *(android.webkit.WebView, java.lang.String, android.graphics.Bitmap);
        public boolean *(android.webkit.WebView, java.lang.String);
    }
    -keepclassmembers class * extends android.webkit.webViewClient {
        public void *(android.webkit.webView, jav.lang.String);
    }
    
3.2 so层混淆

这个之前已经写过了:
文档:函数混淆(JNI_Onload).note
链接:http://note.youdao.com/noteshare?id=f6add1ff6a6c4b78aac4a9e27fe390ed&sub=04FF4E2B1F504587A06C69F39C4DF22C

3.3 签名验证
3.3.1 在MainActivity.java中进行签名验证
    package com.example.signatureverify;
    
    import androidx.appcompat.app.AppCompatActivity;
    
    import android.content.Context;
    import android.content.pm.PackageInfo;
    import android.content.pm.PackageManager;
    import android.content.pm.Signature;
    import android.os.Bundle;
    import android.widget.Toast;
    
    import java.security.MessageDigest;
    import java.security.NoSuchAlgorithmException;
    import java.util.Locale;
    
    public class MainActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate (savedInstanceState);
            setContentView (R.layout.activity_main);
            Context context =getApplicationContext ();
            Toast.makeText (context, "我是正版", Toast.LENGTH_SHORT).show ();
            String cert_sha1="59F8A6B86A367F0586F1A15DDDB63D75263C5D62"; // 通过调试提前获取apk的sha1签名
            boolean is_org_app = false;
            try {
                is_org_app = isOrgApp(context,cert_sha1);
            } catch (PackageManager.NameNotFoundException e) {
                e.printStackTrace ();
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace ();
            }
            if(!is_org_app){
                android.os.Process.killProcess ((android.os.Process.myPid ()));
                //如果签名不一致,说明程序被修改了,直接退出
            }
        }
    //比较签名
        private boolean isOrgApp(Context context, String cert_sha1) throws PackageManager.NameNotFoundException, NoSuchAlgorithmException {
            String current_sha1=getAppSha1(context);
            current_sha1=current_sha1.replace (":","");
            return cert_sha1.equals (current_sha1);
        }
    //生成sha1的签名
        private String getAppSha1(Context context) throws PackageManager.NameNotFoundException, NoSuchAlgorithmException {
            PackageInfo info=context.getPackageManager ().getPackageInfo (context.getPackageName (),PackageManager.GET_SIGNATURES);
            byte[] cert =info.signatures[0].toByteArray ();
            MessageDigest md =MessageDigest.getInstance ("SHA1");
            byte[] publicKey=md.digest (cert);
            StringBuffer hexString =new StringBuffer ();
            for(int i=0;i<publicKey.length;i++){
                String appendString=Integer.toHexString (0xFF&publicKey[i]).toUpperCase(Locale.US);
                if(appendString.length ()==1){
                    hexString.append("0");
                }
                hexString.append(appendString);//签名的格式是11:22,所以需要加上":"
                hexString.append (":");
            }
            String result=hexString.toString ();
            return result.substring (0,result.length ()-1);
        }
    
    }

完成程序后,使用AndroidKiller进行修改时,将"我是正版"字符串修改成"我是盗版",在运行程序时,程序会直接闪退
在这里插入图片描述

3.3.2 在So层中进行签名验证

MainActivity:

    package com.example.jnisignatureverify;
    
    import androidx.appcompat.app.AppCompatActivity;
    
    import android.content.Context;
    import android.content.pm.PackageInfo;
    import android.content.pm.PackageManager;
    import android.os.Bundle;
    import android.view.View;
    import android.widget.Button;
    import android.widget.TextView;
    import android.widget.Toast;
    
    import java.security.MessageDigest;
    import java.util.Locale;
    
    public class MainActivity extends AppCompatActivity {
        // Used to load the 'native-lib' library on application startup.
        static {
            System.loadLibrary("native-lib");
        }
    
        protected TextView appSignaturesTv;
        protected TextView jniSignaturesTv;
        protected Button checkBtn;
        protected Button tokenBtn;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            super.setContentView(R.layout.activity_main);
    
            initView();
    
            appSignaturesTv.setText(getSha1Value(MainActivity.this));
            jniSignaturesTv.setText(getSignaturesSha1(MainActivity.this));
        }
    
        private View.OnClickListener clickListener = new View.OnClickListener(){
            @Override
            public void onClick(View v) {
                boolean result = checkSha1(MainActivity.this);
    
                if(result){
                    Toast.makeText(getApplicationContext(),"验证通过",Toast.LENGTH_LONG).show();
                }else{
                    Toast.makeText(getApplicationContext(),"验证不通过,请检查valid.cpp文件配置的sha1值",Toast.LENGTH_LONG).show();
                }
            }
        };
    
        private View.OnClickListener tokenClickListener = new View.OnClickListener(){
            @Override
            public void onClick(View v) {
                String result = getToken(MainActivity.this,"12345");
    
                Toast.makeText(getApplicationContext(),result,Toast.LENGTH_LONG).show();
            }
        };
    
        private void initView() {
            appSignaturesTv = (TextView) findViewById(R.id.app_signatures_tv);
            jniSignaturesTv = (TextView) findViewById(R.id.jni_signatures_tv);
            checkBtn = (Button) findViewById(R.id.check_btn);
            tokenBtn = (Button) findViewById(R.id.token_btn);
    
            checkBtn.setOnClickListener(clickListener);
            tokenBtn.setOnClickListener(tokenClickListener);
        }
    
        /**
         * A native method that is implemented by the 'native-lib' native library,
         * which is packaged with this application.
         */
        public native String getSignaturesSha1(Context context);
        public native boolean checkSha1(Context context);
        public native String getToken(Context context,String userId);
    
    //获取apk当前的签名
        public String getSha1Value(Context context) {
            try {
                PackageInfo info = context.getPackageManager().getPackageInfo(
                        context.getPackageName(), PackageManager.GET_SIGNATURES);
                byte[] cert = info.signatures[0].toByteArray();
                MessageDigest md = MessageDigest.getInstance("SHA1");
                byte[] publicKey = md.digest(cert);
                StringBuffer hexString = new StringBuffer();
                for (int i = 0; i < publicKey.length; i++) {
                    String appendString = Integer.toHexString(0xFF & publicKey[i])
                            .toUpperCase(Locale.US);
                    if (appendString.length() == 1)
                        hexString.append("0");
                    hexString.append(appendString);
                }
                String result = hexString.toString();
                return result.substring(0, result.length());
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
    }

native-lib.cpp:

    #include <stdio.h>
    #include <stdlib.h>
    #include <jni.h>
    #include <android/log.h>
    #include <cstring>
    
    #define TAG    "jni-log"
    #define LOGD(...)  __android_log_print(ANDROID_LOG_DEBUG,TAG,__VA_ARGS__)
    
    //签名信息
    const char *app_sha1="59F8A6B86A367F0586F1A15DDDB63D75263C5D62";
    const char hexcode[] = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
    
    char* getSha1(JNIEnv *env, jobject context_object){
        //上下文对象
        jclass context_class = env->GetObjectClass(context_object);
    
        //反射获取PackageManager
        jmethodID methodId = env->GetMethodID(context_class, "getPackageManager", "()Landroid/content/pm/PackageManager;");
        jobject package_manager = env->CallObjectMethod(context_object, methodId);
        if (package_manager == NULL) {
            //LOGD("package_manager is NULL!!!");
            return NULL;
        }
    
        //反射获取包名
        methodId = env->GetMethodID(context_class, "getPackageName", "()Ljava/lang/String;");
        jstring package_name = (jstring)env->CallObjectMethod(context_object, methodId);
        if (package_name == NULL) {
            //LOGD("package_name is NULL!!!");
            return NULL;
        }
        env->DeleteLocalRef(context_class);
    
        //获取PackageInfo对象
        jclass pack_manager_class = env->GetObjectClass(package_manager);
        methodId = env->GetMethodID(pack_manager_class, "getPackageInfo", "(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;");
        env->DeleteLocalRef(pack_manager_class);
        jobject package_info = env->CallObjectMethod(package_manager, methodId, package_name, 0x40);
        if (package_info == NULL) {
            LOGD("getPackageInfo() is NULL!!!");
            return NULL;
        }
        env->DeleteLocalRef(package_manager);
    
        //获取签名信息
        jclass package_info_class = env->GetObjectClass(package_info);
        jfieldID fieldId = env->GetFieldID(package_info_class, "signatures", "[Landroid/content/pm/Signature;");
        env->DeleteLocalRef(package_info_class);
        jobjectArray signature_object_array = (jobjectArray)env->GetObjectField(package_info, fieldId);
        if (signature_object_array == NULL) {
            LOGD("signature is NULL!!!");
            return NULL;
        }
        jobject signature_object = env->GetObjectArrayElement(signature_object_array, 0);
        env->DeleteLocalRef(package_info);
    
        //签名信息转换成sha1值
        jclass signature_class = env->GetObjectClass(signature_object);
        methodId = env->GetMethodID(signature_class, "toByteArray", "()[B");
        env->DeleteLocalRef(signature_class);
        jbyteArray signature_byte = (jbyteArray) env->CallObjectMethod(signature_object, methodId);
        jclass byte_array_input_class=env->FindClass("java/io/ByteArrayInputStream");
        methodId=env->GetMethodID(byte_array_input_class,"<init>","([B)V");
        jobject byte_array_input=env->NewObject(byte_array_input_class,methodId,signature_byte);
        jclass certificate_factory_class=env->FindClass("java/security/cert/CertificateFactory");
        methodId=env->GetStaticMethodID(certificate_factory_class,"getInstance","(Ljava/lang/String;)Ljava/security/cert/CertificateFactory;");
        jstring x_509_jstring=env->NewStringUTF("X.509");
        jobject cert_factory=env->CallStaticObjectMethod(certificate_factory_class,methodId,x_509_jstring);
        methodId=env->GetMethodID(certificate_factory_class,"generateCertificate",("(Ljava/io/InputStream;)Ljava/security/cert/Certificate;"));
        jobject x509_cert=env->CallObjectMethod(cert_factory,methodId,byte_array_input);
        env->DeleteLocalRef(certificate_factory_class);
        jclass x509_cert_class=env->GetObjectClass(x509_cert);
        methodId=env->GetMethodID(x509_cert_class,"getEncoded","()[B");
        jbyteArray cert_byte=(jbyteArray)env->CallObjectMethod(x509_cert,methodId);
        env->DeleteLocalRef(x509_cert_class);
        jclass message_digest_class=env->FindClass("java/security/MessageDigest");
        methodId=env->GetStaticMethodID(message_digest_class,"getInstance","(Ljava/lang/String;)Ljava/security/MessageDigest;");
        jstring sha1_jstring=env->NewStringUTF("SHA1");
        jobject sha1_digest=env->CallStaticObjectMethod(message_digest_class,methodId,sha1_jstring);
        methodId=env->GetMethodID(message_digest_class,"digest","([B)[B");
        jbyteArray sha1_byte=(jbyteArray)env->CallObjectMethod(sha1_digest,methodId,cert_byte);
        env->DeleteLocalRef(message_digest_class);
    
        //转换成char
        jsize array_size=env->GetArrayLength(sha1_byte);
        jbyte* sha1 =env->GetByteArrayElements(sha1_byte,NULL);
        char *hex_sha=new char[array_size*2+1];
        for (int i = 0; i <array_size ; ++i) {
            hex_sha[2*i]=hexcode[((unsigned char)sha1[i])/16];
            hex_sha[2*i+1]=hexcode[((unsigned char)sha1[i])%16];
        }
        hex_sha[array_size*2]='\0';
    
        LOGD("hex_sha %s ",hex_sha);
        return hex_sha;
    }
    
    jboolean checkValidity(JNIEnv *env,char *sha1){
        //比较签名
        if (strcmp(sha1,app_sha1)==0)
        {
            LOGD("验证成功");
            return true;
        }
        LOGD("验证失败");
        return false;
    }
    extern "C"
    JNIEXPORT jstring JNICALL
    Java_com_example_jnisignatureverify_MainActivity_getSignaturesSha1(JNIEnv *env, jobject thiz,
                                                                       jobject context) {
        // TODO: implement getSignaturesSha1()
        return env->NewStringUTF(app_sha1);
    }extern "C"
    JNIEXPORT jboolean JNICALL
    Java_com_example_jnisignatureverify_MainActivity_checkSha1(JNIEnv *env, jobject thiz,
                                                               jobject contextObject) {
        // TODO: implement checkSha1()
        char *sha1 = getSha1(env,contextObject);
    
        jboolean result = checkValidity(env,sha1);
        return result;
    }extern "C"
    JNIEXPORT jstring JNICALL
    Java_com_example_jnisignatureverify_MainActivity_getToken(JNIEnv *env, jobject thiz,
                                                              jobject contextObject, jstring user_id) {
        // TODO: implement getToken()
        char *sha1 = getSha1(env,contextObject);
        jboolean result = checkValidity(env,sha1);
    
        if(result){
            return env->NewStringUTF("获取Token成功");
        }else{
            return env->NewStringUTF("获取失败,请检查native-lib.cpp文件配置的sha1值");
        }
    }

activity_main.xml:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_margin="16dp"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context="com.example.jnisignatureverify.MainActivity">
    
        <LinearLayout
            android:layout_width="match_parent"
            android:orientation="vertical"
            android:layout_height="wrap_content">
    
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="APP签名信息:"/>
    
            <TextView
                android:id="@+id/app_signatures_tv"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>
        </LinearLayout>
    
        <LinearLayout
            android:layout_marginTop="16dp"
            android:orientation="vertical"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
    
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="jni配置的签名信息:"/>
    
            <TextView
                android:id="@+id/jni_signatures_tv"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>
        </LinearLayout>
    
        <Button
            android:id="@+id/check_btn"
            android:layout_marginTop="16dp"
            android:text="验证签名是否正确"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
    
        <Button
            android:id="@+id/token_btn"
            android:layout_marginTop="16dp"
            android:text="获取token"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
    </LinearLayout>

4.反调试

4.1 思路一(一个进程最多只能被一个进程ptrace)

本文章主要针对安卓so反调试和最初的加壳方法进行了一下总结

在处于调试状态时,Linux会向/proc/pid/status写入一些进程状态信息,比如TracerPid字段会写入调试进程的pid,因此我们可以自己ptrace自己,然后让android_server不能调试
代码如下:

    #include<sys/ptrace.h>  //这个头文件很重要
    void anti_debug01()
    {
        ptrace(PTRACE_TRACEME,0,0,0);
    }
    

    jint JNI_Onload(JavaVM* vm,void* reserved)
    {
        anti_debug01();
    
    }

一旦开始调试,就会出现
在这里插入图片描述

反反调试思路:nop掉anti_debug01()函数调用

4.2检测Tracepid的值

根据第一种分析得出Tracepid的值只要不为0 就能说明进程正在被调试,因此我们只需要检测Tracepid的值是不是0,如果不为0,直接退出就行了

    void anti_debug02()
    {
        try
        {
            const int bufsize =1024;
            char filename[bufsize];
            char line[bufsize];
            int pid=getpid();
            sprintf(filename,"/proc/%d/status",pid);
            FILE* fd=fopen(filename,"r");
            if(fd!=NULL)
            {
                while(fgets(line,bufsize,fd))
                {
                    if(strncmp(line,"TracerPid",9)==0)
                    {
                        int statue = atoi(&line[10]);    //atoi,将字符串转化为int
                        if(statue !=0)
                        {
                            fclose(fd);
                            int ret =kill(pid,SIGKILL);
                        }
                        break;
                    }
                }
            }
        }
    }
    #include <pthread.h>
    #include <unistd.h>
    #include <stdio.h>
    void* thread_function(void *arg){
        int pid = getpid();
        char file_name[20] = {'\0'};
        sprintf(file_name,"proc/%d/status",pid);
        char line_str[256];
        int i = 0,traceid;
        FILE *fp;
        while(1){
            i = 0;
            fp = fopen(file_name,"r");
            if(fp == NULL){
                break;
            }
            while(!feof(fp)){
                fgets(line_str,256,fp);
                if(i == 5){
                    // traceid = getnumberfor_str(line_str);
                    traceid = atoi(&line_str[10]);
                    if(traceid > 0){
                        exit(0);
                    }
                    break;
                }
                i++;
            }
            fclose(fp);
            sleep(5);
        }
    }
    
    
    void create_thread_check_traceid(){
        pthread_t thread_id;
        int err = pthread_create(&thread_id,NULL,thread_function,NULL);
        if(err != 0){
    
        }
    }

在这里插入图片描述

反反调试思路:使用IDA动态调试在函数调用前下断,对比当前TracerPid为4591,将TracerPid对应的寄存器修改为0,达到“0==0”的效果,绕开反调试。

5.自定义DexClassLoader

5.1 将壳dex放在待加密dex的外面
5.1.1 .原理

在这里插入图片描述

加密的工程中存在的三个加密对象

1.需要加密的APK(源APK)

2.壳程序APK

3.加密工具(负责将源APK进行加密和壳DEX合并成新的DEX)

在这里插入图片描述

5.1.2.DEX头文件的内容
字段名称偏移值长度说明
magic0x08魔数字段,值为"dex\n035\0"
checksum0x84校验码
signature0xc20sha-1签名
file_size0x204dex文件总长度
header_size0x244文件头长度,009版本=0x5c,035版本=0x70
endian_tag0x284标示字节顺序的常量
link_size0x2c4链接段的大小,如果为0就是静态链接
link_off0x304链接段的开始位置
map_off0x344map数据基址
string_ids_size0x384字符串列表中字符串个数
string_ids_off0x3c4字符串列表基址
type_ids_size0x404类列表里的类型个数
type_ids_off0x444类列表基址
proto_ids_size0x484原型列表里面的原型个数
proto_ids_off0x4c4原型列表基址
field_ids_size0x504字段个数
field_ids_off0x544字段列表基址
method_ids_size0x584方法个数
method_ids_off0x5c4方法列表基址
class_defs_size0x604类定义标中类的个数
class_defs_off0x644类定义列表基址
data_size0x684数据段的大小,必须4k对齐
data_off0x6c4数据段基址

这里面需要注意的字段:

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

因为我们需要将一个文件(加密之后的源Apk)写入到Dex中,那么我们肯定需要修改文件校验码(checksum).因为他是检查文件是否有错误。那么signature也是一样,也是唯一识别文件的算法。还有就是需要修改dex文件的大小。
不过这里还需要一个操作,就是标注一下我们加密的Apk的大小,因为我们在脱壳的时候,需要知道Apk的大小,才能正确的得到Apk。那么这个值放到哪呢?这个值直接放到文件的末尾就可以了。
总的来说:我们需要做:修改Dex的三个文件头,将源Apk的大小追加到壳dex的末尾就可以了

根据上述修改后的dex文件样式如下

在这里插入图片描述
由原理可知这里需要进行三个步骤了:
1、自己编写一个源程序项目(需要加密的APK)
2、脱壳项目(解密源APK和加载APK)
3、对源APK进行加密和脱壳项目的DEX合并

5.1.3.项目实现
5.1.3.1源APK(待加密的apk)

命名为:SourceApk

MyApplication:

    package com.example.sourceapk;
    public class MyApplication extends Application {
        @Override
        public void onCreate() {
            super.onCreate();
            Log.i("demo", "source apk onCreate:" + this);
        }
    }

ps:MyApplication的作用:MyApplication类继承Application,查看源码我们知道,Application中有一个attachBaseContext方法,它在Application的onCreate方法执行前就会执行了,这个关键点为后面的加密程序做铺垫。

MainActivity:

    package com.example.sourceapk;
    
    public class MainActivity extends Activity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            
            TextView content = new TextView(this);
            content.setText("I am Source Apk");
            content.setOnClickListener(new OnClickListener(){
                @Override
                public void onClick(View arg0) {
                    Intent intent = new Intent(MainActivity.this, SubActivity.class);
                    startActivity(intent);
                }});
            setContentView(content);
            
            Log.i("demo", "app:"+getApplicationContext());   
        }
    }

就是源程序的一个主要类

SubActivity:

    package com.example.sourceapk;
    
    import android.app.Activity;
    import android.os.Bundle;
    import android.util.Log;
    import android.widget.TextView;
    
    public class SubActivity extends Activity {
    
    	@Override
    	protected void onCreate(Bundle savedInstanceState) {
    		super.onCreate(savedInstanceState);
    		
    		TextView content = new TextView(this);
    		content.setText("I am Source Apk SubMainActivity");
    		
    		setContentView(content);
    		
    		Log.i("demo", "app:"+getApplicationContext());
    		
    	}
    
    }
    同MainActivity

AndroidManifest.xml

    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.example.sourceapk">
    
        <application
            android:allowBackup="true"
            android:label="@string/app_name"
            android:name="com.example.sourceapk.MyApplication" >
    
            <activity
                android:name=".MainActivity"
                android:label="@string/app_name" >
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
            <activity android:name=".SubActivity" />
        </application>
    
    </manifest>

注意一定要加上android:name="com.example.sourceapk.MyApplication"

5.1.3.2加密APK(对加密后的源apk进行解密加载的apk)

ProxyActivity:

    package com.example.packapk;
    
    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;
    
    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 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;
    
    	// 这是context赋值
    	@Override
    	protected void attachBaseContext(Context base) {
    		super.attachBaseContext(base);
    		try {
    			// 创建两个文件夹payload_odex、payload_lib,私有的,可写的文件目录
    			File odex = this.getDir("payload_odex", MODE_PRIVATE);
    			File libs = this.getDir("payload_lib", MODE_PRIVATE);
    			odexPath = odex.getAbsolutePath();
    			libPath = libs.getAbsolutePath();
    			apkFileName = odex.getAbsolutePath() + "/payload.apk";
    			File dexFile = new File(apkFileName);
    			Log.i("demo", "apk size:"+dexFile.length());
    			if (!dexFile.exists())
    			{
    				dexFile.createNewFile();  //在payload_odex文件夹内,创建payload.apk
    				// 读取程序classes.dex文件
    				byte[] dexdata = this.readDexFileFromApk();
    				
    				// 分离出解壳后的apk文件已用于动态加载
    				this.splitPayLoadFromDex(dexdata);
    			}
    			// 配置动态加载环境
    			Object currentActivityThread = RefInvoke.invokeStaticMethod(
    					"android.app.ActivityThread", "currentActivityThread",
    					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"));
    			//把当前进程的mClassLoader设置成了被加壳apk的DexClassLoader
    			RefInvoke.setFieldOjbect("android.app.LoadedApk", "mClassLoader",
    					wr.get(), dLoader);
    			
    			Log.i("demo","classloader:"+dLoader);
    			
    			try{
    				Object actObj = dLoader.loadClass("com.example.sourceapk.MainActivity");
    				Log.i("demo", "actObj:"+actObj);
    			}catch(Exception e){
    				Log.i("demo", "activity:"+Log.getStackTraceString(e));
    			}
    			
    
    		} catch (Exception e) {
    			Log.i("demo", "error:"+Log.getStackTraceString(e));
    			e.printStackTrace();
    		}
    	}
    
    	@Override
    	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 (NameNotFoundException e) {
    				Log.i("demo", "error:"+Log.getStackTraceString(e));
    				e.printStackTrace();
    			}
    			//有值的话调用该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");
    			// http://www.codeceo.com/article/android-context.html
    			ArrayList<Application> mAllApplications = (ArrayList<Application>) 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 data
    	 * @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 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;  
    	} 
    	
    }

RelInvoke(反射工具类):

    package com.example.packapk;
    
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    import java.lang.reflect.Field;
    
    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 (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();
    		}		
    	}
    
    }

分析:
1.首先通过java的反射,置换掉android.app.ActivityTread中的mClassLoader,作为加载解密出的APK的DexClassLoader,,该DexClassloader既加载了源程序,还以mClassLoader作为其父类,使得资源文件和系统代码能正确的被加载
2.找到源程序的Application(即上面我们定义的MyApplication类),通过这个类,运行待加密APK的onCreate方法,达到运行的效果

代码解析:

  • attachBaseContext方法
  • 得到壳程序APK中的DEX文件,然后从这个文件中得到源程序APK进行解密和加载
  • 我们需要在壳程序还没有运行的时候,加载源程序APK,执行它的onCreate方法。而通过查看Application源码发现了attachBaseContext方法,它会在Application的onCreate方法执行前执行
  • 我们注意到attachBaseContext方法中,进行了dex文件的分离,通过该将加壳dex中的源dex源so文件分离出来,进行解密操作后,将他们放到相应的位置
  • onCreate方法
  • 通过java反射,找到源程序的Application类,然后运行

加密程序的AndroidManifest文件也一定要添加上:

     <meta-data android:name="APPLICATION_CLASS_NAME" android:value="com.example.sourceapk.MyApplication"/>

且其他的activity必须和源程序保持一致性

例如:源程序为:

    <activity
                android:name="com.example.sourceapk.MainActivity"
                android:label="@string/app_name" >
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
            
            <activity
                android:name="com.example.sourceapk.SubActivity"></activity>

那么加密程序必须和其一模一样

5.1.3.3加密工具(使用java语言编写,eclipse运行,也可以是其他的脚本语言)

需要源程序apk和加密程序apk的dex文件

    package com.example.packdex;
    
    public class mymain {
        public static void main(String[] args) {
            try {
                File payloadSrcFile = new File("files/SourceApk.apk");   // 需要加壳的源程序
                System.out.println("apk size:"+payloadSrcFile.length());
                File packDexFile = new File("files/SourceApk.dex");  // 壳程序dex
                byte[] payloadArray = encrpt(readFileBytes(payloadSrcFile)); // 以二进制形式读出源apk,并进行加密处理
                byte[] packDexArray = readFileBytes(packDexFile); // 以二进制形式读出dex
                /* 合并文件 */
                int payloadLen = payloadArray.length;
                int packDexLen = packDexArray.length;
                int totalLen = payloadLen + packDexLen + 4; // 多出4字节是存放长度的
                byte[] newdex = new byte[totalLen]; // 申请了新的长度
                // 添加解壳代码
                System.arraycopy(packDexArray, 0, newdex, 0, packDexLen); // 先拷贝dex内容
                // 添加加密后的解壳数据
                System.arraycopy(payloadArray, 0, newdex, packDexLen, 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 = "files/classes.dex"; // 创建一个新文件
                File file = new File(str);
                if (!file.exists()) {
                    file.createNewFile();
                }
                
                FileOutputStream localFileOutputStream = new FileOutputStream(str);
                localFileOutputStream.write(newdex); // 将新计算出的二进制dex数据写入文件
                localFileOutputStream.flush();
                localFileOutputStream.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        
        // 直接返回数据,读者可以添加自己加密方法
        private static byte[] encrpt(byte[] srcdata){
            for (int i = 0; i < srcdata.length; i++) {
                srcdata[i] = (byte)(0xFF ^ srcdata[i]);
            }
            return srcdata;
        }
        ...
    }

完成所有的操作后还需要把新的apk进行签名操作,否则会导致错误发生

脱壳操作简述:获取加壳所使用的加密算法,先进行解密操作,在使用010Editor等工具查看源apk的地址,进行dump获取源apk

github地址:https://github.com/lzh18972615051/AndroidShellCode

转自:https://bbs.pediy.com/thread-258264.htm

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值