Frida Hook基础

本文 Frida 代码使用 TypeScript,与 JavaScript 有细微差异。

frida_example.apk

Frida 开发环境

开发环境为 Visual Studio Code

  • 新建目录 frida-demo

  • 在目录中创建 package.json,内容如下,依赖不指定版本,均为最新

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    
    {
      "name": "frida-demo",
      "version": "1.0.0",
      "description": "Example Frida agent written in TypeScript",
      "private": true,
      "main": "index.ts",
      "scripts": {
        "prepare": "npm run build",
        "build": "frida-compile.cmd index.ts -o _agent.js -c",
        "watch": "frida-compile.cmd index.ts -o _agent.js -w"
      },
      "devDependencies": {
        "@types/frida-gum": "*",
        "@types/node": "*",
        "frida-compile": "*"
      }
    }
    
  • 在目录中创建 tsconfig.json,内容如下

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    {
      "compilerOptions": {
        "target": "esnext",
        "lib": ["esnext"],
        "allowJs": true,
        "noEmit": true,
        "strict": true,
        "esModuleInterop": true
      }
    }
    
  • 新建一个 TypeScript 文件,例如 index.ts

  • 初始化项目

    1
    
    npm install
    
  • 启动 watch 功能监控文件更改,自动编译

    在 vs code 中打开 package.json, 点击”调试“,选择 watch 即可。这样每次修改保存 index.ts 后,都会自动进行编译。

image-20220214145134373

  • 设置用户片段

    文件-首选项-用户片段,新建一个代码片段,内容如下,此处设置自动补全 implementation,如有需要,可参考设置其他补全。

    1
    2
    3
    4
    5
    6
    7
    
    {
    	"frida implementation": {
    		"scope": "javascript,typescript",
    		"prefix": "imp",
    		"body": "implementation = function($1)"
    	}
    }
    

image-20220214145555890

Frida Hook Java

hook 重载方法

启动 app,执行命令获取到当前登录页面的 Activity 名

1
2
# adb shell dumpsys window | grep mCurrentFocus
mCurrentFocus=Window{1f38bfa u0 com.example.androiddemo/com.example.androiddemo.Activity.LoginActivity}

使用 jadx 打开 apk,找到 LoginActivity,可以看到登录逻辑如下

image-20220214152329038

我们 hook 这里的函数 a,修改返回值为 1,这样我们用户名任意输入,密码输入 1 即可正常登录。

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

修改返回值

在 jadx 中拿到字符串,hook 函数,修改返回值即可。

1
2
3
4
5
6
7
8
9
function frida1() {
    Java.perform(function () {
        var FridaActivity1 = Java.use("com.example.androiddemo.Activity.FridaActivity1");
        FridaActivity1.a.implementation = function (x: any) {
            console.log("com.example.androiddemo.Activity.FridaActivity1.a");
            return "R4jSLLLLLLLLLLOrLE7/5B+Z6fsl65yj6BgC6YWz66gO6g2t65Pk6a+P65NK44NNROl0wNOLLLL=";
        }
    })
}

hookActivity2

修改变量的值

直接修改变量的值

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
function frida2_1() {
    Java.perform(function () {
        var FridaActivity2 = Java.use("com.example.androiddemo.Activity.FridaActivity2");
        FridaActivity2.static_bool_var.value = true;
        Java.choose("com.example.androiddemo.Activity.FridaActivity2", {
            onMatch: function (ins) {
                console.log("ins found =>", ins);
                ins.bool_var.value = true;
            }, onComplete: function () {
                console.log("Search completed!");
            }
        })
    })
}

调用静态/非静态方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
function frida2_2() {
    Java.perform(function () {
        Java.use("com.example.androiddemo.Activity.FridaActivity2").setStatic_bool_var();
        Java.choose("com.example.androiddemo.Activity.FridaActivity2", {
            onMatch: function (ins) {
                console.log("ins found =>", ins);
                ins.setBool_var();
            }, onComplete: function () {
                console.log("Search completed!");
            }
        })
    })
}

hook init

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
function frida2_3() {
    Java.perform(function () {
        Java.use("com.example.androiddemo.Activity.FridaActivity2").$init.implementation = function () {
            console.log("Frida2 hook init function");
            this.$init();
            // this.setBool_var();
            // this.setStatic_bool_var();
            this.bool_var.value = true;
            this.static_bool_var.value = true;
        }
    })
}

