4.10 x64dbg 反汇编功能的封装

x64dbg,反汇编,功能,封装 · 浏览次数 : 223

小编点评

**获取下一条汇编指令** 1.检查当前内存地址是否被下绊子(如果存在则说明,此处需要获取到原始的汇编指令长度,然后与当前eip地址相加获得)。 2.如果命中了断点,则此处又会两种情况,如果是用户下的断点,则此处调试器会在指令位置替换为CC断点,也就是汇编中的init停机指令,该指令占用1个字节,需要eip+1得到。而如果是系统断点,EIP所停留的位置,则我们需要正常获取当前指令地址,此处调试器没有改动汇编指令,仅仅只下了异常断点。 3.根据是否命中断点,获取当前指令的上一条指令。 **获取上一条汇编指令** 1.从内存中获取当前指令的长度。 2.循环扫描汇编代码,从第一个代码开始,逐个比较与当前指令的长度。 3.当找到匹配的代码时,将其上一个代码的指令的opcode获取出来并存储在prev_dasm中。 4.循环结束后,返回prev_dasm。 **输出结果** 1.输出当前EIP的下一条指令。 2.输出当前EIP的上一条指令。

正文

LyScript 插件提供的反汇编系列函数虽然能够实现基本的反汇编功能,但在实际使用中,可能会遇到一些更为复杂的需求,此时就需要根据自身需要进行二次开发,以实现更加高级的功能。本章将继续深入探索反汇编功能,并将介绍如何实现反汇编代码的检索、获取上下一条代码等功能。这些功能对于分析和调试代码都非常有用,因此是书中重要的内容之一。在本章的学习过程中,读者不仅可以掌握反汇编的基础知识和技巧,还能够了解如何进行插件的开发和调试,这对于提高读者的技能和能力也非常有帮助。

4.10.1 搜索内存机器码特征

首先我们来实现第一种需求,通过LyScript插件实现搜索内存中的特定机器码,此功能当然可通过scan_memory_all()系列函数实现,但读者希望你能通过自己的理解调用原生API接口实现这个需求,要实现该功能第一步则是需要封装一个GetCode()函数,该函数的作用是读取进程数据到内存中。

其中dbg.get_local_base()用于获取当前进程内的首地址,而通过start_address + dbg.get_local_size()的方式则可获取到该程序的结束地址,当确定了读取范围后再通过dbg.read_memory_byte(index)循环即可将程序的内存数据读入,而ReadHexCode()仅仅只是一个格式化函数,这段程序的核心代码可以总结为如下样子;

# 将可执行文件中的单数转换为 0x00 格式
def ReadHexCode(code):
    hex_code = []

    for index in code:
        if index >= 0 and index <= 15:
            #print("0" + str(hex(index).replace("0x","")))
            hex_code.append("0" + str(hex(index).replace("0x","")))
        else:
            hex_code.append(hex(index).replace("0x",""))
            #print(hex(index).replace("0x",""))

    return hex_code

# 获取到内存中的机器码
def GetCode():
    try:
        ref_code = []
        dbg = MyDebug()
        connect_flag = dbg.connect()
        if connect_flag != 1:
            return None

        start_address = dbg.get_local_base()
        end_address = start_address + dbg.get_local_size()

        # 循环得到机器码
        for index in range(start_address,end_address):
            read_bytes = dbg.read_memory_byte(index)
            ref_code.append(read_bytes)

        dbg.close()
        return ref_code
    except Exception:
        return False

接着则需要读者封装实现一个SearchHexCode()搜索函数,如下这段代码实现了在给定的字节数组中搜索特定的十六进制特征码的功能。

具体而言,函数接受三个参数:Code表示要搜索的字节数组,SearchCode表示要匹配的特征码,ReadByte表示要搜索的字节数。

函数首先获取特征码的长度,并通过一个for循环遍历给定字节数组中的所有可能匹配的位置。对于每个位置,函数获取该位置及其后面SearchCount个字节的十六进制表示形式,并将其与给定的特征码进行比较。如果有一位不匹配,则计数器重置为0,否则计数器加1。如果计数器最终等于特征码长度,则说明已找到完全匹配的特征码,函数返回True。如果遍历完整个数组都没有找到匹配的特征码,则函数返回False。

