应用安全 --- 安卓逆向 之 从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;
}
原版
AI逆向版
根据您提供的 ARM64 汇编代码,这段逻辑非常清晰。它是一个典型的 JNI 方法实现,并且包含了一些 C++ 标准库对象(std::string)的生命周期管理和栈保护机制。
以下是还原后的 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;
}
关键逻辑还原分析:
-
字符串初始化:
在0x1ED20处,汇编代码加载了一个字符串常量aHelloWorldFrom("Hello World from C++!"),并将其作为参数传给了std::string的构造函数。这说明该string对象在创建时就已经包含了这串字符。 -
函数参数传递:
X0寄存器在进入函数时存储的是JNIEnv*指针。- 在
0x1ED3C处,X8(env) 被存入栈中,随后作为参数传给了sub_1EE5C。 sub_1EE5C的第一个参数是string对象的地址,第二个参数是env。
-
栈保护 (Stack Canary):
代码中使用了TPIDR_EL0寄存器(在 ARM64 上用于存储线程局部存储区域),通过0x28偏移量取出 Canary 值并与栈上的值进行对比。这是编译器为了防止缓冲区溢出攻击而加入的防护机制,如果在函数结束时发现值不匹配,会调用__stack_chk_fail终止程序。 -
异常处理 (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 反编译结果问题
// 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. 函数签名识别错误
; 实际汇编
.text:000000000001ED18 STUR X0, [X29,#var_28] ; X0 = JNIEnv* (第1个参数)
.text:000000000001ED1C STR X1, [SP,#0x60+var_30] ; X1 = jobject (第2个参数,被IDA忽略)
// IDA 识别为:
__int64 __fastcall stringFromJNI(_JNIEnv *a1) // ❌ 丢失了第2个参数 jobject
// 正确应该是:
jstring stringFromJNI(JNIEnv* env, jobject thiz) // ✅
原因: IDA 没有加载 JNI 类型库,无法识别 JNI 函数的标准签名规范
2. std::string 构造函数参数识别错误
; 实际汇编调用过程
.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*)
// IDA 错误识别:
std::string::basic_string<decltype(nullptr)>() // ❌ 没有识别出字符串参数
// 正确应该是:
std::string str("Hello World from C++!") // ✅
原因:
┌─────────────────────────────────────────────┐
│ ADRL 是 PC 相对寻址指令 │
│ IDA 在计算相对地址时可能出现偏差 │
│ 导致无法正确解析 aHelloWorldFrom 的实际地址 │
└─────────────────────────────────────────────┘
3. sub_1EE5C 函数参数识别错误
; 实际汇编
.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
// IDA 错误识别:
sub_1EE5C((__int64)v5) // ❌ 只识别出一个参数,且类型错误
// 正确应该是:
sub_1EE5C(&str_obj, env) // ✅ 两个参数
原因:
┌─────────────────────────────────────────────┐
│ IDA 的数据流分析未能追踪 │
│ var_50 中存储的 JNIEnv* 与调用的关联关系 │
│ 导致第2个参数被丢失 │
└─────────────────────────────────────────────┘
4. NewStringUTF 调用对象识别错误
; 实际汇编
.text:000000000001ED44 MOV X1, X0 ; X1 = sub_1EE5C返回的char*
.text:000000000001ED48 LDR X0, [SP,#0x60+var_50] ; X0 = JNIEnv* (从栈上加载!)
.text:000000000001ED4C BL _JNIEnv::NewStringUTF
// IDA 错误识别:
_JNIEnv::NewStringUTF(a1, v1) // ❌ 用的是函数入参a1,而非栈上的env
// 正确应该是:
env->NewStringUTF(c_str) // ✅ env从栈上var_50加载
原因:
┌─────────────────────────────────────────────┐
│ JNIEnv* 被保存到栈(var_50)后 │
│ 又重新从栈加载到 X0 │
│ IDA 的别名分析(Alias Analysis)失败 │
│ 未能识别 var_50 就是最初的 a1(JNIEnv*) │
└─────────────────────────────────────────────┘
根本原因总结
IDA 识别失败的根本原因
├── 1. 环境问题
│ ├── 未加载 JNI 类型库 (jni.h 的类型信息)
│ └── 未加载 NDK libc++ 符号信息
│
├── 2. 分析能力限制
│ ├── PC相对寻址(ADRL)解析偏差
│ ├── 跨基本块的数据流追踪失败
│ └── 栈变量别名分析不精确
│
└── 3. ARM64 ABI 理解偏差
├── 寄存器参数传递规则追踪不完整
└── 栈上临时存储的参数关联失败
修复建议
# 在 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 -> 重新分析
浙公网安备 33010602011771号