GKLBB

当你经历了暴风雨,你也就成为了暴风雨

导航

应用安全 --- 安卓逆向 之 从ida反编译helloworld的so看缺陷

反编译版本

__int64 __fastcall Java_com_example_helloworld_MainActivity_stringFromJNI(_JNIEnv *a1)
{
const char *v1; // x0
__int64 v3; // [xsp+18h] [xbp-48h]
_BYTE v5[24]; // [xsp+40h] [xbp-20h] BYREF
__int64 v6; // [xsp+58h] [xbp-8h]

v6 = *(_QWORD *)(_ReadStatusReg(TPIDR_EL0) + 40);
std::string::basic_string<decltype(nullptr)>();
v1 = (const char *)sub_1EE5C((__int64)v5);
v3 = _JNIEnv::NewStringUTF(a1, v1);
std::string::~string(v5);
_ReadStatusReg(TPIDR_EL0);
return v3;
}

 

原版

#include <jni.h>
#include <string>

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_helloworld_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = "Hello World from C++!";
    return env->NewStringUTF(hello.c_str());
}

 

AI逆向版

根据您提供的 ARM64 汇编代码,这段逻辑非常清晰。它是一个典型的 JNI 方法实现,并且包含了一些 C++ 标准库对象(std::string)的生命周期管理和栈保护机制。

以下是还原后的 C++ 代码:

C++
#include <jni.h>
#include <string>

// 声明外部或内部函数,根据汇编偏移分析
extern "C" const char* sub_1EE5C(std::string* str_obj, JNIEnv* env);

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_helloworld_MainActivity_stringFromJNI(JNIEnv* env, jobject /* this */) 
{
    // 1. 初始化 std::string 对象,内容初始化为 "Hello World from C++!"
    // 对应汇编: ADRL X1, aHelloWorldFrom ... BL std::string::basic_string
    std::string str_obj("Hello World from C++!");

    // 2. 调用 sub_1EE5C 函数
    // 汇编中将 str_obj 的地址和 env 指针传递给 sub_1EE5C
    // 该函数返回一个 const char* 指针
    const char* c_str = sub_1EE5C(&str_obj, env);

    // 3. 调用 JNI 的 NewStringUTF 方法将 C 字符串转为 Java String
    jstring result = env->NewStringUTF(c_str);

    // 4. 函数结束前,std::string 的析构函数会被调用 (对应 loc_1ED58 处的 BL)
    // 编译器会自动处理异常清理工作 (对应 loc_1ED88 的 cleanup 代码块)
    
    return result;
}

关键逻辑还原分析:

  1. 字符串初始化:
    在 0x1ED20 处,汇编代码加载了一个字符串常量 aHelloWorldFrom ("Hello World from C++!"),并将其作为参数传给了 std::string 的构造函数。这说明该 string 对象在创建时就已经包含了这串字符。

  2. 函数参数传递:

    • X0 寄存器在进入函数时存储的是 JNIEnv* 指针。
    • 在 0x1ED3C 处,X8 (env) 被存入栈中,随后作为参数传给了 sub_1EE5C
    • sub_1EE5C 的第一个参数是 string 对象的地址,第二个参数是 env
  3. 栈保护 (Stack Canary):
    代码中使用了 TPIDR_EL0 寄存器(在 ARM64 上用于存储线程局部存储区域),通过 0x28 偏移量取出 Canary 值并与栈上的值进行对比。这是编译器为了防止缓冲区溢出攻击而加入的防护机制,如果在函数结束时发现值不匹配,会调用 __stack_chk_fail 终止程序。

  4. 异常处理 (C++ ABI):
    汇编中出现了 loc_1ED88 处的 cleanup 逻辑和 .__stack_chk_fail。这表明该代码是在开启了异常支持(如 g++ -fexceptions)的情况下编译的。当 NewStringUTF 可能抛出异常时,C++ 运行时需要确保 std::string 的析构函数被调用,这就是为什么代码里存在 try-catch 风格的清理跳转逻辑。

总结:这段代码的核心功能就是将硬编码的字符串传给一个中间处理函数 sub_1EE5C,最后返回给 Java 层。如果你想修改返回的字符串,最直接的办法就是修改 sub_1EE5C 的返回值或者修改该函数内 std::string 的内容。

 

IDA 未能正确识别的原因分析

原始 IDA 反编译结果问题

C
// IDA 错误识别的结果
__int64 __fastcall Java_com_example_helloworld_MainActivity_stringFromJNI(_JNIEnv *a1)
{
    std::string::basic_string<decltype(nullptr)>(); // ❌ 缺少参数
    v1 = (const char *)sub_1EE5C((__int64)v5);      // ❌ 参数类型错误
    v3 = _JNIEnv::NewStringUTF(a1, v1);             // ❌ 调用对象错误
}

