赞
踩
本章使用常见的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(); //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() }
本段代码所涉及知识点如下:
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<u8>
let digest_array = env.convert_byte_array(digest_array).unwrap();
,其中,digest_array
是jarray
类型,digest_array
是Vec<u8>
类型.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
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。