Python 调试与逆向工程详解

调试器是用于逆向工程的主要工具。使用调试器,我们可以在运行时执行分析以理解程序。我们可以识别呼叫链并跟踪间接呼叫。通过调试器,我们可以分析和观察程序运行时,以指导我们的逆向工程。在本章中,我们将学习如何在脚本中使用调试器。

本章涵盖的主题如下:

逆向工程分析主要有三种:

  • 静态分析:对二进制文件内容的分析。这有助于确定可执行部分的结构并打印出可读部分,以获得关于可执行部分用途的更多细节。
  • 动态分析:该类型将执行二进制文件,无论是否附加调试器,以发现其用途以及可执行文件的工作方式。
  • 混合分析:这是静态和动态分析的混合。在静态分析之间重复,然后进行动态调试,可以更好地直观地了解程序。

任何 UNIX 或 Windows 二进制可执行文件都有一个头来描述其结构。这包括其代码的基址、数据段和可从可执行文件导出的函数列表。当操作系统执行可执行文件时,首先操作系统读取其头信息,然后从二进制文件加载二进制数据,以填充相应进程地址的代码和数据部分的内容。

可移植可执行文件PE文件)是 Windows 操作系统可以执行或运行的文件类型。我们在 Windows 系统上运行的文件是 Windows PE 文件;它们可以有 EXE、DLL(动态链接库)和 SYS(设备驱动程序)扩展。此外,它们还包含 PE 文件格式。

Windows 上的二进制可执行文件具有以下结构:

  • DOS 标头(64 字节)
  • PE 头部
  • 章节(代码和数据)

现在,我们将详细研究每一个问题。

DOS 头

DOS 头以幻数4D 5A 50 00(前两个字节为字母MZ)开头,最后四个字节(e_lfanew)表示 PE 头在二进制可执行文件中的位置。所有其他字段都不相关。

PE 割台

PE 标题包含更多有趣的信息。以下是 PE 标头的结构:

PE header

PE 总管由三部分组成:

  • 4 字节幻码
  • 20 字节文件头,数据类型为图像文件头
  • 224 字节可选头,数据类型为图像\可选\头 32

另外,可选标题有两部分。前 96 个字节包含主要操作系统和入口点等信息。第二部分由 16 个条目组成,每个条目中有 8 个字节,以形成一个 128 字节的数据目录。

有关 PE 文件的更多信息,请访问:http://www.microsoft.com/whdc/system/platform/firmware/PECOFF.mspx 文件头中使用的结构:http://msdn2.microsoft.com/en-gb/library/ms680198.aspx

我们可以使用pefile模块(一个用于处理 PE 文件的多平台完整 Python 模块)在 Python 中获取这些文件头的所有详细信息。

加载 PE 文件

加载文件非常简单,只需在模块中创建 PE 类的实例,并将可执行文件的路径作为参数。

首先,导入模块pefile

Import pefile

使用可执行文件启动实例:

pe = pefile.PE('path/to/file')

检查集管

在交互式终端中,我们可以对 PE 文件头进行基本检查。

照常导入pefile并加载可执行文件:

>>>import pefile 
>>>pe = pefile.PE('md5sum.exe') 
>>> dir(pe)

这将打印对象。为了更好地理解,我们可以使用pprint模块以可读的格式打印此对象:

>>> pprint.pprint(dir(pe))

这将以可读格式列出所有内容,如下所示:

Inspecting headers

我们还可以打印特定标题的内容,如下所示:

>>> pprint.pprint(dir(pe.OPTIONAL_HEADER))

可以使用 hex()获取每个标头的十六进制值:

>>>hex( pe.OPTIONAL_HEADER.ImageBase)

检查路段

要检查可执行文件中的节,我们必须迭代pe.sections

>>>for section in pe.sections:
 print (section.Name,
      hex(section.VirtualAddress),
      hex(section.Misc_VirtualSize),
      section.SizeOfRawData)

聚乙烯封隔器

打包机是用于压缩 PE 文件的工具。这将减小文件的大小,并为静态反向工程的文件添加另一层模糊处理。尽管创建打包程序是为了减小可执行文件的总体文件大小,但后来,许多恶意软件作者利用了模糊处理的好处。打包器将压缩后的数据包装在工作 PE 文件结构中,并将 PE 文件数据解压缩到内存中,并在执行时运行。

如果可执行文件被打包,我们可以使用签名数据库来检测使用的打包器。可以通过搜索 Internet 找到签名数据库文件。

为此,我们需要另一个模块peutils,它与pefile模块一起提供。

您可以从本地文件或 URL 加载签名数据库:

Import peutils
signatures = peutils.SignatureDatabase('/path/to/signature.txt')

您还可以使用以下选项:

signatures = peutils.SignatureDatabase('handlers.sans.org/jclausing/userdb.txt')

加载签名数据库后,我们可以使用此数据库运行 PE 实例,以识别所用封隔器的签名:

matches = signatures.match(pe, ep_only = True)
print matches

这将输出可能使用的封隔器。

此外,如果我们检查打包的可执行文件中的节名称,它们将有轻微的差异。例如,一个用 UPX 打包的可执行文件,其节名将是UPX0UPX1等等。

进口可列示如下:

for entry in pe.DIRECTORY_ENTRY_IMPORT:
  print entry.dll
  for imp in entry.imports:
    print '\t', hex(imp.address), imp.name

同样,我们不能列出出口:

for exp in pe.DIRECTORY_ENTRY_EXPORT.symbols:
  print hex(pe.OPTIONAL_HEADER.ImageBase + exp.address), exp.name, exp.ordinal

