基于 FRIDA 的全平台逆向分析

如果无法正常显示,请先停止浏览器的去广告插件。
分享至:
1. 基于 FЯIDA 的全平台逆向分析 caisi.zz@alipay.com
2. 关于 • 蚂蚁金服光年实验室高级安全工程师 • 从事多种平台客户端漏洞攻防研究 • BlackHat, XDEF 等国内外会议演讲者 • 知名 iOS App 审计工具 passionfruit 开发者 • frida 非官方布道师
3. 提纲 • frida 上手 • 自动化测试 • Javascript 进阶 • 操纵本地代码、Java 和 Objective C 运行时 • 多平台案例分析 • frida 高级编程技巧
4. 本课程所有示例代码请访问 https://github.com/ChiChou/gossip-summer-school-2018
5. 什么是 FЯIDA
6. 什么是 frida • https://www.frida.re/ Dynamic instrumentation toolkit for developers, reverse-engineers, and security researchers • 使用 javascript 脚本注入进程,执行二进制插桩。可以与调试器共存 • 在脚本和原生函数中实现了双向的 bridge。既可 hook 函数调用并修 改参数,也可使用脚本调用原生函数实现复杂功能 • 除本地代码外,内置对 Java 和 Objective C 运行时的支持 • 跨平台支持 Windows,macOS,GNU/Linux,iOS,Android 和 QNX(*)
7. 常⻅运行时插桩 • 硬件支持(如 Intel Pin) • 调试器 • Method Swizzling • 硬件断点 • ART 虚拟机 • 软件断点 • 函数指针(如导入表, Objective C isa 指针)重绑定 • Inline hook • 特定运行时
8. frida 插桩实现 • 主要是 inline hook • • • 在 iOS 上禁止 RWX 内存⻚,使用修改临时文件再 mmap 的方式 Objective C(github: frida/frida-objc) • implementation setter:Method Swizzling • Interceptor.attach:直接对 selector 指向的函数指针进行 inline hook Android ART / Dalvik(github: frida/frida-java) • 动态生成 JNI stub,修改结构体中的函数指针
9. 架构 frida 提供多语言绑定: • frida-python(含 cli) • frida-node • frida-qml • frida-swift • frida-clr frida binding 目标进程 脚本引擎 插桩引擎 frida-server IPC Android, iOS 设备,甚至是与 frida binding 运行的同一台计算机
10. • 需要 root 和刷机 ( * 注) • 热更新支持较差,每次修改代码 需要重启设备 ( * 注) • 以上问题可用 VirtualXposed 解决 • 原生 Java 语法,开发体验顺畅 • 不内置原生函数(如 JNI)的修 改,需结合其他框架实现 • 适合开发生产环境下使用的插件 • 无需 root,重打包植入 gadget 库仍然可用 • 支持 JNI 函数的 hook 和调用 • 需要转译 Java 到 js • 更新无需重启,适合快速迭代 的脚本开发
11. • 内置 Substrate 引擎,支持 hook • 模仿 Objective C 和 Javascript 的混合语法,体验非常顺滑 • 由于 iOS 强制代码签名限制,由 v8 改为 duktape解释器。以 ECMAScript5 为主,只支持极少 的 ES6 语法 • 移植现有 Objective C 代码需要手 写翻译到 js,实现复杂功能非常别 扭 • 甚至支持直接使用 RTLD_DFAULT 这样的常量 • iOS 11 后更新缓慢,有(可解决 的)兼容性问题 • 宏定义常量需要自行查找头文件获 得实际的值 • 除语法之外,两者 REPL 的使用 体验非常相似 • 支持到最新的 Electra 越狱(iOS 11),开箱即用
12. Why Javascript • • 优势:世界上最流行的脚本语言 • 学习成本简单 • 庞大的生态系统 缺点:作为插桩 DSL 局限性明显 • 使用弱类型语言操作强类型语言(C、Java、Objective C) • 实现同样功能,语法比原生代码冗⻓ • 操作二进制结构体非常麻烦
13. 安装部署 • 桌面端命令行工具:pip install frida-tools(可能需使用代理) • Android:https://github.com/frida/frida/releases • 有 root:下载 frida-server,adb 推入后以 root 执行。默认监听 TCP 27042 端口, 可通过 adb forward 转发到计算机上;或使用 frida-server -l 0.0.0.0 监听在局域网 • 无 root:重打包 apk 植入 FridaGadget.so • 反编译,修改 smali 添加 System.loadLibrary 调用 • 或使用 LIEF 等工具,附加依赖项到已有的 elf: https://lief.quarkslab.com/doc/latest/tutorials/09_frida_lief.html • 需要确保 AndroidManifest.xml 中开启网络访问权限
14. 安装部署 • iOS:https://github.com/frida/frida/releases • 已越狱:Cydia 市场中添加源 https://build.frida.re,安装 Frida(或 Frida for 32-bit devices) • 未越狱: • 对于具有原始工程的项目:将 FridaGadget.dylib 添加到链接库。需要 对 FridaGadget 手动添加开发者签名,以及关闭 Build Options 中的 Enable Bitcode 选项 • AppStore 的安装包具有加密壳。需从已越狱设备,或第三方市场中获 取解密后的 ipa 安装包,然后使用 MonkeyDev 集成:非越狱App集成
15. 安装部署 • 篇幅限制,完整的步骤参考文档 • https://www.frida.re/docs/ios/ • https://www.frida.re/docs/android/ • macOS 默认启用 SIP,无法附加系统自带进程。如有需求需关闭(具有一定安全⻛险) • 未越狱 / root 环境下的 frida 会有部分功能受限 • macOS, Linux 和 Windows 既可直接安装 frida-tools 测试本机进程,也可使用 frida- server 通过 TCP 协议远程测试 • 不推荐使用局域网方式连接 Android / iOS 设备,稳定性不如 USB,且任何人都可以连接 设备远程执行任意代码💥
16. cli 工具 frida 类似 python 等脚本解释器的 REPL frida-ps 列出可附加的进程 / App 列表 frida-trace 根据 glob 匹配符号并自动生成 hook 代码框架 修改 __handlers__ 中的脚本后会自动重新载入 frida-ls-devices 列出可用的设备 frida-kill 杀进程 frida-discover 记录一段时间内各线程调用的函数和符号名
17. ➜ /tmp frida-ls-devices Id ---------------------------------------- local *** tcp Type ------ local usb remote Name ------------ Local System iPhone Local TCP /tmp frida-ps -U PID Name ----- ----------------------------------------------- 9367 InCallService 13295 Mail 13077 AppleIDAuthAgent 13300 AssetCacheLocatorService 13792 CacheDeleteAppContainerCaches 13012 CloudKeychainProxy 4128 CommCenter ➜ ➜ /tmp frida-trace -i open iTunes Instrumenting functions... open: Loaded handler at "/private/tmp/__handlers__/libsystem_kernel.dylib/open.js" Started tracing 1 function. Press Ctrl+C to stop. /* TID 0x12e07 */ 43527 ms open(path="/Users/codecolorist/Music/iTunes/iTunes Library.itl", oflag=0x26) 43527 ms open(path="/Users/codecolorist/Music/iTunes/Temp File.tmp", oflag=0xa00) 43528 ms open(path="/Users/codecolorist/Music/iTunes/Temp File.tmp", oflag=0x26) /* TID 0x11d17 */ 43560 ms open(path="/Users/codecolorist/Music/iTunes/iTunes Library Genius.itdb-journal", oflag=0x1000202) 43562 ms open(path="/Users/codecolorist/Music/iTunes/iTunes Library Extras.itdb-journal", oflag=0x1000202)
18. REPL ➜ ~ frida Calculator ____ / _ | Frida 11.0.13 - A world-class dynamic instrumentation toolkit | (_| | > _ | Commands: /_/ |_| help -> Displays the help system . . . . object? -> Display information about 'object' . . . . exit/quit -> Exit . . . . . . . . More info at http://www.frida.re/docs/home/ [Local::Calculator]-> const POINTER_WIDTH = Process.pointerSize * 2; function formatPointer(p) { const hex = p.toString().slice(2); const padding = hex.length < POINTER_WIDTH ? '0'.repeat(POINTER_WIDTH - hex.length) : ''; return '0x' + padding + hex; } Process.enumerateRangesSync('').forEach(function(range) { console.log( range.protection, formatPointer(range.base), '-', formatPointer(range.base.add(range.size)), range.file ? range.file.path : ''); }); r-x rw- r-- rw- r-- rw- 0x0000000106d83000 0x0000000106da1000 0x0000000106dae000 0x0000000106db4000 0x0000000106db6000 0x0000000106db7000 - - - - - - 0x0000000106da1000 /Applications/Calculator.app/Contents/MacOS/Calculator 0x0000000106dae000 /Applications/Calculator.app/Contents/MacOS/Calculator 0x0000000106db4000 /Applications/Calculator.app/Contents/MacOS/Calculator 0x0000000106db6000 0x0000000106db7000 0x0000000106db8000
19. REPL • 支持 tab 自动补全 • 特殊命令 • • %load / %unload:载入 / 卸载文件中的 js • %reload:修改外部 js 之后重新载入,且重置之前的 hook • %resume:继续执行以 spawn 模式启动的进程 使用 quit / exit 或 Ctrl + D 退出
20. 语言绑定 • 官方提供的 binding: • frida-gum:没有 js 引擎,单纯的 hook 框架 • frida-core:具有 js 引擎的 C/C++ binding • 其他语言:frida-python(python)、frida-node(node.js)、frida-qml (Qt)、frida-swift(swift) • 缺少文档,建议直接参考源码 • frida-python:https://github.com/frida/frida-python/blob/master/src/ frida/core.py • JavaScript:https://github.com/frida/frida-gum/blob/master/bindings/ gumjs/types/frida-gum/frida-gum.d.ts
21. 自动化测试任务 以 python binding 为例
22. 设备 api 获得设备 all_devies = frida.enumerate_devices() local = frida.get_local_device() usb = frida.get_usb_device() remote = frida.get_device_manager().add_remote_device(ip) 设备事件处理 device_manager = frida.get_device_manager() device_manager.on('changed', on_changed) # listen device_manager.off('changed', on_changed) # remove listener 监听设备插拔 device_manager.on('add', on_changed) device_manager.on('changed', on_changed) device_manager.on('remove', on_removed) 进程管理 pid = device.spawn('com.apple.mobilesafari') device.resume(pid) device.kill(pid) App 信息 device.enumerate_applications()
23. 附加进程 / App • • 启动新的实例:device.spawn(‘path or bundle id’) • 可指定启动参数 • 支持在进程初始化之前执行一些操作 • iOS 上如果已经 App 运行(包括后台休眠)会导致失败 附加到现有进程:device.attach(pid) • 可能会错过 hook 时机 • spawn 在移动设备容易出现不稳定现象,可使用 attach 模式
24. spawn 选项 frida >= 11.0 支持的 spawn 参数 • argv:命令行 • cwd:当前目录 • envp:替换整个环境变量 • env:添加额外环境变量 • stdio:重定向 stdio • aux:平台特定参数(可合并到 kwargs) public class SpawnOptions : GLib.Object { public string[]? argv { get; set; } public string[]? envp { get; set; } public string[]? env { get; set; } public string? cwd { get; set; } public Frida.Stdio stdio { get; set; } public GLib.VariantDict aux { get; } public SpawnOptions (); } device.spawn("/bin/busybox", argv=["/bin/cat", "/etc/passwd"]) device.spawn("com.apple.mobilesafari") # by bundle id, for Android / iOS only device.spawn("/bin/ls", envp={ "CLICOLOR": "1" }) # replace original env device.spawn("/bin/ls", env={ "CLICOLOR": "1" }) # extends original env device.spawn("/bin/ls", stdio="pipe") device.spawn("com.apple.mobilesafari", url="https://frida.re") # invoke url scheme device.spawn("com.android.settings", activity=".SecuritySettings") # specific activity device.spawn("/bin/ls", aslr="disable") # disable ASLR
25. session 生命周期 not running device.spawn suspended device.attach device.resume already running device.attach attached Interceptor callbacks, Process.setExceptionHandler script rpc exports, events session.create_script unhandled exception, process termination session.detach detached frida script
26. session 对象方法 • on / off:添加 / 删除事件监听回调 • detached 事件:会话断开(进程终止等) • create_script:从 js 代码创建 Script 对象 • compile_script / create_script_from_bytes:将 js 编译为字节码,然后创建 Script • enable_debugger / disable_debugger:启用 / 禁用外部调试器 • enable_jit:切换到支持 JIT 的 v8 脚本引擎(不支持 iOS) • enable_child_gating / disable_spawn_gating:启用 / 禁用子进程收集
27. 事件和异常处理 • 脚本使用 send 和 recv 与 python 绑定进行双向通信 • recv 返回的对象提供 wait 方法,可阻塞等待 python 端返回 • 在 js 的回调中产生的异常,会生成一个 type: “error” 的消息 • 除了消息之外,python 还可调用 js 导出的 rpc.exports 对象中的方法。通 过返回 Promise 对象来支持异步任务(详⻅后续章节*) • rpc 接口产生的异常会直接抛出到 python,而不是交给 on(‘message’) 的 回调函数 • 示例代码:hello-frida/rpc.py
28. Javascript 进阶
29. frida 的 Javascript 引擎 • 由于 iOS 的 JIT 限制,以及嵌入式设备的内存压力,新版将默认脚 本引擎从 V8 迁移至 Duktape(http://duktape.org/) • 在 Android 等支持 v8 的平台上仍然可以使用 enable-jit 选项切换回 v8 • Duktape 比 v8 缺失了非常多 ECMAScript 6 特性,如箭头表达式、 let 关键字 http://wiki.duktape.org/PostEs5Features.html • frida --debug 启用调试需使用 Duktape,不兼容 v8-inspector
30. 箭头函数 • ECMAScript 6 引入的书写匿名函数的特性 • 需要启用 JIT,或 frida-compile 转译才可在 frida 中使用 • 比 function 表达式简洁。适合编写逻辑较短的回调函数 • 语义并非完全等价。箭头函数中的 this 指向父作用域中的上下文;而 function 可以通过 Function.prototype.bind 方法指定上下文 • 以下两行代码等价 Process.enumerateModulesSync().filter(function(module) { return module.path.startsWith('/Applications') }) Process.enumerateModulesSync().filter(module => module.path.startsWith('/ Applications'))
31. generator 函数 • generator(生成器)是一种具有“暂停”功能的特殊函数。用于生成集合、数列等 场景 • 语法上比普通函数多了一个 *,在函数内部使用 yield 关键字产生一个输出。调用 后获得一个 generator 对象,generator 对象的 next 方法执行到第一个 yield,暂 停 generator 函数 • 需要 v8 引擎支持;或使用 frida-compile 以及 transform-regenerator 插件 function *gen() { yield 1; yield 2; yield 3; } 暂停执行并返回,直到调 用下一次 next 方法 let list = gen(); console.log(list.next()); console.log(list.next()); console.log(list.next()); ➜ ~ node generator.js { value: 1, done: false } { value: 2, done: false } { value: 3, done: false }
32. generator 函数 • 浏览器和 node.js 中的 Javascript 使用单线程(注*),使用阻塞接口会导致界面 锁死或影响性能。AJAX 等接口设计成异步回调,嵌套使用会出现 callback hell • 因为生成器函数暂停执行而不阻塞事件循环的特点,被 js 社区用来管理异步控制 流 const then = Date.now(); setTimeout(function() { console.log(Date.now() - then); setTimeout(function() { setTimeout(function() { // do something }, 2000); }, 1000); }, 500); const co = require('co'); function sleep(ms) { return function (cb) { setTimeout(cb, ms); }; } co(function* () { var now = Date.now(); yield sleep(500); console.log(Date.now() - now); yield sleep(1000); yield sleep(2000); });
33. async / await • 调用一个 async 函数会返回一个 Promise 对象。当这个 async 函数 返回一个值时,Promise 的 resolve 方法会负责传递这个值; 当 async 函数抛出异常时,Promise 的 reject 方法也会传递这个异 常值 async function name([param[, param[, ... param]]]) { statements } • async 函数中可能会有 await 表达式,这会使 async 函数暂停执 行,等待表达式中的 Promise 解析完成后继续执行 async 函数并返 回解决结果 • await 关键字仅仅在 async function中有效。如果在 async function 函数体外使用 await ,会得到一个语法错误(SyntaxError)
34. async / await step1(function(value1) { // do something step2(value1, function(value2) { // do some other thing step3(value2, function(value3) { console.log('the final result', value3); }); }); }); const step1 = () => new Promise((resolve, reject) => { // do something resolve(result); }); const value1 = await step1(); const value2 = await step2(value1); const value3 = await step2(value2); console.log('the final result', value3);
35. Promise • Promise 对象是一个代理对象(代理一个值),被代理的值在 Promise 对象创建时可 能是未知的。它允许你为异步操作的成功和失败分别绑定相应的处理方法 (handlers)。 这让异步方法可以像同步方法那样返回值,但并不是立即返回最终执 行结果,而是一个能代表未来出现的结果的 promise 对象 • 一个 Promise有以下几种状态: • • pending: 初始状态,既不是成功,也不是失败状态。 • fulfilled: 意味着操作成功完成。 • rejected: 意味着操作失败。 因为 Promise.prototype.then 和 Promise.prototype.catch 方法返回promise 对象, 所以它们可以被链式调用。
36. Promise 状态转换 async actions settled .then() Pending Pending Promise Promise .then() .catch() return error handling .then() .catch() …
37. 使用 Promise • Duktape 原生支持 Promise 规范,但 async/await 或 yield 需要使 用 frida-compile 转译回 ES5 • rpc.exports 接口支持 Promise,可实现等待回调函数返回。示例代 码:hello-frida/rpc.py • frida 内置与 I/O 相关的接口 Socket, SocketListener, IOStream 及 其子类均使用 Promise 的接口。结合 Stream 可实现大文件传输等 异步任务
38. frida-compile • 需求 • 默认使用的 Duktape 不支持最新的 ECMAScript 特性 • 单个 js 文件,难以管理大型项目 • 可将 TypeScript 或 ES6 转译成 Duktape 可用的 ES5 语法 • 支持 Browserify 的打包,支持 ES6 modules、source map 和 uglify 代码压缩。甚至 可生成 Duktape 字节码 • 支持使用 require 或 es6 module 引用第三方 npm 包 • frida-compile 是 npm 包,需要 node.js 运行环境。与 frida-python 不冲突,可同时安 装使用
39. frida-compile • frida-compile 使用解语法糖和 polyfill 实现 ECMAScript 5 之后的特 性 • babel https://babeljs.io/docs/en/plugins/ • typescript https://www.typescriptlang.org/docs/home.html • 使用 TypeScript 可享受到类型系统带来的大型项目管理便利 • Babel 添加插件支持高级的语法特性(generator / async-await)
40. frida-compile npm 创建目录结构、安装依赖,在 package.json 中添加构建脚本 1 { 2 "name": "frida-ipa-agent", 3 "version": "0.0.1", 4 "description": "", 5 "main": "index.js", 6 "scripts": { 7 "test": "echo \"Error: no test specified\" && exit 1", 8 "build": "frida-compile src -o dist.js", 9 "watch": "frida-compile src -o dist.js --watch" 10 }, 11 "browserify": { 12 "transform": [ 13 [ 14 "babelify", 15 { 16 "presets": [ 17 [ 18 "es2015", 19 { 20 "loose": true 21 } 22 ] 23 ], 24 "plugins": [ 25 "transform-runtime" 26 ] 27 } frida-compile 命令参数 -o 输出文件名 -w 监视模式。源文件改动后立即编译 -c 开启 uglify 脚本压缩 -b 输出字节码 -h 查看完整命令行用法 -x, —no-babelify 关闭 babel 转译 browserify 中可深度配置编译参数, 通过加载 plugins 支持各种 babel 扩 展
41. 复用 npm 包 • 使用 frida-compile 之后可以引用第三方 npm 包 • 配置好 frida-compile 之后在相同目录 npm install,然后直接引用 const macho = require('macho') import macho from 'macho' • node.js 与 frida 的 Duktape 运行环境存在差异,无法完全兼容 • 缺少 node.js 内置核心模块导致依赖缺失 • 不支持 node C++ 扩展 • frida-compile 默认提供一些兼容 node.js 的内置包 https://github.com/frida/frida-compile/blob/master/index.js#L19
42. 复用 npm 包 模拟了部分 node.js 中的 process 对象、 socket、文件系统、http、大整数和 Buffer api const fridaBuiltins = Object.assign({}, require('browserify/lib/builtins'), { '_process': require.resolve('frida-process'), 'buffer': require.resolve('frida-buffer'), 'fs': require.resolve('frida-fs'), 'net': require.resolve('frida-net'), 'http': require.resolve('frida-http'), 'bignum': require.resolve('bignumber.js'), 'any-promise': require.resolve('frida-any-promise'), });
43. IOStream • 模仿 node.js 中的流式处理接口。InputStream 对应 ReadableStream,OutStream 对应 WritableStream • 优点:每次只读 / 写指定大小的缓冲区,节省内存使用 • 缺点:不支持文件随机访问 • 场景:大文件传输、流式的 parser、Socket 编程 • frida 内置的流对象:IOStream, OutputStream, InputStream, UnixInputStream, UnixOutputStream, Win32InputStream, Win32OutputStream, SocketConnection
44. IOStream read UnixInputStream fd InputStream write IOStream Win32InputStream HANDLE UnixOutputStream fd OutputStream Win32OutputStream R/W SocketConnection HANDLE
45. {Unix,Win32}{Input,Output}Stream • 构造器使用文件描述符 / 句柄而不是路径。根据不同平台的功能,使用文件句柄可 以访问除文件系统之外的其他资源(如驱动抽象的设备) • new UnixInputStream(fd[, options]) • new UnixOutputStream(fd[, options]) • new Win32InputStream(handle[, options]) • new Win32OutputStream(handle[, options]) • frida 目前没有提供获取 fd / HANDLE 的接口,需要自行封装 NativeFunction / SystemFunction 调用对应平台的接口(libc!open / user32!OpenFileW)获取 • 传输大文件的示例代码:stream-adb-pull/
46. 与本地代码交互
47. 指针和内存管理 • NativePointer:表示 C 中的指针,可指向任意(包括非法)地址 • frida 数据指针、函数指针、代码⻚地址、基地址等均依赖 NativePointer 接口 • 提供 add, sub, and, or, xor 和算术左右移运算。详⻅文档 • 可用 Memory.alloc /.allocUtf8String / .allocAnsiString / .allocUtf16String 分配 • Memory.alloc* 分配的内存,在变量作用域之外会被释放 function alloc() { return Memory.alloc(8); } const p = alloc(); // dangling pointer
48. 指针和内存管理 frida libc Memory.alloc(size) malloc Memory.scan(address, size, pattern, callbacks) Memory.scanSync(address, size, pattern) memmem Memory.copy(dst, src, n) memcpy Memory.protect(address, size, protection) mprotect
49. 内存读写 • 任意地址读写。实现解引用等 C 语言才有的功能 • Memory.write* 和 Memory.read* 系列函数,类型包括 S8, U8, S16, U16, S32, U32, Short, UShort, Int, UInt, Float, Double • 其中 Memory.{read,write}{S64,U64,Long,ULong} 由于 Javascript 引擎默认对数字精度有限制(不支持 64 位大整数),需要使用 frida 的 Int64 或 UInt64 大整数类
50. 内存读写 • • 字符串函数 • 分配 Memory.alloc{Ansi,Utf8,Utf16}String • 读取 Memory.read{C,Utf8,Utf16,Ansi}String(注意 CString 只提 供了 read) • 覆写 Memory.write{Ansi,Utf8,Utf16}String 读写一块连续内存:Memory.{read,write}ByteArray(想想为什么没 有 writeCString 和 allocCString)
51. Unicode 和 ANSI Windows 平台为兼容 16 位系统,以 A/W 后缀区分宽字符,用 TCHAR 宏处理 BOOL CreateProcessW( LPCWSTR LPWSTR LPSECURITY_ATTRIBUTES LPSECURITY_ATTRIBUTES BOOL DWORD LPVOID LPCWSTR LPSTARTUPINFOW LPPROCESS_INFORMATION ); lpApplicationName, lpCommandLine, lpProcessAttributes, lpThreadAttributes, bInheritHandles, dwCreationFlags, lpEnvironment, lpCurrentDirectory, lpStartupInfo, lpProcessInformation BOOL CreateProcessA( LPCSTR LPSTR LPSECURITY_ATTRIBUTES LPSECURITY_ATTRIBUTES BOOL DWORD LPVOID LPCSTR LPSTARTUPINFOA LPPROCESS_INFORMATION ); lpApplicationName, lpCommandLine, lpProcessAttributes, lpThreadAttributes, bInheritHandles, dwCreationFlags, lpEnvironment, lpCurrentDirectory, lpStartupInfo, lpProcessInformation wchar_t * char * Memory.readUtf16String Memory.readAnsiString wchar_t 并未规定宽字符的实际编码,以上只对 Windows API 适用
52. Windows 字符编码示例 const buf = Memory.alloc(1024); GetWindowTextA(hWnd, buf, 1024); console.log('ansi: ', Memory.readAnsiString(buf)); console.log('c string: ', Memory.readCString(buf)); GetWindowTextW(hWnd, buf, 1024); console.log('unicode16: ', Memory.readUtf16String(buf)); • • • • ANSI:使用 GetWindowTextA 返回的的中文正常显示,但终止符出现乱码 CString:Windows 上不支持中文 unicode16:使用 GetWindowTextW 返回的中文完美显示 使用 utf8 可能会抛出编码异常
53. 分析模块和内存⻚ • Process 对象 • 进程基本信息:arch, platform, pageSize, pointerSize 等 • 枚举模块、线程、内存⻚:enumerateThreads(Sync), enumerateModules(Sync), enumerateRanges(Sync) • 查找模块:findModuleByAddress, findModuleByName • 查找内存⻚:
54. 分析模块和内存⻚ • • frida 内置了导入导出表的解析 • Module.enumerateImports(Sync) • Module.enumerateExports(Sync) 确保模块初始化完成 Module.ensureInitialized • 例:等待 Objective C 运行时初始化: Module.ensureInitialized(‘Foundation’)
55. enumerate*Sync? • • frida 的 API 设计中存在大量具有 Sync 后缀的 api • 带 Sync 后缀:直接返回整个列表 • 不带 Sync 后缀:传入一个 js 对象,其 onMatch 和 onComplete 属性分别对应迭代的每一个元素的回调,和结束时 的回调函数。与 IO 不同的是这些回调函数都是同步执行的。 onMatch 函数可以返回 ‘stop’ 字符串终止迭代 猜测应是 Duktape 没有原生支持 js 迭代器的原因
56. 解析函数地址 • • • 导入 / 导出的符号: • Module.findExportByName(null, ‘open’) • Module.enumerateExports(Sync) / enumerateImports(Sync) 内嵌调试符号(未 strip) • DebugSymbol.getFunctionByName(“”) • DebugSymbol.findFunctionsNamed() • DebugSymbol.findFunctionsMatching 无符号:使用模块基地址 + 偏移 • Process.findModuleByName(‘Calculator’).base.add(0x3200)
57. ffi bridge SystemFunction NativeFunction Javascript native NativeCallback frida 中的 javascript 函数 可使用 NativeCallback 封 装成一个有效的机器码 stub 反之 javascript 引擎封装 了可以动态指定参数和返 回值类型的 foreign function interface 给 javascript
58. ffi bridge • NativeFunction:本地代码函数接口 • • 由于缺少类型信息,需要指定函数指针、原型,包括参数列表和返回值 类型,调用约定(可选) new NativeFunction(address, returnType, argTypes[, abi]) SystemFunction:类似 NativeFunction,但返回字典,根据平台不同可访 问 errno (POSIX) 或者 lastError 来获得错误信息。函数返回值在 value 属性 [Local::Finder]-> const openPtr = Module.findExportByName(null, 'open'); const nonExistFile = Memory.allocUtf8String('/aaa'); console.log(JSON.stringify(new SystemFunction(openPtr, 'int', ['pointer', 'int'])(nonExistFile, 0))); console.log(new NativeFunction(openPtr, 'int', ['pointer', 'int']) (nonExistFile, 0)); {"value":-1,"errno":2} -1
59. ffi bridge • NativeCallback • 返回一个 NativePointer,指向一个包装好的 javascript 回调。本 地代码执行到 NativePointer 的指针时将调用到 javascript const handler = new NativeCallback(function(sig) { console.log('signal:', sig); }, 'void', ['int']); const signal = 'int', ['int', const SIGINT = signal(SIGINT, new NativeFunction(Module.findExportByName(null, 'signal'), 'pointer']); 2; handler);
60. ffi bridge • 返回值和参数列表支持的类型:void, pointer, int, uint, long, ulong, char, uchar, float, double, int8, uint8, int16, uint16, int32, uint32, int64。 • 不同的头文件、编译环境可能有会 typedef 别名 • 结构体参数(注意不是结构体指针)可使用嵌套的 Array 表 示 • 可变参数可用 ‘…’ 处理
61. 可变参数 const NSLog = new NativeFunction(Module.findExportByName('Foundation', 'NSLog'), 'void', ['pointer', '...', 'pointer']); const format = ObjC.classes.NSString.stringWithString_('hello %@!'); const param = ObjC.classes.NSString.stringWithString_('world'); NSLog(format, param); ['pointer', '...', 'pointer'] … 格式串的类型 表示可变参数 剩余参数的对应类型 事实上无法像 NSLog 那样完全支持动态参数个数,需要 预先知道每个实际格式化的类型
62. 结构体参数 C 语言支持直接将结构体作为参数传递,调用时即使调用约定为使用寄存 器传参,结构体也将整个由堆栈传递 例如 iOS 中的 CGSize struct CGSize { CGFloat width; CGFloat height; }; 将所有的成员均列到数组中(缺一不可),支持嵌套 const CGFloat = (Process.pointerSize === 4) ? 'float' : 'double'; const CGSize = [CGFloat, CGFloat]; const UIGraphicsBeginImageContextWithOptions = new NativeFunction( Module.findExportByName('UIKit', 'UIGraphicsBeginImageContextWithOptions'), 'void', [CGSize, 'bool', CGFloat]);
63. ABI - default - Windows 32-bit: - sysv - stdcall - thiscall - fastcall - mscdecl - Windows 64-bit: - win64 - UNIX x86: - sysv - unix64 - UNIX ARM: - sysv - vfp default:frida 根据系统和 CPU 自动决定 cdecl:最常⻅的 C 语言调用约定,使用堆栈 传参 stdcall:Win32 API 使用调用约定 thiscall:MSVC 编译的 32 位 x86 C++ 程 序,特点是使用 ecx 传递 this 指针 win64:64 位系统微软统一了调用约定,使 用寄存器和堆栈结合的方式 unix64:与 win64 类似,但使用不同的寄存 器
64. Interceptor • • Interceptor 对指定函数指针设置 inline hook • Interceptor.attach:在进入函数之前,函数返回后分别调用 onEnter 和 onLeave 回调函数。可以对函数参数和返回值进行修 改,但原始函数一定会被调用 • Interceptor.replace:整个替换掉函数调用。可以在 js callback 里继续调用原始函数,也可以完全屏蔽掉 在 js 回调中可以访问 this 获得上下文信息,如常用寄存器、thread id 等;此外 this 还可以在 onEnter 保存额外的参数传递给 onLeave
65. Interceptor args: 以下标函数参数,默认均为 NativePointer。可用 toInt32 转换为整型 Interceptor.attach(Module.findExportByName("libc.so", "open"), { onEnter: function (args) { console.log(Memory.readUtf8String(args[0])); this.fd = args[1].toInt32(); }, onLeave: function (retval) { if (retval.toInt32() == -1) { /* do something with this.fd */ } } }); retVal.replace 可整个替换掉返回值
66. 调用堆栈 • 获取寄存器上下文 • 插桩回调中访问 this.context • Process.enumerateThreadsSync() 枚举线程信息 • Thread.backtrace 可根据上下文回溯出调用堆栈的地址 • DebugSymbol.fromAddress 进一步对地址符号化 console.log('\tBacktrace:\n\t' + Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n\t'));
67. 与 C++ C with classes 交互 • • 与 C 函数交互类似,区别在于 • this 指针传递的调用约定 • 对象内存的分配和成员 • 返回一个对象 不同编译器可能存在实现差异,以反汇编为准
68. 从反编译入手 反编译伪代码(clang)
69. 分配和传参 • • 两种分配方式 • 局部变量:栈上直接分配好 sizeof(Class),用起始地址(this 指针)作为第 一个参数调用 constructor • 动态分配:new 运算符被链接到库函数,同样也是分配 sizeof(Class) 大小, 调用构造器方式相同 构造器和成员函数的调用约定 • x86(_64) 上的 clang 和 gcc,均是将 this 指针作为第一个参数,使用寄存器 和堆栈传递参数的行为与 C 语言一致,使用 frida 的 default ABI 参数即可 • 而 MSVC 使用 ecx / rcx 传递 this,只有 x86 的 thiscall 被 frida 直接支持
70. frida 构造 C++ 对象 class Cat : public Animal { int m_weight; public: Cat(int age) : Animal(age), m_weight(5); Cat(int age, Color color) : Animal(age); void printDescription(); }; Cat cat(1, orange); cat.print(); const ctor = new NativeFunction(DebugSymbol.getFunctionByName('Cat::Cat(int, Color)'), 'pointer', ['pointer', 'int', 'int']); const print = new NativeFunction(DebugSymbol.getFunctionByName('Cat::printDescription()'), 'void', ['pointer']); const instance = Memory.alloc(16); // sizeof(Cat) ctor(instance, 3, 2); // 3 year old orange cat print(instance); // call instance method 完整示例⻅ cxx-clang-mac/
71. frida 调用 C++ 对象 • 实际遇到的 binary 可能没有调试符号 • 如果导出了 C++ 符号,可以使用 name mangling 过后的名字 findExportByName • 如果没有符号,逆向获取偏移量从运行时基地址计算 • 根据逆向结果判断 sizeof(Class),使用 Memory.alloc 或者 new 运算符分 配内存。注意 Memory.alloc 没有对应的手动释放函数,处理析构有麻烦 • 可以绕过成员函数对结构体中的数据直接进行 patch,实现修改私有成员 等高级功能
72. 篡改结构体 clang 从源码打印 class 的结构 ➜ cxx-clang-mac git:(master) ✗ clang -cc1 -fdump-record-layouts main.cpp *** Dumping AST Record Layout 0 | class Cat 0 | class Animal (primary base) 0 | (Animal vtable pointer) 8 | int m_age 12 | int m_weight | [sizeof=16, dsize=16, align=8, | nvsize=16, nvalign=8] 根据 this 和 offset 篡改类成员 const vtable = Memory.readPointer(instance); console.log('relative addr:', vtable.sub(base)); console.log(DebugSymbol.fromAddress(vtable)); console.log('age', Memory.readInt(instance.add(8))); console.log('weight', Memory.readInt(instance.add(12))); Memory.writeInt(instance.add(8), 1); // patched data print(instance);
73. 返回值 • C++ 成员函数如果返回的不是 primitive value,那么第一个参数应 为调用者开辟的,用来保存返回值对象的地址 string toString() { std::ostringstream out(""); out << "<Cat kind=" << kind() << " weight=" << m_weight << "kg" << ">"; return out.str(); }
74. C++ 符号还原 为区分同名的重载函数,将符号按照规则变换(name mangling) 源码 / 调试符号 android::AndroidRuntime::start(char const*, android::Vector<android::String8> const&, bool) 编译器 demangle 可执行文件 _ZN7android14AndroidRuntime5startEPKcRKNS_6VectorINS_7String8EEEb
75. C++ 符号还原 Compiler void h(int) void h(int, char) void h(void) Intel C++ 8.0 for Linux HP aC++ A.05.55 IA-64 _Z1hi _Z1hic _Z1hv _Z<number>hi _Z<number>hic _Z<number>hv h__Fi h__Fic h__Fv ?h@@YAXH@Z ?h@@YAXHD@Z ?h@@YAXXZ Borland C++ v3.1 @h$qi @h$qizc @h$qv OpenVMS C++ V6.5 (ARM mode) H__XI H__XIC H__XV CXX$__7H__FIC26CDH77 CXX$__7H__FV2CB06E8 IAR EWARM C++ 5.4 ARM GCC 3.x and higher Clang 1.x and higher IAR EWARM C++ 7.4 ARM 、 GCC 2.9x HP aC++ A.03.45 PA-RISC Microsoft Visual C++ v6-v10 (mangling details) Digital Mars C++ OpenVMS C++ V6.5 (ANSI mode) OpenVMS C++ X7.1 IA-64 CXX$_Z1HI2DSQ26A CXX$_Z1HIC2NP3LI4 CXX$_Z1HV0BCA19V SunPro CC __1cBh6Fi_v_ __1cBh6Fic_v_ __1cBh6F_v_ Tru64 C++ V6.5 (ARM mode) h__Xi h__Xic h__Xv Tru64 C++ V6.5 (ANSI mode) __7h__Fi __7h__Fic __7h__Fv Watcom C++ 10.6 W?h$n(i)v W?h$n(ia)v W?h$n()v https://en.wikipedia.org/wiki/Name_mangling#How_different_compilers_mangle_the_same_functions
76. C++ 符号还原 • 使用 C++ abi abi::__cxa_demangle • 实际编译后在不同平台上的实现: • • Windows + MSVC: dbghelp.dll!UnDecorateSymbolName(PCSTR name, PSTR outputString, DWORD maxStringLength, DWORD flags) • clang / gcc: __cxa_demangle(const char *mangled_name, char *output_buffer, size_t *length, int *status) 支持 mac/iOS, Android, Windows 和 Linux 的实例代码 cxx-demangle/agent.js
77. swift 符号还原 swift 存在类似 C++ 的 mangling 机制,但规则不同 Foundation._MutableHandle<__ObjC.NSMutableURLRequest> _TtGC10Foundation14_MutableHandleCSo19NSMutableURLRequest_ swift 支持 unicode 函数名(甚至 emoji 🐸) ➜ ~ xcrun swift-demangle __TF4testX4GrIhFTSiSi_Si _TF4testX4GrIhFTSiSi_Si ---> test.💛 (Swift.Int, Swift.Int) -> Swift.Int 版本较新的 macOS 和 iOS 自带了运行时 demangle 的库: /System/Library/PrivateFrameworks/Swift/libswiftDemangle.dylib 导出函数 swift_demangle_getDemangledName swift ABI 目前仍不稳定,以官方文档为准 https://github.com/apple/swift/blob/ master/docs/ABI/Mangling.rst 近期的一个 breaking change 就是将全局符号的前缀 _T 改为 _S
78. swift 符号还原 gossip-summer-school-2018 git:(master) ✗ frida -U Music -l swift-demangle/demangle.js ____ / _ | Frida 12.0.3 - A world-class dynamic instrumentation toolkit | (_| | > _ | Commands: /_/ |_| help -> Displays the help system . . . . object? -> Display information about 'object' . . . . exit/quit -> Exit . . . . . . . . More info at http://www.frida.re/docs/home/ Attaching... classes _TtCCVV5Music4Text7Drawing5CacheP33_BF7DEC98CE203AC9ACE0BF189B2A64B714ContextWrapper >> Music.Text.Drawing.Cache.(ContextWrapper in _BF7DEC98CE203AC9ACE0BF189B2A64B7) _TtGC10FoundationP33_2D7761BAEB66DCEF0A109CF42C1440A718_MutablePairHandleCSo10NSIndexSetCSo17NSMutableIndexSet_ >> Foundation.(_MutablePairHandle in _2D7761BAEB66DCEF0A109CF42C1440A7)<__ObjC.NSIndexSet, __ObjC.NSMutableIndexSet> _TtCV5Music21CloudLibraryUtilities22LibraryUpdatesNotifier >> Music.CloudLibraryUtilities.LibraryUpdatesNotifier _TtC5MusicP33_1491B7AA0651257DFE189F6D76BD86A331ScrollViewContentOffsetObserver >> Music. (ScrollViewContentOffsetObserver in _1491B7AA0651257DFE189F6D76BD86A3) _TtCV5Music7Artwork24LoadingStatusCoordinator >> Music.Artwork.LoadingStatusCoordinator _TtC5MusicP33_F29266F010DB9192D9DBD2C311376C0A20ClassicalWorkSection >> Music.(ClassicalWorkSection in _F29266F010DB9192D9DBD2C311376C0A) _TtCV5Music7Artwork9Component >> Music.Artwork.Component ➜ 示例代码:swift-demangle/
79. Stalker • Stalker 提供了指令级和代码块级的插桩 • 可用作覆盖率测试,追踪一段时间内所有可能的函数等 • frida-discover 即使用 Stalker 实现 • 示例代码:misc/stalker.js
80. Stalker 0x7fff2db0be60 JavaScriptCore!WTF::ThreadSpecific<WTF::RefPtr<WTF::(anonymous namespace)::ThreadData, WTF::DumbPtrTraits<WTF::(anonymous namespace)::ThreadData> >, (WTF::CanBeGCThread)1>::destroy(void*) 0x7fff2cefea60 JavaScriptCore!WTF::fastFree(void*) 0x7fff2d082f90 JavaScriptCore!WTF::ThreadCondition::~ThreadCondition() 0x7fff2db26ee0 JavaScriptCore!bmalloc::PerThread<bmalloc::PerHeapKind<bmalloc::Cache> >::destructor(void*) 0x7fff2cefefe0 JavaScriptCore!WTF::Mutex::lock() 0x7fff2db2d8e6 JavaScriptCore!DYLD-STUB$$std::__1::mutex::~mutex() 0x7fff2db25dc0 JavaScriptCore!bmalloc::Allocator::~Allocator() 0x7fff2db03a30 JavaScriptCore!WTF::ThreadSpecific<std::optional<WTF::GCThreadType>, (WTF::CanBeGCThread)1>::destroy(void*) 0x7fff2db24090 JavaScriptCore!bmalloc::mapToActiveHeapKind(bmalloc::HeapKind) 0x7fff2cf00a60 JavaScriptCore!WTF::monotonicallyIncreasingTime() 0x7fff2daf00e0 JavaScriptCore!WTF::AutomaticThread::threadIsStopping(WTF::AbstractLocker const&) 0x7fff2cefeff0 JavaScriptCore!WTF::Mutex::unlock() …
81. 反汇编和指令 patch • Instruction.parse() • {MIPS,Thumb,Arm,Arm64,X86}Relocator • {MIPS,Thumb,Arm,Arm64,X86}Writer
82. 使用 SQLite • 调用对应平台上的 SQLite 库函数 • 使用 frida 内置的 SQLiteDatabase 和 SQLiteStatement const db = SqliteDatabase.open('/path/to/people.db'); const smt = db.prepare('SELECT name, bio FROM people WHERE age = ?'); console.log('People whose age is 42:'); smt.bindInteger(1, 42); var row = null; while ((row = smt.step()) !== null) { console.log('Name:', row[0], 'Bio:', row[1]); } smt.reset();
83. libclang 生成代码 • 从头文件中查找常量、typedef 和手写 NativeFunction 体验非常糟 糕,可以利用 clang 节省一部分工作量 • 打印源码中的结构体和偏移: clang -cc1 -fdump-record-layouts main.cpp • 使用 libclang AST 生成代码 • 示例⻅:libclang/
84. libclang 生成代码 • • 遍历 AST 查找 CursorKind.CALL_EXPR • 遍历函数参数列表,映射 clang AST 中的类型到 frida 的类型 • 生成 new NativeFunction 语句 由于 #define 属于预处理 pass,AST 阶段处理只能保留一部分 token 信息,结果不够准确
85. libclang 生成代码 ➜ libclang git:(master) python3 codegen.py const __LP64__ = 1; const PROC_PIDLISTFDS = 1; const PROC_PIDLISTFDS = 1; const PROX_FDTYPE_VNODE = 1; const PROC_PIDFDVNODEPATHINFO = 2; const macho_section = section_64; const macho_section = section_64; const = new NativeFunction(Module.findExportByName(null, ''), 'int', ['pointer', 'pointer']); /* [info] function fprintf detected, try `console.log() or OutputStream` */ const proc_pidinfo = new NativeFunction(Module.findExportByName(null, 'proc_pidinfo'), 'int', ['int', 'int', 'uint64', 'pointer', 'int']); const exit = new NativeFunction(Module.findExportByName(null, 'exit'), 'void', ['int']); /* [info] function malloc detected, try `Memory.alloc()` */ const proc_pidfdinfo = new NativeFunction(Module.findExportByName(null, 'proc_pidfdinfo'), 'int', ['int', 'int', 'int', 'pointer', 'int']); /* [info] function _dyld_get_image_header detected, try `Process.enumerateModulesSync()[index].base` */
86. frida on Android
87. frida-java • frida-java 是 frida 内置库,即 Java 命名空间下的函数。可对 ART 和 Dalvik 运行时插桩 • 源代码 github/frida/frida-java • 在 frida 框架基础上完全由 javascript 实现。frida-gum 只实现了通 用的二进制插桩,而 frida-java 借助 jni 打通了 Java 和 Javascript 的世界(有兴趣研究可阅读 /lib/class-factory.js)
88. Java api • available:判断 Java 环境可用 • enumerateLoadedClasses(Sync):枚举所有已加载的 class • perform:执行任意 Java 操作都需要使用此函数 • use:根据完整类名查找类的句柄 • scheduleOnMainThread:在 JVM 主线程执行一段函数 • choose:内存中搜索类的实例 • cast:类型强转
89. Why Java.perform? • ART 和 Dalvik 都按照 JVM 的规范实现:https://docs.oracle.com/ javase/7/docs/technotes/guides/jni/spec/invocation.html • frida 的 js 脚本引擎使用了(非主线程)的其他线程,需要使用 javaVM->AttachCurrentThread。而对应为了释放资源,完成任务后 需 DetachCurrentThread • 为了保证关联和释放,所有涉及 JVM 的操作都需要放在 Java.perform 回调中执行
90. 操作对象 • frida 既可以 new 对象实例,也可以搜索已有的对象 • 在 class-factory.js 可以看到一些文档上未标注的,$ 开头的成员 • $new:new 运算符,初始化新对象。注意与 $init 区分 • $alloc:分配内存,但不初始化 • $init:构造器方法,用来 hook 而不是给 js 调用 • $dispose:析构函数 • $isSameObject:是否与另一个 Java 对象相同 • $className:类名
91. 操作对象 if (!Java.available) throw new Error('requires Android'); Java.perform(function() { const JavaString = Java.use('java.lang.String'); var exampleString1 = JavaString.$new('Hello World, this is an example string in Java.'); console.log('[+] exampleString1: ' + exampleString1); console.log('[+] exampleString1.length(): ' + exampleString1.length()); }); [+] exampleString1: Hello World, this is an example string in Java. [+] exampleString1.length(): 47
92. 操作对象 • • frida 访问 / 修改对象成员 • instance.field.value • instance.field.value = newValue • 这种方式不区分成员可⻅性,即使是私有成员同样可以直接访问 • 除 value 的 setter 和 getter 之外,fieldType 和 fieldReturnType 获取类型信息 frida 对数组做了封装,直接取下标即可访问
93. 操作对象 • 注意 instance 和 Class 的区别 • Java.choose 找到实例后查询字段的类型 Java.perform(function () { var MainActivity = Java.use('com.example.seccon2015.rock_paper_scissors.MainActivity'); Java.choose(MainActivity.$className, { onMatch: function(instance) { console.log(JSON.stringify(instance.P.fieldReturnType)); }, onComplete: function() {} }); }) {"className":"android.widget.Button","name":"Landroid/widget/Button;","type":"pointer","size":1} (注)APK 来源:https://github.com/ctfs/write-ups-2015/tree/master/ seccon-quals-ctf-2015/binary/reverse-engineering-android-apk-1
94. 插桩 • Java 层的插桩 • Java.use().method.implementation = hookCallback • 由于 Java 支持同名方法重载,需要用 .overload 确定具体的方法 Java.use('java.lang.String').$new.overload('[B', 'java.nio.charset.Charset') • JNI 层插桩 • JNI 实现在 so 中,且符号必然是导出函数,照常使用 Interceptor 即可
95. 获取调用堆栈 • Android 提供了工具函数可以打印 Exception 的堆栈。此方式等价 于 Log.getStackTraceString(new Exception) Java.perform(function () { const Log = Java.use('android.util.Log'); const Exception = Java.use('java.lang.Exception'); const MainActivity = Java.use('com.example.seccon2015.rock_paper_scissors.MainActivity'); MainActivity.onClick.implementation = function(v) { this.onClick(v); console.log(Log.getStackTraceString(Exception.$new())); }; });
96. frida on macOS/iOS
97. frida-objc • 对应 Java,ObjC api 是 frida 的另一个“一等公⺠” • 源代码 github/frida/frida-objc • 与 JVM 类似,Objective C 也提供了 runtime api:https:// developer.apple.com/documentation/objectivec/ objective_c_runtime?changes=_3 • frida 将 Objective C 的部分 runtime api 提供到 ObjC.api 中
98. frida-objc • • 与 Java 显著不同,frida-objc 将所有 class 信息保存到 ObjC.classes 中。直接对其 for in 遍历 key 即可 • Objective C 实现:[NSString stringWithString:@"Hello World”] • 对应 frida:var NSString = ObjC.classes.NSString; NSString.stringWithString_("Hello World”); new ObjC.Object 可以将指针转换为 Objective C 对象。如果指针 不是合法的对象或合法的地址,将抛出异常或导致未定义行为
99. hook Objective C • ObjC.classes.Class.method,以及 ObjC.Block 都提供了一 个 .implementation 的 setter 来 hook 方法实现。实际上就是 iOS 开发者熟悉的 Method Swizzling • 另一种方式是使用 Interceptor.attach(ObjC.classes.Class.method.implementation), 看上去很相似,但实现原理是对 selector 指向的代码进行 inline hook • Proxy 也是 Objective C 当中的一种 hook 方式。frida 提供了 ObjC.registerClass 来创建 Proxy
100. 跨平台示例
101. Unix 信号 • 使用 signal 捕获进程信号 • 要点:signal 接收一个函数指针,用 NativeCallback 实现 • 示例代码:misc/signals.js
102. iOS / macOS XPC 通信 • XPC 是 macOS / iOS 上常⻅的进程间通信机制,类似 C/S • 接收端:_xpc_connection_call_event_handler • 发送端:xpc_connection_send_message(_with_reply(_sync)) • xpc_object_t 事实上继承自 NSObject 对象,可用 frida 直接打印其 内容
103. iOS / macOS 定位伪造 • iOS 和 macOS 定位使用统一 API:CLLocationManager • 需指定一个 delegate 实现如下回调方法获取相应事件: • • -(void)locationManager:(CLLocationManager *)manager didUpdateLocations: (NSArray *)locations • - (void)locationManager:(CLLocationManager *)manager didUpdateToLocation: (CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation; 使用如下方法开始定位 • - (void)requestLocation; • - (void)startUpdatingLocation;
104. iOS / macOS 定位伪造 • 先处理 requestLocation 等方法拿到 delegate 的指针 • 在 delegate 上查找对应回调方法是否存在,逐个 hook • CLLocation 的经纬度是只读属性,需要创建新的副本。为了对抗时 间戳等特征检测,最好把正确的 CLLocation 除经纬度之外所有的 属性复制上去 • 示例代码:ios-macOS-fake-location/fake.js
105. Windows 服务端抓包 • 目标:Ws2_32.dll 导出的 WSARecv 和 WSAAccept • 要点:WSARecv 在执行完毕之后缓冲区才有有效数据,但缓冲区 个数和⻓度需要在函数进入之前的参数来确定 • 示例代码:windows-socket/server.js
106. 参考 • frida.re • glider菜⻦ - frida 源码阅读之 frida-java

首页 - Wiki
Copyright © 2011-2024 iteam. Current version is 2.124.0. UTC+08:00, 2024-04-26 12:50
浙ICP备14020137号-1 $访客地图$