本章使用常见的jni方案实现Android签名验证.此种方法优点是简单,且不会像下章使用openssl的方案导致包体增加太多.但是安全性也会差一些.
#[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()
}
本段代码所涉及知识点如下:
JValue转JObjectlet signature_array_obj = signatures_value.l().unwrap();其中,signatures_value是JValue类型,signature_array_obj是JObject类型.JValue 转 JStringlet pkg_name = JString::from(package_name_value.l().unwrap());,其中,package_name_value.l().unwrap()是一个JObject,使用JString::from()可使JObject转为JString.所以总的来说,还是用JValue先转JObject再转其它类型.JString转rust Stringlet pkg_name: String = env.get_string(pkg_name).unwrap().into();,其中,pkg_name是个JString.JObject转jarraylet signature_array = jobjectArray::from(signature_array_obj.cast());,其中,signature_array_obj是JObject类型,signature_array是jarray类型.jarray转Vec let digest_array = env.convert_byte_array(digest_array).unwrap();,其中,digest_array是jarray类型,digest_array是Vec类型.rust String转JString//rust String to JNIString
let hex_sign = JNIString::from(hex_sign);
//JNIString to JString
let hex_sign = env.new_string(hex_sign).unwrap();
其中,hex_sign是rust String类型,通过JNIString::from(hex_sign)将其转为JNIString类型,再由env.new_string(hex_sign).unwrap();转为JString.
本段代码所相关的rust知识点如上所示.参数获取的整体流程用Java表示的话,代码如下:
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某些方法并打印调用栈(如Signature的toByteArray方法,甚至MessageDigest.getInstance()方法).只在java层做验证的话则直接改对应检测方法的返回值即可.如遇和本章一样使用native层校验的方式,可选的破解方式也很多,简单说两个方案:
epic等),可hook的点很多.hook pms(关键类IPackageManager).我的线上app的签名验证就是被别人这样搞掉然后重打包的…
libstd这段知识本来准备放在下章来让大家了解的.但是考虑到下章将会增加一个openssl库,会导致包体增大很多.故在本章提前讲述,让大家可以有一个更明显的对比.(扩展知识部分简单了解即可,大家首先了解这个概念,以后真实场景需要这个功能再去仔细研究).
libstd时so的大小.打包(已在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
libstdnightly版本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实现签名验证的方案,并探讨了其中安全性不是特别优秀的问题.本章另一个重点则是数据类型的转换方法,大家熟练使用各种数据类型的转换之后可以更好的将android与rust相结合.
Android项目地址:https://github.com/tangxuesong6/Android_Rust_JNI_Demo
rust项目地址:https://github.com/tangxuesong6/Rust_JNI_Demo
上一篇:12.15
下一篇:全网唯一,不忽悠的ChatGPT