Android

Deep parsing and security protection guide for Android SO exported symbols

2025-10-29

In the world of Android development, shared libraries (. so files) play a crucial role. They export symbols to enable other modules to access and call their functions. However, the management and security protection of exported symbols cannot be ignored. Today, let's delve into the secrets of exporting symbols from Android SO and how to build a secure defense for it.


Export symbol: Bridge connecting modules

In the Android system, shared libraries (. so files) expose their internal functions and global variables by exporting symbols. These exported symbols are like "interfaces" that enable other libraries or executable files (such as app main modules or other. so files) to find and use them at runtime or link time.

1. The key to JNI function calls

When we load the. so file through System. loadLibrary ("native lib") in Java/Kotlin code, we can call JNI functions declared with the native keyword. And these JNI functions must exist as export symbols in the. so file for the Java Virtual Machine (JVM) to find and execute them.

2. The link for interoperability between libraries

A. so file (referred to as A) may need to call the functionality provided in another. so file (B). At this point, the functions in library B that need to be called by library A must be exported in order to achieve seamless interoperability between libraries.

3. The cornerstone of the plugin system

In a plugin based architecture, the main program dynamically loads the plugin. so file. These plugins need to export specific entry point functions for the main program to call, in order to achieve functional extension and dynamic loading.


Implementation of exporting symbols

In Android NDK development, symbol visibility is mainly controlled through JNIEXPORT and JNICALL macros.

1. JNIEXPORT: Let functions "go global"

JNIEXPORT is a macro definition used to specify the export method of a function. It tells the compiler that this function needs to be exported so that Java code can call it. On the Windows platform, it is typically defined as __declspec (dllexport), while on the Linux/Android platform, it is typically defined as __attributi__ (visibility ("default")).

2. JNICALL: Ensure the correctness of calling conventions

JNICALL is also a macro definition used to specify the calling conventions for functions. It ensures that C/C++functions use the correct calling conventions so that the JVM can call them correctly. On the Windows platform, it is usually defined as __STDcall, while on the Linux/Android platform, it is usually defined as empty.

3. Usage examples

This is the most common way in Android NDK, specifically used to mark JNI functions that need to be called by JVM. Use these two macros before declaring the JNI function, for example:

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

4. Characteristics of macros

Cross platform compatibility: Different operating systems have different function export and call conventions, and these two macros ensure cross platform compatibility.

JVM integration: They ensure that C/C++functions can be correctly recognized and called by the JVM.

Symbol visibility: Ensure that function symbols are visible in the dynamic library.

Must be used in pairs: Each JNI function should use both macros simultaneously.

Function naming conventions: usually follow the format of Java_ package name_ class name_ method name.

Parameter convention: The first parameter is always JNIEnv * env, and the second parameter depends on the method type.


Removing exported symbols: hiding internal logic

If the exported symbol information is not removed, attackers can infer the core functionality of the application through function names, thereby exposing risks such as internal architecture and design patterns. However, we cannot remove all exported symbols, otherwise we cannot make calls. Therefore, we need to reasonably remove some exported symbols based on the actual situation.

1. Remove all exported symbols

If the library does not involve external calls, all exported symbols can be removed. The specific operation is as follows:

Create a symver.txt file with the following content:

{
local: *;
};

Add compilation options to CMakeLists.txt and load the symver.txt file:

add_library(xxxx SHARED ${SRC})

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

After successful compilation, use decompilation tools (such as IDA, Ghidra) to view the. so library, and you can see that there are no exported symbols.

2. Remove some exported symbols

If the library involves external calls, necessary exported symbols can be retained and other unnecessary symbols can be removed. The specific operation is as follows:

Create a symver.txt file with the following content:

{
    global:
    Java_com_test_symboltest_JniTest_encrypt;
    Java_com_test_symboltest_MainActivity_stringFromJNI;
    local: *;
};

Add compilation options to CMakeLists.txt and load the symver.txt file:

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

After successful compilation, use the decompilation tool to view the. so library, and you can see that the set export symbols are retained, while other symbols that have not been set are removed.


Security precautions: Building a strong defense line for the. so library

Although we have reduced the risk of reverse analysis of the. so library by removing unnecessary export symbols, there is still a possibility of debugging, reverse engineering, and tampering with the. so library. Therefore, we need to take further security precautions.

1. Risk of being reversed

Although the reverse analysis of binary files generated by compilation is difficult, with the continuous development of decompilation tools such as IDA and Ghidra, attackers can still decompile them into C-class pseudocode to obtain critical information.

2. The risk of being debugged

Attackers can attach debugging tools to application processes for debugging, which may expose sensitive information such as keys, algorithm logic, etc. during the debugging process.

3. Risk of program tampering

Attackers can cause data breaches by modifying application memory, altering program behavior, bypassing security checks, or implementing malicious functions.

4. Preventive measures

The. so file itself belongs to ELF format. For ELF format files, we can use professional tools such as Virbox Protector to protect them. For example, functional level protection and overall protection schemes can be adopted, and specific measures can refer to the official documentation of relevant tools.

In the journey of Android development, the management and security protection of exported symbols are crucial links. By reasonably controlling the exported symbols, we can hide the internal logic and reduce the risk of being reverse analyzed; By taking effective security measures, we can build a strong defense line for the. so library and ensure the security of the application. I hope this article can provide valuable reference for Android developers and help them create more secure and reliable Android applications.

more stories
See more