Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions speakeasy/speakeasy.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,9 +237,16 @@ def _auto_mount_target_directory(self, path: str) -> None:
for e in entries:
logger.debug(" %s -> %s", e["emu_path"], e["path"])

def load_module(self, path=None, data=None):
def load_module(self, path=None, data=None, filename=None):
"""
Load a module into the speakeasy emulator.

args:
path: file path to PE module
data: bytes object containing PE module
filename: override the module filename seen by the emulated program.
Useful when emulating from bytes and the malware checks its
own filename (e.g. via GetModuleFileName).
"""
if not path and not data:
raise SpeakeasyError("No emulation target supplied")
Expand All @@ -263,7 +270,7 @@ def load_module(self, path=None, data=None):

self._init_emulator(path=path, data=data)

return self.emu.load_module(path=path, data=data) # type: ignore[union-attr]
return self.emu.load_module(path=path, data=data, filename=filename) # type: ignore[union-attr]

def load_image(self, image):
"""
Expand Down Expand Up @@ -299,21 +306,22 @@ def run_module(self, module, all_entrypoints=False, emulate_children=False) -> N
else:
return self.emu.run_module(module=module, all_entrypoints=all_entrypoints) # type: ignore[no-any-return, union-attr]

def load_shellcode(self, fpath=None, arch=None, data=None) -> int:
def load_shellcode(self, fpath=None, arch=None, data=None, filename=None) -> int:
"""
Load a shellcode blob into emulation space

args:
fpath: file path containing shellcode blob
arch: Architecture (x86 | amd64) to load shellcode as
data: bytes object containing shellcode blob
filename: override the module filename seen by the emulated program
return:
Address of the loaded shellcode in the emulation space
"""
self._init_emulator(is_raw_code=True)
self.loaded_bins.append(fpath)

return self.emu.load_shellcode(fpath, arch, data=data) # type: ignore[no-any-return, union-attr]
return self.emu.load_shellcode(fpath, arch, data=data, filename=filename) # type: ignore[no-any-return, union-attr]

@check_init
def run_shellcode(self, sc_addr: int, stack_commit=0x4000, offset=0) -> None:
Expand Down
11 changes: 9 additions & 2 deletions speakeasy/windows/kernel.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,17 @@ def setup(self):
self.setup_kernel_mode()
self.setup_user_shared_data()

def load_module(self, path=None, data=None):
def load_module(self, path=None, data=None, filename=None):
from speakeasy.windows.loaders import PeLoader

if not data:
if filename:
file_name = ntpath.basename(filename)
mod_name = os.path.splitext(file_name)[0]
if not data:
assert path is not None
with open(path, "rb") as f:
data = f.read()
elif not data:
assert path is not None
file_name = os.path.basename(path)
mod_name = os.path.splitext(file_name)[0]
Expand Down
17 changes: 10 additions & 7 deletions speakeasy/windows/win32.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,8 +159,8 @@ def init_processes(self, processes):

self.processes.append(p)

def load_module(self, path=None, data=None):
self._init_name(path, data)
def load_module(self, path=None, data=None, filename=None):
self._init_name(path, data, filename=filename)

if not data:
assert path is not None
Expand Down Expand Up @@ -344,8 +344,11 @@ def run_module(self, module, all_entrypoints=False, emulate_children=False):

return

def _init_name(self, path, data=None):
if not data:
def _init_name(self, path, data=None, filename=None):
if filename:
self.file_name = ntpath.basename(filename)
self.mod_name = os.path.splitext(self.file_name)[0]
elif not data:
self.file_name = os.path.basename(path)
self.mod_name = os.path.splitext(self.file_name)[0]
else:
Expand All @@ -363,10 +366,10 @@ def emulate_module(self, path):
mod = self.load_module(path)
self.run_module(mod)

def load_shellcode(self, path, arch, data=None):
def load_shellcode(self, path, arch, data=None, filename=None):
from speakeasy.windows.loaders import ShellcodeLoader

self._init_name(path, data)
self._init_name(path, data, filename=filename)
if arch == "x86":
arch = _arch.ARCH_X86
elif arch in ("x64", "amd64"):
Expand All @@ -380,7 +383,7 @@ def load_shellcode(self, path, arch, data=None):

loader = ShellcodeLoader(data=data, arch=arch)
image = loader.make_image()
image.name = str(sc_hash)
image.name = self.mod_name if filename else str(sc_hash)
rtmod = self.load_image(image)
sc_addr = rtmod.base

Expand Down
12 changes: 12 additions & 0 deletions tests/test_filename_override.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
"""Tests for the ``filename`` parameter on load_module / load_shellcode."""

from speakeasy import Speakeasy


def test_load_module_filename_override(load_test_bin):
data = load_test_bin("dll_test_x86.dll.xz")
se = Speakeasy()
se.load_module(data=data, filename="malware.dll")

assert se.emu.file_name == "malware.dll"
assert se.emu.mod_name == "malware"
Loading