Android Hook 之 Frida

Frida简介

Frida是一款基于python + javascript 的hook框架,通杀android\ios\linux\win\osx等各平台,由于是基于脚本的交互,相比xposed和substrace cydia更加便捷,本文重点介绍Frida在android下面的使用。

Frida的官网为:http://www.frida.re/

环境配置

  1. pip install frida 安装frida环境
  2. 在root的手机上运行frida server端
    1. wget https://build.frida.re/frida/android/arm/bin/frida-server
    2. adb push frida-server /data/lcoal/tmp
    3. chmod 755 frida-server
    4. ./frida-server &
  3. adb forward
    1. adb forward tcp:27042 tcp:27042
    2. adb forward tcp:27043 tcp:27043
  4. 完成配置,直接在PC端运行hook脚本即可

需要注意的是,frida的客户端和服务度版本应该相同,否则会出现不可预知的其他错误。

1
frida --version 查看版本

免root实现frida hook(重打包)

  1. apktool反编译apk

    1
    $ apktool d test.apk -o test
  2. 将对应版本的gadget拷贝到/lib没有了下.例如arm32的设备路径如下.

/lib/armeabi/libfrida-gadget.so
下载地址:
https://github.com/frida/frida/releases/

  1. smali注入加载library,选择application类或者Activity入口.

    1
    const-string v0, "frida-gadget" invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V
  2. 如果apk没有网络权限需要在配置清单中加入如下权限申明

    1
    <uses-permission android:name="android.permission.INTERNET" />
  3. 回编译apk

    1
    $ apktool b -o newtest.apk test/
  4. 重新签名安装运行.成功后启动app会有如下日志

Frida: Listening on TCP port 27042

实现hook

以下为frida hook的框架代码,只需要修改jscode就可以实现自定义的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
30
31
32
33
34
import frida, sys
def on_message(message, data):
if message['type'] == 'send':
print("[*] {0}".format(message['payload']))
else:
print(message)
jscode = """
Java.perform(function () {
// 要hook的类名
var MainActivity = Java.use('com.example.xxx.MainActivity');
// hook按钮点击事件
MainActivity.onClick.implementation = function (v) {
// Show a message to know that the function got called
send('onClick');

// 执行原始方法代码
this.onClick(v);

//修改MainActivity中属性的值
this.m.value = 0;
this.n.value = 1;
this.cnt.value = 999;
//打印日志
console.log('Done:' + JSON.stringify(this.cnt));
};
});
"""
# 设置要注入的进程名或者pid
process = frida.get_usb_device().attach('com.example.seccon2015.rock_paper_scissors')
script = process.create_script(jscode)
script.on('message', on_message)
print('[*] Running CTF')
script.load()
sys.stdin.read()

采坑记录

  1. 需要自己注意脚本加载时机 app启动后进行attach.可以使用-f参数frida来生成已经注入的进程(先注入Zygote为耗时操作),通常配合–no-pause使用.

    1
    frida -U -f com.tencent.tmgp.pubgmhd  -l chiji.js
  2. hook不上的原因?

  • 要hook的函数是不是正好被打了热补丁?
  • hook的进程是否正确,比如com.tencent.mm有好几个进程
  1. hook中可能出现“函数名参数完全相同,但返回值不同的情况”,这种情况frida暂时无法实现,收到测试此情况代码编译不会通过,但编译后可以改bytecode,不影响执行结果
  • 这种情况可以枚举函数的overloads属性,挨个hook,或者使用数组索引hook,例如a.overloads[1].implement=xxxx
  1. 使用enumerateLoadedClasses报“VM::GetEnv failed”错误
    enumerateLoadedClasses需要在Java.perform()下使用,参考https://github.com/frida/frida/issues/237

  2. Failed to spawn: unable to find application with identifier ‘com.dzvuhumnjt.kfwmalytfds’
    如果包名是正确的,应该是这个包名是一个服务,没有lunch activity, 此时只能靠启动后抢时间的方式来attach, spawn行不通

Frida 基础数据类型

Int

1
2
var Integerclass = Java.use("java.lang.Integer");
var myint = Integerclass.$new(1440);

Long

1
2
var Longclass = Java.use("java.lang.Long");
var myint = Longclass.$new(27893952512)

ByteArray

  1. js传给python

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // js中send(byte[]数组)
    def on_message(message, data):
    if message['type'] == 'send':
    data = message['payload']
    for i in range(len(data)):
    data[i] = data[i] & 0xff
    open("tmp",'wb').write(bytearray(data))
    else:
    pass
  2. python传给js

    1
    var a = Java.array('byte', [ 0xac,0x37,0x43,0x4f,0xaf,0xa8]);

Frida Hook实例

以下为我曾经用过或自己实现的frida脚本,主要适用于Android平台,根据不同的使用场景进行了分类,如需获取更多的使用或脚本,请参考awesome-frida,该项目搜集了各种通过frida实现的hook需求,并且不分区平台

https://github.com/dweinstein/awesome-frida

1. 打印hook函数的返回值

直接运行一遍原函数,将结果赋值给一个新变量,输出并return新变量即可,例如:
1
2
3
4
5
6
7
8
Java.perform(function () {
var cls = Java.use("com.tencent.mm.sdk.platformtools.w");
cls.w.overload("java.lang.String","java.lang.String").implementation=function(p1,p2){
var req = this.w(p1,p2);
console.log(req);
return req;
};
});

2. hook重载函数

1
2
cls.loadUrl.overload("java.lang.String").implementation = function(param)……
cls.loadUrl.overload("java.lang.String","java.util.Map").implementation=

