发现漏洞的第一步则是需要寻找到可利用的反汇编指令片段,在某些时候远程缓冲区溢出需要通过类似于jmp esp
等特定的反汇编指令实现跳转功能,并以此来执行布置好的ShellCode
恶意代码片段,LyScript
插件则可以很好的完成对当前进程内存中特定函数的检索工作。
一般而言远程缓冲区溢出攻击通常利用的是一些具有缓冲区溢出漏洞的函数或是特定的汇编指令片段,如:
- strcpy:该函数将一个字符串复制到另一个字符串缓冲区中,但不会检查缓冲区的大小,因此很容易导致缓冲区溢出。
- gets:该函数将用户输入的数据读入字符串缓冲区中,但不会检查缓冲区的大小,因此很容易导致缓冲区溢出。
- sprintf:该函数将一个字符串格式化到字符串缓冲区中,但不会检查缓冲区的大小,因此很容易导致缓冲区溢出。
在远程缓冲区溢出攻击中,攻击者也可以利用汇编指令jmp esp
来实现对攻击代码的执行。该指令允许攻击者跳转到堆栈中的任意位置,并从那里执行恶意代码。
4.3.1 搜索可利用汇编指令集
在默认情况下,LyScript插件并不具备搜索连续指令的能力,虽然提供了get_disasm_code()
系列的反汇编函数,但此类函数通常仅仅只能实现简单的反汇编功能,读者如果需要实现其他附加功能,含需要自行动手去实现,首先我们自行实现一个简单的汇编指令检索功能,用于寻找可利用的指令片段"pop esp","jmp esp","jmp eax","pop ecx"
等指令集。
这段代码实现的机制可总结为如下步骤;
- 1.调用
connect
函数来连接到要调试的程序,并使用get_local_base
和get_local_size
函数获取程序的内存范围。 - 2.定义一个名为
search_asm
的列表,该列表包含要搜索的汇编指令。 - 3.使用一个
while
循环来遍历内存范围中的每一个地址,并调用get_disasm_one_code
函数获取该地址处的反汇编代码。 - 4.使用另一个for循环来遍历
search_asm
列表中的每一个指令,并检查当前反汇编代码是否与列表中的指令匹配。如果匹配,则输出该地址和反汇编代码。
代码很容易被理解和实现,本质上仅仅只是提取所内存中所有的汇编指令集,并依次枚举对比是否符合列表中的条件,其最终实现代码如下所示;
from LyScript32 import MyDebug
if __name__ == "__main__":
dbg = MyDebug()
dbg.connect()
local_base_start = dbg.get_local_base()
local_base_end = local_base_start + dbg.get_local_size()
print("开始地址: {} --> 结束地址: {}".format(hex(local_base_start),hex(local_base_end)))
search_asm = ["pop esp","jmp esp","jmp eax","pop ecx"]
while local_base_start <= local_base_end:
disasm = dbg.get_disasm_one_code(local_base_start)
# print("地址: 0x{:08x} --> 反汇编: {}".format(local_base_start,disasm))
# 寻找指令
for index in range(0, len(search_asm)):
if disasm == search_asm[index]:
print("地址: {} --> 反汇编: {}".format(hex(local_base_start), disasm))
# 递增计数器
local_base_start = local_base_start + dbg.get_disasm_operand_size(local_base_start)
dbg.close()
如上代码被运行后,则会输出当前进程内所有可被利用的指令片段,其输出效果图如下图所示;
4.3.2 搜索可利用机器码
机器码的搜索与汇编指令集的搜索方式基本保持一致,但庆幸的是搜索指令集可使用scan_memory_all()
这个官方函数,该函数可用于扫描当前EIP所处位置,也就是当前EIP所在模块的所有符合条件的机器码,需要注意的是,在搜索具有漏洞函数时,通常我们会搜索进程内的完整模块,则此时应该先得到该模块的入口地址,并通过set_register()
设置到该模块所在内存,然后再次对该内存区域进行搜索,代码中opcode
用于指定一段机器码序列,此处读者可指定搜索多种机器码,并将搜索结果放入到该列表内进行存储。
这段代码的实现原理可总结为如下所示的步骤;
- 定义一个名为
opcode
的列表,该列表包含要搜索的机器码。 - 然后使用一个for循环来遍历每个模块,并调用
get_all_module
函数获取程序中的模块列表。对于每个模块,它将eip寄存器设置为该模块的入口点,然后调用scan_memory_all
函数搜索该模块中是否存在要搜索的机器码。 - 如果找到了指定的机器码,则输出模块名称、匹配个数以及机器码,并输出该机器码所在的地址。
根据上述流程可总结为如下所示的代码片段;
from LyScript32 import MyDebug
import time
if __name__ == "__main__":
dbg = MyDebug()
dbg.connect()
# 需要搜索的指令集片段
opcode = ['ff 25','ff 55 fc','8b fe']
# 循环搜索指令集内存地址
for index,entry in zip(range(0,len(opcode)), dbg.get_all_module()):
eip = entry.get("entry")
base_name = entry.get("name")
if eip != 0:
dbg.set_register("eip",eip)
search_address = dbg.scan_memory_all(opcode[index])
if search_address != False:
print("搜索模块: {} --> 匹配个数: {} --> 机器码: {}"
.format(base_name,len(search_address),opcode[index]))
# 输出地址
for search_index in search_address:
print("[*] {}".format(hex(search_index)))
time.sleep(0.3)
dbg.close()
以strcpy
函数为例,读者只需要搜索特征['57 8b 7c 24 08 eb 6e','ff 55 fc','8b fe']
即可定位到当前模块内所有调用该函数机器其他函数的内存地址。
运行后即可输出当前模块内所有被调用机器码的详细地址,输出效果如下图所示;