Android反调试浅析

作者: 大批 | 来源:发表于2017-08-19 20:18 被阅读281次

    :)

    侵删

    本文的主要内容

    • 验证apk是否被重新打包
    • 验证apk是否开启了调试,以及是否有debugger已经连接
    • 检测进程的TracerPid

    验证签名

    破解apk的时候一般都会将apk反编译,然后修改一些东西,再编译成apk(这个时候的签名和发布版本的签名是不一样的)

    • 验证签名是否和发布版本一致最好是放到服务器验证(本地传一个签名信息到服务器,服务器只需返回是否正确就行)
    • java层验证 (这里仅仅是获取了签名信息的 hascode)
      public static int getSignature(Context context){
            PackageManager pm = context.getPackageManager();
            PackageInfo pi;
            StringBuilder sb = new StringBuilder();
            try {
                pi = pm.getPackageInfo(context.getPackageName(),PackageManager.GET_SIGNATURES);
                Signature[] signatures = pi.signatures;
                for(Signature signature : signatures){
                    sb.append(signature.toCharsString());
                }
            } catch (PackageManager.NameNotFoundException e) {
                e.printStackTrace();
            }
            Log.i(LOG_TAG,"获取到的签名信息  :  "+sb.toString());
            return sb.toString().hashCode();
        }
    
    
    
        //这个是获取SHA1的方法  上面那个方法是获取签名的hash值 这个和cmd里面获取的是一样的
        public static String getCertificateSHA1Fingerprint(Context context) {
            //获取包管理器
            PackageManager pm = context.getPackageManager();
            //获取当前要获取SHA1值的包名,也可以用其他的包名,但需要注意,
            //在用其他包名的前提是,此方法传递的参数Context应该是对应包的上下文。
            String packageName = context.getPackageName();
            //返回包括在包中的签名信息
            int flags = PackageManager.GET_SIGNATURES;
            PackageInfo packageInfo = null;
            try {
                //获得包的所有内容信息类
                packageInfo = pm.getPackageInfo(packageName, flags);
            } catch (PackageManager.NameNotFoundException e) {
                e.printStackTrace();
            }
            //签名信息
            Signature[] signatures = packageInfo.signatures;
            byte[] cert = signatures[0].toByteArray();
            //将签名转换为字节数组流
            InputStream input = new ByteArrayInputStream(cert);
            //证书工厂类,这个类实现了出厂合格证算法的功能
            CertificateFactory cf = null;
            try {
                cf = CertificateFactory.getInstance("X509");
            } catch (CertificateException e) {
                e.printStackTrace();
            }
            //X509证书,X.509是一种非常通用的证书格式
            X509Certificate c = null;
            try {
                c = (X509Certificate) cf.generateCertificate(input);
            } catch (CertificateException e) {
                e.printStackTrace();
            }
            String hexString = null;
            try {
                //加密算法的类,这里的参数可以使MD4,MD5等加密算法
                MessageDigest md = MessageDigest.getInstance("SHA1");
                //获得公钥
                byte[] publicKey = md.digest(c.getEncoded());
                //字节到十六进制的格式转换
                hexString = byte2HexFormatted(publicKey);
            } catch (NoSuchAlgorithmException e1) {
                e1.printStackTrace();
            } catch (CertificateEncodingException e) {
                e.printStackTrace();
            }
            return hexString;
        }
    
        //这里是将获取到得编码进行16进制转换
        private static String byte2HexFormatted(byte[] arr) {
            StringBuilder str = new StringBuilder(arr.length * 2);
            for (int i = 0; i < arr.length; i++) {
                String h = Integer.toHexString(arr[i]);
                int l = h.length();
                if (l == 1)
                    h = "0" + h;
                if (l > 2)
                    h = h.substring(l - 2, l);
                str.append(h.toUpperCase());
                if (i < (arr.length - 1))
                    str.append(':');
            }
            return str.toString();
        }
    
    
    
    • native层验证(将获取签名信息方法放到native层,这里顺带讲讲jni开发
      • 现在的Android studio 2.2 之后就已经可以使用cmake进行ndk开发了(cmake相比以前的传统方法来说更加方便)

      • 首先是配置app的build.gradle

        • 主要是配置需要生成的平台和CMakeLists.txt的路径
            android {
               ...
              defaultConfig {
                ...
        
                externalNativeBuild {
                  cmake {
                      abiFilters 'x86', 'x86_64', 'armeabi', 'armeabi-v7a', 'arm64-v8a'
                    }
                  }
               }
        
              externalNativeBuild {
                  cmake {
                        path "src/main/cpp/CMakeLists.txt"
                  }
               }
          }
        
      • 创建保存源文件的目录(传统的目录名字是jni,cmake的方式是cpp)


      • 创建一个c源文件,这里已经不需要先生成头文件了(源文件里面直接按照jni的写法写函数就行了)

    #include <string.h>
    #include <jni.h>
    
    jstring
    Java_com_suse_yuxin_opengldemo_OpenGL20DemoActivity_helloJni( JNIEnv* env,
                                                                       jobject thiz ){
    
        return (*env)->NewStringUTF(env,"hello Jni.");
    }
    
        public native String helloJni();
    
        static {
            System.loadLibrary("native-lib");
        }
    
    
    • 配置cmake文件(CMakeLists.txt里面的配置也比较简单 百度百度就知道了)
    
    #指定 cmake的版本
    cmake_minimum_required(VERSION 3.4.1)
    
    
    add_library( native-lib
                 SHARED
                 native-lib.c )
    
    target_link_libraries(native-lib log android)
    
    • 然后就可以运行了,回到正题就是jni获取签名信息
    #include <string.h>
    #include <jni.h>
    
    const char HexCode[]={'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
    
    jobject getApplication(JNIEnv *env) {
        jclass localClass = (*env)->FindClass(env,"android/app/ActivityThread");
        if (localClass!=NULL)
        {
            // LOGI("class have find");
            jmethodID getapplication = (*env)->GetStaticMethodID(env,localClass, "currentApplication", "()Landroid/app/Application;");
            if (getapplication!=NULL)
            {
                jobject application = (*env)->CallStaticObjectMethod(env,localClass, getapplication);
                return application;
            }
            return NULL;
        }
        return NULL;
    }
    
    jstring
    Java_com_suse_yuxin_opengldemo_OpenGL20DemoActivity_stringFromJNI( JNIEnv* env,
                                                      jobject thiz )
    {
        //获取到Context
        jobject context= getApplication(env);
        if(context == NULL){
            return NULL;
        }
        jclass  activity = (*env)->GetObjectClass(env,context);
        // 得到 getPackageManager 方法的 ID
        jmethodID methodID_func = (*env)->GetMethodID(env,activity, "getPackageManager", "()Landroid/content/pm/PackageManager;");
        // 获得PackageManager对象
        jobject packageManager = (*env)->CallObjectMethod(env,context,methodID_func);
        jclass packageManagerclass = (*env)->GetObjectClass(env,packageManager);
        //得到 getPackageName 方法的 ID
        jmethodID methodID_pack = (*env)->GetMethodID(env,activity,"getPackageName", "()Ljava/lang/String;");
        //获取包名
        jstring name_str = (jstring)((*env)->CallObjectMethod(env,context, methodID_pack));
        // 得到 getPackageInfo 方法的 ID
        jmethodID methodID_pm = (*env)->GetMethodID(env,packageManagerclass,"getPackageInfo", "(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;");
        // 获得应用包的信息
        jobject package_info = (*env)->CallObjectMethod(env,packageManager, methodID_pm, name_str, 64);
        // 获得 PackageInfo 类
        jclass package_infoclass = (*env)->GetObjectClass(env,package_info);
        // 获得签名数组属性的 ID
        jfieldID fieldID_signatures = (*env)->GetFieldID(env,package_infoclass,"signatures", "[Landroid/content/pm/Signature;");
        // 得到签名数组,待修改
        jobject signatur = (*env)->GetObjectField(env,package_info, fieldID_signatures);
        jobjectArray  signatures = (jobjectArray)(signatur);
        // 得到签名
        jobject signature = (*env)->GetObjectArrayElement(env,signatures, 0);
        // 获得 Signature 类,待修改
        jclass signature_clazz = (*env)->GetObjectClass(env,signature);
        //---获得签名byte数组
        jmethodID tobyte_methodId = (*env)->GetMethodID(env,signature_clazz, "toByteArray", "()[B");
        jbyteArray signature_byte = (jbyteArray) (*env)->CallObjectMethod(env,signature, tobyte_methodId);
        //把byte数组转成流
        jclass byte_array_input_class=(*env)->FindClass(env,"java/io/ByteArrayInputStream");
        jmethodID init_methodId=(*env)->GetMethodID(env,byte_array_input_class,"<init>","([B)V");
        jobject byte_array_input=(*env)->NewObject(env,byte_array_input_class,init_methodId,signature_byte);
        //实例化X.509
        jclass certificate_factory_class=(*env)->FindClass(env,"java/security/cert/CertificateFactory");
        jmethodID certificate_methodId=(*env)->GetStaticMethodID(env,certificate_factory_class,"getInstance","(Ljava/lang/String;)Ljava/security/cert/CertificateFactory;");
        jstring x_509_jstring=(*env)->NewStringUTF(env,"X.509");
        jobject cert_factory=(*env)->CallStaticObjectMethod(env,certificate_factory_class,certificate_methodId,x_509_jstring);
        //certFactory.generateCertificate(byteIn);
        jmethodID certificate_factory_methodId=(*env)->GetMethodID(env,certificate_factory_class,"generateCertificate",("(Ljava/io/InputStream;)Ljava/security/cert/Certificate;"));
        jobject x509_cert=(*env)->CallObjectMethod(env,cert_factory,certificate_factory_methodId,byte_array_input);
    
        jclass x509_cert_class=(*env)->GetObjectClass(env,x509_cert);
        jmethodID x509_cert_methodId=(*env)->GetMethodID(env,x509_cert_class,"getEncoded","()[B");
        jbyteArray cert_byte=(jbyteArray)(*env)->CallObjectMethod(env,x509_cert,x509_cert_methodId);
    
        //MessageDigest.getInstance("SHA1")
        jclass message_digest_class=(*env)->FindClass(env,"java/security/MessageDigest");
        jmethodID methodId=(*env)->GetStaticMethodID(env,message_digest_class,"getInstance","(Ljava/lang/String;)Ljava/security/MessageDigest;");
        //如果取SHA1则输入SHA1
        //jstring sha1_jstring=(*env)->NewStringUTF(env,"SHA1");
        jstring sha1_jstring=(*env)->NewStringUTF(env,"MD5");
        jobject sha1_digest=(*env)->CallStaticObjectMethod(env,message_digest_class,methodId,sha1_jstring);
        //sha1.digest (certByte)
        methodId=(*env)->GetMethodID(env,message_digest_class,"digest","([B)[B");
        jbyteArray sha1_byte=(jbyteArray)(*env)->CallObjectMethod(env,sha1_digest,methodId,cert_byte);
        //toHexString
        jsize array_size=(*env)->GetArrayLength(env,sha1_byte);
        jbyte* sha1 =(*env)->GetByteArrayElements(env,sha1_byte,NULL);
        char hex_sha[array_size*2+1];
        int i;
        for (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';
        return (*env)->NewStringUTF(env, hex_sha);
    }
    

    可调式状态校验

    • 一个是AndroidManifest的调试flag是否打开


           /**
             *
             * 验证是否可以调试
             * i != 0 已经打开可调式
             */
            int i = getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE;
    
    • debugger是否已经连接
            boolean debuggerConnected = Debug.isDebuggerConnected();
            Log.i(LOG_TAG,"是否连接调试  : "+debuggerConnected);
    

    获取获取TracerPid来判断(TracerPid正常情况是0,如果被调试这个是不为0的)

           /**
             *
             * 获取TracerPid来判断
             *
             */
            int pid = android.os.Process.myPid();
            String info = null;
            File file = new File("/proc/" + pid + "/status");
            try {
                FileInputStream fileInputStream = new FileInputStream(file);
                InputStreamReader reader = new InputStreamReader(fileInputStream);
                BufferedReader bufferedReader = new BufferedReader(reader);
                while((info = bufferedReader.readLine()) != null){
                    Log.i(LOG_TAG,"proecc info :  "+info);
                }
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
    
    • 上面这个是获取到了进程的各种状态下面给一个log日志信息



    Nothing is certain in this life. The only thing i know for sure is that. I love you and my life. That is the only thing i know. have a good day

    相关文章

      网友评论

        本文标题:Android反调试浅析

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