Frida介绍与实践

admin 2024年11月11日21:12:17评论80 views字数 7381阅读24分36秒阅读模式

Frida是一个面向开发人员、逆向工程师和安全研究人员的动态检测工具包。是一个基于 Python + JavaScript 的 Hook 与调试框架。将您自己的脚本注入黑盒流程。挂钩任何函数、监视加密 API 或跟踪私有应用程序代码,无需源代码。编辑,点击保存,立即看到结果。全部没有编译步骤或程序重新启动。

支持众多平台:Windows、Linux、Android、Mac、iOS等。且支持从Java层到Native层的完全hook。

Frida分为服务端与客户端,客户端将运行我们编写的代码并提交到服务端来运行。客户端可以选择与哪个服务端交互。有两种客户端,一种可以直接使用命令,运行javascript进行hook操作。另一种是通过python编写Hook前的准备动作,比如为某个设备运行某个JavaScript代码,具体的hook实现仍然需要JavaScript进行。

Frida与XPosed的区别:

Frida被归类于调试框架,它不能伴随应用启动,调试应用程序时,被调试的程序由Frida启动并进行hook操作。而XPosed则伴随整个系统启动而启动,它总是能在应用程序启动之前对它进行修改。

Frida支持实时热补丁,随时修改Hook代码并应用,不需要重新编译,也不需要重启应用,这是一个很酷的功能。XPosed每次修改需要重启系统。这个限制和第一点区别相互对应。

Frida还可以Hook 其他的多个平台,并且可以Hook 二进制库。

部署十分方便

不过完全可以先使用Frida确定一个可行的方案,再到XPosed中实现。

安装步骤:

客户端:pip install frida,frida-tools

服务端:https://github.com/frida/frida/releases 根据目标平台与位数选择合适的服务端。

Android在root下运行服务端,之后执行 frida-ls-devices,可以看到已经连接的设备。

一般会有一个local一个remote,分别对应着本地调试和本地套接字调试,这两个都表示本机,其他的则是远程设备。使用local可以直接调试本机程序。

Frida介绍与实践

frida-ps -D <ID>可以显示指定设备的进程列表。

Frida介绍与实践

可以通过python对设备信息获取和操作。

  1. 通过frida.get_device_manager获得设备管理器

  2. 通过device_manager.enumerate_devices枚举设

  3. 通过device.enumerate_processes枚举进程

  4. 通过device.attach挂接进程,并获得会话

  5. 通过script.load会话创建脚本并执行

  6. 编写JavaScript代码执行进程内操作。

  7. 通过script.on("message", on_message)注册后在JavaScript中使用send可以发送消息给python

import fridadef on_message(message,data):   print(message)   print(data)device_manager = frida.get_device_manager()devices = device_manager.enumerate_devices()index = 1for device in devices:   print("{0}:{1} => {2}".format(index,device.id, device.name))   index += 1index = int(input("选择设备:"))device = devices[index - 1]processes = device.enumerate_processes()index = 1for process in processes:   print("{0}: {1}".format(index, process.name))   index += 1index = int(input("选择进程:"))process = processes[index - 1]session = device.attach(process.name)script = session.create_script('''  console.log("123")  send("456")''')script.on("message", on_message)script.load()

另一种可以使用命令来完成这个动作

frida -D emulator-5554 -l .agent.js -f com.example.seccon2015.rock_paper_scissors

-D 选择设备

-l 选择JavaScript hook代码

-f 选择目标进程

Frida介绍与实践

Frida介绍与实践

在这之后,剩下的hook工作将在JavaScript中进行。JavaScript的API在官网有详细描述 https://frida.re/docs/javascript-api/

比如Hook一下MainActivity的onCreate方法:

需要在Java环境中进行Hook操作需要将代码放在Java.perform的回调中。

Java.use获得某个类,通过<类对象>.<需要Hook的方法>.impementation = function(){xxx} 就可以hook某个对象。此时不会调用原来的方法,如果需要调用原来的方法需要自行this.Hook的方法。

代码:
Java.perform(() => {   var mainActivityClass = Java.use("com.example.seccon2015.rock_paper_scissors.MainActivity")   mainActivityClass.onCreate.implementation = function (arg){       console.log("Hello world")       this.onCreate(arg)   }});

