You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多
文档控制台
免费开始使用

从NDK提取隐藏字符串及native-lib.cpp多字符串处理咨询

从NDK提取隐藏字符串 & 扩展Native层字符串处理方案

嘿,我来帮你搞定这个问题——先解决你关心的隐藏字符串不被轻易提取的需求,再聊聊怎么在native-lib.cpp里更灵活地处理更多字符串。


一、NDK中隐藏字符串的核心方案:加密+运行时解密

你当前的JNI代码里,字符串是直接硬编码在二进制文件里的,用strings工具或者IDA这类逆向工具一眼就能揪出来。要隐藏它们,最简单高效的方式是提前加密字符串,在JNI运行时解密,推荐用异或加密(性能开销极小,适合Native层)。

完整实现代码

#include <jni.h>
#include <cstring>
#include <cstdint>

// 自定义异或密钥(随便选一个字节值,自己记好就行)
static const uint8_t XOR_KEY = 0x1F;

// 解密函数:对字符串每个字节做异或运算
static void xor_decrypt(uint8_t* str, size_t str_len) {
    for (size_t i = 0; i < str_len; ++i) {
        str[i] ^= XOR_KEY;
    }
}

JNIEXPORT jobjectArray JNICALL Java_como_foo_bar_getData(JNIEnv *env, jobject jobj){
    // 这里是提前加密后的字符串(用Python脚本把原字符串每个字节异或0x1F生成)
    uint8_t encrypted_strs[4][32] = {
        {0x46, 0x38, 0x3D, 0x3D, 0x3E, 0x12, 0x27, 0x3F, 0x3B, 0x3C, 0x12, 0x22, 0x3E, 0x12, 0x00},
        {0x46, 0x38, 0x3D, 0x3D, 0x3E, 0x12, 0x27, 0x3F, 0x3B, 0x3C, 0x12, 0x20, 0x3E, 0x12, 0x00},
        {0x46, 0x38, 0x3D, 0x3D, 0x3E, 0x12, 0x27, 0x3F, 0x3B, 0x3C, 0x12, 0x21, 0x3E, 0x12, 0x00},
        {0x46, 0x38, 0x3D, 0x3D, 0x3E, 0x12, 0x27, 0x3F, 0x3B, 0x3C, 0x12, 0x23, 0x3E, 0x12, 0x00}
    };
    const size_t str_count = sizeof(encrypted_strs) / sizeof(encrypted_strs[0]);

    // 先解密所有字符串
    for (size_t i = 0; i < str_count; ++i) {
        xor_decrypt(encrypted_strs[i], strlen((char*)encrypted_strs[i]));
    }

    // 创建Java字符串数组返回
    jclass string_cls = env->FindClass("java/lang/String");
    if (string_cls == nullptr) return nullptr; // 找不到String类,直接返回

    jobjectArray ret_array = env->NewObjectArray(str_count, string_cls, nullptr);
    if (ret_array == nullptr) {
        env->DeleteLocalRef(string_cls);
        return nullptr; // 内存分配失败
    }

    // 把解密后的字符串逐个存入数组
    for (size_t i = 0; i < str_count; ++i) {
        jstring j_str = env->NewStringUTF((char*)encrypted_strs[i]);
        env->SetObjectArrayElement(ret_array, i, j_str);
        env->DeleteLocalRef(j_str); // 释放局部引用,避免JNI内存泄漏
    }

    env->DeleteLocalRef(string_cls);
    return ret_array;
}

为什么这样有效?

加密后的字符串在二进制文件里是乱码,静态分析工具提取不到原内容,只有当程序运行时,JNI层解密后才会生成正常字符串。如果需要更高安全性,可以换成AES等对称加密算法,但异或已经能应付大部分基础逆向分析了。


二、在native-lib.cpp中处理更多字符串的实用方法

如果需要处理大量字符串,或者不想把字符串硬编码在代码里,可以试试下面几种方案:

1. 从Assets资源文件读取字符串

