题目
[ LoopAndLoop ]
The friendship between native and dex.
给出了一个apk
下载地址:(目前可用,取决于alictf的网站)apk
分析
安装软件,打开,得到一个LoopAndLoop的apk,打开进去,让输密码,点击按钮,可以看到Not Right!或者Not a valid integer number的提示,显然,只要找到密码,应该离flag就不远了。
当然这里只是猜测,jeb逆向得到java,只有一个MainActivity的activity,代码:
package net.bluelotus.tomorrow.easyandroid;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View$OnClickListener;
public class MainActivity extends AppCompatActivity {
static {
System.loadLibrary("lhm");
}
public MainActivity() {
super();
}
public native int chec(int arg1, int arg2) {
}
public int check(int input, int s) {
return this.chec(input, s);
}
public int check1(int input, int s) {
int v1 = input;
int v0;
for(v0 = 1; v0 < 100; ++v0) {
v1 += v0;
}
return this.chec(v1, s);
}
public int check2(int input, int s) {
int v2;
int v3 = 1000;
int v1 = input;
if(s % 2 == 0) {
int i;
for(i = 1; i < v3; ++i) {
v1 += i;
}
v2 = this.chec(v1, s);
}
else {
for(i = 1; i < v3; ++i) {
v1 -= i;
}
v2 = this.chec(v1, s);
}
return v2;
}
public int check3(int input, int s) {
int v1 = input;
int v0;
for(v0 = 1; v0 < 10000; ++v0) {
v1 += v0;
}
return this.chec(v1, s);
}
public String messageMe(String text) {
return "LoopOk" + text;
}
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(2130968600);
this.findViewById(2131492946).setOnClickListener(new View$OnClickListener() {
public void onClick(View v) {
int inputInteger;
String inputIntegerString = this.val$ed.getText().toString();
try {
inputInteger = Integer.parseInt(inputIntegerString);
}
catch(NumberFormatException v0) {
this.val$tv1.setText("Not a Valid Integer number");
return;
}
if(MainActivity.this.check(inputInteger, 99) == 1835996258) {
this.val$tv1.setText("The flag is:");
this.val$tv2.setText("alictf{" + MainActivity.this.stringFromJNI2(inputInteger) +
"}");
}
else {
this.val$tv1.setText("Not Right!");
}
}
});
}
public boolean onCreateOptionsMenu(Menu menu) {
this.getMenuInflater().inflate(2131558400, menu);
return 1;
}
public boolean onOptionsItemSelected(MenuItem item) {
boolean v1 = item.getItemId() == 2131492961 ? true : super.onOptionsItemSelected(item);
return v1;
}
public native String stringFromJNI2(int arg1) {
}
}
其中包含两个原生函数,chec和stringFromJNI2。根据oncreate的setOnClickListener当中得到的逻辑可知,通过一个整数,调用chec与一个magic number 1835996258比较,如果比较成功,通过这个整数,代入stringFromJNI2得到flag。
再关注以下3个check函数,循环的执行结果是可以预测到的,分别是从1加到100,从1加到1000,从1加到10000,显然可以换为常数的加减。
再关注原生层:
IDA打开libs/armaebi/liblhm.so,找到chec(因为如果chec能过,那么这个整数代入stringFromJNI2就可以得到FLAG了)
chec的实现汇编代码:
PUSH {R4-R7,LR}
.text:00000E8E SUB SP, SP, #0x2C
.text:00000E90 STR R1, [SP,#0x40+arg2]
.text:00000E92 STR R3, [SP,#0x40+arg4]
.text:00000E94 LDR R1, =(aNetBluelotusTo - 0xE9E)
.text:00000E96 LDR R3, [R0]
.text:00000E98 MOVS R4, R0
.text:00000E9A ADD R1, PC ; "net/bluelotus/tomorrow/easyandroid/Main"...
.text:00000E9C LDR R3, [R3,#0x18]
.text:00000E9E STR R2, [SP,#0x40+var_34]
.text:00000EA0 BLX R3
.text:00000EA2 LDR R6, =(aIiI - 0xEAC)
.text:00000EA4 LDR R2, =(aCheck1 - 0xEB2)
.text:00000EA6 MOVS R7, R0
.text:00000EA8 ADD R6, PC ; "(II)I"
.text:00000EAA MOVS R3, R6
.text:00000EAC MOVS R1, R7
.text:00000EAE ADD R2, PC ; "check1"
.text:00000EB0 MOVS R0, R4
.text:00000EB2 BL _ZN7_JNIEnv11GetMethodIDEP7_jclassPKcS3_ ; _JNIEnv::GetMethodID(_jclass *,char const*,char const*)
.text:00000EB6 LDR R2, =(aCheck2 - 0xEC2)
.text:00000EB8 MOVS R3, R6
.text:00000EBA STR R0, [SP,#0x40+check1_methodId]
.text:00000EBC MOVS R1, R7
.text:00000EBE ADD R2, PC ; "check2"
.text:00000EC0 MOVS R0, R4
.text:00000EC2 BL _ZN7_JNIEnv11GetMethodIDEP7_jclassPKcS3_ ; _JNIEnv::GetMethodID(_jclass *,char const*,char const*)
.text:00000EC6 LDR R2, =(aCheck3 - 0xED6)
.text:00000EC8 ADD R5, SP, #0x40+check1_methodId
.text:00000ECA STR R0, [R5,#4]
.text:00000ECC MOVS R3, R6
.text:00000ECE MOVS R0, R4
.text:00000ED0 MOVS R1, R7
.text:00000ED2 ADD R2, PC ; "check3"
.text:00000ED4 BL _ZN7_JNIEnv11GetMethodIDEP7_jclassPKcS3_ ; _JNIEnv::GetMethodID(_jclass *,char const*,char const*)
.text:00000ED8 LDR R6, [SP,#0x40+arg4]
.text:00000EDA STR R0, [R5,#8]
.text:00000EDC SUBS R6, #1
.text:00000EDE CMP R6, #0
.text:00000EE0 BLE loc_EFE
.text:00000EE2 LDR R3, [SP,#0x40+arg4]
.text:00000EE4 MOVS R1, #3
.text:00000EE6 LSLS R0, R3, #1
.text:00000EE8 BL __aeabi_idivmod
.text:00000EEC LSLS R1, R1, #2
.text:00000EEE LDR R2, [R1,R5]
.text:00000EF0 MOVS R0, R4
.text:00000EF2 STR R6, [SP,#0x40+var_40]
.text:00000EF4 LDR R1, [SP,#0x40+arg2]
.text:00000EF6 LDR R3, [SP,#0x40+var_34]
.text:00000EF8 BL _ZN7_JNIEnv13CallIntMethodEP8_jobjectP10_jmethodIDz ; _JNIEnv::CallIntMethod(_jobject *,_jmethodID *,...)
.text:00000EFC B loc_F00
.text:00000EFE ; ---------------------------------------------------------------------------
.text:00000EFE
.text:00000EFE loc_EFE ; CODE XREF: Java_net_bluelotus_tomorrow_easyandroid_MainActivity_chec+54j
.text:00000EFE LDR R0, [SP,#0x40+var_34]
.text:00000F00
.text:00000F00 loc_F00 ; CODE XREF: Java_net_bluelotus_tomorrow_easyandroid_MainActivity_chec+70j
.text:00000F00 ADD SP, SP, #0x2C
.text:00000F02 POP {R4-R7,PC}
.text:00000F02 ; End of function Java_net_bluelotus_tomorrow_easyandroid_MainActivity_chec
根据ida提供的字符串信息,显然调用了三次getMethodId函数,分别对应check1,check2,check3(我的ida代码有一些经过我重命名了),稍微查看一下逻辑,[sp, #0x40+check1_methodId](原代码应该是[sp, #0x40+var_24])保存了check1的methodid,之后的两个则保存在他的后面,内存里边大概是这样:
|————| <—– sp + 0x40
|*******|
|*******|
|*******| <—– check3的methodId(4字节)
|*******| <——- check2的methodId (4字节)
|*******| <— sp + 0x40 -0x24(即var_24), 即check1的methodId(4字节)
|*******|
|*******|
|*******|
|*******|
|*******|
|*******| <—-sp
此时的重要内存单元或寄存器状态:
- R4:JNIEnv *env
- arg2(原代码var_2C):Jobject *thiz
- var_34:代入函数的第一个参数
- arg4(原代码var_30):代入函数的第二个参数
- R5:check1的methodId的地址
再查看接下来的代码:
.text:00000ED8 LDR R6, [SP,#0x40+arg4] ;用R6保存函数的第二个参数
.text:00000EDA STR R0, [R5,#8] ;上一个getMethodId所需要的保存工作
.text:00000EDC SUBS R6, #1 ;参数2 - 1 存在R6中
.text:00000EDE CMP R6, #0 ;与0比较
.text:00000EE0 BLE loc_EFE ;根据后文,即直接返回第一个参数
.text:00000EE2 LDR R3, [SP,#0x40+arg4] ;R3保存第二个参数(注意,没有减一)
.text:00000EE4 MOVS R1, #3 ;R1赋值为3
.text:00000EE6 LSLS R0, R3, #1 ;R0赋值为R3左移一位,即R0 = R3 * 2
.text:00000EE8 BL __aeabi_idivmod ;取模运算,R1保存余数 (其实这里是根据函数名字猜的,根据我查到的资料,arm指令集没有除法和取模,是通过移位和加减法实现的除法和取模,所以这里应该就是用来实现idiv的功能,而mod在这里应该会起到保存余数的作用,根据后文R0没有起到作用,以及需要的值应该在0到2之间,所以猜测,R0保存商,R1保存余数,如果谁有资料证明,请留言给我,不甚感激)
.text:00000EEC LSLS R1, R1, #2 ;R1 = R1 * 4(因为4个字节的问题)
.text:00000EEE LDR R2, [R1,R5] ;R2 = R1 + R5,即指向checkx的methodId,R1为0即check1,R1为1即check2,R1为2即check3
.text:00000EF0 MOVS R0, R4 ;R0保存env指针
.text:00000EF2 STR R6, [SP,#0x40+var_40] ;将R6,即减一之后的第二个参数保存到栈中
.text:00000EF4 LDR R1, [SP,#0x40+arg2] ;R1保存thiz指针
.text:00000EF6 LDR R3, [SP,#0x40+var_34] ;R3保存参数1
.text:00000EF8 BL _ZN7_JNIEnv13CallIntMethodEP8_jobjectP10_jmethodIDz ; _JNIEnv::CallIntMethod(_jobject *,_jmethodID *,...) ;调用函数
.text:00000EFC B loc_F00 ;返回
.text:00000EFE ; ---------------------------------------------------------------------------
.text:00000EFE
.text:00000EFE loc_EFE ; CODE XREF: Java_net_bluelotus_tomorrow_easyandroid_MainActivity_chec+54j
.text:00000EFE LDR R0, [SP,#0x40+var_34]
.text:00000F00
.text:00000F00 loc_F00 ; CODE XREF: Java_net_bluelotus_tomorrow_easyandroid_MainActivity_chec+70j
.text:00000F00 ADD SP, SP, #0x2C
.text:00000F02 POP {R4-R7,PC}
.text:00000F02 ; End of function Java_net_bluelotus_tomorrow_easyandroid_MainActivity_chec
根据注释的分析,得到逻辑,chec其实起一个选择的作用,根据第二个参数乘2对3取模的结果,决定调用哪一个check函数。
根据之前对3个check函数的关注,加减过程是可逆的,即加法变减法,减法变加法,就可以把过程倒过来。
所以这里有两个思路:
- 逆向算法得到密码
- 爆破,优化3个check函数之后直接从1开始计算到2的32次方-1,
爆破在这里就不做深入了,直接写个java代码试试就可以了。
逆向的算法:
a = 1835996258
i = 99
while i > 0:
i -= 1
if i > 0:
now = ((i + 1) * 2) % 3 #注意一下这里是i+1,因为这里判断的是没有减一的第二个参数
if now == 0:
a -= 4950
elif now == 1:
if i % 2 == 0:
a -= 499500
else:
a += 499500
else:
a -= 49995000
print a
得到密码236492408,拿到apk里边运行一次就可以看到flag了。