一开始的 Nook,本质上是一个 Android Hook 框架。它已经具备了几类底层能力:注入、Java Hook、Native Hook。但这距离Frida-like的Hook风格还有很大的差距,一个 Hook 框架,不等于一个可用的动态分析工具。框架解决的是“你注入进去之后能做什么”,工具解决的是“你如何把能力稳定、可重复、低摩擦地用起来”。项目仓库在:Nook仓库地址大佬们可以尝试着用一下看,release里下一个server,然后脚本语法基本和Frida一致,希望大家点点star哈哈,有问题可以提提issue或者直接在评论里提,后面有时间会持续维护和更新。接下来要做的是把它推进成一个更接近 Frida 使用体验的东西:因为这里的代码实现比较冗长,所以这篇文章和前面几篇的风格会有所不同:通过案例来介绍Nook以及简单介绍Frida背后是怎么做的。这里案例使用的是536K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6p5c8g2u0q4i4K6u0V1j5h3b7J5x3o6l9I4i4K6u0r3c8Y4u0A6k6r3q4Q4x3X3c8x3j5h3u0K6在此之前,作为一个Hook框架,Nook实现一次hook的基本语义是这样的:并且我们需要手动的编译成so,通过Ninjector这样的注入工具注入到目标app中。而我们的目标是像Frida那样,在设备上启动一个server,我们在host端只需要写好js脚本,通过一行命令就能完成hook,在已经有一个Hook框架的基础上,我们还需要什么呢?首先我们把Nook定为三层结构:另外我们还需要通信和协议层来进行远程的操控:最后还需要一个脚本运行时,将工作模型从写c+编译+注入变为写js+动态加载,通过一个JS Bridge,把底层的Hook能力变成我们熟悉的脚本API先从一个脚本开始:一次Hook的本质是:用户做的事情通常是:这里 Host 侧承担的职责是:nook-server 接到 Host 请求后,不是自己去执行 Hook。它真正负责的是:agent 进入目标进程后,会初始化自己的运行时环境:接着 server 把 SCRIPT_LOAD 这类请求转发给 agent,agent 再把脚本内容交给 JsRuntime::Evaluate(...) 一类入口去执行。脚本一加载,首先跑到的是:这一步表面语义很简单:在 Java 可用的时候执行回调”,对 Nook 来说,它实际做了两件事:接下来脚本会执行:这个 wrapper 里会延迟解析:此时把脚本层意图翻译成底层 Hook 引擎能够理解的安装请求。当 implementation = fn 落到 native bridge 后, Nook 会继续把请求传给 Java hook 子系统。安装成功后,当 app 运行到MainActivity.get_random()时,底层 Java hook 会先截获这次调用,然后再把控制流送回 Nook runtime 持有的 JS callback,也就是:于是完整链路变成:第一个例子是最典型的 Frida Java Hook 入门题。目标app关键方法如下: 在 frida-0x1 里,目标是 Hook MainActivity.get_random(),把返回值强制改成 5,然后再观察后续 check(int, int) 的参数。这个例子很适合作为起点,因为它对应的是 Frida 最基础、也最高频的能力:通过脚本替换 Java 方法实现。我们很容易就可以写出对应的hook代码:Hook效果如下,输入14后就可以在app页面看到flag:“FRIDA{BABY_HOOK_0x1}”:我们第一步就从这个脚本出发:首先是第一句代码Java.perform(function()),这句代码意味着什么?可以简单将其理解为:“等 Java 运行环境准备好之后,再执行这段回调。”它的目的不是单纯“执行一个函数”,而是保证下面这些 Java 相关操作发生在一个安全时机:已经拿到JNIEnv*,Java VM已经可用,目标app的ClassLoader/生命周期已经可以做到Java.use(...) 的阶段。不然你太早去 Java.use("com.ad2001.frida0x1.MainActivity"),很容易因为类还没准备好、ClassLoader 还没就绪而失败。在Nook中,我是这么去处理的:意思很容易理解:在agent_rumtime里面,Nook 维护了一个readyCallbacks 队列。它会检查两类条件:Java._isClassLoaderReady()和Java._isLifecycleReady()。如果还没 ready,就把当前脚本的回调缓存起来;等到 Java.__nookDispatchReady() 被触发时,再把这些回调取出来执行。简单总结Java.perform(fn) 本质上做的是:真正执行回调的是 Java.vm.perform(...):也就是:带着一个有效的 Java 环境去执行你的回调。Frida是怎么做的呢?其实做的事情是类似的,判断当前是不是app process,classFactory.loader是否已经准备好,ready了就this.vm.perform(fn),还没ready就把回调塞进 _pendingVmOps,然后启动 _performPendingVmOpsWhenReady()。Frida 的 Java.perform 负责等待 Java / loader 可用,并把回调排队到 VM-ready 路径,也就是说,Frida 的 Java.perform 自己就带了一套 “等 app class loader ready 并把它接起来” 的逻辑。Nook 这边则是以及Frida 的vm.perform() 自己 attach/detach 线程,而Nook的Java.vm.perform() 先解析/确保可用 env,再在当前 JS 执行上下文里带着它跑。简单总结就是Frida的Java.perform自己处理 pending + bootstrap,Nook 的 Java.perform的处理更简单,一个“基于 ready 条件和脚本桥接机制的等待执行器”,更复杂的时机控制被放到了后面的 spawn gate /script runtime bridge 里。然后是Java.use(...) 在 Nook 里做了什么?其实只是:Java.use("com.ad2001.frida0x1.MainActivity") 本质不是“立刻找类并返回 JNI 对象”,也不会“立刻把这个类执行起来”,而是“构造一个能代表这个 Java 类的 JS wrapper”。后面的比如都是在这个wrapper之上继续展开的。这个 wrapper 的生成逻辑在 CreateJavaUseWrapper(),CreateJavaUseWrapper() 里面内嵌了一大段 JS factory 代码,用来
...(已截断)
---
来源: 看雪论坛
原文链接: https://bbs.kanxue.com/thread-291347.htm
[原创]从0到1构建一个Hook工具之Frida-like风格的Hook
412 浏览
10 回复
太强了
感谢分享
tql
强
感谢分享
先收藏,慢慢学习。
大佬厉害呀
666太强了
没办法指定端口么
mb_msgksrpq
没办法指定端口么
目前还没写,过两天会和gadget一起加一下
没办法指定端口么
目前还没写,过两天会和gadget一起加一下