4.1 探索LyScript漏洞挖掘插件

在第一章中我们介绍了x64dbg这款强大的调试软件,通过该软件逆向工程师们可以手动完成对特定进程的漏洞挖掘及脱壳等操作,虽然x64dbg支持内置Script脚本执行模块,但脚本引擎通常来说是不够强大的,LyScript 插件的出现填补了这方面的不足,该插件的开发灵感来源于Immunity调试器中的ImmLib库,因Immunity调试器继承自Ollydbg导致该调试器无法支持64位应用的调试,同时该调试器也长期没有开发者进行维护,正是在这种情形之下LyScript诞生,如下图是插件的官方图标及下载地址;

LyScript是一款 x64dbg 自动化控制插件,通过Python控制x64dbg的行为,实现远程动态调试,解决了逆向工作者分析程序,反病毒人员脱壳,漏洞分析者寻找指令片段,原生脚本不够强大的问题,通过与Python相结合利用Python语法的灵活性以及其丰富的第三方库,加速漏洞利用程序的开发,辅助漏洞挖掘以及恶意软件分析。

LyScript插件的安装非常容易,读者只需要在官方下载对应的插件并放入到plugins/根目录下即可,当插件加载成功,读者可看到如下图所示的输出信息,过程中可能会出现网络通信授权框,请读者允许并放行即可;

接着读者还需要打开命令行,并通过pip命令安装对应的插件包,LyScript插件一般而言分为两个核心包LyScript用于实现基于函数的通信机制,而LyScriptTools则是对函数的类版封装,两者建议全部安装;

  • 安装标准包:pip install LyScript32 或者 pip install LyScript64
  • 安装扩展包:pip install LyScriptTools32 或者 pip install LyScriptTools64

安装此插件读者应该具备了Python开发环境,版本必须大于3.6才可以,当环境具备后,读者可执行上述命令,依次安装这几个不同的版本。

Microsoft Windows [版本 10.0.19042.1826]
(c) Microsoft Corporation。保留所有权利。

C:\Users\admin> pip install LyScript32
C:\Users\admin> pip install LyScript64
C:\Users\admin> pip install LyScriptTools32
C:\Users\admin> pip install LyScriptTools64

4.1.1 如何使用插件

LyScript 插件提供了丰富的 API 封装函数,包括寄存器、调试、模块、内存、堆栈、进程和线程等不同类别的函数。这些函数可以帮助开发者更方便地访问和修改程序的内部信息,便于调试和破解,但需要开发者具备一定的编程和调试经验,并遵守相应的规范和准则,以确保软件的安全和稳定性。

注意:首先读者需要启动带有插件功能的x64dbg调试器,并手动拖入一个任意被调试进程到调试器中,这是使用插件功能的必备条件,基于Python下的LyScript模块无法独立运行,这一点读者需要格外注意。

根据插件官方解释,用户在使用该插件时首先需要通过dbg = MyDebug()初始化一个调试控制类,当这个类被初始化结束后则可以通过dbg.connect()函数连接到调试器中,当连接被建立时则默认会创建一个持久会话直到Python脚本结束才会被强制断开,在连接期间读者也可通过dbg.is_connect()检测套接字是否存在,如下面这段代码则是一个最基本的实现方法。

>>> from LyScript32 import MyDebug
>>>
>>> dbg=MyDebug()
>>>
>>> connect_flag = dbg.connect()
>>> print("连接状态: {}".format(connect_flag))
连接状态: True
>>>
>>> ref = dbg.is_connect()
>>> print("是否在连接: ", ref)
是否在连接: True
>>>
>>> dbg.close()
True

在该代码示例中,用户首先通过导入 LyScript 插件中的MyDebug类,创建了一个名为dbg的实例,并初始化该类。接着调用dbg.connect()函数,建立连接。连接成功后,使用is_connect()函数检查套接字是否存在,并将结果打印出来。最后,调用dbg.close()函数以关闭调试器连接。

