Frida 常用姿势
Frida是一个跨平台的动态代码注入工具集,你可以使用它hook应用程序,插入自己的JavaSciprt代码,同时也能获取应用的内存和函数的完全访问权限。
推荐使用此工具进行追踪函数调用等操作,比使用Theos
的logify.pl
工具方便多了。
文档:https://www.frida.re/docs/examples/ios/
使用局域网连接
ssh root@192.168.50.227 -p 22 # 登录设备
/usr/sbin/frida-server -l 0.0.0.0:6666 # 手动启用服务,监听 6666 端口
frida -H 192.168.50.227:6666 -n Springboard # 电脑端连接远程设备
加载 JS 注入到指定名字的进程
frida -n Twitter -l demo1.js # 本机
frida -U -n Twitter -l demo1.js # USB 设备
frida -U -f com.sougu.MyBus -l demo1.js --no-pause # 启动应用,并且启动后不暂停
列出本机所有正在运行的进程和名称
frida-ps
列出 USB 连接的设备的所有正在运行的进程和名称
frida-ps -Uai
帮助
frida-ps -h
列出所有连接的设备
frida-ls-devices
跟踪 Native API
frida-trace -n Twitter -i "*URL*" # 本机
frida-trace -U Twitter -i "*URL*" # USB设备上的Twitter
跟踪 Objective-C API
frida-trace -U -f 'com.sougu.MyBus' -m "-[NSURL* *HTTP*]"
frida-trace -U Twitter -m "-[NSURL* *HTTP*]"
frida-trace -U -p 6604 -m "-[NSURL* *HTTP*]"
frida-trace -U QQ -m "-[CFT_PayCenterBusi *]" -m "+[CFT_PayCenterBusi init*]" -m "*[QIMService postRegisteNotification:Object:userInfo:]" # 追踪多个,实例方法,类方法,所有方法
追溯一个 Objective-C 方法调用
# Add the following code to the onEnter event-handler in the auto-generated JS of the desired API
log('\tBacktrace:\n\t' + Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n\t'));
将数据写入文件
agent.js:
var data = { foo: 'bar' };
send(data);
app.py:
import frida
def on_message(message, data):
print(message['payload'])
调用 Native 函数
var address = Module.findExportByName('libsqlite3.dylib', 'sqlite3_sql');
var sql = new NativeFunction(address, 'char', ['pointer']);
sql(statement);
将 NSData 转换为字符串
var data = new ObjC.Object(args[2]);
Memory.readUtf8String(data.bytes(), data.length());
# Tip: 2nd argument (number of bytes) is not required if the string data is null-terminated.
将 NSData 转换为二进制数据
Memory.readByteArray(data.bytes(), data.length());
迭代一个 NSArray
var count = array.count();
for (var i = 0; i !== count; i++) {
var element = array.objectAtIndex_(i);
}
迭代一个 NSDictionary
var enumerator = dict.keyEnumerator();
var key;
while ((key = enumerator.nextObject()) !== null) {
var value = dict.objectForKey_(key);
}
解档
var parsedValue = ObjC.classes.NSKeyedUnarchiver.unarchiveObjectWithData_(value);
读一个结构体
# If args[0] is a pointer to a struct, and let’s say you want to read the uint32 at offset 4, you can do it as shown below:
Memory.readU32(args[0].add(4));
显示一个弹窗
<= 7.0
var UIAlertView = ObjC.classes.UIAlertView; /* iOS 7 */
var view = UIAlertView.alloc().initWithTitle_message_delegate_cancelButtonTitle_otherButtonTitles_(
'Frida',
'Hello from Frida',
NULL,
'OK',
NULL);
view.show();
view.release();
>= 8.0
// Defining a Block that will be passed as handler parameter to +[UIAlertAction actionWithTitle:style:handler:]
var handler = new ObjC.Block({
retType: 'void',
argTypes: ['object'],
implementation: function () {
}
});
// Import ObjC classes
var UIAlertController = ObjC.classes.UIAlertController;
var UIAlertAction = ObjC.classes.UIAlertAction;
var UIApplication = ObjC.classes.UIApplication;
// Using Grand Central Dispatch to pass messages (invoke methods) in application's main thread
ObjC.schedule(ObjC.mainQueue, function () {
// Using integer numerals for preferredStyle which is of type enum UIAlertControllerStyle
var alert = UIAlertController.alertControllerWithTitle_message_preferredStyle_('Frida', 'Hello from Frida', 1);
// Again using integer numeral for style parameter that is enum
var defaultAction = UIAlertAction.actionWithTitle_style_handler_('OK', 0, handler);
alert.addAction_(defaultAction);
// Instead of using `ObjC.choose()` and looking for UIViewController instances
// on the heap, we have direct access through UIApplication:
UIApplication.sharedApplication().keyWindow().rootViewController().presentViewController_animated_completion_(alert, true, NULL);
})
调用 [UIApplication openURL:]
// Get a reference to the openURL selector
var openURL = ObjC.classes.UIApplication["- openURL:"];
// Intercept the method
Interceptor.attach(openURL.implementation, {
onEnter: function(args) {
// As this is an ObjectiveC method, the arguments are as follows:
// 0. 'self'
// 1. The selector (openURL:)
// 2. The first argument to the openURL selector
var myNSURL = new ObjC.Object(args[2]);
// Convert it to a JS string
var myJSURL = myNSURL.absoluteString().toString();
// Log it
console.log("Launching URL: " + myJSURL);
}
});
IDE 自动提示
$ git clone git://github.com/oleavr/frida-agent-example.git
$ cd frida-agent-example/
$ npm install
$ npm run watch # 监控代码修改自动编译生成js文件
使用 VSCode 等 IDE 打开此工程,在 agent 下编写 typescript,会有智能提示。
$ frida -U -f com.example.android --no-pause -l _agent.js
Python 脚本的编写
from __future__ import print_function
import frida
import sys
# 进程名
process_name = 'myprocess'
# 导入的js脚本
js_file_name = 'myhookjs.js'
# 自定义回调函数
# 数据通过send(message [,data])传递给 python 的 on_message(消息,数据)函数,其中我们前面已经介绍过了,
# 第一个参数是一个 python 字典类型,其中的 message['payload'] 存放的就是第一个参数内容
def on_message(message, data):
if message['type'] == 'send':
print(message['payload'])
elif message['type'] == 'error':
print(message['stack'])
# hook逻辑脚本
def get_js_code():
js_file = open(js_file_name) # type: BinaryIO
return js_file.read()
# start here
if __name__ == '__main__':
# 注入进程,attach传入进程名称(字符串)或者进程号(整数)
process_id = 0
device = frida.get_usb_device()
# 循环等待,根据进程名查找进程pid。找到执行hook
while True:
try:
process1 = device.get_process(process_name)
process_id = process1.pid
# 也可用
# pid = device.spawn([“com.android.chrome”])
print(process_id)
break
except:
pass
session = device.attach(process_id)
# 指定JavaScript脚本
# script = session.create_script(get_js_code()% int(sys.argv[1], 16)))
script = session.create_script(get_js_code())
script.on('message', on_message)
script.load()
# 读取返回输入
sys.stdin.read()
# int() 函数把字符串表示的16进制数转换成整数
# 上面的jscode % int(sys.argv[1], 16)是python格式化字符串的语法
Frida hook Object-C
Attach 方法
var className = "className";
var funcName = "functionName";
var hook = eval('ObjC.classes.' + className + '["' + funcName + '"]');
# Interceptor.attach(target, callbacks)
# target是NativePointer指定要拦截调用的函数的地址
# 如果从Frida API获取地址(例如Module.getExportByName()),Frida将处理详细信息
Interceptor.attach(hook.implementation,{
# 回调函数给出一个参数 args,可用于读取或写入参数作为NativePointer对象数组
onEnter: function(args)
{
},
# 给定一个参数的回调函数,该参数 retval是NativePointer包含原始返回值的衍生对象
# 请注意,此对象在onLeave调用中循环使用,因此请勿在回调之外存储和使用它。如果需要存储包含的值,请进行深层复制,例如:ptr(retval.toString())
onLeave: function(retval)
{
}
}
);
查看参数类型
# objc的函数,第0个参数是id,第1个参数是SEL,真正的参数从args[2]开始
console.log("Type of arg[2] -> " + new ObjC.Object(args[2]).$className)
参数(返回值)NS 与 JS 类型转换
# log String
var myString = new ObjC.Object(args[2]);
console.log("String argument: " + myString.toString());
# NSString(NCFString) to String
var NSString = new ObjC.Object(args[2]);
var str = NSString.UTF8String();
# replace js String
str = str.replace(/BJP/,"HZH");
# log String
console.log(str);
# NSNumber to Int
var myNumber = args[3].toInt32();
console.log(myNumber);
# Converting NSData to String
var data = new ObjC.Object(args[2]);
var myString = data.bytes().readUtf8String(data.length());
console.log(myString);
# Converting NSData to Base64String
var myString = new ObjC.Object(args[2]); var base = myString.base64EncodedStringWithOptions_(0)
console.log("String argument: " + base);
Tip: 2nd argument (number of bytes) is not required if the string data is null-terminated.
# Converting NSData to Binary Data
var data = new ObjC.Object(args[2]);
data.bytes().readByteArray(data.length());
替换参数
var str ="hello";
var newstring = ObjC.classes.NSString.stringWithString_(str);
args[2] = newstring;
替换返回值
#用整数1337替换返回值
retval.replace(1337)
#用指针替换
retval.replace(ptr("0x1234"))
调用函数
var st = Memory.allocUtf8String("TESTMEPLZ!");
#In NativeFunction param 2 is the return value type,
#and param 3 is an array of input types
var f = new NativeFunction(hook.implementation, 'pointer', ['pointer','char','pointer']);
#f(st,0,NSString1);
通过一个函数获得其他函数地址进行 Hook
var className = "DTURLRequestOperation";
var funcName = "- rpcV1Sign:newSign:request: ";
var hook = eval('ObjC.classes.' + className + '["' + funcName + '"]');
var rpcV1SignAddr = hook.implementation;
console.log('rpcV1SignAddr: ' + rpcV1SignAddr );
/*
var className2 = "DTURLRequestOperation";
var funcName2 = "- avmpSign: ";
var hook2 = eval('ObjC.classes.' + className2 + '["' + funcName2 + '"]');
var avmpSignAddr = hook2.implementation;
console.log('avmpSignAddr: ' + avmpSignAddr );
*/
#add的这个偏移是通过IDA的静态地址相减得到的
var avmpSignAddr = rpcV1SignAddr.add(0x1DCE);
console.log('avmpSignAddr: ' + avmpSignAddr);
Interceptor.attach(avmpSignAddr, {
onEnter: function(args){
console.log("onEnter");
console.log(args[0]);
console.log(args[1]);
},
onLeave: function(retval){
console.log("onLeave");
console.log(retval);
}
});
本文为原创文章,版权归字节时代所有,欢迎分享本文,转载请保留出处!
你可能也喜欢
- ♥ 使用宝塔安装 YOURLS 搭建短链接服务09/26
- ♥ macOS 提升应用执行权限的方案04/05
- ♥ 在越狱设备调试任意 iOS App 的 WebView04/05
- ♥ 内网穿透工具 ZeroTier 的私有化部署和组网07/03
- ♥ C++ 多态的实现原理04/05
- ♥ 使用 IOKit 监听 Mac 电脑的 USB 事件09/15