白乐天

道阻且长,行则将至。

AndroidDemo Hook案例

App信息

包名:com.example.androiddemo

第一关

一个登录界面

把apk丢进jadx进行分析

查看AndroidManifest.xml文件

程序启动的第一个Activitycom.example.androiddemo.Activity.LoginActivity

LoginActivity

如下,调用了一个a方法来对用户名和密码进行校验

a

如下,对Username进行了一次HmacSHA256签名

UsernameHmacSHA256签名与Password相同时,可以登录成功。

hook a

1
2
3
4
5
6
7
8
9
10
11
12
13
function hook_a(){
Java.perform(function(){
var LoginActivity = Java.use("com.example.androiddemo.Activity.LoginActivity")
LoginActivity.a.overload('java.lang.String', 'java.lang.String').implementation = function(str1,str2){
console.log("str1:",str1);
console.log("str2:",str2);
var result = this.a(str1,str2);
console.log("result:",result);
return result;
}
})
}
hook_a()

hook 结果

1
2
bileton
77cd249b2fa536da37dcd3fc6993fe94aefb61a70328aa4cd12984a053f6a7c3

输入用户名和密码

登录成功

进入下一关

点击进入下一关,会出现Check Failed

FridaActivity1

查看源码,得知点击登录之后,会进入FridaActivity1

如下是FridaActivity1的内容

绕过方式很简单,可以直接修改a方法的返回值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function hook_a(){
Java.perform(function(){
var FridaActivity1 = Java.use("com.example.androiddemo.Activity.FridaActivity1");
FridaActivity1.a.implementation = function(data){
var result = this.a(data);
console.log("result:",result)
result = "R4jSLLLLLLLLLLOrLE7/5B+Z6fsl65yj6BgC6YWz66gO6g2t65Pk6a+P65NK44NNROl0wNOLLLL=";
console.log("replaced result:",result)
return result;
}

})
}
hook_a()

hook 结果

1
2
result: R4jSLLLLLLLLLRknplkBpZDpis69kh7i+YAPTmMn2ABsOLLLLL==
replaced result: R4jSLLLLLLLLLLOrLE7/5B+Z6fsl65yj6BgC6YWz66gO6g2t65Pk6a+P65NK44NNROl0wNOLLLL=

还有另一种绕过方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function hook_a(){
Java.perform(function(){
var FridaActivity1 = Java.use("com.example.androiddemo.Activity.FridaActivity1");
FridaActivity1.a.implementation = function(original_data){
console.log("original_data:",original_data);
var my_data = FridaActivity1.b("bileton");
console.log("data:",my_data);
var result = this.a(my_data);
console.log("result:",result);
result = "R4jSLLLLLLLLLLOrLE7/5B+Z6fsl65yj6BgC6YWz66gO6g2t65Pk6a+P65NK44NNROl0wNOLLLL=";
console.log("replaced result:",result)
return result;
}
})
}
hook_a()

hook 结果

1
2
3
4
original_data: 31,-117,8,0,0,0,0,0,0,0,123,-79,126,-5,-117,125,-109,-97,-74,46,125,-70,-66,-19,-7,-126,70,43,0,-15,-40,-110,-30,16,0,0,0
data: 31,-117,8,0,0,0,0,0,0,0,75,-54,-52,73,45,-55,-49,3,0,-26,-70,19,-27,7,0,0,0
result: R4jSLLLLLLLLLOlTzOuiyc8MLDa5O+URLLLL
replaced result: R4jSLLLLLLLLLLOrLE7/5B+Z6fsl65yj6BgC6YWz66gO6g2t65Pk6a+P65NK44NNROl0wNOLLLL=

进入到了第二关

第二关

第一关结束跳转到了FridaActivity2

需要把static_bool_varbool_var的值都修改为true才能进入FridaActivity3

这里可以主动调用setStatic_bool_var方法和setBool_var方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function hook_activity2(){
Java.perform(function(){
var FridaActivity2 = Java.use("com.example.androiddemo.Activity.FridaActivity2");
console.log("static_bool_var:",FridaActivity2.static_bool_var.value);
FridaActivity2.setStatic_bool_var();
console.log("replaced_static_bool_var:",FridaActivity2.static_bool_var.value);
Java.choose("com.example.androiddemo.Activity.FridaActivity2",{
onMatch:function(obj){
console.log("obj.bool_var:",obj.bool_var.value);
obj.setBool_var();
console.log("replaced_obj.bool_var:",obj.bool_var.value);
},
onComplete:function(){

}
})
})
}

hook 结果

1
2
3
4
static_bool_var: false
replaced_static_bool_var: true
obj.bool_var: false
replaced_obj.bool_var: true

点击进入第三关

第三关

第二关结束跳转到了FridaActivity3

需要把static_bool_varbool_varsame_name_bool_var三个值都设置为true才能进入FridaActivity4