4.1.2 读取或设置寄存器参数

寄存器是计算机中的一种高速存储设备,位于CPU内部。它们由一些元器件构成,通常是用于存储和操作CPU指令和数据的硬件单元。不同的CPU架构可能会有不同的寄存器数量、位宽和功能。

寄存器是计算机架构中非常重要的一部分,因为他们能够在CPU执行指令时快速地存储和读取数据,从而提高计算速度。寄存器的存储速度比内存快得多,通常称为“零缓存器延迟”(zero cache delay),所以它们是程序优化的主要目标之一。

寄存器的功能多种多样,包括存储数据、指令地址、指令结果,以及用于计算、判断等各种用途。常见的寄存器包括累加器、计数器、指针寄存器、标志寄存器等。在程序中,寄存器经常用来存储中间计算结果和临时变量,通常比内存访问更快,从而提高程序运行效率。

在x64dbg中寄存器通常会展现在屏幕的右上角,以32位为例,默认情况下寄存器可被分为,通用寄存器,标志寄存器,DR系列特殊寄存器组,以及段选择子等。

通用寄存器以及该寄存器的功能如下表格所示;

寄存器名 描述
EAX 累加器,用于存放一些计算结果或指针。
ECX 计数器,主要用于循环计数等功能。
EDX 数据寄存器,被用于存放一些指针或标志信息。
EBX 基地址寄存器,主要用于访问内存中的数据。
ESP 栈指针,用于指向当前的栈顶。
EBP 基址指针,通常被用来指向当前的栈帧。
ESI 源索引寄存器,通常用于字符串操作。
EDI 目标索引寄存器,也常常用于字符串操作。

标志寄存器以及该寄存器的功能如下表格所示;

标志寄存器 含义
CF 进位标志 (Carry Flag)。该标志表示在执行无符号算术指令时是否发生了进位。
PF 奇偶标志 (Parity Flag)。该标志表示指令执行后结果的低八位中1的个数是否为偶数。如果是偶数,标志位被设置为1,否则为0。
AF 奥半字进位标志 (Auxiliary Carry Flag)。该标志表示执行指令后低四位的进位情况。
ZF 零标志 (Zero Flag)。该标志表示上一条指令执行后结果是否为零。如果结果为零,标志位被设置为1,否则为0。
SF 符号标志 (Sign Flag)。该标志表示结果是否为负数。如果结果为负数,标志位被设置为1,否则为0。
TF 调试标志 (Trap Flag)。该标志用于单步调试,当该标志被设置为1时,CPU将在执行完每一条指令后暂停,这使得调试器可以检查这一指令对寄存器和存储器的影响。
IF 中断允许标志 (Interrupt Flag)。如果该标志为1,表示CPU允许响应来自外部设备的可屏蔽中断请求。
DF 方向标志 (Direction Flag)。该标志用于指示字符串操作指令是否应该向前 (DF=0) 或向后 (DF=1) 方向进行操作。
OF 溢出标志 (Overflow Flag)。该标志表示在执行有符号算术指令时是否发生了溢出。

LyScript插件中,通常会使用get_register()函数获取特定通用寄存器的参数,与之对应的可以使用set_register()函数实现设置,如下片段则是分别获取与设置特定寄存器组的函数调用规范;

>>> eax = dbg.get_register("eax")
>>> hex(eax)
'0xa'
>>>
>>> eip = dbg.get_register("eip")
>>> hex(eip)
'0x76fdc3b8'
>>>
>>> flag = dbg.set_register("eax",100)
>>> flag
True
>>> hex(dbg.get_register("eax"))
'0x64'

同理,对于获取与设置标志寄存器,也有一对标准的通用函数get_flag_register()函数用于获取一个标志,而与之对应的set_flag_register()函数则用于设置一个标志,需要注意的是在设置标志时,第二个参数需传入一个状态[设置为真 True] / [设置为假 False]而不是接受一个字符或整数;

