Android SO逆向-流程控制语句及表达式运算

来源:互联网 发布:ifix组态软件 编辑:程序博客网 时间:2024/05/01 21:30

    0x00

    在前一篇文章Android SO逆向-基本数据类型及函数的工作原理中,我们介绍了ndk的使用,这篇文章直接列出C++源码及对应的汇编代码。


    0x01

    在java层主要是调用native方法,现在列出java层的代码:

    Lesson1.java

package com.example.ndkreverse1;public class Lesson1 { static {          System.loadLibrary("lesson1");   }   public static native int getInt();  public native String getString();  public static native int getFor1(int n);  public static native String getIfElse(int n);  public static native int getWhile(int n);  public static native int getSwitch(int a,int b,int i);  public static native int getOperation(int a, int b);}
    MainActivity.java

package com.example.ndkreverse1;import android.app.Activity;import android.os.Bundle;import android.view.Menu;import android.view.MenuItem;public class MainActivity extends Activity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);Lesson1 lesson1 = new Lesson1();lesson1.getInt();lesson1.getString();lesson1.getFor1(5);lesson1.getIfElse(20);lesson1.getWhile(5);lesson1.getSwitch(3, 4, 3);lesson1.getOperation(9, 16);}}
    

    在MainActivity中调用的native方法,是在native层实现的。

#include "com_example_ndkreverse1_Lesson1.h"#include <android/log.h>#define LOG_TAG "lesson1"#define ALOGD(...) ((void)__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__))JNIEXPORT jint JNICALL Java_com_example_ndkreverse1_Lesson1_getInt  (JNIEnv * env, jclass jclass) {return 8;}JNIEXPORT jstring JNICALL Java_com_example_ndkreverse1_Lesson1_getString  (JNIEnv * env, jobject jobject) {return env->NewStringUTF("method call");}JNIEXPORT jint JNICALL Java_com_example_ndkreverse1_Lesson1_getFor1  (JNIEnv * env, jclass jclass, jint n) {int i = 0;int s = 0;for (i = 0; i < n; i++){s += i * 2;}return s;}JNIEXPORT jstring JNICALL Java_com_example_ndkreverse1_Lesson1_getIfElse  (JNIEnv * env, jclass jclass, jint n) {if(n < 16) {return env->NewStringUTF("he is a boy");} else if(n < 30){return env->NewStringUTF("he is a young man");} else if(n < 45){return env->NewStringUTF("he is a strong man");} else{return env->NewStringUTF("he is an old man");}}JNIEXPORT jint JNICALL Java_com_example_ndkreverse1_Lesson1_getWhile  (JNIEnv * env, jclass jclass, jint n) {int i = 1;int s = 0;while(i <= n) {s += i++;}return s;}JNIEXPORT jint JNICALL Java_com_example_ndkreverse1_Lesson1_getSwitch  (JNIEnv * env, jclass jclass, jint a, jint b, jint i) {switch (i) {case 1:return a + b;break;case 2:return a - b;break;case 3:return a * b;break;case 4:return a / b;break;default:return a + b;break;}}JNIEXPORT jint JNICALL Java_com_example_ndkreverse1_Lesson1_getOperation  (JNIEnv * env, jclass jclass, jint a, jint b) {if (a > 10 || !(b <=20 && b != 15)) {return 8;} else {return 9;}}

    0x02

    使用ida打开so,下面我们来看一下这些native方法对应的汇编代码。

    Java_com_example_ndkreverse1_Lesson1_getInt:

.text:00001026                 EXPORT Java_com_example_ndkreverse1_Lesson1_getInt.text:00001026 Java_com_example_ndkreverse1_Lesson1_getInt.text:00001026                 MOVS    R0, #8.text:00001028                 BX      LR
    此时仅仅是返回了一个数字8。BX LR返回到调用Java_com_example_ndkreverse1_Lesson1_getInt的函数,动态调试可以看到是libdvm.so里面的一个方法。


    Java_com_example_ndkreverse1_Lesson1_getString:

.text:0000102C                 EXPORT Java_com_example_ndkreverse1_Lesson1_getString.text:0000102C Java_com_example_ndkreverse1_Lesson1_getString.text:0000102C                 PUSH    {R3,LR}.text:0000102E                 LDR     R1, =(aMethodCall - 0x1034).text:00001030                 ADD     R1, PC          ; "method call".text:00001032                 BL      _ZN7_JNIEnv12NewStringUTFEPKc ; _JNIEnv::NewStringUTF(char const*).text:00001036                 POP     {R3,PC}.text:00001036 ; End of function Java_com_example_ndkreverse1_Lesson1_getString
    我们看Java_com_example_ndkreverse1_Lesson1_getString这个函数有两个参数,分别是env,jobject。所以执行这个函数时,R0为env,R1为jobject。经过操作后,R1的值为method call的地址,我们在Android SO逆向-基本数据类型及函数的工作原理已经讲过了这个地址是怎么获取的,R0还是env,然后调用_ZN7_JNIEnv12NewStringUTFEPKc也就是Ne
