当前位置:   article > 正文

Android Rust JNI系列教程(四) Rust 调用Android API 实现签名验证_rust 安卓无障碍api

rust 安卓无障碍api

前言

本章使用常见的jni方案实现Android签名验证.此种方法优点是简单,且不会像下章使用openssl的方案导致包体增加太多.但是安全性也会差一些.

开始编写代码

rust端代码
#[no_mangle]
pub extern "system" fn Java_com_jni_rust_RustNative_getSignatureNormal(env: JNIEnv, _: JClass) -> jstring {
    let activity_thread_clz = env.find_class("android/app/ActivityThread").unwrap();
    let application_value = env.call_static_method(activity_thread_clz, "currentApplication", "()Landroid/app/Application;", &[]).unwrap();
    let application = JObject::try_from(application_value).unwrap();

    //packageName
    let package_name_value = env.call_method(application, "getPackageName", "()Ljava/lang/String;", &[]).unwrap();
    //JValue to JString
    let pkg_name = JString::from(package_name_value.l().unwrap());
    //JString to rust String
    let pkg_name: String = env.get_string(pkg_name).unwrap().into();
    log::d("sign".to_string(), format!("package name = {}", pkg_name));

    //PackageManager.GET_SIGNATURES
    let pm_signatures = JValue::from(64);
    let package_manager = env.call_method(application, "getPackageManager", "()Landroid/content/pm/PackageManager;", &[]).unwrap();
    let package_info = env.call_method(package_manager.l().unwrap(), "getPackageInfo", "(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;", &[package_name_value, pm_signatures]).unwrap();
    let signatures_value = env.get_field(package_info.l().unwrap(), "signatures", "[Landroid/content/pm/Signature;").unwrap();

    //JValue to JObject
    let signature_array_obj = signatures_value.l().unwrap();

    //JObject to jarray
    let signature_array = jobjectArray::from(signature_array_obj.cast());

    let signature_obj = env.get_object_array_element(signature_array, 0).unwrap();
    let sign_value = env.call_method(signature_obj, "toByteArray", "()[B", &[]).unwrap();

    let message_digest_clz = env.find_class("java/security/MessageDigest").unwrap();
    let md5 = env.new_string("md5").unwrap();

    //JString to JValue
    let md5 = JValue::from(md5);
    let message_digest_value = env.call_static_method(message_digest_clz, "getInstance",
                                                      "(Ljava/lang/String;)Ljava/security/MessageDigest;", &[md5]).unwrap();
    let _reset = env.call_method(message_digest_value.l().unwrap(), "reset", "()V", &[]).unwrap();
    let _update = env.call_method(message_digest_value.l().unwrap(), "update", "([B)V", &[sign_value]).unwrap();
    let digest_value = env.call_method(message_digest_value.l().unwrap(), "digest", "()[B", &[]).unwrap();

    let digest_array = jbyteArray::from(digest_value.l().unwrap().cast());

    //jarray to Vec
    let digest_array = env.convert_byte_array(digest_array).unwrap();
    //get hex
    let hex_sign: String = digest_array.iter()
        .map(|b| format!("{:02x}", b).to_string())
        .collect::<Vec<String>>().join("");
    log::d("sign".to_string(), format!("{}", hex_sign));

    let hex_sign = JNIString::from(hex_sign);
    let hex_sign = env.new_string(hex_sign).unwrap();
    hex_sign.into_raw()
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54

本段代码所涉及知识点如下:

  1. JValueJObject
    涉及代码let signature_array_obj = signatures_value.l().unwrap();其中,signatures_valueJValue类型,signature_array_objJObject类型.
  2. JValueJString
    涉及代码let pkg_name = JString::from(package_name_value.l().unwrap());,其中,package_name_value.l().unwrap()是一个JObject,使用JString::from()可使JObject转为JString.所以总的来说,还是JValue先转JObject再转其它类型.
  3. JStringrust String
    涉及代码let pkg_name: String = env.get_string(pkg_name).unwrap().into();,其中,pkg_name是个JString.
  4. JObjectjarray
    涉及代码let signature_array = jobjectArray::from(signature_array_obj.cast());,其中,signature_array_objJObject类型,signature_arrayjarray类型.
  5. jarrayVec<u8>
    涉及代码 let digest_array = env.convert_byte_array(digest_array).unwrap();,其中,digest_arrayjarray类型,digest_arrayVec<u8>类型.
  6. rust StringJString
    涉及代码:
//rust String to JNIString
let hex_sign = JNIString::from(hex_sign);
//JNIString to JString
let hex_sign = env.new_string(hex_sign).unwrap();
  • 1
  • 2
  • 3
  • 4

其中,hex_signrust String类型,通过JNIString::from(hex_sign)将其转为JNIString类型,再由env.new_string(hex_sign).unwrap();转为JString.
本段代码所相关的rust知识点如上所示.参数获取的整体流程用Java表示的话,代码如下:

Android端代码
    public String getSignMd5() {
        StringBuffer md5StrBuff = new StringBuffer();
        try {
            PackageInfo packageInfo = this.getPackageManager().getPackageInfo(this.getPackageName(), PackageManager.GET_SIGNATURES);
            Signature[] signs = packageInfo.signatures;
            Signature sign = signs[0];
            MessageDigest messageDigest = null;
            messageDigest = MessageDigest.getInstance("md5");
            messageDigest.reset();
            messageDigest.update(sign.toByteArray());
            byte[] byteArray = messageDigest.digest();

            for (int i = 0; i < byteArray.length; i++) {
                if (Integer.toHexString(0xFF & byteArray[i]).length() == 1) {
                    md5StrBuff.append("0").append(Integer.toHexString(0xFF & byteArray[i]));
                } else {
                    md5StrBuff.append(Integer.toHexString(0xFF & byteArray[i]));
                }
            }
        } catch (PackageManager.NameNotFoundException ex) {
            ex.printStackTrace();
        } catch (NoSuchAlgorithmException ex) {
            ex.printStackTrace();
        }
        return md5StrBuff.toString();
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

与上节对比,可以发现rust端代码其实就是通过native方式调用了这些api.由于代码被编译成了so文件,确实会比Android端使用Javaapi进行签名验证安全一点点点.如果我来破解签名验证的话,我会先使用frida或者LSPosed(高版本上的Xposed)来hook某些方法并打印调用栈(如SignaturetoByteArray方法,甚至MessageDigest.getInstance()方法).只在java层做验证的话则直接改对应检测方法的返回值即可.如遇和本章一样使用native层校验的方式,可选的破解方式也很多,简单说两个方案:

  1. 集成任意一个进程内hook框架进去(epic等),可hook的点很多.
  2. hook pms(关键类IPackageManager).

我的线上app的签名验证就是被别人这样搞掉然后重打包的…

扩展知识

优化libstd

这段知识本来准备放在下章来让大家了解的.但是考虑到下章将会增加一个openssl库,会导致包体增大很多.故在本章提前讲述,让大家可以有一个更明显的对比.(扩展知识部分简单了解即可,大家首先了解这个概念,以后真实场景需要这个功能再去仔细研究).

  1. 看看不优化libstdso的大小.

打包(已在Cargo.toml做过相关优化配置):

 cargo build --target aarch64-linux-android --release   
  • 1

查看so大小:

$ ls -lh                                                   
总用量 328K
drwxrwxr-x 10 txs txs 4.0K 12月 14 16:15 build
drwxrwxr-x  2 txs txs 4.0K 12月 14 16:15 deps
drwxrwxr-x  2 txs txs 4.0K 12月 14 16:13 examples
drwxrwxr-x  2 txs txs 4.0K 12月 14 16:13 incremental
-rw-rw-r--  1 txs txs  171 12月 14 16:14 librust_jni_demo.d
-rwxrwxr-x  2 txs txs 305K 12月 14 16:15 librust_jni_demo.so
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

可以看到,so大小为305k

  1. 优化libstd
  • 需使用nightly版本rust,请自行安装.
  • 打包
cargo build --target aarch64-linux-android --release -Z build-std=std,panic_abort -Z build-std-features=panic_immediate_abort 
  • 1
  • 查看so大小
 $ ls -lh                                                        
总用量 96K
drwxrwxr-x 18 txs txs 4.0K 12月 14 16:19 build
drwxrwxr-x  2 txs txs  12K 12月 14 16:20 deps
drwxrwxr-x  2 txs txs 4.0K 12月 14 16:13 examples
drwxrwxr-x  2 txs txs 4.0K 12月 14 16:13 incremental
-rw-rw-r--  1 txs txs  171 12月 14 16:14 librust_jni_demo.d
-rwxrwxr-x  2 txs txs  66K 12月 14 16:20 librust_jni_demo.so
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

可以看到,so文件优化到了66k.

总结

本章使用rust实现了在native层调用java api实现签名验证的方案,并探讨了其中安全性不是特别优秀的问题.本章另一个重点则是数据类型的转换方法,大家熟练使用各种数据类型的转换之后可以更好的将androidrust相结合.

Android项目地址:https://github.com/tangxuesong6/Android_Rust_JNI_Demo
rust项目地址:https://github.com/tangxuesong6/Rust_JNI_Demo

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小丑西瓜9/article/detail/484821
推荐阅读
相关标签
  

闽ICP备14008679号