BruceFan's Blog

Stay hungry, stay foolish

0%

BCTF2017 re2.apk Writeup

题目下载:re2.apk
关于Android Studio的使用一直不太熟悉,借着题目学习一下。
这里是Android Studio中NDK开发的官方文档。
将APK文件用JEB打开:


MainActivity中有这么一些代码,代码并不复杂,也没有混淆加密,逻辑大概是:有两个按钮,需要轮流点这两个按钮,奇数次点pong,偶数次点ping,总共要点100万次。还有一个关键是调用了pp库,每次点击按钮会调用库中的ping()pong()函数。
解决思路:新建一个项目,是通过编写代码,模拟点击,调用这两个库函数。
代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package com.geekerchina.pingpongmachine;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;

public class MainActivity extends Activity {

static {
System.loadLibrary("pp");
}

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
int p = 0;
int num = 0;
int ttt = 1000000;
int tt = ttt;
while (tt != 0) {
if (tt % 2 == 0) { // 偶数次点ping
--tt;
p = ping(p, num);
++num;
if (num >= 7) num = 0;
if (tt == 0) Log.d("FLAG:", "BCTF{MagicNum"+Integer.toString(p)+"}");
} else { // 奇数次点pong
--tt;
p = pong(p, num);
++num;
if (num >= 7) num = 0;
if (tt == 0) Log.d("FLAG:", "BCTF{MagicNum"+Integer.toString(p)+"}");
}
}
}

public native int ping(int arg1, int arg2);
public native int pong(int arg1, int arg2);
}

需要注意的是项目的包名要与题目中的一致。
项目的关键向Android Studio中添加so文件,这里卡了好久,网上找了好多文章,没有一个成功的,真的不如eclipse方便。
后来不经意看到别人项目中的gradle文件内容才解决。
src/main下添加jniLibs/armeabi/xxx.so
删除build.gradle中的

1
2
3
4
5
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}

build.gradle完整的配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
apply plugin: 'com.android.application'

android {
compileSdkVersion 24
buildToolsVersion "25.0.2"
defaultConfig {
applicationId "com.geekerchina.pingpongmachine"
minSdkVersion 19
targetSdkVersion 24
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {
cppFlags ""
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}

dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.android.support.constraint:constraint-layout:1.0.2'
testCompile 'junit:junit:4.12'
}

从题目中解压出so文件放到自己创建的项目中(so库也没有加密或做其他处理,能够直接调用),运行程序,好长时间都没有结果。这时逆向so文件或在每次”点击”中加一个Log都行,我在”点击”中加了

1
Log.d("pingpong", "tt:"+Integer.toString(tt))

监控tt的变化,发现Log打得很慢。用IDA打开so文件,发现ping()pong()函数中都有一个sleep(1)函数,只要NOP掉这个sleep就可以了。这里我犯了一个错误,sleep(1)对应的机器码是32位,我就改成mov r0, r0对应的ARM机器码00 00 A0 E1了,结果程序运行不了。

其实这段代码对应的是Thumb指令,应该改成mov r0, r0对应的Thumb机器码,查机器码时我用的radare2的rasm2:

1
2
$ rasm2 -a arm -b 16 'mov r0, r0'
0046

将sleep(1)的机器码改成00 46 00 46,即两条mov r0, r0指令。将修改后的so文件放到项目中,再次运行项目:

最终得到FLAG: BCTF{MagicNum4500009}

补充:
最近看了一些Frida的用法,想自己用Frida解决一些问题,就选了以前做过的题目试试。之前用的patch的方法,这里借用来学一下Frida方法。
和上面一样,还是需要新建一个项目调用libpp.so,不过libpp.so不需要patch。
新建一个Frida脚本,hook so文件中调用的sleep函数,替换为自己的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
setImmediate(function() {
console.log("[*] Starting script");
Java.perform(function(){
var sleepPtr = undefined;
imports = Module.enumerateImportsSync('libpp.so');
for (i = 0; i < imports.length; i++) {
if (imports[i].name == "sleep") {
sleepPtr = imports[i].address;
break;
}
}
console.log("[*] sleepPtr = " + sleepPtr);
var sleep = new NativeFunction(sleepPtr, 'int', ['int']);
// t是so文件中调用时的实际参数
Interceptor.replace(sleepPtr, new NativeCallback(function(t) {
var ret = sleep(0);
return ret;
}, 'int', ['int']));
});
});

这里将so文件中的sleep调用替换为了sleep(0),达到的效果和patch是一样的。