mirror of
https://github.com/mandiant/speakeasy.git
synced 2026-10-12 02:18:47 +00:00
120 lines
3.8 KiB
Python
120 lines
3.8 KiB
Python
import pytest
|
|
|
|
import speakeasy.winenv.arch as e_arch
|
|
from speakeasy import Speakeasy
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"bin_file,arch",
|
|
[
|
|
("dll_test_x86.dll.xz", e_arch.ARCH_X86),
|
|
("dll_test_x64.dll.xz", e_arch.ARCH_AMD64),
|
|
],
|
|
)
|
|
def test_registers_reset_between_runs(config, load_test_bin, bin_file, arch):
|
|
"""Each run must start with zeroed GP registers and DF=0."""
|
|
data = load_test_bin(bin_file)
|
|
se = Speakeasy(config=config, argv=[])
|
|
module = se.load_module(data=data)
|
|
|
|
run_register_snapshots = []
|
|
seen_runs = set()
|
|
|
|
if arch == e_arch.ARCH_X86:
|
|
gp_regs = [
|
|
e_arch.X86_REG_EAX,
|
|
e_arch.X86_REG_EBX,
|
|
e_arch.X86_REG_ECX,
|
|
e_arch.X86_REG_EDX,
|
|
e_arch.X86_REG_ESI,
|
|
e_arch.X86_REG_EDI,
|
|
]
|
|
else:
|
|
gp_regs = [
|
|
e_arch.AMD64_REG_RAX,
|
|
e_arch.AMD64_REG_RBX,
|
|
e_arch.AMD64_REG_RCX,
|
|
e_arch.AMD64_REG_RDX,
|
|
e_arch.AMD64_REG_RSI,
|
|
e_arch.AMD64_REG_RDI,
|
|
e_arch.AMD64_REG_R8,
|
|
e_arch.AMD64_REG_R9,
|
|
e_arch.AMD64_REG_R10,
|
|
e_arch.AMD64_REG_R11,
|
|
e_arch.AMD64_REG_R12,
|
|
e_arch.AMD64_REG_R13,
|
|
e_arch.AMD64_REG_R14,
|
|
e_arch.AMD64_REG_R15,
|
|
]
|
|
|
|
def code_hook(emu, addr, size):
|
|
run_id = id(se.emu.curr_run)
|
|
if run_id not in seen_runs:
|
|
seen_runs.add(run_id)
|
|
snapshot = {}
|
|
for reg in gp_regs:
|
|
snapshot[reg] = se.emu.reg_read(reg)
|
|
eflags = se.emu.reg_read(e_arch.X86_REG_EFLAGS)
|
|
snapshot["df"] = bool(eflags & (1 << 10))
|
|
run_register_snapshots.append(snapshot)
|
|
|
|
se.emu.add_code_hook(cb=code_hook)
|
|
se.run_module(module, all_entrypoints=True)
|
|
se.shutdown()
|
|
|
|
assert len(run_register_snapshots) > 1, "Need multiple runs to test isolation"
|
|
|
|
for i, snapshot in enumerate(run_register_snapshots[1:], 1):
|
|
if arch == e_arch.ARCH_X86:
|
|
for reg in gp_regs:
|
|
assert snapshot[reg] == 0, f"Run {i}: register should be 0, got {snapshot[reg]:#x}"
|
|
else:
|
|
non_arg_regs = [
|
|
e_arch.AMD64_REG_RAX,
|
|
e_arch.AMD64_REG_RBX,
|
|
e_arch.AMD64_REG_RSI,
|
|
e_arch.AMD64_REG_RDI,
|
|
e_arch.AMD64_REG_R10,
|
|
e_arch.AMD64_REG_R11,
|
|
e_arch.AMD64_REG_R12,
|
|
e_arch.AMD64_REG_R13,
|
|
e_arch.AMD64_REG_R14,
|
|
e_arch.AMD64_REG_R15,
|
|
]
|
|
for reg in non_arg_regs:
|
|
assert snapshot[reg] == 0, f"Run {i}: register should be 0, got {snapshot[reg]:#x}"
|
|
assert not snapshot["df"], f"Run {i}: DF should be clear"
|
|
|
|
|
|
@pytest.mark.parametrize("bin_file", ["dll_test_x86.dll.xz"])
|
|
def test_stack_cleared_between_runs(config, load_test_bin, bin_file):
|
|
"""Stack memory must be zeroed at the start of each run."""
|
|
data = load_test_bin(bin_file)
|
|
se = Speakeasy(config=config, argv=[])
|
|
module = se.load_module(data=data)
|
|
|
|
stack_snapshots = []
|
|
seen_runs = set()
|
|
SAMPLE_SIZE = 256
|
|
|
|
def code_hook(emu, addr, size):
|
|
run_id = id(se.emu.curr_run)
|
|
if run_id not in seen_runs:
|
|
seen_runs.add(run_id)
|
|
sp = se.emu.get_stack_ptr()
|
|
try:
|
|
data_below = se.emu.mem_read(sp - SAMPLE_SIZE, SAMPLE_SIZE)
|
|
stack_snapshots.append(bytes(data_below))
|
|
except Exception:
|
|
stack_snapshots.append(None)
|
|
|
|
se.emu.add_code_hook(cb=code_hook)
|
|
se.run_module(module, all_entrypoints=True)
|
|
se.shutdown()
|
|
|
|
assert len(stack_snapshots) > 1
|
|
|
|
for i, snap in enumerate(stack_snapshots[1:], 1):
|
|
if snap is not None:
|
|
assert snap == b"\x00" * SAMPLE_SIZE, f"Run {i}: stack below SP should be zeroed"
|