- 开发工具:
Android Studio 2020.3.1
- Rust 版本:
1.57.0
- NDK 版本:
21.4.7075529
- 电脑系统:
Windows 10 家庭中文版 (21H1) 64位
在开发中,为了防止别人对我们的apk
进行二次打包,都会加上签名校验的操作,这部分代码写在java
层对于破解来说形同虚设,写在NDK
可以一定程度上的增加破解的难度。使用 C/C++
写的签名校验例子很多,下面分享下关于使用 Rust
编写的校验代码。如果只想看代码,可直接访问gitee
仓库
Java
代码:hijack/src/main/java/com/hulytu/android/hijack/AntiHijack.java · Chris/ThinDroid
Rust
代码:[hijack_rust · Chris/ThinDroid](https://gitee.com/hulytu/thin-droid/tree/master/hijack_rust
# 准备工作
Android studio
或IntelliJ IDEA
建议安装下Rust
这个插件,安装完会提示安装Toml
,也一并安装上。Rust
和cargo
的安装,可参照上一节进行。
一、 Java 代码
`Java` 层代码比较简单,比如我在 `NDK` 初始化的时候添加签名校验
package com.hulytu.android.hijack;
import android.content.Context;
public class AntiHijack {
static {
System.loadLibrary("hijack");
}
public static native boolean init(Context context);
}
二、 Rust 实现
-
rust 项目创建
上一节我讲了
cargo-ndk
的安装,这节就直接使用cargo
来创建一个项目,这个目录可以放在和当前项目目录下,也可以放在其他地方,建议和当前项目放在一起。- 可以使用
Android studio
自带的控制台(Terminal
一般在底部位置)进入到当前项目目录下,我的项目叫ThinDroid
那打开的效果是这样的:
- 可以使用
rust_ndk_2_terminal.png
-
然后输入命令
cargo new hijack_rust --lib
然后会在当前目录下生成
hijack_rust
这个目录,名字可以取其他的。 -
添加
jni
依赖进入
hijack_rust
目录下,打开Cargo.toml
,在dependencies
节点下添加jni = "0.19.0"
添加
lib
节点,其中name
表示生成so
文件的名字[lib] name = "hijack" crate_type = ["dylib"]
-
完整
Cargo.toml
[package] name = "hijack_rust" version = "0.1.0" edition = "2021" [lib] name = "hijack" crate_type = ["dylib"] [dependencies] jni = "0.19.0"
-
编写
Rust
代码打开
src/lib.rs
文件,删除原来的代码先放完整代码,后面进行拆解
use std::ffi::c_void; use jni::{JavaVM, JNIEnv, NativeMethod}; use jni::objects::{JObject, JString, JValue}; use jni::strings::JNIString; use jni::sys::*; // 校验的包名 macro_rules! app_package { () => { "com.hulytu.android" }; } // 检验的签名 hash-code 获取方式可使用 com.hulytu.android.hijack.Utils.getSignInfoHashCode 方式获取 macro_rules! signature { () => { -779219788 }; } #[no_mangle] #[allow(non_snake_case)] fn JNI_OnLoad(jvm: JavaVM, _reserved: *mut c_void) -> jint { let methods = [ NativeMethod { name: JNIString::from("init"), sig: JNIString::from("(Landroid/content/Context;)Z"), fn_ptr: init as *mut c_void }, ]; let env = jvm.get_env().unwrap(); let version = env.get_version().unwrap(); // 注册native 方法 let cls = match env.find_class("com/hulytu/android/hijack/AntiHijack") { Ok(clazz) => clazz, Err(_) => { return JNI_ERR; } }; let result = env.register_native_methods(cls, &methods); return if result.is_ok() { version.into() } else { JNI_ERR }; } fn init(env: JNIEnv, _: jclass, context: JObject) -> jboolean { if !is_valid_package(&env, &context) { return JNI_FALSE; } JNI_TRUE } fn is_valid_package(env: &JNIEnv, context: &JObject) -> bool { // #1 let package_name = env.call_method(*context, "getPackageName", "()Ljava/lang/String;", &[]).unwrap(); // app: com.hulytu.android let pkg = app_package!(); let expect_package = env.new_string(pkg) .expect("load string error."); // 暂时没想到好的比较方案 临时解决办法 let obj = JObject::from(expect_package); let equals = env.call_method(obj, "equals", "(Ljava/lang/Object;)Z", &[package_name]).unwrap(); // 比较包名 if !equals.z().unwrap() { return false; } // #2 // 获取签名 let manager = env.call_method(*context, "getPackageManager", "()Landroid/content/pm/PackageManager;", &[]).unwrap().l(); let version_clazz = env.find_class("android/os/Build$VERSION").unwrap(); let sdk_int = env.get_static_field(version_clazz, "SDK_INT", "I").unwrap().i().unwrap(); let args = [package_name, JValue::Int(if sdk_int >= 28 { 0x08000000 } else { 64 })]; let result = env.call_method(manager.unwrap(), "getPackageInfo", "(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;", &args); let pkg_info = match result { Ok(info) => info, Err(_) => return false, }; let signatures: JValue; if sdk_int >= 28 { let result = env.get_field(pkg_info.l().unwrap(), "signingInfo", "Landroid/content/pm/SigningInfo;"); let info = match result { Ok(sif) => sif, Err(_) => return false, }; let result = env.call_method(info.l().unwrap(), "getApkContentsSigners", "()[Landroid/content/pm/Signature;", &[]); signatures = match result { Ok(rs) => rs, Err(_) => return false, }; } else { // 低于 28 获取方式 let result = env.get_field(pkg_info.l().unwrap(), "signatures", "[Landroid/content/pm/Signature;"); signatures = match result { Ok(rs) => rs, Err(_) => return false, }; } let signatures = signatures.l().unwrap(); if signatures.is_null() { return false; } // #3 let array = jobjectArray::from(*signatures); if env.get_array_length(array).unwrap() == 0 { return false; } let signature = env.get_object_array_element(array, jsize::from(0)).unwrap(); if signature.is_null() { return false; } let hash = env.call_method(signature, "hashCode", "()I", &[]).unwrap().i().unwrap(); hash == signature!() }
-
动态注册
这部分我使用动态注册的方式来绑定到
com.hulytu.android.hijack.AntiHijack#init(Context)
方法。#[no_mangle] #[allow(non_snake_case)] fn JNI_OnLoad(jvm: JavaVM, _reserved: *mut c_void) -> jint { let methods = [ NativeMethod { name: JNIString::from("init"), sig: JNIString::from("(Landroid/content/Context;)Z"), fn_ptr: init as *mut c_void }, ]; let env = jvm.get_env().unwrap(); let version = env.get_version().unwrap(); // 注册native 方法 let cls = match env.find_class("com/hulytu/android/hijack/AntiHijack") { Ok(clazz) => clazz, Err(_) => { return JNI_ERR; } }; let result = env.register_native_methods(cls, &methods); return if result.is_ok() { version.into() } else { JNI_ERR }; }
-
签名校验
fn is_valid_package(env: &JNIEnv, context: &JObject) -> bool { // #1 let package_name = env.call_method(*context, "getPackageName", "()Ljava/lang/String;", &[]).unwrap(); // app: com.hulytu.android let pkg = app_package!(); let expect_package = env.new_string(pkg) .expect("load string error."); // 暂时没想到好的比较方案 临时解决办法 let obj = JObject::from(expect_package); let equals = env.call_method(obj, "equals", "(Ljava/lang/Object;)Z", &[package_name]).unwrap(); // 比较包名 if !equals.z().unwrap() { return false; } // #2 // 获取签名 let manager = env.call_method(*context, "getPackageManager", "()Landroid/content/pm/PackageManager;", &[]).unwrap().l(); let version_clazz = env.find_class("android/os/Build$VERSION").unwrap(); let sdk_int = env.get_static_field(version_clazz, "SDK_INT", "I").unwrap().i().unwrap(); let args = [package_name, JValue::Int(if sdk_int >= 28 { 0x08000000 } else { 64 })]; let result = env.call_method(manager.unwrap(), "getPackageInfo", "(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;", &args); let pkg_info = match result { Ok(info) => info, Err(_) => return false, }; let signatures: JValue; if sdk_int >= 28 { let result = env.get_field(pkg_info.l().unwrap(), "signingInfo", "Landroid/content/pm/SigningInfo;"); let info = match result { Ok(sif) => sif, Err(_) => return false, }; let result = env.call_method(info.l().unwrap(), "getApkContentsSigners", "()[Landroid/content/pm/Signature;", &[]); signatures = match result { Ok(rs) => rs, Err(_) => return false, }; } else { // 低于 28 获取方式 let result = env.get_field(pkg_info.l().unwrap(), "signatures", "[Landroid/content/pm/Signature;"); signatures = match result { Ok(rs) => rs, Err(_) => return false, }; } let signatures = signatures.l().unwrap(); if signatures.is_null() { return false; } // #3 let array = jobjectArray::from(*signatures); if env.get_array_length(array).unwrap() == 0 { return false; } let signature = env.get_object_array_element(array, jsize::from(0)).unwrap(); if signature.is_null() { return false; } let hash = env.call_method(signature, "hashCode", "()I", &[]).unwrap().i().unwrap(); hash == signature!() }
-
宏定义
上面使用了两个宏
// 校验的包名 macro_rules! app_package { () => { "com.hulytu.android" }; } // 检验的签名 hash-code macro_rules! signature { () => { -779219788 }; }
主要是方便在修改代码的时候不需要去到具体方法里面去修改
-
构建打包,如构建
armabi-v7
命令
cargo ndk -t armeabi-v7a -o ./jniLibs build --release
正常构建完成后,会在当前目录下生成 jniLibs
,把此目录复制到项目中,如 app/src/main/jniLibs
;在项目中需要的地方调用。
后面计划:gitee
上代码已经完成,博客有延后
- 防
mt 管理器
一键过签名校验 - 使用
rust
对字符串进行签名