Android Rust JNI系列教程(四) Rust 调用Android API 实现签名验证
创始人
2024-04-24 07:58:33
0

前言

本章使用常见的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();//packageNamelet package_name_value = env.call_method(application, "getPackageName", "()Ljava/lang/String;", &[]).unwrap();//JValue to JStringlet pkg_name = JString::from(package_name_value.l().unwrap());//JString to rust Stringlet pkg_name: String = env.get_string(pkg_name).unwrap().into();log::d("sign".to_string(), format!("package name = {}", pkg_name));//PackageManager.GET_SIGNATURESlet 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 JObjectlet signature_array_obj = signatures_value.l().unwrap();//JObject to jarraylet 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 JValuelet 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 Veclet digest_array = env.convert_byte_array(digest_array).unwrap();//get hexlet hex_sign: String = digest_array.iter().map(|b| format!("{:02x}", b).to_string()).collect::>().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. 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
    涉及代码 let digest_array = env.convert_byte_array(digest_array).unwrap();,其中,digest_arrayjarray类型,digest_arrayVec类型.
  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();

其中,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();}

与上节对比,可以发现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   

查看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

可以看到,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 
  • 查看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

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

总结

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

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

上一篇:12.15

下一篇:全网唯一,不忽悠的ChatGPT

相关内容

热门资讯

监控摄像头接入GB28181平... 流程简介将监控摄像头的视频在网站和APP中直播,要解决的几个问题是:1&...
Windows10添加群晖磁盘... 在使用群晖NAS时,我们需要通过本地映射的方式把NAS映射成本地的一块磁盘使用。 通过...
protocol buffer... 目录 目录 什么是protocol buffer 1.protobuf 1.1安装  1.2使用...
在Word、WPS中插入AxM... 引言 我最近需要写一些文章,在排版时发现AxMath插入的公式竟然会导致行间距异常&#...
【PdgCntEditor】解... 一、问题背景 大部分的图书对应的PDF,目录中的页码并非PDF中直接索引的页码...
Fluent中创建监测点 1 概述某些仿真问题,需要创建监测点,用于获取空间定点的数据࿰...
educoder数据结构与算法...                                                   ...
MySQL下载和安装(Wind... 前言:刚换了一台电脑,里面所有东西都需要重新配置,习惯了所...
MFC文件操作  MFC提供了一个文件操作的基类CFile,这个类提供了一个没有缓存的二进制格式的磁盘...
有效的括号 一、题目 给定一个只包括 '(',')','{','}'...