逆向工程 --- AI反编译可行性测试
android x86 so 输出helloworld字符串
反汇编
=== S U B R O U T I N E ======================================= .text:000237F0 .text:000237F0 ; Attributes: bp-based frame .text:000237F0 .text:000237F0 ; int __cdecl Java_com_example_myapplication1_MainActivity_stringFromJNI(_JNIEnv *) .text:000237F0 public Java_com_example_myapplication1_MainActivity_stringFromJNI .text:000237F0 Java_com_example_myapplication1_MainActivity_stringFromJNI proc near .text:000237F0 ; DATA XREF: LOAD:00001420↑o .text:000237F0 .text:000237F0 var_30 = dword ptr -30h .text:000237F0 var_2C = dword ptr -2Ch .text:000237F0 var_28 = dword ptr -28h .text:000237F0 var_24 = dword ptr -24h .text:000237F0 var_20 = dword ptr -20h .text:000237F0 var_1C = dword ptr -1Ch .text:000237F0 var_18 = dword ptr -18h .text:000237F0 var_14 = byte ptr -14h .text:000237F0 var_8 = dword ptr -8 .text:000237F0 arg_0 = dword ptr 8 .text:000237F0 arg_4 = dword ptr 0Ch .text:000237F0 .text:000237F0 ; __unwind { // __gxx_personality_v0 .text:000237F0 push ebp .text:000237F1 mov ebp, esp .text:000237F3 push ebx .text:000237F4 sub esp, 34h .text:000237F7 call $+5 .text:000237FC .text:000237FC loc_237FC: ; DATA XREF: Java_com_example_myapplication1_MainActivity_stringFromJNI+D↓o .text:000237FC pop ebx .text:000237FD add ebx, (offset off_581EC - offset loc_237FC) .text:00023803 mov [ebp+var_28], ebx .text:00023806 mov eax, [ebp+arg_4] .text:00023809 mov eax, [ebp+arg_0] .text:0002380C mov eax, large gs:14h .text:00023812 mov [ebp+var_8], eax .text:00023815 lea ecx, (aHelloFromC - 581ECh)[ebx] ; "Hello from C++" .text:0002381B mov eax, esp .text:0002381D mov [eax+4], ecx .text:00023820 lea ecx, [ebp+var_14] .text:00023823 mov [ebp+var_2C], ecx .text:00023826 mov [eax], ecx .text:00023828 call __ZNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEC2B8ne180000ILi0EEEPKc ; std::string::basic_string<0>(char const*) .text:0002382D mov ebx, [ebp+var_28] .text:00023830 mov ecx, [ebp+var_2C] .text:00023833 mov eax, [ebp+arg_0] .text:00023836 mov [ebp+var_24], eax .text:00023839 mov eax, esp .text:0002383B mov [eax], ecx .text:0002383D call sub_239A0 .text:00023842 mov ebx, [ebp+var_28] .text:00023845 mov ecx, [ebp+var_24] .text:00023848 mov edx, eax .text:0002384A ; try { .text:0002384A mov eax, esp .text:0002384C mov [eax+4], edx .text:0002384F mov [eax], ecx .text:00023851 call __ZN7_JNIEnv12NewStringUTFEPKc ; _JNIEnv::NewStringUTF(char const*) .text:00023851 ; } // starts at 2384A .text:00023856 mov [ebp+var_20], eax .text:00023859 jmp $+5 .text:0002385E ; --------------------------------------------------------------------------- .text:0002385E .text:0002385E loc_2385E: ; CODE XREF: Java_com_example_myapplication1_MainActivity_stringFromJNI+69↑j .text:0002385E mov ebx, [ebp+var_28] .text:00023861 mov eax, esp .text:00023863 lea ecx, [ebp+var_14] .text:00023866 mov [eax], ecx .text:00023868 call __ZNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEED2Ev ; std::string::~string() .text:0002386D mov eax, large gs:14h .text:00023873 mov ecx, [ebp+var_8] .text:00023876 cmp eax, ecx .text:00023878 jnz loc_238C0 .text:0002387E mov eax, [ebp+var_20] .text:00023881 add esp, 34h .text:00023884 pop ebx .text:00023885 pop ebp .text:00023886 retn .text:00023887 ; --------------------------------------------------------------------------- .text:00023887 ; cleanup() // owned by 2384A .text:00023887 mov ebx, [ebp+var_28] .text:0002388A mov [ebp+var_18], eax .text:0002388D mov [ebp+var_1C], edx .text:00023890 lea eax, [ebp+var_14] .text:00023893 mov [esp], eax .text:00023896 call __ZNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEED2Ev ; std::string::~string() .text:0002389B mov eax, [ebp+var_18] .text:0002389E mov [ebp+var_30], eax .text:000238A1 mov eax, large gs:14h .text:000238A7 mov ecx, [ebp+var_8] .text:000238AA cmp eax, ecx .text:000238AC jnz loc_238C0 .text:000238B2 mov ebx, [ebp+var_28] .text:000238B5 mov eax, [ebp+var_30] .text:000238B8 mov [esp], eax .text:000238BB call sub_50B30 .text:000238C0 ; --------------------------------------------------------------------------- .text:000238C0 .text:000238C0 loc_238C0: ; CODE XREF: Java_com_example_myapplication1_MainActivity_stringFromJNI+88↑j .text:000238C0 ; Java_com_example_myapplication1_MainActivity_stringFromJNI+BC↑j .text:000238C0 mov ebx, [ebp+var_28] .text:000238C3 call ___stack_chk_fail .text:000238C3 ; } // starts at 237F0 .text:000238C3 Java_com_example_myapplication1_MainActivity_stringFromJNI endp .text:000238C3 .text:000238C3 ; --------------------------------------------------------------------------- .text:000238C8 align 10h
注解分析版本:
=== S U B R O U T I N E ======================================= .text:000237F0 .text:000237F0 ; Attributes: bp-based frame .text:000237F0 ; C++源码对应:extern "C" JNIEXPORT jstring JNICALL Java_com_example_myapplication1_MainActivity_stringFromJNI(JNIEnv *env, jobject /* this */) .text:000237F0 ; int __cdecl Java_com_example_myapplication1_MainActivity_stringFromJNI(_JNIEnv *) .text:000237F0 public Java_com_example_myapplication1_MainActivity_stringFromJNI .text:000237F0 Java_com_example_myapplication1_MainActivity_stringFromJNI proc near .text:000237F0 ; DATA XREF: LOAD:00001420↑o .text:000237F0 ; 局部变量定义(对应C++函数内的变量) .text:000237F0 var_30 = dword ptr -30h ; 异常处理相关变量 .text:000237F0 var_2C = dword ptr -2Ch ; std::string对象指针 .text:000237F0 var_28 = dword ptr -28h ; 程序基址(PIC代码) .text:000237F0 var_24 = dword ptr -24h ; JNIEnv *env 指针 .text:000237F0 var_20 = dword ptr -20h ; jstring result 返回值 .text:000237F0 var_1C = dword ptr -1Ch ; 异常处理相关 .text:000237F0 var_18 = dword ptr -18h ; 异常处理相关 .text:000237F0 var_14 = byte ptr -14h ; std::string hello 对象 .text:000237F0 var_8 = dword ptr -8 ; 栈保护值(stack canary) .text:000237F0 arg_0 = dword ptr 8 ; 第一个参数:JNIEnv *env .text:000237F0 arg_4 = dword ptr 0Ch ; 第二个参数:jobject this .text:000237F0 ; C++源码对应:函数开始,try块开始 .text:000237F0 ; __unwind { // __gxx_personality_v0 .text:000237F0 push ebp ; 保存调用者的基指针 .text:000237F1 mov ebp, esp ; 建立新的栈帧 .text:000237F3 push ebx ; 保存ebx寄存器 在 x86 调用约定中,ebx 寄存器被视为一个 被调用者保存 的寄存器。这意味着如果函数(被调用者)打算使用这个寄存器,它需要首先保存其原始值,然后在函数返回之前恢复它。这确保了调用者的状态不会被破坏。在这个特定的子程序中,ebx 寄存器被广泛使用,主要用于位置无关代码(PIC)来计算程序数据段的基地址。正如你在 .text:000237FC pop ebx 处看到的,下一条指令的地址被弹出到 ebx 中,并稍后用于计算。因此,push ebx 是函数序言的关键部分,用于在修改之前保存调用者的 ebx 值。相应的 pop ebx 在 .text:00023884 处在函数返回之前恢复了这个原始值。 .text:000237F4 sub esp, 34h ; 为局部变量分配52字节栈空间, 不确定 ; 获取程序基址(位置无关代码PIC技术) 这段汇编代码实现了一个非常巧妙的技巧,用来 找到程序在内存中的实际位置 。让我用生活中的例子来解释: ### 🏠 生活类比 想象你在一个陌生的城市迷路了,但你知道: - 你现在站的位置 - 你家相对于这个位置的距离和方向 - 那么你就能算出你家的确切地址 ### 为什么需要这样做? 问题 :共享库(.so文件)可能被加载到内存的任意位置 - 编译时:不知道程序会被加载到哪里 - 运行时:需要动态计算数据的真实地址 解决方案 :PIC技术 - 通过相对位置关系来计算绝对地址 - 无论程序加载到哪里,相对偏移都是固定的 1 .text:000237F7 call $+5 ; call 指令会把 返回地址 压入栈 ,返回地址就是 call 指令后面的地址 ( 0x000237FC ) $ 表示 当前指令的地址 (即 0x000237F7 ) +5 表示 偏移5个字节 .text:000237FC .text:000237FC loc_237FC: ; DATA XREF: Java_com_example_myapplication1_MainActivity_stringFromJNI+D↓o .text:000237FC pop ebx ; 弹栈获取当前指令地址到ebx,这样一来 "好,我现在知道我在这个地址" .text:000237FD add ebx, (offset off_581EC - offset loc_237FC) ; 计算程序基址 实际GOT地址 = 当前实际地址 + 编译时偏移量其中:编译时偏移量 = GOT编译地址 - 当前指令编译地址 = off_581EC - loc_237FC 计算出GOT地址后,可以访问所有全局数据 .text:00023803 mov [ebp+var_28], ebx ; 保存基址到局部变量 ; 处理函数参数 .text:00023806 mov eax, [ebp+arg_4] ; 加载jobject this参数(未使用) .text:00023809 mov eax, [ebp+arg_0] ; 加载JNIEnv *env参数 ; 栈保护机制(防止栈溢出攻击) .text:0002380C mov eax, large gs:14h ; 获取栈保护值 标准化 : Linux系统的标准实现,偏移量固定为0x14 .text:00023812 mov [ebp+var_8], eax ; 保存到局部变量 ; C++源码对应:std::string hello = "Hello from C++"; .text:00023815 lea ecx, (aHelloFromC - 581ECh)[ebx] ; 计算"Hello from C++"字符串地址 实际字符串地址 = GOT实际地址 + 相对偏移 .text:0002381B mov eax, esp ; 准备函数调用参数 .text:0002381D mov [eax+4], ecx ; 字符串地址作为第二个参数 ### 数调用约定分析 这段代码调用的是 std::string 的构造函数,其C++原型为: ``` std::string::basic_string(const char* str) ``` 但实际上, 成员函数 还有一个隐含的 this 指针参数,所以完整的参数列表是: 1. 第一个参数 : this 指针(指向 std::string 对象) 2. 第二个参数 : const char* 指针(指向字符串 "Hello from C++") ### 栈布局详解 在x86架构中,参数通过栈传递,布局如下: ``` 栈顶(esp)→[eax+0]←第一个参数(this指针) [eax+4]←第二个参数(字符串地址) [eax+8]←第三个参数(如果有) 栈底方向↓ ``` .text:00023820 lea ecx, [ebp+var_14] ; 获取std::string对象地址 .text:00023823 mov [ebp+var_2C], ecx ; 保存string对象指针 .text:00023826 mov [eax], ecx ; ; 在栈顶位置放置this指针 string对象地址作为第一个参数 .text:00023828 call __ZNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEC2B8ne180000ILi0EEEPKc ; 调用std::string构造函数,创建hello对象 ; 准备调用c_str()方法 .text:0002382D mov ebx, [ebp+var_28] ; 恢复基址 .text:00023830 mov ecx, [ebp+var_2C] ; 获取string对象指针 .text:00023833 mov eax, [ebp+arg_0] ; 获取JNIEnv指针 .text:00023836 mov [ebp+var_24], eax ; 保存JNIEnv指针 .text:00023839 mov eax, esp ; 准备函数调用 .text:0002383B mov [eax], ecx ; string对象作为参数 ; C++源码对应:const char* cStr = hello.c_str(); .text:0002383D call sub_239A0 ; 调用string.c_str()方法 ; 准备调用JNI函数 .text:00023842 mov ebx, [ebp+var_28] ; 恢复基址 .text:00023845 mov ecx, [ebp+var_24] ; 获取JNIEnv指针 .text:00023848 mov edx, eax ; 保存c_str()返回的字符指针 ; C++源码对应:jstring result = env->NewStringUTF(cStr); .text:0002384A ; try { ; try块开始标记 .text:0002384A mov eax, esp ; 准备函数调用 .text:0002384C mov [eax+4], edx ; C字符串指针作为第二个参数 .text:0002384F mov [eax], ecx ; JNIEnv指针作为第一个参数 .text:00023851 call __ZN7_JNIEnv12NewStringUTFEPKc ; 调用JNI函数创建Java字符串 .text:00023851 ; } // starts at 2384A .text:00023856 mov [ebp+var_20], eax ; 保存返回的jstring到result变量 .text:00023859 jmp $+5 ; 跳转到正常清理代码 ; 正常返回路径(无异常) .text:0002385E ; --------------------------------------------------------------------------- .text:0002385E .text:0002385E loc_2385E: ; CODE XREF: Java_com_example_myapplication1_MainActivity_stringFromJNI+69↑j .text:0002385E mov ebx, [ebp+var_28] ; 恢复基址 .text:00023861 mov eax, esp ; 准备析构函数调用 .text:00023863 lea ecx, [ebp+var_14] ; 获取string对象地址 .text:00023866 mov [eax], ecx ; 作为析构函数参数 ; C++源码对应:std::string hello对象自动析构 .text:00023868 call __ZNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEED2Ev ; 调用std::string析构函数 ; 栈保护检查 .text:0002386D mov eax, large gs:14h ; 获取当前栈保护值 .text:00023873 mov ecx, [ebp+var_8] ; 获取保存的栈保护值 .text:00023876 cmp eax, ecx ; 比较两个值 .text:00023878 jnz loc_238C0 ; 如果不匹配,跳转到栈溢出处理 ; C++源码对应:return result; .text:0002387E mov eax, [ebp+var_20] ; 获取返回值(jstring) .text:00023881 add esp, 34h ; 恢复栈指针 .text:00023884 pop ebx ; 恢复ebx寄存器 .text:00023885 pop ebp ; 恢复基指针 .text:00023886 retn ; 函数返回 ; 异常处理路径 .text:00023887 ; --------------------------------------------------------------------------- ; C++源码对应:catch (...) { 异常处理开始 .text:00023887 ; cleanup() // owned by 2384A .text:00023887 mov ebx, [ebp+var_28] ; 恢复基址 .text:0002388A mov [ebp+var_18], eax ; 保存异常信息 .text:0002388D mov [ebp+var_1C], edx ; 保存异常信息 .text:00023890 lea eax, [ebp+var_14] ; 获取string对象地址 .text:00023893 mov [esp], eax ; 作为析构函数参数 ; 异常情况下也要析构std::string对象 .text:00023896 call __ZNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEED2Ev ; 调用std::string析构函数 .text:0002389B mov eax, [ebp+var_18] ; 恢复异常信息 .text:0002389E mov [ebp+var_30], eax ; 保存异常信息 ; 异常路径的栈保护检查 .text:000238A1 mov eax, large gs:14h ; 获取当前栈保护值 .text:000238A7 mov ecx, [ebp+var_8] ; 获取保存的栈保护值 .text:000238AA cmp eax, ecx ; 比较 .text:000238AC jnz loc_238C0 ; 如果不匹配,跳转到栈溢出处理 .text:000238B2 mov ebx, [ebp+var_28] ; 恢复基址 .text:000238B5 mov eax, [ebp+var_30] ; 获取异常信息 .text:000238B8 mov [esp], eax ; 作为参数 ; C++源码对应:return nullptr; (异常情况下的返回) .text:000238BB call sub_50B30 ; 调用异常处理函数(可能返回nullptr) ; 栈溢出检测失败处理 .text:000238C0 ; --------------------------------------------------------------------------- .text:000238C0 .text:000238C0 loc_238C0: ; CODE XREF: Java_com_example_myapplication1_MainActivity_stringFromJNI+88↑j .text:000238C0 ; Java_com_example_myapplication1_MainActivity_stringFromJNI+BC↑j .text:000238C0 mov ebx, [ebp+var_28] ; 恢复基址 .text:000238C3 call ___stack_chk_fail ; 调用栈保护失败处理函数(程序终止) .text:000238C3 ; } // starts at 237F0 .text:000238C3 Java_com_example_myapplication1_MainActivity_stringFromJNI endp ; 函数结束 .text:000238C3 .text:000238C3 ; --------------------------------------------------------------------------- .text:000238C8 align 10h ; 16字节对齐
源码
#include <jni.h> #include <string> extern "C" JNIEXPORT jstring JNICALL Java_com_example_myapplication1_MainActivity_stringFromJNI( JNIEnv* env, jobject /* this */) { std::string hello = "Hello from C++"; return env->NewStringUTF(hello.c_str()); }
ida pro反编译
int __cdecl Java_com_example_myapplication1_MainActivity_stringFromJNI(_JNIEnv *a1) { const char *v1; // eax int v3; // [esp+18h] [ebp-20h] char v4[12]; // [esp+24h] [ebp-14h] BYREF unsigned int v5; // [esp+30h] [ebp-8h] v5 = __readgsdword(0x14u); std::string::basic_string[abi:ne180000]<0>((int)v4, "Hello from C++"); v1 = (const char *)sub_239A0(v4); v3 = _JNIEnv::NewStringUTF(a1, v1); std::string::~string(v4); return v3; }
ai反编译
根据汇编代码分析,这是一个Android JNI函数的反编译结果。我将其转换为对应的C++代码: ``` #include <jni.h> #include <string> // JNI函数:返回一个字符串给Java层 extern "C" JNIEXPORT jstring JNICALL Java_com_example_myapplication1_MainActivity_stringFromJNI(JNIEnv *env, jobject /* this */) { try { // 创建C++字符串对象,内容为"Hello from C++" std::string hello = "Hello from C++"; // 获取字符串的C风格字符指针 const char* cStr = hello.c_str(); // 将C字符串转换为Java字符串并返回 jstring result = env->NewStringUTF(cStr); return result; } catch (...) { // 异常处理:如果发生异常,返回nullptr return nullptr; } } ``` 代码分析说明: 1. 函数签名 :这是一个标准的JNI函数,用于从C++返回字符串给Java层 2. 核心逻辑 : - 创建一个 std::string 对象,内容为"Hello from C++" - 调用 hello.c_str() 获取C风格字符串指针 - 使用JNI的 NewStringUTF 函数将C字符串转换为Java字符串 - 返回Java字符串对象 3. 异常处理 :汇编代码中包含了异常处理机制(try-catch块),在C++代码中用try-catch实现 4. 栈保护 :汇编代码中的 ___stack_chk_fail 调用表明原代码启用了栈保护机制 这个函数通常在Android应用中用于从Native层向Java层返回字符串数据,是JNI开发中的常见模式。
通过分析可以发现
trae的claude近乎完美的方式反编译了so,使得我们逆向分析的难度降至最低。
比较好的反编译ai,claude,gemini,推荐gemini
浙公网安备 33010602011771号