4.5 x64dbg 探索钩子劫持技术

x64dbg,探索,钩子,劫持,技术 · 浏览次数 : 195

小编点评

标题:自定义窗口与MessageBoxA交互 内容: 1. 定义两个变量,存放字符串: * msgBoxAddr:存储MessageBoxA内存地址 * msgTextAddr:存储MessageBoxA内容地址 2. 分配空间: * HookMem:存储自定义窗口内存地址 3. 写出FindWindowA内存地址和跳转地址: * patchCode:存储FindWindowA代码片段 4. 写出自定义窗口内存地址: * assemble(dbg, HookMem, patchCode) 5. 定义两个变量,存放字符串: * msgBoxAddr:存储MessageBoxA内存地址 * msgTextAddr:存储MessageBoxA内容地址 6. 填充字符串内容: * for txt_count in range(0, len(txt)): dbg.write_memory_byte(msgBoxAddr + txt_count, txt[txt_count]) 7. 写出FindWindowA内存地址和跳转地址: * patchCode:存储FindWindowA代码片段 8. 写出自定义窗口内存地址: * assemble(dbg, HookMem, patchCode) 9. 打印标题地址和自定义窗口内存地址 10. 此处是MessageBox替换后的片段: * patchCode:存储FindWindowA代码片段 11. 写出自定义窗口内存地址: * assemble(dbg, HookMem, patchCode) 12. 打印地址已被替换,可以运行了。 13. 返回程序领空。

正文

钩子劫持技术是计算机编程中的一种技术,它们可以让开发者拦截系统函数或应用程序函数的调用,并在函数调用前或调用后执行自定义代码,钩子劫持技术通常用于病毒和恶意软件,也可以让开发者扩展或修改系统函数的功能,从而提高软件的性能和增加新功能。

4.5.1 探索反汇编写出函数原理

钩子劫持技术的实现一般需要在对端内存中通过create_alloc()函数准备一块空间,并通过assemble_write_memory()函数,将一段汇编代码转为机器码,并循环写出自定义指令集到堆中,函数write_opcode_from_assemble()就是我们自己实现的,该函数传入一个汇编指令列表,自动转为机器码并写出到堆内,函数的核心代码如下所示。

def write_opcode_from_assemble(dbg_ptr,asm_list):
    addr_count = 0
    addr = dbg_ptr.create_alloc(1024)
    if addr != 0:
        for index in asm_list:
            asm_size = dbg_ptr.assemble_code_size(index)
            if asm_size != 0:
                # print("长度: {}".format(asm_size))
                write = dbg_ptr.assemble_write_memory(addr + addr_count, index)
                if write == True:
                    addr_count = addr_count + asm_size
                else:
                    dbg_ptr.delete_alloc(addr)
                    return 0
            else:
                dbg_ptr.delete_alloc(addr)
                return 0
    else:
        return 0
    return addr

我们以写出一段MessageBox弹窗代码为例,首先通过get_module_from_function函数获取到位于user32.dll模块内MessageBoxA的函数地址,该函数的栈传参数为五个,其中前四个为push压栈,最后一个则是调用call,为了构建这个指令集需要在asm_list写出所需参数列表及调用函数地址,并通过set_local_protect设置可执行属性,通过set_register将当前EIP设置到写出位置,并执行程序。

from LyScript32 import MyDebug

def write_opcode_from_assemble(dbg_ptr,asm_list):
              pass

if __name__ == "__main__":
    dbg = MyDebug()
    dbg.connect()

    # 得到messagebox内存地址
    msg_ptr = dbg.get_module_from_function("user32.dll","MessageBoxA")
    call = "call {}".format(str(hex(msg_ptr)))
    print("函数地址: {}".format(call))

    # 写出指令集到内存
    asm_list = ['push 0','push 0','push 0','push 0',call]
    write_addr = write_opcode_from_assemble(dbg,asm_list)
    print("写出地址: {}".format(hex(write_addr)))

    # 设置执行属性
    dbg.set_local_protect(write_addr,32,1024)

    # 将EIP设置到指令集位置
    dbg.set_register("eip",write_addr)

    # 执行代码
    dbg.set_debug("Run")
    dbg.close()