具体原因逐一分析

1. 函数签名识别错误

asm
; 实际汇编
.text:000000000001ED18    STUR    X0, [X29,#var_28]  ; X0 = JNIEnv*  (第1个参数)
.text:000000000001ED1C    STR     X1, [SP,#0x60+var_30] ; X1 = jobject (第2个参数,被IDA忽略)
C
// IDA 识别为:
__int64 __fastcall stringFromJNI(_JNIEnv *a1)  // ❌ 丢失了第2个参数 jobject

// 正确应该是:
jstring stringFromJNI(JNIEnv* env, jobject thiz) // ✅

原因: IDA 没有加载 JNI 类型库,无法识别 JNI 函数的标准签名规范


2. std::string 构造函数参数识别错误

asm
; 实际汇编调用过程
.text:000000000001ED20    ADRL    X1, aHelloWorldFrom  ; X1 = "Hello World from C++!" ← 字符串参数
.text:000000000001ED28    SUB     X0, X29, #-var_20    ; X0 = &str_obj (string对象地址)
.text:000000000001ED30    BL      std::string::basic_string(char const*)
C
// IDA 错误识别:
std::string::basic_string<decltype(nullptr)>()  // ❌ 没有识别出字符串参数

// 正确应该是:
std::string str("Hello World from C++!")        // ✅

原因:

text
┌─────────────────────────────────────────────┐
│  ADRL 是 PC 相对寻址指令                      │
│  IDA 在计算相对地址时可能出现偏差              │
│  导致无法正确解析 aHelloWorldFrom 的实际地址   │
└─────────────────────────────────────────────┘

3. sub_1EE5C 函数参数识别错误

asm
; 实际汇编
.text:000000000001ED34    LDR     X0, [SP,#0x60+var_58]  ; X0 = &str_obj  (第1个参数)
.text:000000000001ED38    LDUR    X8, [X29,#var_28]       ; X8 = JNIEnv*
.text:000000000001ED3C    STR     X8, [SP,#0x60+var_50]   ; 存储JNIEnv*
.text:000000000001ED40    BL      sub_1EE5C                ; 调用时 X0=&str_obj
C
// IDA 错误识别:
sub_1EE5C((__int64)v5)           // ❌ 只识别出一个参数,且类型错误

// 正确应该是:
sub_1EE5C(&str_obj, env)         // ✅ 两个参数

原因:

text
┌─────────────────────────────────────────────┐
│  IDA 的数据流分析未能追踪                     │
│  var_50 中存储的 JNIEnv* 与调用的关联关系     │
│  导致第2个参数被丢失                          │
└─────────────────────────────────────────────┘

4. NewStringUTF 调用对象识别错误

asm
; 实际汇编
.text:000000000001ED44    MOV     X1, X0           ; X1 = sub_1EE5C返回的char*
.text:000000000001ED48    LDR     X0, [SP,#0x60+var_50] ; X0 = JNIEnv* (从栈上加载!)
.text:000000000001ED4C    BL      _JNIEnv::NewStringUTF
C
// IDA 错误识别:
_JNIEnv::NewStringUTF(a1, v1)    // ❌ 用的是函数入参a1,而非栈上的env

// 正确应该是:
env->NewStringUTF(c_str)          // ✅ env从栈上var_50加载

原因:

text
┌─────────────────────────────────────────────┐
│  JNIEnv* 被保存到栈(var_50)后               │
│  又重新从栈加载到 X0                         │
│  IDA 的别名分析(Alias Analysis)失败          │
│  未能识别 var_50 就是最初的 a1(JNIEnv*)      │
└─────────────────────────────────────────────┘

根本原因总结

text
IDA 识别失败的根本原因
├── 1. 环境问题
│   ├── 未加载 JNI 类型库 (jni.h 的类型信息)
│   └── 未加载 NDK libc++ 符号信息
├── 2. 分析能力限制
│   ├── PC相对寻址(ADRL)解析偏差
│   ├── 跨基本块的数据流追踪失败
│   └── 栈变量别名分析不精确
└── 3. ARM64 ABI 理解偏差
    ├── 寄存器参数传递规则追踪不完整
    └── 栈上临时存储的参数关联失败

修复建议

Python
# 在 IDA 中手动修复步骤:

# 1. 加载类型库
File -> Load File -> Parse C Header File -> 加载 jni.h

# 2. 修改函数签名
Y 键 -> 修改为:
jstring Java_..._stringFromJNI(JNIEnv* env, jobject thiz)

# 3. 重新定义变量类型
Alt+Q -> 手动指定变量类型

# 4. 使用 Hex-Rays 插件辅助
Edit -> Plugins -> Hex-Rays Decompiler -> 重新分析

posted on 2026-04-05 07:49  GKLBB  阅读(2)  评论(0)    收藏  举报