GKLBB

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

导航

逆向工程 --- 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

 

posted on 2025-06-15 09:38  GKLBB  阅读(203)  评论(0)    收藏  举报