把字符串放在Android项目的assets目录下(比如hidden_strings.txt,每行一个字符串),JNI层通过AssetManager读取,这样字符串不会编译进二进制:

#include <jni.h>
#include <android/asset_manager.h>
#include <android/asset_manager_jni.h>
#include <cstring>

JNIEXPORT jobjectArray JNICALL Java_como_foo_bar_getData(JNIEnv *env, jobject jobj, jobject asset_mgr){
    // 把Java层的AssetManager转成Native层的AAssetManager
    AAssetManager* native_mgr = AAssetManager_fromJava(env, asset_mgr);
    if (native_mgr == nullptr) return nullptr;

    // 打开assets目录下的文件
    AAsset* file = AAssetManager_open(native_mgr, "hidden_strings.txt", AASSET_MODE_BUFFER);
    if (file == nullptr) return nullptr;

    // 读取文件内容
    const char* file_content = (const char*)AAsset_getBuffer(file);
    off_t file_len = AAsset_getLength(file);
    if (file_len <= 0) {
        AAsset_close(file);
        return nullptr;
    }

    // 这里可以按行分割内容(示例用简单的换行分割)
    // 先统计行数
    size_t line_count = 1;
    for (off_t i = 0; i < file_len; ++i) {
        if (file_content[i] == '\n') line_count++;
    }

    // 创建字符串数组
    jclass string_cls = env->FindClass("java/lang/String");
    jobjectArray ret_array = env->NewObjectArray(line_count, string_cls, nullptr);
    if (ret_array == nullptr) {
        AAsset_close(file);
        env->DeleteLocalRef(string_cls);
        return nullptr;
    }

    // 分割字符串并存入数组
    char* line_start = (char*)file_content;
    size_t idx = 0;
    for (off_t i = 0; i < file_len; ++i) {
        if (file_content[i] == '\n' || i == file_len - 1) {
            size_t line_len = i - (line_start - file_content) + 1;
            char line_buf[256];
            strncpy(line_buf, line_start, line_len);
            line_buf[line_len - (file_content[i] == '\n' ? 1 : 0)] = '\0'; // 去掉换行符
            jstring j_str = env->NewStringUTF(line_buf);
            env->SetObjectArrayElement(ret_array, idx++, j_str);
            env->DeleteLocalRef(j_str);
            line_start = (char*)file_content + i + 1;
        }
    }

    // 清理资源
    AAsset_close(file);
    env->DeleteLocalRef(string_cls);
    return ret_array;
}

注意:要在CMakeLists.txt里添加target_link_libraries(your-lib-name android),链接Android的Asset库。

2. 动态生成字符串

如果字符串有规律,可以在运行时动态拼接,避免硬编码完整字符串:

char buffer[64];
// 动态生成"Hello from C++"、"Hello from D++"等
for (char c = 'C'; c <= 'F'; ++c) {
    snprintf(buffer, sizeof(buffer), "Hello from %c++", c);
    jstring j_str = env->NewStringUTF(buffer);
    // 存入数组...
    env->DeleteLocalRef(j_str);
}

3. 延迟加载的字符串池

把加密后的字符串数据存在一个数组里,需要的时候再解密,减少内存占用同时增加逆向难度:

// 只在需要使用某个字符串时才解密
static jstring get_hidden_string(JNIEnv* env, uint8_t* encrypted_str) {
    xor_decrypt(encrypted_str, strlen((char*)encrypted_str));
    jstring j_str = env->NewStringUTF((char*)encrypted_str);
    // 可以重新加密回去,避免内存里留下明文
    xor_decrypt(encrypted_str, strlen((char*)encrypted_str));
    return j_str;
}

对你原有代码的小补充

你原来的代码里NewObjectArray的第三个参数没写完,应该传nullptr(表示数组初始元素为空);另外要记得释放JNI局部引用,不然会导致内存泄漏哦~

内容的提问来源于stack exchange,提问作者user9427911

火山引擎 最新活动