>>> tf = dbg.get_flag_register("tf")
>>> tf
False
>>>
>>> zf = dbg.get_flag_register("zf")
>>> zf
True
>>>
>>> flag = dbg.set_flag_register("cf",True)
>>> flag
True
>>>

4.1.3 使用调试控制系列函数

调试器的核心功是对程序进行调试,而调试器内部实现往往会调用操作系统提供的调试API,调试系列函数是这些API之一,用于帮助开发者在程序执行时得到更多的信息,包括内存值、指令执行状态、变量状态等,以便更加全面和深入地了解代码的运行情况和错误。调试系列函数的主要作用如下:

  • 用于设置和管理断点,包括硬件断点、软件断点等。
  • 提供各种调试命令,包括单步执行、运行到某个指定位置、查看内存寄存器的状态信息等,为程序的调试过程提供必要的信息。

总之,调试系列函数为调试器提供了丰富的操作接口和调试工具,使得开发人员能够更加深入和全面地了解程序的运行状态,并借助这些信息更好地调试和定位程序的错误和漏洞。

首先介绍的是set_debug()函数,该函数是调试器中一个非常重要的函数,它被用于影响调试器的状态,常见的操作包括前进一次、后退一次、暂停调试、终止调试等。在调试过程中,开发人员会根据需要进行不同的调试操作,以理解程序执行的过程、获得代码执行的状态信息以及找到代码中的错误。

该函数的可用的动作范围包括:暂停(Pause)、运行(Run)、步入(StepIn)、步过(StepOut)、到结束(StepOver)、停止(Stop)以及等待(Wait)。通过调用set_debug()函数,并传递相应的参数,读者可以方便地进行调试操作,并随时获取程序的运行状态信息。例如,将参数设置为“暂停”,则可以暂停程序的执行、查看程序内存中的值以及检查程序调用栈等信息;将参数设置为“运行”则可以继续程序的执行,直到遇到下一个断点或者程序结束。

我们以步入(StepIn)、步过(StepOut)为例,读者可使用如下命令执行一次动作;

>>> dbg.set_debug("StepIn")
True
>>> dbg.set_debug("StepOut")
True
>>>

此外判断调试器动作也是一种非常普遍的功能,插件内提供了is_debugger() /is_running()/is_run_locked()三个调试函数,函数is_debugger可用于验证当前调试器是否处于调试状态,函数is_running则用于验证是否在运行,函数is_run_locked用于检查调试器是否被锁定(暂停),这三个函数的调用规范与上方基本一致;

>>> dbg.is_debugger()
True
>>> dbg.is_running()
False
>>> dbg.is_run_locked()
True

4.1.4 使用断点设置系列函数

断点是调试器中常用的工具之一,可以帮助开发人员暂停程序的运行并检查程序中的错误。x64dbg中的断点分为以下几类:

  • 软件断点(BP):软件断点是一种在程序执行期间暂停程序并引起中断的代码指令,可用于修复软件中的一些缺陷或调试程序。在x64dbg中,使用“F2”键可以在程序的代码段中设置软件断点,碰到指定断点时会暂停程序并进入调试模式,以便对程序进行调试。

  • 硬件断点:硬件断点是一种针对某个具体的地址,由CPU硬件支持的断点,当程序执行到该地址时,CPU会中断程序并通知调试器进行调试。硬件断点在调试器中设置方式和软件断点相同,也是通过“F2”键来设置。在使用硬件断点时,需要特殊注意硬件断点的数量,因为硬件断点数量通常非常有限。

  • 内存断点:内存断点是一种根据条件变化暂停程序执行的断点,它可以对内存地址进行监视,当内存中的指定值在程序运行时发生变化时触发中断。在x64dbg中,可以通过“右键菜单”中的“内存浏览器”或“内存”窗口设置内存断点。