3. 打印java函数的调用栈

1
2
3
4
5
6
7
var Exc = Java.use("java.lang.Exception");
var Log = Java.use("android.util.Log");
xxxx.implementation = function(param){
var e = Exc.$new("");
var log = Log.$new();
console.log(log.getStackTraceString(e));
};

4. 打印native函数调用栈

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
console.log("begin====");
var libavmp = Module.findBaseAddress("libsgavmpso-6.4.20.so");
var func = ptr(parseInt(libavmp)+0x1ea);
console.log("libavmp base: "+libavmp);
console.log("function base: "+func);
Interceptor.attach(func, {
onEnter: function(args) {
console.log(Thread.backtrace(this.context,Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join(" "));


},
onLeave:function(retval){
console.log("retval: "+retval);
}
});

5. dump内存

1
2
var data = Memory.readByteArray(ptr(0x824a9000), 159744);
console.log({type: 'data-for-you' }, data);

6. Hook Android IMEI

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Java.perform(function () {
var TM = Java.use("android.telephony.TelephonyManager");
console.log("hook start...");

TM.getSimSerialNumber.overload().implementation = function () {
console.log("Called - getSimSerialNumber(String)");
var temp = this.getSimSerialNumber();
console.log(temp);
return "123456789";
};

TM.getDeviceId.overload().implementation = function () {
console.log("Called - deviceID()");
var temp = this.getDeviceId();
console.log(temp);
return "867979021642856";
};

});

7. Hook Android webview http请求

主要针对以下webview中的以下函数:

1
2
3
4
5
- loadUrl(String url)   
- loadUrl(String url, Map<String, String> additionalHttpHeaders)
- loadData(String data, String mimeType, String encoding)
- loadDataWithBaseURL(String baseUrl, String data, String mimeType, String encoding, String historyUrl)
- postUrl(String url, byte[] postData)
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
Java.perform(function () {
var cls = Java.use("android.webkit.WebView");
console.log("class start:------------------");

cls.loadUrl.overload("java.lang.String").implementation = function(param){
console.log("loadUrl hooked "+param);
this.loadUrl("file:///sdcard/1.html");
};

cls.loadUrl.overload("java.lang.String","java.util.Map").implementation = function(p1,p2){
console.log("loadUrl2 hooked"+p1 + p2);
this.loadUrl("file:///sdcard/1.html",null);
};

cls.loadDataWithBaseURL.implementation = function(p1,p2,p3,p4,p5){
console.log("loadDataWithBaseURL hooked"+p1 + p2);
this.loadDataWithBaseURL("file:///sdcard/1.html",null,null,null,null);
};


cls.postUrl.implementation = function(p1,p2){
console.log("postUrl hooked"+p1);
this.postUrl("file:///sdcard/1.html",null);
};


});

8. 获取context

1
2
var currentApplication = Java.use('android.app.ActivityThread').currentApplication();
var context = currentApplication.getApplicationContext();

9. 创建bundle对象

1
2
3
4
5
6
7
8
9
var bundle = Bundle.$new();
//调用实例方法需要使用call
Bundle.putString.call(bundle,'key1','value1')
//也可以指定具体的参数类型
//Bundle.putString.overload('java.lang.String','java.lang.String').call(bundle,'key1','value1')
this.getIntent().putExtra('testBundle',bundle)
//activity.getIntent().getBundleExtra("testBundle");
var outB = this.getIntent().getBundleExtra('testBundle')
console.log(outB);

10. hook 对象

1
2
3
4
5
6
7
//读取实例对象的属性值,对于得到的对象,需要使用Java.cast()方法转换后才可以使用
// PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(),0);
// String pkg = packageInfo.packageName;
var t = this.getPackageManager().getPackageInfo(this.getPackageName(),0);
var packageInfo = Java.cast(t.$handle, PackageInfo);
var pkg = packageInfo.packageName.value
console.log(pkg)

11. 导出函数并被任意调用

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
rdev = frida.get_remote_device()
session = rdev.attach("com.eg.android.AlipayGphone")

jscode="""
rpc.exports = {
myfunc: function(aa,bb,cc){
Java.perform(function () {
try{
classf = Java.use('com.alipay.android.phone.wallet.sharetoken.service.f');
var result = classf.a(aa,bb,cc);//a为static函数
//f = Hrida.$new(); 非static函数需要new一个实例
console.log("myfunc result: "+result);
return result.toString()

}catch(e){
console.log(e)
}
});
//return result
}
}
"""
script = session.create_script(jscode)
script.on("message", my_message_handler)
script.load()

command = ""
while 1 == 1:
command = raw_input("Exit: 9999 0: default others: zhikouling")
if command == "9999":
break
else:
a = "b54578ff9d5fcbf6"
b = None
c = "快来吱付寳"
script.exports.myfunc(a,b,c)

12. hook构造函数

1
2
obj.$init.implementation = function (){
}

13.枚举所有加载的类

1
2
3
4
5
6
7
8
9
10
Java.enumerateLoadedClasses({
onMatch: function(classname){
if (classname.indexOf("XmlPullParser")>-1){
console.log(classname);
}
},

onComplete: function (){
}
});

其他参考

  1. https://github.com/FloatingGuy/fg-Blog/blob/7df0bd47b42d11b787fe394259fa30288307ae48/source/_posts/%E5%BC%80%E5%8F%91/frida%20hook%20%E6%93%8D%E4%BD%9C%E6%89%8B%E5%86%8C.md