运行上述代码片段,则首先会在0x3130000的位置处写出调用MessageBox的指令集。

当执行set_debug("Run")则会执行如下图所示代码,这些代码则是经过填充的,由于此处仅仅只是一个演示案例,所以不具备任何实战性,读者在该案例中学会指令的替换是如何实现的即可;

4.5.2 实现Hook改写MsgBox弹窗

在之前的内容中笔者通过封装write_opcode_from_assemble函数实现了自定义写出内存的功能,本章将继续探索Hook劫持技术的实现原理,如下案例中我们先来实现一个Hook通用模板,在代码中实现中转机制,代码中以MessageBoxA函数为案例实现修改汇编参数传递。

from LyScript32 import MyDebug

# 传入汇编列表,写出到内存
def assemble(dbg, address=0, asm_list=[]):
    asm_len_count = 0
    for index in range(0,len(asm_list)):
        # 写出到内存
        dbg.assemble_at(address, asm_list[index])
        # print("地址: {} --> 长度计数器: {} --> 写出: {}".format(hex(address + asm_len_count), asm_len_count,asm_list[index]))
        # 得到asm长度
        asm_len_count = dbg.assemble_code_size(asm_list[index])
        # 地址每次递增
        address = address + asm_len_count

if __name__ == "__main__":
    dbg = MyDebug()
    connect_flag = dbg.connect()
    print("连接状态: {}".format(connect_flag))

    # 找到MessageBoxA
    messagebox_address = dbg.get_module_from_function("user32.dll","MessageBoxA")
    print("MessageBoxA内存地址 = {}".format(hex(messagebox_address)))

    # 分配空间
    HookMem = dbg.create_alloc(1024)
    print("自定义内存空间: {}".format(hex(HookMem)))

    # 写出MessageBoxA内存地址,跳转地址
    asm = [
        f"push {hex(HookMem)}",
        "ret"
    ]

    # 将列表中的汇编指令写出到内存
    assemble(dbg,messagebox_address,asm)

    dbg.close()

如上代码中,通过找到user32.dll库中的MessageBoxA函数,并返回其内存地址。接着,程序会分配1024字节大小的自定义内存空间,获取刚刚写入的内存地址,并将其写入到MessageBoxA函数的内存地址中,代码运行后读者可看到如下图所示的提示信息;

提示:解释一下为什么需要增加asm列表中的指令集,此处的指令集作用只有一个那就是跳转,当原始MessageBoxA函数被调用时,则此处通过push;ret的组合跳转到我们自定义的HookMem内存空间中,而此内存空间中后期则需要填充我们自己的弹窗代码片段,所以需要提前通过HookMem = dbg.create_alloc(1024)构建出这段内存区域;

由于MessageBox弹窗需要使用两个变量这两个变量依次代表标题和内容,所以我们通过create_alloc函数在对端内存中分配两块堆空间,并依次将弹窗字符串通过write_memory_byte写出到内存中,至此弹窗内容也算填充好了,其中txt代表标题,而box则代表内容;

    # 定义两个变量,存放字符串
    MsgBoxAddr = dbg.create_alloc(512)
    MsgTextAddr = dbg.create_alloc(512)

    # 填充字符串内容
    # lyshark 标题
    txt = [0x6c, 0x79, 0x73, 0x68, 0x61, 0x72, 0x6b]
    # 内容 lyshark.com
    box = [0x6C, 0x79, 0x73, 0x68, 0x61, 0x72, 0x6B, 0x2E, 0x63, 0x6F, 0x6D]

    for txt_count in range(0,len(txt)):
        dbg.write_memory_byte(MsgBoxAddr + txt_count, txt[txt_count])

    for box_count in range(0,len(box)):
        dbg.write_memory_byte(MsgTextAddr + box_count, box[box_count])

    print("标题地址: {} 内容: {}".format(hex(MsgBoxAddr),hex(MsgTextAddr)))

紧接着,我们需要跳转到MessageBoxA函数所在内存中,并提取出该函数调用时的核心汇编指令集,如下图所示则是弹窗的具体实现流程;