这里存在一个坑点是存在一个与字段名same_name_bool_var相同的方法名same_name_bool_var,操作字段名的话需要在其名字前面加上一个_,即_same_name_bool_var

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function hook_activity3(){
var FridaActivity3 = Java.use("com.example.androiddemo.Activity.FridaActivity3");
console.log("static_bool_var:",FridaActivity3.static_bool_var.value);
FridaActivity3.static_bool_var.value = true;
console.log("replaced_static_bool_var:",FridaActivity3.static_bool_var.value);
Java.choose("com.example.androiddemo.Activity.FridaActivity3",{
onMatch:function(obj){
console.log("obj.bool_var:",obj.bool_var.value);
console.log("obj._same_name_bool_var:",obj._same_name_bool_var.value);
obj.bool_var.value = true;
obj._same_name_bool_var.value = true;
console.log("replaced_obj.bool_var:",obj.bool_var.value);
console.log("replaced_obj._same_name_bool_var:",obj._same_name_bool_var.value);
},
onComplete:function(){
}
})
}

hook 结果

1
2
3
4
5
6
static_bool_var: false
replaced_static_bool_var: true
obj.bool_var: false
obj._same_name_bool_var: false
replaced_obj.bool_var: true
replaced_obj._same_name_bool_var: true

点击进入第四关

第四关

第三关结束跳转到了FridaActivity4

如下是第四关的源代码

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
public class FridaActivity4 extends BaseFridaActivity {
@Override // com.example.androiddemo.Activity.BaseFridaActivity
public String getNextCheckTitle() {
return "当前第4关";
}

private static class InnerClasses {
public static boolean check1() {
return false;
}

public static boolean check2() {
return false;
}

public static boolean check3() {
return false;
}

public static boolean check4() {
return false;
}

public static boolean check5() {
return false;
}

public static boolean check6() {
return false;
}

private InnerClasses() {
}
}

@Override // com.example.androiddemo.Activity.BaseFridaActivity
public void onCheck() {
if (InnerClasses.check1() && InnerClasses.check2() && InnerClasses.check3() && InnerClasses.check4() && InnerClasses.check5() && InnerClasses.check6()) {
CheckSuccess();
startActivity(new Intent(this, (Class<?>) FridaActivity5.class));
finishActivity(0);
return;
}
super.CheckFailed();
}
}

这里出现了内部类,需要把内部类中的方法返回值都设置为true

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
function hook_activity4(){
var InnerClasses = Java.use("com.example.androiddemo.Activity.FridaActivity4$InnerClasses");
InnerClasses.check1.implementation = function(){
return true;
}

InnerClasses.check2.implementation = function(){
return true;
}

InnerClasses.check3.implementation = function(){
return true;
}

InnerClasses.check4.implementation = function(){
return true;
}

InnerClasses.check5.implementation = function(){
return true;
}

InnerClasses.check6.implementation = function(){
return true;
}
}

点击进入第五关

第五关

第四关结束跳转到了FridaActivity5

查看check逻辑

尝试hook check方法

1
2
3
4
5
6
7
8
9
10
11
function hook_activity5(){
Java.perform(function(){
let CheckInterface = Java.use("com.example.androiddemo.Dynamic.CheckInterface");
CheckInterface["check"].implementation = function () {
console.log(`CheckInterface.check is called`);
let result = this["check"]();
console.log(`CheckInterface.check result=${result}`);
return result;
};
})
}

没有输出

查看getDynamicDexCheck方法

hook getDynamicDexCheck

1
2
3
4
5
6
7
8
9
10
11
function hook_activity5(){
Java.perform(function(){
let FridaActivity5 = Java.use("com.example.androiddemo.Activity.FridaActivity5");
FridaActivity5["getDynamicDexCheck"].implementation = function () {
console.log(`FridaActivity5.getDynamicDexCheck is called`);
let result = this["getDynamicDexCheck"]();
console.log(`FridaActivity5.getDynamicDexCheck result=${JSON.stringify(result)}`);
return result;
};
})
}

hook 结果

1
2
FridaActivity5.getDynamicDexCheck is called
FridaActivity5.getDynamicDexCheck result="<instance: com.example.androiddemo.Dynamic.CheckInterface, $className: com.example.androiddemo.Dynamic.DynamicCheck>"

查看loaddex()方法

源码如下

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
private void loaddex() {
File cacheDir = getCacheDir();
if (!cacheDir.exists()) {
cacheDir.mkdir();
}
String str = cacheDir.getAbsolutePath() + File.separator + "DynamicPlugin.dex";
File file = new File(str);
try {
if (!file.exists()) {
file.createNewFile();
copyFiles(this, "DynamicPlugin.dex", file);
}
} catch (IOException e) {
e.printStackTrace();
}
try {
this.DynamicDexCheck = (CheckInterface) new DexClassLoader(str, cacheDir.getAbsolutePath(), null, getClassLoader()).loadClass("com.example.androiddemo.Dynamic.DynamicCheck").newInstance();
if (this.DynamicDexCheck == null) {
Toast.makeText(this, "loaddex Failed!", 1).show();
}
} catch (Exception e2) {
e2.printStackTrace();
}
}

