一、Frida介绍
Frida是一个跨平台的轻量级hook框架,通过向程序中注入JavaScript完成动态插桩。所以,他需要你懂一些JavaScript的知识。Frida提供了python、Node.js、Swift、.net、Qml等语言的接口封装,本文我们使用python+javascript完成一些简单程序的hook。
二、Frida安装
在服务端通过pip安装frida
pip install frida
根据手机处理器下载frida-server
https://github.com/frida/frida/releases
将frida-server push到手机并运行
adb push frida-server /data/local/tmp
adb shell
su
cd data/local/tmp
chmod 777 frida-server
./frida-server
一定要注意让frida-server在root权限下运行
另开一个命令行窗口进行端口转发
adb forward tcp:27042 tcp:27042
adb forward tcp:27043 tcp:27043
我们可以通过
frida-ps -R或frida-ps -U
测试是否搭建成功
正常情况下应该会显示我们手机上的所有进程
三、Frida使用
接下来我们分别通过hook java函数和native函数进行简单的使用介绍
我们的示例程序代码:
package com.example.a13251.fridatest;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
private TextView tv;
private TextView textView;
private Button button;
// Used to load the 'native-lib' library on application startup.
static {
System.loadLibrary("native-lib");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Example of a call to a native method
tv = (TextView) findViewById(R.id.sample_text);
textView=(TextView)findViewById(R.id.textView);
button= (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
tv.setText(String.valueOf(jia(5)));
textView.setText(stringFromJava());
}
});
}
private String stringFromJava() {
return "hello java";
}
public native int jia(int k);
}
jint Java_com_example_a13251_fridatest_MainActivity_jia(JNIEnv *env, jobject obj,
jint k) {
return k + 1;
}
当我们点击button时会调用jia函数和stringFromJava函数,其中jia()返回参数加一,stringFromJava()返回“hello java”
java hook代码如下
import frida
import sys
def on_message(message,data):
type = message["type"]
msg = message
if type == "send":
msg = message["payload"]
elif type == 'error':
msg = message['stack']
else:
msg=message
print msg
jscode="""
Java.perform(function(){
var s=Java.use("com.example.a13251.fridatest.MainActivity");
s.stringFromJava.implementation=function(){
var ss=this.stringFromJava();
send(ss);
return ss;
};
});
"""
rdev=frida.get_remote_device()
session=rdev.attach("com.example.a13251.fridatest")
script=session.create_script(jscode)
script.on('message',on_message)
script.load()
我们对于MainActivity类中的函数stringFromJava进行覆盖,在覆盖的函数中,我们调用了原函数,并将返回值返回,这样并不会影响程序的运行,不过我们在覆盖的函数中将返回值进行了输出。
对于jia() hook如下:
import frida
import sys
def on_message(message,data):
type = message["type"]
msg = message
if type == "send":
msg = message["payload"]
elif type == 'error':
msg = message['stack']
else:
msg=message
print msg
jjscode="""
Java.perform(function(){
var getJia=undefined;
exports=Module.enumerateExportsSync("libnative-lib.so");
for(i=0;i<exports.length;i++){
if(exports[i].name=="Java_com_example_a13251_fridatest_MainActivity_jia"){
getJia=exports[i].address;
send(getJia);
break;
}
}
Interceptor.attach(getJia,
{
onEnter:function(args){
send(args[2].toInt32());
},
onLeave:function(retval){
retval.replace(9999);
}
});
});
"""
rdev=frida.get_remote_device()
session=rdev.attach("com.example.a13251.fridatest")
script=session.create_script(jjscode)
script.on('message',on_message)
script.load()
通过Module.enumerateExportsSync("libnative-lib.so")获取so文件中所有的的函数,然后对其进行遍历,寻找名为Java_com_example_a13251_fridatest_MainActivity_jia的函数
其实
var getJia=undefined;
for(i=0;i<exports.length;i++){
if(exports[i].name=="Java_com_example_a13251_fridatest_MainActivity_jia"){
getJia=exports[i].address;
send(getJia);
break;
}
}
这一段函数我们可以通过
Module.findExportByName("libnative-lib.so","Java_com_example_a13251_fridatest_MainActivity_jia");
来完成,该函数的第一个参数可以为空,frida会遍历所有的文件去寻找具有相同名字的函数。
接下来,我们通过Interceptor.attach对jia()进行附加,Interceptor.attach的第一个参数为要附加的函数的地址,第二个参数为附加后执行的方法,onEnter表示当调用被附加函数时,onLeave表示当结束调用被附加函数时。我们在onEnter中输出jia()的参数,在onLeave中将返回值替换为9999。
除本文所出现的所有函数外,个人认为比较常用的函数还有
1.Memory.alloc(size),在被附加的程序堆中开辟size的空间,然后我们可以通过Memory.writeInt(addr,value) writeDouble(addr,value) writeUtf8String(addr,str)写入开辟的空间,通过Memory.readInt(addr)对于指定内存空间进行读操作;
2.New NativeFunction(address,returnType,argument),address为原函数的地址,returnType为原函数返回值,argument为原函数参数list,支持的类型为
- void
- pointer
- int
- uint
- long
- ulong
- char
- uchar
- float
- double
- int8
- uint8
- int16
- uint16
- int32
- uint32
- int64
- uint64
frida对于参数和返回值类型并不支持string,而是使用pointer进行了替代。
四、后记
frida对于art的支持并不理想,最好是使用安卓4.4对程序进行hook,我们可以通过观看frida使用手册https://www.frida.re/docs/home/ 来了解更多关于frida的使用说明。