而对于一个完整的弹窗来说,只需要提取出核心代码即可不必提取所有指令集,但需要注意的是图中的call 0x75B20E20地址需要进行替换,根据系统的不同此处的地址也不会相同,在提取时需要格外注意;

    # 此处是MessageBox替换后的片段
    PatchCode =\
    [
        "mov edi, edi",
        "push ebp",
        "mov ebp,esp",
        "push -1",
        "push 0",
        "push dword ptr ss:[ebp+0x14]",
        f"push {hex(MsgBoxAddr)}",
        f"push {hex(MsgTextAddr)}",
        "push dword ptr ss:[ebp+0x8]",
        "call 0x75B20E20",
        "pop ebp",
        "ret 0x10"
    ]

    # 写出到自定义内存
    assemble(dbg, HookMem, PatchCode)

如上则是替换弹窗的代码解释,将这段代码整合在一起,读者则可实现一段替换弹窗功能的代码,如下弹窗中的消息替换成我们自己的版权信息,此处完整代码实现如下所示;

from LyScript32 import MyDebug

# 传入汇编列表,写出到内存
def assemble(dbg, address=0, asm_list=[]):
    asm_len_count = 0
    for index in range(0,len(asm_list)):
        # 写出到内存
        dbg.assemble_at(address, asm_list[index])
        # print("地址: {} --> 长度计数器: {} --> 写出: {}".format(hex(address + asm_len_count), asm_len_count,asm_list[index]))
        # 得到asm长度
        asm_len_count = dbg.assemble_code_size(asm_list[index])
        # 地址每次递增
        address = address + asm_len_count

if __name__ == "__main__":
    dbg = MyDebug()
    connect_flag = dbg.connect()
    print("连接状态: {}".format(connect_flag))

    # 找到MessageBoxA
    messagebox_address = dbg.get_module_from_function("user32.dll","MessageBoxA")
    print("MessageBoxA内存地址 = {}".format(hex(messagebox_address)))

    # 分配空间
    HookMem = dbg.create_alloc(1024)
    print("自定义内存空间: {}".format(hex(HookMem)))

    # 写出FindWindowA内存地址,跳转地址
    asm = [
        f"push {hex(HookMem)}",
        "ret"
    ]

    # 将列表中的汇编指令写出到内存
    assemble(dbg,messagebox_address,asm)

    # 定义两个变量,存放字符串
    MsgBoxAddr = dbg.create_alloc(512)
    MsgTextAddr = dbg.create_alloc(512)

    # 填充字符串内容
    # lyshark 标题
    txt = [0x6c, 0x79, 0x73, 0x68, 0x61, 0x72, 0x6b]
    # 内容 lyshark.com
    box = [0x6C, 0x79, 0x73, 0x68, 0x61, 0x72, 0x6B, 0x2E, 0x63, 0x6F, 0x6D]

    for txt_count in range(0,len(txt)):
        dbg.write_memory_byte(MsgBoxAddr + txt_count, txt[txt_count])

    for box_count in range(0,len(box)):
        dbg.write_memory_byte(MsgTextAddr + box_count, box[box_count])

    print("标题地址: {} 内容: {}".format(hex(MsgBoxAddr),hex(MsgTextAddr)))

    # 此处是MessageBox替换后的片段
    PatchCode =\
    [
        "mov edi, edi",
        "push ebp",
        "mov ebp,esp",
        "push -1",
        "push 0",
        "push dword ptr ss:[ebp+0x14]",
        f"push {hex(MsgBoxAddr)}",
        f"push {hex(MsgTextAddr)}",
        "push dword ptr ss:[ebp+0x8]",
        "call 0x75B20E20",
        "pop ebp",
        "ret 0x10"
    ]

    # 写出到自定义内存
    assemble(dbg, HookMem, PatchCode)

    print("地址已被替换,可以运行了.")
    dbg.set_debug("Run")
    dbg.set_debug("Run")

    dbg.close()

当如上代码被运行后,则会替换进程内MessageBoxA函数为我们自己的地址,运行输出效果如下图所示;

读者可通过Ctrl+G并输入MessageBoxA跳转到原函数弹窗位置,此时输出的则是一个跳转地址0x6C0000该地址则代表我们自己的自定义内存区域,如下图所示;

继续跟进这内存区域,读者可看到我们自己构建的MessageBoxA弹窗的核心代码片段,当这段代码被执行结束后则通过ret会返回到程序领空,如下图所示;