# 在字节数组中匹配是否与特征码一致
def SearchHexCode(Code,SearchCode,ReadByte):
    SearchCount = len(SearchCode)
    #print("特征码总长度: {}".format(SearchCount))
    for item in range(0,ReadByte):
        count = 0
        # 对十六进制数切片,每次向后遍历SearchCount
        OpCode = Code[ 0+item :SearchCount+item ]
        #print("切割数组: {} --> 对比: {}".format(OpCode,SearchCode))
        try:
            for x in range(0,SearchCount):
                if OpCode[x] == SearchCode[x]:
                    count = count + 1
                    #print("寻找特征码计数: {} {} {}".format(count,OpCode[x],SearchCode[x]))
                    if count == SearchCount:
                        # 如果找到了,就返回True,否则返回False
                        return True
                        exit(0)
        except Exception:
            pass
    return False

有了这两段程序的实现流程,那么完成特征码搜索功能将变得很容易实现,如下主函数中运行后则可搜索进程内search中所涉及到的机器码,当搜索到后则返回一个状态。

if __name__ == "__main__":
    # 读取到内存机器码
    ref_code = GetCode()
    if ref_code != False:
        # 转为十六进制
        hex_code = ReadHexCode(ref_code)
        code_size = len(hex_code)

        # 指定要搜索的特征码序列
        search = ['c0', '74', '0d', '66', '3b', 'c6', '77', '08']

        # 搜索特征: hex_code = exe的字节码,search=搜索特征码,code_size = 搜索大小
        ret = SearchHexCode(hex_code, search, code_size)
        if ret == True:
            print("特征码 {} 存在".format(search))
        else:
            print("特征码 {} 不存在".format(search))
    else:
        print("读入失败")

由于此类搜索属于枚举类,所以搜索效率会明显变低,搜索结束后则会返回该特征值是否存在的一个标志;

4.10.2 搜索内存反汇编特征

而与之对应的,当读者搜索反汇编代码时则无需自行实现内存读入功能,LyScript插件内提供了dbg.get_disasm_code(eip,1000)函数,可以让我们很容易的实现读取内存的功能,如下案例中,搜索特定反汇编指令集,当找到后返回其内存地址;

from LyScript32 import MyDebug

# 检索指定序列中是否存在一段特定的指令集
def SearchOpCode(OpCodeList,SearchCode,ReadByte):
    SearchCount = len(SearchCode)
    for item in range(0,ReadByte):
        count = 0
        OpCode_Dic = OpCodeList[ 0 + item : SearchCount + item ]
        # print("切割字典: {}".format(OpCode_Dic))
        try:
            for x in range(0,SearchCount):
                if OpCode_Dic[x].get("opcode") == SearchCode[x]:
                    #print(OpCode_Dic[x].get("addr"),OpCode_Dic[x].get("opcode"))
                    count = count + 1
                    if count == SearchCount:
                        #print(OpCode_Dic[0].get("addr"))
                        return OpCode_Dic[0].get("addr")
                        exit(0)
        except Exception:
            pass

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

    # 得到EIP位置
    eip = dbg.get_register("eip")

    # 反汇编前1000行
    disasm_dict = dbg.get_disasm_code(eip,1000)

    # 搜索一个指令序列,用于快速查找构建漏洞利用代码
    SearchCode = [
        ["ret", "push ebp", "mov ebp,esp"],
        ["push ecx", "push ebx"]
    ]

    # 检索内存指令集
    for item in range(0,len(SearchCode)):
        Search = SearchCode[item]
        # disasm_dict = 返回汇编指令 Search = 寻找指令集 1000 = 向下检索长度
        ret = SearchOpCode(disasm_dict,Search,1000)
        if ret != None:
            print("指令集: {} --> 首次出现地址: {}".format(SearchCode[item],hex(ret)))

    dbg.close()

如上代码当搜寻到SearchCode内的指令序列时则自动输出内存地址,输出效果图如下所示;

4.10.3 获取上下一条汇编指令

LyScript 插件默认并没有提供上一条与下一条汇编指令的获取功能,笔者认为通过亲自动手封装实现功能能够让读者更好的理解内存断点的工作原理,则本次我们将亲自动手实现这两个功能。