LyScript插件可以使用set_breakpoint()函数设置软件断点,使用delete_breakpoint()函数删除一个软件断点,使用check_breakpoint()函数可用于检测断点是否被命中,使用get_all_breakpoint()可用于输出当前所有断点

>>> eip = dbg.get_register("eip")
>>> dbg.set_breakpoint(eip)
True
>>> dbg.check_breakpoint(eip)
True
>>> ref = dbg.get_all_breakpoint()
>>> ref
[{'addr': 4584081, 'enabled': 1, 'hitcount': 0, 'type': 1}, {'addr': 1996337564, 'enabled': 1, 'hitcount': 0, 'type': 1}, {'addr': 1996337612, 'enabled': 1, 'hitcount': 0, 'type': 1}, {'addr': 1996337643, 'enabled': 1, 'hitcount': 0, 'type': 1}]

同理,当读者需要设置硬件断点是可使用set_hardware_breakpoint()设置,但需要注意硬件断点在32位系统中最多设置4个,如果需要取消断点则可使用delete_hardware_breakpoint()函数将断点进行移除;

断点类型可用范围:[类型 0 = HardwareAccess / 1 = HardwareWrite / 2 = HardwareExecute]

>>> ref = dbg.set_hardware_breakpoint(eip,2)
>>> print(ref)
True
>>> ref = dbg.delete_hardware_breakpoint(eip)
>>> print(ref)
True

4.1.5 使用堆栈系列函数

堆栈窗口是调试器非常重要的一个功能窗口,可以帮助开发人员监视程序运行时的堆栈信息。该窗口能够显示当前线程的调用栈、局部变量(Local Variables)以及函数参数(Function Parameters)等重要信息,并以图形化的方式呈现出来,方便用户进行查看和调试。

在x64dbg的堆栈窗口中,对于每一个程序运行时的线程,都会显示当前线程的调用栈信息,最上面的栈帧表示当前正在执行的函数,下面栈帧则为调用该函数的函数。用户可以通过向上和向下翻转堆栈栈帧查看程序函数调用的层级,便于查找程序执行过程中的错误和问题。

此外,在当前调用栈帧中,用户还可以查看当前函数的参数和局部变量等信息,这些信息通常是有助于调试程序问题的关键性信息。用户可以通过右键单击堆栈窗口中的任何一帧来切换该帧的状态,包括指针、参数、图像、字符串、十六进制等,以便更加准确地查看当前栈的信息。

综上所述,堆栈窗口是x64dbg调试器中非常重要的一个功能窗口,可以帮助用户在程序执行过程中理解、调试和跟踪程序执行的层次结构和变量信息,解决代码的问题,提高开发效率。

堆栈系列函数包括了push_stack()用于向目标堆栈中压入一个数值,与之对应的pop_stack()则用于在堆栈中弹出一个元素,peek_stack()函数用于检查堆栈内的参数,可设置偏移值,不设置则默认检查第一个也就是栈顶。

>>> check = dbg.peek_stack()
>>> check
1996055601
>>>
>>> check = dbg.peek_stack(1)
>>> hex(check)
'-0x757bb72f'
>>>
>>> dbg.push_stack(10)
True
>>> dbg.push_stack(10)
True
>>> dbg.pop_stack()
True
>>> check = dbg.peek_stack(1)
>>> hex(check)
'0x76f96431'
>>>

4.1.6 使用反汇编系列函数

反汇编是调试器中非常重要的核心功能之一,它可以将已编译的二进制代码转换为对应的汇编代码,帮助开发人员了解程序的执行流程和代码逻辑,并定位代码问题。在x64dbg调试器中,反汇编功能是其核心功能之一,具有以下特点:

  • 显示汇编代码:x64dbg反汇编窗口可以将二进制代码反汇编为对应的汇编代码,包括指令、寄存器、内存地址等信息,方便开发人员进行代码分析和调试。
  • 支持多种格式:除了常见的AT&T格式和Intel格式汇编代码之外,x64dbg还支持多种反汇编码格式,比如Hopper、GDB和IDA等,满足不同开发人员的需求。
  • 集成调试功能:x64dbg反汇编窗口还集成了调试功能,支持在反汇编视图中设置断点、单步执行、条件断点等操作,可以帮助开发人员更加有效地调试程序。
  • 支持多种反汇编引擎:x64dbg支持多种反汇编引擎,包括TitanEngine、Capstone、BeaEngine等,可以进行更加准确和全面的反汇编分析。

