移动安全之Android逆向系列:NDK开发&JNI接口
字数 2449 2025-08-09 15:23:15

Android逆向系列:NDK开发与JNI接口详解

一、NDK开发基础

1.1 NDK简介

Android NDK(Native Development Kit)是原生开发工具包,用于:

  • 编译生成.so共享库文件
  • 编译生成基于ARM架构的可执行程序
  • 实现Java与C/C++代码的交互

1.2 交叉编译概念

  • 平台差异:Linux系统通常使用x86_64架构,而Android手机基本使用ARM架构
  • 交叉编译器:在本平台编译其他平台可执行程序的工具
  • NDK作用:在Windows/Linux上编译出基于ARM架构的程序

二、NDK环境配置

2.1 安装步骤

  1. 下载NDK工具包(如android-ndk-r10e)
  2. 解压至系统目录(如C:\android-ndk-r10e)
  3. 配置环境变量,添加NDK根目录到PATH

2.2 目录结构

jni/
  ├── Android.mk    # 模块编译规则
  ├── Application.mk # 应用配置
  └── anquan.c      # C源文件

三、NDK编译流程

3.1 编写C源文件

示例anquan.c

#include <stdio.h>

int main() {
    printf("test");
    printf("\n");
    return 0;
}

3.2 Makefile文件详解

Android.mk

LOCAL_PATH := $(call my-dir)  # 获取当前目录路径
include $(CLEAR_VARS)         # 清理LOCAL_变量

LOCAL_ARM_MODE := arm        # 指定ARM指令集
LOCAL_MODULE := anquan       # 模块名称
LOCAL_SRC_FILES := anquan.c  # 源文件

# PIE安全机制编译选项
LOCAL_CFLAGS += -pie -fPIE
LOCAL_LDFLAGS += -pie -fPIE

include $(BUILD_EXECUTABLE)   # 编译为可执行文件

关键变量说明:

  • LOCAL_PATH:当前目录路径
  • CLEAR_VARS:清理LOCAL_开头的全局变量
  • LOCAL_ARM_MODE:指定指令集(arm/thumb)
  • LOCAL_MODULE:输出文件名
  • LOCAL_SRC_FILES:源文件列表
  • BUILD_EXECUTABLE:编译目标类型(可执行文件)

Application.mk

APP_ABI := x86 armeabi-v7a
  • APP_ABI:指定目标CPU架构
    • armeabi-v7a:第7代及以上ARM处理器

3.3 编译命令

在jni目录下执行:

ndk-build

输出目录结构:

libs/
  └── armeabi-v7a/
      └── anquan    # 生成的可执行文件

四、Android设备测试

4.1 ADB操作流程

  1. 连接设备并检查:

    adb devices
    
  2. 推送可执行文件:

    adb push libs/armeabi-v7a/anquan /data/local/tmp
    
  3. 进入设备shell:

    adb shell
    su
    cd /data/local/tmp
    
  4. 修改权限并执行:

    chmod 777 anquan
    ./anquan
    

五、JNI接口详解

5.1 JNI概述

JNI(Java Native Interface)是:

  • Java与Native代码(C/C++)之间的桥梁
  • 一大组函数接口API集合
  • 实现双向调用(Java↔Native)

5.2 JNI作用

  1. 保护核心逻辑:反编译工具(如Jadx)只能显示方法名,无法显示Native方法体
  2. 复用现有C/C++库
  3. 提升关键代码性能
  4. 实现跨平台开发

5.3 JNI头文件分析

基本类型定义

Java类型与JNI类型对应关系:

Java类型 JNI类型
byte jbyte
char jchar
short jshort
int jint
long jlong
float jfloat
double jdouble
boolean jboolean
void void

引用类型对应关系:

Java类型 JNI类型
所有对象 jobject
java.lang.Class jclass
java.lang.String jstring
数组 jarray
Throwable jthrowable

本地接口结构体

主要包含三类方法:

  1. Call方法 - 调用Java方法

    jobject CallObjectMethod(JNIEnv*, jobject, jmethodID, ...);
    

    参数说明:

    • JNIEnv*:JNI环境指针
    • jobject:Java对象实例
    • jmethodID:方法ID(通过GetMethodID获取)
    • ...:可变参数(方法参数)
  2. Get方法 - 获取字段/方法ID

    jfieldID GetFieldID(JNIEnv*, jclass, const char*, const char*);
    

    参数说明:

    • jclass:Java类对象(FindClass获取)
    • const char*:字段名
    • const char*:字段签名
  3. Set方法 - 设置字段值

    void SetIntField(JNIEnv*, jobject, jfieldID, jint);
    

5.4 方法签名格式

Java方法在JNI中的表示形式:

(参数类型)返回值类型

示例:

  • ()V:无参,返回void
  • (I)V:接收int,返回void
  • (Ljava/lang/String;)Z:接收String,返回boolean

基本类型签名:

类型 签名
boolean Z
byte B
char C
short S
int I
long J
float F
double D
void V
L全限定名;
数组 [类型

六、实践建议

  1. 编译优化

    • 使用最新NDK版本
    • 合理设置APP_ABI(armeabi-v7a, arm64-v8a等)
    • 启用PIE安全机制(Android 5.0+要求)
  2. JNI开发

    • 使用javahjavac -h自动生成头文件
    • 注意内存管理(局部引用/全局引用)
    • 处理Java异常(ExceptionCheck/ExceptionOccurred)
  3. 逆向分析

    • IDA Pro分析.so文件
    • 关注JNI_OnLoad函数
    • 跟踪JNIEnv的函数调用
  4. 兼容性考虑

    • 32位/64位兼容
    • 不同Android版本API差异
    • 多CPU架构支持

通过本教程,您应该掌握了NDK开发的基本流程、JNI接口的核心概念以及如何在Android设备上运行原生程序。这些知识是Android逆向工程和底层开发的重要基础。

Android逆向系列:NDK开发与JNI接口详解 一、NDK开发基础 1.1 NDK简介 Android NDK(Native Development Kit)是原生开发工具包,用于: 编译生成.so共享库文件 编译生成基于ARM架构的可执行程序 实现Java与C/C++代码的交互 1.2 交叉编译概念 平台差异 :Linux系统通常使用x86_ 64架构,而Android手机基本使用ARM架构 交叉编译器 :在本平台编译其他平台可执行程序的工具 NDK作用 :在Windows/Linux上编译出基于ARM架构的程序 二、NDK环境配置 2.1 安装步骤 下载NDK工具包(如android-ndk-r10e) 解压至系统目录(如C:\android-ndk-r10e) 配置环境变量,添加NDK根目录到PATH 2.2 目录结构 三、NDK编译流程 3.1 编写C源文件 示例 anquan.c : 3.2 Makefile文件详解 Android.mk 关键变量说明: LOCAL_PATH :当前目录路径 CLEAR_VARS :清理LOCAL_ 开头的全局变量 LOCAL_ARM_MODE :指定指令集(arm/thumb) LOCAL_MODULE :输出文件名 LOCAL_SRC_FILES :源文件列表 BUILD_EXECUTABLE :编译目标类型(可执行文件) Application.mk APP_ABI :指定目标CPU架构 armeabi-v7a :第7代及以上ARM处理器 3.3 编译命令 在jni目录下执行: 输出目录结构: 四、Android设备测试 4.1 ADB操作流程 连接设备并检查: 推送可执行文件: 进入设备shell: 修改权限并执行: 五、JNI接口详解 5.1 JNI概述 JNI(Java Native Interface)是: Java与Native代码(C/C++)之间的桥梁 一大组函数接口API集合 实现双向调用(Java↔Native) 5.2 JNI作用 保护核心逻辑:反编译工具(如Jadx)只能显示方法名,无法显示Native方法体 复用现有C/C++库 提升关键代码性能 实现跨平台开发 5.3 JNI头文件分析 基本类型定义 Java类型与JNI类型对应关系: | Java类型 | JNI类型 | |----------|------------| | byte | jbyte | | char | jchar | | short | jshort | | int | jint | | long | jlong | | float | jfloat | | double | jdouble | | boolean | jboolean | | void | void | 引用类型对应关系: | Java类型 | JNI类型 | |-----------------|-------------| | 所有对象 | jobject | | java.lang.Class | jclass | | java.lang.String| jstring | | 数组 | jarray | | Throwable | jthrowable | 本地接口结构体 主要包含三类方法: Call方法 - 调用Java方法 参数说明: JNIEnv* :JNI环境指针 jobject :Java对象实例 jmethodID :方法ID(通过GetMethodID获取) ... :可变参数(方法参数) Get方法 - 获取字段/方法ID 参数说明: jclass :Java类对象(FindClass获取) const char* :字段名 const char* :字段签名 Set方法 - 设置字段值 5.4 方法签名格式 Java方法在JNI中的表示形式: 示例: ()V :无参,返回void (I)V :接收int,返回void (Ljava/lang/String;)Z :接收String,返回boolean 基本类型签名: | 类型 | 签名 | |---------|------| | boolean | Z | | byte | B | | char | C | | short | S | | int | I | | long | J | | float | F | | double | D | | void | V | | 类 | L全限定名; | | 数组 | [ 类型 | 六、实践建议 编译优化 : 使用最新NDK版本 合理设置APP_ ABI(armeabi-v7a, arm64-v8a等) 启用PIE安全机制(Android 5.0+要求) JNI开发 : 使用 javah 或 javac -h 自动生成头文件 注意内存管理(局部引用/全局引用) 处理Java异常(ExceptionCheck/ExceptionOccurred) 逆向分析 : IDA Pro分析.so文件 关注JNI_ OnLoad函数 跟踪JNIEnv的函数调用 兼容性考虑 : 32位/64位兼容 不同Android版本API差异 多CPU架构支持 通过本教程,您应该掌握了NDK开发的基本流程、JNI接口的核心概念以及如何在Android设备上运行原生程序。这些知识是Android逆向工程和底层开发的重要基础。