在x64dbg中,软件断点的实现原理与通用的软件断点实现原理类似。具体来说,x64dbg会在程序的指令地址处插入一个中断指令,一般是int3指令。这个指令会触发一个软件中断,从而让程序停止执行,等待调试器处理。在插入中断指令之前,x64dbg会先将这个地址处的原始指令保存下来。这样,当程序被调试器停止时,调试器就可以将中断指令替换成原始指令,让程序恢复执行。

为了实现软件断点,x64dbg需要修改程序的可执行代码。具体来说,它会将指令的第一个字节替换成中断指令的操作码,这样当程序执行到这个指令时就会触发中断。如果指令长度不足一个字节,x64dbg会将这个指令转换成跳转指令,跳转到另一个地址,然后在这个地址处插入中断指令。

此外在调试器中设置软件断点时,x64dbg会根据指令地址的特性来判断是否可以设置断点。如果指令地址不可执行,x64dbg就无法在这个地址处设置断点。另外,由于软件断点会修改程序的可执行代码,因此在某些情况下,设置过多的软件断点可能会影响程序的性能。

读者注意:实现获取下一条汇编指令的获取,需要注意如果是被命中的指令,则此处应该是CC断点占用一个字节,如果不是则正常获取到当前指令即可。

  • 1.我们需要检查当前内存断点是否被命中,如果没有命中则说明,此处需要获取到原始的汇编指令长度,然后与当前eip地址相加获得。
  • 2.如果命中了断点,则此处又会两种情况,如果是用户下的断点,则此处调试器会在指令位置替换为CC断点,也就是汇编中的init停机指令,该指令占用1个字节,需要eip+1得到。而如果是系统断点,EIP所停留的位置,则我们需要正常获取当前指令地址,此处调试器没有改动汇编指令,仅仅只下了异常断点。
from LyScript32 import MyDebug

# 获取当前EIP指令的下一条指令
def get_disasm_next(dbg,eip):
    next = 0

    # 检查当前内存地址是否被下了绊子
    check_breakpoint = dbg.check_breakpoint(eip)

    # 说明存在断点,如果存在则这里就是一个字节了
    if check_breakpoint == True:

        # 接着判断当前是否是EIP,如果是EIP则需要使用原来的字节
        local_eip = dbg.get_register("eip")

        # 说明是EIP并且命中了断点
        if local_eip == eip:
            dis_size = dbg.get_disasm_operand_size(eip)
            next = eip + dis_size
            next_asm = dbg.get_disasm_one_code(next)
            return next_asm
        else:
            next = eip + 1
            next_asm = dbg.get_disasm_one_code(next)
            return next_asm
        return None

    # 不是则需要获取到原始汇编代码的长度
    elif check_breakpoint == False:
        # 得到当前指令长度
        dis_size = dbg.get_disasm_operand_size(eip)
        next = eip + dis_size
        next_asm = dbg.get_disasm_one_code(next)
        return next_asm
    else:
        return None

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

    eip = dbg.get_register("eip")

    next = get_disasm_next(dbg,eip)
    print("下一条指令: {}".format(next))

    prev = get_disasm_next(dbg,4584103)
    print("下一条指令: {}".format(prev))

    dbg.close()

如上代码则是显现设置断点的核心指令集,读者可自行测试是否可读取到当前指令的下一条指令,其输出效果如下图所示;

读者注意:获取上一条汇编指令时,由于上一条指令的获取难点就在于,我们无法确定当前指令的上一条指令到底有多长,所以只能用笨办法,逐行扫描对比汇编指令,如果找到则取出其上一条指令即可。

from LyScript32 import MyDebug

# 获取当前EIP指令的上一条指令
def get_disasm_prev(dbg,eip):
    prev_dasm = None
    # 得到当前汇编指令
    local_disasm = dbg.get_disasm_one_code(eip)

    # 只能向上扫描10行
    eip = eip - 10
    disasm = dbg.get_disasm_code(eip,10)

    # 循环扫描汇编代码
    for index in range(0,len(disasm)):
        # 如果找到了,就取出他的上一个汇编代码
        if disasm[index].get("opcode") == local_disasm:
            prev_dasm = disasm[index-1].get("opcode")
            break

    return prev_dasm

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

    eip = dbg.get_register("eip")

    next = get_disasm_prev(dbg,eip)
    print("上一条指令: {}".format(next))

    dbg.close()