至此,当用户再次打开弹窗按钮时,则不会提示原始内容,而是提示自定义弹窗,如下图所示;

本文作者: 王瑞
本文链接: https://www.lyshark.com/post/6b7ca168.html
版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

与4.5 x64dbg 探索钩子劫持技术相似的内容:

4.5 x64dbg 探索钩子劫持技术

钩子劫持技术是计算机编程中的一种技术,它们可以让开发者拦截系统函数或应用程序函数的调用,并在函数调用前或调用后执行自定义代码,钩子劫持技术通常用于病毒和恶意软件,也可以让开发者扩展或修改系统函数的功能,从而提高软件的性能和增加新功能。钩子劫持技术的实现一般需要在对端内存中通过`create_alloc()`函数准备一块空间,并通过`assemble_write_memory()`函数,将一段汇编代

4.5 C++ Boost 文件目录操作库

Boost 库是一个由C/C++语言的开发者创建并更新维护的开源类库,其提供了许多功能强大的程序库和工具,用于开发高质量、可移植、高效的C应用程序。Boost库可以作为标准C库的后备,通常被称为准标准库,是C标准化进程的重要开发引擎之一。使用Boost库可以加速C应用程序的开发过程,提高代码质量和性能,并且可以适用于多种不同的系统平台和编译器。Boost库已被广泛应用于许多不同领域的C++应用程序

4.5 MinHook 挂钩技术

MinHook是一个轻量级的Hooking库,可以在运行时劫持函数调用。它支持钩子API函数和普通函数,并且可以运行在32位和64位Windows操作系统上。其特点包括易于使用、高性能和低内存占用。MinHook使用纯汇编语言实现,在安装和卸载钩子时只需要短暂地锁定目标线程,因此对目标线程的影响非常小。

【RocketMQ】DLedger模式下的选主流程分析

RocketMQ 4.5版本之前,可以采用主从架构进行集群部署,但是如果master节点挂掉,不能自动在集群中选举出新的Master节点,需要人工介入,在4.5版本之后提供了DLedger模式,使用Raft算法,如果Master节点出现故障,可以自动选举出新的Master进行切换。 Raft协议 R

【RocketMQ】【源码】DLedger选主源码分析

RocketMQ 4.5版本之前,可以采用主从架构进行集群部署,但是如果master节点挂掉,不能自动在集群中选举出新的Master节点,需要人工介入,在4.5版本之后提供了DLedger模式,使用Raft算法,如果Master节点出现故障,可以自动选举出新的Master进行切换。 **Raft协议

[转帖]BASH编写入门与实例

1 2 3 4 5 6 7 8 9 10 怎么写shell脚本: 。使用任何编辑工具编写shell脚本 例如vi -#!/bin/bash #在第一行放置头格式说明 -#!/usr/bin/gawk //awk需要添加的头格式,让系统知道用什么方式去解析此文件 -#!/usr/local/bin/p

瀚高4.5.8的安装部署过程

瀚高4.5.8的安装部署过程 说明 大周末的写文档. 主要是备忘,之前写的太乱了. 本来想自己写一份, 但是怀疑找到了瀚高工程师的文档 准备直接学习一下: https://blog.csdn.net/qiuchenjun/article/details/125985256 感谢原作者 安装 rpm

[转帖]Redhat 8 磁盘调度策略变化:NOOP改为NONE

说明 在 redhat 4/5/6/7版本中的NOOP调度策略,从8开始修改为NONE,官方解释: none Implements a first-in first-out (FIFO) scheduling algorithm. It merges requests at the generic

.NET周报 【4月第5期 2023-04-30】

国内文章 基于 Github 平台的 .NET 开源项目模板. 嘎嘎实用! https://www.cnblogs.com/NMSLanX/p/17326728.html 大家好,为了使开源项目的维护和管理更方便一些,出于个人需求写了一款开源项目的模板,该模板基于 Github 平台,并使用 .NE

队列的内置模块(deque)--双向队列

博客地址:https://www.cnblogs.com/zylyehuo/ # -*- coding: utf-8 -*- from collections import deque q = deque([1,2,3,4,5], 5) q.append(6) # 队尾进队 print(q.popl