[TOC]
- 開發(fā)工具:
Android Studio 2020.3.1
- Rust 版本:
1.57.0
- NDK 版本:
21.4.7075529
- 電腦系統(tǒng):
Windows 10 家庭中文版 (21H1) 64位
在開發(fā)中则北,為了防止別人對我們的apk
進(jìn)行二次打包,都會加上簽名校驗的操作朋魔,這部分代碼寫在java
層對于破解來說形同虛設(shè),寫在NDK
可以一定程度上的增加破解的難度显押。使用 C/C++
寫的簽名校驗例子很多,下面分享下關(guān)于使用 Rust
編寫的校驗代碼傻挂。如果只想看代碼乘碑,可直接訪問gitee
倉庫
Java
代碼:hijack/src/main/java/com/hulytu/android/hijack/AntiHijack.java · Chris/ThinDroid
Rust
代碼:hijack_rust · Chris/ThinDroid
# 準(zhǔn)備工作
-
Android studio
或IntelliJ IDEA
建議安裝下Rust
這個插件,安裝完會提示安裝Toml
金拒,也一并安裝上兽肤。 -
Rust
和cargo
的安裝,可參照上一節(jié)進(jìn)行绪抛。
一资铡、 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 實現(xiàn)
-
rust 項目創(chuàng)建
上一節(jié)我講了
cargo-ndk
的安裝笤休,這節(jié)就直接使用cargo
來創(chuàng)建一個項目,這個目錄可以放在和當(dāng)前項目目錄下症副,也可以放在其他地方店雅,建議和當(dāng)前項目放在一起。- 可以使用
Android studio
自帶的控制臺(Terminal
一般在底部位置)進(jìn)入到當(dāng)前項目目錄下贞铣,我的項目叫ThinDroid
那打開的效果是這樣的:
- 可以使用
-
然后輸入命令
cargo new hijack_rust --lib
然后會在當(dāng)前目錄下生成
hijack_rust
這個目錄闹啦,名字可以取其他的。 -
添加
jni
依賴進(jìn)入
hijack_rust
目錄下辕坝,打開Cargo.toml
窍奋,在dependencies
節(jié)點下添加jni = "0.19.0"
添加
lib
節(jié)點,其中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
文件酱畅,刪除原來的代碼先放完整代碼费变,后面進(jìn)行拆解
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!() }
-
動態(tài)注冊
這部分我使用動態(tài)注冊的方式來綁定到
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 }; }
主要是方便在修改代碼的時候不需要去到具體方法里面去修改
- 構(gòu)建打包圣贸,如構(gòu)建
armabi-v7
命令cargo ndk -t armeabi-v7a -o ./jniLibs build --release
正常構(gòu)建完成后挚歧,會在當(dāng)前目錄下生成 jniLibs
,把此目錄復(fù)制到項目中吁峻,如 app/src/main/jniLibs
滑负;在項目中需要的地方調(diào)用。
后面計劃:gitee
上代碼已經(jīng)完成用含,博客有延后
- 防
mt 管理器
一鍵過簽名校驗 - 使用
rust
對字符串進(jìn)行簽名
本文同步發(fā)布到 Rust NDK 開發(fā)#2 - 簽名校驗 - 八阿哥客棧 (hulytu.com)