美文网首页
Rust NDK 开发#2 - 签名校验

Rust NDK 开发#2 - 签名校验

作者: Artfox丶艺狸 | 来源:发表于2021-12-12 12:53 被阅读0次

    [TOC]

    • 开发工具: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

    # 准备工作

    • Android studioIntelliJ IDEA 建议安装下 Rust 这个插件,安装完会提示安装 Toml ,也一并安装上。
    • Rustcargo 的安装,可参照上一节进行。

    一、 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 实现

    1. 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"
      
    1. 编写 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!()
      }
      
    1. 动态注册

      这部分我使用动态注册的方式来绑定到 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 };
      }
      
    1. 签名校验

      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!()
      }
      
    2. 宏定义

      上面使用了两个宏

      // 校验的包名
      macro_rules! app_package { () => { "com.hulytu.android" }; }
      
      // 检验的签名 hash-code
      macro_rules! signature { () => { -779219788 }; }
      

      主要是方便在修改代码的时候不需要去到具体方法里面去修改

    1. 构建打包,如构建 armabi-v7 命令
      cargo ndk -t armeabi-v7a -o ./jniLibs build --release
      

    正常构建完成后,会在当前目录下生成 jniLibs ,把此目录复制到项目中,如 app/src/main/jniLibs;在项目中需要的地方调用。


    后面计划:gitee 上代码已经完成,博客有延后

    • mt 管理器 一键过签名校验
    • 使用 rust 对字符串进行签名

    本文同步发布到 Rust NDK 开发#2 - 签名校验 - 八阿哥客栈 (hulytu.com)

    相关文章

      网友评论

          本文标题:Rust NDK 开发#2 - 签名校验

          本文链接:https://www.haomeiwen.com/subject/jwsrfrtx.html