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;
}
}
使用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
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。