wStringUTF函数,返回的结果保持在R0中,然后返回到上一层函数中。


    Java_com_example_ndkreverse1_Lesson1_getFor1:

.text:0000103C                 EXPORT Java_com_example_ndkreverse1_Lesson1_getFor1.text:0000103C Java_com_example_ndkreverse1_Lesson1_getFor1.text:0000103C                 MOVS    R0, #0 ;R0 初始化为0.text:0000103E                 ADDS    R3, R0, #0 ;R3初始化为0.text:00001040.text:00001040 loc_1040                                ; CODE XREF: Java_com_example_ndkreverse1_Lesson1_getFor1+Ej.text:00001040                 CMP     R3, R2 ;R3和R2比较.text:00001042                 BGE     locret_104C ;如果R3>=R2,则跳转到locret_104C返回.text:00001044                 LSLS    R1, R3, #1  ;否则R3左移1位,相当于乘以2.text:00001046                 ADDS    R0, R0, R1  ;R0累加R1的值,再赋值给R0.text:00001048                 ADDS    R3, #1      ;R3加1,再赋值R3.text:0000104A                 B       loc_1040    ;继续循环执行.text:0000104C ; ---------------------------------------------------------------------------.text:0000104C.text:0000104C locret_104C                             ; CODE XREF: Java_com_example_ndkreverse1_Lesson1_getFor1+6j.text:0000104C                 BX      LR          ;程序的出口
    传入的参数R2是n的值,其他的操作系统请看代码中注释,然后对比C++代码,读者便可理解for循环在汇编层的实现。


    JNICALL Java_com_example_ndkreverse1_Lesson1_getIfElse:

.text:00001050                 EXPORT Java_com_example_ndkreverse1_Lesson1_getIfElse.text:00001050 Java_com_example_ndkreverse1_Lesson1_getIfElse.text:00001050                 PUSH    {R3,LR}.text:00001052                 CMP     R2, #0xF ;R2的值和16比较.text:00001054                 BGT     loc_105C ;如果R2比16的值大,则跳转到loc_105C.text:00001056                 LDR     R1, =(aHeIsABoy - 0x105C) ;否则返回he is a boy.text:00001058                 ADD     R1, PC          ; "he is a boy".text:0000105A                 B       loc_1074        ;跳转到loc_1074,调用NewStringUTF.text:0000105C ; ---------------------------------------------------------------------------.text:0000105C.text:0000105C loc_105C                                ; CODE XREF: Java_com_example_ndkreverse1_Lesson1_getIfElse+4j.text:0000105C                 CMP     R2, #0x1D  ;R2的值和30比较.text:0000105E                 BGT     loc_1066   ;如果R2比30大,则跳转到loc_1066.text:00001060                 LDR     R1, =(aHeIsAYoungMan - 0x1066) ;否则返回he is a young man.text:00001062                 ADD     R1, PC          ; "he is a young man".text:00001064                 B       loc_1074       ;跳转到loc_1074,调用NewStringUTF.text:00001066 ; ---------------------------------------------------------------------------.text:00001066.text:00001066 loc_1066                                ; CODE XREF: Java_com_example_ndkreverse1_Lesson1_getIfElse+Ej.text:00001066                 CMP     R2, #0x2C  ;R2的值和45比较.text:00001068                 BGT     loc_1070   ;如果R2比45大,则跳转到loc_1070.text:0000106A                 LDR     R1, =(aHeIsAStrongMan - 0x1070) ;否则返回he is a strong man.text:0000106C                 ADD     R1, PC          ; "he is a strong man".text:0000106E                 B       loc_1074       ;跳转到loc_1074,调用NewStringUTF.text:00001070 ; ---------------------------------------------------------------------------.text:00001070.text:00001070 loc_1070                                ; CODE XREF: Java_com_example_ndkreverse1_Lesson1_getIfElse+18j.text:00001070                 LDR     R1, =(aHeIsAnOldMan - 0x1076).text:00001072                 ADD     R1, PC          ; "he is an old man" ;继续执行到loc_1074,调用NewStringUTF.text:00001074.text:00001074 loc_1074                                ; CODE XREF: Java_com_example_ndkreverse1_Lesson1_getIfElse+Aj.text:00001074                                         ; Java_com_example_ndkreverse1_Lesson1_getIfElse+14j ....text:00001074                 BL      _ZN7_JNIEnv12NewStringUTFEPKc ; _JNIEnv::NewStringUTF(char const*).text:00001078                 POP     {R3,PC}
    传入的参数R2是n的值,其他的操作系统请看代码中注释,然后对比C++代码,读者便可理解if else循环在汇编层的实现。我们再看下ida对这段汇编的按F5形成的C语言。