拆卸是与组装相反的过程。反汇编程序试图从二进制机器代码创建汇编代码。为此,我们使用了一个名为Capstone的 Python 模块。Capstone 是一个自由、多平台和多体系结构的反汇编引擎。

安装后,我们可以在 Python 脚本中使用此模块。

首先,我们需要运行一个简单的测试脚本:

from capstone import *
cs = Cs(CS_ARCH_X86, CS_MODE_64)
for i in cs.disasm('\x85\xC0', 0x1000)
   print("0x%x:\t%s\t%s" %(i.address, i.mnemonic, i.op_str))

脚本的输出如下所示:

0x1000:     test  eax, eax

第一行导入模块,然后使用Cs启动capstonePython 类,该类包含两个参数:硬件架构和硬件模式。在这里,我们说明如何为 x86 体系结构反汇编 64 位代码。

下一行迭代代码列表,并将代码传递给capstone实例cs中的disasm()disasm()的第二个参数是第一次安装的地址。disasm()的输出是Cslnsn类型的装置列表。

最后,我们打印出一些输出。Cslnsn公开有关已拆卸装置的所有内部信息。

其中一些措施如下:

  • Id:该指令的指令 Id
  • 地址:指令的地址
  • 助记符:指令的助记符
  • op_str:指令的操作数
  • 大小:指令的大小
  • 字节:指令的字节序列

像这样,我们可以用顶点分解二进制文件。

接下来,我们使用capstone反汇编程序对我们用pefile提取的代码进行反汇编,得到汇编代码。

像往常一样,我们从导入所需的模块开始。这里是capstonepefile

from capstone import *
import pefile
pe = pefile.PE('md5sum.exe')
entryPoint = pe.OPTIONAL_HEADER.AddressOfEntryPoint
data = pe.get_memory_mapped_image()[entryPoint:]
cs = Cs(CS_ARCH_X86, CS_MODE_32)
for i in cs.disasm(data, 0x1000):
    print("0x%x:\t%s\t%s" %(i.address, i.mnemonic, i.op_str))

IMAGE_OPTIONAL_HEADER中的AddressofEntryPoint值是指向相对于映像基址的入口点函数的指针。对于可执行文件,这正是应用程序代码开始的位置。因此,我们借助于pefile 作为pe.OPTIONAL_HEADER.AddressOfEntryPoint获得代码的开头,并将其传递给反汇编程序。

调试是修复程序中错误的过程。调试器是那些可以运行并监视另一个程序执行的程序。因此,调试器可以控制目标程序的执行,并可以监视或更改目标程序的内存和变量

断点

断点有助于在我们选择的位置停止调试器中目标程序的执行。此时,执行停止,控制权传递给调试器。

断点有两种不同的形式:

  • 硬件断点:硬件断点需要 CPU 的硬件支持。它们使用特殊的调试寄存器。这些寄存器包含断点地址、控制信息和断点类型。

  • 软件断点:软件断点将原始指令替换为捕获调试器的指令。这只能在执行时中断。它们之间的主要区别在于可以在内存上设置硬件断点。但是,无法在内存上设置软件断点。

我们可以使用 PyDBG 模块在运行时调试可执行文件。我们可以使用 PyDBG 浏览一个基本脚本,以了解它是如何工作的。

首先,我们导入模块:

from pydbg import *
import sys

然后我们定义一个函数来处理断点。另外,它以pydbg实例作为参数。在该函数中,它打印出流程的执行上下文,并指示pydbg继续:

define breakpoint_handler(dbg):
   print dbg.dump_context()
   return DBG_CONTINUE

然后初始化pydbg实例,设置handler_breakpoint函数处理断点异常:

dbg = pydbg()
dbg.set_callback(EXEPTION_BREAKPOINT, breakpoint_handler)

然后使用pydbg附加我们需要调试的流程的流程 ID:

dbg.attach(int(sys.argv[1]))

接下来,我们将设置触发断点的地址。这里,我们使用bp_set()函数,它接受三个参数。第一个是设置断点的地址,第二个是可选描述,第三个参数表示pydbg是否恢复该断点:

dbg.bp_set(int(sys.argv[1], 16), "", 1)

最后,在事件循环中启动pydbg

dbg.debug_event_loop()

在本例中,我们将断点作为参数传递给此脚本。因此,我们可以按如下方式运行此脚本:

$ python debug.py 1234 0x00001fa6

pydbg包含许多其他有用的功能,可在以下文档中找到:http://pedramamini.com/PaiMei/docs/PyDbg/public/pydbg.pydbg.pydbg-class.html

我们已经讨论了一些基本工具,这些工具可用于以编程方式使用 Python 对二进制文件进行反向工程和调试。现在,您将能够编写自定义脚本来调试和反向工程可执行文件,这将有助于恶意软件分析。在下一章中,我们将用 Python 讨论一些加密、哈希和转换函数。

教程来源于Github,感谢apachecn大佬的无私奉献,致敬!

技术教程推荐

Web协议详解与抓包实战 -〔陶辉〕

后端技术面试 38 讲 -〔李智慧〕

互联网人的英语私教课 -〔陈亦峰〕

用户体验设计实战课 -〔相辉〕

etcd实战课 -〔唐聪〕

陈天 · Rust 编程第一课 -〔陈天〕

现代C++20实战高手课 -〔卢誉声〕

B端体验设计入门课 -〔林远宏(汤圆)〕

B端产品经理入门课 -〔董小圣〕