动态hook神器

准备工作

电脑上安装好 frida 环境后,在 github 上 https://github.com/frida/frida/releases 下载你手机对应架构的frida-server ,解压然后

1
adb push frida-server-12.6.21-android-arm64 /data/local/tmp/

手机连接电脑,利用 adb shell 运行该服务

1
2
3
4
adb shell
cd /data/local/tmp/
chmod 755 frida-server-12.6.21-android-arm64
./frida-server-12.6.21-android-arm64

此时如果没有报错,手机上的frida-server就算启动了

接着再把frida-server的端口转发到电脑

1
adb forward tcp:27042 tcp:27042

编写 hook 代码

以下使用的 python 环境来运行 frida,记录了使用 frida 的学习过程,可能会不定期更新

目录结构:

hook.py

intent.js

入口代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import frida
import sys
import os

packageName = "com.xxx.xxx"

def messagesCallback(message, data):
    if message['type'] == 'send':
        print(message['payload'])
    elif message['type'] == 'error':
        print(message['stack'])

hookCode = None
with open(os.path.dirname(os.path.realpath(__file__)) + '/intent.js') as f:
    hookCode = f.read()

process = frida.get_remote_device().attach(packageName)
print(process)
process.enable_debugger()
script = process.create_script(hookCode)
script.on('message', messagesCallback)
script.load()
sys.stdin.read()

其中packageName是你要 hook 的 app 包名

intent.js是用 js编写的 hook 代码,注意这个是文件路径。我自己是用读取hook代码文件的方式,这样就不必把 js 代码混在 python 里,比较清晰明了。

先来写一个简单的 hook, 用来读取 intent 的传值

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
Java.perform(function () {
    var act = Java.use("android.app.Activity");
    var process = Java.use("android.os.Process");

    act.getIntent.overload().implementation = function () {

        send("act:" + this.getIntent().getExtras());
        return this.getIntent();

    };

    process.start.overload('java.lang.String', 'java.lang.String', 'int', 'int', '[I', 'int', 'int', 'int', 'java.lang.String', 'java.lang.String', 'java.lang.String', 'java.lang.String', 'java.lang.String', '[Ljava.lang.String;').implementation = function (a, b, c, d, e, f, g, h, j, k, l, m, n, o) {
        var id = 5;
        var flags = arguments[id];
        send("before arg:" + flags);
        arguments[5] = 1;
        send("after arg:" + arguments[id]);

        return this.start(a, b, c, d, e, f, g, h, j, k, l, m, n, o);
    };

})
  • Java.use用来寻找要 hook 的类

  • act.getIntent.overload().implementation = function () {}是重写该hook类的 getIntent()方法

  • 如果要 hook 的方法带参数,需要在overload里传对应类型

function传自定义的参数名字. 例如上面例子里的process.start.overload

或者可以不写 overload,但是function里面的参数个数需要和 hook 的函数一样

1
  process.start.implementation = function (a, b, c, d, e, f, g, h, j, k, l, m, n, o) {}
  • 参数为基本类型可以简写,比如byte -> B, int -> I, boolean -> Z

对于数组类型,要添加前缀[,例如int[] -> [I, String[] -> [Ljava.lang.String

  • 获取 hook 回调里对象的普通field 字段值
1
  this.myField.value

如果字段名字和函数名重复,需要加_访问

1
2
3
4
5
6
  class Test{
   public int a;
   public void a(){
   	return 0;
   }
  }
1
  this._a.value
  • 获取 hook 回调里对象的Map类型 field 字段值

https://github.com/frida/frida/issues/488

1
2
3
4
5
6
7
8
  var HashMapNode = Java.use('java.util.HashMap$Node');
  
  var iterator = this.myMap.value.entrySet().iterator();
  while (iterator.hasNext()) {
    var entry = Java.cast(iterator.next(), HashMapNode);
    console.log(entry.getKey());
    console.log(entry.getValue());
  }
  • 打印一个类的方法和字段

https://github.com/frida/frida-java-bridge/issues/44

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
  
  function describeJavaClass(className) {
      var jClass = Java.use(className);
      console.log(JSON.stringify({
          _name: className,
          _methods: Object.getOwnPropertyNames(jClass.__proto__).filter(function (m) {
              return !m.startsWith('$') // filter out Frida related special properties
                  || m == 'class' || m == 'constructor' // optional
          }),
          _fields: jClass.class.getFields().map(function (f) {
              return f.toString()
          })
      }, null, 2));
  }
  • 构造一个对象
1
2
  var array_list = Java.use("java.util.ArrayList");
  var k = array_list.$new();
  • 类型强转
1
2
  var hookCls = Java.use("java.util.Map");
  var map = Java.cast(obj,hookCls)