int __fastcall Java_com_example_ndkreverse1_Lesson1_getIfElse(_JNIEnv *a1, int a2, signed int a3){  const char *v3; // r1@2  if ( a3 > 15 )  {    if ( a3 > 29 )    {      if ( a3 > 44 )        v3 = "he is an old man";      else        v3 = "he is a strong man";    }    else    {      v3 = "he is a young man";    }  }  else  {    v3 = "he is a boy";  }  return _JNIEnv::NewStringUTF(a1, v3);} 
    我们能够看到反汇编后的C++语言和最初的C++语言还是有一定差别的。


    Java_com_example_ndkreverse1_Lesson1_getWhile:

.text:0000108C                 EXPORT Java_com_example_ndkreverse1_Lesson1_getWhile.text:0000108C Java_com_example_ndkreverse1_Lesson1_getWhile.text:0000108C                 MOVS    R0, #0.text:0000108E                 MOVS    R3, #1.text:00001090.text:00001090 loc_1090                                ; CODE XREF: Java_com_example_ndkreverse1_Lesson1_getWhile+Cj.text:00001090                 CMP     R3, R2.text:00001092                 BGT     locret_109A ;如果R3大于R2,就跳转到locret_109A.text:00001094                 ADDS    R0, R0, R3.text:00001096                 ADDS    R3, #1.text:00001098                 B       loc_1090.text:0000109A ; ---------------------------------------------------------------------------.text:0000109A.text:0000109A locret_109A                             ; CODE XREF: Java_com_example_ndkreverse1_Lesson1_getWhile+6j.text:0000109A                 BX      LR
    这个函数和for循环的汇编实现很像。
    

    Java_com_example_ndkreverse1_Lesson1_getSwitch:

.text:0000109C                 EXPORT Java_com_example_ndkreverse1_Lesson1_getSwitch.text:0000109C Java_com_example_ndkreverse1_Lesson1_getSwitch.text:0000109C.text:0000109C arg_0           =  0.text:0000109C.text:0000109C                 PUSH    {R3,LR}.text:0000109E                 LDR     R1, [SP,#8+arg_0] ;取参数i.text:000010A0                 ADDS    R0, R2, R3        ;R2和R3分别代表a和b参数,相加得到默认值存放在R0中.text:000010A2                 SUBS    R1, #1            ;R1值减一.text:000010A4                 CMP     R1, #3          ; switch 4 cases ;R1的值和3对比.text:000010A6                 BHI     def_10AA        ; jumptable 000010AA default case ;如果R1的值大于3,那么就跳转到def_10AA.text:000010A8                 MOVS    R0, R1          ;把R1的值赋值给R0.text:000010AA                 BL      __gnu_thumb1_case_uqi ; switch jump ;跳转到_gnu_thumb1_case_uqi.text:000010AA ; ---------------------------------------------------------------------------.text:000010AE jpt_10AA        DCB 2                   ; jump table for switch statement.text:000010AF                 DCB 4.text:000010B0                 DCB 6.text:000010B1                 DCB 9.text:000010B2 ; ---------------------------------------------------------------------------.text:000010B2.text:000010B2 loc_10B2                                ; CODE XREF: Java_com_example_ndkreverse1_Lesson1_getSwitch+Ej.text:000010B2                 ADDS    R0, R2, R3      ; jumptable 000010AA case 0.text:000010B6 ; ---------------------------------------------------------------------------.text:000010B6.text:000010B6 loc_10B6                                ; CODE XREF: Java_com_example_ndkreverse1_Lesson1_getSwitch+Ej.text:000010B6                 SUBS    R0, R2, R3      ; jumptable 000010AA case 1.text:000010B8                 B       def_10AA        ; jumptable 000010AA default case.text:000010BA ; ---------------------------------------------------------------------------.text:000010BA.text:000010BA loc_10BA                                ; CODE XREF: Java_com_example_ndkreverse1_Lesson1_getSwitch+Ej.text:000010BA                 MOVS    R0, R3          ; jumptable 000010AA case 2.text:000010BC                 MULS    R0, R2.text:000010BE                 B       def_10AA        ; jumptable 000010AA default case.text:000010C0 ; ---------------------------------------------------------------------------.text:000010C0.text:000010C0 loc_10C0                                ; CODE XREF: Java_com_example_ndkreverse1_Lesson1_getSwitch+Ej.text:000010C0                 MOVS    R1, R3          ; jumptable 000010AA case 3.text:000010C2                 MOVS    R0, R2.text:000010C4                 BL      __divsi3.text:000010C8.text:000010C8 def_10AA                                ; CODE XREF: Java_com_example_ndkreverse1_Lesson1_getSwitch+Aj.text:000010C8                                         ; Java_com_example_ndkreverse1_Lesson1_getSwitch+18j ....text:000010C8                 POP     {R3,PC}         ; jumptable 000010AA default case.text:000010E0                 EXPORT __gnu_thumb1_case_uqi.text:000010E0 __gnu_thumb1_case_uqi                   ; CODE XREF: Java_com_example_ndkreverse1_Lesson1_getSwitch+Ep.text:000010E0                                         ; _Unwind_VRS_Get+Ap ....text:000010E0                 PUSH    {R1}      .text:000010E2                 MOV     R1, LR       ;把返回值LR赋值给R1.text:000010E4                 LSRS    R1, R1, #1   ;无用.text:000010E6                 LSLS    R1, R1, #1   ;无用.text:000010E8                 LDRB    R1, [R1,R0]  ;根据R0的值,取出R1(LR)加上R0地址的内容,也就是上面代码中jpt_10AA某个地址中的内容.text:000010EA                 LSLS    R1, R1, #1   ;然后把此内容值再乘以2.text:000010EC                 ADD     LR, R1       ;把LR加上R1的值赋值给LR,此时LR就是保存是要跳转的switch case的语句的地址了。.text:000010EE                 POP     {R1}.text:000010F0                 BX      LR           ;跳转到switch case对应的语句块,在本例中就是loc_10B2,loc_10B6,loc_10BA,loc_10C0

    由于getSwitch参数较多,JNIEnv * env, jclass jclass, jint a, jint b, jint i。前四个参数的的值是由R0~R3传递过来,最后一个参数i是由堆栈传递过来的。详细的解释请看代码。


    Java_com_example_ndkreverse1_Lesson1_getOperation:

.text:000010CA                 EXPORT Java_com_example_ndkreverse1_Lesson1_getOperation.text:000010CA Java_com_example_ndkreverse1_Lesson1_getOperation.text:000010CA                 MOVS    R0, #8       ;R0被赋值为8.text:000010CC                 CMP     R2, #0xA     ;比较a与10的值.text:000010CE                 BGT     locret_10DE  ;如果大于,就去执行locret_10DE,直接把刚赋值的8返回去.text:000010D0                 CMP     R3, #0x14    ;否则,b和20比较.text:000010D2                 BGT     locret_10DE  ;如果大于20,就去执行locret_10DE,直接把刚赋值的8返回去.text:000010D4                 SUBS    R3, #0xF     ;否则b的值减去15.text:000010D6                 NEGS    R2, R3       ;取反b的值.text:000010D8                 ADCS    R3, R2       ;b的值加上b取反的值再加上进位,得到的值再赋值给b。PS:如果b为15,那么此时b的值为1,否则为0.text:000010DA                 MOVS    R0, #9       ;R0被赋值为9.text:000010DC                 SUBS    R0, R0, R3   ;R0减去R3的值,再赋值给R0返回.text:000010DE.text:000010DE locret_10DE                             ; CODE XREF: Java_com_example_ndkreverse1_Lesson1_getOperation+4j.text:000010DE                                         ; Java_com_example_ndkreverse1_Lesson1_getOperation+8j.text:000010DE                 BX      LR
    Java_com_example_ndkreverse1_Lesson1_getOperation函数,R2是参数a,R3是参数b。详细的解释请看代码。

    我们再看下反汇编之后形成的C++语言和Java_com_example_ndkreverse1_Lesson1_getOperation原始实现的C++语言还是有比较大的差别的:

int __fastcall Java_com_example_ndkreverse1_Lesson1_getOperation(int a1, int a2, signed int a3, signed int a4){  int result; // r0@1  result = 8;  if ( a3 <= 10 && a4 <= 20 )    result = 9 - ((unsigned int)(a4 - 15) <= 0);  return result;}
    里面还有有些错误的,比如a4如果为14,原本函数应该返回9,但是这个函数却返回8。
    如果大家遇到不明白的地方,动态调试是个非常不错的方式,可能更快的理解程序,详见ida动态调试so,在init_array和JNI_ONLOAD处下断点。

0 0
原创粉丝点击