反汇编是x64dbg调试器的核心功能之一,可以帮助开发人员进行代码分析和调试,定位代码问题,提高开发效率。通过x64dbg的反汇编功能,开发人员可以有效地了解程序的代码逻辑、执行流程和指令序列,为调试和分析程序提供有力的支持。

当读者需要使用插件控制调试器反汇编时可以使用get_disasm_code()该函数主要用于对特定内存地址进行反汇编,需传入两个参数,并输出一个字典类型的数据集合,如下案例我们反汇编EIP位置处向下的30个行;

>>> eip = dbg.get_register("eip")
>>> disasm_dict = dbg.get_disasm_code(eip,30)
>>>
>>> for ds in disasm_dict:
... print("地址: {} 反汇编: {}".format(hex(ds.get("addr")),ds.get("opcode")))
...
地址: 0x76fdb19c 反汇编: ret
地址: 0x76fdb19d 反汇编: mov ecx, dword ptr ds:[0x770537C0]
地址: 0x76fdb1a3 反汇编: test cl, 0x3
地址: 0x76fdb1a6 反汇编: je 0x76FDC66E
地址: 0x76fdb1ac 反汇编: push eax
地址: 0x76fdb1ad 反汇编: push 0x76F36CF0

4.1.7 使用内存读写系列函数

内存读写功能,可以让读者在调试阶段直接读取和修改程序的内存内容,方便程序调试和分析。下面是对LyScript插件内存读写功能的简要概述:

  • 内存读取:通过LyScript插件,开发人员可以通过代码读取已经加载的程序进程的内存中特定地址的值。该插件提供了几个不同的内存读取函数,包括ReadByte()、ReadWord()、ReadDword()、ReadQword()等。通过这些函数,开发人员可以直接读取目标窗口中的指定内存地址,以获取其值。

  • 内存写入:除了读取程序内存的值,LyScript插件还支持修改程序的内存值。内存写入函数为WriteByte()、WriteWord()、WriteDword()、WriteQword()等几个相关的函数。通过这些函数,开发人员可以直接修改目标窗口中的指定内存地址,以达到调试和分析程序的目的。

通过LyScript插件的内存读写功能,读者可以直接读取和修改目标程序的内存信息,方便调试和分析程序。同时,该插件也提供了一些内存操作API,一些常用的内存操作功能,帮助安全从业者更加方便地操作内存数据。

当读者需要读取内存数据时可以使用read_memory_byte()来实现,如下案例中通过get_register()函数获取到当前EIP的内存地址,并使用内存读函数获取该内存中的前十条数据集,输出效果如下所示;

>>> eip = dbg.get_register("eip")
>>>
>>> for index in range(0,10):
... ref = dbg.read_memory_byte(eip+index)
... print(hex(ref))
...
0xc3
0x8b
0xd
0xc0
0x37
0x5
0x77
0xf6
0xc1
0x3

至此本章基本介绍就到此为止,本章介绍了在Windows平台中使用LyScript插件进行反汇编和调试的相关内容。首先介绍了LyScript插件的基本功能和使用方法,如通过不同的命令进行反汇编、查看汇编代码、设置断点等。并结合具体案例,演示了如何在LyScript中使用不同的命令进行程序反汇编和调试。同时,还介绍了调试器中常用的断点类型及其功能,并以x64dbg为例对堆栈窗口的功能进行详细介绍。读者如果需要了解更多关于LyScript插件的使用手册可自行去官方查阅。