hook 同名变量

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
function frida3() {
    Java.perform(function () {
        Java.use("com.example.androiddemo.Activity.FridaActivity3").$init.implementation = function () {
            console.log("Frida3 hook init function");
            this.$init();
            this.static_bool_var.value = true;
            this.bool_var.value = true;
            this._same_name_bool_var.value = true;
        }
    })
}

hook Activity4

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
function frida4_1() {
    Java.perform(function () {
        Java.use("com.example.androiddemo.Activity.FridaActivity4$InnerClasses").check1.implementation = function () {
            console.log("InnerClasses.check1 called!");
            return true;
        }
        Java.use("com.example.androiddemo.Activity.FridaActivity4$InnerClasses").check2.implementation = function () {
            console.log("InnerClasses.check2 called!");
            return true;
        }
        Java.use("com.example.androiddemo.Activity.FridaActivity4$InnerClasses").check3.implementation = function () {
            console.log("InnerClasses.check3 called!");
            return true;
        }
        Java.use("com.example.androiddemo.Activity.FridaActivity4$InnerClasses").check4.implementation = function () {
            console.log("InnerClasses.check4 called!");
            return true;
        }
        Java.use("com.example.androiddemo.Activity.FridaActivity4$InnerClasses").check5.implementation = function () {
            console.log("InnerClasses.check5 called!");
            return true;
        }
        Java.use("com.example.androiddemo.Activity.FridaActivity4$InnerClasses").check6.implementation = function () {
            console.log("InnerClasses.check6 called!");
            return true;
        }
    })
}

枚举内部类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
function frida4_2() {
    Java.perform(function () {
        var class_name = "com.example.androiddemo.Activity.FridaActivity4$InnerClasses";
        var all_methods = Java.use(class_name).class.getDeclaredMethods();
        for (var i = 0; i < all_methods.length; i++) {
            var method = all_methods[i];
            var methodStr = method.toString();
            var substring = methodStr.substr(methodStr.indexOf(class_name) + class_name.length + 1);
            var funcname = substring.substr(0, substring.indexOf("("));
            Java.use(class_name)[funcname].implementation = function () {
                return true;
            }
        }
    })
}

hook动态加载的dex

 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
function frida5() {
    // Java.perform(function () {
    //     var groups = Java.enumerateMethods("*!check");
    //     console.log(JSON.stringify(groups, null, 2));
    // })
    var getDynamicDexCheckClassName = "";
    Java.choose("com.example.androiddemo.Activity.FridaActivity5", {
        onMatch: function (instance) {
            getDynamicDexCheckClassName = instance.getDynamicDexCheck().$className;
        }, onComplete: function () {

        }
    });
    console.log("className is =>", getDynamicDexCheckClassName);
    Java.enumerateClassLoaders({
        onMatch: function (loader) {
            try {
                if (loader.findClass(getDynamicDexCheckClassName)) {
                    console.log("Successfully found loader");
                    //Java.classFactory.loader 属性为只读,需要进入源码,删除只读属性
                    Java.classFactory.loader = loader;
                    console.log("Switch Classloader Successfully ! ")
                }
            } catch (error) {
                console.warn("continuing =>", error)
            }

        }, onComplete: function () {
            console.log("EnumerateClassloader END")
        }
    })

    Java.use(getDynamicDexCheckClassName).check.implementation = function () {
        return true;
    }
}

hookActivity6

依次hook

1
2
3
4
5
6
7
function frida6_1() {
    Java.perform(() => {
        Java.use("com.example.androiddemo.Activity.Frida6.Frida6Class0").check.implementation = function () { return true; };
        Java.use("com.example.androiddemo.Activity.Frida6.Frida6Class1").check.implementation = function () { return true; };
        Java.use("com.example.androiddemo.Activity.Frida6.Frida6Class2").check.implementation = function () { return true; };
    })
}

枚举类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
function frida6_2() {
    Java.perform(() => {
        Java.enumerateLoadedClasses({
            onMatch: function (classname) {
                if (classname.toString().indexOf("com.example.androiddemo.Activity.Frida6.Frida6Class") >= 0) {
                    console.log("Found Class => ", classname);
                    Java.use(classname).check.implementation = function () { return true; }
                }
            }, onComplete: function () {
                console.log("Search Class Completed!");
            }
        })
    })
}
updatedupdated2022-02-242022-02-24