在当今复杂多变的网络安全领域,恶意软件分析、逆向工程和漏洞挖掘已成为网络安全专家的日常任务。然而,面对海量的样本和复杂的技术手段,传统的手动分析方法不仅耗时费力,还容易出错。如何在有限的时间内高效完成大规模分析任务,成为每一个安全从业者面临的挑战。今天,我们将为您介绍一个强大的工具——x64dbg Automate[1]。它是为x64dbg设计的自动化解决方案,基于x64dbg的命令执行引擎和插件API,提供了一个表达力强、现代化且易于使用的Python客户端库。通过强大的自动化功能,x64dbg Automate能帮助我们轻松应对恶意软件分析中的重复性工作、复杂性难题以及反调试机制,让您的分析工作变得高效且可复现。
为什么我们需要Automate?如果要用几个关键词来概括自动化能够帮助我解决的问题,它主要涉及以下方面:
-
减少重复性工作 -
应对复杂性 -
扩展分析流程和工作流 -
提升协作能力(可复现性)
假设我们面临以下场景:
笔者先从一个目标恶意软件样本入手。分析发现,该恶意软件家族将其有效载荷嵌入到合法的MSVC编译二进制文件中(本示例中是7z.exe
)。值得注意的是,它利用被篡改的 C 运行时_initterm
回调函数来部署恶意载荷。
二进制文件和库以调查变更。
-
自解密机制(类似“套娃”结构)
-
基本的反调试技术
-
加密字符串(已有的开源软件无法实现自动解密 )
-
混淆的跨模块调用
笔者之前提到,我们可能拥有多个恶意软件样本,但并不确定哪些属于我们关心的恶意软件家族。第一步,我们可以编写YARA规则来筛选感兴趣的样本。
import yara
from pathlib import Path
rules_src = """
import "pe"
// 通过 .reloc 配置和特定加载器签名匹配来识别可疑的二进制文件
rule demo_malware_family
{
strings:
$loader_iter = { 48 C7 C7 00 E0 48 00 80 ?? ?? 48 FF C7 E0 F8 EB D0 }
condition:
$loader_iter and
pe.is_pe and
pe.section_index(".reloc") and
pe.data_directories[5].size == 0 // IMAGE_DIRECTORY_ENTRY_BASERELOC
}
"""
def sample_match() -> Path:
rules = yara.compile(source=rules_src)
for f in Path('samples').iterdir():
if f.is_file() and rules.match(str(f)):
yield f
if __name__ == "__main__":
print(next(sample_match()))
# 输出示例:
# PS E:reautomate-demo> python .automate.py
# samplesdc59d01e485f2c2d0aa9176cda683dcf.exe
这里,我们使用YARA规则来匹配带有可疑.reloc
配置和特定加载器签名的恶意软件样本。这样,我们可以快速筛选出感兴趣的二进制文件,减少手动分析的工作量。
from x64dbg_automate import X64DbgClient
def seek_payload_entrypoint(client: X64DbgClient):
# 定位有效载荷所在的内存页
module_base, _ = client.eval_sync("mod.main()")
payload_mem_page = [mem for mem in client.memmap() if '.reloc' in mem.info
and mem.base_address > module_base][0]
# 在有效载荷的内存页上设置执行断点
client.set_memory_breakpoint(payload_mem_page.base_address, bp_type='x', restore=False)
client.go() # 运行到程序入口点
client.wait_until_stopped()
client.go() # 运行到内存断点
client.wait_until_stopped()
# 遍历 N 层解密过程,找到最终入口点
while True:
addr = client.get_reg('rip')
# 确保当前指令地址仍在解密函数内
if addr < payload_mem_page.base_address
or addr >= payload_mem_page.base_address + payload_mem_page.region_size:
raise ValueError('解密遍历失败,rip 超出预期范围')
# 如果当前指令不是 "mov rcx, XYZ",则找到入口点
ins = client.disassemble_at(addr)
if not ins.instruction.startswith('mov rcx,'):
break
# 否则,继续单步执行,遍历下一层
while True:
addr += ins.instr_size
ins = client.disassemble_at(addr)
if ins.instruction.startswith('jmp'):
client.set_breakpoint(addr, singleshoot=True)
client.go()
client.wait_until_stopped()
client.stepi()
break
# 现在可以自由分析有效载荷的入口点了
client.stepi()
if __name__ == "__main__":
# 启动 x64dbg 并附加到目标进程
client = X64DbgClient(r'E:rex64dbg_devreleasex64x64dbg.exe')
client.start_session(str(sample))
seek_payload_entrypoint(client)
client.detach_session()
自动化使我们能够断开客户端,并对有效负载本身进行进一步分析。由于已经完成了解密的繁重工作,我们现在可以毫无顾虑地进行调试,因为我们随时都能轻松回到关键位置。
在入口点检查样本时,可以发现一些可重复的特征,这些特征可以用于识别字符串和调用。
我们深知,反复逆向解析字符串和模块间调用是多么令人头疼的事情。通过可重复使用的自动化手段来标注字符串并标记调用,可以大大减轻这项工作的枯燥程度。
让我们来看一下在我们的样本中,这种自动化是如何实现的:
from x64dbg_automate.models import ReferenceViewRef
def annotate_strings_and_calls(client: X64DbgClient):
mem = client.virt_query(client.get_reg('rip'))
payload = client.read_memory(mem.base_address, mem.region_size)
refs = []
# 查找字符串解密器
obf_string_pattern = bytes.fromhex('49 09 C6 49 81 CE CC 00 00 00 EB')
for i in range(len(payload) - len(obf_string_pattern)):
if payload[i:i + len(obf_string_pattern)] == obf_string_pattern:
str_loc = i + len(obf_string_pattern) + 1
str_size = payload[str_loc - 1]
obf_str = bytearray(payload[str_loc:str_loc + str_size])
char_size = int(obf_str[-3:] == b'x00x00x00') + 1
for ix, i in enumerate(range(0, len(obf_str) - char_size, char_size)):
obf_str[i] = (obf_str[i] - (0xCC + (ix * 13))) & 0xFF
obf_str = obf_str.decode('utf-16-le' if char_size == 2 else 'utf-8').rstrip('x00')
client.set_comment_at(mem.base_address + str_loc - 2, f"解码字符串: '{obf_str}'")
refs.append(ReferenceViewRef(
address=mem.base_address + str_loc - 2,
text=f"解码字符串: '{obf_str}'"
))
# 在 GUI 中显示标注信息
client.gui_show_reference_view("混淆调用和字符串", refs)
if __name__ == "__main__":
annotate_strings_and_calls(client)
client.detach_session()
这样之后,我们就在应用数据库中收集了大量有用的注释和提示。而分析的恶意软件样本越多,自动化分析的价值就越大。
此外,我们还可以在参考视图(reference view)中看到对分析结果的摘要展示。
在此阶段单步执行有效负载时,发现了两种反调试机制。我们需要修改脚本,使其不仅能绕过解密过程,还能绕过反调试,从而实现无障碍的调试。
def bypass_anti_debug(client: X64DbgClient):
client.hide_debugger_peb() # 绕过 PEB 反调试
addr, _ = client.eval_sync('FindWindowW')
client.set_breakpoint(addr, singleshoot=True)
client.go()
client.wait_until_stopped()
if client.read_memory(client.get_reg('rdx'), 12) != 'x64dbg'.encode('utf-16-le'):
raise ValueError("FindWindowW 预期检测 x64dbg")
client.write_memory(client.get_reg('rdx'), 'zzz'.encode('utf-16-le'))
client.ret()
client.stepi()
if __name__ == "__main__":
bypass_anti_debug(client)
client.detach_session()
通过本次实践,笔者展示了x64dbg Automate的一些强大应用场景。笔者不仅仅使用脚本自动化了整个分析流程,使我们能够轻松进入复杂的执行状态。同时,还以可复现的方式记录了分析步骤,从而为重用、调整和协作提供了可能。
原文始发于微信公众号(山石网科安全技术研究院):x64dbg Automate--自动化分析的“利器”
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论