Frida介绍与实践

除了这种方法,我推荐使用vscode搭配TypeScript来进行操作。因为它可以给出方法补全,不用频繁查找文档。

Frida介绍与实践

具体安装参考 https://github.com/oleavr/frida-agent-example

安装好后前往agent/index.ts下编写相同的代码:

要注意TypeScript是强类型,按照规范参数尽量写明类型。
import { log } from "./logger.js";Java.perform(() => {   var mainActivityClass = Java.use("com.example.seccon2015.rock_paper_scissors.MainActivity")   mainActivityClass.onCreate.implementation = function (args:any){       console.log("Hello world")       this.onCreate(args)  }});

JavaScript是相同的Api,在需要时将代码翻译为JavaScript并执行。

如果不使用npm run watch命令,每次运行前需要使用npm install再次安装。

使用npm run watch命令后每次修改TypeScript都会自动编译并应用修改到应用程序中。

不过,要注意像MainActivity.onCreate之类的方法它一般只在启动时执行,此时仍然需要重启脚本才能生效。

为了方便,这里直接使用官网教程中的例题进行参考https://frida.re/docs/examples/android/

它是SECCON的SECCON Quals CTF 2015 APK1例题,可以在链接中下载。官网中只提供了一种解法,我这里为了演示更多功能,使用另外三种不同的方法进行解题。

首先通过观察代码:

Frida介绍与实践

Frida介绍与实践

代码中判断结果的逻辑被放在了一个匿名类中,需要当cnt等于1000时才会得到flag

因此我们需要让cnt等于999并且让最后一次猜拳是胜利的。

frida教程中是在onClick时修改nmcnt的值。由于onClick处于MainActivity下,直接获得对象就可以改(参考frida教程的做法)。

而我们会尝试在showMessageTask去修改MainActivity中的值。展示如何在内部类中访问外部类的成员:

来自于jadx的指引我们知道了匿名内部类的名字为com.example.seccon2015.rock_paper_scissors.MainActivity$1

Frida介绍与实践

随后去修改了它的run方法,通过this.this$0.value访问到了它的外部类对象。随后修改cntmn的值,就像frida教程那样。
Java.perform(() => {   const showMessageTask = Java.use("com.example.seccon2015.rock_paper_scissors.MainActivity$1")   showMessageTask.run.implementation = function()   {       console.log("called")       this.this$0.value.cnt.value = 999       this.this$0.value.m.value = 0       this.this$0.value.n.value = 1       this.run()   }});

三个之中的一个按钮后就可以得到 flag

第二种方法是复制输出flag的方法并调用MainActivity.this.calc()
Java.perform(() => {   const showMessageTask = Java.use("com.example.seccon2015.rock_paper_scissors.MainActivity$1")   showMessageTask.run.implementation = function(){       console.log("SECCON{" +(this.this$0.value.calc()+ 1000)*107 + "}")   }});
或者不绕圈子,直接启动时就输出
Java.perform(() => {   const MainActivity = Java.use("com.example.seccon2015.rock_paper_scissors.MainActivity")   MainActivity.onCreate.implementation = function(arg:any){       console.log("SECCON{" +(this.calc()+ 1000)*107 + "}")       this.onCreate(arg)   }});

Frida介绍与实践

第三种方法是直接调用二进制库:

