BruceFan's Blog

Stay hungry, stay foolish

0%

Frida解决Android Crackme1

了解了Frida基本使用方法之后,下面对Frida进行一些实践。
首先下载crackme1,安装到模拟器上并运行:


点击OK就会退出程序。将APK用JEB打开,查看MainActivity类

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
41
42
43
44
45
46
package sg.vantagepoint.uncrackable1;
import android.app.Activity;
import android.app.AlertDialog$Builder;
import android.app.AlertDialog;
import android.content.Context;
import android.os.Bundle;
import android.view.View;
import sg.vantagepoint.a.c;

public class MainActivity extends Activity {
public MainActivity() {
super();
}
private void a(String arg5) {
AlertDialog v0 = new AlertDialog$Builder(((Context)this)).create();
v0.setTitle(((CharSequence)arg5));
v0.setMessage("This in unacceptable. The app is now going to exit.");
v0.setButton(-3, "OK", new b(this));
v0.setCancelable(false);
v0.show();
}
protected void onCreate(Bundle arg2) {
if((c.a()) || (c.b()) || (c.c())) {
this.a("Root detected!");
}
if(sg.vantagepoint.a.b.a(this.getApplicationContext())) {
this.a("App is debuggable!");
}
super.onCreate(arg2);
this.setContentView(2130903040);
}
public void verify(View arg5) {
String v0 = this.findViewById(2131230720).getText().toString();
AlertDialog v1 = new AlertDialog$Builder(((Context)this)).create();
if(a.a(v0)) {
v1.setTitle("Success!");
v1.setMessage("This is the correct secret.");
}
else {
v1.setTitle("Nope...");
v1.setMessage("That\'s not it. Try again.");
}
v1.setButton(-3, "OK", new sg.vantagepoint.uncrackable1.c(this));
v1.show();
}
}

程序onCreate就会对设备的root权限进行检查,如果已root则退出。按照之前的思路是将APK用baksmali反汇编,修改代码来解决,现在采用Frida方式来进行解决。
查看sg.vantagepoint.a.c类,里面都是与root权限相关的检查。可以通过覆盖这些方法让它们返回false,但是用命令启动脚本:

1
2
3
$ frida -U -l checkroot.js -f sg.vantagepoint.uncrackable1 --no-pause
...
Process terminated

检测root权限的方法在代码注入之前就执行了,注入没有起到应有的作用。
另一种方法是,先启动应用程序,当检测到了root权限,hook MainActivity中对话框调用的方法:

1
2
3
4
5
6
7
8
private void a(String arg5) {
AlertDialog v0 = new AlertDialog$Builder(((Context)this)).create();
v0.setTitle(((CharSequence)arg5));
v0.setMessage("This in unacceptable. The app is now going to exit.");
v0.setButton(-3, "OK", new b(this)); // 对话框调用的方法
v0.setCancelable(false);
v0.show();
}

对话框方法的代码如下:

1
2
3
4
5
6
7
8
9
class b implements DialogInterface$OnClickListener {
b(MainActivity arg1) {
this.a = arg1;
super();
}
public void onClick(DialogInterface arg2, int arg3) {
System.exit(0);
}
}

点击对话框的OK就会调用onClick里的System.exit(0),退出程序。为了防止应用程序退出,hook这个onClick方法:

1
2
3
4
5
6
7
8
9
10
11
// uncrackable1.js
setImmediate(function() {
console.log("[*] Starting script");
Java.perform(function() {
bClass = Java.use("sg.vantagepoint.uncrackable1.b");
bClass.onClick.implementation = function(v) {
console.log("[*] onClick called");
}
console.log("[*] onClick handler modified")
});
});

这里将onClick方法重写为只打印消息不退出程序,打开应用,注入脚本:

1
2
3
$ frida -U -l uncrackable1.js sg.vantagepoint.uncrackable1
...
[*] onClick handler modified

注入需要几秒钟的时间,注入完成后点击OK,程序就不会退出了:

下面就要找出需要的密码,即找到加密/解密例程以及结果和输入的对比。MainActivity中有一个verify函数,它调用了sg.vantagepoint.uncrackable1.a类的a方法:

1
2
3
4
if(a.a(v0)) { // v0为输入的密码
v1.setTitle("Success!");
v1.setMessage("This is the correct secret.");
}

下面是sg.vantagepoint.uncrackable1.a类的反编译结果:

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
public class a {
public static boolean a(String arg6) {
byte[] v0;
String v2 = "8d127684cbc37c17616d806cf50473cc";
byte[] v3 = Base64.decode("5UJiFctbmgbDoLXmpL12mkno8HT4Lv8dlat8FxR2GOc=", 0);
new byte[0];
try {
v0 = sg.vantagepoint.a.a.a(a.b(v2), v3);
}
catch(Exception v2_1) {
Log.d("CodeCheck", "AES error:" + v2_1.getMessage());
}
boolean v0_1 = arg6.equals(new String(v0)) ? true : false;
return v0_1;
}

public static byte[] b(String arg7) {
int v1 = 16;
int v2 = arg7.length();
byte[] v3 = new byte[v2 / 2];
int v0;
for(v0 = 0; v0 < v2; v0 += 2) {
v3[v0 / 2] = ((byte)((Character.digit(arg7.charAt(v0), v1) << 4) + Character.digit(arg7.charAt(v0 + 1), v1)));
}
return v3;
}
}

a方法的最后,arg6.equals比较输入的字符串和String(v0)是否相等,v0是sg.vantagepoint.a.a类的a方法返回的值(字节数组的形式),所以要知道正确的输入,只要hook a方法,将其返回值转换为可读字符串并打印到控制台即可。

1
2
3
4
5
6
7
8
9
10
11
aaClass = Java.use("sg.vantagepoint.a.a");
aaClass.a.implementation = function(arg1, arg2) {
retval = this.a(arg1, arg2);
password = '';
for (i = 0; i < retval.length; i++) {
password += String.fromCharCode(retval[i]);
}
console.log("[*] Decrypted: " + password);
return retval;
}
console.log("[*] sg.vantagepoint.a.a.a modified");

将上述代码和之前的代码放到一起组成完整的脚本后运行,等到代码注入成功,点击Root Detected对话框OK按钮,在文本框中随意输入字符,点击VERIFY按钮:

1
2
3
4
5
6
$ frida -U -l uncrackable1.js sg.vantagepoint.uncrackable1
...
[*] onClick handler modified
[*] sg.vantagepoint.a.a.a modified
[*] onClick called
[*] Decrypted: I want to believe

控制台上即可看到所需的正确输入:I want to believe。将字符串输入文本框中:

reference
http://bobao.360.cn/learning/detail/3634.html