使用Frida实现内存注入主动调用函数

上篇文章中,我演示如何使用OllyDbg逆向一个简单的C语言程序,找到HOOK的内存地址和参数汇编指令。最后使用Frida框架编写了一个HOO脚本实现内存注入(远程线程插入)。当C语言函数被调用时,HOOK脚本会监听对应的函数,读取ESP寄存器中的函数参数,并打印在屏幕上。

本文将更深一步的研究如何使用Frida通过内存注入的方式,主动调用应用程序在内存中的函数。大致原理就是在内存中申请一个新的区域,在这个新内存片段中写入自己定义的汇编指令。最后注入到已经运行的应用中,从而实现在内存中调用应用内部方法。本文根据《使用OllyDbg逆向查找HOOK地址和寄存器使用》中逆向获取的内存地址和寄存器操作,使用Frida模拟调用应用中的方法。因此在继续本文内容之前,建议先阅读一下这篇文章,否则阅读源代码过程中可能会有困难。

使用Frida内存注入调用应用方法

相比于使用Frida内存注入HOOK应用方法访问内存数据,主动调用应用方法要复杂很多。特别是应用方法如果有参数数据,就需要在内存中初始化好所有的参数以后,再模拟调用远程线程函数。因此在内存中初始化会用到汇编语言。使用Frida可以避免直接编写汇编语言,但是编写难度也不会太小。

Python部分源代码

下面先看一下Python代码部分:

# -*- coding: UTF-8 -*-
# py3
from __future__ import print_function
import frida
import codecs
import sys


def main(target_process):
    session = frida.attach(target_process)
    with codecs.open('js/call.js', 'r', 'utf-8') as f:
        source = f.read()
    script = session.create_script(source)
    script.load()
    session.detach()

if __name__ == '__main__':
    main('hello.exe')

JS部分源代码

在python部分的代码,调用Frida并加载call.js文件。这个JS文件中的内容才是真正申请内存地址,初始化函数参数,并最终调用应用方法的内容。

var ModAddress=Process.findModuleByName('hello.exe');
console.log('Mod Address:' + ModAddress.base);

var callAddress=ModAddress.base.add('0x15D5')
console.log('Hook Address: ' + callAddress);


const fastCallback = Memory.alloc(Process.pageSize);
Memory.patchCode(fastCallback, 128, code => {
    const cw = new X86Writer(code, { pc: fastCallback });
    cw.putMovRegU32("eax", 5)
    cw.putMovRegOffsetPtrReg("esp", 4, "eax")

    cw.putMovRegU32("eax", 15)
    cw.putMovRegPtrReg("esp", "eax")

    //call hello.004015D5
    cw.putCallAddress(callAddress);
    cw.flush();
})
const callMemFun = new NativeFunction(fastCallback, 'void', [])
var result = callMemFun()

以上代码的主要工作是在内存中申请一片内存空间,并在空间内定义一个汇编指令块。汇编指令主要是初始化参数并调用原生应用的方法。汇编指令的编写依照OD中逆向的结果即可。
寄存器的自加操作和入参赋值

样例效果

准备好内存注入脚本以后,我们同样打开两个终端,一个窗口运行C程序,一个窗口运行内存注入脚本。然后我们可以在窗口中看出,当我们在原程序中调用函数h()时,程序的两个输入参数是一样的,并且经过每一次调用,输入的参数会累加。但是在第二个窗口中运行内存注入脚本,初始化的两个函数参数分别是15和5。当脚本成功执行后,原程序的函数h()会被调用,但是输出的数据显然和程序本身输出的数据不同。
frida远程调用的输出
如图所示,图中左边窗口红框内部的输出是右边窗口通过内存注入的方式发起调用的结果。

总结

使用Frida通过内存注入调用原生应用方法需要用到汇编语言,因此难度比实现一个HOOK脚本要难很多。本文中的用例还是比较简单的两个整型参数,如果是一个复杂的结构体,还需要为结构体初始化空间和赋值。但是无论数据结构多复杂,实现的方式都是一样的。

Captain QR Code

扫码联系船长

发表回复

您的电子邮箱地址不会被公开。