我们可以首先通过如下代码定位那个函数的名称。如果你有ida之类的工具或者知道java JNI命名规则则可以不用这步。这里尽量演示更多功能。
Java.perform(() => {   const MainActivity = Java.use("com.example.seccon2015.rock_paper_scissors.MainActivity")   MainActivity.onCreate.implementation = function(arg:any){       var modules = Process.enumerateModules()       for (var index in modules)        {          if (modules[index].name == "libcalc.so"          {               var exports = modules[index].enumerateExports()               for (var export_index in exports)                {                  console.log(exports[export_index].name)               }          }       }       this.onCreate(arg)   }});

为了执行时lib库已经加载,选择onCreate作为挂载点。

Process.enumerateModules可以枚举当前进程的所有模块

此外Process还可以:

Frida介绍与实践

Modules.enumerateExports可以枚举导出表

Modules此外还可以:

Frida介绍与实践

最终我们会看到这样一条

Frida介绍与实践

最后我们可以通过:

Module.findExportByName获得指定库的导出函数的地址,它是一个NativePointer对象。

通过new NativeFunction让他变成一个函数,并为其设置它的返回值与参数,之后直接调用call就可以调用到二进制库中的函数了。

Java.perform(() => {   const MainActivity = Java.use("com.example.seccon2015.rock_paper_scissors.MainActivity")   MainActivity.onCreate.implementation = function(arg:any){       var pointer = Module.findExportByName("libcalc.so","Java_com_example_seccon2015_rock_1paper_1scissors_MainActivity_calc")       var nativeMethod = new NativeFunction(pointer as NativePointer, 'int', [])       console.log("SECCON{" +(nativeMethod.call()+1000)*107 + "}")       this.onCreate(arg)   }});

除了Androidhook,它在Windows下也表现得十分出色。我们可以通过将设备ID改为local来访问本机程序。

这里演示劫持网易云音乐的网络访问:

首先找到网易云程序路径,把启动命令改成类似于这样:

frida -D local -l ._agent.js -f D:CloudMusiccloudmusic.exe

Hook代码就像这样:

使用Interceptor.attach挂接Native函数,第一个参数为一个指针,通过Module.getExportByName可以获得。

第二个匿名类中包含onEnteronLeave两个回调函数,分别对应着进入函数时与退出函数时,它们都是可选的。

onEnter(args)args为参数列表,可以通过下标访问某个参数。

onLeave(retval)retval为函数返回值。

Hook connect需要先了解函数原型:

Frida介绍与实践

第一个为SOCKET对象。第二个就是我们关注的连接信息。

它的结构如图:(可以直接参考sockaddr_in的结构)

Frida介绍与实践

其中sin_port为端口,in_addrip地址,但它们无法直接使用,它们现在处于网络传送内存顺序(大端顺序),我们需要将其转换回小端顺序,并且还原回字符串形式的ip地址。

我们需要两个方法,分别是:

inet_ntoa:负责将十六进制的ip地址转换为字符串。

ntohs:将端口号转换为小端顺序。

使用Module.getExportByName获得函数地址。

new SystemFunction把它转换为可以调用的函数,并指定好参数和调用约定。要注意inet_ntoa传入的参数是in_addr结构,而不是in_addr指针,in_addr总长度为4

之后就可以通过call方法调用这两个函数。

剩下的就是读取出第二个参数的数值,并传递给上面两个函数并记录返回值。

point = args[1]取出第二个参数sockaddr

point .readUShort()取出sin_family==AF_INET,判断是不是IPv4协议,不是则放过。注意read并不会让这个指针向后偏移。

point.add(2)将会指向sin_port

point.add(4)将会指向in_addr

得到的返回值通过.value就可以取出数值,注意读取字符串需要用readXXXString

import { log } from "./logger.js";var AF_INET = 2var inet_ntoa = new SystemFunction(Module.getExportByName("ws2_32.dll","inet_ntoa"), 'pointer', ['uint32'], 'stdcall')var ntohs = new SystemFunction(Module.getExportByName("ws2_32.dll","ntohs"), 'uint16', ['uint16'], 'stdcall')Interceptor.attach(Module.getExportByName("ws2_32.dll","connect"), {   onEnter(args) {       let socket = args[0]       let point = args[1]       let family = point.readUShort()       if (family != AF_INET)           return       let ip_port = ntohs.call(null, point.add(2).readUShort())       let ip_addr = inet_ntoa.call(null, point.add(4).readUInt())       console.log(`socket=${socket},ip=${ip_addr.value.readAnsiString()},port=${ip_port.value}`)   },});

效果:

Frida介绍与实践

Frida基本用法就是这些,如果有其他需求可以前往api文档查阅。

https://frida.re/docs/javascript-api/

原文始发于微信公众号(锋刃科技):Frida介绍与实践

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年11月11日21:12:17
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Frida介绍与实践https://cn-sec.com/archives/1801747.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息