本章使用常见的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
转JObject
let signature_array_obj = signatures_value.l().unwrap();
其中,signatures_value
是JValue
类型,signature_array_obj
是JObject
类型.JValue
转 JString
let pkg_name = JString::from(package_name_value.l().unwrap());
,其中,package_name_value.l().unwrap()
是一个JObject
,使用JString::from()
可使JObject
转为JString
.所以总的来说,还是用JValue
先转JObject
再转其它类型.JString
转rust String
let pkg_name: String = env.get_string(pkg_name).unwrap().into();
,其中,pkg_name
是个JString
.JObject
转jarray
let 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
端使用Java
api进行签名验证安全一点点点.如果我来破解签名验证的话,我会先使用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
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
实现签名验证的方案,并探讨了其中安全性不是特别优秀的问题.本章另一个重点则是数据类型的转换方法,大家熟练使用各种数据类型的转换之后可以更好的将android
与rust
相结合.
Android
项目地址:https://github.com/tangxuesong6/Android_Rust_JNI_Demo
rust
项目地址:https://github.com/tangxuesong6/Rust_JNI_Demo
上一篇:12.15
下一篇:全网唯一,不忽悠的ChatGPT