运行后即可读入当前EIP的上一条指令位置处的反汇编指令,输出效果如下图所示;

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

与4.10 x64dbg 反汇编功能的封装相似的内容:

4.10 x64dbg 反汇编功能的封装

LyScript 插件提供的反汇编系列函数虽然能够实现基本的反汇编功能,但在实际使用中,可能会遇到一些更为复杂的需求,此时就需要根据自身需要进行二次开发,以实现更加高级的功能。本章将继续深入探索反汇编功能,并将介绍如何实现反汇编代码的检索、获取上下一条代码等功能。这些功能对于分析和调试代码都非常有用,因此是书中重要的内容之一。在本章的学习过程中,读者不仅可以掌握反汇编的基础知识和技巧,还能够了解如

Linux定时任务概述

Linux定时任务概述 基于centos7.6.1810 参考鸟哥私房菜 /etc/crontab [root@VM-4-10-centos ~]# cat /etc/crontab SHELL=/bin/bash PATH=/sbin:/bin:/usr/sbin:/usr/bin MAILTO=

10.4 认识Capstone反汇编引擎

Capstone 是一款开源的反汇编框架,目前该引擎支持的CPU架构包括x86、x64、ARM、MIPS、POWERPC、SPARC等,Capstone 的特点是快速、轻量级、易于使用,它可以良好地处理各种类型的指令,支持将指令转换成AT&T汇编语法或Intel汇编语法等多种格式。Capstone的...

.NET周报【10月最后一期 2022-11-01】

精选要闻 .NET 7 NativeAOT比.NET单文件发布文件小80% https://twitter.com/JamesNK/status/1584919726861737984?s=20&t=cOsB41s2cydu_Ibts4xnEw AOT GRPC服务器应用程序比.NET运行时自包含的

[转帖]seafile 7.1.12专业版升级到seafile 8.0.11专业版教程

https://www.ittel.cn/archives/11643.html 目录 seafile 8.0.11专业版新功能 版本变更 部署环境升级 Ubuntu 18.04/20.4 Debian 10 CentOS 7.X CentOS 8.X 升级到 8.0.11 Ubuntu 18.04

解Bug之路-应用999线升高

前言 监控指标诚然是发现问题于微末之时的极佳手段,但指标往往有其表达的极限。在很多情况下,单独看一个黄金指标并不能表征系统的健康程度,反而有可能被其迷惑,进而忽略相关问题。(本文所提及的Linux Kernel源码版本为4.18.10) Bug现场 某天中午,某应用的999线突然升高。由于是个QPS

深入理解MD5:Message Digest Algorithm 5

title: 深入理解MD5:Message Digest Algorithm 5 date: 2024/4/21 18:10:18 updated: 2024/4/21 18:10:18 tags: MD5 哈希函数 密码学 数据完整性 碰撞攻击 安全性 替代算法 导论 MD5的背景和历史 MD5

文心一言 VS 讯飞星火 VS chatgpt (106)-- 算法导论10.1 4题

四、用go语言,重写 ENQUEUE 和 DEQUEUE 的代码,使之能处理队列的下溢和上溢。 文心一言: 在 Go 语言中,我们可以使用切片来实现队列。下面是如何实现 ENQUEUE 和 DEQUEUE 操作,同时处理队列的下溢和上溢的情况: package main import "fmt" t

[转帖]py_innodb_page_info.py工具使用

目录 一、Linux安装Python3 1. 解压包 2. 安装环境 3. 生成编译脚本 4. 检查python3.10的编译器 5. 建立Python3和pip3的软链 6. 添加到PATH 7. 验证Python3和pip3是否正常 二、py_innodb_page_info.py工具 1. p

7.4 C/C++ 实现链表栈

相对于顺序栈,链表栈的内存使用更加灵活,因为链表栈的内存空间是通过动态分配获得的,它不需要在创建时确定其大小,而是根据需要逐个分配节点。当需要压入一个新的元素时,只需要分配一个新的节点,并将其插入到链表的头部;当需要弹出栈顶元素时,只需要删除链表头部的节点,并释放其所占用的内存空间即可。由于链表栈的空间利用率更高,因此在实际应用中,链表栈通常比顺序栈更受欢迎。在实现上,链表栈通过使用`malloc