public CheckInterface getDynamicDexCheck() {
if (this.DynamicDexCheck == null) {
loaddex();
}
return this.DynamicDexCheck;
}

通过 DexClassLoader动态加载一个 .dex 文件(DynamicPlugin.dex),然后通过反射创建并实例化其中的一个类 com.example.androiddemo.Dynamic.DynamicCheck,并将其转换为 CheckInterface 接口的实例。

enumerateClassLoader()

枚举类加载器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function enumclassLoader(){
Java.perform(function(){
Java.perform(function () {
Java.enumerateClassLoaders({
onMatch: function (loader) {
console.log("Found ClassLoader: " + loader);
},
onComplete: function () {
console.log("ClassLoader enumeration complete");
}
});
});
})
}

hook 结果

1
2
3
4
5
Found ClassLoader: dalvik.system.PathClassLoader[DexPathList[[directory "."],nativeLibraryDirectories=[/system/lib64, /system/lib64]]]
Found ClassLoader: java.lang.BootClassLoader@1a74373
Found ClassLoader: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.example.androiddemo-gOT-dni2KR2j2MT4Q9cP5g==/base.apk"],nativeLibraryDirectories=[/data/app/com.example.androiddemo-gOT-dni2KR2j2MT4Q9cP5g==/lib/arm64, /data/app/com.example.androiddemo-gOT-dni2KR2j2MT4Q9cP5g==/base.apk!/lib/arm64-v8a, /system/lib64]]]
Found ClassLoader: dalvik.system.DexClassLoader[DexPathList[[dex file "/data/user/0/com.example.androiddemo/cache/DynamicPlugin.dex"],nativeLibraryDirectories=[/system/lib64]]]
ClassLoader enumeration complete

dalvik.system.DexClassLoader就是要找的类加载器

修改DexClassLoadercheck进行hook

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
function enumclassLoader(){
Java.perform(function () {
Java.enumerateClassLoaders({
onMatch: function (loader) {
try{
if(loader.findClass("com.example.androiddemo.Dynamic.DynamicCheck")){
console.log("loader:",loader)
Java.classFactory.loader = loader;
}
}catch(e){

}
},
onComplete: function () {
console.log("ClassLoader enumeration complete");
}
});

let DynamicCheck = Java.use("com.example.androiddemo.Dynamic.DynamicCheck");
DynamicCheck["check"].implementation = function () {
console.log(`DynamicCheck.check is called`);
let result = this["check"]();
console.log(`DynamicCheck.check result=${result}`);
result = true;
console.log(`DynamicCheck.check replaced result=${result}`)
return result;
};
});
}

hook结果

1
2
3
4
5
6
enumclassLoader()
loader: dalvik.system.DexClassLoader[DexPathList[[dex file "/data/user/0/com.example.androiddemo/cache/DynamicPlugin.dex"],nativeLibraryDirectories=[/system/lib64]]]
ClassLoader enumeration complete
DynamicCheck.check is called
DynamicCheck.check result=false
DynamicCheck.check replaced result=true

点击进入第六关

第六关

第五关结束跳转到了FridaActivity6

这里有三个check函数,他们的逻辑都是一样的,如下

hook check

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
function hook_activity6(){
Java.perform(function(){
let Frida6Class0 = Java.use("com.example.androiddemo.Activity.Frida6.Frida6Class0");
Frida6Class0["check"].implementation = function () {
console.log(`Frida6Class0.check is called`);
let result = this["check"]();
console.log(`Frida6Class0.check result=${result}`);
result = true;
console.log(`Frida6Class0.check replaced result=${result}`);
return result;
};

let Frida6Class1 = Java.use("com.example.androiddemo.Activity.Frida6.Frida6Class1");
Frida6Class1["check"].implementation = function () {
console.log(`Frida6Class1.check is called`);
let result = this["check"]();
console.log(`Frida6Class1.check result=${result}`);
result = true;
console.log(`Frida6Class1.check replaced result=${result}`);
return result;
};

let Frida6Class2 = Java.use("com.example.androiddemo.Activity.Frida6.Frida6Class2");
Frida6Class2["check"].implementation = function () {
console.log(`Frida6Class2.check is called`);
let result = this["check"]();
console.log(`Frida6Class2.check result=${result}`);
result = true;
console.log(`Frida6Class2.check replaced result=${result}`);
return result;
};
})
}

hook 结果

1
2
3
4
5
6
7
8
9
Frida6Class0.check is called
Frida6Class0.check result=false
Frida6Class0.check replaced result=true
Frida6Class1.check is called
Frida6Class1.check result=false
Frida6Class1.check replaced result=true
Frida6Class2.check is called
Frida6Class2.check result=false
Frida6Class2.check replaced result=true