首页
论坛
课程
招聘
[分享]udbg在目标进程内动态Hook、执行任意函数
2021-5-16 18:35 5233

[分享]udbg在目标进程内动态Hook、执行任意函数

2021-5-16 18:35
5233

测试环境

  1. 通过spy调试引擎附加到notepad进程

    PS D:\dist> notepad.exe
    PS D:\dist> .\udbg.exe -A spy -a notepad.exe

  2. 在udbg内执行.edit temp命令,会自动在script/autorun目录下打开(创建)temp.lua文件,并监控其写入,然后自动执行

  3. 执行下面的示例脚本:在temp.lua里编辑脚本并保存,udbg自动执行

udbg版本:https://gitee.com/udbg/udbg/releases/v0.1.0-nightly

工作原理

udbg的spy调试引擎,会向目标进程内注入一个模块,然后使用封装好的udbg.uspy模块可以在目标进程内执行lua脚本,然后通过uspy提供的lua接口完成Hook任意函数、调用任意函数的任务

 

在目标进程内执行lua代码的基本用法如下

1
2
3
4
5
local uspy = require "udbg.uspy"
local s = 'hello'
uspy(function()
    log(s, 'world')
end)
  • uspy会把传入的function(闭包)转换为字节码,然后通过RPC发送到目标进程进行执行
  • 假如闭包捕获了upvalue,也会将这些upvalue序列化并传过去,前提是被捕获的upvalue都可进行序列化

后面的示例脚本没有特殊说明,默认都是通过uspy(function() ... end)在目标进程内进行调用的

Hook任意函数

基本用法

1
2
3
4
5
6
7
8
9
10
11
12
local uspy = require "udbg.uspy"
local CreateFileW = PA 'kernelbase!CreateFileW'
 
uspy(function()
    local libffi = require 'libffi'
    local api = require 'win.api'
    -- Hook CreateFileW 函数,并打印第一个参数
    inline_hook(CreateFileW, function(args)
        local path = libffi.read_pack(args[1], 'w')
        log('CreateFileW', path)
    end)
end)

Hook并进行参数替换

1
2
3
4
5
6
7
8
9
10
inline_hook(CreateFileW, function(args)
    -- args[1] 在x64下相当于 args.rcx
    local path = libffi.read_pack(args[1], 'w')
    log('CreateFileW', path)
    if path:find 'a.txt$' then
        path = path:gsub('a.txt$', 'b.txt')
        log('[redirect]', 'a.txt', '->', path)
        args[1] = topointer(path:to_utf16())
    end
end)

Hook并调用原函数、阻止原函数继续执行

1
2
3
4
5
6
7
8
9
local MessageBoxA = api.GetProcAddress(api.LoadLibraryA('user32'), 'MessageBoxA')
inline_hook(MessageBoxA, function(args)
    -- 手动调用原函数
    libffi.fn(args.trampoline)(0, 'LALALA', 'AAAAAA', 0)
    -- 返回后不再调用原函数
    args 'reject'
end)
local msgbox = libffi.fn(MessageBoxA)
msgbox(0, 'ABC', 'DEF', 0)

libffi的使用

无类型调用

上述Hook示例中,对 MessageBoxA 函数的调用就是一个无类型调用的例子

1
2
local msgbox = libffi.fn(MessageBoxA)
msgbox(0, 'ABC', 'DEF', 0)

libffi.fn函数直接传入一个整数(C函数地址),返回一个无类型的函数对象,无类型的函数对象会根据传入的lua参数类型自动为每个C参数分配类型,映射规则如下

  • string|userdata => pointer
  • integer|boolean => size_t
  • nil|none => NULL
  • number => double

无类型的调用在于写起来非常简单,能够覆盖大部分应用场景,但有些时候还是需要知道明确的函数参数才能成功调用一些函数,比如涉及到浮点数的函数、非标准的调用约定等情况

声明函数类型

声明函数类型需要指定函数的返回值类型和参数类型 libffi.fn(returnType, {argsType...}),支持的类型如下

  • void
  • char int8
  • byte uchar
  • short int16
  • ushort uint16
  • int int32
  • uint uint32
  • int64 long long
  • uint64
  • float
  • double
  • pointer
1
2
3
4
5
6
7
8
local pow = api.GetProcAddress(api.LoadLibraryA'msvcrt', 'pow')
-- 通过fn声明函数类型
pow = libffi.fn('double', {'double', 'double'})(pow)
log('powf(2, 2)', pow(2, 2))
 
local powf = api.GetProcAddress(api.LoadLibraryA'msvcrt', 'powf')
powf = libffi.fn('float', {'float', 'float'})(powf)
log('powf(2, 3)', powf(2, 3))

指定x86调用约定: TODO

生成回调函数

示例如下

1
2
3
4
local WNDENUMPROC = libffi.fn('int', {'pointer', 'pointer'})
api.EnumChildWindows(0, WNDENUMPROC(function(hwnd, param)
    log('HWND:', hwnd)
end), 0)

主线程执行

有些函数只能在某些特定线程中调用,一般是UI主线程,可以Hook GetMessageW PeekMessageW之类的函数,Hook触发时,脚本则是在UI线程中调用的,然后通过libffi去调用想测试的函数

1
2
3
4
5
-- inline_once是对inline_hook函数的封装,Hook触发一次后立即消除Hook
inline_once(api.GetProcAddress(api.LoadLibraryA'user32', 'GetMessageW'), function()
    local msgbox = libffi.fn(MessageBoxA)
    msgbox(0, 'ABC', 'DEF', 0)
end)

更多示例

udbg本身功能也使用了很多libffi调用,比如 script/win/api.lua script/win/win.lua


[注意] 欢迎加入看雪团队!base上海,招聘CTF安全工程师,将兴趣和工作融合在一起!看雪20年安全圈的口碑,助你快速成长!

收藏
点赞1
打赏
分享
最新回复 (3)
雪    币: 69
活跃值: 活跃值 (366)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
saloyun 活跃值 2021-5-18 14:42
2
0
挺好的,但是这个跟frida相比,有什么更爽的地方吗?
雪    币: 426
活跃值: 活跃值 (972)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
metaworm 活跃值 2021-5-18 18:58
3
0
saloyun 挺好的,但是这个跟frida相比,有什么更爽的地方吗?
功能上没有比frida更多,主要是有GUI,语言用的lua,还有可以多线程执行lua(这个不知道frida有没有),看个人口味吧