Android C++读取系统级JNI库并校验一致性的技术实现问询
Android JNI库校验和对比实现(防Root替换)
核心逻辑
- 要么提前将JNI库的原始SHA256校验和存入APK的assets目录,要么直接读取APK内的JNI库文件计算基准校验和
- 获取当前应用安装后,系统中JNI库的实际存储路径
- 计算系统中JNI库的校验和,与APK内的基准值对比,不一致则判定库被篡改
C++代码示例
1. 计算文件SHA256校验和的工具函数
#include <openssl/sha.h> #include <fcntl.h> #include <unistd.h> #include <vector> #include <string> std::string calculateSHA256(const char* filePath) { int fd = open(filePath, O_RDONLY); if (fd < 0) { return ""; } SHA256_CTX sha256; SHA256_Init(&sha256); char buffer[4096]; ssize_t bytesRead; while ((bytesRead = read(fd, buffer, sizeof(buffer))) > 0) { SHA256_Update(&sha256, buffer, bytesRead); } close(fd); unsigned char hash[SHA256_DIGEST_LENGTH]; SHA256_Final(hash, &sha256); // 转换为十六进制字符串 char hexHash[SHA256_DIGEST_LENGTH * 2 + 1]; for (int i = 0; i < SHA256_DIGEST_LENGTH; ++i) { sprintf(hexHash + i * 2, "%02x", hash[i]); } hexHash[SHA256_DIGEST_LENGTH * 2] = '\0'; return std::string(hexHash); }
2. 读取APK内assets中的基准校验和
如果是提前将校验和写入lib_baseline_sha256.txt并存入assets:
#include <android/asset_manager.h> #include <android/asset_manager_jni.h> std::string getBaselineChecksum(AAssetManager* assetManager) { AAsset* asset = AAssetManager_open(assetManager, "lib_baseline_sha256.txt", AASSET_MODE_BUFFER); if (!asset) { return ""; } const char* buffer = (const char*)AAsset_getBuffer(asset); off_t length = AAsset_getLength(asset); std::string checksum(buffer, length); AAsset_close(asset); // 去除可能的换行符 size_t pos = checksum.find('\n'); if (pos != std::string::npos) { checksum = checksum.substr(0, pos); } return checksum; }
3. 直接读取APK内JNI库计算基准校验和
如果不想预存校验和,直接读取APK内的原始库文件计算:
std::string calculateBaselineFromAsset(AAssetManager* assetManager, const char* libPath) { AAsset* asset = AAssetManager_open(assetManager, libPath, AASSET_MODE_STREAMING); if (!asset) { return ""; } SHA256_CTX sha256; SHA256_Init(&sha256); char buffer[4096]; int bytesRead; while ((bytesRead = AAsset_read(asset, buffer, sizeof(buffer))) > 0) { SHA256_Update(&sha256, buffer, bytesRead); } AAsset_close(asset); unsigned char hash[SHA256_DIGEST_LENGTH]; SHA256_Final(hash, &sha256); char hexHash[SHA256_DIGEST_LENGTH * 2 + 1]; for (int i = 0; i < SHA256_DIGEST_LENGTH; ++i) { sprintf(hexHash + i * 2, "%02x", hash[i]); } hexHash[SHA256_DIGEST_LENGTH * 2] = '\0'; return std::string(hexHash); }
4. 获取当前JNI库的系统路径
#include <dlfcn.h> #include <string> std::string getCurrentLibPath() { // 获取当前库的句柄(假设当前代码就在要校验的JNI库中) void* handle = dlopen(nullptr, RTLD_NOW); if (!handle) { return ""; } Dl_info info; if (dladdr((void*)&getCurrentLibPath, &info) == 0) { dlclose(handle); return ""; } std::string path(info.dli_fname); dlclose(handle); return path; }
5. 主校验入口(JNI调用函数)
#include <jni.h> extern "C" JNIEXPORT jboolean JNICALL Java_com_your_package_SecurityChecker_checkLibIntegrity(JNIEnv* env, jobject thiz, jobject assetManager) { AAssetManager* am = AAssetManager_fromJava(env, assetManager); if (!am) { return JNI_FALSE; } // 二选一:使用预存校验和 std::string baseline = getBaselineChecksum(am); // 或者直接读取APK内的库计算(需替换为对应架构的库路径) // std::string baseline = calculateBaselineFromAsset(am, "lib/arm64-v8a/libyour_core.so"); if (baseline.empty()) { return JNI_FALSE; } std::string systemLibPath = getCurrentLibPath(); if (systemLibPath.empty()) { return JNI_FALSE; } std::string systemChecksum = calculateSHA256(systemLibPath.c_str()); if (systemChecksum.empty()) { return JNI_FALSE; } return (baseline == systemChecksum) ? JNI_TRUE : JNI_FALSE; }
关键注意事项
- 依赖配置:需要在NDK项目中链接OpenSSL库,或改用Android系统内置的
libcrypto.so - 架构适配:APK内的JNI库路径需匹配设备架构(如armeabi-v7a、arm64-v8a),可通过
android_getCpuAbi()获取当前架构 - 权限问题:Android 10+无需额外权限即可读取自身安装目录,低版本需确保应用有对应文件读取权限
- 增强防护:可将基准校验和加密存储,避免攻击者直接篡改APK内的校验和文件
- Root环境联动:结合Root检测逻辑,在Root环境下强制触发校验,甚至终止应用运行
内容的提问来源于stack exchange,提问作者suren
相关产品推荐
相关产品推荐

