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

    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 {

	@Override
	protected 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 
#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处下断点。

你可能感兴趣的:(Android,Security)