文章目录

  • 需求
  • 实操
  • 其他


需求

工作对 Hook 的需求很频繁.希望能将Hook的数据方便打印转储,有同学会说:使用Hook库呀…
可是Hook库需要各种编译环境 真令人头大,为什么不使用一门简单 易用 随身携带的 弱类型的 脚本语言 来负责处理数据呢?

很早之前就知道 Frida 了,但是似乎对方的更多例子是玩转 Android,而且其混用了两种 语言: Python 和 JS.

其中Python 我又并不擅长,直到最近有所改观.

后来自己试图根据工作需求,造一个Hook库+node.js 的轮子… 无奈工作时间太可爱,自己时间太珍惜… …

所以本次端午假期 尝试搞定 Frida 的例子. 希望在工作能用到.

实操

实操官方例子来源:

https://frida.re/docs/examples/windows/

JS的Api都在这里:

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

其实我们的工作主要是修改 js 用js的逻辑来完成数据接管后的工作.

先写一个例子,循环 printf 做为要Hook记录的程序,如下:

#include <iostream>
#include <Windows.h>
#include <stdio.h>

int main()
{
  while (true) {
    Sleep(1000);
    std::cout << "Start" << std::endl;
    //ReadFile(0, 0, 0, 0, 0);
    printf("Hello%d%d %d",1,2,3);
    std::cout << "结束" << std::endl;
  }
}

非常简单,不再多言.

接下来,我们的 frida的部分:

  1. 先安装python
  2. 再安装frida的库:

    准备一个编辑器:

frida 中的 python 代码为:

from __future__ import print_function
import frida
import sys

def on_message(message, data):
    print("[%s] => %s" % (message, data))

def main(target_process):
    session = frida.attach(target_process)
    handle = open("printf.js", "r",encoding='utf-8')

    jsScript = handle.read()
    handle.close()
    script = session.create_script(jsScript)
    script.on('message', on_message)
    script.load()
    print("[!] Ctrl+D on UNIX, Ctrl+Z on Windows/cmd.exe to detach from instrumented program.\n\n")
    sys.stdin.read()
    session.detach()

if __name__ == '__main__':
    if len(sys.argv) != 2:
        print("Usage: %s <process name or PID>" % __file__)
        sys.exit(1)

    try:
        target_process = int(sys.argv[1])
    except ValueError:
        target_process = sys.argv[1]
    main(target_process)

我把 负责处理数据的 js 部分 文件单独存放 printf.js 了,而不是像原作者那样 python 和 js 混放.这样能够实现 py 和 js 的单独的代码高亮.

我们看一下官方的效果,非常打击学习者 😦

ios Frida hook 修改参数 frida hook exe_python

独立成文件就好多:

printf.js ::

// Find base address of current imported jvm.dll by main process fledge.exe
    var moduleName = 'testfrida.exe'
    var baseAddr = Module.findBaseAddress(moduleName);
    console.log('${moduleName} baseAddr: ' + baseAddr);

    var monitorFuncAddr = resolveAddress('0x125D0'); // Here we use the function address as seen in our disassembler

    Interceptor.attach(monitorFuncAddr, { // Intercept calls to our SetAesDecrypt function

        // When function is called, print out its parameters
        /*
        以下内容演示了
        1. 怎么提取 printf 的第一个参数的字符串
        2. 怎么结合 onLever 做进入函数的时候获取 该函数要操作的内存和长度 ,等函数工作完毕,提取该数据
        其他API 用法
        https://frida.re/docs/javascript-api/
        */
        onEnter: function (args) {
            console.log('');
            console.log('[+] Called monitorFuncAddr' + monitorFuncAddr);
            console.log('[+] Ctx: ' + args[-1]);
            console.log('[+] FormatString: ' + Memory.readAnsiString(args[0])); // Plaintext
            console.log('[+] Argv1: ' + args[1]); // This pointer will store the de/encrypted data
            console.log('[+] Argv2: ' + args[2]); // Length of data to en/decrypt
            console.log('[+] Argv3: ' + args[3]); // Length of data to en/decrypt
            /*
            dumpAddr('Input', args[0], args[3].toInt32());
            this.outptr = args[1]; // Store arg2 and arg3 in order to see when we leave the function
            this.outsize = args[2].toInt32();
            */
        },

        // When function is finished
        onLeave: function (retval) {
            /*
            dumpAddr('Output', this.outptr, this.outsize); // Print out data array, which will contain de/encrypted data as output
            console.log('[+] Returned from SomeFunc: ' + retval);
            */
        }
    });

    function dumpAddr(info, addr, size) {
        if (addr.isNull())
            return;

        console.log('Data dump ' + info + ' :');
        var buf = addr.readByteArray(size);

        // If you want color magic, set ansi to true
        console.log(hexdump(buf, { offset: -1, length: size, header: true, ansi: false }));
    }

    function resolveAddress(addr) {
        var idaBase = ptr('0x0'); // Enter the base address of jvm.dll as seen in your favorite disassembler (here IDA)
        var offset = ptr(addr).sub(idaBase); // Calculate offset in memory from base address in IDA database
        var result = baseAddr.add(offset); // Add current memory base address to offset of function to monitor
        console.log('[+] New addr=' + result); // Write location of function in memory to console
        return result;
    }

可以 看到 我使用了 硬编码 printf 的RVA :
0x125D0
你完全可以 使用 从导出函数的名字 获取地址的方式.
但实际工作中这种 RVA hook的方式拥有最广泛的用途.

我们看一眼效果:

先手工启动一个 TestFrida.exe 的程序:

使其一直打印

ios Frida hook 修改参数 frida hook exe_python_02

再启动我们编写的frida 脚本:
python main.py TestFrida.exe

窗口显示:

ios Frida hook 修改参数 frida hook exe_#include_03


如上图 数据出来了.

其他

  1. 据称 Frida 的注入的核心是 将一个包含有 v8内核的 dll 注入到 目标进程.
  2. 按键盘Ctrl+Z: 可以使Frida的控制台退出(就是上面截图的黑色的内容)
    Frida 退出的时候 会呼唤自己注入对方进程中的 v8 引擎也卸载退出.