Android SO导出符号介绍和安全防范

导出符号介绍

Android 中的共享库(.so 文件)使用导出符号来公开其内部的函数和全局变量,使得其他库或可执行文件(如你的 App 主模块或其他 .so)能够在运行时或链接时找到并使用它们。

导出符号是指那些被明确标记为可供外部使用的符号,当一个.so文件被加载或链接时,只有其导出的符号才能被其他模块(App 或其他 .so)访问调用。

导出符号作用

JNI函数调用时,当 Java/Kotlin 代码通过 System.loadLibrary("native-lib") 加载 .so 后,它需要调用用 native 关键字声明的 JNI 函数,这些 JNI 函数必须作为导出符号存在于.so中,Java 虚拟机(JVM)才能找到并执行它们。

库间互操作:一个.so(A)可能需要调用另一个 .so(B)中提供的功能,B库中需要被A库调用的函数必须导出。

插件系统: 主程序动态加载插件 .so,插件需要导出特定的入口点函数供主程序调用。

导出方式

在 Android NDK 开发中,主要通过JNIEXPORTJNICALL 宏 (主要用于 JNI 函数)方式控制符号可见性。

JNIEXPORT

JNIEXPORT是一个宏定义,用于指定函数的导出方式,告诉编译器这个函数需要被导出,以便Java代码能够调用。

在Windows平台上,通常定义为__declspec(dllexport),而Linux/Android平台上,通常定义为 __attribute__((visibility("default")))

JNICALL

JNICALL是一个宏定义,用于指定函数的调用约定,确保C/C++函数使用正确的调用约定,以便JVM能够正确调用。

在Windows平台上,通常定义为__stdcall, 而Linux/Android平台上,通常为空定义。

用法示例

这是 Android NDK 中最常见的方式,专门用于标记需要被 JVM 调用的 JNI 函数。

在 JNI 函数声明前使用:

JNIEXPORT jstring JNICALL
Java_com_test_symboltest_MainActivity_stringFromJNI(JNIEnv *env, jobject thiz) {
   // 实现代码
   return (*env)->NewStringUTF(env, "Hello from JNI!");
}

宏特性

  • 跨平台兼容性: 不同操作系统有不同的函数导出和调用约定;
  • JVM集成: 确保C/C++函数能够被JVM正确识别和调用;
  • 符号可见性: 确保函数符号在动态库中可见;
  • 必须成对使用: 每个JNI函数都应该同时使用这两个宏;
  • 函数命名规范: 通常遵循Java包名类名_方法名的格式;
  • 参数约定: 第一个参数总是JNIEnv *env,第二个参数根据方法类型而定。

导出符号的去除

导出符号信息如果不去除,则通过函数名可以推断出应用的核心功能,暴露内部架构和设计模式等风险,但是还不能全部都去除,否则就无法去调用。

为了Android so库导出符号的安全性,如果库不涉及到调用则可以全部去除符号,如果库涉及到调用则可以将调用的符号进行保留,其余的可以去除,以下介绍下去除所有导出符号和去除部分导出符号的方式。

移除所有导出符号

  1. 创建个symver.txt 文件,文件内容如下所示:
{
local: *;
};

如图所示:

  1. 在CMakeList.txt中加入编译选项去加载symver.txt文件,如下所示:
add_library(xxxx SHARED ${SRC})

# --version-script 控制动态符号
set_target_properties(xxxx PROPERTIES LINK_FLAGS "-Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/symver.txt")

参考如图所示:

  1. 编译成功后,使用反编译工具(比如ida、ghidra)查看so库,可以看到没有任何导出符号;

移除部分导出符号

  1. 创建个symver.txt 文件,文件内容如下所示:
{
global:
Java_com_test_symboltest_JniTest_encrypt;
Java_com_test_symboltest_MainActivity_stringFromJNI;
local: *;
};

如图所示:

  1. 在CMakeList.txt中加入编译选项去加载symver.txt文件,如下所示:
# --version-script 控制动态符号
set_target_properties(xxxx PROPERTIES LINK_FLAGS "-Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/symver.txt")
  1. 编译成功后,使用反编译工具(比如ida、ghidra)查看so库,可以看到设置的导出符号进行保留,没有设置的就直接去除了;

安全防范

安全问题

虽然so库的不需要的导出符号被去除了,但so库仍然有被调试、逆向和篡改的风险,比如:

被逆向风险

虽然编译生成的二进制文件逆向分析难度较高,但由于相应的反编译工具(比如ida、Ghidra)也成熟强大,依然可以反编译为类C伪代码;

被调试风险

攻击者可通过调试工具附加应用进程进行调试,在调试过程中可能暴露敏感信息(如密钥、算法逻辑);

程序被篡改风险

攻击者可以通过修改应用内存改变程序行为,绕过安全检查或实现恶意功能,导致数据泄露;

防范措施

Android so本身的格式还是属于elf格式,比如:

针对ELF格式的文件,Virbox Protector工具在对ELF程序的保护上有着成熟的方案,可以实现对elf程序的函数级和整体保护,具体方案参考官网文档Native程序保护最佳实践

滚动至顶部
售前客服
周末值班
电话

电话

13910187371