Initial commit

This commit is contained in:
drewvis
2020-08-21 17:05:28 -04:00
parent ce08c78714
commit 228134b8a8
132 changed files with 29652 additions and 1 deletions
+2
View File
@@ -0,0 +1,2 @@
[flake8]
max-line-length = 99
+106
View File
@@ -0,0 +1,106 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
.DS_Store
.vscode/
# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
*.swo
*.swp
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# pyenv
.python-version
# celery beat schedule file
celerybeat-schedule
# SageMath parsed files
*.sage.py
# dotenv
.env
# virtualenv
.venv
venv/
ENV/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
+45
View File
@@ -0,0 +1,45 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
FROM python:3.8-alpine as base
# Use this base to build and install everything (this will bloat the size of this build image)
FROM base as builder
RUN apk add --no-cache gcc python3-dev make bash postgresql-dev libc-dev linux-headers
RUN which python3.8
RUN ln -sf /usr/local/bin/python3.8 /usr/local/bin/python
ADD ./requirements.txt /
RUN python -m pip install --upgrade pip && python -m pip install -r requirements.txt
FROM base
COPY --from=builder /usr/local/lib/python3.8/site-packages/ /usr/local/lib/python3.8/site-packages/
# Create an app user so we don't run as root
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
# make the sandbox directory
RUN mkdir /sandbox
# Create the home directory
ENV APP_HOME=/app
RUN mkdir ${APP_HOME}
WORKDIR ${APP_HOME}
COPY ./speakeasy/ ${APP_HOME}/speakeasy
COPY ./run_speakeasy.py ${APP_HOME}
COPY ./setup.py ${APP_HOME}
COPY ./README.md ${APP_HOME}
COPY ./MANIFEST.in ${APP_HOME}
COPY ./examples/ ${APP_HOME}/examples
# Chown all the files to the app user
RUN chown -R appuser:appgroup ${APP_HOME}
RUN chown -R appuser:appgroup /sandbox
RUN python ./setup.py install
# Change to the app user
USER appuser
CMD /bin/sh
+9
View File
@@ -0,0 +1,9 @@
MIT License
Copyright (c) 2020-present, FireEye, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+6
View File
@@ -0,0 +1,6 @@
global-exclude *.pyc
recursive-include speakeasy/winenv/decoys/ *
recursive-include speakeasy/resources/ *
include speakeasy/configs/*.json
include speakeasy/config_schema.json
+215 -1
View File
@@ -1 +1,215 @@
# speakeasy
# Speakeasy
Speakeasy is a portable, modular, binary emulator designed to emulate Windows kernel and user mode malware.
Instead of attempting to perform dynamic analysis using an entire virtualized operating system, Speakeasy will emulate specific components of Windows. Specifically, by emulating operating system APIs, objects, running processes/threads, filesystems, and networks it should be possible to present an environment where samples can fully "execute". Samples can be easily emulated in a container or in cloud services which allow for great scalability of many samples to be simultaneously analyzed. Currently, Speakeasy supports both user mode and kernel mode Windows applications.
Before emulating, entry points are identified within the binary. For example, exported functions are all identified and emulated sequentially. Additionally, dynamic entry points (e.g. new threads, registered callbacks, IRP handlers) that are discovered at runtime are also emulated. The goal here is to have as much code coverage as possible during emulation. Events are logged on a per-entry-point basis so that functionality can be attributed to specific functions or exports.
Speakeasy is currently written entirely in Python 3 and relies on the [Unicorn emulation engine](https://github.com/unicorn-engine/unicorn) in order to emulate CPU instructions. The CPU emulation engine can be swapped out and there are plans to support other engines in the future.
APIs are emulated in Python code in order to handle their expected inputs and outputs in order to keep malware on their "happy path". These APIs and their structure should be consistent with the API documentation provided by Microsoft.
---
## Installation
Speakeasy can be executed in a docker container, as a stand-alone script, or in cloud services. The easiest method of installation is by first installing the required package dependencies, and then running the included setup.py script (replace "python3" with your current Python3 interpreter):
```console
cd <repo_base_dir>
python3 -m pip install -r requirements.txt
python3 setup.py install
```
A docker file is also included in order to build a docker image, however, Speakeasy's dependencies can be installed on the local system and run from Python directly.
---
### Running within a docker container
The included Dockerfile can be used to generate a docker image.
---
#### Building the docker image
1. Build the Docker image; the following commands will create a container with the tag named "my_tag":
```console
cd <repo_base_dir>
docker build -t "my_tag" .
```
2. Run the Docker image and create a local volume in `/sandbox`:
```console
docker run -v <path_containing_malware>:/sandbox -it "my_tag"
```
## Usage
---
### As a library
Speakeasy can be imported and used as a general purpose Windows emulation library. The main public interface named `Speakeasy` should be used when interacting with the framework. The lower level emulator objects can also be used, however their interfaces may change in the future and may lack documentation.
Below is a quick example of how to emulate a Windows DLL:
```python
import speakeasy
# Get a speakeasy object
se = speakeasy.Speakeasy()
# Load a DLL into the emulation space
module = se.load_module("myfile.dll")
# Emulate the DLL's entry point (i.e. DllMain)
se.run_module(module)
# Set up some args for the export
arg0 = 0x0
arg1 = 0x1
# Walk the DLLs exports
for exp in module.get_exports():
if exp.name == 'myexport':
# Call an export named 'myexport' and emulate it
se.call(exp.address, [arg0, arg1])
# Get the emulation report
report = se.get_report()
# Do something with the report; parse it or save it off for post-processing
```
For more examples, see the [examples](examples/) directory.
---
### As a standalone command line tool
For users who don't wish to programatically interact with the speakeasy framework as a library, a standalone script is provided to automatically emulate Windows binaries. Speakeasy can be invoked via the `run_speakeasy.py` script located within the base repo directory. This script will parse a specified PE and invoke the appropriate emulator (kernel mode or user mode). The script's parameters are shown below.
```
usage: run_speakeasy.py [-h] [-t TARGET] [-o OUTPUT]
[-p [PARAMS [PARAMS ...]]] [-c CONFIG] [-m] [-r]
[-a ARCH] [-d DUMP_PATH] [-q TIMEOUT]
[-z DROP_FILES_PATH] [-l MODULE_DIR]
Emulate a Windows binary with speakeasy
optional arguments:
-h, --help show this help message and exit
-t TARGET, --target TARGET
Path to input file to emulate
-o OUTPUT, --output OUTPUT
Path to output file to save report
-p [PARAMS [PARAMS ...]], --params [PARAMS [PARAMS ...]]
Commandline parameters to supply to emulated process
(e.g. main(argv)
-c CONFIG, --config CONFIG
Path to emulator config file
-m, --mem-tracing Enables memory tracing. This will log all memory
access by the sample but will impact speed
-r, --raw Attempt to emulate file as-is with no parsing (e.g.
shellcode
-a ARCH, --arch ARCH Force architecture to use during emulation (for multi-
architecture files or shellcode). Supported archs: [
x86 | amd64 ]
-d DUMP_PATH, --dump DUMP_PATH
Path to store compressed memory dump package
-q TIMEOUT, --timeout TIMEOUT
Emulation timeout in seconds (default 60 sec)
-z DROP_FILES_PATH, --dropped-files DROP_FILES_PATH
Path to store files created during emulation
-l MODULE_DIR, --module-dir MODULE_DIR
Path to directory containing loadable PE modules. When
modules are parsed or loaded by samples, PEs from this
directory will be loaded into the emulated address
space
```
---
### Examples
Emulating a Windows driver:
```console
user@mybox:~/speakeasy$ python3 run_speakeasy.py -t ~/drivers/MyDriver.sys
```
Emulating 32-bit Windows shellcode:
```console
user@mybox:~/speakeasy$ python3 run_speakeasy.py -t ~/sc.bin -r -a x86
```
Emulating 64-bit Windows shellcode and create a full memory dump:
```console
user@mybox:~/speakeasy$ python3 run_speakeasy.py -t ~/sc.bin -r -a x64 -d memdump.zip
```
---
## Configuration
Speakeasy uses configuration files that describe the environment that is presented to the emulated binaries. For a full description of these fields see the README [here](doc/configuration.md).
---
## Memory Management
Speakeasy implements a lightweight memory manager on top of the emulator engine’s memory management. Each chunk of memory allocated by malware is tracked and tagged so that meaningful memory dumps can be acquired. Being able to attribute activity to specific chunks of memory can prove to be extremely useful for analysts. Logging memory reads and writes to sensitive data structures can reveal the true intent of malware not revealed by API call logging which is particularly useful for samples such as rootkits.
---
## Speed
Because Speakeasy is written in Python, speed is an obvious concern. Transitioning between native code and Python is extremely expensive and should be done as little as possible. Therefore, the goal is to only execute Python code when it is absolutely necessary. By default, the only events handled in Python are memory access exceptions or Windows API calls. In order to catch Windows API calls and emulate them in Python, import tables are doped with invalid memory addresses so that Python code is only executed when import tables are accessed. Similar techniques are used for when shellcode accesses the export tables of DLLs loaded within the emulated address space of shellcode. By executing as little Python code as possible, reasonable speeds can be achieved while still allowing users to rapidly develop capabilities for the framework.
---
## Limitations
Since we do not rely on a physical OS to handle API calls, object and memory allocation, and I/O operations, these responsibilities fall to the emulator. Upon emulating multiple samples, users are likely to encounter samples that do not fully emulate. This can most likely be attributed to missing API handlers, specific OS implementation details, or environmental factors. For more details see [doc/limitations](doc/limitations.md).
---
## Module export parsing
Many malware samples such as shellcode will attempt to manually parse the export tables of PE modules in order resolve API function pointers. An attempt is made to make "decoy" export tables using the emulated function names currently supported but this may not be enough for some samples. The configuration files support two fields named `module_directory_x86` and `module_directory_x64`. These fields are directories that can contain DLLs or other modules that are loaded into the virtual address space of the emulated sample. There is also a command line option (`-l`) that can specify this directory at runtime. This can be useful for samples that do deep parsing of PE modules that are expected to be loaded within memory.
---
## Adding API handlers
Like most emulators, API calls made to the OS are handled by the framework. Emulated API handlers can be added by simply defining a function with the correct name in its corresponding emulated module. Depending on the outputs expected by the API, it may be sufficient enough to simply return a success code. The argument count must be specified in order for the stack to be cleaned up correctly. If no calling convention is specified, stdcall is assumed. The argument list is passed to the emulated function as raw integers.
Below is an example of an API handler for the HeapAlloc function in the kernel32 module.
```python
@apihook('HeapAlloc', argc=3)
def HeapAlloc(self, emu, argv, ctx={}):
'''
DECLSPEC_ALLOCATOR LPVOID HeapAlloc(
HANDLE hHeap,
DWORD dwFlags,
SIZE_T dwBytes
);
'''
hHeap, dwFlags, dwBytes = argv
chunk = self.heap_alloc(dwBytes, heap='HeapAlloc')
if chunk:
emu.set_last_error(windefs.ERROR_SUCCESS)
return chunk
```
---
## Further information
- [doc/configuration](doc/configuration.md)
- [doc/memory](doc/memory.md)
- [doc/reporting](doc/reporting.md)
- [doc/limitations](doc/limitations.md)
+83
View File
@@ -0,0 +1,83 @@
# Configuration
---
Speakeasy uses configuration files that describe the environment that is presented to emulated binaries. This includes OS specific fields such as OS version, environment variables, loaded user and system modules, and running processes. In addition, more complex OS components such as networking, file systems, object management and the registry are emulated in an attempt to simulate a full Windows operating system as much as possible. The fields in the config file can be customized to create easily interchangable simulated environments. For example, if a sample expects a specific process to be running at execution time, this can easily be added to a config file.
The specifics to how this simulation is presented to emulated binaries are described in JSON configuration files.
The fields supported by these configuration files are documented below:
* config_version
* The current version of the config file (currently: 1.1.0).
* emu_engine
* Backend CPU emulator to use. Currently, only the unicorn engine is supported.
* timeout
* Indicates the maximum number seconds to run the emulator.
* max_api_count
* Maximum number of API calls per execution run before continuing to the next run.
* analysis
* Identifies different analysis options (note: these may affect emulation speed).
* memory_tracing
* When true, memory access to each memory block is logged. This can identify activity such as hooking or PEB walking.
* strings
* Acquires strings from memory after emulation completion.
* keep_memory_on_free
* Denies free'ing of memory blocks so that all memory allocated by the sample can be analyzed.
* exceptions
* Identifies different exception handling options; specifically CPU exceptions
* dispatch_handlers
* If set to “true”, the emulator will attempt to dispatch exception handlers registered by the application (e.g. SEH handlers). Otherwise, emulation will stop when a CPU exception occurs.
* os_ver
* Defines the version of the operating system to present to emulated binaries
* This field allows the user to set version specific information such as major, minor, and build number
* current_dir
* Sets the currently working directory presented to the emulated sample
* command_line
* Sets the command line returned to the sample when queried
* env
* Sets environment variables presented to emulated binaries
* hostname
* Sets the Hostname returned when queried
* user
* Sets user specific options that are returned to samples
* name
* Sets the current user name
* is_admin
* Specifies whether the current user is identified as an administrator
* symlinks
* Allows the user to set symbolic links to devices (e.g. having “C:\” point to “\Device\HarddiskVolume1”
* filesystem
* An attempt is made to create a mock file system by allowing users to supply handlers when samples try to access files during emulation. This field allows users to specify what data will be returned when specific files are accessed, or what data is returned for certain extensions (e.g. .exe, .dll, .txt).
* Each handler entry describes how to handle specific file access scenarios and are described below:
* mode - Describes how to match on file accesses
* full_path
* Matches on access of a full file path: (e.g. C:\Temp\myfile.bin)
* by_ext
* Matches on any file with the specified file extension
* default
* If no other filter matches, this handler is used
* registry
* An attempt is made to create a mock registry by allowing users to supply handlers when samples try to access registry keys and values during emulation
* Each handler entry describes how to handle specific keys and value paths
* network
* Creates a mock network manager that allows emulated samples to think they are connected to a real network. This includes allowing the user to configure DNS responses, HTTP responses (including reply data), and socket connections
* dns
* names
* Specifies specific IP addresses returned during domain name queries
* txt
* Specifies data returned during DNS TXT queries
* http
* Describes how to respond to specific HTTP requests.
* winsock
* Describes how to respond to binary TCP/UDP traffic.
* processes
* Defines the processes that will be presented to the emulated sample when queried. The full path of the main process image along with command line arguments and base load address can be specified. This can be useful when emulated samples expect a specific process environment. When "is_main_exe" is set, this will be used as the main container process for shellcode or DLLs that do not normally have a process object created by default. Additionally, the session the process is being executed in can specified.
* modules
* modules_always_exist
* When "true" module loads will always succeed in order to satisfy dynamic library loads. Otherwise if unavailable, a "module not found" error will be returned. This can be used to set the emulation evironment to be similar to what samples expect.
* module_directory_x86
* Sets the directory that will be searched for when modules are dynamically loaded at runtime (for 32-bit binaries). This is often necessary for shellcode that will manually parse module export tables to find API functions. Arbitrary files can be placed in this directory that will be loaded when "LoadLibrary" style functions are called and the files will be mapped into the emulated address space.
* module_directory_x64
* This is the same as the "module_directory_x86" field except it applies to 64-bit binaries.
* system_modules
* Defines the system modules (e.g. drivers) that will be presented to the emulated sample when queried. The base load address for each module can be specified along with the full module path. In addition, a supplied module can be loaded into the emulated address space in order to support samples that manually parse PE headers for export resolution. Driver and device objects can also be attributed to system modules using their corresponding fields (see the default configuration for an example).
* user_modules
* Defines the user modules (e.g. DLLs) that will be presented to the emulated sample when queried. The base load address for each module can be specified along with the full module path. In addition, a supplied module can be loaded into the emulated address space in order to support samples that manually parse PE headers for export resolution. Note: the order is maintained when loaded into the PEB load order module list for older malware samples that expected the module list to be in a certain order (e.g. kernel32 to be located second in the linked list).
+19
View File
@@ -0,0 +1,19 @@
# Limitations
Since we do not rely on a physical OS to handle API calls, object and memory allocation, and I/O operations, these responsibilities fall to the emulator. Upon emulating multiple samples, users are likely to encounter samples that do not fully emulate. This can most likely be attributed to missing API handlers, specific OS implementation details, or environmental factors.
## Unimplemented APIs
Malware samples will call system APIs to interact with the OS. For example, on Windows systems samples will typically open files using the CreateFile* API set. This will invoke the emulator to search for a handler for this specific API. The API handlers are expected to handle expected inputs and outputs for the function. This will vary function by function depending on what the malware expects to get returned. For many API handlers, simply returning a success code will be sufficient to make the malware to continue execution. The goal here is to allow the malware to emulate until most of its functionality can be recorded.
When the system encounters an API that does not have a handler, the current run is recorded and stopped. Since we don't know the amount of arguments the unhandled API requires, the stack would be corrupt on return. This was a design choice in order to avoid potentially spurious report details. If there are other queued runs such as new threads or exports, they will still be emulated.
When unsupported APIs are encountered, the error message: `Unsupported API: <module_name>.<api_name>` is presented to the user and logged in the report.
For how to add API handlers that are not currently supported see [Adding API handlers](../README.md).
## Environmental requirements
Other reasons a sample may fail to fully emulate can be related to its expected environment. This may include files or registry keys that the malware expected to be present and weren't found or unexpected data returned by the emulated network. These types of "misses" can be configured in the supplied config file for each emulation run. (See [configuration](configuration.md) for more details.)
Environment details that are harder to deal with include opaque data structures that are expected to be preallocated by the OS, specific details about loaded code modules, or CPU specific data. These types of issues would require an update to the emulator code itself and need to be addressed on a case by case basis.
+25
View File
@@ -0,0 +1,25 @@
# Memory Management
---
Speakeasy implements a lightweight memory manager on top of the emulator engine’s memory management. Each chunk of memory allocated by malware is tracked and tagged so that meaningful memory dumps can be acquired. Being able to attribute activity to specific chunks of memory can prove to be extremely useful for analysts. Logging memory reads and writes to sensitive data structures can reveal the true intent of malware not revealed by API call logging which is particularly useful for samples such as rootkits.
## Memory Tagging
Each memory allocation has a tag associated with it so its origin can be determined with the following namespacing:
`<origin>.<object_type>.<object_name>.<base_address>`
Typically, the "origin" is set to either `emu` or `api`. The `emu` origin means the emulator engine itself allocated the memory block. Examples of memory allocated by emulator are memory mapped images, stacks, or core data structures (e.g. TEB and PEB). When a memory mapping has the origin of `api` it means it was allocated by an API handler invoked by the emulated sample. For example, if a malware sample allocates memory with the VirtualAlloc API, the resulting memory mapping will begin with the following tag: `api.VirtualAlloc`.
---
## Memory Freezing
Generally, memory will be freed when the sample calls APIs that free blocks of memory. There is a configuration option named `keep_memory_on_free` that will instruct the emulator to not free memory blocks when requested. This can be useful when memory contents are important for analysis and a sample attempts to obfucate or remove memory.
---
## Acquiring memory
Full memory dumps can be acquired by calling the top level `get_memory_dumps()` api or using the standalone script with the `-d` option.
---
## Memory Tracing
A configuration option named `memory_tracing` exists that when enabled will track each read, write and execution to each memory block. Note: this can greatly decrease performance since hooks are setup to track these events.
+330
View File
@@ -0,0 +1,330 @@
# Reporting
---
When samples have finished emulating, an emulation report can be obtained, containing all the events that were captured while executing. This report is currently formatted as a JSON object. The fields in the JSON report contain event types such as memory and object access, API calls with arguments, network activity, file system access, and other meta data.
## Report Format
---
* path
* Local path of the file that was emulated
* sha256
* Hash of the file that was emulated
* size
* Size of the file that was emulated
* arch
* Architecture of the emulated file
* emu_version
* Version of the emulator that generated the report
* os_run
* The operating system version that was presented during emulation
* report_version
* Version of the format of the generated report
* emulation_total_runtime
* Total time spent emulating the sample in seconds
* timestamp
* Time at which the sample was emulated in epoch
* strings
* static
* Strings found in the static image of the emulated sample
* in_memory
* Strings found at runtime in memory (not found in the static strings)
* entry_points
* List of entry points that were emulated (in order). This includes all execution primitives such as threads, child processes, or callbacks.
* ep_type
* The type of entry point that was logged. This can be any execution primitive including an injected thread, a driver IRP handler, or a DLL export.
* start_addr
* The virtual address where emulation began
* apihash
* As API functions are emulated their names are hashed. This can potentially be used to find similar entry points across samples regardless of architecture.
* apis
* List of API calls made by the emulated sample
* pc
* The return address where emulation will resume after the API call returns
* api_name
* Name of the module name and API name that was emulated
* args
* List of arguments passed to the emulated API call
* ret_val
* Value returned to the caller
* ret_val
* Value returned by the entry point
* error
* Any errors during emulation are logged here included a Python stack trace if applicable. Additionally, if the emulated sample encounters a memory access error, the CPU register and stack state are logged here.
* network_events
* DNS name queries and network traffic are logged here
* process_events
* When samples attempt to interact with other processes (e.g. reading/writing their virtual memory, injecting threads) these events are logged here.
* file_access
* When samples access the emulated file system, these events are tracked here.
* registry_access
* When samples access the emulated registry, these events are tracked here.
* handled_exceptions
* Exceptions that are handled by the emulated sample are logged here (e.g. SEH handlers that are dispatched)
* mem_access
* When memory tracing is enabled, log all access to each memory block
* tag
* Tag of the memory block that was accessed
* base
* Base virtual address of the memory block that was accessed
* reads
* Number of times the memory block was read from
* writes
* Number of times the memory block was written to
* execs
* Number of times the memory block was executed from
* sym_accesses
* When memory tacing is enabled, log all access to symbols within the emulation enviroment. For example, when shellcode is manually parsing an export table to call an API function, the function that was accessed will be logged.
* dropped_files
* Files that are written to disk are logged here.
* dynamic_code_segments
* When code is dynamically allocated and executed at runtime, these segments are logged here. This can be useful in identifying samples unpacking themselves.
Below is an example output JSON report of a shellcode payload that executes a reverse shell:
```json
{
"path": "/sandbox/sc.bin",
"sha256": "01a1b13281b7fb50740f945b4f205bbdf32399844c2264bbe027dd6447f29e40",
"size": 324,
"arch": "x86",
"mem_tag": "emu.shellcode.01a1b13281b7fb50740f945b4f205bbdf32399844c2264bbe027dd6447f29e40",
"emu_version": "1.4.4",
"os_run": "windows.6_1",
"report_version": "1.1.0",
"emulation_total_runtime": 1.084,
"timestamp": 1596556067,
"strings": {
"static": {
"ansi": [
";}$u",
"D$$[[aYZQ",
"]h32",
"hws2_ThLw&",
"TPh)",
"PPPP@P@Ph",
"hcmd",
"WWW1",
"DTPVVVFVNVVSVhy"
],
"unicode": []
},
"in_memory": {
"ansi": [
"ws2_32"
],
"unicode": []
}
},
"entry_points": [
{
"ep_type": "shellcode",
"start_addr": "0x1000",
"ep_args": [
"0x41420000",
"0x41421000",
"0x41422000",
"0x41423000"
],
"instr_count": 345329,
"apihash": "3b818f37253eb7d32c030b29fea97caf76c3199f9263fbca93268c096794605f",
"apis": [
{
"pc": "0x109b",
"api_name": "kernel32.LoadLibraryA",
"args": [
"ws2_32"
],
"ret_val": "0x78c00000"
},
{
"pc": "0x10ab",
"api_name": "ws2_32.WSAStartup",
"args": [
"0x190",
"0x1203e4c"
],
"ret_val": "0x0"
},
{
"pc": "0x10ba",
"api_name": "ws2_32.WSASocketA",
"args": [
"AF_INET",
"SOCK_STREAM",
"0x0",
"0x0",
"0x0",
"0x0"
],
"ret_val": "0x4"
},
{
"pc": "0x10d4",
"api_name": "ws2_32.connect",
"args": [
"0x4",
"127.0.0.1:4444",
"0x10"
],
"ret_val": "0x0"
},
{
"pc": "0x1117",
"api_name": "kernel32.CreateProcessA",
"args": [
"0x0",
"cmd",
"0x0",
"0x0",
"0x1",
"0x0",
"0x0",
"0x0",
"0x1203df8",
"0x1203de8"
],
"ret_val": "0x1"
},
{
"pc": "0x1125",
"api_name": "kernel32.WaitForSingleObject",
"args": [
"0x220",
"0xffffffff"
],
"ret_val": "0x0"
},
{
"pc": "0x1131",
"api_name": "kernel32.GetVersion",
"args": [],
"ret_val": "0x1db10106"
},
{
"pc": "0x1144",
"api_name": "kernel32.ExitProcess",
"args": [
"0x0"
],
"ret_val": "0x0"
}
],
"ret_val": "0x770002ac",
"error": {},
"network_events": {
"dns": [],
"traffic": [
{
"server": "127.0.0.1",
"proto": "tcp",
"port": 4444,
"method": "winsock.connect",
"type": "connect"
}
]
},
"process_events": [
{
"event": "create",
"pid": 1208,
"path": "C:\\Windows\\system32\\cmd",
"cmdline": "cmd"
}
],
"mem_access": [
{
"tag": "emu.shellcode.01a1b13281b7fb50740f945b4f205bbdf32399844c2264bbe027dd6447f29e40.0x1000",
"base": "0x1000",
"reads": 0,
"writes": 0,
"execs": 345329
},
{
"tag": "emu.stack.0x1200000",
"base": "0x1200000",
"reads": 7855,
"writes": 210,
"execs": 0
},
{
"tag": "emu.segment.fs.0x4000",
"base": "0x4000",
"reads": 8,
"writes": 0,
"execs": 0
},
{
"tag": "emu.struct.PEB.0x1150",
"base": "0x1150",
"reads": 8,
"writes": 0,
"execs": 0
},
{
"tag": "emu.struct.PEB_LDR_DATA.0x6000",
"base": "0x6000",
"reads": 8,
"writes": 0,
"execs": 0
},
{
"tag": "emu.struct.LDR_DATA_TABLE_ENTRY.0x11d0",
"base": "0x11d0",
"reads": 176,
"writes": 0,
"execs": 0
},
{
"tag": "emu.module.main.0x400000",
"base": "0x400000",
"reads": 16,
"writes": 0,
"execs": 0
},
{
"tag": "emu.struct.LDR_DATA_TABLE_ENTRY.0x1290",
"base": "0x1290",
"reads": 235,
"writes": 0,
"execs": 0
},
{
"tag": "emu.module.kernel32.0x77000000",
"base": "0x77000000",
"reads": 53262,
"writes": 0,
"execs": 0
},
{
"tag": "emu.struct.LDR_DATA_TABLE_ENTRY.0x1350",
"base": "0x1350",
"reads": 72,
"writes": 0,
"execs": 0
},
{
"tag": "emu.module.ntdll.0x7c000000",
"base": "0x7c000000",
"reads": 8892,
"writes": 0,
"execs": 0
},
{
"tag": "emu.struct.LDR_DATA_TABLE_ENTRY.0x1410",
"base": "0x1410",
"reads": 75,
"writes": 0,
"execs": 0
},
{
"tag": "emu.module.ws2_32.0x78c00000",
"base": "0x78c00000",
"reads": 3951,
"writes": 0,
"execs": 0
}
],
"dynamic_code_segments": []
}
]
```
+58
View File
@@ -0,0 +1,58 @@
import argparse
import speakeasy
class DbgView(speakeasy.Speakeasy):
'''
Print debug port prints to the console
'''
def __init__(self, debug=False):
super(DbgView, self).__init__(debug=debug)
def debug_print_hook(self, emu, api_name, func, params):
# Call the DbgPrint* function and print the formatted string to the console
rv = func(params)
formatted_str = params[0]
print(formatted_str)
return rv
def debug_printex_hook(self, emu, api_name, func, params):
# Call the DbgPrintEx function and print the formatted string to the console
rv = func(params)
formatted_str = params[2]
print(formatted_str)
return rv
def main(args):
dbg = DbgView()
module = dbg.load_module(args.file)
dbg.add_api_hook(dbg.debug_print_hook,
'ntoskrnl',
'DbgPrint'
)
dbg.add_api_hook(dbg.debug_printex_hook,
'ntoskrnl',
'DbgPrintEx'
)
# Emulate the module
dbg.run_module(module, all_entrypoints=True)
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Print debug port prints to the console')
parser.add_argument('-f', '--file', action='store', dest='file',
required=True, help='Path of driver to emulate')
args = parser.parse_args()
main(args)
+110
View File
@@ -0,0 +1,110 @@
import argparse
import speakeasy
import logging
def get_logger():
"""
Get the default logger for speakeasy
"""
logger = logging.getLogger('emu_dll')
if not logger.handlers:
sh = logging.StreamHandler()
logger.addHandler(sh)
logger.setLevel(logging.INFO)
return logger
def hook_messagebox(emu, api_name, func, params):
"""
API hook that is installed to intercept MessageBox calls as an example
Args:
api_name: The full name including module of the hooked API
func: the real emulated function provided by the framework
Users can call this by passing in "params" whenever they choose
params: the argments passed to the function
"""
# Call the MessageBox function and print its text string data
rv = func(params)
logger = get_logger()
hWnd, lpText, lpCaption, uType = params
msg = '%s text: %s' % (api_name, lpText)
logger.log(logging.INFO, msg)
# Lets read where the stack pointer is
logger.log(logging.INFO, 'Stack pointer is at: 0x%x' % (emu.reg_read('esp')))
return rv
def hook_mem_write(emu, access, address, size, value, ctx):
"""
Hook that is called whenever memory is written to
Args:
access: memory access requested
address: Memory address that is being written to
size: Size of the data being written
value: data that is being written to "address"
"""
# For a quick example, lets just log writes that occur to the stack
for mm in emu.get_mem_maps():
if mm.tag and mm.tag.startswith('emu.stack'):
start = mm.get_base()
end = start + mm.get_size()
if start < address < end:
logger = get_logger()
# Get the assembly instruction that did the write
mnem, op, instr = emu.disasm(emu.reg_read('eip'), 0x20)
msg = 'Stack written to: instr: %s addr:0x%x' % (instr, address)
logger.log(logging.INFO, msg)
return
def main(args):
# Init the speakeasy object, an optional logger can be supplied
se = speakeasy.Speakeasy(logger=get_logger())
module = se.load_module(args.file)
# Begin emulating the DLL at its defined entry point.
# If all_entrypoints is set to True, all exports will be emulated sequentially.
# In this example, lets just run the main entry point and call exports manually.
se.run_module(module, all_entrypoints=False)
# Hook user32!MessageBoxA/W and call our function; wild cards are supported here so we can
# hook both versions with a single hook
se.add_api_hook(hook_messagebox,
'user32',
'MessageBox*'
)
# Hook all memory writes as an example
se.add_mem_write_hook(hook_mem_write)
# Set up some fake args
arg0 = 0x0
arg1 = 0x1
# Walk the DLLs exports
for exp in module.get_exports():
if exp.name == 'emu_test_one':
# Call an export named 'emu_test_one' and emulate it
se.call(exp.address, [arg0, arg1])
if exp.name == 'emu_test_two':
# Call an export named 'emu_test_two' and emulate it
se.call(exp.address, [arg0, arg1])
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Emulate a DLL and manually call its exports')
parser.add_argument('-f', '--file', action='store', dest='file',
required=True, help='Path of DLL to emulate')
args = parser.parse_args()
main(args)
+69
View File
@@ -0,0 +1,69 @@
import argparse
import speakeasy
import logging
def get_logger():
"""
Get the default logger for speakeasy
"""
logger = logging.getLogger('emu_exe')
if not logger.handlers:
sh = logging.StreamHandler()
logger.addHandler(sh)
logger.setLevel(logging.INFO)
return logger
def hook_ntreadfile(emu, api_name, func, params):
"""
API hook that is installed to intercept MessageBox calls as an example
Args:
api_name: The full name including module of the hooked API
func: the real emulated function provided by the framework
Users can call this by passing in "params" whenever they choose
params: the argments passed to the function
"""
# Call the NtReadFile function
rv = func(params)
logger = get_logger()
hnd, evt, apcf, apcc, ios, buf, size, offset, key = params
# Read the buffer containing the file data
data = emu.mem_read(buf, size)
logger.log(logging.INFO, data)
# Write something to the buffer instead
emu.mem_write(buf, b'A' * size)
return rv
def main(args):
# Init the speakeasy object, an optional logger can be supplied
se = speakeasy.Speakeasy(logger=get_logger())
# Hook ntdll!NtReadFile so we can modify the returned buffer
se.add_api_hook(hook_ntreadfile,
'ntdll',
'NtReadFile'
)
# Load the module into the emulation space
module = se.load_module(args.file)
# Begin emulating the EXE at its defined entry point.
se.run_module(module)
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Emulate an EXE and call its entry point')
parser.add_argument('-f', '--file', action='store', dest='file',
required=True, help='Path of EXE to emulate')
args = parser.parse_args()
main(args)
+67
View File
@@ -0,0 +1,67 @@
import argparse
import speakeasy
class UpxUnpacker(speakeasy.Speakeasy):
'''
Generic UPX unpacker class
'''
def __init__(self, output_path, debug=False):
super(UpxUnpacker, self).__init__(debug=debug)
self.output_path = output_path
self.base_addr = 0
def set_dump_range(self, base, start, end):
# Set some variables used to identify when a dump should occur
self.base_addr = base
self.start_addr = start
self.end_addr = end
def save_unpacked_file(self):
# Save the module to disk in it's unpacked state
with open(self.output_path, "wb") as up:
mm = self.get_address_map(self.base_addr)
up.write(self.mem_read(mm.get_base(), mm.get_size()))
# TODO: Fixup the import table after dumping
def code_hook(self, emu, addr, size, ctx):
if self.end_addr >= addr >= self.start_addr:
print('[*] Section hop signature hit, dumping module')
self.save_unpacked_file()
self.stop()
return True
def main(args):
unpacker = UpxUnpacker(args.outfile)
# Load the module
module = unpacker.load_module(args.file)
base = module.get_base()
print('[*] Unpacking module with section hop')
# Get the section info for "UPX0" to detect the section hop
upx0 = module.get_section_by_name('UPX0')
start = base + upx0.VirtualAddress
end = start + upx0.Misc_VirtualSize
unpacker.set_dump_range(base, start, end)
# Add the callback
unpacker.add_code_hook(unpacker.code_hook)
# Emulate the module
unpacker.run_module(module)
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Generic UPX unpacker')
parser.add_argument('-f', '--file', action='store', dest='file',
required=True, help='Path of UPX file to unpack')
parser.add_argument('-o', '--outfile', action='store', dest='outfile',
required=True, help='Path to save unpacked file')
args = parser.parse_args()
main(args)
+259
View File
@@ -0,0 +1,259 @@
import argparse
import speakeasy
import speakeasy.winenv.defs.wdf as wdf
import speakeasy.winenv.defs.nt.ddk as ddk
import speakeasy.winenv.defs.usb as usbdefs
import speakeasy.winenv.defs.nt.ntoskrnl as ntos
import speakeasy.winenv.defs.registry.reg as reg
IOCTL_IS_SUPER_SPEED_SUPPORTED = 0x49104b
class UsbEmu(speakeasy.Speakeasy):
'''
WDF USB Emulator
'''
def __init__(self, debug=False):
super(UsbEmu, self).__init__(debug=debug)
self.device_add_func = None
self.prepare_hardware_func = None
self.dev_handle = None
self.ddesc = usbdefs.USB_DEVICE_DESCRIPTOR()
self.cdesc = usbdefs.USB_CONFIGURATION_DESCRIPTOR()
self.idesc = usbdefs.USB_INTERFACE_DESCRIPTOR()
self.endpoints = [usbdefs.USB_ENDPOINT_DESCRIPTOR() for i in range(3)]
self.pEvtIoRead = 0
self.pEvtIoWrite = 0
def init_usb_descriptors(self):
for i, ep in enumerate(self.endpoints):
ep.bLength = 7
ep.bDescriptorType = 5
self.endpoints[0].bEndpointAddress = 0x01 | usbdefs.USB_DIR_IN
self.endpoints[0].bmAttributes = usbdefs.USB_ENDPOINT_TYPE_INTERRUPT
self.endpoints[0].wMaxPacketSize = 1
self.endpoints[0].bInterval = 2
self.endpoints[1].bEndpointAddress = 0x06 | usbdefs.USB_DIR_OUT
self.endpoints[1].bmAttributes = usbdefs.USB_ENDPOINT_TYPE_BULK
self.endpoints[1].wMaxPacketSize = 512
self.endpoints[1].bInterval = 2
self.endpoints[2].bEndpointAddress = 0x08 | usbdefs.USB_DIR_IN
self.endpoints[2].bmAttributes = usbdefs.USB_ENDPOINT_TYPE_BULK
self.endpoints[2].wMaxPacketSize = 512
self.endpoints[2].bInterval = 2
ifd = self.idesc
ifd.bLength = 9
ifd.bDescriptorType = usbdefs.USB_INTERFACE_DESCRIPTOR_TYPE
ifd.bInterfaceNumber = 0
ifd.bAlternateSetting = 0
ifd.bNumEndpoints = len(self.endpoints)
ifd.bInterfaceClass = 255
cd = self.cdesc
cd.bLength = 9
cd.bDescriptorType = usbdefs.USB_CONFIGURATION_DESCRIPTOR_TYPE
cd.wTotalLength = ifd.sizeof() + (self.endpoints[0].sizeof() * len(self.endpoints))
cd.bNumInterfaces = 1
cd.bConfigurationValue = 1
cd.MaxPower = 250
dd = self.ddesc
dd.bLength = 18
dd.bDescriptorType = usbdefs.USB_DEVICE_DESCRIPTOR_TYPE
dd.idVendor = 0x0547
dd.idProduct = 0x1002
dd.bNumConfigurations = 1
def emit_config_descriptor(self):
data = self.cdesc.get_bytes()
data += self.idesc.get_bytes()
for ep in self.endpoints:
data += ep.get_bytes()
return data
def wdf_driver_create_hook(self, emu, api_name, func, params):
DriverGlobals, DriverObject, RegistryPath, DriverAttributes, DriverConfig, Driver = params
config = self.mem_cast(wdf.WDF_DRIVER_CONFIG(emu.get_ptr_size()), DriverConfig)
self.device_add_func = config.EvtDriverDeviceAdd
rv = func(params)
return rv
def wdf_device_set_pnp_hooks(self, emu, api_name, func, params):
DriverGlobals, DeviceInit, PnpPowerEventCallbacks = params
rv = func(params)
callbacks = wdf.WDF_PNPPOWER_EVENT_CALLBACKS(emu.get_ptr_size())
callbacks = self.mem_cast(callbacks, PnpPowerEventCallbacks)
self.prepare_hardware_func = callbacks.EvtDevicePrepareHardware
return rv
def wdf_get_usb_config_descriptor(self, emu, api_name, func, params):
DriverGlobals, UsbDevice, ConfigDescriptor, ConfigDescriptorLength = params
rv = ddk.STATUS_BUFFER_TOO_SMALL
buf_len = self.mem_read(ConfigDescriptorLength, 2)
buf_len = int.from_bytes(buf_len, 'little')
cfg = self.emit_config_descriptor()
if buf_len < len(cfg):
self.mem_write(ConfigDescriptorLength, (len(cfg)).to_bytes(2, 'little'))
return rv
if ConfigDescriptor:
self.mem_write(ConfigDescriptor, cfg)
self.mem_write(ConfigDescriptorLength, (len(cfg)).to_bytes(2, 'little'))
rv = func(params)
return rv
def wdf_get_usb_device_descriptor(self, emu, api_name, func, params):
DriverGlobals, UsbDevice, UsbDeviceDescriptor = params
self.mem_write(UsbDeviceDescriptor, self.ddesc.get_bytes())
return
def wdf_get_usb_info(self, emu, api_name, func, params):
DriverGlobals, UsbDevice, Information = params
info = wdf.WDF_USB_DEVICE_INFORMATION(emu.get_ptr_size())
info = self.mem_cast(info, Information)
# Set the traits to inform the driver we are a highspeed device
info.Traits |= wdf.WDF_USB_DEVICE_TRAIT_AT_HIGH_SPEED
self.mem_write(Information, info.get_bytes())
rv = func(params)
return rv
def wdf_queue_create_hook(self, emu, api_name, func, params):
DriverGlobals, Device, Config, QueueAttributes, Queue = params
rv = func(params)
queue_config = wdf.WDF_IO_QUEUE_CONFIG(emu.get_ptr_size())
queue_config = self.mem_cast(queue_config, Config)
if not self.pEvtIoRead:
self.pEvtIoRead = queue_config.EvtIoRead
if not self.pEvtIoWrite:
self.pEvtIoWrite = queue_config.EvtIoWrite
return rv
def iof_call_driver(self, emu, api_name, func, params):
DeviceObject, pIrp = params
rv = ddk.STATUS_SUCCESS
_irp = ntos.IRP(emu.get_ptr_size())
_irp = self.mem_cast(_irp, pIrp)
stack = _irp.Tail.Overlay.CurrentStackLocation
csl = ntos.IO_STACK_LOCATION(emu.get_ptr_size())
csl = self.mem_cast(csl, stack - csl.sizeof())
if IOCTL_IS_SUPER_SPEED_SUPPORTED == csl.Parameters.DeviceIoControl.IoControlCode:
rv = ddk.STATUS_NOT_SUPPORTED
_irp.IoStatus.Status = rv
self.mem_write(pIrp, _irp.get_bytes())
else:
# Call the API handler
rv = func(params)
_irp.IoStatus.Status = 0
self.mem_write(pIrp, _irp.get_bytes())
return rv
def main(args):
usb = UsbEmu()
# Load the module
module = usb.load_module(args.file)
# Set the API hooks so we initialize everything
usb.add_api_hook(usb.wdf_driver_create_hook,
'wdfldr',
'WdfDriverCreate'
)
usb.add_api_hook(usb.wdf_queue_create_hook,
'wdfldr',
'WdfIoQueueCreate'
)
usb.add_api_hook(usb.wdf_device_set_pnp_hooks,
'wdfldr',
'WdfDeviceInitSetPnpPowerEventCallbacks'
)
usb.add_api_hook(usb.wdf_get_usb_device_descriptor,
'wdfldr',
'WdfUsbTargetDeviceGetDeviceDescriptor'
)
usb.add_api_hook(usb.wdf_get_usb_config_descriptor,
'wdfldr',
'WdfUsbTargetDeviceRetrieveConfigDescriptor'
)
usb.add_api_hook(usb.wdf_get_usb_info,
'wdfldr',
'WdfUsbTargetDeviceRetrieveInformation'
)
usb.add_api_hook(usb.iof_call_driver,
'ntoskrnl',
'IofCallDriver'
)
# Setup out USB descriptors
usb.init_usb_descriptors()
# Emulate the module
usb.run_module(module)
param_key = usb.get_registry_key(path='HKLM\\System\\CurrentControlSet\\Services\\*\\Parameters') # noqa
param_key.create_value('MaximumTransferSize', reg.REG_DWORD, 65536)
# Call the AddDevice function to get us setup
if usb.device_add_func:
usb.call(usb.device_add_func, [0x41414142, 0x42424242])
# Call the prepare hardware callback
if usb.prepare_hardware_func:
usb.call(usb.prepare_hardware_func, [0, 0x42424242, 0x43434343])
profile = usb.get_json_report()
print(profile)
# TODO: call the EvtRead/Write
print('Found EvtIoRead at 0x%x' % (usb.pEvtIoRead))
print('Found EvtIoWrite at 0x%x' % (usb.pEvtIoWrite))
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='USB driver emulator')
parser.add_argument('-f', '--file', action='store', dest='file',
required=True, help='Path of USB driver to emulate')
args = parser.parse_args()
main(args)
+5
View File
@@ -0,0 +1,5 @@
pefile
capstone
lznt1
unicorn==1.0.2rc4
jsonschema
+216
View File
@@ -0,0 +1,216 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
import os
import json
import time
import logging
import argparse
import multiprocessing as mp
import speakeasy
from speakeasy import Speakeasy
import speakeasy.winenv.arch as e_arch
def get_logger():
"""
Get the default logger for speakeasy
"""
logger = logging.getLogger('speakeasy')
if not logger.handlers:
sh = logging.StreamHandler()
logger.addHandler(sh)
logger.setLevel(logging.INFO)
return logger
def emulate_binary(q, exit_event, fpath, cfg, argv, do_raw, arch='', drop_path='', dump_path=''):
"""
Setup the binary for emulation
"""
logger = get_logger()
try:
report = None
se = Speakeasy(config=cfg, logger=logger, argv=argv, exit_event=exit_event)
if do_raw:
arch = arch.lower()
if arch == 'x86':
arch = e_arch.ARCH_X86
elif arch in ('x64', 'amd64'):
arch = e_arch.ARCH_AMD64
else:
raise Exception('Unsupported architecture: %s' % arch)
sc_addr = se.load_shellcode(fpath, arch)
se.run_shellcode(sc_addr)
else:
module = se.load_module(fpath)
se.run_module(module, all_entrypoints=True)
finally:
report = se.get_json_report()
q.put(report)
# If a memory dump was requested, do it now
if dump_path:
data = se.create_memdump_archive()
logger.info('* Saving memory dump archive to %s' % (dump_path))
with open(dump_path, 'wb') as f:
f.write(data)
if drop_path:
data = se.create_file_archive()
if data:
logger.info('* Saving dropped files archive to %s' % (drop_path))
with open(drop_path, 'wb') as f:
f.write(data)
else:
logger.info('* No dropped files found')
class Main(object):
"""
Main class for emulation of Windows shellcode, user mode, and kernel mode binaries
"""
def __init__(self, parser):
args = parser.parse_args()
self.target = args.target
self.output = args.output
self.dump_path = args.dump_path
self.drop_files_path = args.drop_files_path
self.config_path = args.config
self.cfg = None
self.do_raw = args.do_raw
self.do_memtrace = args.do_memtrace
self.module_dir = args.module_dir
self.arch = args.arch
self.timeout = 0
self.argv = args.params
self.logger = get_logger()
if not self.config_path:
self.config_path = './speakeasy/configs/default.json'
if not os.path.isfile(self.config_path):
self.config_path = os.path.join(os.path.dirname(speakeasy.__file__),
'configs', 'default.json')
if not os.path.isfile(self.config_path):
parser.print_help()
self.logger.error('[-] No emulator config file supplied')
return
with open(self.config_path, 'r') as f:
self.cfg = json.load(f)
if args.timeout:
self.timeout = args.timeout
self.cfg.update({'timeout': self.timeout})
self.cfg.update({'max_api_count': self.timeout * 500})
else:
self.timeout = self.cfg.get('timeout', 0)
if self.do_memtrace:
analysis = self.cfg.get('analysis', {})
# Override and enable memory tracing
if analysis:
analysis['memory_tracing'] = True
else:
self.cfg.update({'analysis': {'memory_tracing': True}})
if self.module_dir:
modules = self.cfg.get('modules', {})
if modules:
modules['module_directory_x86'] = self.module_dir
modules['module_directory_x64'] = self.module_dir
else:
self.cfg.update({'modules': {'module_directory_x86': self.module_dir,
'module_directory_x64': self.module_dir}})
if self.target and not os.path.isfile(self.target):
parser.print_help()
self.logger.error('[-] Target file not found: %s' % (self.target))
return
if not self.target:
parser.print_help()
self.logger.error('[-] No target file supplied')
return
q = mp.Queue()
evt = mp.Event()
# We are using a child process here so we can maintain absolute control over its execution
p = mp.Process(target=emulate_binary, args=(q, evt, args.target, self.cfg,
self.argv, self.do_raw, self.arch,
self.drop_files_path, self.dump_path))
p.start()
report = None
start_time = time.time()
while True:
if self.timeout and self.timeout < (time.time() - start_time):
evt.set()
self.logger.error('* Child process timeout reached after %d seconds' %
(self.timeout))
report = q.get(5)
try:
report = q.get(timeout=1)
break
except mp.queues.Empty:
if not p.is_alive():
break
except KeyboardInterrupt:
evt.set()
self.logger.error('\n* User exited')
report = q.get(5)
break
self.logger.info('* Finished emulating')
if report:
if self.output:
self.logger.info('* Saving emulation report to %s' % (self.output))
with open(self.output, 'w') as f:
f.write(report)
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Emulate a Windows binary with speakeasy')
parser.add_argument('-t', '--target', action='store', dest='target',
required=False, help='Path to input file to emulate')
parser.add_argument('-o', '--output', action='store', dest='output',
required=False, help='Path to output file to save report')
parser.add_argument('-p', '--params', action='store', default=[],
nargs='*', dest='params', required=False,
help='Commandline parameters to supply to emulated '
'process (e.g. main(argv))')
parser.add_argument('-c', '--config', action='store', dest='config',
required=False, help='Path to emulator config file')
parser.add_argument('-m', '--mem-tracing', action='store_true', dest='do_memtrace',
required=False, help='Enables memory tracing.\n'
'This will log all memory access by the '
'sample but will impact speed')
parser.add_argument('-r', '--raw', action='store_true', dest='do_raw',
required=False, help='Attempt to emulate file as-is '
'with no parsing (e.g. shellcode)')
parser.add_argument('-a', '--arch', action='store', dest='arch',
required=False,
help='Force architecture to use during emulation (for '
'multi-architecture files or shellcode). '
'Supported archs: [ x86 | amd64 ]')
parser.add_argument('-d', '--dump', action='store', dest='dump_path',
required=False, help='Path to store compressed memory dump package')
parser.add_argument('-q', '--timeout', action='store', dest='timeout', type=int,
required=False, help='Emulation timeout in seconds (default 60 sec)')
parser.add_argument('-z', '--dropped-files', action='store', dest='drop_files_path',
required=False, help='Path to store files created during emulation')
parser.add_argument('-l', '--module-dir', action='store', dest='module_dir',
required=False, help='Path to directory containing loadable PE modules.\n'
'When modules are parsed or loaded by samples,\n'
'PEs from this directory will be loaded into the\n'
'emulated address space')
Main(parser)
+37
View File
@@ -0,0 +1,37 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
import setuptools
with open('README.md', 'r') as fh:
long_description = fh.read()
with open("speakeasy/version.py") as fp:
vl = fp.readline()
gv, ver_num = vl.split('=')
if gv.strip() != '__version__':
raise Exception('Invalid version file found')
version = ver_num.strip().strip("\"\'")
setuptools.setup(
name='speakeasy',
author='Andrew Davis',
description='Speakeasy malware emulation framework',
version=version,
long_description=long_description,
long_description_content_type='text/markdown',
packages=setuptools.find_packages(),
include_package_data=True,
install_requires=[
'pefile',
'capstone',
'lznt1',
'unicorn==1.0.2rc4',
'jsonschema'
],
classifiers=[
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
],
python_requires='>=3.6',
)
+12
View File
@@ -0,0 +1,12 @@
# flake8: noqa
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
# Import relevant classes for easy access
from speakeasy.windows.common import PeFile
from speakeasy.windows.win32 import Win32Emulator
from speakeasy.windows.kernel import WinKernelEmulator
from speakeasy.speakeasy import Speakeasy
import speakeasy.winenv.defs as defs
import speakeasy.winenv.arch as arch
+968
View File
@@ -0,0 +1,968 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
import re
import json
import fnmatch
import traceback
import speakeasy.common as common
import speakeasy.winenv.arch as e_arch
from speakeasy.engines import unicorn_eng
from speakeasy.profiler import Profiler
import speakeasy.version as version
from speakeasy.memmgr import MemoryManager
from speakeasy.errors import EmuException
EMU_ENGINES = (
('unicorn', unicorn_eng.EmuEngine),
)
# Generic emulator class for binary code
class BinaryEmulator(MemoryManager):
"""
Base class for emulating binaries
"""
def __init__(self, config, logger=None):
super(BinaryEmulator, self).__init__()
self.stack_base = 0
self.page_size = None
self.inst_count = 0
self.curr_instr_size = 0
self.disasm_eng = None
self.builtin_hooks_set = False
self.emu_eng = None
self.maps = []
self.config = config
self.hooks = {}
self.profiler = Profiler()
self.runtime = 0
self.emu_version = self.get_emu_version()
self.logger = logger
def log_info(self, msg):
if self.logger:
self.logger.info(msg)
def log_error(self, msg):
if self.logger:
self.logger.error(msg)
def log_exception(self, msg):
if self.logger:
self.logger.exception(msg)
def get_profiler(self):
"""
Get the current event profiler object (if any)
"""
return self.profiler
def get_report(self):
"""
Get the emulation report for all runs that were executed
"""
if self.profiler:
return self.profiler.get_report()
def get_json_report(self):
"""
Get the emulation report for all runs that were executed formatted as a JSON string
"""
if self.profiler:
return self.profiler.get_json_report()
def _parse_config(self, config):
"""
Parse the config to be used for emulation
"""
if isinstance(config, str):
config = json.loads(config)
self.config = config
_eng = config.get('emu_engine', '')
for name, eng in EMU_ENGINES:
if name.lower() == _eng.lower():
self.emu_eng = eng()
if not self.emu_eng:
raise EmuException('Unsupported emulation engine: %s' % (_eng))
self.osversion = config.get('os_ver', {})
self.env = config.get('env', {})
self.user_config = config.get('user', {})
self.hostname = config.get('hostname')
self.symlinks = config.get('symlinks', [])
self.config_modules = config.get('modules', {})
self.config_system_modules = self.config_modules.get('system_modules', [])
self.config_processes = config.get('processes', [])
self.config_user_modules = self.config_modules.get('user_modules', [])
self.config_analysis = config.get('analysis', {})
self.max_instructions = config.get('max_instructions', -1)
self.timeout = config.get('timeout', 0)
self.max_api_count = config.get('max_api_count', 5000)
self.exceptions = config.get('exceptions', {})
self.filesystem_config = config.get('filesystem', {})
self.keep_memory_on_free = config.get('keep_memory_on_free', False)
self.network_config = config.get('network', {})
self.command_line = config.get('command_line', '')
def get_emu_version(self):
"""
Get the version of the emulator
"""
return version.__version__
def get_os_version(self):
"""
Get version of the OS being emulated
"""
return self.osversion
def get_osver_string(self):
"""
Get the human readable OS version string
"""
osver = self.get_os_version()
if osver:
os_name = osver.get('name', '')
major = osver.get('major')
minor = osver.get('minor')
if major is not None and minor is not None:
verstr = '%s.%d_%d' % (os_name, major, minor)
return verstr
def get_hostname(self):
"""
Get hostname of the machine being emulated
"""
return self.hostname
def get_user(self):
"""
Get the current emulated user's name
"""
return self.user_config
def sizeof(self, obj):
"""
Get the size (in the emulation space) of the supplied object
"""
return obj.sizeof()
def get_bytes(self, obj):
"""
Get the bytes represented in the emulation space of the supplied object
"""
return obj.get_bytes()
def stop(self):
"""
Stop emulation completely
"""
self.emu_eng.stop()
if self.profiler:
self.profiler.stop_run_clock()
def start(self, addr, size):
"""
Begin emulation
"""
self.set_hooks()
self._set_emu_hooks()
if self.profiler:
self.profiler.set_start_time()
try:
self.emu_eng.start(addr, timeout=self.timeout, count=self.max_instructions)
except Exception:
if self.profiler:
self.profiler.log_error(traceback.format_exc())
self.on_emu_complete()
def get_network_config(self):
"""
Get the network settings specified in the network section of the config file
"""
return self.network_config
def get_filesystem_config(self):
"""
Get the filesystem settings specified in the filesystem section of the config file
"""
return self.filesystem_config
def reg_write(self, reg, val):
"""
Write a value to an emulated cpu register
"""
if isinstance(reg, str):
_reg = e_arch.REG_LOOKUP.get(reg.lower())
if not _reg:
raise EmuException('Invalid register access %s' % (reg))
reg = _reg
self.emu_eng.reg_write(reg, val)
def reg_read(self, reg):
"""
Read a value from an emulated cpu register
"""
if isinstance(reg, str):
_reg = e_arch.REG_LOOKUP.get(reg.lower())
if not _reg:
raise EmuException('Invalid register access %s' % (reg))
reg = _reg
return self.emu_eng.reg_read(reg)
def set_hooks(self):
"""
Set instruction level hooks
"""
for ht in (common.HOOK_CODE, common.HOOK_MEM_READ, common.HOOK_MEM_WRITE,
common.HOOK_MEM_INVALID, common.HOOK_INTERRUPT):
for hook in self.hooks.get(ht, []):
if not hook.added:
hook.add()
def _cs_disasm(self, mem, addr, fast=True):
"""
Disassemble bytes using capstone
"""
try:
if fast:
tu = [i for i in self.disasm_eng.disasm_lite(bytes(mem), addr)]
address, size, mnem, oper = tu[0]
else:
tu = [(i.mnemonic, i.op_str) for i in
self.disasm_eng.disasm(bytes(mem), addr)]
mnem, oper = tu[0]
except IndexError:
raise EmuException("Failed to disasm at address: 0x%x" % (addr))
op = '%s %s' % (mnem, oper)
return ((mnem, oper, op))
def disasm(self, mem, addr):
"""
Disassemble bytes at a specified address
"""
return self._cs_disasm(mem, addr)
def get_register_state(self):
"""
Get the current state of registers from the emulator
"""
regs = {}
if e_arch.ARCH_X86 == self.get_arch():
for name, reg in (('esp', e_arch.X86_REG_ESP),
('ebp', e_arch.X86_REG_EBP),
('eip', e_arch.X86_REG_EIP),
('esi', e_arch.X86_REG_ESI),
('edi', e_arch.X86_REG_EDI),
('eax', e_arch.X86_REG_EAX),
('ebx', e_arch.X86_REG_EBX),
('ecx', e_arch.X86_REG_ECX),
('edx', e_arch.X86_REG_EDX)):
val = self.reg_read(reg)
regs[name] = "{0:#0{1}x}".format(val, 2 + (self.get_ptr_size() * 2))
elif e_arch.ARCH_AMD64 == self.get_arch():
for name, reg in (('rsp', e_arch.AMD64_REG_RSP),
('rbp', e_arch.AMD64_REG_RBP),
('rip', e_arch.AMD64_REG_RIP),
('rsi', e_arch.AMD64_REG_RSI),
('rdi', e_arch.AMD64_REG_RDI),
('r8', e_arch.AMD64_REG_R8),
('r9', e_arch.AMD64_REG_R9),
('r10', e_arch.AMD64_REG_R10),
('r11', e_arch.AMD64_REG_R11),
('rax', e_arch.AMD64_REG_RAX),
('rbx', e_arch.AMD64_REG_RBX),
('rcx', e_arch.AMD64_REG_RCX),
('rdx', e_arch.AMD64_REG_RDX)):
val = self.reg_read(reg)
regs[name] = "{0:#0{1}x}".format(val, 2 + (self.get_ptr_size() * 2))
return regs
def get_disasm(self, addr, size):
"""
Get the disassembly from an address
"""
return self.disasm(self.mem_read(addr, size), addr)
def set_func_args(self, stack_addr, ret_addr, *args, home_space=True):
"""
Set the arguments before an emulated function call. This is how we pass
arguments to a function when calling it through the emulator.
"""
curr_sp = stack_addr - self.ptr_size
nargs = len(args)
if self.get_arch() == e_arch.ARCH_X86:
sp = e_arch.X86_REG_ESP
elif self.get_arch() == e_arch.ARCH_AMD64:
sp = e_arch.AMD64_REG_RSP
i = 0
for i, r in enumerate((e_arch.AMD64_REG_RCX, e_arch.AMD64_REG_RDX,
e_arch.AMD64_REG_R8, e_arch.AMD64_REG_R9)):
if nargs == 0:
break
self.reg_write(r, args[i])
nargs -= 1
# Set the stack home space
if home_space:
curr_sp -= 0x20
self.reg_write(sp, curr_sp)
else:
raise EmuException('Unsupported architecture')
if nargs > 0:
for arg in args[-nargs:][::-1]:
a = arg.to_bytes(self.ptr_size, byteorder='little')
self.mem_write(curr_sp, a)
self.reg_write(sp, curr_sp)
curr_sp -= self.ptr_size
# Set the return address
r = ret_addr.to_bytes(self.ptr_size, byteorder='little')
self.mem_write(curr_sp, r)
self.reg_write(sp, curr_sp)
def get_func_argv(self, callconv, argc):
"""
Get the arguments for a function given the supplied calling convention
"""
argv = []
ptr_size = self.get_ptr_size()
arch = self.get_arch()
nargs = argc
endian = 'little'
# Handle calling conventions using floats
if arch in (e_arch.ARCH_X86, e_arch.ARCH_AMD64):
if callconv == e_arch.CALL_CONV_FLOAT:
for i, r in enumerate((e_arch.X86_REG_XMM0,
e_arch.X86_REG_XMM1,
e_arch.X86_REG_XMM2,
e_arch.X86_REG_XMM3)):
if nargs == 0:
break
val = self.reg_read(r)
argv.append(val)
nargs -= 1
if arch == e_arch.ARCH_X86:
sp = self.reg_read(e_arch.X86_REG_ESP)
if callconv == e_arch.CALL_CONV_FASTCALL:
if nargs >= 2:
argv.append(self.reg_read(e_arch.X86_REG_ECX))
argv.append(self.reg_read(e_arch.X86_REG_EDX))
nargs -= 2
elif nargs == 1:
argv.append(self.reg_read(e_arch.X86_REG_ECX))
nargs -= 1
elif arch == e_arch.ARCH_AMD64:
sp = self.reg_read(e_arch.AMD64_REG_RSP)
sp += 0x20
for i, r in enumerate((e_arch.AMD64_REG_RCX, e_arch.AMD64_REG_RDX,
e_arch.AMD64_REG_R8, e_arch.AMD64_REG_R9)):
if nargs == 0:
break
val = self.reg_read(r)
argv.append(val)
nargs -= 1
else:
raise EmuException('Unsupported architecture')
# Skip past the saved ret addr
sp += ptr_size
for i in range(nargs):
ptr = self.mem_read(sp, ptr_size)
argv.append(int.from_bytes(ptr, endian))
sp += ptr_size
return argv
def do_call_return(self, ret_addr, argc, ret_value=None, conv=None):
"""
Set the emulation state after a call has completed
"""
if self.get_arch() == e_arch.ARCH_X86:
sp = e_arch.X86_REG_ESP
pc = e_arch.X86_REG_EIP
rr = e_arch.X86_REG_EAX
elif self.get_arch() == e_arch.ARCH_AMD64:
sp = e_arch.AMD64_REG_RSP
pc = e_arch.AMD64_REG_RIP
rr = e_arch.AMD64_REG_RAX
else:
raise EmuException('Unsupported architecture')
if conv == e_arch.CALL_CONV_FLOAT:
rr = e_arch.X86_REG_XMM0
stk_ptr = self.reg_read(sp)
self.reg_write(sp, stk_ptr + self.ptr_size)
self.reg_write(pc, ret_addr)
if ret_value is not None:
self.reg_write(rr, ret_value)
# Cleanup the stack
if conv == e_arch.CALL_CONV_CDECL:
# If cdecl, the emu engine will clean the stack
pass
elif conv == e_arch.CALL_CONV_FASTCALL:
if self.get_arch() == e_arch.ARCH_X86:
if argc > 2:
self.clean_stack_args(argc - 2)
else:
self.clean_stack_args(argc)
def get_ret_address(self):
"""
Get the return address from the stack
"""
endian = 'little'
sp = self.get_stack_ptr()
ret = self.mem_read(sp, self.ptr_size)
ret = int.from_bytes(ret, endian)
return ret
def push_stack(self, val):
"""
Put a value on the stack and adjust the stack pointer
"""
endian = 'little'
sp = self.get_stack_ptr()
bval = val.to_bytes(self.ptr_size, endian)
sp -= self.ptr_size
self.mem_write(sp, bval)
self.set_stack_ptr(sp)
return val
def pop_stack(self):
"""
Get value from the stack and adjust the stack pointer
"""
endian = 'little'
sp = self.get_stack_ptr()
val = self.mem_read(sp, self.ptr_size)
val = int.from_bytes(val, endian)
sp += self.ptr_size
self.set_stack_ptr(sp)
return val
def get_stack_ptr(self):
"""
Get the current address of the stack pointer
"""
if self.get_arch() == e_arch.ARCH_X86:
sp = self.reg_read(e_arch.X86_REG_ESP)
elif self.get_arch() == e_arch.ARCH_AMD64:
sp = self.reg_read(e_arch.AMD64_REG_RSP)
return sp
def set_stack_ptr(self, addr):
"""
Set the current address of the stack pointer
"""
if self.get_arch() == e_arch.ARCH_X86:
self.reg_write(e_arch.X86_REG_ESP, addr)
elif self.get_arch() == e_arch.ARCH_AMD64:
self.reg_write(e_arch.AMD64_REG_RSP, addr)
def print_stack(self, num_ptrs):
"""
This a debug function used to print the current stack state
"""
sp = self.get_stack_ptr()
print('Stack:')
print('***********************')
for i in range(num_ptrs):
ptr = self.mem_read(sp, self.get_ptr_size())
ptr = int.from_bytes(ptr, 'little')
tag = self.get_address_tag(ptr)
if tag:
fmt = 'sp=0x%x:\t0x%x\t->\t%s' % (sp, ptr, tag)
else:
fmt = 'sp=0x%x:\t0x%x\t' % (sp, ptr)
print(fmt.expandtabs(5))
sp += self.get_ptr_size()
def get_stack_trace(self, num_ptrs=16):
"""
Get the current stack state
"""
trace = []
sp = self.get_stack_ptr()
try:
for i in range(num_ptrs):
ptr = self.mem_read(sp, self.get_ptr_size())
ptr = int.from_bytes(ptr, 'little')
tag = self.get_address_tag(ptr)
fmt = "{0:#0{1}x}".format(ptr, 2 + (self.get_ptr_size() * 2))
sp_off = "{0:#0{1}x}".format(i * self.get_ptr_size(), 2 * 2)
if not tag:
entry = 'sp+%s: %s' % (sp_off, fmt)
else:
entry = 'sp+%s: %s -> %s' % (sp_off, fmt, tag)
trace.append(entry)
sp += self.get_ptr_size()
finally:
return trace
def get_pc(self):
"""
Get the value of the current program counter
"""
if self.get_arch() == e_arch.ARCH_X86:
pc = self.reg_read(e_arch.X86_REG_EIP)
elif self.get_arch() == e_arch.ARCH_AMD64:
pc = self.reg_read(e_arch.AMD64_REG_RIP)
else:
raise EmuException('Unsupported architecture')
return pc
def set_pc(self, addr):
"""
Set the value of the current program counter
"""
if self.get_arch() == e_arch.ARCH_X86:
self.reg_write(e_arch.X86_REG_EIP, addr)
elif self.get_arch() == e_arch.ARCH_AMD64:
self.reg_write(e_arch.AMD64_REG_RIP, addr)
else:
raise EmuException('Unsupported architecture')
def get_return_val(self):
"""
Get the current value in the return register
"""
if self.get_arch() == e_arch.ARCH_X86:
val = self.reg_read(e_arch.X86_REG_EAX)
elif self.get_arch() == e_arch.ARCH_AMD64:
val = self.reg_read(e_arch.AMD64_REG_RAX)
else:
raise EmuException('Unsupported architecture')
return val
def reset_stack(self, base):
"""
Reset stack to the supplied base address
"""
arch = self.get_arch()
ptr = base
if arch == e_arch.ARCH_X86:
self.reg_write(e_arch.X86_REG_ESP, base)
self.reg_write(e_arch.X86_REG_EBP, base)
elif arch == e_arch.ARCH_AMD64:
# Save room for the "home space"
ptr -= self.ptr_size * 5
self.reg_write(e_arch.AMD64_REG_RSP, ptr)
self.reg_write(e_arch.AMD64_REG_RBP, ptr)
return base, ptr
def alloc_stack(self, size):
"""
Allocate memory to use for the program stack
"""
# Allocate memory for our stack
# Stack grows down
chunk = self.get_valid_ranges(size, addr=0x1200000)
addr, block_size = chunk
self.mem_map(block_size, base=addr, tag='emu.stack')
base = addr + block_size
self.mem_reserve(size, base=base)
base, ptr = self.reset_stack(base)
return base, ptr
def clean_stack_args(self, argc):
"""
Adjust the stack for arguments that were supplied
"""
ptr_size = self.get_ptr_size()
arch = self.get_arch()
if argc == 0:
return
if arch == e_arch.ARCH_X86:
sp = self.reg_read(e_arch.X86_REG_ESP)
sp += (ptr_size * argc)
self.reg_write(e_arch.X86_REG_ESP, sp)
elif arch == e_arch.ARCH_AMD64:
return
else:
raise EmuException('Unsupported architecture')
def get_arch(self):
"""
Get the current emulated architecture
"""
return self.arch
def get_arch_name(self):
"""
Get the name of current emulated architecture
"""
if self.arch == e_arch.ARCH_AMD64:
return 'amd64'
elif self.arch == e_arch.ARCH_X86:
return 'x86'
return ''
def eval_emu_var(self):
"""
Used to expand variables supplied in the emulator config file. This
might be useful for accessing files that are a relative path of the
speakeasy package.
For example:
$ROOT$: This variable corresponds to the package root for speakeasy
"""
def read_mem_string(self, address, width=1, max_chars=0):
"""
Read a string from emulated memory
"""
char = b'\xFF'
string = b''
i = 0
if width == 1:
decode = 'utf-8'
elif width == 2:
decode = 'utf-16le'
else:
raise ValueError('Invalid string encoding')
while int.from_bytes(char, 'little') != 0:
if max_chars and i >= max_chars:
break
char = self.mem_read(address, width)
string += char
address += width
i += 1
try:
dec = string.decode(decode, 'ignore').replace('\x00', '')
except Exception:
dec = string.replace(b'\x00', b'')
return dec
def mem_string_len(self, address, width=1):
"""
Get the length of a string from emulated memory
"""
slen = -1
char = b'\xFF'
while int.from_bytes(char, 'little') != 0:
char = self.mem_read(address, width)
address += width
slen += 1
return slen
def get_ansi_strings(self, data, min_len=4):
"""
Get all ansi strings from a supplied memory blob
"""
astrs = []
pat = b'[\x20-\x7f]{%d,}' % (min_len)
res = re.compile(pat)
hits = res.findall(data)
for s in hits:
try:
s = s.decode('utf-8')
astrs.append(s)
except UnicodeDecodeError:
continue
return astrs
def get_unicode_strings(self, data, min_len=4):
"""
Get all unicode strings from a supplied memory blob
"""
wstrs = []
pat = b'(?:[\x20-\x7f]\x00){%d,}' % (min_len)
res = re.compile(pat)
hits = res.findall(data)
for ws in hits:
try:
ws = ws.decode('utf-16le')
wstrs.append(ws)
except UnicodeDecodeError:
continue
return wstrs
def mem_copy(self, dst, src, n):
"""
Copy bytes from one emulated address to another
"""
sbytes = self.mem_read(src, n)
self.mem_write(dst, sbytes)
return n
def write_mem_string(self, string, address, width=1):
"""
Write string data to an emulated memory address
"""
if width == 1:
encode = 'utf-8'
elif width == 2:
encode = 'utf-16le'
else:
raise ValueError('Invalid string encoding')
enc_str = string.encode(encode)
self.mem_write(address, enc_str)
def get_ptr_size(self):
"""
Get the pointer size of the current emulation state
"""
return self.ptr_size
def get_mem_strings(self):
"""
Get ansi and unicode strings from emulated memory
"""
tgt_tag_prefixes = ('emu.stack', 'api')
ansi_strings = []
unicode_strings = []
ret_ansi = []
ret_unicode = []
for mmap in self.get_mem_maps():
tag = mmap.get_tag()
if tag and tag.startswith(tgt_tag_prefixes) and tag != self.input.get('mem_tag'):
data = self.mem_read(mmap.get_base(), mmap.get_size()-1)
ansi_strings += self.get_ansi_strings(data)
unicode_strings += self.get_unicode_strings(data)
[ret_ansi.append(a) for a in ansi_strings if a not in ret_ansi]
[ret_unicode.append(a) for a in unicode_strings if a not in ret_unicode]
return (ret_ansi, ret_unicode)
def set_ptr_size(self, arch):
"""
Set the current pointer size used in the emulator
"""
if arch == e_arch.ARCH_AMD64:
self.ptr_size = 8
elif arch == e_arch.ARCH_X86:
self.ptr_size = 4
else:
raise EmuException('Unsupported architecture')
def get_module_from_addr(self, addr):
"""
If the supplied address belongs to a module, return it
"""
for mod in self.modules:
base, size = mod[1]
if addr >= base and addr <= base + size:
return mod[0]
return None
def get_api_hook(self, mod_name, func_name):
"""
If an API hook has been set, return it here
"""
for hook in self.hooks.get(common.HOOK_API, []):
if fnmatch.fnmatch(mod_name.lower(), hook.module.lower()):
if fnmatch.fnmatch(func_name.lower(), hook.api_name.lower()):
return hook
return None
def add_api_hook(self, cb, module='', api_name='', argc=0, call_conv=None, emu=None):
"""
Add an API level hook (e.g. kernel32.CreateFile) here
"""
if not emu:
emu = self
hook = common.ApiHook(emu, self.emu_eng, cb, module, api_name, argc, call_conv)
hl = self.hooks.get(common.HOOK_API)
if not hl:
self.hooks.update({common.HOOK_API: [hook, ]})
else:
hl.append(hook)
def add_code_hook(self, cb, begin=1, end=0, ctx={}, emu=None):
"""
Add a hook that will fire for every CPU instruction
"""
hl = self.hooks.get(common.HOOK_CODE, [])
if not emu:
emu = self
hook = common.CodeHook(self, self.emu_eng, cb, begin, end, ctx)
if not hl:
self.hooks.update({common.HOOK_CODE: [hook, ]})
else:
hl.insert(0, hook)
if self.emu_eng:
hook.add()
return hook
def _dynamic_code_cb(self, emu, addr, size, ctx={}):
"""
Call all subscribers that want callbacks dynamic code callbacks
"""
profiler = self.get_profiler()
mm = self.get_address_map(addr)
if profiler:
run = self.get_current_run()
profiler.log_dyn_code(run, mm.get_tag(), mm.get_base(), mm.get_size())
for h in self.hooks.get(common.HOOK_DYN_CODE, []):
h.cb(mm)
# Delete the code hook that got us here
if ctx and isinstance(ctx, dict):
h = ctx.get('_delete_hook')
if h:
h.disable()
def _set_dyn_code_hook(self, addr, size, ctx={}):
"""
Set the top level dispatch hook for dynamic code execution
"""
max_hook_size = 0x10
if size > max_hook_size:
size = max_hook_size
ch = self.add_code_hook(cb=self._dynamic_code_cb, begin=addr, end=addr + size, ctx=ctx)
ctx.update({'_delete_hook': ch})
def add_dyn_code_hook(self, cb, ctx=[], emu=None):
"""
Add a hook that will fire when dynamically generated/copied code is executed
"""
if not emu:
emu = self
hl = self.hooks.get(common.HOOK_DYN_CODE, [])
hook = common.DynCodeHook(emu, self.emu_eng, cb, ctx)
if not hl:
self.hooks.update({common.HOOK_DYN_CODE: [hook, ]})
else:
hl.insert(0, hook)
return hook
def add_mem_read_hook(self, cb, begin=1, end=0, emu=None):
"""
Add a hook that will fire for memory reads
"""
if not emu:
emu = self
hook = common.ReadMemHook(emu, self.emu_eng, cb, begin, end)
hl = self.hooks.get(common.HOOK_MEM_READ)
if not hl:
self.hooks.update({common.HOOK_MEM_READ: [hook, ]})
else:
hl.insert(0, hook)
if self.emu_eng:
hook.add()
return hook
def add_mem_write_hook(self, cb, begin=1, end=0, emu=None):
"""
Add a hook that will fire for memory writes
"""
if not emu:
emu = self
hook = common.WriteMemHook(emu, self.emu_eng, cb, begin, end)
hl = self.hooks.get(common.HOOK_MEM_WRITE)
if not hl:
self.hooks.update({common.HOOK_MEM_WRITE: [hook, ]})
else:
hl.insert(0, hook)
if self.emu_eng:
hook.add()
return hook
def _hook_mem_invalid_dispatch(self, emu, access, address, size, value, ctx):
"""
This handler will dispatch other invalid memory hooks
"""
hl = self.hooks.get(common.HOOK_MEM_INVALID, [])
rv = True
for mem_access_hook in hl[:-1]:
if mem_access_hook.enabled:
rv = mem_access_hook.cb(emu, access, address, size, value, ctx)
if rv is False:
break
return rv
def add_mem_invalid_hook(self, cb, emu=None):
"""
Add a hook that will fire for invalid memory access
"""
hook = common.InvalidMemHook(self, self.emu_eng, cb, native_hook=False)
hl = self.hooks.get(common.HOOK_MEM_INVALID)
if not emu:
emu = self
if not hl:
dispatch_hook = common.InvalidMemHook(emu, self.emu_eng,
self._hook_mem_invalid_dispatch,
native_hook=True)
if self.emu_eng:
dispatch_hook.add()
self.hooks.update({common.HOOK_MEM_INVALID: [hook, dispatch_hook]})
else:
hl.insert(0, hook)
if self.emu_eng:
hook.add()
return hook
def add_interrupt_hook(self, cb, ctx=[], emu=None):
"""
Add a hook that will fire for software interrupts
"""
if not emu:
emu = self
hook = common.InterruptHook(emu, self.emu_eng, cb, ctx=[])
hl = self.hooks.get(common.HOOK_INTERRUPT)
if not hl:
self.hooks.update({common.HOOK_INTERRUPT: [hook, ]})
else:
hl.insert(0, hook)
if self.emu_eng:
hook.add()
return hook
+212
View File
@@ -0,0 +1,212 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
import os
# Emulation hook types
HOOK_CODE = 1000
HOOK_MEM_INVALID = 1001
HOOK_MEM_PERM_EXEC = 1002
HOOK_MEM_READ = 1003
HOOK_MEM_WRITE = 1004
HOOK_INTERRUPT = 1005
HOOK_MEM_ACCESS = 1006
HOOK_MEM_PERM_WRITE = 1007
HOOK_API = 1008
HOOK_DYN_CODE = 1009
# Emulation memory protection types
PERM_MEM_NONE = 0
PERM_MEM_EXEC = 0x10
PERM_MEM_READ = 0x02
PERM_MEM_WRITE = 0x04
PERM_MEM_RW = PERM_MEM_READ | PERM_MEM_WRITE
PERM_MEM_RWX = PERM_MEM_READ | PERM_MEM_WRITE | PERM_MEM_EXEC
# Emulation memory access types
INVALID_MEM_EXEC = 2000
INVALID_MEM_READ = 2001
INVALID_MEM_WRITE = 2002
INVAL_PERM_MEM_WRITE = 2003
INVAL_PERM_MEM_EXEC = 2004
INVAL_PERM_MEM_READ = 2005
def normalize_package_path(path):
"""
Get the supplied path in relation to the package root
"""
def _get_speakeasy_root():
return os.path.join(os.path.dirname(__file__))
root_var = '$ROOT$'
if root_var in path:
root = _get_speakeasy_root()
return path.replace(root_var, root)
return path
class Hook(object):
"""
Base class for all emulator hooks
"""
def __init__(self, se_obj, emu_eng, cb, ctx=[], native_hook=False):
"""
Arguments:
se_obj: speakeasy emulator object
emu_eng: emulation engine object
cb: Python callback function
ctx: Arbitrary context that be passed between hook callbacks
native_hook: When set to True, a new, raw callback will be registered with
with the underlying emulation engine that is called directly by the DLL.
Otherwise, this hook will be dispatched via a wrapper hook
(e.g. see _wrap_code_cb below)
"""
self.cb = cb
self.handle = 0
self.enabled = False
self.added = False
self.native_hook = native_hook
self.emu_eng = emu_eng
self.se_obj = se_obj
self.ctx = ctx
def enable(self):
self.enabled = True
self.emu_eng.hook_enable(self.handle)
def disable(self):
self.enabled = False
self.emu_eng.hook_disable(self.handle)
def _wrap_code_cb(self, emu, addr, size, ctx=[]):
try:
if self.enabled:
if self.se_obj.exit_event and self.se_obj.exit_event.is_set():
self.se_obj.stop()
return False
return self.cb(self.se_obj, addr, size, self.ctx)
return True
except KeyboardInterrupt:
self.se_obj.stop()
return False
def _wrap_intr_cb(self, emu, num, ctx=[]):
if self.enabled:
return self.cb(self.se_obj, num, self.ctx)
return True
def _wrap_memory_access_cb(self, emu, access, addr, size, value, ctx):
try:
if self.enabled:
if self.se_obj.exit_event and self.se_obj.exit_event.is_set():
self.se_obj.stop()
return False
return self.cb(self.se_obj, access, addr, size, value, ctx)
return True
except KeyboardInterrupt:
self.se_obj.stop()
return False
class ApiHook(Hook):
"""
This hook type is used when using a specific API (e.g. kernel32.CreateFile)
"""
def __init__(self, se_obj, emu_eng, cb, module='', api_name='', argc=0, call_conv=None):
super(ApiHook, self).__init__(se_obj, emu_eng, cb)
self.module = module
self.api_name = api_name
self.argc = argc
self.call_conv = call_conv
class DynCodeHook(Hook):
"""
This hook type is used to get a callback when dynamically created/copied code is executed
Currently, this will only fire once per dynamic code mapping. Could be useful for unpacking.
"""
def __init__(self, se_obj, emu_eng, cb, ctx=[]):
super(DynCodeHook, self).__init__(se_obj, emu_eng, cb)
class CodeHook(Hook):
"""
This hook callback will fire for every CPU instruction
"""
def __init__(self, se_obj, emu_eng, cb, begin=1, end=0, ctx=[],
native_hook=True):
super(CodeHook, self).__init__(se_obj, emu_eng, cb, ctx=ctx, native_hook=native_hook)
self.begin = begin
self.end = end
def add(self):
if not self.added and self.native_hook:
self.handle = self.emu_eng.hook_add(htype=HOOK_CODE, cb=self._wrap_code_cb,
begin=self.begin, end=self.end)
self.added = True
self.enabled = True
class ReadMemHook(Hook):
"""
This hook will fire each time a valid chunk of memory is read from
"""
def __init__(self, se_obj, emu_eng, cb, begin=1, end=0, native_hook=True):
super(ReadMemHook, self).__init__(se_obj, emu_eng, cb, native_hook=native_hook)
self.begin = begin
self.end = end
def add(self):
if not self.added and self.native_hook:
self.emu_eng.hook_add(htype=HOOK_MEM_READ, cb=self._wrap_memory_access_cb,
begin=self.begin, end=self.end)
self.added = True
self.enabled = True
class WriteMemHook(Hook):
"""
This hook will fire each time a valid chunk of memory is written to
"""
def __init__(self, se_obj, emu_eng, cb, begin=1, end=0, native_hook=True):
super(WriteMemHook, self).__init__(se_obj, emu_eng, cb, native_hook=native_hook)
self.begin = begin
self.end = end
def add(self):
if not self.added and self.native_hook:
self.handle = self.emu_eng.hook_add(htype=HOOK_MEM_WRITE,
cb=self._wrap_memory_access_cb,
begin=self.begin, end=self.end)
self.added = True
self.enabled = True
class InvalidMemHook(Hook):
"""
This hook will fire each time a invalid chunk of memory is accessed
"""
def __init__(self, se_obj, emu_eng, cb, native_hook=False):
super(InvalidMemHook, self).__init__(se_obj, emu_eng, cb, native_hook=native_hook)
def add(self):
if not self.added and self.native_hook:
self.handle = self.emu_eng.hook_add(htype=HOOK_MEM_INVALID,
cb=self._wrap_memory_access_cb)
self.added = True
self.enabled = True
class InterruptHook(Hook):
"""
This hook will fire each time a a software interrupt is triggered
"""
def __init__(self, se_obj, emu_eng, cb, ctx=[], native_hook=True):
super(InterruptHook, self).__init__(se_obj, emu_eng, cb, ctx=ctx, native_hook=native_hook)
def add(self):
if not self.added and self.native_hook:
self.handle = self.emu_eng.hook_add(htype=HOOK_INTERRUPT, cb=self._wrap_intr_cb)
self.added = True
self.enabled = True
+532
View File
@@ -0,0 +1,532 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "TODO",
"title": "SpeakEasy configuration JSON schema",
"description": "SpeakEasy configuration",
"type": "object",
"definitions": {
"file": {
"type": "object",
"properties": {
"mode": {
"type": "string",
"enum": [
"full_path",
"by_ext",
"default"
]
},
"emu_path": {
"type": "string",
"description": "The path of the target file in the emulation space (e.g. \"C:\\boot.ini\""
},
"byte_fill": {
"type": "object",
"properties": {
"byte": {
"type": "string"
},
"size": {
"type": "number"
}
},
"additionalProperties": false,
"required": [
"byte",
"size"
]
},
"path": {
"type": "string",
"description": "Path to the physical file that will be used to provide data to the emulated file"
},
"ext": {
"type": "string",
"description": "Catch-all for specific file extensions"
}
},
"additionalProperties": false,
"anyOf": [
{
"properties": {
"mode": {
"const": "default"
}
},
"required": [
"mode"
]
},
{
"properties": {
"mode": {
"const": "full_path"
}
},
"required": [
"mode",
"emu_path"
]
},
{
"properties": {
"mode": {
"const": "by_ext"
}
},
"required": [
"mode",
"ext"
]
}
]
}
},
"properties": {
"config_version": {
"type": "number",
"enum": [
0.2
],
"description": "config format version"
},
"description": {
"type": "string",
"description": "config description"
},
"emu_engine": {
"type": "string",
"enum": [
"unicorn"
],
"description": "emulation engine to use"
},
"timeout": {
"type": "number",
"description": "emulation timeout"
},
"max_api_count": {
"type": "number",
"description": "Maximum number of API calls allowed"
},
"system": {
"type": "string",
"enum": [
"windows"
],
"description": "System type to emulate"
},
"analysis": {
"type": "object",
"properties": {
"memory_tracing": {
"type": "boolean",
"description": "Enable memory tracing"
},
"strings": {
"type": "boolean",
"description": "Return static and in-memory strings"
}
},
"additionalProperties": false,
"required": [
"memory_tracing",
"strings"
]
},
"keep_memory_on_free": {
"type": "boolean",
"description": "Enables retaining all allocated memory to include in the output memory snapshot"
},
"exceptions": {
"type": "object",
"properties": {
"dispatch_handlers": {
"type": "boolean",
"description": "When set to \"true\", SEH exception handlers will be dispatched to the emulated program"
}
},
"additionalProperties": false,
"required": [
"dispatch_handlers"
]
},
"os_ver": {
"type": "object",
"description": "OS version information",
"properties": {
"name": {
"type": "string",
"enum": [
"windows"
]
},
"major": {
"type": "number"
},
"minor": {
"type": "number"
},
"build": {
"type": "number"
}
},
"additionalProperties": false,
"required": [
"name"
]
},
"current_dir": {
"type": "string",
"description": "Directory to set as CWD for the emulation"
},
"command_line": {
"type": "string",
"description": "Command line string to pass to the process"
},
"env": {
"type": "object",
"description": "Key-value environment variables to use",
"additionalProperties": {
"type": "string"
}
},
"hostname": {
"type": "string",
"description": "Hostname returned during emulation"
},
"user": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Username returned during emulation"
},
"is_admin": {
"type": "boolean",
"description": "Determines if this user is an administrator"
}
},
"additionalProperties": false,
"required": [
"name"
]
},
"symlinks": {
"type": "array",
"description": "Lists symbolic links to present to the emulated sample",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "The symbolic link name (e.g. \"\\??\\C:\""
},
"target": {
"type": "string",
"description": "Name of the object the symbolic link resolves to"
}
},
"additionalProperties": false,
"required": [
"name",
"target"
]
}
},
"filesystem": {
"type": "object",
"properties": {
"files": {
"type": "array",
"items": {
"$ref": "#/definitions/file"
}
}
},
"additionalProperties": false,
"required": [
"files"
]
},
"registry": {
"type": "object",
"properties": {
"keys": {
"type": "array",
"items": {
"type": "object",
"properties": {
"path": {
"type": "string"
},
"values": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Name of the registry value"
},
"type": {
"type": "string",
"description": "Type of data e.g. string, dword, etx."
},
"data": {
"type": "string",
"description": "The data to present when the registry value is accessed"
}
},
"required": [
"name",
"type",
"data"
],
"additionalProperties": false
}
}
},
"required": [
"path",
"values"
],
"additionalProperties": false
}
}
},
"required": [
"keys"
],
"additionalProperties": false
},
"network": {
"type": "object",
"properties": {
"dns": {
"type": "object",
"properties": {
"names": {
"type": "object",
"description": "Key-value dictionary of hostnames (key) to IP address (value)",
"additionalProperties": {
"type": "string"
}
},
"txt": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Name of the DNS TXT record"
},
"path": {
"type": "string",
"description": "Path to physical file that will be presented when a DNS TXT record is accessed"
}
},
"additionalProperties": false
}
}
},
"additionalProperties": false
},
"http": {
"type": "object",
"properties": {
"responses": {
"type": "array",
"items": {
"type": "object",
"properties": {
"verb": {
"type": "string",
"enum": [
"GET",
"POST",
"HEAD"
]
},
"files": {
"type": "array",
"items": {
"$ref": "#/definitions/file"
}
}
},
"additionalProperties": false
}
}
},
"additionalProperties": false
},
"winsock": {
"type": "object",
"properties": {
"responses": {
"type": "array",
"items": {
"$ref": "#/definitions/file"
}
}
}
}
},
"additionalProperties": false
},
"processes": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Name of the process"
},
"base_addr": {
"type": "string",
"description": "Base address of the main executable image"
},
"pid": {
"type": "number",
"description": "Hardcoded pid to assign to the process"
},
"path": {
"type": "string",
"description": "Emulated path of the main executable image"
},
"command_line": {
"type": "string",
"description": "Command line to assign to a process when a sample accesses it"
},
"is_main_exe": {
"type": "boolean",
"description": "When set, this process will be a container to use when emulating shellcode or libraries"
},
"session": {
"type": "number",
"description": "Windows session number to assign to process"
}
},
"additionalProperties": false,
"required": [
"name",
"base_addr",
"path"
]
}
},
"modules": {
"type": "object",
"properties": {
"modules_always_exist": {
"type": "boolean",
"description": "When a sample attempts to load a module, always present a decoy module"
},
"user_modules": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Name of the system module"
},
"base_addr": {
"type": "string",
"description": "Requested base address (in hex) that the module will be loaded at"
},
"path": {
"type": "string",
"description": "Emulated full path that will be presented to samples"
}
},
"additionalProperties": false,
"required": [
"name",
"base_addr",
"path"
]
}
},
"system_modules": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Name of the system module"
},
"base_addr": {
"type": "string",
"description": "Requested base address (in hex) that the module will be loaded at"
},
"path": {
"type": "string",
"description": "Emulated full path that will be presented to samples"
},
"driver": {
"type": "object",
"description": "",
"properties": {
"name": {
"type": "string",
"description": "Kernel DRIVER_OBJECT to present to the kernel emulator"
},
"devices": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Device objects associated with the driver object"
}
},
"required": [
"name"
],
"additionalProperties": false
}
}
},
"additionalProperties": false,
"required": [
"name"
]
}
},
"additionalProperties": false,
"required": [
"name",
"base_addr",
"path"
]
}
}
}
}
},
"additionalProperties": false,
"required": [
"config_version",
"emu_engine",
"timeout",
"system",
"analysis",
"exceptions",
"os_ver",
"current_dir",
"env",
"hostname",
"user",
"symlinks",
"filesystem",
"network",
"processes",
"modules"
]
}
+402
View File
@@ -0,0 +1,402 @@
{
"config_version": 0.2,
"description": "Default emulation profile to use when not overridden by user",
"emu_engine": "unicorn",
"timeout": 60,
"max_api_count": 6000,
"system": "windows",
"analysis": {
"memory_tracing": false,
"strings": true
},
"keep_memory_on_free": false,
"exceptions": {
"dispatch_handlers": true
},
"os_ver": {
"name": "windows",
"major": 6,
"minor": 1,
"build": 7601
},
"current_dir": "C:\\Windows\\system32",
"command_line": "svchost.exe myarg1 myarg2",
"env": {
"comspec": "C:\\Windows\\system32\\cmd.exe",
"systemroot": "C:\\Windows\\system32",
"windir": "C:\\Windows",
"temp": "C:\\Windows\\temp\\"
},
"hostname": "speakeasy_host",
"user": {
"name": "speakeasy_user",
"is_admin": true
},
"symlinks": [
{
"name": "\\??\\C:",
"target": "\\Device\\HarddiskVolume1"
},
{
"name": "\\??\\PhysicalDrive0",
"target": "\\Device\\Harddisk0\\DR0"
}
],
"filesystem": {
"files": [
{
"mode": "full_path",
"emu_path": "c:\\programdata\\mydir\\myfile.bin",
"byte_fill": {
"byte": "0x41",
"size": 512
}
},
{
"mode": "full_path",
"emu_path": "c:\\Windows\\system32\\cmd.exe",
"path": "$ROOT$/resources/files/default.bin"
},
{
"mode": "full_path",
"emu_path": "c:\\Windows\\system32\\svchost.exe",
"path": "$ROOT$/resources/files/default.bin"
},
{
"mode": "by_ext",
"ext": "exe",
"path": "$ROOT$/resources/files/default.bin"
},
{
"mode": "default",
"path": "$ROOT$/resources/files/default.bin"
}
]
},
"registry": {
"keys": [
{
"path": "HKEY_LOCAL_MACHINE\\System\\CurrentControlSet\\Services\\usbsamp",
"values": [
{
"name": "DisplayName",
"type": "REG_SZ",
"data": "An example service"
},
{
"name": "Start",
"type": "REG_DWORD",
"data": "0x00000003"
}
]
}
]
},
"network": {
"dns": {
"names":{
"speakeasy_host": "127.0.0.1",
"default": "10.1.2.3",
"google.com": "8.8.8.8",
"localhost": "127.0.0.1"
},
"txt":[
{
"name": "default",
"path": "$ROOT$/resources/web/default.bin"
}
]
},
"http": {
"responses": [
{
"verb": "GET",
"files": [
{
"mode": "default",
"path": "$ROOT$/resources/web/default.bin"
},
{
"mode": "by_ext",
"ext": "gif",
"path": "$ROOT$/resources/web/decoy.gif"
},
{
"mode": "by_ext",
"ext": "jpg",
"path": "$ROOT$/resources/web/decoy.jpg"
}
]
}
]
},
"winsock": {
"responses": [
{
"mode": "default",
"path": "$ROOT$/resources/web/stager.bin"
}
]
}
},
"processes": [
{
"name": "System",
"base_addr": "0x80000000",
"pid": 4,
"path": ""
},
{
"name": "smss",
"base_addr": "0x05000000",
"path": "C:\\Windows\\system32\\smss.exe"
},
{
"name": "csrss",
"base_addr": "0x05510000",
"path": "C:\\Windows\\system32\\csrss.exe"
},
{
"name": "wininit",
"base_addr": "0x05520000",
"path": "C:\\Windows\\system32\\wininit.exe"
},
{
"name": "services",
"base_addr": "0x05530000",
"path": "C:\\Windows\\system32\\services.exe"
},
{
"name": "lsass",
"base_addr": "0x05540000",
"path": "C:\\Windows\\system32\\lsass.exe"
},
{
"name": "winlogon",
"base_addr": "0x05550000",
"path": "C:\\Windows\\system32\\winlogon.exe"
},
{
"name": "svchost",
"base_addr": "0x05560000",
"path": "C:\\Windows\\system32\\svchost.exe"
},
{
"name": "outlook",
"base_addr": "0x05590000",
"path": "C:\\Windows\\system32\\outlook.exe"
},
{
"name": "explorer",
"base_addr": "0x05570000",
"path": "C:\\Windows\\explorer.exe"
},
{
"name": "main",
"base_addr": "0x00400000",
"path": "C:\\Windows\\system32\\main.exe",
"command_line": "svchost.exe",
"is_main_exe": true,
"session": 1
}
],
"modules": {
"modules_always_exist": false,
"module_directory_x86": "$ROOT$/winenv/decoys/x86",
"module_directory_x64": "$ROOT$/winenv/decoys/amd64",
"system_modules": [
{
"name": "ntoskrnl",
"base_addr": "0x803d0000",
"path": "C:\\Windows\\system32\\ntoskrnl.exe"
},
{
"name": "hal",
"base_addr": "0xC1000000",
"path": "C:\\Windows\\system32\\hal.dll"
},
{
"name": "ntfs",
"base_addr": "0xC2000000",
"path": "C:\\Windows\\system32\\drivers\\ntfs.sys"
},
{
"name": "netio",
"base_addr": "0xD4000000",
"path": "C:\\Windows\\system32\\drivers\\netio.sys"
},
{
"name": "volmgr",
"base_addr": "0xC6000000",
"path": "C:\\Windows\\system32\\drivers\\volmgr.sys",
"driver": {
"name": "\\Driver\\volmgr",
"devices": [
{
"name": "\\Device\\HarddiskVolume1"
}
]
}
},
{
"name": "disk",
"base_addr": "0xC3000000",
"path": "C:\\Windows\\system32\\drivers\\disk.sys",
"driver": {
"name": "\\Driver\\Disk",
"devices": [
{
"name": "\\Device\\Harddisk0\\DR0"
}
]
}
},
{
"name": "tcpip",
"base_addr": "0xC4000000",
"path": "C:\\Windows\\system32\\drivers\\tcpip.sys",
"driver": {
"name": "\\Driver\\Tcpip",
"devices": [
{
"name": "\\Device\\Tcp"
}
]
}
},
{
"name": "ndis",
"base_addr": "0xC7000000",
"path": "C:\\Windows\\system32\\drivers\\ndis.sys",
"driver": {
"name": "\\Driver\\Ndis",
"devices": [
{
"name": "\\Device\\Ndis"
}
]
}
}
],
"user_modules": [
{
"name": "kernel32",
"base_addr": "0x77000000",
"path": "C:\\Windows\\system32\\kernel32.dll"
},
{
"name": "ntdll",
"base_addr": "0x7C000000",
"path": "C:\\Windows\\system32\\ntdll.dll"
},
{
"name": "ws2_32",
"base_addr": "0x78C00000",
"path": "C:\\Windows\\system32\\ws2_32.dll"
},
{
"name": "wininet",
"base_addr": "0x7BC00000",
"path": "C:\\Windows\\system32\\wininet.dll"
},
{
"name": "winhttp",
"base_addr": "0x7BA00000",
"path": "C:\\Windows\\system32\\winhttp.dll"
},
{
"name": "advapi32",
"base_addr": "0x78000000",
"path": "C:\\Windows\\system32\\advapi32.dll"
},
{
"name": "psapi",
"base_addr": "0x71000000",
"path": "C:\\Windows\\system32\\psapi.dll"
},
{
"name": "user32",
"base_addr": "0x77D10000",
"path": "C:\\Windows\\system32\\user32.dll"
},
{
"name": "gdi32",
"base_addr": "0x77E10000",
"path": "C:\\Windows\\system32\\gdi32.dll"
},
{
"name": "msvcrt",
"base_addr": "0x77F10000",
"path": "C:\\Windows\\system32\\msvcrt.dll"
},
{
"name": "dnsapi",
"base_addr": "0x78F10000",
"path": "C:\\Windows\\system32\\dnsapi.dll"
},
{
"name": "shlwapi",
"base_addr": "0x67000000",
"path": "C:\\Windows\\system32\\shlwapi.dll"
},
{
"name": "advpack",
"base_addr": "0x68F00000",
"path": "C:\\Windows\\system32\\advpack.dll"
},
{
"name": "dbghelp",
"base_addr": "0x62000000",
"path": "C:\\Windows\\system32\\dbghelp.dll"
},
{
"name": "shell32",
"base_addr": "0x69000000",
"path": "C:\\Windows\\system32\\shell32.dll"
},
{
"name": "WTSAPI32",
"base_addr": "0x63000000",
"path": "C:\\Windows\\system32\\WTSAPI32.dll"
},
{
"name": "CRYPT32",
"base_addr": "0x58000000",
"path": "C:\\Windows\\system32\\CRYPT32.dll"
},
{
"name": "mscoree",
"base_addr": "0x53000000",
"path": "C:\\Windows\\system32\\mscoree.dll"
}
]
}
}
+353
View File
@@ -0,0 +1,353 @@
{
"config_version": 0.2,
"description": "Emulate Windows 10 with basic analysis for quicker emulation times",
"emu_engine": "unicorn",
"timeout": 60,
"system": "windows",
"analysis": {
"memory_tracing": false,
"strings": false
},
"exceptions": {
"dispatch_handlers": true
},
"os_ver": {
"name": "windows",
"major": 10,
"minor": 0,
"build": 1703
},
"current_dir": "C:\\Windows\\system32",
"env": {
"comspec": "C:\\Windows\\system32\\cmd.exe",
"systemroot": "C:\\Windows\\system32",
"windir": "C:\\Windows",
"temp": "C:\\Windows\\temp\\"
},
"hostname": "mybox",
"user": {
"name": "user"
},
"symlinks": [
{
"name": "\\??\\C:",
"target": "\\Device\\HarddiskVolume1"
},
{
"name": "\\??\\PhysicalDrive0",
"target": "\\Device\\Harddisk0\\DR0"
}
],
"filesystem": {
"files": [
{
"mode": "full_path",
"emu_path": "c:\\Windows\\system32\\cmd.exe",
"path": "$ROOT$/resources/files/default.bin"
},
{
"mode": "full_path",
"emu_path": "c:\\Windows\\system32\\svchost.exe",
"path": "$ROOT$/resources/files/default.bin"
},
{
"mode": "default",
"path": "$ROOT$/resources/files/default.bin"
}
]
},
"network": {
"dns": {
"names":{
"default": "10.1.2.3",
"google.com": "8.8.8.8",
"localhost": "127.0.0.1"
},
"txt":[
{
"name": "default",
"path": "$ROOT$/resources/web/default.bin"
}
]
},
"http": {
"responses": [
{
"verb": "GET",
"files": [
{
"mode": "by_ext",
"ext": "gif",
"path": "$ROOT$/resources/web/decoy.gif"
},
{
"mode": "by_ext",
"ext": "jpg",
"path": "$ROOT$/resources/web/decoy.jpg"
},
{
"mode": "default",
"path": "$ROOT$/resources/web/default.bin"
}
]
}
]
},
"winsock": {
"responses": [
{
"mode": "default",
"path": "$ROOT$/resources/web/stager.bin"
}
]
}
},
"processes": [
{
"name": "System",
"base_addr": "0x80000000",
"pid": 4,
"path": ""
},
{
"name": "smss",
"base_addr": "0x05000000",
"path": "C:\\Windows\\system32\\smss.exe"
},
{
"name": "csrss",
"base_addr": "0x05510000",
"path": "C:\\Windows\\system32\\csrss.exe"
},
{
"name": "wininit",
"base_addr": "0x05520000",
"path": "C:\\Windows\\system32\\wininit.exe"
},
{
"name": "services",
"base_addr": "0x05530000",
"path": "C:\\Windows\\system32\\services.exe"
},
{
"name": "lsass",
"base_addr": "0x05540000",
"path": "C:\\Windows\\system32\\lsass.exe"
},
{
"name": "winlogon",
"base_addr": "0x05550000",
"path": "C:\\Windows\\system32\\winlogon.exe"
},
{
"name": "svchost",
"base_addr": "0x05560000",
"path": "C:\\Windows\\system32\\svchost.exe"
},
{
"name": "explorer",
"base_addr": "0x05570000",
"path": "C:\\Windows\\explorer.exe"
},
{
"name": "main",
"base_addr": "0x00400000",
"path": "C:\\Windows\\system32\\main.exe",
"command_line": "svchost.exe",
"is_main_exe": true,
"session": 1
}
],
"modules": {
"modules_always_exist": false,
"module_directory_x86": "$ROOT$/winenv/decoys/x86",
"module_directory_x64": "$ROOT$/winenv/decoys/amd64",
"system_modules": [
{
"name": "ntoskrnl",
"base_addr": "0x803d0000",
"path": "C:\\Windows\\system32\\ntoskrnl.exe"
},
{
"name": "hal",
"base_addr": "0xC1000000",
"path": "C:\\Windows\\system32\\hal.dll"
},
{
"name": "ntfs",
"base_addr": "0xC2000000",
"path": "C:\\Windows\\system32\\drivers\\ntfs.sys"
},
{
"name": "netio",
"base_addr": "0xD4000000",
"path": "C:\\Windows\\system32\\drivers\\netio.sys"
},
{
"name": "volmgr",
"base_addr": "0xC6000000",
"path": "C:\\Windows\\system32\\drivers\\volmgr.sys",
"driver": {
"name": "\\Driver\\volmgr",
"devices": [
{
"name": "\\Device\\HarddiskVolume1"
}
]
}
},
{
"name": "disk",
"base_addr": "0xC3000000",
"path": "C:\\Windows\\system32\\drivers\\disk.sys",
"driver": {
"name": "\\Driver\\Disk",
"devices": [
{
"name": "\\Device\\Harddisk0\\DR0"
}
]
}
},
{
"name": "tcpip",
"base_addr": "0xC4000000",
"path": "C:\\Windows\\system32\\drivers\\tcpip.sys",
"driver": {
"name": "\\Driver\\Tcpip",
"devices": [
{
"name": "\\Device\\Tcp"
}
]
}
},
{
"name": "ndis",
"base_addr": "0xC7000000",
"path": "C:\\Windows\\system32\\drivers\\ndis.sys",
"driver": {
"name": "\\Driver\\Ndis",
"devices": [
{
"name": "\\Device\\Ndis"
}
]
}
}
],
"user_modules": [
{
"name": "kernel32",
"base_addr": "0x77000000",
"path": "C:\\Windows\\system32\\kernel32.dll"
},
{
"name": "ntdll",
"base_addr": "0x7C000000",
"path": "C:\\Windows\\system32\\ntdll.dll"
},
{
"name": "ws2_32",
"base_addr": "0x78C00000",
"path": "C:\\Windows\\system32\\ws2_32.dll"
},
{
"name": "wininet",
"base_addr": "0x7BC00000",
"path": "C:\\Windows\\system32\\wininet.dll"
},
{
"name": "winhttp",
"base_addr": "0x7BA00000",
"path": "C:\\Windows\\system32\\winhttp.dll"
},
{
"name": "advapi32",
"base_addr": "0x78000000",
"path": "C:\\Windows\\system32\\advapi32.dll"
},
{
"name": "user32",
"base_addr": "0x77D10000",
"path": "C:\\Windows\\system32\\user32.dll"
},
{
"name": "gdi32",
"base_addr": "0x77E10000",
"path": "C:\\Windows\\system32\\gdi32.dll"
},
{
"name": "msvcrt",
"base_addr": "0x77F10000",
"path": "C:\\Windows\\system32\\msvcrt.dll"
},
{
"name": "dnsapi",
"base_addr": "0x78F10000",
"path": "C:\\Windows\\system32\\dnsapi.dll"
},
{
"name": "shlwapi",
"base_addr": "0x67000000",
"path": "C:\\Windows\\system32\\shlwapi.dll"
},
{
"name": "advpack",
"base_addr": "0x68F00000",
"path": "C:\\Windows\\system32\\advpack.dll"
},
{
"name": "dbghelp",
"base_addr": "0x62000000",
"path": "C:\\Windows\\system32\\dbghelp.dll"
},
{
"name": "shell32",
"base_addr": "0x69000000",
"path": "C:\\Windows\\system32\\shell32.dll"
},
{
"name": "WTSAPI32",
"base_addr": "0x63000000",
"path": "C:\\Windows\\system32\\WTSAPI32.dll"
},
{
"name": "CRYPT32",
"base_addr": "0x58000000",
"path": "C:\\Windows\\system32\\CRYPT32.dll"
},
{
"name": "mscoree",
"base_addr": "0x53000000",
"path": "C:\\Windows\\system32\\mscoree.dll"
}
]
}
}
+373
View File
@@ -0,0 +1,373 @@
{
"config_version": 0.2,
"description": "Emulate Windows 10 with full memory tracing and in-memory string analysis",
"emu_engine": "unicorn",
"timeout": 60,
"system": "windows",
"analysis": {
"memory_tracing": true,
"strings": true
},
"exceptions": {
"dispatch_handlers": true
},
"os_ver": {
"name": "windows",
"major": 10,
"minor": 0,
"build": 1703
},
"current_dir": "C:\\Windows\\system32",
"env": {
"comspec": "C:\\Windows\\system32\\cmd.exe",
"systemroot": "C:\\Windows\\system32",
"windir": "C:\\Windows",
"temp": "C:\\Windows\\temp\\"
},
"hostname": "mybox",
"user": {
"name": "user"
},
"symlinks": [
{
"name": "\\??\\C:",
"target": "\\Device\\HarddiskVolume1"
},
{
"name": "\\??\\PhysicalDrive0",
"target": "\\Device\\Harddisk0\\DR0"
}
],
"filesystem": {
"files": [
{
"mode": "full_path",
"emu_path": "c:\\Windows\\system32\\cmd.exe",
"path": "$ROOT$/resources/files/default.bin"
},
{
"mode": "full_path",
"emu_path": "c:\\Windows\\system32\\svchost.exe",
"path": "$ROOT$/resources/files/default.bin"
},
{
"mode": "default",
"path": "$ROOT$/resources/files/default.bin"
}
]
},
"registry": {
"keys": [
{
"path": "HKEY_LOCAL_MACHINE\\System\\CurrentControlSet\\Services\\usbsamp",
"values": [
{
"name": "DisplayName",
"type": "REG_SZ",
"data": "An example service"
},
{
"name": "Start",
"type": "REG_DWORD",
"data": "0x00000003"
}
]
}
]
},
"network": {
"dns": {
"names":{
"default": "10.1.2.3",
"google.com": "8.8.8.8",
"localhost": "127.0.0.1"
},
"txt":[
{
"name": "default",
"path": "$ROOT$/resources/web/default.bin"
}
]
},
"http": {
"responses": [
{
"verb": "GET",
"files": [
{
"mode": "by_ext",
"ext": "gif",
"path": "$ROOT$/resources/web/decoy.gif"
},
{
"mode": "by_ext",
"ext": "jpg",
"path": "$ROOT$/resources/web/decoy.jpg"
},
{
"mode": "default",
"path": "$ROOT$/resources/web/default.bin"
}
]
}
]
},
"winsock": {
"responses": [
{
"mode": "default",
"path": "$ROOT$/resources/web/stager.bin"
}
]
}
},
"processes": [
{
"name": "System",
"base_addr": "0x80000000",
"pid": 4,
"path": ""
},
{
"name": "smss",
"base_addr": "0x05000000",
"path": "C:\\Windows\\system32\\smss.exe"
},
{
"name": "csrss",
"base_addr": "0x05510000",
"path": "C:\\Windows\\system32\\csrss.exe"
},
{
"name": "wininit",
"base_addr": "0x05520000",
"path": "C:\\Windows\\system32\\wininit.exe"
},
{
"name": "services",
"base_addr": "0x05530000",
"path": "C:\\Windows\\system32\\services.exe"
},
{
"name": "lsass",
"base_addr": "0x05540000",
"path": "C:\\Windows\\system32\\lsass.exe"
},
{
"name": "winlogon",
"base_addr": "0x05550000",
"path": "C:\\Windows\\system32\\winlogon.exe"
},
{
"name": "svchost",
"base_addr": "0x05560000",
"path": "C:\\Windows\\system32\\svchost.exe"
},
{
"name": "explorer",
"base_addr": "0x05570000",
"path": "C:\\Windows\\explorer.exe"
},
{
"name": "main",
"base_addr": "0x00400000",
"path": "C:\\Windows\\system32\\main.exe",
"command_line": "svchost.exe",
"is_main_exe": true,
"session": 1
}
],
"modules": {
"modules_always_exist": false,
"module_directory_x86": "$ROOT$/winenv/decoys/x86",
"module_directory_x64": "$ROOT$/winenv/decoys/amd64",
"system_modules": [
{
"name": "ntoskrnl",
"base_addr": "0x803d0000",
"path": "C:\\Windows\\system32\\ntoskrnl.exe"
},
{
"name": "hal",
"base_addr": "0xC1000000",
"path": "C:\\Windows\\system32\\hal.dll"
},
{
"name": "ntfs",
"base_addr": "0xC2000000",
"path": "C:\\Windows\\system32\\drivers\\ntfs.sys"
},
{
"name": "netio",
"base_addr": "0xD4000000",
"path": "C:\\Windows\\system32\\drivers\\netio.sys"
},
{
"name": "volmgr",
"base_addr": "0xC6000000",
"path": "C:\\Windows\\system32\\drivers\\volmgr.sys",
"driver": {
"name": "\\Driver\\volmgr",
"devices": [
{
"name": "\\Device\\HarddiskVolume1"
}
]
}
},
{
"name": "disk",
"base_addr": "0xC3000000",
"path": "C:\\Windows\\system32\\drivers\\disk.sys",
"driver": {
"name": "\\Driver\\Disk",
"devices": [
{
"name": "\\Device\\Harddisk0\\DR0"
}
]
}
},
{
"name": "tcpip",
"base_addr": "0xC4000000",
"path": "C:\\Windows\\system32\\drivers\\tcpip.sys",
"driver": {
"name": "\\Driver\\Tcpip",
"devices": [
{
"name": "\\Device\\Tcp"
}
]
}
},
{
"name": "ndis",
"base_addr": "0xC7000000",
"path": "C:\\Windows\\system32\\drivers\\ndis.sys",
"driver": {
"name": "\\Driver\\Ndis",
"devices": [
{
"name": "\\Device\\Ndis"
}
]
}
}
],
"user_modules": [
{
"name": "kernel32",
"base_addr": "0x77000000",
"path": "C:\\Windows\\system32\\kernel32.dll"
},
{
"name": "ntdll",
"base_addr": "0x7C000000",
"path": "C:\\Windows\\system32\\ntdll.dll"
},
{
"name": "ws2_32",
"base_addr": "0x78C00000",
"path": "C:\\Windows\\system32\\ws2_32.dll"
},
{
"name": "wininet",
"base_addr": "0x7BC00000",
"path": "C:\\Windows\\system32\\wininet.dll"
},
{
"name": "winhttp",
"base_addr": "0x7BA00000",
"path": "C:\\Windows\\system32\\winhttp.dll"
},
{
"name": "advapi32",
"base_addr": "0x78000000",
"path": "C:\\Windows\\system32\\advapi32.dll"
},
{
"name": "user32",
"base_addr": "0x77D10000",
"path": "C:\\Windows\\system32\\user32.dll"
},
{
"name": "gdi32",
"base_addr": "0x77E10000",
"path": "C:\\Windows\\system32\\gdi32.dll"
},
{
"name": "msvcrt",
"base_addr": "0x77F10000",
"path": "C:\\Windows\\system32\\msvcrt.dll"
},
{
"name": "dnsapi",
"base_addr": "0x78F10000",
"path": "C:\\Windows\\system32\\dnsapi.dll"
},
{
"name": "shlwapi",
"base_addr": "0x67000000",
"path": "C:\\Windows\\system32\\shlwapi.dll"
},
{
"name": "advpack",
"base_addr": "0x68F00000",
"path": "C:\\Windows\\system32\\advpack.dll"
},
{
"name": "dbghelp",
"base_addr": "0x62000000",
"path": "C:\\Windows\\system32\\dbghelp.dll"
},
{
"name": "shell32",
"base_addr": "0x69000000",
"path": "C:\\Windows\\system32\\shell32.dll"
},
{
"name": "WTSAPI32",
"base_addr": "0x63000000",
"path": "C:\\Windows\\system32\\WTSAPI32.dll"
},
{
"name": "CRYPT32",
"base_addr": "0x58000000",
"path": "C:\\Windows\\system32\\CRYPT32.dll"
},
{
"name": "mscoree",
"base_addr": "0x53000000",
"path": "C:\\Windows\\system32\\mscoree.dll"
}
]
}
}
+374
View File
@@ -0,0 +1,374 @@
{
"config_version": 0.2,
"description": "Emulate Windows 7 with full memory tracing and in-memory string analysis",
"emu_engine": "unicorn",
"timeout": 60,
"system": "windows",
"analysis": {
"memory_tracing": true,
"strings": true
},
"exceptions": {
"dispatch_handlers": true
},
"os_ver": {
"name": "windows",
"major": 6,
"minor": 1,
"build": 7601
},
"current_dir": "C:\\Windows\\system32",
"env": {
"comspec": "C:\\Windows\\system32\\cmd.exe",
"systemroot": "C:\\Windows\\system32",
"windir": "C:\\Windows",
"temp": "C:\\Windows\\temp\\"
},
"hostname": "mybox",
"user": {
"name": "user"
},
"symlinks": [
{
"name": "\\??\\C:",
"target": "\\Device\\HarddiskVolume1"
},
{
"name": "\\??\\PhysicalDrive0",
"target": "\\Device\\Harddisk0\\DR0"
}
],
"filesystem": {
"files": [
{
"mode": "full_path",
"emu_path": "c:\\Windows\\system32\\cmd.exe",
"path": "$ROOT$/resources/files/default.bin"
},
{
"mode": "full_path",
"emu_path": "c:\\Windows\\system32\\svchost.exe",
"path": "$ROOT$/resources/files/default.bin"
},
{
"mode": "default",
"path": "$ROOT$/resources/files/default.bin"
}
]
},
"registry": {
"keys": [
{
"path": "HKEY_LOCAL_MACHINE\\System\\CurrentControlSet\\Services\\usbsamp",
"values": [
{
"name": "DisplayName",
"type": "REG_SZ",
"data": "An example service"
},
{
"name": "Start",
"type": "REG_DWORD",
"data": "0x00000003"
}
]
}
]
},
"network": {
"dns": {
"names":{
"default": "10.1.2.3",
"google.com": "8.8.8.8",
"localhost": "127.0.0.1"
},
"txt":[
{
"name": "default",
"path": "$ROOT$/resources/web/default.bin"
}
]
},
"http": {
"responses": [
{
"verb": "GET",
"files": [
{
"mode": "by_ext",
"ext": "gif",
"path": "$ROOT$/resources/web/decoy.gif"
},
{
"mode": "by_ext",
"ext": "jpg",
"path": "$ROOT$/resources/web/decoy.jpg"
},
{
"mode": "default",
"path": "$ROOT$/resources/web/default.bin"
}
]
}
]
},
"winsock": {
"responses": [
{
"mode": "default",
"path": "$ROOT$/resources/web/stager.bin"
}
]
}
},
"processes": [
{
"name": "System",
"base_addr": "0x80000000",
"pid": 4,
"path": ""
},
{
"name": "smss",
"base_addr": "0x05000000",
"path": "C:\\Windows\\system32\\smss.exe"
},
{
"name": "csrss",
"base_addr": "0x05510000",
"path": "C:\\Windows\\system32\\csrss.exe"
},
{
"name": "wininit",
"base_addr": "0x05520000",
"path": "C:\\Windows\\system32\\wininit.exe"
},
{
"name": "services",
"base_addr": "0x05530000",
"path": "C:\\Windows\\system32\\services.exe"
},
{
"name": "lsass",
"base_addr": "0x05540000",
"path": "C:\\Windows\\system32\\lsass.exe"
},
{
"name": "winlogon",
"base_addr": "0x05550000",
"path": "C:\\Windows\\system32\\winlogon.exe"
},
{
"name": "svchost",
"base_addr": "0x05560000",
"path": "C:\\Windows\\system32\\svchost.exe"
},
{
"name": "explorer",
"base_addr": "0x05570000",
"path": "C:\\Windows\\explorer.exe"
},
{
"name": "main",
"base_addr": "0x00400000",
"path": "C:\\Windows\\system32\\main.exe",
"command_line": "svchost.exe",
"is_main_exe": true,
"session": 1
}
],
"modules": {
"modules_always_exist": false,
"module_directory_x86": "$ROOT$/winenv/decoys/x86",
"module_directory_x64": "$ROOT$/winenv/decoys/amd64",
"system_modules": [
{
"name": "ntoskrnl",
"base_addr": "0x803d0000",
"path": "C:\\Windows\\system32\\ntoskrnl.exe"
},
{
"name": "hal",
"base_addr": "0xC1000000",
"path": "C:\\Windows\\system32\\hal.dll"
},
{
"name": "ntfs",
"base_addr": "0xC2000000",
"path": "C:\\Windows\\system32\\drivers\\ntfs.sys"
},
{
"name": "netio",
"base_addr": "0xD4000000",
"path": "C:\\Windows\\system32\\drivers\\netio.sys"
},
{
"name": "volmgr",
"base_addr": "0xC6000000",
"path": "C:\\Windows\\system32\\drivers\\volmgr.sys",
"driver": {
"name": "\\Driver\\volmgr",
"devices": [
{
"name": "\\Device\\HarddiskVolume1"
}
]
}
},
{
"name": "disk",
"base_addr": "0xC3000000",
"path": "C:\\Windows\\system32\\drivers\\disk.sys",
"driver": {
"name": "\\Driver\\Disk",
"devices": [
{
"name": "\\Device\\Harddisk0\\DR0"
}
]
}
},
{
"name": "tcpip",
"base_addr": "0xC4000000",
"path": "C:\\Windows\\system32\\drivers\\tcpip.sys",
"driver": {
"name": "\\Driver\\Tcpip",
"devices": [
{
"name": "\\Device\\Tcp"
}
]
}
},
{
"name": "ndis",
"base_addr": "0xC7000000",
"path": "C:\\Windows\\system32\\drivers\\ndis.sys",
"driver": {
"name": "\\Driver\\Ndis",
"devices": [
{
"name": "\\Device\\Ndis"
}
]
}
}
],
"user_modules": [
{
"name": "kernel32",
"base_addr": "0x77000000",
"path": "C:\\Windows\\system32\\kernel32.dll"
},
{
"name": "ntdll",
"base_addr": "0x7C000000",
"path": "C:\\Windows\\system32\\ntdll.dll"
},
{
"name": "ws2_32",
"base_addr": "0x78C00000",
"path": "C:\\Windows\\system32\\ws2_32.dll"
},
{
"name": "wininet",
"base_addr": "0x7BC00000",
"path": "C:\\Windows\\system32\\wininet.dll"
},
{
"name": "winhttp",
"base_addr": "0x7BA00000",
"path": "C:\\Windows\\system32\\winhttp.dll"
},
{
"name": "advapi32",
"base_addr": "0x78000000",
"path": "C:\\Windows\\system32\\advapi32.dll"
},
{
"name": "user32",
"base_addr": "0x77D10000",
"path": "C:\\Windows\\system32\\user32.dll"
},
{
"name": "gdi32",
"base_addr": "0x77E10000",
"path": "C:\\Windows\\system32\\gdi32.dll"
},
{
"name": "msvcrt",
"base_addr": "0x77F10000",
"path": "C:\\Windows\\system32\\msvcrt.dll"
},
{
"name": "dnsapi",
"base_addr": "0x78F10000",
"path": "C:\\Windows\\system32\\dnsapi.dll"
},
{
"name": "shlwapi",
"base_addr": "0x67000000",
"path": "C:\\Windows\\system32\\shlwapi.dll"
},
{
"name": "advpack",
"base_addr": "0x68F00000",
"path": "C:\\Windows\\system32\\advpack.dll"
},
{
"name": "dbghelp",
"base_addr": "0x62000000",
"path": "C:\\Windows\\system32\\dbghelp.dll"
},
{
"name": "shell32",
"base_addr": "0x69000000",
"path": "C:\\Windows\\system32\\shell32.dll"
},
{
"name": "WTSAPI32",
"base_addr": "0x63000000",
"path": "C:\\Windows\\system32\\WTSAPI32.dll"
},
{
"name": "CRYPT32",
"base_addr": "0x58000000",
"path": "C:\\Windows\\system32\\CRYPT32.dll"
},
{
"name": "mscoree",
"base_addr": "0x53000000",
"path": "C:\\Windows\\system32\\mscoree.dll"
}
]
}
}
+3
View File
@@ -0,0 +1,3 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
__all__ = ["unicorn_eng"]
+266
View File
@@ -0,0 +1,266 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
# Unicorn specific wrappers and abstraction implemented here
import platform
import ctypes as ct
import pkg_resources
import unicorn as uc
import unicorn.unicorn
import unicorn.x86_const as u
import speakeasy.winenv.arch as arch
import speakeasy.common as common
from speakeasy.errors import EmuEngineError
_uc = unicorn.unicorn._uc
_uc.uc_hook_add = _uc.uc_hook_add
_uc.uc_hook_add.argtypes = [ct.c_void_p, ct.c_void_p, ct.c_void_p, ct.c_void_p,
ct.c_void_p, ct.c_uint64, ct.c_uint64]
_uc.uc_hook_add.restype = ct.c_uint32
hook_id = ct.c_void_p()
# Unicorn version we can assert on
# 1.0.2rc4
__required_version__ = '1.0.2rc4'
def is_platform_intel():
mach = platform.machine()
if mach in ('x86_64', 'i386', 'x86'):
return True
return False
class ToggleableHook(object):
"""
Hook than can be toggled on/off at arbitrary times.
"""
def __init__(self, cb):
self.cb = cb
self.enabled = False
def enable(self):
if self.enabled:
return
self.enabled = True
def disable(self):
self.enabled = False
class EmuEngine(object):
"""Wrapper class for underlying cpu emulation engines"""
def __init__(self):
self.name = 'unicorn'
self.emu = None
self.mmap = None
self._callbacks = {}
unicorn_ver = pkg_resources.get_distribution("unicorn").version
assert __required_version__ == unicorn_ver, ('Requires unicorn version: %s' %
(__required_version__))
self.regs = {
arch.X86_REG_EAX: u.UC_X86_REG_EAX,
arch.X86_REG_EBX: u.UC_X86_REG_EBX,
arch.X86_REG_ESP: u.UC_X86_REG_ESP,
arch.X86_REG_EIP: u.UC_X86_REG_EIP,
arch.X86_REG_EBP: u.UC_X86_REG_EBP,
arch.X86_REG_ECX: u.UC_X86_REG_ECX,
arch.X86_REG_EDX: u.UC_X86_REG_EDX,
arch.X86_REG_EDI: u.UC_X86_REG_EDI,
arch.X86_REG_ESI: u.UC_X86_REG_ESI,
arch.X86_REG_EFLAGS: u.UC_X86_REG_EFLAGS,
arch.AMD64_REG_RIP: u.UC_X86_REG_RIP,
arch.AMD64_REG_RAX: u.UC_X86_REG_RAX,
arch.AMD64_REG_RBX: u.UC_X86_REG_RBX,
arch.AMD64_REG_RSP: u.UC_X86_REG_RSP,
arch.AMD64_REG_RCX: u.UC_X86_REG_RCX,
arch.AMD64_REG_RDX: u.UC_X86_REG_RDX,
arch.AMD64_REG_RSI: u.UC_X86_REG_RSI,
arch.AMD64_REG_RDI: u.UC_X86_REG_RDI,
arch.AMD64_REG_RBP: u.UC_X86_REG_RBP,
arch.AMD64_REG_R8: u.UC_X86_REG_R8,
arch.AMD64_REG_R9: u.UC_X86_REG_R9,
arch.AMD64_REG_R10: u.UC_X86_REG_R10,
arch.AMD64_REG_R11: u.UC_X86_REG_R11,
arch.AMD64_REG_R12: u.UC_X86_REG_R12,
arch.AMD64_REG_R13: u.UC_X86_REG_R13,
arch.AMD64_REG_R14: u.UC_X86_REG_R14,
arch.AMD64_REG_R15: u.UC_X86_REG_R15,
arch.X86_REG_IDTR: u.UC_X86_REG_IDTR,
arch.X86_REG_XMM0: u.UC_X86_REG_XMM0,
arch.X86_REG_XMM1: u.UC_X86_REG_XMM1,
arch.X86_REG_XMM2: u.UC_X86_REG_XMM2,
arch.X86_REG_XMM3: u.UC_X86_REG_XMM3,
arch.X86_REG_GDTR: u.UC_X86_REG_GDTR,
arch.X86_REG_CS: u.UC_X86_REG_CS,
arch.X86_REG_ES: u.UC_X86_REG_ES,
arch.X86_REG_SS: u.UC_X86_REG_SS,
arch.X86_REG_DS: u.UC_X86_REG_DS,
arch.X86_REG_FS: u.UC_X86_REG_FS,
arch.X86_REG_GS: u.UC_X86_REG_GS,
arch.X86_REG_MSR: u.UC_X86_REG_MSR
}
self.mem_access = {
uc.UC_MEM_FETCH_UNMAPPED: common.INVALID_MEM_EXEC, # noqa
uc.UC_MEM_READ_UNMAPPED: common.INVALID_MEM_READ,
uc.UC_MEM_FETCH_PROT: common.INVAL_PERM_MEM_EXEC,
uc.UC_MEM_WRITE_PROT: common.INVAL_PERM_MEM_WRITE,
uc.UC_MEM_READ_PROT: common.INVAL_PERM_MEM_READ,
uc.UC_MEM_WRITE_UNMAPPED: common.INVALID_MEM_WRITE
}
self.perms = {
common.PERM_MEM_RWX: uc.UC_PROT_ALL,
common.PERM_MEM_WRITE: uc.UC_PROT_WRITE,
common.PERM_MEM_READ: uc.UC_PROT_READ,
common.PERM_MEM_RW: uc.UC_PROT_READ | uc.UC_PROT_WRITE
}
self.hook_types = {
common.HOOK_CODE: uc.UC_HOOK_CODE,
common.HOOK_MEM_ACCESS: uc.UC_HOOK_MEM_VALID,
common.HOOK_MEM_INVALID: uc.UC_HOOK_MEM_INVALID,
common.HOOK_MEM_PERM_EXEC: uc.UC_HOOK_MEM_FETCH_PROT,
common.HOOK_MEM_PERM_WRITE: uc.UC_HOOK_MEM_WRITE_PROT,
common.HOOK_MEM_READ: uc.UC_HOOK_MEM_READ,
common.HOOK_MEM_WRITE: uc.UC_HOOK_MEM_WRITE,
common.HOOK_INTERRUPT: uc.UC_HOOK_INTR
}
def _sec_to_usec(self, sec):
"""
Unicorn expects timeouts to be supplied in microsecond granularity
"""
return sec * 1000000
def init_engine(self, eng_arch, mode):
"""Initialize cpu engine"""
if eng_arch == arch.ARCH_X86 or eng_arch == arch.ARCH_AMD64:
_arch = uc.UC_ARCH_X86
else:
raise Exception('Invalid architecture')
if mode == arch.BITS_32:
_mode = uc.UC_MODE_32
elif mode == arch.BITS_64:
_mode = uc.UC_MODE_64
else:
raise Exception('Invalid bitness')
self.emu = uc.Uc(_arch, _mode)
def mem_map(self, base, size, perms=common.PERM_MEM_RWX):
"""Allocate memory in the cpu engine"""
perm = self.perms.get(perms, uc.UC_PROT_ALL)
return self.emu.mem_map(base, size, perm)
def mem_unmap(self, addr, size):
"""Free memory in the cpu engine"""
return self.emu.mem_unmap(addr, size)
def mem_regions(self):
"""Get current memory allocations from the engine"""
return self.emu.mem_regions()
def mem_write(self, addr, data):
"""Write data into the address space of the engine"""
return self.emu.mem_write(addr, data)
def mem_read(self, addr, size):
"""Read data from the address space of the engine"""
return self.emu.mem_read(addr, size)
def mem_protect(self, addr, size, perms):
"""Change the memory protections for pages in the emu engine"""
perm = self.perms.get(perms, uc.UC_PROT_ALL)
return self.emu.mem_protect(addr, size, perm)
def reg_write(self, reg, val):
"""Modify register values"""
ereg = self.regs.get(reg)
if not ereg:
raise EmuEngineError('Unknown register: %d' % (reg))
return self.emu.reg_write(ereg, val)
def reg_read(self, reg):
"""Read register values"""
ereg = self.regs.get(reg)
if not ereg:
raise EmuEngineError('Unknown register: %d' % (reg))
return self.emu.reg_read(ereg)
def stop(self):
"""Stop the emulation engine"""
return self.emu.emu_stop()
def start(self, addr, timeout=0, count=0):
"""Start the emulation engine"""
if count == -1:
count = 0
# Unicorn expects the timeout to be in microseconds, convert it here
timeout = self._sec_to_usec(timeout)
return self.emu.emu_start(addr, 0xFFFFFFFF, timeout=timeout, count=count)
def hook_add(self, addr=None, cb=None, htype=None, ctx=None, begin=1, end=0):
"""
Add a callback function for a specific event type or address
"""
hook_type = self.hook_types.get(htype)
if not hook_type:
raise EmuEngineError('Invalid hook type')
handle = self.emu._uch
# The unicorn bindings have a default python wrapper. We want to use
# our own wrapper and don't need the extra overhead. Add callbacks directly
# to the unicorn library here.
if hook_type == uc.UC_HOOK_CODE:
cb = ct.cast(unicorn.unicorn.UC_HOOK_CODE_CB(cb),
unicorn.unicorn.UC_HOOK_CODE_CB)
elif hook_type in (uc.UC_HOOK_MEM_READ, uc.UC_HOOK_MEM_WRITE):
cb = ct.cast(unicorn.unicorn.UC_HOOK_MEM_ACCESS_CB(cb),
unicorn.unicorn.UC_HOOK_MEM_ACCESS_CB)
elif hook_type == uc.UC_HOOK_MEM_INVALID:
cb = ct.cast(unicorn.unicorn.UC_HOOK_MEM_INVALID_CB(cb),
unicorn.unicorn.UC_HOOK_MEM_INVALID_CB)
else:
return self.emu.hook_add(htype=hook_type, callback=cb, user_data=ctx,
begin=begin, end=end)
ptr = ct.cast(cb, ct.c_void_p)
rv = _uc.uc_hook_add(handle, ct.byref(hook_id), hook_type, ptr.value,
None, begin, end)
if rv != uc.UC_ERR_OK:
raise uc.UcError(rv)
th = ToggleableHook(cb)
self._callbacks.update({hook_id.value: th})
return hook_id.value
def hook_enable(self, hook_handle):
"""
Enable a previously disabled hook
"""
hook = self._callbacks.get(hook_handle)
if hook:
return hook.enable()
def hook_disable(self, hook_handle):
"""
Disable a previously enabled hook
"""
hook = self._callbacks.get(hook_handle)
if hook:
return hook.disable()
def hook_remove(self, hid):
return self.emu.hook_del(hid)
+81
View File
@@ -0,0 +1,81 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
class SpeakeasyError(Exception):
"""
Base class for Speakeasy errors
"""
pass
class NotSupportedError(SpeakeasyError):
"""
Sample is not currently supported
"""
pass
class ApiEmuError(SpeakeasyError):
"""
Base class for API errors
"""
pass
class EmuException(SpeakeasyError):
"""
Base class for emulation errors
"""
pass
class EmuEngineError(SpeakeasyError):
pass
class WindowsEmuError(SpeakeasyError):
"""
Base class for Windows emulation errors
"""
pass
class KernelEmuError(SpeakeasyError):
"""
Base class for Windows kernel mode emulation errors
"""
pass
class Win32EmuError(SpeakeasyError):
"""
Base class for Windows user mode emulation errors
"""
pass
class FileSystemEmuError(SpeakeasyError):
"""
Base class for Windows user mode emulation errors
"""
pass
class NetworkEmuError(SpeakeasyError):
"""
Raised during network emulation errors
"""
pass
class RegistryEmuError(SpeakeasyError):
"""
Raised during registry emulation errors
"""
pass
class ConfigError(SpeakeasyError):
"""
Raised during validating configuration
"""
pass
+355
View File
@@ -0,0 +1,355 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
from itertools import groupby
from operator import itemgetter
import speakeasy.common as common
class MemMap(object):
"""
Class that defines a memory mapping (e.g. heap/pool alloc, binary image, etc.)
"""
def __init__(self, base, size, tag, prot, flags, block_base, block_size,
shared=False, process=None):
self.base = base
self.size = size
base_addr_tag = '.0x%x' % (base)
if tag and base_addr_tag not in tag:
tag += base_addr_tag
if tag:
tag = list(tag)
bad_chars = '\\?[]:]'
[tag.__setitem__(j, '_') for j in [i for i, e in enumerate(tag) if e in bad_chars]]
tag = ''.join(tag)
self.tag = tag
self.prot = prot
self.flags = flags
self.shared = shared
self.free = False
self.process = process
self.block_base = block_base
self.block_size = block_size
def update_tag(self, new_tag):
"""
Set the tag for the memory mapping
"""
self.tag = new_tag
def get_process(self):
"""
Get the process object associated with a memory map
"""
return self.process
def set_process(self, process):
"""
Set the process object associated with a memory map
"""
self.process = process
def get_tag(self):
"""
Get the tag for the memory mapping
"""
return self.tag
def get_prot(self):
"""
Get the memory permissions for a map
"""
return self.prot
def get_flags(self):
"""
Get the memory flags for a map
"""
return self.flags
def get_size(self):
"""
Get the byte size for the current memory mapping
"""
return self.size
def get_base(self):
"""
Get the base address (lowest possible address) of the current memory map
"""
return self.base
def set_alloc(self):
"""
Set the current mapping to be in an allocated state
"""
self.free = False
def set_free(self):
"""
Set the current mapping to be in a free state
"""
self.free = True
def is_free(self):
"""
Return the alloc state of a memory block
"""
return self.free
def __hash__(self):
return hash(self.base)
def __eq__(self, other):
if other is not None:
return self.base == other.base
def __ne__(self, other):
return not(self == other)
class MemoryManager(object):
"""
Primative memory manager used to block OS sized allocation units into something more practical
"""
def __init__(self, *args, **kwargs):
super(MemoryManager, self).__init__()
self.maps = []
self.emu_eng = None
self.mem_reserves = []
self.block_base = 0
self.block_size = 0
self.block_offset = 0
self.page_size = 0x1000
def mem_map(self, size, base=None, perms=common.PERM_MEM_RWX,
tag=None, flags=0, shared=False, process=None):
"""
Map a block of memory with specifed permissions and a tag
"""
if not process and tag and not tag.startswith('emu'):
process = self.get_current_process()
if base is None:
if size < self.page_size and size % self.page_size:
addr = self.block_base + self.block_offset
pad_size = 0x10 - (size % 0x10)
size += pad_size
if not self.block_base or ((addr + size) > self.block_base + self.page_size):
block = self.get_valid_ranges(self.page_size)
self.block_base, self.block_size = block
self.emu_eng.mem_map(self.block_base, self.block_size)
self.block_offset = 0
addr = self.block_base + self.block_offset
self.block_offset += size
base = addr
mm = MemMap(base, size, tag, perms, flags, self.block_base, self.block_size,
shared, process)
self.maps.append(mm)
return base
block = self.get_valid_ranges(size, addr=base)
base, size = block
block_size = self.block_size
if size > self.block_size:
block_size = size
mm = MemMap(base, size, tag, perms, flags, base, block_size, shared, process)
self.emu_eng.mem_map(base, size, perms=perms)
self.maps.append(mm)
return base
def mem_free(self, base):
"""
Free a block of memory, if all blocks in a block are set to free, unmap the entire block
"""
mm = self.get_address_map(base)
if mm:
mm.set_free()
# If we want to freeze memory, just return
if self.keep_memory_on_free:
return
ml = [m for m in self.get_mem_maps() if m.block_base == mm.block_base]
# if all blocks are free in the current block, free it from the emu engine
if all([m.free for m in ml]):
self.block_base = 0
self.mem_unmap(mm.block_base, mm.block_size)
[self.maps.remove(mm) for mm in ml]
def mem_unmap(self, base, size):
"""
Free a block of emulated memory
"""
self.emu_eng.mem_unmap(base, size)
def mem_write(self, addr, data):
"""
Write bytes into the emulated address space
"""
self.emu_eng.mem_write(addr, data)
def mem_read(self, addr, size):
"""
Read bytes from the emulated address space
"""
return bytes(self.emu_eng.mem_read(addr, size))
def mem_protect(self, addr, size, perms):
"""
Change memory protections
"""
self.emu_eng.mem_protect(addr, size, perms)
def _mem_unmap_region(self, base, size):
"""
Remove an entire memory region that may not have blocks allocated within it
"""
self.emu_eng.mem_unmap(base, size)
def get_address_map(self, address):
"""
Get the "MemMap" object associated with a specific address
"""
for m in self.maps:
if m.base <= address <= (m.base + m.size) - 1:
return m
def get_reserve_map(self, address):
"""
Get the "MemMap" object that was only reserved for a specific address
"""
for m in self.mem_reserves:
if m.base <= address <= (m.base + m.size) - 1:
return m
def is_address_valid(self, address):
"""
Was this address previously reserved or mapped?
"""
if self.get_address_map(address):
return True
if self.get_reserve_map(address):
return True
return False
def get_address_tag(self, address):
"""
Get the tag for a supplied memory address
"""
for m in self.maps:
if address >= m.base and address <= (m.base + m.size) - 1:
return m.tag
def mem_reserve(self, size, base=None, perms=None, tag=None, flags=0, shared=False):
"""
Reserve (but do not map) a block of memory
"""
if base is None:
block = self.get_valid_ranges(size)
base, size = block
mm = MemMap(base, size, tag, perms, flags, base, self.block_size, shared)
self.mem_reserves.append(mm)
return base
def purge_memory(self):
"""
Unmamp all current blocks of mapped memory
"""
for region in self.get_mem_regions():
base, end, perms = region
size = (end - base) + 1
self._mem_unmap_region(base, size)
def get_mem_maps(self):
"""
Get the listing of current memory maps
"""
return self.maps
def mem_map_reserve(self, mapped_base):
"""
Map a previously reserved block of memory
"""
for r in self.mem_reserves:
if mapped_base == r.base:
self.mem_reserves.remove(r)
return self.mem_map(r.size, base=r.base, perms=r.prot, tag=r.tag)
return None
def get_mem_regions(self):
"""
Get the current regions of mapped memory
"""
return self.emu_eng.mem_regions()
def get_valid_ranges(self, size, addr=None):
"""
Retrieve a valid address range that can satisfy the requested size.
Optionally, a base address can be specified to test if it can be used
"""
def get_runs(i):
for k, g in groupby(enumerate(i),
lambda ix: ix[0] - (ix[1] >> 12)):
yield tuple(map(itemgetter(1), g))
page_size = self.page_size
# mem_map needs to be page aligned
total = size
# alloced address needs to also be on a page boundary
if addr is None:
addr = page_size
base = addr - (addr % page_size)
if total < page_size:
total = page_size
elif total % page_size:
total += (page_size - (total % page_size))
curr = []
for m in self.get_mem_regions():
curr += range(m[0], m[1], page_size)
# Add reserved memory so we don't accidently allocate it
for res in self.mem_reserves:
curr += range(res.base, (res.base + res.size), page_size)
curr = sorted(set(curr))
attempts = 9999
while attempts:
req = set(range(base, base + total, page_size))
diffs = sorted(req.difference(curr))
if len(diffs) == len(req):
break
if not attempts % 10:
base += (page_size * 1000)
else:
base += total
attempts -= 1
if attempts == 0:
raise Exception('Failed to allocate emulator memory')
a = [r for r in get_runs(diffs)][0]
return (min(a), total)
+501
View File
@@ -0,0 +1,501 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
# Data format versioning
__report_version__ = '1.1.0'
import time
import json
import hashlib
from collections import deque
from base64 import b64encode
PROC_CREATE = 'create'
MEM_ALLOC = 'mem_alloc'
MEM_WRITE = 'mem_write'
MEM_READ = 'mem_read'
MEM_PROTECT = 'mem_protect'
THREAD_INJECT = 'thread_inject'
class ProfileError(Exception):
pass
class MemAccess(object):
"""
Represents a symbolicated chunk of memory that can be tracked
"""
def __init__(self, base=None, sym=None, size=0):
self.base = base
self.size = size
self.sym = sym
self.reads = 0
self.writes = 0
self.execs = 0
class Run(object):
"""
This class represents the basic execution primative for the emulation engine
A "run" can represent any form of execution: a thread, a callback, an exported function,
or even a child process.
"""
def __init__(self):
self.instr_cnt = 0
self.ret_val = None
self.apis = []
self.sym_access = {}
self.network = {'dns': [], 'traffic': []}
self.file_access = []
self.dropped_files = []
self.registry_access = []
self.process_events = []
self.mem_access = {}
self.dyn_code = {'mmap': [], 'base_addrs': set()}
self.process_context = None
self.thread = None
self.unique_apis = []
self.api_hash = hashlib.sha256()
self.handled_exceptions = []
self.stack = None
self.exec_cache = deque(maxlen=4)
self.read_cache = deque(maxlen=4)
self.write_cache = deque(maxlen=4)
self.args = None
self.start_addr = None
self.type = None
self.error = {}
self.num_apis = 0
def get_api_count(self):
"""
Get the number of APIs that were called during the run
"""
return self.num_apis
class Profiler(object):
"""
The profiler class exists to generate an execution report
for all runs that occur within a binary emulation.
"""
def __init__(self):
super(Profiler, self).__init__()
self.start_time = 0
self.strings = {'ansi': [], 'unicode': []}
self.decoded_strings = {'ansi': [], 'unicode': []}
self.last_data = [0, 0]
self.last_event = {}
self.set_start_time()
self.runtime = 0
self.meta = {}
self.runs = []
def add_input_metadata(self, meta):
"""
Add top level profiler fields containing metadata for the
module that will be emulated
"""
self.meta = meta
def set_start_time(self):
"""
Get the start time for a sample so we can time the execution length
"""
self.start_time = time.time()
def get_run_time(self):
"""
Get the time spent emulating a specific "run"
"""
return time.time() - self.start_time
def stop_run_clock(self):
"""
Stop the runtime clock to include in the report
"""
self.runtime = self.get_run_time()
def get_epoch_time(self):
"""
Get the current time in epoch format
"""
return int(time.time())
def add_run(self, run):
"""
Add a new run to the captured run list
"""
self.runs.append(run)
def handle_binary_data(self, data):
"""
Compress and encode binary data to be included in a report
"""
return b64encode(data).decode('utf-8')
def log_error(self, error):
"""
Log a top level emulator error for the emulation report
"""
if not self.meta.get('errors'):
self.meta['errors'] = []
self.meta['errors'].append(error)
def log_dropped_files(self, run, files):
for f in files:
data = f.get_data()
_hash = f.get_hash()
entry = {'path': f.get_path(), 'size': len(data), 'sha256': _hash}
run.dropped_files.append(entry)
def log_api(self, run, pc, name, ret, argv, ctx=[]):
"""
Log a call to an OS API. This includes arguments, return address, and return value
"""
run.num_apis += 1
if name not in run.unique_apis:
run.api_hash.update(name.lower().encode('utf-8'))
run.unique_apis.append(name)
if not run.apis:
run.apis = []
pc = hex(pc)
if ret is not None:
ret = hex(ret)
args = argv.copy()
for i, arg in enumerate(args):
if isinstance(arg, int):
args[i] = hex(arg)
entry = {'pc': pc, 'api_name': name, 'args': args, 'ret_val': ret}
if entry not in run.apis[-3:]:
run.apis.append(entry)
def log_file_access(self, run, path, event_type, data=None,
handle=0, disposition=[], access=[], buffer=0,
size=None):
"""
Log file access events. This will include things like handles being opened,
data reads, and data writes.
"""
for et in ('write', 'read'):
if event_type == et:
for fa in run.file_access:
if path == fa.get('path') and fa['event'] == et:
fa['size'] += size
return
enc = None
if data:
enc = self.handle_binary_data(data[:1024])
event = {'event': event_type, 'path': path}
if enc:
event.update({'data': enc})
if handle:
event.update({'handle': handle})
if size is not None:
event.update({'size': size})
if buffer:
event.update({'buffer': hex(buffer)})
if disposition:
event.update({'open_flags': disposition})
if access:
event.update({'access_flags': access})
if event not in run.file_access:
run.file_access.append(event)
def log_registry_access(self, run, path, event_type, value_name=None, data=None,
handle=0, disposition=[], access=[], buffer=0,
size=None):
"""
Log registry access events. This includes values and keys being accessed and
being read/written
"""
enc = None
if data:
enc = self.handle_binary_data(data[:1024])
event = {'event': event_type, 'path': path}
if enc:
event.update({'data': enc})
if handle:
event.update({'handle': hex(handle)})
if value_name:
event.update({'value_name': value_name})
if size is not None:
event.update({'size': size})
if buffer:
event.update({'buffer': hex(buffer)})
if disposition:
event.update({'open_flags': disposition})
if access:
event.update({'access_flags': access})
if event not in run.registry_access:
run.registry_access.append(event)
def log_process_event(self, run, proc, event_type, kwargs):
"""
Log events related to a process accessing another process. This includes:
creating a child process, reading/writing to a process, or creating a thread
within another process.
"""
event = {}
if event_type == PROC_CREATE:
event.update({'event': event_type})
event.update({'pid': proc.get_id()})
event.update({'path': proc.get_process_path()})
event.update({'cmdline': proc.get_command_line()})
elif event_type == MEM_ALLOC:
event.update({'event': event_type})
event.update({'pid': proc.get_id()})
event.update({'path': proc.get_process_path()})
event.update(kwargs)
elif event_type == MEM_PROTECT:
event.update({'event': event_type})
event.update({'pid': proc.get_id()})
event.update({'path': proc.get_process_path()})
event.update(kwargs)
elif event_type == MEM_WRITE:
base = kwargs['base']
size = kwargs['size']
data = kwargs['data']
last_base, last_size = self.last_data
last_evt_type = self.last_event.get('event')
if event_type == last_evt_type and (last_base + last_size) == base:
self.last_event['data'] += data
self.last_event['size'] += len(data)
self.last_data = [base, size]
return
event.update({'event': event_type})
event.update({'pid': proc.get_id()})
event.update({'path': proc.get_process_path()})
data = kwargs['data']
event.update({'data': data})
event.update({'base': base})
event.update({'size': size})
self.last_data = [base, size]
elif event_type == MEM_READ:
base = kwargs['base']
size = kwargs['size']
data = kwargs['data']
last_base, last_size = self.last_data
last_evt_type = self.last_event.get('event')
if event_type == last_evt_type and (last_base + last_size) == base:
self.last_event['data'] += data
self.last_event['size'] += len(data)
self.last_data = [base, size]
return
event.update({'event': event_type})
event.update({'pid': proc.get_id()})
event.update({'path': proc.get_process_path()})
data = kwargs['data']
event.update({'data': data})
event.update({'size': size})
event.update({'base': base})
self.last_data = [base, size]
elif event_type == THREAD_INJECT:
event.update({'event': event_type})
event.update({'pid': proc.get_id()})
event.update({'path': proc.get_process_path()})
event.update({'start_addr': hex(kwargs['start_addr'])})
event.update({'param': hex(kwargs['param'])})
run.process_events.append(event)
self.last_event = event
def log_dns(self, run, domain, ip=''):
"""
Log DNS name lookups for the emulation report
"""
query = {"query": domain, "response": ip}
if query not in run.network['dns']:
run.network['dns'].append(query)
def log_http(self, run, server, port, proto='http',
headers='', body=b'', secure=False):
"""
Log HTTP traffic that occur during emulation
"""
conns = run.network['traffic']
proto = 'http'
if secure:
proto = 'https'
http_conn = {'server': server, 'proto': 'tcp.%s' % (proto), 'port': port,
'headers': headers}
if body:
data = self.handle_binary_data(body[:0x3000])
http_conn.update({'body': data})
if http_conn not in conns:
conns.append(http_conn)
def log_dyn_code(self, run, tag, base, size):
"""
Log code that is generated at runtime and then executed
"""
if base not in run.dyn_code['base_addrs']:
entry = {'tag': tag, 'base': hex(base), 'size': hex(size)}
run.dyn_code['mmap'].append(entry)
run.dyn_code['base_addrs'].add(base)
def log_network(self, run, server, port, typ='unknown', proto='unknown', data=b'', method=''):
"""
Log network activity for an emulation run
"""
conns = run.network['traffic']
conn = {'server': server, 'proto': proto, 'port': port}
if data:
data = self.handle_binary_data(data[:0x3000])
conn.update({'data': data})
if method:
conn.update({'method': method})
conn.update({'type': typ})
conns.append(conn)
def get_json_report(self):
"""
Retrieve the execution profile for the emulator as a json string
"""
profile = self.get_report()
return json.dumps(profile, indent=4, sort_keys=False)
def get_report(self):
"""
Retrieve the execution profile for the emulator
"""
profile = {}
meta = self.meta
meta.update({'report_version': __report_version__})
meta.update({'emulation_total_runtime': round(self.runtime, 3)})
meta.update({'timestamp': int(self.start_time)})
# For now, we only support single file emulation
exec_paths = []
for r in self.runs:
if r.ret_val is not None:
ret = hex(r.ret_val)
else:
ret = None
args = []
for a in r.args:
if isinstance(a, int):
args.append(hex(a))
else:
args.append(a)
ep = {'ep_type': r.type,
'start_addr': hex(r.start_addr),
'ep_args': args,
}
if r.instr_cnt:
ep.update({'instr_count': r.instr_cnt})
ep.update(
{
'apihash': r.api_hash.hexdigest(),
'apis': r.apis,
'ret_val': ret,
'error': r.error
}
)
if r.handled_exceptions:
ep.update({"handled_exceptions": r.handled_exceptions})
if r.network and (r.network.get('dns', []) or
r.network.get('traffic', {})):
ep.update({'network_events': r.network})
if r.file_access:
ep.update({'file_access': r.file_access})
if r.registry_access:
ep.update({'registry_access': r.registry_access})
if r.process_events:
for evt in r.process_events:
if evt.get('event') in (MEM_WRITE, MEM_READ):
evt['data'] = self.handle_binary_data(evt['data'][:1024])
if evt.get('base'):
evt['base'] = hex(evt['base'])
ep.update({'process_events': r.process_events})
if r.mem_access:
mem_accesses = []
for mmap, maccess in r.mem_access.items():
mem_accesses.append({'tag': mmap.get_tag(),
'base': hex(mmap.get_base()),
'reads': maccess.reads,
'writes': maccess.writes,
'execs': maccess.execs})
ep.update({'mem_access': mem_accesses})
sym_accesses = []
for address, maccess in r.sym_access.items():
sym_accesses.append({'symbol': maccess.sym,
'reads': maccess.reads,
'writes': maccess.writes,
'execs': maccess.execs})
if sym_accesses:
ep.update({'sym_accesses': sym_accesses})
if r.dyn_code:
ep.update({'dynamic_code_segments': r.dyn_code['mmap']})
exec_paths.append(ep)
if r.dropped_files:
ep.update({'dropped_files': r.dropped_files})
if (self.strings['ansi'] or self.strings['unicode'] or
self.decoded_strings['ansi'] or self.decoded_strings['unicode']):
meta.update({'strings': {'static':self.strings, 'in_memory': self.decoded_strings}}) # noqa
profile = {**profile, **meta}
profile.update({'entry_points': exec_paths})
return profile
+1
View File
@@ -0,0 +1 @@
瑞瑞瑞瑞瑞瑞瑞瑞フ
+1
View File
@@ -0,0 +1 @@
GIF89a��������������Ã

After

Width:  |  Height:  |  Size: 21 B

+1
View File
@@ -0,0 +1 @@
瑞瑞瑞瑞瑞瑞静
+1
View File
@@ -0,0 +1 @@
瑞瑞瑞瑞瑞瑞瑞瑞昔フフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフ
Binary file not shown.
+643
View File
@@ -0,0 +1,643 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
import os
import json
import ntpath
import hashlib
import zipfile
from io import BytesIO
from typing import Callable
from pefile import MACHINE_TYPE
import jsonschema
import jsonschema.exceptions
import speakeasy
import speakeasy.winenv.arch as _arch
from speakeasy import PeFile
from speakeasy import Win32Emulator
from speakeasy import WinKernelEmulator
from speakeasy.errors import SpeakeasyError, ConfigError, NotSupportedError
class Speakeasy(object):
"""
Wrapper class for invoking the speakeasy emulators
"""
def check_init(func):
"""Wrapper to make sure the emulator is initialized"""
def wrap(self, *args, **kwargs):
if not self.emu:
raise SpeakeasyError('Emulator not initialized')
return func(self, *args, **kwargs)
return wrap
def __init__(self, config=None, logger=None, argv=[], debug=False, exit_event=None):
self.logger = logger
self._init_config(config)
self.emu = None
self.api_hooks = []
self.code_hooks = []
self.mem_read_hooks = []
self.argv = argv
self.exit_event = exit_event
self.debug = debug
self.loaded_bins = []
self.mem_write_hooks = []
self.mem_invalid_hooks = []
self.interrupt_hooks = []
def _init_config(self, config: dict) -> None:
"""
Init the emulator config
args:
config: The configuration data to set up the emulator environment
If none is supplied, a default config is used.
return:
None
"""
if not config:
config_path = os.path.join(os.path.dirname(speakeasy.__file__),
'configs', 'default.json')
with open(config_path, 'r') as f:
self.config = json.load(f)
else:
self.config = config
try:
validate_config(self.config)
except jsonschema.exceptions.SchemaError as err:
if self.logger:
self.logger.exception('Invalid config schema: %s', str(err))
raise ConfigError('Invalid config schema')
except jsonschema.exceptions.ValidationError as err:
if self.logger:
self.logger.exception('Invalid config: %s', str(err))
raise ConfigError('Invalid config')
def _init_emulator(self, path=None, data=None, is_raw_code=False) -> None:
"""
Based on the PE metadata, use the appropriate emulator. That is,
a user mode emulator vs a kernel mode emulator
"""
if not is_raw_code:
pe = PeFile(path=path, data=data)
# Get the machine type we only support x86/x64 atm
mach = MACHINE_TYPE[pe.FILE_HEADER.Machine].split('_')[-1:][0].lower()
if mach not in ('amd64', 'i386'):
raise SpeakeasyError('Unsupported architecture: %s' % mach)
if pe.is_dotnet():
raise NotSupportedError('.NET assemblies are not currently supported')
if pe.is_driver():
self.emu = WinKernelEmulator(config=self.config, logger=self.logger,
debug=self.debug, exit_event=self.exit_event)
else:
self.emu = Win32Emulator(config=self.config, logger=self.logger, argv=self.argv,
debug=self.debug, exit_event=self.exit_event)
else:
self.emu = Win32Emulator(config=self.config, logger=self.logger, argv=self.argv,
debug=self.debug, exit_event=self.exit_event)
def _init_hooks(self) -> None:
"""
Lazily add hooks if users added them early before emulator engine was instantiated
"""
# Add any configured hooks here
while self.api_hooks:
h = self.api_hooks.pop(0)
cb, mod, func, argc, cconv = h
self.add_api_hook(cb, mod, func, argc, cconv)
while self.code_hooks:
h = self.code_hooks.pop(0)
cb, begin, end, ctx = h
self.add_code_hook(cb, begin, end, ctx)
while self.mem_read_hooks:
h = self.mem_read_hooks.pop(0)
cb, begin, end = h
self.add_mem_read_hook(cb, begin, end)
while self.mem_write_hooks:
h = self.mem_write_hooks.pop(0)
cb, begin, end = h
self.add_mem_write_hook(cb, begin, end)
while self.mem_invalid_hooks:
h = self.mem_invalid_hooks.pop(0)
cb, = h
self.add_mem_invalid_hook(cb)
while self.interrupt_hooks:
h = self.interrupt_hooks.pop(0)
cb, ctx = h
self.add_interrupt_hook(cb, ctx)
def disasm(self, addr: int, size: int):
"""
Get the disassembly from an address
args:
addr: address to being disassebmly
size: number of bytes to include
return:
A tuple of: (mnemonic, operands, and the full instruction)
"""
return self.emu.get_disasm(addr, size)
def is_pe(self, data: bytes) -> bool:
"""
Test data to see if it looks like a PE
args:
data: Bytes to be tested for a PE
return:
True is data appears to be a PE
"""
# Check for the PE header
if data[:2] == b'MZ':
return True
else:
return False
def load_module(self, path=None, data=None) -> PeFile:
"""
Load a module into the speakeasy emulator
args:
path: Path to file to load into the emulation space
data: Raw data to load as a module into the emulation space
return:
A PeFile object representing the newly loaded module
"""
if not path and not data:
raise SpeakeasyError('No emulation target supplied')
if path and not os.path.exists(path):
raise SpeakeasyError('Target file not found: %s' % (path))
if data:
test = data
else:
with open(path, 'rb') as f:
test = f.read(4)
self.loaded_bins.append(path)
if not self.is_pe(test):
raise SpeakeasyError('Target file is not a PE')
self._init_emulator(path=path, data=data)
return self.emu.load_module(path=path, data=data)
@check_init
def run_module(self, module, all_entrypoints=False) -> None:
"""
Run a previously loaded module through the configured emulator
args:
module: The module whose entry point to be run
all_entrypoints: If true, all exports will be emulated, otherwise
just the main PE entry point is emulated.
return:
None
"""
self._init_hooks()
return self.emu.run_module(module=module, all_entrypoints=all_entrypoints)
def load_shellcode(self, fpath, arch, data=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
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)
@check_init
def run_shellcode(self, sc_addr: int, offset=0) -> None:
"""
Run a previously loaded shellcode blob by address
args:
sc_addr: address of the previously loaded shellcode blog to emulate
offset: offset within the blob to begin emulation
return:
None
"""
self._init_hooks()
return self.emu.run_shellcode(sc_addr, offset=offset)
@check_init
def get_report(self) -> dict:
"""
Get the emulation report from the emulator
return:
Get the raw emulation report as a python dictionary
"""
return self.emu.get_report()
@check_init
def get_json_report(self) -> str:
"""
Get the emulation report from the emulator formatted as a JSON string
return:
Get the emulation report as a JSON object
"""
return self.emu.get_json_report()
def add_api_hook(self, cb: Callable, module='', api_name='', argc=0, call_conv=None):
"""
Set a callback to fire when a specified API is called during emulation
args:
cb: Callable python function to execute
module: name of the module containing the target API
api_name: Name of the API to hook. Wild cards (e.g. *) are supported.
argc: force the emulator to account for this amount of arguments (for stack cleanup)
call_conv: force the emulator to use the suppolied calling convention for this hook
return:
Hook object for newly registered hooks
"""
if not self.emu:
self.api_hooks.append((cb, module, api_name, argc, call_conv))
return
return self.emu.add_api_hook(cb, module=module, api_name=api_name, argc=argc,
call_conv=call_conv, emu=self)
def stop(self) -> None:
"""
Stops emulation
"""
return self.emu.stop()
def call(self, addr: int, params=[]) -> None:
"""
Start emulating at the specified address
args:
addr: Address to being emulation at
params: list of arguments to push onto the stack for the call
return:
None
"""
return self.emu.call(addr, params=params)
def add_code_hook(self, cb: Callable, begin=1, end=0, ctx={}):
"""
Set a callback to fire for every CPU instruction that is emulated
args:
cb: Callable python function to execute
begin: beginning of the address range to hook
end: end of the address range to hook
ctx: Optional context to pass back and forth between the hook function
return:
Hook object for newly registered hooks
"""
if not self.emu:
self.code_hooks.append((cb, begin, end, ctx))
return
return self.emu.add_code_hook(cb, begin=begin, end=end, ctx=ctx, emu=self)
def add_mem_read_hook(self, cb: Callable, begin=1, end=0):
"""
Set a callback to fire when a memory address is read from
args:
cb: Callable python function to execute
begin: beginning of the address range to hook
end: end of the address range to hook
return:
Hook object for newly registered hooks
"""
if not self.emu:
self.mem_read_hooks.append((cb, begin, end))
return
return self.emu.add_mem_read_hook(cb, begin=begin, end=end, emu=self)
def add_mem_write_hook(self, cb: Callable, begin=1, end=0):
"""
Set a callback to fire when a memory address is written to
args:
cb: Callable python function to execute
begin: beginning of the address range to hook
end: end of the address range to hook
return:
Hook object for newly registered hooks
"""
if not self.emu:
self.mem_write_hooks.append((cb, begin, end))
return
return self.emu.add_mem_write_hook(cb, begin=begin, end=end, emu=self)
def add_mem_invalid_hook(self, cb: Callable):
"""
Get a callback for when a memory access violation occurs
args:
cb: Callable python function to execute
return:
Hook object for newly registered hooks
"""
if not self.emu:
self.mem_invalid_hooks.append((cb, ))
return
return self.emu.add_mem_invalid_hook(cb, emu=self)
def add_interrupt_hook(self, cb: Callable, ctx={}):
"""
Get a callback for software interrupts
args:
cb: Callable python function to execute
ctx: Optional context to pass back and forth between the hook function
return:
Hook object for newly registered hooks
"""
if not self.emu:
self.interrupt_hooks.append((cb, ))
return
return self.emu.add_interrupt_hook(cb, ctx=ctx, emu=self)
def get_registry_key(self, handle=0, path=''):
"""
Get registry key by path or handle
args:
handle: handle corresponding for a registry key
path: Path to a registry key
return:
If valid, a registry key object
"""
return self.emu.reg_get_key(handle=handle, path=path)
def get_address_map(self, addr: int):
"""
Get the address mapping object associated with the specified address
args:
addr: Address in the emulation space
return:
A memory map object that holds the specified address
"""
return self.emu.get_address_map(addr)
def mem_alloc(self, size, base=None, tag='speakeasy.None') -> int:
"""
Allocate a block of memory in the emulation space
args:
size: Size of requested memory block
base: Optionally request a base address. If in use, the next nearest
address will be returned
tag: Tag to assign the new memory mapping
return:
Address of the newly allocated memory block
"""
return self.emu.mem_map(size, base=base, tag=tag)
def mem_free(self, base: int) -> None:
"""
Free a block of memory in the emulation space
args:
base: Address to free
return:
None
"""
return self.emu.mem_free(base)
def mem_read(self, addr: int, size: int) -> bytes:
"""
Read bytes from a memory address
args:
addr: address to read bytes from
size: number of bytes to read
return:
Python bytes object contained the data read
"""
return self.emu.mem_read(addr, size)
def mem_write(self, addr: int, data: bytes) -> None:
"""
Write bytes to a memory address
args:
addr: address to write bytes to
data: data to write
return:
None
"""
return self.emu.mem_write(addr, data)
def mem_cast(self, obj, addr: int):
"""
Cast an address as an object for easy access
args:
obj: object to cast into
addr: address containing the data to cast into type "obj"
return:
Python object based on the data located at addr
"""
return self.emu.mem_cast(obj, addr)
def reg_read(self, reg: str) -> int:
"""
Read value from a register
args:
reg: name of the register to read from
return:
value contained in the requested register
"""
return self.emu.reg_read(reg)
def get_dyn_imports(self) -> list:
"""
Returns the imports dynamically resolved at runtime
return:
List of functions that were resolved at runtime (e.g. GetProcAddress,
MmGetSystemRoutineAddress)
"""
return self.emu.get_dyn_imports()
def reg_write(self, reg: str, val: int) -> None:
"""
Write value to a register
args:
reg: name of the register to write to
return:
None
"""
return self.emu.reg_write(reg, val)
def get_dropped_files(self) -> list:
"""
Get files that were written to disk during emulation
return:
Returns a list of files that were written by the sample
"""
return self.emu.get_dropped_files()
def create_file_archive(self) -> bytes:
"""
Creates a file archive package.
The archive contains a manifest that can be used to match dropped files
metadata with the acquired files.
return:
A Bytes object containing a zip archive of dropped files
"""
manifest = []
_zip = BytesIO()
files = self.get_dropped_files()
if not files:
return b''
with zipfile.ZipFile(_zip, mode="w", compression=zipfile.ZIP_DEFLATED) as zf:
for f in files:
path = f.get_path()
file_name = ntpath.basename(path)
manifest.append({'path': path,
'file_name': file_name,
'size': f.get_size(),
'sha256': f.get_hash()})
zf.writestr(file_name, f.get_data())
manifest = json.dumps(manifest, indent=4, sort_keys=False)
zf.writestr('speakeasy_manifest.json', manifest)
return _zip.getvalue()
def get_mem_maps(self) -> list:
"""
Get all memory maps in the emulation space
return:
A list of all valid memory maps from the emulator
"""
return self.emu.get_mem_maps()
def get_memory_dumps(self) -> tuple:
"""
Returns all memory contents along with context information
return:
A generator of tuples of all valid memory with context
"""
for mm in self.emu.get_mem_maps():
base = mm.get_base()
size = mm.get_size()
tag = mm.get_tag()
proc = mm.get_process()
is_free = mm.is_free()
try:
data = self.emu.mem_read(base, size)
except Exception:
continue
yield (tag, base, size, is_free, proc, data)
def create_memdump_archive(self) -> bytes:
"""
Creates a memory dump archive package of the emulated sample.
The archive contains a manifest that can be used to match memory chunk
metadata with the dumped binary memory files.
return:
Bytes object containing a zip of all memory
"""
manifest = []
_zip = BytesIO()
loaded_bins = [os.path.splitext(os.path.basename(b))[0] for b in self.loaded_bins]
with zipfile.ZipFile(_zip, mode="w", compression=zipfile.ZIP_DEFLATED) as zf:
procs = []
[procs.append(block[4]) for block in self.get_memory_dumps()
if block[4] not in procs]
for process in procs:
memory_blocks = []
arch = self.emu.get_arch()
if arch == _arch.ARCH_X86:
arch = 'x86'
else:
arch = 'amd64'
if process:
pid = process.get_pid()
path = process.get_process_path()
else:
continue
manifest.append({'pid': pid, 'process_name': path, 'arch': arch,
'memory_blocks': memory_blocks})
for block in self.get_memory_dumps():
tag, base, size, is_free, _proc, data = block
if not tag:
continue
if _proc != process:
continue
# Ignore emulator noise such as structures created by the emulator, or
# modules that were loaded
if tag and tag.startswith('emu') and not tag.startswith('emu.shellcode.'):
bns = [b for b in loaded_bins if b in tag]
if not len(bns):
continue
h = hashlib.sha256()
h.update(data)
_hash = h.hexdigest()
file_name = '%s.mem' % (tag)
memory_blocks.append({'tag': tag, 'base': hex(base), 'size': hex(size),
'is_free': is_free, 'sha256': _hash,
'file_name': file_name})
zf.writestr(file_name, data)
manifest = json.dumps(manifest, indent=4, sort_keys=False)
zf.writestr('speakeasy_manifest.json', manifest)
return _zip.getvalue()
def validate_config(config) -> None:
"""
Validates the given configuration objects against the built-in schemas.
Raises jsonschema.exceptions.ValidationError on invalid configuration.
Expose the underlying jsonschema exception due to it having lots of information
about failures.
On success, returns without exception.
"""
schema_path = os.path.join(os.path.dirname(speakeasy.__file__), 'config_schema.json')
with open(schema_path, 'r') as ff:
schema = json.load(ff)
validator = jsonschema.Draft7Validator(schema)
validator.validate(config)
+313
View File
@@ -0,0 +1,313 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
import ctypes as ct
from ctypes import * # noqa
from collections import OrderedDict
class EmuStructException(Exception):
"""
Container class for struct exceptions
"""
pass
class Enum(object):
"""
For now, a basic python object will serve as a C style enum
"""
pass
class PtrMeta(type):
"""
Metaclass for pointer types
"""
def __mul__(self, mult):
return tuple((self, mult))
class Ptr(object, metaclass=PtrMeta):
"""
Generic object to identify pointer variables that will be expanded
according to the "ptr_size" parameter passed to our init
"""
_points_to_ = None
class CMeta(type):
"""
meta class to hook __call__ and make __dict__ ordered on all versions
of python
"""
@classmethod
def __prepare__(self, name, bases):
# This is default behavior for Python 3.6+ but lets do this anyway
# to make sure __dict__ is ordered on older versions
return OrderedDict()
def __new__(self, name, bases, classdict):
classdict['__ordered__'] = [k for k in classdict.keys()
if k not in ('__module__', '__qualname__')]
return type.__new__(self, name, bases, classdict)
def __call__(cls, *args, **kwargs):
obj = type.__call__(cls, *args, **kwargs)
obj.create_struct()
return obj
def __mul__(self, mult):
return tuple((self, mult))
class EmuStruct(object, metaclass=CMeta):
"""
Advanced Python class for interacting with C structures
"""
# Save the unique types we create here
# This is necessary since the ctypes metaclass won't allow us
# to assign structures even if the types are identical on the surface.
# ctypes will test the id (address) of the type to make sure they match
# upon assignment. Otherwise we will hit spurious TypeErrors.
__types__ = {}
class FilteredStruct(ct.Structure):
def __hash__(self):
return hash(repr(self))
def __init__(self, ptr_size=0, pack=0):
# Set __dict__ directly here to avoid __getattribute__ loops
self.__dict__['__pack__'] = pack
self.__dict__['__struct__'] = None
self.__dict__['__fields__'] = []
self.__dict__['__ptrsize__'] = ptr_size
self.__dict__['__filtermap__'] = {}
def _is_ctype(self, obj):
"""
Test whether the object has a ctype base
"""
tests = (ct._SimpleCData, ct.Structure, ct.Union, ct.Array)
return any([issubclass(obj, t) for t in tests])
def create_struct(self, types={}):
"""
Walk each attribute and handle accordingly. Since ctypes.Structure
is a metaclass, we have to buld the "_fields_" dynamically via a
factory
"""
if self.__struct__:
return
for d, obj in self.__dict__.items():
try:
if isinstance(obj, tuple):
if issubclass(obj[0], EmuStruct):
_type, count = obj
try:
tmp = _type(self.__ptrsize__, self.__pack__)
array = [_type(self.__ptrsize__, self.__pack__) for i in range(count)]
except TypeError:
try:
tmp = _type(self.__ptrsize__)
array = [_type(self.__ptrsize__) for i in range(count)]
except TypeError as e:
raise EmuStructException(str(e))
ctarray = tmp.__struct__.__class__ * count
self.__filtermap__.update({d: array})
self.__fields__.append((d, ctarray))
elif issubclass(obj[0], Ptr):
_type, count = obj
ptype = self.get_ptr_field()
self.__fields__.append((d, (ptype * count)))
except TypeError:
continue
try:
if self._is_ctype(obj):
# Simply append ctypes since they will be handled
# automatically
self.__fields__.append((d, obj))
elif issubclass(obj, Ptr):
# Expand pointers to the required width
self.__fields__.append((d, self.get_ptr_field()))
elif issubclass(obj, EmuStruct):
# Allow nesting of this class which we are calling a
# "filter class". That is, when fields are accessed in the
# underlying ctypes struct, we pass the getattr/setattr through
if obj.__name__ != self.__class__.__name__:
try:
filt = obj(self.__ptrsize__, self.__pack__)
except TypeError:
try:
filt = obj(self.__ptrsize__)
except TypeError as e:
raise EmuStructException(str(e))
cts = filt.__struct__
self.__filtermap__.update({d: filt})
self.__fields__.append((d, cts.__class__))
except TypeError:
continue
self.__init_struct()
def get_ptr_field(self):
"""
Get ctypes value for the required pointer size
"""
if self.__ptrsize__ == 4:
return ct.c_uint32
elif self.__ptrsize__ == 8:
return ct.c_uint64
else:
return ct.c_void_p
def get_pack(self):
"""
Get the required structure pack (defaults to pointer size)
"""
if self.__pack__:
return self.__pack__
else:
if self.__ptrsize__:
return self.__ptrsize__ * 2
else:
return 1
def _link_cstructs(self, obj):
"""
Link the ctypes structures together in the case of nesting.
This will allow the buffer API to convert it to bytes easily
"""
for fname, subobj in obj.__filtermap__.items():
if isinstance(subobj, list):
x = getattr(obj.__struct__, fname)
for i, e in enumerate(x):
self._link_cstructs(subobj[i])
x[i] = subobj[i].__struct__
elif isinstance(subobj, EmuStruct):
self._link_cstructs(subobj)
setattr(obj.__struct__, fname, subobj.__struct__)
def get_bytes(self):
"""
Convert the structure to bytes and respecting endianness
"""
self._link_cstructs(self)
struct = self.__struct__
buf = (ct.c_ubyte * ct.sizeof(struct))()
ct.memmove(buf, ct.byref(struct), ct.sizeof(struct))
return bytes(buf[:])
def sizeof(self):
"""
Get the size of the C structure
"""
return ct.sizeof(self.__struct__)
def _deep_cast(self, obj, bytez, offset):
obj.__struct__ = type(obj.__struct__).from_buffer(bytearray(bytez[offset[0]:]))
for fn, c in obj.__fields__:
subobj = obj.__filtermap__.get(fn)
if subobj:
if isinstance(subobj, list):
for sso in subobj:
self._deep_cast(sso, bytez, offset)
else:
self._deep_cast(subobj, bytez, offset)
else:
offset[0] += ct.sizeof(c)
def cast(self, bytez):
"""
Convert a bytes object to the C structure by "casting" them
"""
offset = [0]
self._deep_cast(self, bytez, offset=offset)
return self
def get_cstruct(self):
return self.__struct__.__class__
def __struct_factory(self, name):
"""
Factory used to generate ctypes structures using the ctypes metaclass
"""
_type_name = 'ct' + name + '%d' % (self.__ptrsize__)
_type = EmuStruct.__types__.get(_type_name)
if not _type:
_type = type(_type_name, (self.__class__.FilteredStruct, ),
{"_pack_": self.get_pack(), "_fields_": self.__fields__})
EmuStruct.__types__[_type_name] = _type
return _type
def __init_struct(self):
self.__struct__ = self.__struct_factory(self.__class__.__name__)()
return self.__struct__
def __setattr__(self, name, value):
"""
Hook setattr so that accessing the underlying ctypes structure can be
handled correctly
"""
struct = self.__struct__
if struct:
fields = struct._fields_
for fn, val in fields:
if fn == name:
if type(value) == bytes:
barray = getattr(struct, fn)
barray[:len(value)] = value
return
struct.__setattr__(fn, value)
return
super(EmuStruct, self).__setattr__(name, value)
def __getattribute__(self, name):
"""
Hook getattribute so that accessing the underlying ctypes structure
can be handled correctly
"""
try:
struct = super(EmuStruct, self).__getattribute__('__struct__')
if struct:
fields = struct._fields_
for fn, val in fields:
if fn == name:
val = struct.__getattribute__(name)
tests = (EmuStruct.FilteredStruct, EmuUnion.FilteredStruct, ct.Array)
if any([isinstance(val, t) for t in tests]):
fm = super(EmuStruct,
self).__getattribute__('__filtermap__')
filt_obj = fm.get(name)
if filt_obj:
return filt_obj
return struct.__getattribute__(name)
except AttributeError:
pass
return super(EmuStruct, self).__getattribute__(name)
class EmuUnion(EmuStruct, metaclass=CMeta):
class FilteredStruct(ct.Union):
def __hash__(self):
return hash(repr(self))
+1
View File
@@ -0,0 +1 @@
__version__ = "1.4.4"
View File
+567
View File
@@ -0,0 +1,567 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
import os
import ntpath
import hashlib
from collections import namedtuple
import pefile
import speakeasy.winenv.arch as _arch
import speakeasy.winenv.defs.nt.ddk as ddk
from speakeasy.struct import Enum
# GDT Constants needed to set our emulator into protected mode
# Access bits
GDT_ACCESS_BITS = Enum()
GDT_ACCESS_BITS.ProtMode32 = 0x4
GDT_ACCESS_BITS.PresentBit = 0x80
GDT_ACCESS_BITS.Ring3 = 0x60
GDT_ACCESS_BITS.Ring0 = 0
GDT_ACCESS_BITS.DataWritable = 0x2
GDT_ACCESS_BITS.CodeReadable = 0x2
GDT_ACCESS_BITS.DirectionConformingBit = 0x4
GDT_ACCESS_BITS.Code = 0x18
GDT_ACCESS_BITS.Data = 0x10
GDT_FLAGS = Enum()
GDT_FLAGS.Ring3 = 0x3
GDT_FLAGS.Ring0 = 0
IMPORT_HOOK_ADDR = 0xFEEDFACE
DEFAULT_LOAD_ADDR = 0x40000
EMU_RESERVED = 0xfeedf000
EMU_RESERVE_SIZE = 0x4000
DYM_IMP_RESERVE = EMU_RESERVED + 0x1000
EMU_CALLBACK_RESERVE = DYM_IMP_RESERVE + 0x1000
EMU_SYSCALL_RESERVE = EMU_CALLBACK_RESERVE + 0x1000
EMU_RESERVED_END = (EMU_RESERVED + EMU_RESERVE_SIZE)
EMU_RETURN_ADDR = EMU_RESERVED
EXIT_RETURN_ADDR = EMU_RETURN_ADDR + 1
SEH_RETURN_ADDR = EMU_RETURN_ADDR + 4
IMPORT_HOOK_ADDR = EMU_RETURN_ADDR + 8
# Common blank DOS header
DOS_HEADER = b'MZ\x90\x00\x03\x00\x00\x00\x04\x00\x00\x00\xff\xff\x00\x00\xb8\x00\x00\x00' \
b'\x00\x00\x00\x00@' + (b'\x00' * 35) + \
b'\xb0\x00\x00\x00\x0e\x1f\xba\x0e\x00\xb4\t\xcd!\xb8\x01L\xcd!This program ' \
b'cannot be run in DOS mode.\r\r\n$' + (b'\x00' * 39) + \
b'Rich\xbeL\x1c\x41\x00\x00\x00\x00\x00\x00\x00\x00'
# Blank header used for a 32-bit PE header
EMPTY_PE_32 = DOS_HEADER + b'PE\x00\x00L\x01\x00\x00ABCD\x00\x00\x00\x00\x00\x00\x00\x00' \
b'\xe0\x00\x03\x01\x0b\x01\x08\x00\x04\x00\x00\x00\x00\x00\x00' \
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\xd0\x01\x00\x00\xd4\x01' \
b'\x00\x00\x00\x00@\x00\x01\x00\x00\x00\x01\x00\x00\x00\x04\x00' \
b'\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\xd4' \
b'\x01\x00\x00\xd0\x01\x00\x00\x00\x00\x00\x00\x02\x00\x00\x04' \
b'\x00\x00\x10\x00\x00\x10\x00\x00\x00\x00\x10\x00\x00\x10\x00' \
b'\x00\x00\x00\x00\x00\x10' + (b'\x00' * 131)
# Blank header used for a 64-bit PE header
EMPTY_PE_64 = DOS_HEADER + b'PE\x00\x00d\x86\x00\x00ABCD\x00\x00\x00\x00\x00\x00\x00\x00' \
b'\xf0\x00\x03\x10\x0b\x02\x08\x00\x04\x00\x00\x00\x00\x00\x00' \
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00@' \
b'\x00\x00\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x06\x00' \
b'\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\xb8' \
b'\x01\x00\x00\x00\x00\x00\x00AAAA\x02\x00\x00\x04\x00\x00\x10' \
b'\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00' \
b'\x10\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00' \
b'\x00\x00\x00\x10' + (b'\x00' * 131)
class PeParseException(Exception):
pass
class PeFile(pefile.PE):
"""
Class the will represent all PE files loaded into the emulator
"""
def __init__(self, path=None, data=None, imp_id=IMPORT_HOOK_ADDR,
imp_step=4, emu_path='', fast_load=False):
super(PeFile, self).__init__(name=path, data=data, fast_load=fast_load)
if 0 == self.OPTIONAL_HEADER.ImageBase:
self.relocate_image(DEFAULT_LOAD_ADDR)
super(PeFile, self).__init__(name=None, data=self.write())
self.imp_id = imp_id
self.imp_step = imp_step
self.file_size = 0
self.base = self.OPTIONAL_HEADER.ImageBase
self.hash = self._hash_pe(path=path, data=data)
self.imports = self._get_pe_imports()
self.exports = self._get_pe_exports()
self.mapped_image = self.get_memory_mapped_image(max_virtual_address=0xf0000000)
self.image_size = self.OPTIONAL_HEADER.SizeOfImage
self.import_table = {}
self.is_mapped = True
self.pe_sections = self._get_pe_sections()
self.ep = self.OPTIONAL_HEADER.AddressOfEntryPoint
self.stack_commit = self.OPTIONAL_HEADER.SizeOfStackCommit
self.path = ''
self.name = ''
if path:
self.path = os.path.abspath(path)
self.emu_path = emu_path
self.arch = self._get_architecture()
if self.arch == _arch.ARCH_X86:
self.ptr_size = 4
else:
self.ptr_size = 8
self._patch_imports()
def get_emu_path(self):
"""
Get the path of the module (as it appears to the emulated binary)
"""
return self.emu_path
def set_emu_path(self, path):
self.emu_path = path
def _hash_pe(self, path=None, data=None):
hasher = hashlib.sha256()
buf = b''
if path:
with open(path, 'rb') as f:
buf = f.read()
elif data:
buf = data
hasher.update(buf)
self.file_size = len(buf)
return hasher.hexdigest()
def _get_pe_imports(self):
pe = self
imports = {}
try:
pe.DIRECTORY_ENTRY_IMPORT
except Exception:
return imports
for entry in pe.DIRECTORY_ENTRY_IMPORT:
dll = entry.dll
dll = dll.decode('utf-8')
dll = os.path.splitext(dll)[0]
for imp in entry.imports:
if imp.import_by_ordinal:
func_name = 'ordinal_%d' % (imp.ordinal)
imports.update({imp.address: (dll, func_name)})
else:
func_name = imp.name.decode('utf-8')
imports.update({imp.address: (dll, func_name)})
return imports
def get_exports(self):
self.exports = self._get_pe_exports()
return self.exports
def _get_pe_exports(self):
pe = self
exports = []
try:
pe.DIRECTORY_ENTRY_EXPORT
except Exception:
return exports
for exp in pe.DIRECTORY_ENTRY_EXPORT.symbols:
entry = namedtuple('export', ['name', 'address', 'forwarder', 'ordinal'])
entry.name = exp.name
entry.address = exp.address + pe.get_base()
entry.forwarder = exp.forwarder
entry.ordinal = exp.ordinal
if entry.name:
entry.name = entry.name.decode('utf-8')
exports.append(entry)
return exports
def _get_pe_sections(self):
pe = self
sections = []
for section in pe.sections:
sect = (section.Name, section.VirtualAddress,
section.Misc_VirtualSize, section.SizeOfRawData)
sections.append(sect)
return sections
def get_sections(self):
return self.sections
def get_section_by_name(self, name):
sect = [s for s in self.get_sections() if s.Name.decode('utf-8').strip('\x00') == name]
if sect:
return sect[0]
def _get_architecture(self):
# 0x010b: PE32, 0x020b: PE32+ (64 bit)
magic = self.OPTIONAL_HEADER.Magic
if magic & ddk.PE32_BIT:
return _arch.ARCH_X86
elif magic & ddk.PE32_PLUS_BIT:
return _arch.ARCH_AMD64
else:
raise ValueError('Unsupported architecture: 0x%x' % (magic))
def _patch_imports(self):
"""
Imports are patched with invalid memory addresses. When the API is called
by the emulated binary, the invalid memory fetch callback will trigger,
allowing us to handle the Windows API within the emulator
"""
if not self.imports:
return
if not self.mapped_image:
raise ValueError('PE image has not been mapped yet')
for addr, imp in self.imports.items():
tmp = bytearray(self.mapped_image)
offset = addr - self.base
tmp[offset: offset + self.ptr_size] = \
self.imp_id.to_bytes(self.ptr_size, 'little')
self.mapped_image = bytes(tmp)
self.import_table.update({self.imp_id: imp})
self.imp_id += self.imp_step
def get_export_by_name(self, name):
for exp in self.get_exports():
if name == exp.name:
return exp.address
def get_raw_data(self):
return self.get_memory_mapped_image()
def find_bytes(self, pattern, offset=0):
return self.get_raw_data().find(pattern, offset)
def set_bytes(self, offset, pattern):
self.set_bytes_at_offset(offset, pattern)
def get_ptr_size(self):
return self.ptr_size
def get_base(self):
return self.base
def get_base_name(self):
fn = os.path.basename(self.path)
bn = os.path.splitext(fn)[0]
return bn
def get_image_size(self):
return self.image_size
def is_decoy(self):
return False
def is_driver(self):
rv = super(PeFile, self).is_driver()
if rv:
return rv
system_DLLs = set((b'ntoskrnl.exe', b'hal.dll', b'ndis.sys',
b'bootvid.dll', b'kdcom.dll', b'win32k.sys'))
if hasattr(self, 'DIRECTORY_ENTRY_IMPORT'):
if system_DLLs.intersection(
[imp.dll.lower() for imp in self.DIRECTORY_ENTRY_IMPORT]):
return True
if self.OPTIONAL_HEADER.Subsystem == pefile.SUBSYSTEM_TYPE['IMAGE_SUBSYSTEM_NATIVE'] \
and self.ep == 0:
return True
def is_dotnet(self):
"""
Is the current PE file a .NET assembly?
"""
for addr, imp in self.imports.items():
dll, func = imp
if dll == 'mscoree' and func in ['_CorExeMain', '_CorDllMain']:
return True
return False
class DecoyModule(PeFile):
"""
Class that represents "decoy" modules that are loaded into emulated memory.
We use decoy modules so that shellcode
(or other modules) can parse the PE file to resolve exports.
"""
def __init__(self, path=None, data=None, fast_load=True, base=0, emu_path=''):
self.image_size = 0
self.ep = 0
if path or data:
super(DecoyModule, self).__init__(path=path, data=data, fast_load=fast_load)
if data:
self.image_size = len(data)
self.decoy_base = base
self.decoy_path = emu_path
self.base_name = ''
self.is_mapped = False
self.data = b''
def get_memory_mapped_image(self, max_virtual_address=0x10000000):
mmi = super(DecoyModule, self).get_memory_mapped_image(max_virtual_address)
if len(mmi) < len(self.__data__):
return self.__data__
return mmi
def get_base(self):
return self.decoy_base
def get_emu_path(self):
return self.decoy_path
def get_base_name(self):
p = self.get_emu_path()
img = ntpath.basename(p)
bn = os.path.splitext(img)[0]
return bn
def get_ep(self):
return self.get_base() + self.ep
def is_decoy(self):
return True
class JitPeFile(object):
'''
Class used to rapidly assemble a decoy PE that will only contain an export table
so malware can parse it.
'''
def __init__(self, arch):
if arch == _arch.ARCH_X86:
self.pattern_size = 9
husk = EMPTY_PE_32
else:
self.pattern_size = 12
husk = EMPTY_PE_64
self.arch = arch
self.basepe = pefile.PE(data=husk, fast_load=True)
def get_section_by_name(self, pe, name):
'''
Get a PE section by name
'''
for sect in pe.sections:
if sect.Name.decode('utf-8').strip('\x00') == name.strip('\x00'):
return self.cast_section(sect.get_file_offset())
def get_raw_pe(self):
'''
Get the raw data associated with a decoy PE
'''
return self.basepe.__data__
def update(self):
'''
Update the raw data associated with a decoy PE
'''
self.basepe = pefile.PE(None, self.basepe.write(), fast_load=True)
self.update_image_size()
def cast_section(self, offset=None):
'''
Get a section from a given offset
'''
if offset is None:
offset = self.get_current_offset()
data = pefile.Structure(self.basepe.__IMAGE_SECTION_HEADER_format__).sizeof() * b'\x00'
else:
data = self.basepe.get_data(offset, pefile.Structure(self.basepe.__IMAGE_SECTION_HEADER_format__).sizeof()) # noqa
sect = self.basepe.__unpack_data__(self.basepe.__IMAGE_SECTION_HEADER_format__, data,
offset)
return sect
def update_image_size(self):
'''
Update the size of the image within the optional header
'''
self.basepe.OPTIONAL_HEADER.SizeOfImage = len(self.basepe.get_memory_mapped_image())
def add_section(self, name, chars=0x40000040):
'''
Add a section to the decoy PE
'''
new_sect = self.cast_section()
new_sect.Name = name.encode('utf-8')
new_sect.Characteristics = chars
hdr_size = pefile.Structure(self.basepe.__IMAGE_SECTION_HEADER_format__).sizeof()
self.basepe.OPTIONAL_HEADER.SizeOfHeaders += hdr_size
self.basepe.OPTIONAL_HEADER.SizeOfImage += hdr_size
self.basepe.FILE_HEADER.NumberOfSections += 1
self.update()
return new_sect
def get_current_offset(self):
'''
Get the current offset (or size) of the PE
'''
return len(self.basepe.__data__)
def append_data(self, data):
'''
Append data to the decoy PE
'''
self.basepe.__data__ += data
def get_exports_size(self, name, exports):
'''
Get the total size of the export directory
'''
# Get the total size needed for the new export section
exp_size = pefile.Structure(self.basepe.__IMAGE_EXPORT_DIRECTORY_format__).sizeof()
exp_size += (len(name) + 1)
for exp in exports:
exp_size += len(exp) + 1
exp_size += (0x4 + 0x4 + 0x4)
return exp_size
def get_decoy_pe_image(self, mod_name, exports):
self.add_section(name='.text')
self.add_section(name='.edata')
self.init_text_section(exports)
self.init_export_section(mod_name.encode('utf-8'), exports)
self.update()
return self.get_raw_pe()
def init_export_section(self, name, exports):
'''
Initialize and add the export table to the PE
'''
exports_size = self.get_exports_size(name, exports)
dest_exp_sect = self.get_section_by_name(self.basepe, '.edata')
dest_exp_sect.Misc_VirtualSize = exports_size
dest_exp_sect.Misc_PhysicalAddress = 0
dest_exp_sect.VirtualAddress = self.get_current_offset()
dest_exp_sect.SizeOfRawData = exports_size
dest_exp_sect.PointerToRawData = self.get_current_offset()
self.basepe.OPTIONAL_HEADER.SizeOfInitializedData += exports_size
export_dir = self.basepe.OPTIONAL_HEADER.DATA_DIRECTORY[0]
export_dir.VirtualAddress = dest_exp_sect.VirtualAddress
export_dir.Size = exports_size
offset = self.get_current_offset()
self.append_data(b'\x00' * exports_size)
dest_export_dir = self.basepe.__unpack_data__(self.basepe.__IMAGE_EXPORT_DIRECTORY_format__, # noqa
pefile.Structure(self.basepe.__IMAGE_EXPORT_DIRECTORY_format__).sizeof() * b'\x00', # noqa
offset)
offset += pefile.Structure(self.basepe.__IMAGE_EXPORT_DIRECTORY_format__).sizeof()
dest_export_dir.Characteristics = 0
dest_export_dir.TimeDateStamp = 0xD1234567
dest_export_dir.MajorVersion = 0
dest_export_dir.MinorVersion = 0
dest_export_dir.Base = 1
dest_export_dir.NumberOfFunctions = len(exports)
dest_export_dir.NumberOfNames = len(exports)
# Set the address of functions array
num_funcs = dest_export_dir.NumberOfFunctions
funcs_offset = offset
names_offset = funcs_offset + (4 * num_funcs)
ord_offset = names_offset + (4 * num_funcs)
strings_offset = ord_offset + (2 * num_funcs)
dest_export_dir.Name = strings_offset
dest_export_dir.AddressOfFunctions = offset
dest_export_dir.AddressOfNames = names_offset
dest_export_dir.AddressOfNameOrdinals = ord_offset
# Set the export name
self.basepe.set_bytes_at_offset(strings_offset, name)
strings_offset += len(name) + 1
ep = self.basepe.OPTIONAL_HEADER.AddressOfEntryPoint
for i, exp in enumerate(exports):
exp = exp.encode('utf-8')
# Add fluff to pass forwarded export checks
self.append_data(b'\x00' * len(exports))
# Add the function addresses
self.basepe.set_dword_at_offset(funcs_offset, ep)
funcs_offset += 4
if funcs_offset > self.get_current_offset():
raise Exception('Functions offset exceeds total PE size')
# Add the ordinals
self.basepe.set_word_at_offset(ord_offset, (i + 1) - dest_export_dir.Base)
ord_offset += 2
if ord_offset > self.get_current_offset():
raise Exception('Ordinals offset exceeds total PE size')
# Add the function names in
if strings_offset > self.get_current_offset():
raise Exception('Export string offset exceeds total PE size')
self.basepe.set_dword_at_offset(names_offset, strings_offset)
names_offset += 4
self.basepe.set_bytes_at_offset(strings_offset, exp)
strings_offset += len(exp) + 1
ep += self.pattern_size
if strings_offset:
self.basepe.__data__ = self.basepe.__data__[:strings_offset]
self.update()
def init_text_section(self, names):
'''
Initialize and add the text section to the PE
'''
pattern = b''
# Add placeholder code in case emulated samples want to hook the function
if self.arch == _arch.ARCH_X86:
for i in range(len(names)):
pattern += (b'\x89\xff\x90\xB8' + i.to_bytes(4, 'little') + b'\xc3')
else:
for i in range(len(names)):
pattern += (b'\x48\x89\xFF\x90\x48\xC7\xC0' + i.to_bytes(4, 'little') + b'\xc3')
if pattern:
sect = self.get_section_by_name(self.basepe, '.text')
sect.VirtualAddress = self.get_current_offset()
sect.Misc_VirtualSize = len(pattern)
sect.Misc_PhysicalAddress = 0
sect.SizeOfRawData = len(pattern)
sect.PointerToRawData = self.get_current_offset()
self.basepe.OPTIONAL_HEADER.AddressOfEntryPoint = sect.VirtualAddress
self.append_data(pattern)
self.update()
+41
View File
@@ -0,0 +1,41 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
class CryptContext(object):
"""
Represents crypto context used by crypto functions
"""
curr_handle = 0x680
def __init__(self, cname, pname, ptype, flags):
self.container_name = cname
self.provider_name = pname
self.ptype = ptype
self.flags = flags
def get_handle(self):
hkey = CryptContext.curr_handle
CryptContext.curr_handle += 4
return hkey
class CryptoManager(object):
"""
Manages the emulation of crypto functions
"""
def __init__(self, config=None):
super(CryptoManager, self).__init__()
self.ctx_handles = {}
self.config = config
def crypt_open(self, cname, pname, ptype, flags):
ctx = CryptContext(cname, pname, ptype, flags)
hnd = ctx.get_handle()
self.ctx_handles.update({hnd: ctx})
return hnd
def crypt_close(self, hnd):
self.ctx_handles.pop(hnd)
def crypt_get(self, hnd):
return self.ctx_handles.get(hnd, None)
+365
View File
@@ -0,0 +1,365 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
import os
import io
import ntpath
import hashlib
import speakeasy.winenv.defs.windows.windows as windefs
from speakeasy.errors import FileSystemEmuError
def normalize_response_path(path):
def _get_speakeasy_root():
return os.path.join(os.path.dirname(__file__), os.pardir)
root_var = '$ROOT$'
if root_var in path:
root = _get_speakeasy_root()
return path.replace(root_var, root)
return path
class MapView(object):
"""
Represents a shared memory view
"""
def __init__(self, base, offset, size, protect, process=None):
self.base = base
self.offset = offset
self.size = size
self.protect = protect
self.process = process
class FileMap(object):
"""
Represents a memory mapped file
"""
curr_handle = 0x280
def __init__(self, name, size, prot, backed_file=None):
self.name = name
self.backed_file = backed_file
self.views = {}
def get_handle(self):
hmap = FileMap.curr_handle
FileMap.curr_handle += 4
return hmap
def get_name(self):
return self.name
def get_backed_file(self):
return self.backed_file
def add_view(self, base, offset, size, protect):
view = MapView(base, offset, size, protect)
self.views.update({base: view})
class File(object):
"""
Base class for an emulated file
"""
curr_handle = 0x80
def __init__(self, path, config={}, data=b''):
self.path = path
self.data = None
self.bytes_written = 0
if data:
self.data = io.BytesIO(data)
self.curr_offset = 0
self.is_dir = False
self.config = config
def get_handle(self):
hfile = File.curr_handle
File.curr_handle += 4
return hfile
def get_path(self):
return self.path
def get_hash(self):
h = hashlib.sha256()
data = self.get_data()
h.update(data)
return h.hexdigest()
def get_size(self):
if not self.data and self.config:
self.data = self.handle_file_data()
if not self.data:
return 0
off = self.data.tell()
self.data.seek(0, io.SEEK_SET)
size = len(self.data.read())
self.data.seek(off, io.SEEK_SET)
return size
def get_data(self, size=-1):
if not self.data and self.config:
self.data = self.handle_file_data()
if not self.data:
return b''
off = self.data.tell()
if off == self.get_size():
# Reset the file pointer
self.data.seek(0)
return self.data.read(size)
def seek(self, offset, whence):
if whence not in [io.SEEK_CUR, io.SEEK_SET, io.SEEK_END]:
return
if self.data:
self.data.seek(offset, whence)
def add_data(self, data):
if not self.data:
self.data = io.BytesIO()
off = self.data.tell()
self.data.seek(0, io.SEEK_END)
self.data.write(data)
self.data.seek(off, io.SEEK_SET)
self.bytes_written += len(data)
def remove_data(self):
self.data = io.BytesIO(b'')
def is_directory(self):
return self.is_dir
def handle_file_data(self):
"""
Based on the emulation config, determine what data
to return from the read request
"""
path = self.config.get('path')
if path:
path = normalize_response_path(path)
with open(path, 'rb') as f:
return io.BytesIO(f.read())
bf = self.config.get('byte_fill')
if bf:
byte = bf.get('byte')
if byte.startswith('0x'):
byte = 0xFF & int(byte, 0)
else:
byte = 0xFF & int(byte, 16)
size = bf.get('size')
b = (byte).to_bytes(1, 'little')
return b * size
return io.BytesIO(b'')
class Pipe(File):
"""
Emulated named pipe objects
"""
curr_handle = 0x400
def __init__(self, name, mode, num_instances, out_size, in_size, config={}):
super(Pipe, self).__init__(path=name, config=config)
self.name = name
self.mode = mode
self.num_instances = num_instances
self.out_size = out_size
self.in_size = in_size
def get_handle(self):
hpipe = Pipe.curr_handle
Pipe.curr_handle += 4
return hpipe
class FileManager(object):
"""
Manages file system activity during emulation
"""
def __init__(self, config=None):
super(FileManager, self).__init__()
self.file_handles = {}
self.pipe_handles = {}
self.file_maps = {}
self.config = config
self.files = []
def file_create_mapping(self, hfile, name, size, prot):
if hfile not in (windefs.INVALID_HANDLE_VALUE, 0):
f = self.get_file_from_handle(hfile)
fm = FileMap(name, size, prot, f)
hnd = fm.get_handle()
self.file_maps.update({hnd: fm})
return hnd
else:
fm = FileMap(name, size, prot, None)
hnd = fm.get_handle()
self.file_maps.update({hnd: fm})
return hnd
def walk_files(self):
for f in self.config.get('files', []):
path = f.get('emu_path')
if not path:
continue
yield path
def get_dropped_files(self):
return [f for f in self.files if f.bytes_written == f.get_size()]
def get_mapping_from_handle(self, handle):
return self.file_maps.get(handle)
def get_mapping_from_addr(self, addr):
for h, fmap in self.file_maps.items():
for base, view in fmap.views.items():
if base == addr:
return fmap
def get_file_from_handle(self, handle):
return self.file_handles.get(handle)
def get_pipe_from_handle(self, handle):
return self.pipe_handles.get(handle)
def get_file_from_path(self, path):
for f in self.files:
if f.get_path().lower() == path.lower():
return f
return None
def get_all_files(self):
return self.files
def handle_file_data(self, fconf):
path = fconf.get('path')
if path:
path = normalize_response_path(path)
with open(path, 'rb') as f:
return f.read()
bf = fconf.get('byte_fill')
if bf:
byte = bf.get('byte')
if byte.startswith('0x'):
byte = 0xFF & int(byte, 0)
else:
byte = 0xFF & int(byte, 16)
size = bf.get('size')
b = (byte).to_bytes(1, 'little')
return b * size
def add_existing_file(self, path, data):
"""
Register an existing file already included in the emulation space
(with data)
"""
f = File(path, data=data)
self.files.append(f)
return f
def create_file(self, path):
f = self.get_file_from_path(path)
if f:
self.files.remove(f)
f = File(path)
self.files.append(f)
return f
def get_emu_file(self, path):
# Does this file exist in our emulation environment
# See if we have a handler for this exact file
for f in self.config.get('files', []):
mode = f.get('mode')
if mode == 'full_path':
if path.lower() == f.get('emu_path').lower():
return f
# If no full path handler exists, do we have an extension handler?
for f in self.config.get('files', []):
path_ext = ntpath.splitext(path)[-1:][0].strip('.')
if path_ext:
mode = f.get('mode')
if mode == 'by_ext':
if path_ext.lower() == f.get('ext'):
return f
# Finally, do we have a catch-all default handler?
for f in self.config.get('files', []):
mode = f.get('mode')
if mode == 'default':
return f
return None
def pipe_open(self, path, mode, num_instances, out_size, in_size):
hnd = None
fconf = self.get_emu_file(path)
if not fconf:
return hnd
p = Pipe(path, mode, num_instances, out_size, in_size, config=fconf)
hnd = p.get_handle()
self.pipe_handles.update({hnd: p})
return hnd
def does_file_exist(self, path):
if self.get_file_from_path(path):
return True
if self.get_emu_file(path):
return True
return False
def get_object_from_handle(self, handle):
obj = self.file_maps.get(handle)
if obj:
return obj
obj = self.pipe_handles.get(handle)
if obj:
return obj
obj = self.file_handles.get(handle)
if obj:
return obj
def file_open(self, path, create=False, truncate=False, is_dir=False):
hnd = None
if create:
f = self.create_file(path)
hnd = f.get_handle()
self.file_handles.update({hnd: f})
else:
f = self.get_file_from_path(path)
if f:
hnd = f.get_handle()
self.file_handles.update({hnd: f})
return hnd
fconf = self.get_emu_file(path)
if not fconf:
return hnd
real_path = fconf.get('path', '')
real_path = normalize_response_path(real_path)
if not truncate:
if real_path and not os.path.exists(real_path):
raise FileSystemEmuError('File path not found: %s' % (real_path))
f = File(path, config=fconf)
self.files.append(f)
else:
if real_path and not os.path.exists(real_path):
raise FileSystemEmuError('File path not found: %s' % (real_path))
f = File(path, config=fconf)
self.files.append(f)
hnd = f.get_handle()
self.file_handles.update({hnd: f})
return hnd
+31
View File
@@ -0,0 +1,31 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
import speakeasy.winenv.defs.nt.ddk as ddk
import speakeasy.windows.kernel_mods as km
class IoManager(object):
"""
Directs IO requests to a module handler. For example, if a user mode application
sends an ioctl to a device this can be handled here.
"""
def __init__(self):
super(IoManager, self).__init__()
self.emu_kmods = [m.DriverModule() for m in km._get_kmods()]
def dev_ioctl(self, arch, dev, ioctl, inbuf):
rv = ddk.STATUS_INVALID_DEVICE_REQUEST
outbuf = b''
# Get parent driver for you
drv = dev.get_parent_driver()
bn = drv.get_basename()
# Find the emulated kernel module (if any)
mod = [m for m in self.emu_kmods if bn == m.get_mod_name()]
if not mod:
rv = ddk.STATUS_INVALID_DEVICE_REQUEST
return (rv, outbuf)
mod = mod[0]
return mod.ioctl(arch, ioctl, inbuf)
+731
View File
@@ -0,0 +1,731 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
import os
import ntpath
import hashlib
from speakeasy.profiler import Run
from speakeasy.windows.winemu import WindowsEmulator
from speakeasy.windows.ioman import IoManager
import speakeasy.winenv.arch as _arch
import speakeasy.windows.common as w32common
import speakeasy.windows.objman as objman
from speakeasy.winenv.api.winapi import WindowsApi
import speakeasy.winenv.defs.nt.ddk as ddk
import speakeasy.winenv.defs.registry.reg as regdefs
from speakeasy.errors import KernelEmuError
import speakeasy.winenv.defs.nt.ntoskrnl as ntos
import capstone as cs
EP_DRIVER_ENTRY = ddk.IRP_MJ_MAXIMUM_FUNCTION
EP_DRIVER_UNLOAD = ddk.IRP_MJ_MAXIMUM_FUNCTION + 1
MAX_EXPORTS_TO_EMULATE = 10
SYSTEM_TIME_START = 131911108955110000
class WinKernelEmulator(WindowsEmulator, IoManager):
"""
Class used to emulate Windows drivers
"""
def __init__(self, config, debug=False, logger=None, exit_event=None):
super(WinKernelEmulator, self).__init__(config, debug=debug, logger=logger,
exit_event=exit_event)
self.disasm_eng = None
self.curr_mod = None
self.debug = debug
self.drivers = []
self.pool_allocs = []
self.all_entrypoints = False
self.kernel_mode = True
self.irql = ddk.PASSIVE_LEVEL
self.delayed_runs = []
self.system_time = SYSTEM_TIME_START
self.ktypes = ntos
def get_system_time(self):
return self.system_time
def get_system_process(self):
"""
Get the process object for the system process (PID 4)
"""
for proc in self.processes:
if proc.get_pid() == 4:
return proc
def get_current_irql(self):
"""
Get the current interrupt request level
"""
return self.irql
def set_current_irql(self, irql):
"""
Set the current interrupt request level
"""
self.irql = irql
def create_driver_object(self, name=None, pe=None):
"""
Create a driver object for the driver that is going to be emulated
"""
drv = objman.Driver(emu=self)
# If no PE was supplied, assign a dummy driver
if not pe:
# Get the path for the dummy driver
default_path = self.get_native_module_path('default_sys')
pe = w32common.DecoyModule(path=default_path)
if name:
bn = ntpath.basename(name)
else:
bn = 'none'
pe.decoy_path = ('%sdrivers\\%s.sys' %
(self.get_system_root(), os.path.basename(bn)))
pe.decoy_base = pe.get_base()
ep = pe.get_base() + pe.ep
drv.object.MajorFunction[ddk.IRP_MJ_CREATE] = ep + 1
drv.object.MajorFunction[ddk.IRP_MJ_READ] = ep + 2
drv.object.MajorFunction[ddk.IRP_MJ_WRITE] = ep + 3
drv.object.MajorFunction[ddk.IRP_MJ_DEVICE_CONTROL] = ep + 4
drv.object.MajorFunction[ddk.IRP_MJ_PNP] = ep + 5
drv.object.MajorFunction[ddk.IRP_MJ_INTERNAL_DEVICE_CONTROL] = ep+6
else:
if not name:
bn = pe.path
path = '%sdrivers\\%s' % (self.get_system_root(),
os.path.basename(bn))
pe.decoy_path = path
pe.decoy_base = pe.base
drv.init_driver_object(name, pe)
self.add_object(drv)
self.drivers.append(drv)
return drv
def load_module(self, path=None, data=None):
"""
Load the kernel module to be emulated
"""
pe = self.load_pe(path, data=data, imp_id=w32common.IMPORT_HOOK_ADDR)
if pe.arch == _arch.ARCH_X86:
disasm_mode = cs.CS_MODE_32
elif pe.arch == _arch.ARCH_AMD64:
disasm_mode = cs.CS_MODE_64
else:
raise KernelEmuError('Unsupported architecture: %s', pe.arch)
if not self.arch:
self.arch = pe.arch
self.set_ptr_size(self.arch)
self.emu_eng.init_engine(_arch.ARCH_X86, pe.arch)
if not self.disasm_eng:
self.disasm_eng = cs.Cs(cs.CS_ARCH_X86, disasm_mode)
self.api = WindowsApi(self)
self.om = objman.ObjectManager(emu=self)
if not data:
file_name = os.path.basename(path)
mod_name = os.path.splitext(file_name)[0]
else:
drv_hash = hashlib.sha256()
drv_hash.update(data)
drv_hash = drv_hash.hexdigest()
mod_name = drv_hash
file_name = '%s.sys' % (mod_name)
emu_path = '%sdrivers\\%s' % (self.get_system_root(), file_name)
pe.emu_path = emu_path
self.map_pe(pe, mod_name=mod_name, emu_path=emu_path)
self.mem_write(pe.base, pe.mapped_image)
# Strings the initial buffer so that we can detect decoded strings later on
if self.profiler and self.do_strings:
astrs = self.get_ansi_strings(pe.mapped_image)
wstrs = self.get_unicode_strings(pe.mapped_image)
for s in astrs:
if s not in self.profiler.strings['ansi']:
self.profiler.strings['ansi'].append(s)
for s in wstrs:
if s not in self.profiler.strings['unicode']:
self.profiler.strings['unicode'].append(s)
# Init imported data
for addr, imp in pe.imports.items():
mn, fn = imp
mod, eh = self.api.get_data_export_handler(mn, fn)
if eh:
data_ptr = self.handle_import_data(mn, fn)
sym = "%s.%s" % (mn, fn)
self.global_data.update({addr: [sym, data_ptr]})
self.mem_write(addr,
data_ptr.to_bytes(self.get_ptr_size(),
'little'))
# Set the emulator to run in protected mode
self._setup_gdt(self.get_arch())
self.setup_kernel_mode()
self.setup_user_shared_data()
if not self.stack_base:
self.stack_base, stack_ptr = self.alloc_stack(pe.stack_commit)
return pe
def pool_alloc(self, pooltype, size, tag='None'):
"""
Allocate memory in the emulated "pool"
"""
if pooltype == ddk.POOL_TYPE.NonPagedPool:
pt = 'NonPagedPool'
elif pooltype == ddk.POOL_TYPE.PagedPool:
pt = 'PagedPool'
elif pooltype == ddk.POOL_TYPE.NonPagedPoolNx:
pt = 'NonPagedPoolNx'
else:
pt = 'unk'
system_proc = self.get_system_process()
addr = self.mem_map(size, base=None, tag='api.pool.%s.%s' % (pt, tag), process=system_proc)
self.pool_allocs.append((addr, pooltype, size, tag))
return addr
def init_sys_modules(self, modules_config):
"""
Initialize kernel mode modules that may be accessed by emulated modules
"""
sysmods = super(WinKernelEmulator, self).init_sys_modules(modules_config)
# Initalize any DRIVER_OBJECTs needed by the module
for mc in modules_config:
drv = mc.get('driver')
if drv:
mod = [m for m in sysmods if m.name == mc.get('name')]
if not mod:
continue
mod = mod[0]
driver = self.create_driver_object(name=drv.get('name'),
pe=mod)
devs = drv.get('devices')
for dev in devs:
name = dev.get('name', '')
ext_size = dev.get('ext_size', 0)
devtype = dev.get('devtype', 0)
chars = dev.get('chars', 0)
self.create_device_object(name, driver, ext_size,
devtype, chars)
for m in self.modules:
mod = m[0]
sysmods.append(mod)
return sysmods
def get_processes(self):
"""
Get processes that exist in the emulation space
"""
if not self.processes:
self.init_processes(self.config_processes)
return self.processes
def init_processes(self, processes):
for proc in processes:
p = objman.Process(self)
self.add_object(p)
p.name = proc.get('name', '')
p.pid = proc.get('pid')
if p.name.lower() == 'system':
p.pid = 4
p.path = 'System'
if not p.pid:
p.pid = self.om.new_id()
base = proc.get('base_addr')
if isinstance(base, str):
base = int(base, 16)
p.base = base
if not p.path:
p.path = proc.get('path')
p.image = ntpath.basename(p.path)
# Create an initial thread for each process
t = objman.Thread(self)
self.add_object(t)
t.process = p
p.threads.append(t)
self.processes.append(p)
# The SYSTEM process should be the starting context
sp = [p for p in self.processes if p.name.lower() == 'system']
if sp:
sp = sp[0]
self.set_current_process(sp)
def alloc_peb(self, proc):
"""
Allocate PEB and related substructures for a given process
"""
ldr = proc.get_peb_ldr()
if not ldr.address:
size = ldr.sizeof()
res, size = self.get_valid_ranges(size)
base = self.mem_map(size, base=res, tag='emu.struct.PEB_LDR_DATA')
proc.set_peb_ldr_address(base)
return proc.get_peb()
def get_process_peb(self, process):
"""
Retrieve the PEB for the supplied process
"""
self.alloc_peb(process)
user_mods = self.get_user_modules()
process.init_peb(user_mods)
return process.peb
def set_current_process(self, process):
"""
Set the current process context
"""
self.curr_process = process
def get_current_process(self):
"""
Get the current process context
"""
if not self.processes:
self.processes = self.get_processes()
return self.curr_process
def run_module(self, module, all_entrypoints=False):
"""
Begin emulation fo a previously loaded kernel module
"""
self.all_entrypoints = all_entrypoints
# Create the service key for the driver
drv = self.create_driver_object(pe=module)
svc_key = self.regman.create_key(drv.get_reg_path())
# Create the values for the service key
svc_key.create_value('ImagePath', regdefs.REG_EXPAND_SZ, module.get_emu_path())
svc_key.create_value('Type', regdefs.REG_DWORD, 0x1) # SERVICE_KERNEL_DRIVER
svc_key.create_value('Start', regdefs.REG_DWORD, 0x3) # SERVICE_DEMAND_START
svc_key.create_value('ErrorControl', regdefs.REG_DWORD, 0x1) # SERVICE_ERROR_NORMAL
# Create the parameters subkey
self.regman.create_key(drv.get_reg_path() + '\\Parameters')
if module.ep > 0:
ep = module.base + module.ep
run = Run()
run.type = EP_DRIVER_ENTRY
run.start_addr = ep
run.instr_cnt = 0
run.args = [drv.address, drv.reg_path_ptr]
self.add_run(run)
if self.all_entrypoints:
# Only emulate a subset of all the exported functions
# There are some modules (such as the windows kernel) with thousands of exports
exports = [k for k in module.get_exports()[: MAX_EXPORTS_TO_EMULATE]]
if exports:
args = [self.mem_map(8, tag='emu.export_arg_%d' % (i)) for i in range(4)]
for exp in exports:
run = Run()
if exp.name:
fn = exp.name
else:
fn = 'no_name'
run.type = 'export.%s' % (fn)
run.start_addr = exp.address
# Here we set dummy args to pass into the export function
run.args = args
# Store these runs and only queue them before the unload routine
# this is because some exports may not be ready to be called yet
self.delayed_runs.append(run)
self.start()
def create_device_object(self, name='', drv=0, ext_size=0,
devtype=0, chars=0, tag=''):
"""
Create a device object to use for kernel emulation
"""
dev = objman.Device(self)
alloc_size = ext_size + dev.sizeof()
if not name:
devname = r'\Device\%x' % (dev.get_id())
if not tag:
tag = 'emu.device.autogen'
name = '%s.%s' % (tag, devname)
else:
devname = name
if not tag:
tag = 'emu.object'
name = '%s.%s' % (tag, devname)
dev.address = self.mem_map(alloc_size, tag=name)
dev.name = devname
# Create a FILE_OBJECT for the device
fobj = objman.FileObject(self)
dev.object.DeviceObject = dev.address
dev.file_object = fobj
self.add_object(dev)
if drv:
drv.read_back()
dev.object.DriverObject = drv.address
dev.driver = drv
if not drv.object.DeviceObject:
drv.object.DeviceObject = dev.address
drv.write_back()
else:
next_dev = self.get_object_from_addr(drv.object.DeviceObject)
while next_dev:
if next_dev.object.NextDevice:
next_dev = \
self.get_object_from_addr(drv.object.NextDevice)
else:
# This is the last in the list, add our new device
next_dev.object.NextDevice = dev.address
next_dev.write_back()
break
drv.devices.append(dev)
dev.object.Characteristics = chars
dev.object.DeviceType = devtype
if ext_size > 0:
dev.object.DeviceObjectExtension = dev.address + dev.sizeof()
dev.write_back()
return dev
def add_symlink(self, symlink, devname):
"""
Add a symlink for a device
"""
self.om.add_symlink(symlink, devname)
def _call_driver_dispatch(self, func, dev_addr, irp_addr):
"""
Call a WDM driver dispatch function with a supplied IRP
"""
stk_ptr = self.get_stack_ptr()
self.set_func_args(stk_ptr, self.return_hook, dev_addr, irp_addr)
self.set_pc(func)
def new_irp(self):
"""
Create a new IRP
"""
return objman.Irp(emu=self)
def irp_mj_create(self, func, dev):
# Generate an IRP for the create request
irp = self.new_irp()
self._call_driver_dispatch(func, dev.address, irp.address)
return irp
def irp_mj_close(self, func, dev):
irp = self.new_irp()
self._call_driver_dispatch(func, dev.address, irp.address)
return irp
def irp_mj_dev_io(self, func, dev):
irp = self.new_irp()
ios = irp.get_curr_stack_loc()
ios.object.MajorFunction = ddk.IRP_MJ_DEVICE_CONTROL
ios.object.Parameters.DeviceIoControl.IoControlCode = 0x5d5d5d5d
ios.write_back()
irp.write_back()
self._call_driver_dispatch(func, dev.address, irp.address)
return irp
def irp_mj_read(self, func, dev):
irp = self.new_irp()
self._call_driver_dispatch(func, dev.address, irp.address)
return irp
def irp_mj_write(self, func, dev):
irp = self.new_irp()
self._call_driver_dispatch(func, dev.address, irp.address)
return irp
def irp_mj_cleanup(self, func, dev):
irp = self.new_irp()
self._call_driver_dispatch(func, dev.address, irp.address)
return irp
def driver_unload(self, drv):
"""
Call the unload routine for a driver
"""
if not drv.on_unload or drv.unload_called:
self.on_emu_complete()
return
stk_ptr = self.get_stack_ptr()
self.set_func_args(stk_ptr, self.exit_hook, drv.address)
self.set_pc(drv.on_unload)
run = Run()
run.type = EP_DRIVER_UNLOAD
run.start_addr = drv.on_unload
run.instr_cnt = 0
run.args = (drv.address,)
run.ret_val = None
self.add_run(run)
drv.unload_called = True
def next_driver_func(self, drv):
func_addr = None
func_handler = None
if self.curr_run.type is not None:
self.curr_run.ret_val = self.get_return_val()
# Check if theres anything in the run queue
if len(self.run_queue):
return
if not self.all_entrypoints:
return
# TODO right now just use the first device object that was created
dev = None
if len(drv.devices):
dev = drv.devices[0]
# Run any remaining IRP handlers
for hdlr, i in ((self.irp_mj_create, ddk.IRP_MJ_CREATE),
(self.irp_mj_dev_io, ddk.IRP_MJ_DEVICE_CONTROL),
(self.irp_mj_read, ddk.IRP_MJ_READ),
(self.irp_mj_write, ddk.IRP_MJ_WRITE),
(self.irp_mj_close, ddk.IRP_MJ_CLOSE),
(self.irp_mj_cleanup, ddk.IRP_MJ_CLEANUP)
):
# Did we run this mj func yet?
if i not in [r.type for r in self.runs]:
func_handler = hdlr
func_addr = int(drv.mj_funcs[i])
if not func_addr:
continue
break
if len(self.delayed_runs):
[self.add_run(r) for r in self.delayed_runs]
self.delayed_runs = []
if not func_addr or not dev:
# We are done here, call the unload routine
self.driver_unload(drv)
return
irp = func_handler(func_addr, dev)
run = Run()
run.type = i
run.start_addr = func_addr
run.instr_cnt = 0
run.args = (dev.address, irp.address)
self.add_run(run)
def on_run_complete(self):
self.curr_run.ret_val = self.get_return_val()
for drv in self.drivers:
drv.read_back()
if drv.pe == self.curr_mod:
self.next_driver_func(drv)
# Dispatch the next run
return self._exec_next_run()
def on_emu_complete(self):
if not self.emu_complete:
self.emu_complete = True
if self.do_strings and self.profiler:
dec_ansi, dec_unicode = self.get_mem_strings()
dec_ansi = [a for a in dec_ansi if a not in self.profiler.strings['ansi']]
dec_unicode = [u for u in dec_unicode if u not in self.profiler.strings['unicode']]
self.profiler.decoded_strings['ansi'] = dec_ansi
self.profiler.decoded_strings['unicode'] = dec_unicode
self.stop()
def set_hooks(self):
"""Set the emulator callbacks"""
super(WinKernelEmulator, self).set_hooks()
if not self.builtin_hooks_set:
self.add_mem_invalid_hook(cb=self._hook_mem_unmapped)
self.add_interrupt_hook(cb=self._hook_interrupt)
self.builtin_hooks_set = True
self.set_mem_tracing_hooks()
def get_kernel_base(self):
"""
Get the base address of the kernel image (ntoskrnl.exe)
"""
# Get kernel base address
kern = self.get_kernel_mod()
return kern.get_base()
def get_kernel_mod(self):
"""
Get the kernel image module
"""
sys_mods = self.get_sys_modules()
for mod in sys_mods:
if mod.name.lower() == 'ntoskrnl':
return mod
raise KernelEmuError('Failed to get kernel base')
def _set_entry_point_names(self):
run_types = {ddk.IRP_MJ_CREATE: 'irp_mj_create',
ddk.IRP_MJ_DEVICE_CONTROL: 'irp_mj_device_control',
ddk.IRP_MJ_READ: 'irp_mj_read',
ddk.IRP_MJ_WRITE: 'irp_mj_write',
ddk.IRP_MJ_CLOSE: 'irp_mj_close',
ddk.IRP_MJ_CLEANUP: 'irp_mj_cleanup',
EP_DRIVER_ENTRY: 'entry_point',
EP_DRIVER_UNLOAD: 'driver_unload'}
for r in self.runs:
if not r.type or run_types.get(r.type):
r.type = run_types.get(r.type, 'unk')
def get_report(self):
""" Retrieve the execution profile for the emulator """
self._set_entry_point_names()
return super(WinKernelEmulator, self).get_report()
def get_json_report(self):
self._set_entry_point_names()
return super(WinKernelEmulator, self).get_json_report()
def setup_kernel_mode(self):
idt = objman.IDT(self)
idt.init_descriptors()
# selector, base, limit, flags
self.reg_write(_arch.X86_REG_IDTR,
(0, idt.object.Descriptors, idt.object.Limit, 0))
# Setup the SSDT
ssdt = self.ktypes.SSDT(self.get_ptr_size())
size = self.get_ptr_size() * 256
ptr = self.mem_map(size, base=None, tag='api.struct.SSDT')
ssdt.NumberOfServices = 256
ssdt.pServiceTable = ptr + self.sizeof(ssdt)
self.mem_write(ptr, self.get_bytes(ssdt))
self.get_sys_modules()
self.setup_msrs()
for sl in self.symlinks:
self.om.add_symlink(sl['name'], sl['target'])
def setup_msrs(self):
"""
Setup machine specific registers for kernel emulation
"""
# Initalize the LSTAR on amd64 with the address of KiSystemCall64
km = self.get_kernel_mod()
if self.get_arch() == _arch.ARCH_AMD64:
ksc64_off = km.find_bytes(b'\x00' * 100, 0)
if ksc64_off != -1:
self.map_decoy(km)
sdt = km.get_export_by_name('KeServiceDescriptorTable')
if sdt:
kbase = km.get_base()
sdt_addr = kbase + sdt
# Set the symbols up
for i in range(0x20):
self.symbols.update({sdt_addr + i:
(km.get_base_name(), 'KeServiceDescriptorTable')})
self.symbols.update({sdt_addr:
(km.get_base_name(),
'KeServiceDescriptorTable.pServiceTable')})
self.symbols.update({sdt_addr + 0x10:
(km.get_base_name(),
'KeServiceDescriptorTable.NumberOfServices')})
ksc64_off += 5
ksc64_addr = kbase + ksc64_off
self.symbols.update({ksc64_addr:
(km.get_base_name(), 'KiSystemCall64')})
# Write the address of our fake KiSystemCall64 to the LSTAR register
self.reg_write(_arch.X86_REG_MSR, (_arch.LSTAR, ksc64_addr))
# ssdt load:
# KeServiceDescriptorTable
sdt_offset = (sdt_addr - ksc64_addr) - 7
data = b'\x90\x90\xc3' + sdt_offset.to_bytes(4, 'little')
self.mem_write(kbase+ksc64_off, data)
ksc64_off += 7
# shadow_ssdt_load:
# KeServiceDescriptorTableShadow
sdt_offset = sdt_addr - (kbase + ksc64_off)
data = b'\x90\x90\xc3' + sdt_offset.to_bytes(4, 'little')
km.set_bytes(ksc64_off, data)
ksc64_off += 7
data = b'\x90\x90\x90\x90\x90\x90\xc3'
km.set_bytes(ksc64_off, data)
+19
View File
@@ -0,0 +1,19 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
__all__ = ["volmgr"] # noqa
from . import * # noqa
def _get_kmods():
def imports():
import types
for name, val in globals().items():
if isinstance(val, types.ModuleType):
yield val
kmods = []
imps = list(imports())
for i in imps:
if 'DriverModule' in dir(i):
kmods.append(i)
return kmods
@@ -0,0 +1,12 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
class KernelModule(object):
def __init__(self):
self.name = ''
def get_mod_name(self):
return self.name.lower()
def ioctl(self, arch, code, inbuf):
raise NotImplementedError
+39
View File
@@ -0,0 +1,39 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
from . import kernel_mod as km
from speakeasy.struct import EmuStruct
import ctypes as ct
import speakeasy.winenv.defs.nt.ddk as ddk
class DISK_EXTENT(EmuStruct):
def __init__(self, ptr_size):
super().__init__(ptr_size)
self.DiskNumber = ct.c_uint32
self.StartingOffset = ct.c_uint64
self.ExtentLength = ct.c_uint64
class VOLUME_DISK_EXTENTS(EmuStruct):
def __init__(self, ptr_size):
super().__init__(ptr_size)
self.NumberOfDiskExtents = ct.c_uint32
self.Extents = DISK_EXTENT * 1
class DriverModule(km.KernelModule):
'''Class for emulation of specific drivers (e.g. their ioctl handlers)'''
def __init__(self):
super(DriverModule, self).__init__()
self.name = 'volmgr'
def ioctl(self, ptr_size, code, inbuf):
vde = VOLUME_DISK_EXTENTS(ptr_size)
vde.NumberOfDiskExtents = 1
vde.Extents[0].DiskNumber = 0
vde.Extents[0].StartingOffset = 0
vde.Extents[0].ExtentLength = 0x1000
return (ddk.STATUS_SUCCESS, vde.get_bytes())
+402
View File
@@ -0,0 +1,402 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
import io
import os
from urllib.parse import urlparse
from io import BytesIO
from speakeasy.errors import NetworkEmuError
def is_empty(bio):
if len(bio.getbuffer()) == bio.tell():
return True
return False
def normalize_response_path(path):
def _get_speakeasy_root():
return os.path.join(os.path.dirname(__file__), os.pardir)
root_var = '$ROOT$'
if root_var in path:
root = _get_speakeasy_root()
return path.replace(root_var, root)
return path
class Socket(object):
"""
Represents a Windows network socket
"""
def __init__(self, fd, family, stype, protocol, flags):
self.fd = fd
self.family = family
self.type = stype
self.protocol = protocol
self.flags = flags
self.connected_host = ''
self.connected_port = 0
self.curr_packet = BytesIO(b'')
self.packet_queue = []
def get_fd(self):
return self.fd
def get_type(self):
return self.type
def set_connection_info(self, host, port):
self.connected_host = host
self.connected_port = port
def get_connection_info(self):
return (self.connected_host, self.connected_port)
def fill_recv_queue(self, responses):
for resp in responses:
mode = resp.get('mode', '')
if mode.lower() == 'default':
default_resp_path = resp.get('path')
if default_resp_path:
default_resp_path = normalize_response_path(default_resp_path)
with open(default_resp_path, 'rb') as f:
self.curr_packet = BytesIO(f.read())
def get_recv_data(self, size, peek=False):
data = self.curr_packet.read(size)
if not peek:
return data
elif peek:
self.curr_packet.seek(-size, os.SEEK_CUR)
return data
class WSKSocket(Socket):
"""
Represents a WSK socket used in kernel mode applications
"""
def __init__(self, fd, family, stype, protocol, flags):
super(WSKSocket, self).__init__(self, fd, family, stype,
protocol, flags)
class WininetComponent(object):
"""
Base class used for WinInet connections
"""
curr_handle = 0x20
config = None
def __init__(self):
super(WininetComponent, self).__init__()
self.handle = self.new_handle()
def new_handle(self):
tmp = WininetComponent.curr_handle
WininetComponent.curr_handle += 4
return tmp
def get_handle(self):
return self.handle
class WininetRequest(WininetComponent):
"""
WinInet request object
"""
def __init__(self, session, verb, objname, ver, ref, accepts, flags, ctx):
super(WininetRequest, self).__init__()
# The WiniNet APIs default to a HTTP "GET" if no verb is specified
if not verb:
self.verb = 'get'
else:
self.verb = verb.lower()
self.objname = objname
if not self.objname:
self.objname = ''
self.objname = urlparse(self.objname)
self.session = session
if not ver:
ver = 'HTTP/1.1'
self.ver = ver
self.referrer = ref
self.accept_types = accepts
self.flags = flags
self.ctx = ctx
self.response = None
def get_session(self):
return self.session
def get_server(self):
return self.get_session().server
def get_port(self):
return self.get_session().port
def get_instance(self):
sess = self.get_session()
return sess.get_instance()
def is_secure(self):
if 'INTERNET_FLAG_SECURE' in self.flags:
return True
return False
def format_http_request(self, headers=None):
request_string = ''
action = '%s %s %s\n' % (self.verb.upper(), self.objname.path,
self.ver.upper())
request_string += action
if headers:
request_string += headers
inst = self.get_instance()
sess = self.get_session()
host = sess.server
request_string += 'Host: %s\n' % (host)
ua = inst.get_user_agent()
if ua:
request_string += 'User-Agent: %s\n' % (ua)
if 'INTERNET_FLAG_KEEP_CONNECTION' in self.flags:
request_string += 'Connection: Keep-Alive\n'
else:
request_string += 'Connection: Close\n'
if 'INTERNET_FLAG_DONT_CACHE' in self.flags:
request_string += 'Cache-Control: no-cache\n'
return request_string
def get_response_size(self):
resp = self.get_response()
off = resp.tell()
size = len(resp.read())
resp.seek(off, io.SEEK_SET)
return size
def get_response(self):
"""
Check the configuration file so see if there is a
handler for the current WinInet request
"""
cfg = WininetComponent.config
if self.response:
return self.response
http = cfg.get('http')
if not http:
raise NetworkEmuError('No HTTP configuration supplied')
resps = http.get('responses')
if not resps:
raise NetworkEmuError('No HTTP responses supplied')
self.response = None
for res in resps:
verb = res.get('verb', '')
if verb.lower() == self.verb:
resp_files = res.get('files', [])
if resp_files:
for file in resp_files:
mode = file.get('mode', '')
if mode.lower() == 'by_ext':
ext = file.get('ext', '')
fn, obj_ext = os.path.splitext(self.objname.path)
if (ext.lower().strip('.') ==
obj_ext.lower().strip('.')):
path = file.get('path')
path = normalize_response_path(path)
with open(path, 'rb') as f:
self.response = BytesIO(f.read())
elif mode.lower() == 'default':
default_resp_path = file.get('path')
default_resp_path = normalize_response_path(default_resp_path)
if not self.response and default_resp_path:
default_resp_path = normalize_response_path(default_resp_path)
with open(default_resp_path, 'rb') as f:
self.response = BytesIO(f.read())
return self.response
def get_object_path(self):
return self.objname
class WininetSession(WininetComponent):
def __init__(self, instance, server, port, user,
password, service, flags, ctx):
super(WininetSession, self).__init__()
self.server = server
self.port = port
self.user = user
self.password = password
self.service = service
self.flags = flags
self.ctx = ctx
self.requests = {}
self.instance = instance
def get_instance(self):
return self.instance
def get_flags(self):
return self.flags
def new_request(self, verb, objname, ver, ref, accepts, flags, ctx):
req = WininetRequest(self, verb, objname, ver, ref,
accepts, flags, ctx)
hdl = req.get_handle()
self.requests.update({hdl: req})
return req
class WininetInstance(WininetComponent):
def __init__(self, user_agent, access, proxy, bypass, flags):
super(WininetInstance, self).__init__()
self.user_agent = user_agent
self.access = access
self.proxy = proxy
self.bypass = bypass
self.flags = flags
self.sessions = {}
def get_session(self, sess_handle):
self.sessions.get(sess_handle)
def add_session(self, handle, session):
self.sessions.update({handle: session})
def new_session(self, server, port, user, password, service, flags, ctx):
sess = WininetSession(self, server, port, user,
password, service, flags, ctx)
hdl = sess.get_handle()
self.sessions.update({hdl: sess})
return sess
def get_user_agent(self):
return self.user_agent
class NetworkManager(object):
"""
Class that manages network connections during emulation
"""
def __init__(self, config):
super(NetworkManager, self).__init__()
self.sockets = {}
self.wininets = {}
self.curr_fd = 4
self.curr_handle = 0x20
self.config = config
self.dns = {}
WininetComponent.config = config
self.dns = self.config.get('dns')
def new_socket(self, family, stype, protocol, flags):
fd = self.curr_fd
sock = Socket(fd, family, stype, protocol, flags)
self.curr_fd += 4
if self.config:
winsock = self.config.get('winsock')
if winsock:
responses = winsock.get('responses')
if responses:
sock.fill_recv_queue(responses)
self.sockets.update({fd: sock})
return sock
def name_lookup(self, domain):
if not self.dns:
return None
names = self.dns.get('names')
# Do we have an IP for this name?
if domain.lower() not in names.keys():
# use the default IP (if any)
return names.get('default')
return names.get(domain)
def get_dns_txt(self, domain):
"""
Return a configured DNS TXT record (if any)
"""
def _read_txt_data(txt):
path = txt.get('path')
if path:
path = normalize_response_path(path)
with open(path, 'rb') as f:
return f.read()
if not self.dns:
return None
txts = self.dns.get('txt', [])
txt = [t for t in txts if t.get('name', '') == domain]
if txt:
return _read_txt_data(txt[0])
txt = [t for t in txts if t.get('name', '') == 'default']
if txt:
return _read_txt_data(txt[0])
def ip_lookup(self, ip):
for item in self.dns:
if item['response'] == ip:
return item['query']
return None
def new_wininet_inst(self, user_agent, access, proxy, bypass, flags):
wini = WininetInstance(user_agent, access, proxy, bypass, flags)
self.wininets.update({wini.get_handle(): wini})
return wini
def get_wininet_object(self, handle):
for hinst, inst in self.wininets.items():
if hinst == handle:
return inst
for hsess, sess in inst.sessions.items():
if hsess == handle:
return sess
for hreq, req in sess.requests.items():
if hreq == handle:
return req
def close_wininet_object(self, handle):
if self.wininets.get(handle):
self.wininets.pop(handle)
def get_socket(self, fd):
return self.sockets.get(fd)
def close_socket(self, fd):
self.sockets.pop(fd)
+876
View File
@@ -0,0 +1,876 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
import os
import ntpath
import speakeasy.winenv.defs.nt.ntoskrnl as ntoskrnl
import speakeasy.winenv.arch as _arch
import speakeasy.winenv.defs.nt.ddk as ddk
import speakeasy.winenv.defs.windows.windows as windef
class Console(object):
"""
Represents a console window object
"""
curr_handle = 0x340
def __init__(self):
self.handle = self.get_handle()
self.window = 0
def get_handle(self):
tmp = Console.curr_handle
Console.curr_handle += 4
return tmp
def set_window(self, window):
self.window = window
def get_window(self):
return self.window
class SEH(object):
"""
Implements the structures needed to support SEH handling during emulation
"""
class ScopeRecord(object):
def __init__(self, record):
self.record = record
self.filter_called = False
self.handler_called = False
class Frame(object):
def __init__(self, entry, scope_table, scope_records):
self.entry = entry
self.scope_table = scope_table
self.scope_records = []
for rec in scope_records:
SEH.ScopeRecord(rec)
self.scope_records.append(SEH.ScopeRecord(rec))
self.searched = False
def __init__(self):
self.context = None
self.context_address = 0
self.record = None
self.frames = []
self.last_func = 0
self.last_exception_code = 0
self.exception_ptrs = 0
self.handler_ret_val = None
def set_context(self, context, address=0):
self.context = context
self.context_address = address
def get_context(self):
return self.context
def set_last_func(self, func):
self.last_func = func
def set_record(self, record, address=0):
self.record = record
def set_current_frame(self, frame):
self.frame = frame
def get_frames(self):
return self.frames
def clear_frames(self):
self.frames = []
def add_frame(self, entry, scope_table, records):
frame = SEH.Frame(entry, scope_table, records)
self.frames.append(frame)
class KernelObject(object):
"""
Base class for Kernel objects managed by the object manager
"""
curr_handle = 0x220
curr_id = 0x400
def __init__(self, emu):
self.emu = emu
self.address = None
self.name = ''
self.object = 0
self.ref_cnt = 0
self.handles = []
self.arch = emu.get_arch()
self.id = KernelObject.curr_id
KernelObject.curr_id += 4
self.nt_types = ntoskrnl
self.win_types = windef
def sizeof(self, obj=None):
if obj:
return obj.sizeof()
return self.object.sizeof()
def get_bytes(self, obj=None):
if obj:
return obj.get_bytes()
return self.object.get_bytes()
def read_back(self):
data = self.emu.mem_read(self.address, self.sizeof())
self.object.cast(data)
return self
def write_back(self):
data = self.get_bytes()
if data and self.address:
self.emu.mem_write(self.address, data)
def get_id(self):
return self.id
def set_id(self, oid):
self.id = oid
def get_class_name(self):
if self.object:
return self.object.__class__.__name__
def get_mem_tag(self):
return 'emu.struct.%s' % (self.get_class_name())
def get_handle(self):
tmp = KernelObject.curr_handle
KernelObject.curr_handle += 4
self.handles.append(tmp)
return tmp
class Driver(KernelObject):
"""
Class that represents DRIVER_OBJECTs created by the Windows kernel
"""
ldr_entries = []
def __init__(self, emu):
super(Driver, self).__init__(emu=emu)
self.pe = None
self.devices = []
self.mj_funcs = [None] * (ddk.IRP_MJ_MAXIMUM_FUNCTION + 1)
self.on_unload = None
self.unload_called = False
self.reg_path_ptr = 0
self.reg_path = ''
self.name = ''
self.basename = ''
self.object = self.nt_types.DRIVER_OBJECT(emu.get_ptr_size())
def create_reg_path(self, name):
"""
Create the service path in the registry for the created driver
"""
self.reg_path = '\\Registry\\Machine\\System\\CurrentControlSet\\Services\\%s\x00' % (name) # noqa
buf = self.reg_path.encode('utf-16le')
self.reg_path = self.reg_path.strip('\x00')
us = self.nt_types.UNICODE_STRING(self.emu.get_ptr_size())
size = self.sizeof(us) + len(buf)
addr = self.emu.mem_map(size, tag='emu.object.%s.reg_path' % self.name)
us.Length = len(buf) - 2
us.MaximumLength = len(buf)
us.Buffer = addr + self.sizeof(us)
self.emu.mem_write(addr, self.get_bytes(us))
self.emu.mem_write(us.Buffer, buf)
self.reg_path_ptr = addr
def get_basename(self):
return self.basename.lower()
def get_reg_path(self):
return self.reg_path
def init_driver_section(self):
"""
Create the driver section for the driver. This is a linked list that
links together driver objects
"""
tag = 'emu.object.%s.DriverSection' % self.name
mod = self.pe
if mod and mod.get_emu_path():
mod_name = mod.get_emu_path()
else:
mod_name = 'None'
ldte = LdrDataTableEntry(self.emu, mod_name, tag=tag)
first = None
last = None
if len(self.ldr_entries):
first = self.ldr_entries[0]
last = self.ldr_entries[-1]
self.ldr_entries.append(ldte)
ldte.object.DllBase = mod.get_base()
dllname = mod.get_emu_path().encode('utf-16le')
name_addr = ldte.address + ldte.sizeof()
self.emu.mem_write(name_addr, dllname)
# Set the dll full name
ldte.object.FullDllName.Length = len(dllname)
ldte.object.FullDllName.MaximumLength = len(dllname)
ldte.object.FullDllName.Buffer = name_addr
# Set the dll base name
dllname = ntpath.basename(mod.get_emu_path()).encode('utf-16le')
ldte.object.BaseDllName.Length = len(dllname)
ldte.object.BaseDllName.MaximumLength = len(dllname)
ldte.object.BaseDllName.Buffer = name_addr + \
(ldte.object.FullDllName.Length - len(dllname))
if not last:
ldte.object.InLoadOrderLinks.Flink = ldte.address
ldte.object.InLoadOrderLinks.Flink = ldte.address
ldte.object.InLoadOrderLinks.Blink = ldte.address
ldte.object.InMemoryOrderLinks.Blink = ldte.address
else:
ldte.object.InLoadOrderLinks.Flink = \
last.object.InLoadOrderLinks.Flink
ldte.object.InMemoryOrderLinks.Flink = \
last.object.InMemoryOrderLinks.Flink
ldte.object.InLoadOrderLinks.Blink = last.address
ldte.object.InMemoryOrderLinks.Blink = last.address
last.object.InLoadOrderLinks.Flink = ldte.address
last.object.InMemoryOrderLinks.Flink = ldte.address
first.object.InLoadOrderLinks.Blink = ldte.address
first.object.InMemoryOrderLinks.Blink = ldte.address
last.write_back()
ldte.write_back()
return ldte
def init_driver_object(self, name=None, pe=None):
"""
Initialize the DRIVER_OBJECT
"""
self.pe = pe
drvobj = self.object
drvobj.Type = 4
drvobj.Size = self.sizeof()
drvobj.DeviceObject = 0
drvobj.Flags = 2
us = ''
if pe:
drvobj.DriverStart = pe.get_base()
drvobj.DriverSize = pe.image_size
drvobj.DriverInit = pe.get_base() + pe.ep
if not name:
drvname = os.path.splitext(pe.path)[0]
drvname = os.path.basename(drvname)
self.name = r'\Driver\%s' % (drvname)
us = ('\\Driver\\%s\x00' % (drvname)).encode('utf-16le')
name = self.name
if name:
us = name.encode('utf-16le')
self.name = name
if not pe and not self.name:
name = 'none'
# Allocate the driver object
addr = self.emu.mem_map(drvobj.Size + len(us),
tag='emu.object.%s' % name)
drvobj.DriverName.Length = len(us)
drvobj.DriverName.MaximumLength = len(us)
drvobj.DriverName.Buffer = addr + drvobj.Size
name = self.name
idx = name.rfind('\\')
if idx >= 0 and idx != len(name) - 1:
name = self.name[idx+1:]
else:
name = 'None'
self.basename = name
self.create_reg_path(name)
drv_sect = self.init_driver_section()
drvobj.DriverSection = drv_sect.address
self.address = addr
self.emu.mem_write(addr, self.get_bytes() + us)
def read_back(self):
super(Driver, self).read_back()
for i, func in enumerate(self.mj_funcs):
self.mj_funcs[i] = self.object.MajorFunction[i]
self.on_unload = self.object.DriverUnload
class Device(KernelObject):
"""
Represents a DEVICE_OBJECT created by the windows kernel
"""
def __init__(self, emu):
super(Device, self).__init__(emu=emu)
devobj = self.nt_types.DEVICE_OBJECT(emu.get_ptr_size())
devobj.Type = 0x3
devobj.Size = self.sizeof(devobj)
devobj.ReferenceCount = 1
self.file_object = None
self.object = devobj
self.driver = None
def get_parent_driver(self):
return self.driver
class FileObject(KernelObject):
"""
Represents a FILE_OBJECT created by the windows kernel
"""
def __init__(self, emu):
super(FileObject, self).__init__(emu=emu)
fileobj = self.nt_types.FILE_OBJECT(emu.get_ptr_size())
fileobj.Type = 0x5
fileobj.Size = self.sizeof(fileobj)
self.object = fileobj
self.address = emu.mem_map(self.sizeof(), tag=self.get_mem_tag())
class IoStackLocation(KernelObject):
"""
Represents a IO_STACK_LOCATION struct that is part of
an IRP.
"""
def __init__(self, emu):
super(IoStackLocation, self).__init__(emu=emu)
self.object = self.nt_types.IO_STACK_LOCATION(emu.get_ptr_size())
# Allocate two stack locations for now to handle IoGetNextIrpStackLocation calls
self.address = emu.mem_map(self.sizeof() * 2, tag=self.get_mem_tag())
class Irp(KernelObject):
"""
I/O request packet used when performing device input/output
"""
def __init__(self, emu):
super(Irp, self).__init__(emu=emu)
self.object = self.nt_types.IRP(emu.get_ptr_size())
self.address = emu.mem_map(self.sizeof(), tag=self.get_mem_tag())
self.stack_locations = []
ios = IoStackLocation(emu=emu)
ios.write_back()
self.object.Tail.Overlay.CurrentStackLocation = ios.address + ios.sizeof()
self.stack_locations.append(ios)
self.object.Type = 0x6
self.object.Size = self.sizeof()
self.write_back()
def get_curr_stack_loc(self):
return self.stack_locations[0]
class Thread(KernelObject):
"""
Represents a Windows ETHREAD object that describes a
an OS level thread
"""
def __init__(self, emu, stack_base=0, stack_commit=0):
super(Thread, self).__init__(emu=emu)
self.emu = emu
self.object = self.nt_types.ETHREAD(emu.get_ptr_size())
self.address = emu.mem_map(self.sizeof(), tag=self.get_mem_tag())
self.object.Data = b'\xFF' * self.sizeof()
self.tid = self.get_id()
self.modified_pc = False
self.teb = None
self.seh = SEH()
self.tls = []
self.ctx = None
self.fls = []
self.suspend_count = 0
self.token = Token(self.emu)
self.last_error = 0
self.stack_base = stack_base
self.stack_commit = stack_commit
self.write_back()
self.process = None
def get_seh(self):
return self.seh
def get_context(self):
if self.ctx:
return self.ctx
return self.emu.get_thread_context()
def set_context(self, ctx):
if self.ctx:
if self.emu.get_arch() == _arch.ARCH_X86:
if ctx.Eip != self.ctx.Eip:
self.modified_pc = True
self.ctx = ctx
def init_teb(self, teb_addr, peb_addr):
if not self.teb:
self.teb = TEB(emu=self.emu, address=teb_addr)
self.teb.object.NtTib.StackBase = self.stack_base
self.teb.object.NtTib.Self = teb_addr
self.teb.object.NtTib.StackLimit = self.stack_commit
self.teb.object.ProcessEnvironmentBlock = peb_addr
self.teb.write_back()
def get_teb(self):
return self.teb.read_back()
def set_last_error(self, code):
self.last_error = code
def get_last_error(self):
return self.last_error
def get_tls(self):
return self.tls
def set_tls(self, tls):
self.tls = tls
def get_fls(self):
return self.fls
def set_fls(self, fls):
self.fls = fls
def get_token(self):
return self.token
class Token(KernelObject):
"""
Represents a TOKEN object
"""
def __init__(self, emu):
super(Token, self).__init__(emu=emu)
class Process(KernelObject):
"""
An EPROCESS object used by the Windows kernel to represent a process
"""
ldr_entries = []
def __init__(self, emu, pe=None, user_modules=[],
name='', path='', cmdline='', base=0, session=0):
super(Process, self).__init__(emu=emu)
# TODO: For now just allocate a blank opaque struct for an EPROCESS
self.object = self.nt_types.EPROCESS(emu.get_ptr_size())
self.address = emu.mem_map(self.sizeof(), tag=self.get_mem_tag(), perms=1, base=0xe0000000)
self.name = name
self.base = base
self.pid = self.get_id()
self.modules = user_modules
self.threads = []
self.console = None
self.curr_thread = None
self.cmdline = cmdline
self.session = session
self.token = Token(self.emu)
emu.add_object(self.token)
self.pe = pe
self.stdin = (0xf000) + 1
self.stdout = (0xf000) + 2
self.stderr = (0xf000) + 3
# Initialize the process PEB
self.peb = PEB(emu=emu)
self.peb_ldr_data = PebLdrData(self.emu)
self.is_peb_active = False
self.set_process_parameters()
self.path = path
self.image = ''
self.title = ''
if pe and pe.OPTIONAL_HEADER.Subsystem & ddk.WINDOWS_CONSOLE:
self.alloc_console()
def get_peb(self):
return self.peb
def set_peb_ldr_address(self, addr):
self.peb.object.Ldr = addr
self.peb.write_back()
self.peb_ldr_data.address = addr
def set_process_parameters(self):
self.peb.object.ProcessParameters = self.emu.mem_map(0x40,
tag=self.get_mem_tag() +
'.ProcessParameters')
self.peb.write_back()
def get_peb_ldr(self):
return self.peb_ldr_data
def alloc_console(self):
if not self.console:
self.console = Console()
sm = self.emu.get_session_manager()
desk = sm.get_current_desktop()
self.console.set_window(desk.new_window())
def get_desktop_name(self):
sm = self.emu.get_session_manager()
stat = sm.get_current_station()
stat_name = stat.get_name()
desk = sm.get_current_desktop()
desk_name = desk.get_name()
name = '%s\\%s' % (stat_name, desk_name)
return name
def get_token(self):
"""
Get the token associated with the process
"""
return self.token
def get_std_handle(self, dev):
STD_INPUT_HANDLE = 0xfffffff6
STD_OUTPUT_HANDLE = 0xfffffff5
STD_ERROR_HANDLE = 0xfffffff4
for k, v in ((STD_INPUT_HANDLE, self.stdin),
(STD_OUTPUT_HANDLE, self.stdout),
(STD_ERROR_HANDLE, self.stderr),
):
if k == dev:
return v
return 0
def get_title_name(self):
return self.title
def get_module(self):
return self.pe
def get_ep(self):
if self.pe:
return self.pe.get_ep()
def get_console(self):
return self.console
def get_session_id(self):
return self.session
def get_pid(self):
return self.pid
def get_process_path(self):
return self.path
def get_command_line(self):
return self.cmdline
def set_user_modules(self, mods):
self.modules = mods
def new_thread(self):
thr = Thread(self.emu)
self.threads.append(thr)
def add_module_to_peb(self, module):
pld = self.peb_ldr_data
list_type = self.nt_types.LIST_ENTRY(self.emu.get_ptr_size())
# Initialize the LDTE
ldte = LdrDataTableEntry(self.emu, module.get_emu_path())
if not self.ldr_entries:
prev = ldte
else:
prev = self.ldr_entries[-1]
self.ldr_entries.append(ldte)
first = self.ldr_entries[0]
ldte.object.DllBase = module.get_base()
dllname = (module.get_emu_path() + '\x00').encode('utf-16le')
name_addr = ldte.address + ldte.sizeof()
self.emu.mem_write(name_addr, dllname)
# Set the dll full name
ldte.object.FullDllName.Length = len(dllname) - 2
ldte.object.FullDllName.MaximumLength = len(dllname)
ldte.object.FullDllName.Buffer = name_addr
# Set the dll base name
dllname = (ntpath.basename(module.get_emu_path()) +
'\x00').encode('utf-16le')
ldte.object.BaseDllName.Length = len(dllname) - 2
ldte.object.BaseDllName.MaximumLength = len(dllname)
ldte.object.BaseDllName.Buffer = name_addr + \
(ldte.object.FullDllName.MaximumLength - len(dllname))
ldte.write_back()
prev.object.InLoadOrderLinks.Flink = ldte.address
prev.object.InMemoryOrderLinks.Flink = ldte.address + \
self.sizeof(list_type)
imol = prev.object.InMemoryOrderLinks.Flink
prev.object.InInitializationOrderLinks.Flink = imol + \
self.sizeof(list_type)
ldte.object.InLoadOrderLinks.Blink = prev.address
ldte.object.InMemoryOrderLinks.Blink = prev.address + \
self.sizeof(list_type)
imol = ldte.object.InMemoryOrderLinks.Blink
ldte.object.InInitializationOrderLinks.Blink = imol + \
self.sizeof(list_type)
prev.write_back()
ldte.write_back()
first.object.InLoadOrderLinks.Blink = prev.address
first.object.InMemoryOrderLinks.Blink = prev.address
first.object.InInitializationOrderLinks.Blink = prev.address
first.write_back()
pld.object.InLoadOrderModuleList.Flink = first.address
pld.object.InMemoryOrderModuleList.Flink = \
pld.object.InLoadOrderModuleList.Flink + \
self.sizeof(list_type)
pld.object.InInitializationOrderModuleList.Flink = \
pld.object.InMemoryOrderModuleList.Flink + \
self.sizeof(list_type)
pld.object.InLoadOrderModuleList.Blink = prev.address
pld.object.InMemoryOrderModuleList.Blink = \
pld.object.InLoadOrderModuleList.Blink + \
self.sizeof(list_type)
pld.object.InInitializationOrderModuleList.Blink = \
pld.object.InMemoryOrderModuleList.Blink + \
self.sizeof(list_type)
pld.write_back()
self.peb.object.Ldr = pld.address
self.peb.write_back()
def init_peb(self, modules):
self.peb.object.ImageBaseAddress = self.base
# Add an entry for each module in the module list
for mod in modules:
self.add_module_to_peb(mod)
class PEB(KernelObject):
"""
Represents the process environment block. This structure contains a large amount of
fields that are used internally by Windows. Shellcode may parse this structure in
order to resolve exported functions.
"""
def __init__(self, emu):
super(PEB, self).__init__(emu=emu)
self.object = self.nt_types.PEB(emu.get_ptr_size())
self.address = emu.mem_map(self.sizeof(), tag=self.get_mem_tag())
class TEB(KernelObject):
"""
Represents the thread environment block. This structure contains a large amount of
fields that are used internally by Windows.
"""
def __init__(self, emu, address=0):
super(TEB, self).__init__(emu=emu)
self.object = self.nt_types.TEB(emu.get_ptr_size())
if address:
self.address = address
else:
self.address = emu.mem_map(self.sizeof(), tag=self.get_mem_tag())
class PebLdrData(KernelObject):
def __init__(self, emu):
super(PebLdrData, self).__init__(emu=emu)
self.object = self.nt_types.PEB_LDR_DATA(emu.get_ptr_size())
self.address = 0
class LdrDataTableEntry(KernelObject):
def __init__(self, emu, dllname, tag=''):
super(LdrDataTableEntry, self).__init__(emu=emu)
self.object = self.nt_types.LDR_DATA_TABLE_ENTRY(emu.get_ptr_size())
size = self.sizeof()
size += len((dllname + '\x00').encode('utf-16le'))
if not tag:
tag = self.get_mem_tag()
self.address = emu.mem_map(size, tag=tag)
class IDT(KernelObject):
"""
Represents the Interrupt descriptor table. This is currently a dummy structure and
only exists to detect if samples read or write to it.
"""
def __init__(self, emu):
super(IDT, self).__init__(emu=emu)
self.object = self.nt_types.IDT(emu.get_ptr_size())
self.address = emu.mem_map(self.sizeof(), tag=self.get_mem_tag())
def init_descriptors(self):
tbl = self.nt_types.DESCRIPTOR_TABLE(self.emu.get_ptr_size())
kbase = self.emu.get_kernel_base()
descs = self.emu.mem_map(self.sizeof(tbl),
tag=self.get_mem_tag() + '.idt_entries')
self.object.Limit = 0xFFF
self.object.Descriptors = descs
if self.emu.get_arch() == _arch.ARCH_X86:
for i, entry in enumerate(tbl.Table):
entry.OffsetLow = 0 + (4 * i)
entry.Base = kbase
elif self.emu.get_arch() == _arch.ARCH_AMD64:
for i, entry in enumerate(tbl.Table):
entry.OffsetLow = 0xFFFF & kbase
entry.OffsetMiddle = (0xFFFF0000 & kbase) >> 16
entry.OffsetHigh = (0xFFFFFFFF00000000 & kbase) >> 32
self.emu.mem_write(descs, self.get_bytes(tbl))
self.write_back()
class Event(KernelObject):
"""
Describes event objects used by Windows for synchronization
"""
def __init__(self, emu):
super(Event, self).__init__(emu=emu)
self.object = self.nt_types.KEVENT(emu.get_ptr_size())
self.address = emu.mem_map(self.sizeof(), tag=self.get_mem_tag())
class Mutant(KernelObject):
"""
Describes mutant objects used by Windows for synchronization
"""
def __init__(self, emu):
super(Mutant, self).__init__(emu=emu)
self.object = self.nt_types.MUTANT(emu.get_ptr_size())
self.address = emu.mem_map(self.sizeof(), tag=self.get_mem_tag())
class ObjectManager(object):
"""
Class that manages kernel objects during emulation
"""
def __init__(self, emu):
super(ObjectManager, self).__init__()
self.emu = emu
self.objects = {}
self.symlinks = []
def add_symlink(self, link, dev):
self.symlinks.append((link, dev))
def new_object(self, obj_type):
obj = obj_type(emu=self.emu)
obj.set_id(self.new_id())
return self.add_object(obj)
def add_object(self, obj):
if not self.get_object_from_addr(obj.address):
self.objects.update({obj.address: obj})
if not obj.id:
obj.id = self.new_id()
obj.ref_cnt += 1
return obj
def get_handle(self, obj):
tmp = KernelObject.curr_handle
KernelObject.curr_handle += 4
obj.handles.append(tmp)
return tmp
def new_id(self):
_id = KernelObject.curr_id
KernelObject.curr_id += 4
return _id
def get_object_from_addr(self, addr):
return self.objects.get(addr)
def get_object_from_id(self, id):
for a, o in self.objects.items():
if o.get_id() == id:
return o
def get_object_from_name(self, name, check_symlinks=True):
name = name.rstrip('\\')
for a, o in self.objects.items():
if o.name.lower() == name.lower():
return o
if check_symlinks:
m = [sl[1] for sl in self.symlinks
if name.lower() == sl[0].lower()]
if m:
name = m[0]
return self.get_object_from_name(name, False)
def get_object_from_handle(self, handle):
for a, o in self.objects.items():
if handle in o.handles:
return o
+215
View File
@@ -0,0 +1,215 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
import base64
import fnmatch
import speakeasy.winenv.defs.registry.reg as regdefs
from speakeasy.errors import RegistryEmuError
HKEY_CLASSES_ROOT = 0x80000000
HKEY_CURRENT_USER = 0x80000001
HKEY_LOCAL_MACHINE = 0x80000002
HKEY_USERS = 0x80000003
HKEY_CLASSES_ROOT = 0x80000005
class RegValue(object):
"""
Represents a registry value
"""
def __init__(self, name, val_type, data):
self.name = name
self.type = val_type
self.data = self.normalize_value(val_type, data)
def normalize_value(self, val_type, data):
"""
Convert registry values to python types
"""
if val_type in (regdefs.REG_EXPAND_SZ, regdefs.REG_MULTI_SZ, regdefs.REG_SZ):
if not isinstance(data, str):
raise RegistryEmuError('Invalid registry value expected string')
return data
elif val_type in (regdefs.REG_DWORD, regdefs.REG_QWORD):
if isinstance(data, str):
return int(data, 0)
elif isinstance(data, int):
return data
elif val_type == regdefs.REG_BINARY:
# Binary data is expected to be base64'd
return base64.b64encode(data.encode('utf-8'))
else:
return data
def get_name(self):
return self.name
def get_type(self):
return self.type
def get_data(self):
return self.data
class RegKey(object):
"""
Represents a registry key
"""
curr_handle = 0x180
def __init__(self, path):
self.path = path
self.values = []
def get_handle(self):
hkey = RegKey.curr_handle
RegKey.curr_handle += 4
return hkey
def get_path(self):
return self.path
def create_value(self, name, val_type, value):
val = RegValue(name, val_type, value)
self.values.append(val)
return val
def get_values(self):
return self.values
def get_value(self, val_name):
for v in self.values:
if val_name.lower() == v.get_name().lower():
return v
class RegistryManager(object):
"""
Manages the emulation of the windows registry. This includes creating keys, subkeys and values
"""
def __init__(self, config=None):
super(RegistryManager, self).__init__()
self.reg_handles = {}
self.keys = []
self.config = config
self.reg_tree = []
for hk in (HKEY_CLASSES_ROOT, HKEY_CURRENT_USER,
HKEY_LOCAL_MACHINE, HKEY_USERS, HKEY_CLASSES_ROOT):
path = regdefs.get_hkey_type(hk)
key = self.create_key(path)
self.reg_handles.update({hk: key})
def normalize_reg_path(self, path):
new = path
roots = ('\\registry\\machine\\', 'hklm\\')
for r in roots:
if path.lower().startswith(r):
new = 'HKEY_LOCAL_MACHINE\\' + path[len(r):]
return new
return path
def get_key_from_handle(self, handle):
return self.reg_handles.get(handle)
def get_key_from_path(self, path):
path = self.normalize_reg_path(path)
for key in self.keys:
if fnmatch.fnmatch(key.get_path().lower(), path.lower()):
return key
return None
def is_key_a_parent_key(self, path):
for key in self.keys:
if key.get_path().lower().startswith(path.lower()):
return True
return False
def get_subkeys(self, key):
# TODO: once we revamp the registry emulation,
# make this better
parent_path = key.get_path()
subkeys = []
for k in self.keys:
test_path = k.get_path()
if test_path.lower().startswith(parent_path.lower()):
sub = test_path[len(parent_path):]
if sub.startswith('\\'):
sub = sub[1:]
end_slash = sub.find('\\')
if end_slash >= 0:
sub = sub[:end_slash]
if not sub:
continue
subkeys.append(sub)
return subkeys
def get_key_from_config(self, path):
"""
See if the emulator config file contains a handler for the requested registry path
"""
for key in self.config.get('keys', []):
if key['path'].lower() == path.lower():
new_key = RegKey(path)
for value in key.get('values', []):
val_type = value.get('type')
vts = regdefs.get_flag_value(val_type) # noqa
val_name = value.get('name', '')
data = value.get('data')
new_key.create_value(val_name, val_type, data)
return new_key
return None
def create_key(self, path):
"""
Create a registry key
"""
path = self.normalize_reg_path(path)
# Does this key already exist?
key = self.get_key_from_path(path)
if key:
return key
# Does this key exist in our config
key = self.get_key_from_config(path)
if key:
return key
key = RegKey(path)
self.keys.append(key)
return key
def open_key(self, path, create=False):
"""
Open or optionally create a registry key
"""
hnd = None
path = self.normalize_reg_path(path)
# Does the key already exist?
key = self.get_key_from_path(path)
if key:
hnd = key.get_handle()
self.reg_handles.update({hnd: key})
return hnd
# Does this key exist in our config
key = self.get_key_from_config(path)
if key:
return key
# If we are instructed to create the key, do so
if create or self.is_key_a_parent_key(path):
key = RegKey(path)
hnd = key.get_handle()
self.reg_handles.update({hnd: key})
self.keys.append(key)
return hnd
+128
View File
@@ -0,0 +1,128 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
class GuiObject(object):
"""
Base class for all GUI objects
"""
curr_handle = 0x120
def __init__(self):
self.handle = self.get_handle()
def get_handle(self):
tmp = GuiObject.curr_handle
GuiObject.curr_handle += 4
return tmp
class Session(GuiObject):
"""
Represents a windows Session
"""
def __init__(self, sess_id):
super(Session, self).__init__()
self.id = sess_id
self.stations = {}
def new_station(self, name='WinSta0'):
stat = Station(name=name)
self.stations.update({stat.get_handle(): stat})
return stat
class Station(GuiObject):
"""
Represents a window station
"""
def __init__(self, name=''):
super(Station, self).__init__()
self.name = name
self.desktops = {}
def new_desktop(self, name=''):
desk = Desktop(name=name)
self.desktops.update({desk.get_handle(): desk})
return desk
def get_name(self):
return self.name
class Desktop(GuiObject):
"""
Represents a Desktop object
"""
def __init__(self, name=''):
super(Desktop, self).__init__()
self.windows = {}
self.desktop_window = self.new_window()
self.name = name
def new_window(self):
# create the desktop window
window = Window()
self.windows.update({window.get_handle(): window})
return window
def get_desktop_window(self):
return self.desktop_window
def get_name(self):
return self.name
class Window(GuiObject):
"""
Represents a GUI window
"""
def __init__(self):
super(Window, self).__init__()
class SessionManager(object):
"""
The session manager for the emulator. This will manage things like desktops,
windows, and session isolation
"""
def __init__(self, config):
super(SessionManager, self).__init__()
self.sessions = {}
self.curr_session = None
self.curr_station = None
self.curr_desktop = None
self.config = config
self.dev_ctx = GuiObject.curr_handle
# create a session 0
self.curr_session = Session(sess_id=0)
# create WinSta0
self.curr_station = self.curr_session.new_station(name='WinSta0')
# Create a desktop
self.curr_station.new_desktop('Winlogon')
default = self.curr_station.new_desktop('Default')
self.curr_station.new_desktop('Disconnect')
# For now lets default to the Default desktop
self.curr_desktop = default
def get_device_context(self):
return self.dev_ctx
def get_current_desktop(self):
return self.curr_desktop
def get_current_station(self):
return self.curr_station
def get_gui_object(self, handle):
for hsess, sess in self.sessions.items():
if hsess == handle:
return sess
for hstat, stat in sess.stations.items():
if hstat == handle:
return stat
for hdesk, desk in stat.desktops.items():
if hdesk == handle:
return desk
+598
View File
@@ -0,0 +1,598 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
import os
import shlex
import ntpath
import hashlib
import capstone as cs
import speakeasy.winenv.arch as _arch
import speakeasy.windows.common as w32common
from speakeasy.profiler import Run
from speakeasy.windows.winemu import WindowsEmulator
import speakeasy.windows.objman as objman
from speakeasy.winenv.api.winapi import WindowsApi
import speakeasy.common as common
from speakeasy.errors import Win32EmuError
from speakeasy.windows.sessman import SessionManager
DLL_PROCESS_DETACH = 0
DLL_PROCESS_ATTACH = 1
MAX_EXPORTS_TO_EMULATE = 10
class Win32Emulator(WindowsEmulator):
"""
User Mode Windows Emulator Class
"""
def __init__(self, config, argv=[], debug=False, logger=None, exit_event=None):
super(Win32Emulator, self).__init__(config, debug=debug, logger=logger,
exit_event=exit_event)
self.last_error = 0
self.peb_addr = 0
self.heap_allocs = []
self.argv = argv
self.sessman = SessionManager(config)
def get_argv(self):
"""
Get command line arguments (if any) that are being passed
to the emulated process. (e.g. main(argv))
"""
argv0 = ''
out = []
if len(self.argv):
for m in self.modules:
pe = m[0]
emu_path = m[2]
if pe.is_exe():
argv0 = emu_path
out = [argv0] + self.argv
elif self.command_line:
out = shlex.split(self.command_line, posix=False)
return out
def set_last_error(self, code):
"""
Set the last error code for the current thread
"""
if self.curr_thread:
self.curr_thread.set_last_error(code)
def get_last_error(self):
"""
Get the last error code for the current thread
"""
if self.curr_thread:
return self.curr_thread.get_last_error()
def get_session_manager(self):
"""
Get the session manager for the emulator. This will manage things like desktops,
windows, and session isolation
"""
return self.sessman
def add_vectored_exception_handler(self, first, handler):
"""
Add a vectored exception handler that will be executed on an exception
"""
self.veh_handlers.append(handler)
def remove_vectored_exception_handler(self, handler):
"""
Remove a vectored exception handler
"""
self.veh_handlers.remove(handler)
def get_processes(self):
if len(self.processes) <= 1:
self.init_processes(self.config_processes)
return self.processes
def init_processes(self, processes):
"""
Initialize configured processes set in the emulator config
"""
for proc in processes:
p = objman.Process(self)
self.add_object(p)
p.name = proc.get('name', '')
new_pid = proc.get('pid')
if new_pid:
p.pid = new_pid
base = proc.get('base_addr')
if isinstance(base, str):
base = int(base, 16)
p.base = base
p.path = proc.get('path')
p.session = proc.get('session', 0)
p.image = ntpath.basename(p.path)
self.processes.append(p)
def load_module(self, path=None, data=None):
"""
Load a module into the emulator space from the specified path
"""
pe = self.load_pe(path=path, data=data, imp_id=w32common.IMPORT_HOOK_ADDR)
if pe.arch == _arch.ARCH_X86:
disasm_mode = cs.CS_MODE_32
elif pe.arch == _arch.ARCH_AMD64:
disasm_mode = cs.CS_MODE_64
else:
raise Win32EmuError('Unsupported architecture: %s', pe.arch)
if not self.arch:
self.arch = pe.arch
self.set_ptr_size(self.arch)
self.emu_eng.init_engine(_arch.ARCH_X86, pe.arch)
if not self.disasm_eng:
self.disasm_eng = cs.Cs(cs.CS_ARCH_X86, disasm_mode)
if not data:
file_name = os.path.basename(path)
mod_name = os.path.splitext(file_name)[0]
else:
mod_hash = hashlib.sha256()
mod_hash.update(data)
mod_hash = mod_hash.hexdigest()
mod_name = mod_hash
file_name = '%s.exe' % (mod_name)
self.api = WindowsApi(self)
cd = self.get_cd()
if not cd.endswith('\\'):
cd += '\\'
emu_path = cd + file_name
if not data:
with open(path, 'rb') as f:
data = f.read()
self.fileman.add_existing_file(emu_path, data)
# Strings the initial buffer so that we can detect decoded strings later on
if self.profiler and self.do_strings:
self.profiler.strings['ansi'] = self.get_ansi_strings(data)
self.profiler.strings['unicode'] = self.get_unicode_strings(data)
# Set the emulated path
emu_path = ''
self.cd = self.get_cd()
if self.cd:
if not self.cd.endswith('\\'):
self.cd += '\\'
emu_path = self.cd + os.path.basename(file_name)
pe.set_emu_path(emu_path)
self.map_pe(pe, mod_name=mod_name, emu_path=emu_path)
self.mem_write(pe.base, pe.mapped_image)
self.setup()
if not self.stack_base:
self.stack_base, stack_addr = self.alloc_stack(0x12000)
self.set_func_args(self.stack_base, self.return_hook)
# Init imported data
for addr, imp in pe.imports.items():
mn, fn = imp
mod, eh = self.api.get_data_export_handler(mn, fn)
if eh:
data_ptr = self.handle_import_data(mn, fn)
sym = "%s.%s" % (mn, fn)
self.global_data.update({addr: [sym, data_ptr]})
self.mem_write(addr, data_ptr.to_bytes(self.get_ptr_size(),
'little'))
return pe
def run_module(self, module, all_entrypoints=False):
"""
Begin emulating a previously loaded module
Arguments:
module: Module to emulate
"""
if not module:
self.stop()
raise Win32EmuError('Module not found')
ep = module.base + module.ep
run = Run()
run.start_addr = ep
run.instr_cnt = 0
main_exe = None
if not module.is_exe():
run.args = [module.base, DLL_PROCESS_ATTACH, 0]
run.type = 'dll_entry.DLL_PROCESS_ATTACH'
container = self.init_container_process()
if container:
self.processes.append(container)
self.curr_process = container
else:
run.type = 'module_entry'
main_exe = module
run.args = [self.mem_map(8, tag='emu.module_arg_%d' % (i)) for i in range(4)]
if main_exe:
self.user_modules = [main_exe] + self.user_modules
self.add_run(run)
if all_entrypoints:
# Only emulate a subset of all the exported functions
# There are some modules (such as the windows kernel) with
# thousands of exports
exports = [k for k in module.get_exports()[: MAX_EXPORTS_TO_EMULATE]]
if exports:
args = [self.mem_map(8, tag='emu.export_arg_%d' % (i), base=0x41420000) for i in range(4)] # noqa
for exp in exports:
if exp.name in ('DllMain', 'ServiceMain'):
continue
run = Run()
if exp.name:
fn = exp.name
else:
fn = 'no_name'
run.type = 'export.%s' % (fn)
run.start_addr = exp.address
# Here we set dummy args to pass into the export function
run.args = args
# Store these runs and only queue them before the unload
# routine this is because some exports may not be ready to
# be called yet
self.add_run(run)
# Create an empty process object for the module if none is
# supplied
if len(self.processes) == 0:
p = objman.Process(self, path=module.get_emu_path(), base=module.base,
pe=module)
self.curr_process = p
t = objman.Thread(self,
stack_base=self.stack_base,
stack_commit=module.stack_commit)
self.om.objects.update({t.address: t})
self.curr_process.threads.append(t)
self.curr_thread = t
peb = self.alloc_peb(self.curr_process)
# Set the TEB
self.init_teb(t, peb)
# Begin emulation
self.start()
def emulate_module(self, path):
"""
Load and emulate binary from the given path
"""
mod = self.load_module(path)
self.run_module(mod)
def load_shellcode(self, path, arch, data=None):
"""
Load position independent code (i.e. shellcode) to prepare for emulation
"""
self.arch = arch
sc_hash = None
if data:
sc_hash = hashlib.sha256()
sc_hash.update(data)
sc_hash = sc_hash.hexdigest()
sc = data
else:
with open(path, 'rb') as scpath:
sc = scpath.read()
sc_hash = hashlib.sha256()
sc_hash.update(sc)
sc_hash = sc_hash.hexdigest()
if self.arch == _arch.ARCH_X86:
disasm_mode = cs.CS_MODE_32
elif self.arch == _arch.ARCH_AMD64:
disasm_mode = cs.CS_MODE_64
else:
raise Win32EmuError('Unsupported architecture: %s' % self.arch)
self.emu_eng.init_engine(_arch.ARCH_X86, self.arch)
if not self.disasm_eng:
self.disasm_eng = cs.Cs(cs.CS_ARCH_X86, disasm_mode)
sc_tag = 'emu.shellcode.%s' % (sc_hash)
# Map the shellcode into memory
sc_addr = self.mem_map(len(sc), tag=sc_tag)
self.mem_write(sc_addr, sc)
self.pic_buffers.append((path, sc_addr, len(sc)))
sc_arch = 'unknown'
if arch == _arch.ARCH_AMD64:
sc_arch = 'x64'
elif arch == _arch.ARCH_X86:
sc_arch = 'x86'
if self.profiler:
self.input = {'path': path, 'sha256': sc_hash, 'size': len(sc),
'arch': sc_arch, 'mem_tag': sc_tag,
'emu_version': self.get_emu_version(),
'os_run': self.get_osver_string()}
self.profiler.add_input_metadata(self.input)
# Strings the initial buffer so that we can detect decoded strings later on
if self.do_strings:
self.profiler.strings['ansi'] = self.get_ansi_strings(sc)
self.profiler.strings['unicode'] = self.get_unicode_strings(sc)
return sc_addr
def run_shellcode(self, sc_addr, offset=0):
"""
Begin emulating position independent code (i.e. shellcode) to prepare for emulation
"""
target = None
for sc_path, _sc_addr, size in self.pic_buffers:
if _sc_addr == sc_addr:
target = _sc_addr
break
if not target:
raise Win32EmuError('Invalid shellcode address')
self.setup()
stack_commit = 0x4000
self.stack_base, stack_addr = self.alloc_stack(stack_commit)
self.set_func_args(self.stack_base, self.return_hook, 0x7000)
run = Run()
run.type = 'shellcode'
run.start_addr = sc_addr + offset
run.instr_cnt = 0
args = [self.mem_map(1024, tag='emu.shellcode_arg_%d' % (i), base=0x41420000 + i)
for i in range(4)]
run.args = (args)
self.reg_write(_arch.X86_REG_ECX, 1024)
self.add_run(run)
# Create an empty process object for the shellcode if none is
# supplied
container = self.init_container_process()
if container:
self.processes.append(container)
self.curr_process = container
else:
p = objman.Process(self)
self.processes.append(p)
self.curr_process = p
mm = self.get_address_map(sc_addr)
if mm:
mm.set_process(self.curr_process)
t = objman.Thread(self,
stack_base=self.stack_base, stack_commit=stack_commit)
self.om.objects.update({t.address: t})
self.curr_process.threads.append(t)
self.curr_thread = t
peb = self.alloc_peb(self.curr_process)
# Set the TEB
self.init_teb(t, peb)
self.start()
def alloc_peb(self, proc):
"""
Allocate memory for the Process Environment Block (PEB)
"""
if proc.is_peb_active:
return
size = proc.get_peb_ldr().sizeof()
res, size = self.get_valid_ranges(size)
self.mem_reserve(size, base=res, tag='emu.struct.PEB_LDR_DATA')
proc.set_peb_ldr_address(res)
peb = proc.get_peb()
proc.is_peb_active = True
return peb
def setup(self, stack_commit=0):
# Set the emulator to run in protected mode
self.om = objman.ObjectManager(emu=self)
arch = self.get_arch()
self._setup_gdt(arch)
self.setup_user_shared_data()
self.set_ptr_size(self.arch)
if arch == _arch.ARCH_X86:
self.peb_addr = self.fs_addr + 0x30
elif arch == _arch.ARCH_AMD64:
self.peb_addr = self.gs_addr + 0x60
self.api = WindowsApi(self)
# Init symlinks
for sl in self.symlinks:
self.om.add_symlink(sl['name'], sl['target'])
self.init_sys_modules(self.config_system_modules)
def init_sys_modules(self, modules_config):
"""
Get the system modules (e.g. drivers) that are loaded in the emulator
"""
sys_mods = []
for modconf in modules_config:
mod = w32common.DecoyModule()
mod.name = modconf['name']
base = modconf.get('base_addr')
if isinstance(base, str):
base = int(base, 16)
mod.decoy_base = base
mod.decoy_path = modconf['path']
drv = modconf.get('driver')
if drv:
devs = drv.get('devices')
for dev in devs:
name = dev.get('name', '')
do = self.new_object(objman.Device)
do.name = name
sys_mods.append(mod)
return sys_mods
def init_container_process(self):
"""
Create a process to be used to host shellcode or DLLs
"""
for p in self.config_processes:
if p.get('is_main_exe'):
name = p.get('name', '')
emu_path = p.get('path', '')
base = p.get('base_addr', 0)
if isinstance(base, str):
base = int(base, 0)
cmd_line = p.get('command_line', '')
proc = objman.Process(self, name=name,
path=emu_path, base=base, cmdline=cmd_line)
return proc
return None
def get_user_modules(self):
"""
Get the user modules (e.g. dlls) that are loaded in the emulator
"""
# Generate the decoy user module list
if len(self.user_modules) < 2:
# Check if we have a host process configured
proc_mod = None
for p in self.config_processes:
if not self.user_modules and p.get('is_main_exe'):
proc_mod = p
break
if proc_mod:
all_user_mods = [proc_mod] + self.config_user_modules
user_modules = self.init_user_modules(all_user_mods)
else:
user_modules = self.init_user_modules(self.config_user_modules)
self.user_modules += user_modules
return self.user_modules
def exit_process(self):
"""
An emulated binary is attempted to terminate its current process.
Signal that the run has finished.
"""
self.enable_code_hook()
self.run_complete = True
def _hook_mem_unmapped(self, emu, access, address, size, value, user_data):
_access = self.emu_eng.mem_access.get(access)
if _access == common.INVALID_MEM_READ:
p = self.get_current_process()
pld = p.get_peb_ldr()
if address > pld.address and address < (pld.address + pld.sizeof()):
self.mem_map_reserve(pld.address)
user_mods = self.get_user_modules()
self.init_peb(user_mods)
return True
return super(Win32Emulator, self)._hook_mem_unmapped(emu, access, address, size,
value, user_data)
def set_hooks(self):
"""Set the emulator callbacks"""
super(Win32Emulator, self).set_hooks()
if not self.builtin_hooks_set:
self.add_mem_invalid_hook(cb=self._hook_mem_unmapped)
self.add_interrupt_hook(cb=self._hook_interrupt)
self.builtin_hooks_set = True
self.set_mem_tracing_hooks()
def stop(self):
self.run_complete = True
super(Win32Emulator, self).stop()
def on_emu_complete(self):
"""
Called when all runs have completed emulation
"""
if not self.emu_complete:
self.emu_complete = True
if self.do_strings and self.profiler:
dec_ansi, dec_unicode = self.get_mem_strings()
dec_ansi = [a for a in dec_ansi if a not in self.profiler.strings['ansi']]
dec_unicode = [u for u in dec_unicode if u not in self.profiler.strings['unicode']]
self.profiler.decoded_strings['ansi'] = dec_ansi
self.profiler.decoded_strings['unicode'] = dec_unicode
self.stop()
def on_run_complete(self):
"""
Clean up after a run completes. This function will pop the
next run from the run queue and emulate it.
"""
self.run_complete = True
self.curr_run.ret_val = self.get_return_val()
if self.profiler:
self.profiler.log_dropped_files(self.curr_run, self.get_dropped_files())
return self._exec_next_run()
def heap_alloc(self, size, heap='None'):
"""
Allocate a memory chunk and add it to the "heap"
"""
addr = self.mem_map(size, base=None, tag='api.heap.%s' % (heap))
self.heap_allocs.append((addr, size, heap))
return addr
File diff suppressed because it is too large Load Diff
View File
View File
+433
View File
@@ -0,0 +1,433 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
import speakeasy.winenv.arch as _arch
from speakeasy.profiler import Run
from speakeasy.struct import EmuStruct
from speakeasy.errors import ApiEmuError
import speakeasy.winenv.defs.nt.ntoskrnl as ntos
class ApiHandler(object):
"""
Base class for handling exported functions
"""
name = ''
@staticmethod
def apihook(impname, argc=0, conv=_arch.CALL_CONV_STDCALL, ordinal=None):
def apitemp(f):
if not callable(f):
raise ApiEmuError('Invalid function type supplied: %s' % (str(f)))
f.__apihook__ = (impname, f, argc, conv, ordinal)
return f
return apitemp
@staticmethod
def impdata(impname):
def datatmp(f):
if not callable(f):
raise ApiEmuError('Invalid function type supplied: %s' % (str(f)))
f.__datahook__ = (impname, f)
return f
return datatmp
@staticmethod
def get_api_name(func):
return func.__apihook__[0]
def __init__(self, emu):
super(ApiHandler, self).__init__()
self.funcs = {}
self.data = {}
self.mod_name = ''
self.emu = emu
arch = self.emu.get_arch()
if arch == _arch.ARCH_X86:
self.ptr_size = 4
elif arch == _arch.ARCH_AMD64:
self.ptr_size = 8
else:
raise ApiEmuError('Invalid architecture')
for name in dir(self):
val = getattr(self, name, None)
if val is None:
continue
func_attrs = getattr(val, '__apihook__', None)
data_attrs = getattr(val, '__datahook__', None)
if func_attrs:
name, func, argc, conv, ordinal = func_attrs
self.funcs[name] = (name, func, argc, conv, ordinal)
if ordinal:
self.funcs[ordinal] = (name, func, argc, conv, ordinal)
elif data_attrs:
name, func = data_attrs
self.data[name] = func
def __get_hook_attrs__(self, obj):
for name in dir(obj):
val = getattr(obj, name, None)
if val is None:
continue
func_attrs = getattr(val, '__apihook__', None)
data_attrs = getattr(val, '__datahook__', None)
if func_attrs:
name, func, argc, conv, ordinal = func_attrs
obj.funcs[name] = (name, func, argc, conv, ordinal)
if ordinal:
obj.funcs[ordinal] = (name, func, argc, conv, ordinal)
elif data_attrs:
name, func = data_attrs
obj.data[name] = func
def get_data_handler(self, exp_name):
return self.data.get(exp_name)
def get_func_handler(self, exp_name):
if exp_name.startswith('ordinal_'):
ord_num = exp_name.split('_')
if len(ord_num) == 2 and ord_num[1].isdigit():
ord_num = int(ord_num[1])
handler = self.funcs.get(ord_num)
if handler:
return handler
return self.funcs.get(exp_name)
def get_ptr_size(self):
return self.ptr_size
def sizeof(self, obj):
if isinstance(obj, EmuStruct):
return obj.sizeof()
else:
raise ApiEmuError('Invalid object')
def get_bytes(self, obj):
if isinstance(obj, EmuStruct):
return obj.get_bytes()
else:
raise ApiEmuError('Invalid object')
def cast(self, obj, bytez):
if isinstance(obj, EmuStruct):
return obj.cast(bytez)
else:
raise ApiEmuError('Invalid object')
return obj
def write_back(self, addr, obj):
bytez = self.get_bytes(obj)
self.emu.mem_write(addr, bytez)
def pool_alloc(self, pool_type, size, tag):
return self.emu.pool_alloc(pool_type, size, tag)
def heap_alloc(self, size, heap):
return self.emu.heap_alloc(size, heap)
def mem_alloc(self, size, base=None, tag=None, flags=0, perms=0, shared=False, process=None):
return self.emu.mem_map(size, base=base, tag=tag, flags=flags, perms=perms,
shared=shared, process=process)
def mem_free(self, addr):
return self.emu.mem_free(addr)
def mem_reserve(self, size, base=None, tag=None):
return self.emu.mem_reserve(size, base=base, tag=tag)
def mem_cast(self, obj, addr):
struct_bytes = self.emu.mem_read(addr, self.sizeof(obj))
return self.cast(obj, struct_bytes)
def mem_copy(self, dst, src, n):
return self.emu.mem_copy(dst, src, n)
def read_mem_string(self, addr, width, max_chars=0):
string = self.emu.read_mem_string(addr, width=width)
return string
def mem_string_len(self, addr, width):
return self.emu.mem_string_len(addr, width)
def read_ansi_string(self, addr):
ans = ntos.STRING(self.emu.get_ptr_size())
ans = self.mem_cast(ans, addr)
string = self.emu.read_mem_string(ans.Buffer, width=1)
return string
def read_unicode_string(self, addr):
us = ntos.UNICODE_STRING(self.emu.get_ptr_size())
us = self.mem_cast(us, addr)
string = self.emu.read_mem_string(us.Buffer, width=2)
return string
def read_wide_string(self, addr, max_chars=0):
string = self.emu.read_mem_string(addr, width=2, max_chars=max_chars)
return string
def read_string(self, addr, max_chars=0):
string = self.emu.read_mem_string(addr, width=1, max_chars=max_chars)
return string
def write_mem_string(self, string, addr, width):
return self.emu.write_mem_string(string, addr, width)
def write_wide_string(self, string, addr):
return self.write_mem_string(string, addr, width=2)
def write_string(self, string, addr):
return self.write_mem_string(string, addr, width=1)
def queue_run(self, run_type, ep, run_args=[]):
run = Run()
if not isinstance(run_type, str):
raise ApiEmuError('Invalid run type')
if not isinstance(ep, int):
raise ApiEmuError('Invalid run entry point')
if not any((isinstance(run_args, list), isinstance(run_args, tuple))):
raise ApiEmuError('Invalid run args')
run.type = run_type
run.start_addr = ep
run.args = run_args
self.emu.add_run(run)
def log_file_access(self, path, event_type, data=None,
handle=0, disposition=[], access=[], buffer=0,
size=None):
profiler = self.emu.get_profiler()
if profiler:
run = self.emu.get_current_run()
profiler.log_file_access(run, path, event_type, data, handle,
disposition, access, buffer, size)
def log_process_event(self, proc, event_type, **kwargs):
profiler = self.emu.get_profiler()
if profiler:
run = self.emu.get_current_run()
profiler.log_process_event(run, proc, event_type, kwargs)
def log_registry_access(self, path, event_type, value_name=None, data=None,
handle=0, disposition=[], access=[], buffer=0,
size=None):
profiler = self.emu.get_profiler()
if profiler:
run = self.emu.get_current_run()
profiler.log_registry_access(run, path, event_type, value_name, data, handle,
disposition, access, buffer, size)
def log_dns(self, domain, ip=''):
profiler = self.emu.get_profiler()
if profiler:
run = self.emu.get_current_run()
profiler.log_dns(run, domain, ip)
def log_network(self, server, port, typ='unknown', proto='unknown', data=b'', method=''):
profiler = self.emu.get_profiler()
if profiler:
run = self.emu.get_current_run()
profiler.log_network(run, server, port, typ=typ, proto=proto,
data=data, method=method)
def log_http(self, server, port, headers='', body=b'', secure=False):
profiler = self.emu.get_profiler()
if profiler:
run = self.emu.get_current_run()
profiler.log_http(run, server, port, headers=headers,
body=body, secure=secure)
def get_max_int(self):
# Byte order is irrelevant here
return int.from_bytes(b'\xFF' * self.get_ptr_size(), 'little')
def mem_read(self, addr, size):
return self.emu.mem_read(addr, size)
def file_open(self, path, create=False):
return self.emu.file_open(path, create)
def file_create_mapping(self, hfile, name, size, prot):
return self.emu.file_create_mapping(hfile, name, size, prot)
def file_get(self, handle):
return self.emu.file_get(handle)
def does_file_exist(self, path):
return self.emu.does_file_exist(path)
def reg_open_key(self, path, create=False):
return self.emu.reg_open_key(path, create)
def reg_get_key(self, handle):
return self.emu.reg_get_key(handle)
def reg_get_subkeys(self, hkey):
return self.emu.reg_get_subkeys(hkey)
def mem_write(self, addr, data):
# If the data being written to a shared memory mapping, update all mappings
# This will likely have to be made more robust to handle more complicated
# scenarios with varying file offsets
mm = self.emu.get_address_map(addr)
if mm and mm.shared:
fm = self.emu.get_file_manager()
fmap = fm.get_mapping_from_addr(mm.get_base())
if fmap:
for base, view in fmap.views.items():
if base == mm.get_base():
continue
tgt_offset = addr - mm.get_base()
self.emu.mem_write(base + tgt_offset, data)
return self.emu.mem_write(addr, data)
def create_thread(self, addr, ctx, hproc, thread_type='thread'):
return self.emu.create_thread(addr, ctx, hproc, thread_type=thread_type)
def get_object_from_id(self, id):
return self.emu.get_object_from_id(id)
def get_object_from_addr(self, addr):
return self.emu.get_object_from_addr(addr)
def get_object_handle(self, obj):
return self.emu.get_object_handle(obj)
def get_object_from_handle(self, hnd):
return self.emu.get_object_from_handle(hnd)
def get_object_from_name(self, name):
return self.emu.get_object_from_name(name)
def get_os_version(self):
return self.emu.osversion
def exit_process(self):
self.emu.exit_process()
def get_char_width(self, ctx):
"""
Based on the API name, determine the character width
being used by the function
"""
name = ctx.get('func_name', '')
if name.endswith('A'):
return 1
elif name.endswith('W'):
return 2
raise ApiEmuError('Failed to get character width from function: %s' % (name))
def get_va_arg_count(self, fmt):
"""
Get the number of arguments in the variable argument list
"""
# Ignore escapes
i = fmt.count('%%')
c = fmt.count('%')
if self.get_ptr_size() != 8:
c += fmt.count('%ll')
return c - i
def va_args(self, va_list, num_args):
"""
Get the variable argument list
"""
args = []
ptr = va_list
ptrsize = self.get_ptr_size()
for n in range(num_args):
arg = int.from_bytes(self.emu.mem_read(ptr, ptrsize), 'little')
args.append(arg)
ptr += ptrsize
return args
def do_str_format(self, string, argv):
"""
Format a string similar to msvcrt.printf
"""
# Skip over the format string
args = list(argv)
new = list(string)
curr_fmt = ''
new_fmts = []
# Very brittle format string parser, should improve later
inside_fmt = False
for i, c in enumerate(string):
if c == '%':
if inside_fmt:
inside_fmt = False
else:
inside_fmt = True
if inside_fmt:
if c == 'S':
s = self.read_wide_string(args.pop(0))
new_fmts.append(s)
new[i] = 's'
inside_fmt = False
elif c == 's':
if curr_fmt.startswith('w'):
s = self.read_wide_string(args.pop(0))
new[i - 1] = '\xFF'
curr_fmt = ''
new_fmts.append(s)
else:
s = self.read_string(args.pop(0))
new_fmts.append(s)
elif c in ('x', 'X', 'd', 'u', 'i'):
if curr_fmt.startswith('ll'):
if self.get_ptr_size() == 8:
new_fmts.append(args.pop(0))
else:
low = args.pop(0)
high = args.pop(0)
new_fmts.append(high << 32 | low)
new = new[: i - 2] + new[i:]
curr_fmt = ''
else:
new_fmts.append(0xFFFFFFFF & args.pop(0))
elif c == 'c':
new_fmts.append(0xFF & args.pop(0))
elif c == 'P':
new[i] = 'X'
new_fmts.append(args.pop(0))
elif c == 'p':
new[i] = 'x'
new_fmts.append(args.pop(0))
elif c == 'l':
curr_fmt += c
elif c == 'w':
curr_fmt += c
if inside_fmt and c in 'diuoxXfFeEgGaAcspn':
inside_fmt = False
if not args:
break
new = ''.join(new)
new = new.replace('\xFF', '')
new = new % tuple(new_fmts)
return new
+368
View File
@@ -0,0 +1,368 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
import uuid
import speakeasy.winenv.defs.nt.ddk as ddk
import speakeasy.winenv.defs.wfp.fwpmtypes as fwp
from .. import api
FWP_E_NOT_FOUND = 0x80320008
FWP_E_CALLOUT_NOT_FOUND = 0x80320001
FWP_E_FILTER_NOT_FOUND = 0x80320003
FWP_E_LAYER_NOT_FOUND = 0x80320004
FWP_E_SUBLAYER_NOT_FOUND = 0x80320007
class NetIoEmuError(Exception):
pass
class Fwpkclnt(api.ApiHandler):
"""
Fwpkclnt is the Windows driver that implements the Windows Filtering Platform.
For example, packet filters can be writting using this framework.
"""
name = 'fwpkclnt'
apihook = api.ApiHandler.apihook
impdata = api.ApiHandler.impdata
def __init__(self, emu):
super(Fwpkclnt, self).__init__(emu)
self.funcs = {}
self.data = {}
self.handle = 4
self.sessions = {}
self.injections = {}
self.sublayers = {}
self.callouts = {}
self.filters = {}
self.emu = emu
self.fwp = fwp
super(Fwpkclnt, self).__get_hook_attrs__(self)
def new_id(self):
tmp = self.handle
self.handle += 4
return tmp
def new_session(self):
# stub
ret = self.new_id()
self.sessions.update({ret: 'sess'})
return ret
def new_injection(self):
ret = self.new_id()
# TODO
self.injections.update({ret: 'inj'})
return ret
def new_filter(self, name, desc, key):
ret = self.new_id()
flt = {ret: {'name': name, 'desc': desc, 'key': key}}
self.filters.update(flt)
return ret
@apihook('FwpmEngineOpen0', argc=5)
def FwpmEngineOpen0(self, emu, argv, ctx={}):
"""
DWORD FwpmEngineOpen0(
const wchar_t *serverName,
UINT32 authnService,
SEC_WINNT_AUTH_IDENTITY_W *authIdentity,
const FWPM_SESSION0 *session,
HANDLE *engineHandle
);
"""
sname, asvc, authid, sess, eng = argv
rv = ddk.STATUS_SUCCESS
hnd = self.new_session()
self.mem_write(eng, hnd.to_bytes(self.get_ptr_size(), 'little'))
return rv
@apihook('FwpsInjectionHandleCreate0', argc=3)
def FwpsInjectionHandleCreate0(self, emu, argv, ctx={}):
"""
NTSTATUS FwpsInjectionHandleCreate0(
ADDRESS_FAMILY addressFamily,
UINT32 flags,
HANDLE *injectionHandle
);
"""
family, flags, inj_handle = argv
rv = ddk.STATUS_SUCCESS
hnd = self.new_injection()
self.mem_write(inj_handle,
hnd.to_bytes(self.get_ptr_size(), 'little'))
return rv
@apihook('FwpmSubLayerAdd0', argc=3)
def FwpmSubLayerAdd0(self, emu, argv, ctx={}):
"""
DWORD FwpmSubLayerAdd0(
HANDLE engineHandle,
const FWPM_SUBLAYER0 *subLayer,
PSECURITY_DESCRIPTOR sd
);
"""
engineHandle, subLayer, sd = argv
name = ''
desc = ''
weight = 0
rv = ddk.STATUS_SUCCESS
_sl = self.fwp.FWPM_SUBLAYER0(emu.get_ptr_size())
layer = self.mem_cast(_sl, subLayer)
if layer.displayData.name:
name = self.read_wide_string(layer.displayData.name)
if layer.displayData.description:
desc = self.read_wide_string(layer.displayData.description)
subkey = self.get_bytes(layer.subLayerKey)
if all(x == 0 for x in subkey):
subkey = uuid.uuid4().bytes
subkey = uuid.UUID(bytes_le=subkey)
subkey = str(subkey)
provkey = self.get_bytes(layer.providerKey)
if all(x == 0 for x in provkey):
provkey = uuid.uuid4().bytes
provkey = uuid.UUID(bytes_le=provkey)
provkey = str(provkey)
layer = {subkey: {'name': name, 'desc': desc, 'weight': weight,
'subkey': subkey, 'provkey': provkey}}
self.sublayers.update(layer)
return rv
@apihook('FwpsCalloutRegister1', argc=3)
def FwpsCalloutRegister1(self, emu, argv, ctx={}):
"""
NTSTATUS FwpsCalloutRegister1(
void *deviceObject,
const FWPS_CALLOUT1 *callout,
UINT32 *calloutId
);
"""
deviceObject, pCallout, calloutId = argv
rv = ddk.STATUS_SUCCESS
cid = self.new_id()
_co = self.fwp.FWPS_CALLOUT1(emu.get_ptr_size())
callout = self.mem_cast(_co, pCallout)
classify_fn = callout.classifyFn
notify_fn = callout.notifyFn
delete_fn = callout.flowDeleteFn
co_key = self.get_bytes(callout.calloutKey)
if all(x == 0 for x in co_key):
co_key = uuid.uuid4().bytes
co_key = uuid.UUID(bytes_le=co_key)
co_key = str(co_key)
co = {cid: {'key': co_key, 'flags': callout.flags,
'classify_fn': classify_fn, 'notify_fn': notify_fn,
'delete_fn': delete_fn}}
self.callouts.update(co)
if calloutId:
self.mem_write(calloutId, cid.to_bytes(4, 'little'))
return rv
@apihook('FwpmCalloutAdd0', argc=4)
def FwpmCalloutAdd0(self, emu, argv, ctx={}):
"""
DWORD FwpmCalloutAdd0(
HANDLE engineHandle,
const FWPM_CALLOUT0 *callout,
PSECURITY_DESCRIPTOR sd,
UINT32 *id
);
"""
eng, pCallout, sd, pCid = argv
name = ''
desc = ''
rv = ddk.STATUS_SUCCESS
_co = self.fwp.FWPM_CALLOUT0(emu.get_ptr_size())
callout = self.mem_cast(_co, pCallout)
if callout.displayData.name:
name = self.read_wide_string(callout.displayData.name)
if callout.displayData.description:
desc = self.read_wide_string(callout.displayData.description)
co_key = self.get_bytes(callout.calloutKey)
# was the guid set to 0?
if all(x == 0 for x in co_key):
co_key = uuid.uuid4().bytes
co_key = uuid.UUID(bytes_le=co_key)
co_key = str(co_key)
cid = 0
for k, v in self.callouts.items():
if v['key'] == co_key:
cid = k
self.callouts[k].update({'name': name, 'desc': desc})
if pCid:
cid = self.mem_write(pCid, cid.to_bytes(4, 'little'))
return rv
@apihook('FwpmFilterAdd0', argc=4)
def FwpmFilterAdd0(self, emu, argv, ctx={}):
"""
DWORD FwpmFilterAdd0(
HANDLE engineHandle,
const FWPM_FILTER0 *filter,
PSECURITY_DESCRIPTOR sd,
UINT64 *id
);
"""
eng, pFilter, sd, pId = argv
self.mem_write(pId, b'\x41\x41')
rv = ddk.STATUS_SUCCESS
name = ''
desc = ''
_filt = self.fwp.FWPM_FILTER0(emu.get_ptr_size())
filt = self.mem_cast(_filt, pFilter)
if filt.displayData.name:
name = self.read_wide_string(filt.displayData.name)
if filt.displayData.description:
desc = self.read_wide_string(filt.displayData.description)
flt_key = self.get_bytes(filt.filterKey)
# was the guid set to 0?
if all(x == 0 for x in flt_key):
flt_key = uuid.uuid4().bytes
flt_key = uuid.UUID(bytes_le=flt_key)
flt_key = str(flt_key)
fid = self.new_filter(name, desc, flt_key)
self.mem_write(pId, fid.to_bytes(8, 'little'))
return rv
@apihook('FwpmFilterDeleteById0', argc=2)
def FwpmFilterDeleteById0(self, emu, argv, ctx={}):
"""
DWORD FwpmFilterDeleteById0(
HANDLE engineHandle,
UINT64 id
);
"""
eng, fid = argv
rv = ddk.STATUS_SUCCESS
return rv
@apihook('FwpmCalloutDeleteById0', argc=2)
def FwpmCalloutDeleteById0(self, emu, argv, ctx={}):
"""
DWORD FwpmCalloutDeleteById0(
HANDLE engineHandle,
UINT32 id
);
"""
eng, cid = argv
rv = FWP_E_CALLOUT_NOT_FOUND
co = self.callouts.get(cid)
if co:
rv = ddk.STATUS_SUCCESS
return rv
@apihook('FwpsCalloutUnregisterById0', argc=1)
def FwpsCalloutUnregisterById0(self, emu, argv, ctx={}):
"""
NTSTATUS FwpsCalloutUnregisterById0(
const UINT32 calloutId
);
"""
cid, = argv
rv = FWP_E_CALLOUT_NOT_FOUND
co = self.callouts.get(cid)
if co:
rv = ddk.STATUS_SUCCESS
return rv
@apihook('FwpmSubLayerDeleteByKey0', argc=2)
def FwpmSubLayerDeleteByKey0(self, emu, argv, ctx={}):
"""
DWORD FwpmSubLayerDeleteByKey0(
HANDLE engineHandle,
const GUID *key
);
"""
eng, key = argv
rv = FWP_E_SUBLAYER_NOT_FOUND
guid = self.mem_read(key, 16)
guid = uuid.UUID(bytes_le=guid)
if self.sublayers.get(guid):
rv = ddk.STATUS_SUCCESS
return rv
@apihook('FwpmEngineClose0', argc=1)
def FwpmEngineClose0(self, emu, argv, ctx={}):
"""
DWORD FwpmEngineClose0(
HANDLE engineHandle
);
"""
eng, = argv
rv = ddk.STATUS_SUCCESS
return rv
@apihook('FwpsInjectionHandleDestroy0', argc=1)
def FwpsInjectionHandleDestroy0(self, emu, argv, ctx={}):
"""
NTSTATUS FwpsInjectionHandleDestroy0(
HANDLE injectionHandle
);
"""
handle, = argv
rv = ddk.STATUS_SUCCESS
if not self.injections.get(handle):
rv = FWP_E_NOT_FOUND
return rv
+54
View File
@@ -0,0 +1,54 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
import speakeasy.winenv.arch as _arch
import speakeasy.winenv.defs.nt.ntoskrnl as w
from speakeasy.winenv.api import api
class Hal(api.ApiHandler):
"""
Implements the hardware abstraction layer (hal.dll) that allows
Windows to interact with hardware at a high level.
"""
name = 'hal'
apihook = api.ApiHandler.apihook
impdata = api.ApiHandler.impdata
def __init__(self, emu):
super(Hal, self).__init__(emu)
self.funcs = {}
self.data = {}
self.emu = emu
self.win = w
super(Hal, self).__get_hook_attrs__(self)
@apihook('KeGetCurrentIrql', argc=0)
def KeGetCurrentIrql(self, emu, argv, ctx={}):
"""
NTHALAPI KIRQL KeGetCurrentIrql();
"""
irql = emu.get_current_irql()
return irql
@apihook('ExAcquireFastMutex', argc=1, conv=_arch.CALL_CONV_FASTCALL)
def ExAcquireFastMutex(self, emu, argv, ctx={}):
"""
VOID ExAcquireFastMutex(
_Inout_ PFAST_MUTEX FastMutex
);
"""
return
@apihook('ExReleaseFastMutex', argc=1, conv=_arch.CALL_CONV_FASTCALL)
def ExReleaseFastMutex(self, emu, argv, ctx={}):
"""
VOID ExReleaseFastMutex(
_Inout_ PFAST_MUTEX FastMutex
);
"""
return
+326
View File
@@ -0,0 +1,326 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
import speakeasy.winenv.defs.nt.ddk as ddk
import speakeasy.winenv.defs.nt.ntoskrnl as nt
import speakeasy.winenv.defs.ndis.ndis as ndis
from .. import api
NDIS_STATUS_FAILURE = 0xC0000001
NDIS_STATUS_SUCCESS = 0x00000000
NDIS_STATUS_RESOURCES = 0xC000009A
class Ndis(api.ApiHandler):
"""
Implements the Network Driver Interface Specification (NDIS) used for
interacting with network cards.
"""
name = 'ndis'
apihook = api.ApiHandler.apihook
impdata = api.ApiHandler.impdata
def __init__(self, emu):
super(Ndis, self).__init__(emu)
self.funcs = {}
self.data = {}
self.handle = 4
self.wrappers = {}
self.drivers = {}
self.protocols = {}
self.emu = emu
self.win = nt
self.ndis = ndis
super(Ndis, self).__get_hook_attrs__(self)
def convert_pool_tag(self, tag):
ret = '0x00'
if tag:
try:
ret = tag.to_bytes(4, 'little').decode('utf-8')
except Exception:
ret = '0x%x' % tag
return ret
def new_id(self):
tmp = self.handle
self.handle += 4
return tmp
@apihook('NdisGetVersion', argc=0)
def NdisGetVersion(self, emu, argv, ctx={}):
"""
UINT NdisGetVersion();
"""
ndis_major = 5
ndis_minor = 0
osver = self.get_os_version()
major, minor = osver['major'], osver['minor']
if major >= 6:
ndis_major = 6
if major >= 10:
ndis_minor = 0x51
elif minor >= 1:
ndis_minor = 1
out_ver = (ndis_major << 16) | ndis_minor
return out_ver
@apihook('NdisGetRoutineAddress', argc=1)
def NdisGetRoutineAddress(self, emu, argv, ctx={}):
"""
PVOID NdisGetRoutineAddress(
PNDIS_STRING NdisRoutineName
);
"""
NdisRoutineName, = argv
fn = self.read_unicode_string(NdisRoutineName)
addr = emu.get_proc('ndis', fn)
argv[0] = fn
return addr
@apihook('NdisMRegisterMiniportDriver', argc=5)
def NdisMRegisterMiniportDriver(self, emu, argv, ctx={}):
"""
NDIS_STATUS NdisMRegisterMiniportDriver(
PDRIVER_OBJECT DriverObject,
PUNICODE_STRING RegistryPath,
NDIS_HANDLE MiniportDriverContext,
PNDIS_MINIPORT_DRIVER_CHARACTERISTICS MiniportDriverCharacteristics,
PNDIS_HANDLE NdisMiniportDriverHandle
);
"""
drv, reg, drv_ctx, chars, phnd = argv
rv = NDIS_STATUS_SUCCESS
if phnd:
hnd = self.new_id()
hnd = hnd.to_bytes(4, 'little')
self.mem_write(phnd, hnd)
return rv
@apihook('NdisInitializeWrapper', argc=4)
def NdisInitializeWrapper(self, emu, argv, ctx={}):
"""
VOID NdisInitializeWrapper(
PNDIS_HANDLE NdisWrapperHandle,
PVOID SystemSpecific1,
PVOID SystemSpecific2,
PVOID SystemSpecific3)
"""
pHandle, ss1, ss2, ss3 = argv
hnd = self.new_id()
self.wrappers.update({hnd: True})
self.mem_write(pHandle, hnd.to_bytes(self.get_ptr_size(), 'little'))
@apihook('NdisTerminateWrapper', argc=2)
def NdisTerminateWrapper(self, emu, argv, ctx={}):
"""
VOID NdisTerminateWrapper(
_In_ NDIS_HANDLE NdisWrapperHandle,
_In_ PVOID SystemSpecific
);
"""
hnd, ss = argv
@apihook('NdisInitializeReadWriteLock', argc=1)
def NdisInitializeReadWriteLock(self, emu, argv, ctx={}):
"""
void NdisInitializeReadWriteLock(
PNDIS_RW_LOCK Lock
);
"""
lock, = argv
@apihook('NdisMRegisterUnloadHandler', argc=2)
def NdisMRegisterUnloadHandler(self, emu, argv, ctx={}):
"""
VOID NdisMRegisterUnloadHandler(
_In_ NDIS_HANDLE NdisWrapperHandle,
_In_ PDRIVER_UNLOAD UnloadHandler
);
"""
hnd, unload = argv
@apihook('NdisRegisterProtocol', argc=4)
def NdisRegisterProtocol(self, emu, argv, ctx={}):
"""
VOID NdisRegisterProtocol(
_Out_ PNDIS_STATUS Status,
_Out_ PNDIS_HANDLE NdisProtocolHandle,
_In_ PNDIS_PROTOCOL_CHARACTERISTICS ProtocolCharacteristics,
_In_ UINT CharacteristicsLength
);
"""
pStatus, pProtoHandle, pChars, clen = argv
rv = NDIS_STATUS_SUCCESS
hnd = self.new_id()
pchr = self.mem_read(pChars, clen)
self.protocols.update({hnd: pchr})
if pStatus:
self.mem_write(pStatus, rv.to_bytes(4, 'little'))
if pProtoHandle:
self.mem_write(pProtoHandle, hnd.to_bytes(4, 'little'))
@apihook('NdisIMRegisterLayeredMiniport', argc=4)
def NdisIMRegisterLayeredMiniport(self, emu, argv, ctx={}):
"""
NDIS_STATUS NdisIMRegisterLayeredMiniport(
_In_ NDIS_HANDLE NdisWrapperHandle,
_In_ PNDIS_MINIPORT_CHARACTERISTICS MiniportCharacteristics,
_In_ UINT CharacteristicsLength,
_Out_ PNDIS_HANDLE DriverHandle
);
"""
hnd, mp_chars, clen, drv_hnd = argv
rv = NDIS_STATUS_SUCCESS
if not self.wrappers.get(hnd):
rv = NDIS_STATUS_FAILURE
dhnd = self.new_id()
mpc = self.mem_read(mp_chars, clen)
self.drivers.update({dhnd: mpc})
if drv_hnd:
self.mem_write(drv_hnd, dhnd.to_bytes(4, 'little'))
return rv
@apihook('NdisIMAssociateMiniport', argc=2)
def NdisIMAssociateMiniport(self, emu, argv, ctx={}):
"""
void NdisIMAssociateMiniport(
NDIS_HANDLE DriverHandle,
NDIS_HANDLE ProtocolHandle
);
"""
drv_hnd, phnd = argv
@apihook('NdisAllocateGenericObject', argc=3)
def NdisAllocateGenericObject(self, emu, argv, ctx={}):
"""
PNDIS_GENERIC_OBJECT NdisAllocateGenericObject(
PDRIVER_OBJECT DriverObject,
ULONG Tag,
USHORT Size
);
"""
drv, tag, size = argv
ptr = 0
stag = self.convert_pool_tag(tag)
argv[1] = stag
go = self.ndis.NDIS_GENERIC_OBJECT(emu.get_ptr_size())
go.DriverObject = drv
total = size + self.sizeof(go)
ptr = self.mem_alloc(size=total,
tag='api.struct.NDIS_GENERIC_OBJECT.%s' % (stag))
self.mem_write(ptr, self.get_bytes(go))
return ptr
@apihook('NdisAllocateMemoryWithTag', argc=3)
def NdisAllocateMemoryWithTag(self, emu, argv, ctx={}):
"""
NDIS_STATUS NdisAllocateMemoryWithTag(
_Out_ PVOID *VirtualAddress,
_In_ UINT Length,
_In_ ULONG Tag
);
"""
va, size, tag = argv
rv = ddk.STATUS_SUCCESS
stag = self.convert_pool_tag(tag)
argv[2] = stag
ptr = self.mem_alloc(size=size, tag='api.ndis_pool.%s' % (stag))
self.mem_write(va,
ptr.to_bytes(emu.get_ptr_size(), 'little'))
return rv
@apihook('NdisAllocateNetBufferListPool', argc=2)
def NdisAllocateNetBufferListPool(self, emu, argv, ctx={}):
"""
NDIS_HANDLE NdisAllocateNetBufferListPool(
NDIS_HANDLE NdisHandle,
PNET_BUFFER_LIST_POOL_PARAMETERS Parameters
);
"""
NdisHandle, Parameters = argv
params = self.mem_cast(self.ndis.NET_BUFFER_LIST_POOL_PARAMETERS(emu.get_ptr_size()),
Parameters)
nbl = self.ndis.NET_BUFFER_LIST(emu.get_ptr_size())
if params.fAllocateNetBuffer:
net_buf = self.ndis.NET_BUFFER(emu.get_ptr_size())
nb_ptr = self.mem_alloc(size=self.sizeof(net_buf),
tag='api.struct.NET_BUFFER')
nbl.FirstNetBuffer = nb_ptr
self.mem_write(nb_ptr, self.get_bytes(net_buf))
nbl_ptr = self.mem_alloc(size=self.sizeof(nbl),
tag='api.struct.NET_BUFFER_LIST')
self.mem_write(nbl_ptr, self.get_bytes(nbl))
return nbl_ptr
@apihook('NdisFreeNetBufferListPool', argc=1)
def NdisFreeNetBufferListPool(self, emu, argv, ctx={}):
"""
void NdisFreeNetBufferListPool(
NDIS_HANDLE PoolHandle
);
"""
handle, = argv
return
@apihook('NdisFreeMemory', argc=3)
def NdisFreeMemory(self, emu, argv, ctx={}):
"""
void NdisFreeMemory(
PVOID VirtualAddress,
UINT Length,
UINT MemoryFlags
);
"""
va, length, flags = argv
return
@apihook('NdisFreeGenericObject', argc=1)
def NdisFreeGenericObject(self, emu, argv, ctx={}):
"""
void NdisFreeGenericObject(
PNDIS_GENERIC_OBJECT NdisObject
);
"""
pObj, = argv
return
+404
View File
@@ -0,0 +1,404 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
from socket import inet_ntoa, ntohs
import speakeasy.winenv.defs.nt.ddk as ddk
import speakeasy.winenv.defs.winsock.ws2_32 as winsock
import speakeasy.winenv.defs.nt.ntoskrnl as nt
import speakeasy.winenv.defs.wsk as wsk
import speakeasy.windows.netman as netman
import speakeasy.windows.objman as objman
from speakeasy.errors import ApiEmuError
from .. import api
WSK_FLAG_BASIC_SOCKET = 0x00000000
WSK_FLAG_LISTEN_SOCKET = 0x00000001
WSK_FLAG_CONNECTION_SOCKET = 0x00000002
WSK_FLAG_DATAGRAM_SOCKET = 0x00000004
class WskSocket(objman.KernelObject):
def __init__(self, api, family, stype, protocol, flags):
self.emu = api.emu
super(WskSocket, self).__init__(self.emu)
self.family = family
self.sock_type = stype
self.protocol = protocol
self.flags = flags
self.dispatch = None
self.dispatch_addr = 0
self.dispatch_ptr = 0
self.types = api.win
# TODO: support other wsk socket types
if flags == WSK_FLAG_DATAGRAM_SOCKET:
self.dispatch = self.types.WSK_PROVIDER_DATAGRAM_DISPATCH(self.emu.get_ptr_size())
self.dispatch_ptr = \
self.emu.mem_map(size=self.sizeof(self.dispatch) +
self.emu.get_ptr_size(),
tag='api.struct.WSK_PROVIDER_DATAGRAM_DISPATCH') # noqa
# We need a ptr to the dispatch table ptr
self.dispatch_addr = self.dispatch_ptr + self.emu.get_ptr_size()
self.emu.mem_write(self.dispatch_ptr, self.dispatch_addr.to_bytes(self.emu.get_ptr_size(), 'little')) # noqa
addr = self.emu.add_callback(Netio.name, api.WskControlSocket.__apihook__[0]) # noqa
self.dispatch.Basic.WskControlSocket = addr
addr = self.emu.add_callback(Netio.name, api.WskCloseSocket.__apihook__[0]) # noqa
self.dispatch.Basic.WskCloseSocket = addr
addr = self.emu.add_callback(Netio.name, api.WskBind.__apihook__[0]) # noqa
self.dispatch.WskBind = addr
addr = self.emu.add_callback(Netio.name, api.WskSendTo.__apihook__[0]) # noqa
self.dispatch.WskSendTo = addr
addr = self.emu.add_callback(Netio.name, api.WskReceiveFrom.__apihook__[0]) # noqa
self.dispatch.WskReceiveFrom = addr
addr = self.emu.add_callback(Netio.name, api.WskRelease.__apihook__[0]) # noqa
self.dispatch.WskRelease = addr
addr = self.emu.add_callback(Netio.name, api.WskGetLocalAddress.__apihook__[0]) # noqa
self.dispatch.WskGetLocalAddress = addr
self.emu.mem_write(self.dispatch_addr, self.get_bytes(self.dispatch))
else:
raise ApiEmuError('Unsupported WSK socket type: 0x%x' % (flags))
def get_dispatch_ptr(self):
return self.dispatch_ptr
class Netio(api.ApiHandler):
"""
Implements kernel networking functions implemented in netio.sys
"""
name = 'netio'
apihook = api.ApiHandler.apihook
impdata = api.ApiHandler.impdata
def __init__(self, emu):
super(Netio, self).__init__(emu)
self.funcs = {}
self.data = {}
self.win = None
self.nt = None
self.wsk_registrations = {}
self.wsk_sockets = {}
self.provider_npi = None
self.win = wsk
self.nt = nt
self.netman = netman.NetworkManager(config=emu.get_network_config())
super(Netio, self).__get_hook_attrs__(self)
self.prov_disp = self.win.WSK_PROVIDER_DISPATCH(emu.get_ptr_size())
self.prov_disp.Version = 0x100
addr = emu.add_callback(Netio.name, self.WskSocket.__apihook__[0]) # noqa
self.prov_disp.WskSocket = addr
addr = emu.add_callback(Netio.name, self.WskSocketConnect.__apihook__[0]) # noqa
self.prov_disp.WskSocketConnect = addr
addr = emu.add_callback(Netio.name, self.WskControlClient.__apihook__[0]) # noqa
self.prov_disp.WskControlClient = addr
addr = emu.add_callback(Netio.name, self.WskGetAddressInfo.__apihook__[0]) # noqa
self.prov_disp.WskGetAddressInfo = addr
addr = emu.add_callback(Netio.name, self.WskFreeAddressInfo.__apihook__[0]) # noqa
self.prov_disp.WskFreeAddressInfo = addr
addr = emu.add_callback(Netio.name, self.WskGetNameInfo.__apihook__[0])
self.prov_disp.WskGetNameInfo = addr
@apihook('WskRegister', argc=2)
def WskRegister(self, emu, argv, ctx={}):
"""NTSTATUS WskRegister(
PWSK_CLIENT_NPI WskClientNpi,
PWSK_REGISTRATION WskRegistration
);
"""
WskClientNpi, WskRegistration = argv
rv = 0
self.wsk_registrations.update({WskRegistration: WskClientNpi})
return rv
@apihook('WskCaptureProviderNPI', argc=3)
def WskCaptureProviderNPI(self, emu, argv, ctx={}):
"""NTSTATUS WskCaptureProviderNPI(
PWSK_REGISTRATION WskRegistration,
ULONG WaitTimeout,
PWSK_PROVIDER_NPI WskProviderNpi
);
"""
WskRegistration, WaitTimeout, WskProviderNpi = argv
rv = 0
cli = self.wsk_registrations.get(WskRegistration)
if not cli:
rv = ddk.STATUS_NOINTERFACE
else:
if not self.provider_npi:
wpn = self.win.WSK_PROVIDER_NPI(emu.get_ptr_size())
wpn.Client = cli
# Allocate the wpn dispatch table
wpd_addr = self.mem_alloc(size = self.sizeof(self.prov_disp), # noqa
tag='api.struct.WSK_PROVIDER_DISPATCH')
wpn.Dispatch = wpd_addr
self.mem_write(wpd_addr, self.get_bytes(self.prov_disp))
self.mem_write(WskProviderNpi, self.get_bytes(wpn))
self.provider_npi = wpn
return rv
@apihook('callback_WskSocket', argc=11)
def WskSocket(self, emu, argv, ctx={}):
"""NTSTATUS PfnWskSocket(
PWSK_CLIENT Client,
ADDRESS_FAMILY AddressFamily,
USHORT SocketType,
ULONG Protocol,
ULONG Flags,
PVOID SocketContext,
const VOID *Dispatch,
PEPROCESS OwningProcess,
PETHREAD OwningThread,
PSECURITY_DESCRIPTOR SecurityDescriptor,
PIRP Irp
)"""
cli, af, stype, proto, flags, sctx, disp, proc, thr, secdesc, pIrp = argv # noqa
rv = ddk.STATUS_INVALID_PARAMETER
if pIrp:
irp = self.mem_cast(self.nt.IRP(emu.get_ptr_size()), pIrp)
sock = WskSocket(self, af, stype, proto, flags)
addr = sock.get_dispatch_ptr()
irp.IoStatus.Information = addr
self.write_back(pIrp, irp)
rv = ddk.STATUS_SUCCESS
self.wsk_sockets.update({addr: sock})
return rv
@apihook('callback_WskSocketConnect', argc=12)
def WskSocketConnect(self, emu, argv, ctx={}):
"""NTSTATUS PfnWskSocketConnect(
PWSK_CLIENT Client,
USHORT SocketType,
ULONG Protocol,
PSOCKADDR LocalAddress,
PSOCKADDR RemoteAddress,
ULONG Flags,
PVOID SocketContext,
const WSK_CLIENT_CONNECTION_DISPATCH *Dispatch,
PEPROCESS OwningProcess,
PETHREAD OwningThread,
PSECURITY_DESCRIPTOR SecurityDescriptor,
PIRP Irp
)"""
cli, stype, proto, laddr, raddr, flags, sctx, disp, proc, thr, secdesc, irp = argv # noqa
rv = 0
return rv
@apihook('callback_WskControlClient', argc=8)
def WskControlClient(self, emu, argv, ctx={}):
"""NTSTATUS PfnWskControlClient(
PWSK_CLIENT Client,
ULONG ControlCode,
SIZE_T InputSize,
PVOID InputBuffer,
SIZE_T OutputSize,
PVOID OutputBuffer,
SIZE_T *OutputSizeReturned,
PIRP Irp
)"""
cli, ctl, insiz, inbuf, osiz, obuf, oret, irp = argv
rv = 0
return rv
@apihook('callback_WskGetAddressInfo', argc=10)
def WskGetAddressInfo(self, emu, argv, ctx={}):
"""NTSTATUS PfnWskGetAddressInfo(
PWSK_CLIENT Client,
PUNICODE_STRING NodeName,
PUNICODE_STRING ServiceName,
ULONG NameSpace,
GUID *Provider,
PADDRINFOEXW Hints,
PADDRINFOEXW *Result,
PEPROCESS OwningProcess,
PETHREAD OwningThread,
PIRP Irp
)"""
cli, node, svc, namespace, prov, hints, res, proc, thr, irp = argv
rv = 0
return rv
@apihook('callback_WskFreeAddressInfo', argc=2)
def WskFreeAddressInfo(self, emu, argv, ctx={}):
"""void PfnWskFreeAddressInfo(
PWSK_CLIENT Client,
PADDRINFOEXW AddrInfo
)"""
cli, addr = argv
rv = 0
return rv
@apihook('callback_WskGetNameInfo', argc=9)
def WskGetNameInfo(self, emu, argv, ctx={}):
"""NTSTATUS PfnWskGetNameInfo(
PWSK_CLIENT Client,
PSOCKADDR SockAddr,
ULONG SockAddrLength,
PUNICODE_STRING NodeName,
PUNICODE_STRING ServiceName,
ULONG Flags,
PEPROCESS OwningProcess,
PETHREAD OwningThread,
PIRP Irp
)"""
cli, saddr, saddrlen, node, svc, flags, proc, thr, irp = argv
rv = 0
return rv
@apihook('callback_WskControlSocket', argc=10)
def WskControlSocket(self, emu, argv, ctx={}):
"""NTSTATUS PfnWskControlSocket(
PWSK_SOCKET Socket,
WSK_CONTROL_SOCKET_TYPE RequestType,
ULONG ControlCode,
ULONG Level,
SIZE_T InputSize,
PVOID InputBuffer,
SIZE_T OutputSize,
PVOID OutputBuffer,
SIZE_T *OutputSizeReturned,
PIRP Irp
)"""
sock, rtype, ctl, level, isize, ibuf, osize, obuf, oret, irp = argv
rv = 0
return rv
@apihook('callback_WskCloseSocket', argc=2)
def WskCloseSocket(self, emu, argv, ctx={}):
"""NTSTATUS PfnWskCloseSocket(
PWSK_SOCKET Socket,
PIRP Irp
)"""
sock, irp = argv
rv = 0
return rv
@apihook('callback_WskBind', argc=4)
def WskBind(self, emu, argv, ctx={}):
"""NTSTATUS PfnWskBind(
PWSK_SOCKET Socket,
PSOCKADDR LocalAddress,
ULONG Flags,
PIRP Irp
)"""
sock, laddr, flags, irp = argv
rv = 0
sock_addr = self.mem_cast(winsock.sockaddr_in(emu.get_ptr_size()), laddr)
raddr = inet_ntoa(sock_addr.sin_addr.to_bytes(4, 'little'))
rport = ntohs(sock_addr.sin_port)
argv[1] = '%s:%d' % (raddr, rport)
return rv
@apihook('callback_WskSendTo', argc=7)
def WskSendTo(self, emu, argv, ctx={}):
"""NTSTATUS PfnWskSendTo(
PWSK_SOCKET Socket,
PWSK_BUF Buffer,
ULONG Flags,
PSOCKADDR RemoteAddress,
ULONG ControlInfoLength,
PCMSGHDR ControlInfo,
PIRP Irp
)"""
sock, buf, flags, raddr, infolen, ctlinfo, irp = argv
rv = 0
return rv
@apihook('callback_WskReceiveFrom', argc=8)
def WskReceiveFrom(self, emu, argv, ctx={}):
"""NTSTATUS PfnWskReceiveFrom(
PWSK_SOCKET Socket,
PWSK_BUF Buffer,
ULONG Flags,
PSOCKADDR RemoteAddress,
PULONG ControlLength,
PCMSGHDR ControlInfo,
PULONG ControlFlags,
PIRP Irp
)"""
sock, buf, flags, raddr, ctllen, ctlinfo, irp = argv
rv = 0
return rv
@apihook('callback_WskRelease', argc=2)
def WskRelease(self, emu, argv, ctx={}):
"""NTSTATUS WSKAPI WSKAPI * WskRelease(
_In_ PWSK_SOCKET Socket,
_In_ PWSK_DATA_INDICATION DataIndication
)"""
sock, data_indic = argv
rv = 0
return rv
@apihook('callback_WskGetLocalAddress', argc=2)
def WskGetLocalAddress(self, emu, argv, ctx={}):
"""NTSTATUS PfnWskGetLocalAddress(
PWSK_SOCKET Socket,
PSOCKADDR LocalAddress,
PIRP Irp
)"""
sock, laddr = argv
rv = 0
return rv
@apihook('WskReleaseProviderNPI', argc=1)
def WskReleaseProviderNPI(self, emu, argv, ctx={}):
"""
void WskReleaseProviderNPI(
PWSK_REGISTRATION WskRegistration
);
"""
reg, = argv
return
@apihook('WskDeregister', argc=1)
def WskDeregister(self, emu, argv, ctx={}):
"""
void WskDeregister(
PWSK_REGISTRATION WskRegistration
);
"""
reg, = argv
return
File diff suppressed because it is too large Load Diff
+42
View File
@@ -0,0 +1,42 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
import speakeasy.winenv.defs.nt.ddk as ddk
from speakeasy.winenv.api import api
class Usbd(api.ApiHandler):
"""
Implements the USB stack driver (USBD.sys)
"""
name = 'usbd'
apihook = api.ApiHandler.apihook
impdata = api.ApiHandler.impdata
class ApiEmuError(Exception):
pass
def __init__(self, emu):
super(Usbd, self).__init__(emu)
self.funcs = {}
self.data = {}
super(Usbd, self).__get_hook_attrs__(self)
@apihook('USBD_ValidateConfigurationDescriptor', argc=5)
def USBD_ValidateConfigurationDescriptor(self, emu, argv, ctx={}):
"""
USBD_STATUS USBD_ValidateConfigurationDescriptor(
PUSB_CONFIGURATION_DESCRIPTOR ConfigDesc,
ULONG BufferLength,
USHORT Level,
PUCHAR *Offset,
ULONG Tag
);
"""
rv = ddk.STATUS_SUCCESS
ConfigDesc, BufferLength, Level, Offset, Tag = argv
return rv
+880
View File
@@ -0,0 +1,880 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
import uuid
import speakeasy.winenv.arch as e_arch
import speakeasy.winenv.defs.nt.ddk as ddk
import speakeasy.winenv.defs.wdf as wdf
import speakeasy.winenv.defs.usb as usbdefs
import speakeasy.winenv.defs.nt.ntoskrnl as ntos
from .. import api
class WdfDriver(object):
def __init__(self):
self.reg_path = None
self.typed_context_worker = None
self.queues = {}
self.driver_object_addr = None
self.driver_object = None
class WdfDevice(object):
def __init__(self):
self.device_object_addr = None
self.device_object = None
class WdfUsbDevice(object):
def __init__(self):
self.num_interfaces = 0
self.config_desc = None
class WdfUsbInterface(object):
def __init__(self):
self.config_desc = 0
self.iface_index = 0
self.setting_index = 0
class WdfUsbPipe(object):
def __init__(self):
self.interface = None
self.index = 0
class Wdfldr(api.ApiHandler):
"""
Implements the Windows Driver Framework (WDK)
"""
name = 'wdfldr'
apihook = api.ApiHandler.apihook
impdata = api.ApiHandler.impdata
def __init__(self, emu):
super(Wdfldr, self).__init__(emu)
self.funcs = {}
self.curr_handle = 4
self.pnp_device = None
self.data = {}
self.emu = emu
self.wdf_drivers = {}
self.wdf_devices = {}
self.usb_devices = {}
self.usb_pipes = {}
self.usb_interfaces = {}
self.handles = {}
self.types = wdf
self.func_table = self.types.WDFFUNCTIONS(emu.get_ptr_size())
self.func_table_ptr = None
self.component_globals = None
super(Wdfldr, self).__get_hook_attrs__(self)
def get_handle(self):
self.curr_handle += 4
return self.curr_handle
def set_func_table(self, emu):
addr = emu.add_callback(Wdfldr.name, self.WdfDriverCreate.__apihook__[0])
self.func_table.pfnWdfDriverCreate = addr
addr = emu.add_callback(Wdfldr.name,
self.WdfDeviceInitSetPnpPowerEventCallbacks.__apihook__[0])
self.func_table.pfnWdfDeviceInitSetPnpPowerEventCallbacks = addr
addr = emu.add_callback(Wdfldr.name,
self.WdfDeviceInitSetRequestAttributes.__apihook__[0])
self.func_table.pfnWdfDeviceInitSetRequestAttributes = addr
addr = emu.add_callback(Wdfldr.name,
self.WdfDeviceInitSetFileObjectConfig.__apihook__[0])
self.func_table.pfnWdfDeviceInitSetFileObjectConfig = addr
addr = emu.add_callback(Wdfldr.name,
self.WdfDeviceInitSetIoType.__apihook__[0])
self.func_table.pfnWdfDeviceInitSetIoType = addr
addr = emu.add_callback(Wdfldr.name,
self.WdfDeviceCreate.__apihook__[0])
self.func_table.pfnWdfDeviceCreate = addr
addr = emu.add_callback(Wdfldr.name,
self.WdfObjectGetTypedContextWorker.__apihook__[0])
self.func_table.pfnWdfObjectGetTypedContextWorker = addr
addr = emu.add_callback(Wdfldr.name,
self.WdfDriverOpenParametersRegistryKey.__apihook__[0])
self.func_table.pfnWdfDriverOpenParametersRegistryKey = addr
addr = emu.add_callback(Wdfldr.name, self.WdfRegistryQueryULong.__apihook__[0])
self.func_table.pfnWdfRegistryQueryULong = addr
addr = emu.add_callback(Wdfldr.name, self.WdfRegistryClose.__apihook__[0])
self.func_table.pfnWdfRegistryClose = addr
addr = emu.add_callback(Wdfldr.name, self.WdfDeviceSetPnpCapabilities.__apihook__[0])
self.func_table.pfnWdfDeviceSetPnpCapabilities = addr
addr = emu.add_callback(Wdfldr.name, self.WdfIoQueueCreate.__apihook__[0])
self.func_table.pfnWdfIoQueueCreate = addr
addr = emu.add_callback(Wdfldr.name, self.WdfIoQueueReadyNotify.__apihook__[0])
self.func_table.pfnWdfIoQueueReadyNotify = addr
addr = emu.add_callback(Wdfldr.name,
self.WdfDeviceCreateDeviceInterface.__apihook__[0])
self.func_table.pfnWdfDeviceCreateDeviceInterface = addr
addr = emu.add_callback(Wdfldr.name,
self.WdfDeviceWdmGetAttachedDevice.__apihook__[0])
self.func_table.pfnWdfDeviceWdmGetAttachedDevice = addr
addr = emu.add_callback(Wdfldr.name,
self.WdfDeviceWdmGetDeviceObject.__apihook__[0])
self.func_table.pfnWdfDeviceWdmGetDeviceObject = addr
addr = emu.add_callback(Wdfldr.name,
self.WdfUsbTargetDeviceCreateWithParameters.__apihook__[0])
self.func_table.pfnWdfUsbTargetDeviceCreateWithParameters = addr
addr = emu.add_callback(Wdfldr.name,
self.WdfUsbTargetDeviceGetDeviceDescriptor.__apihook__[0])
self.func_table.pfnWdfUsbTargetDeviceGetDeviceDescriptor = addr
addr = emu.add_callback(Wdfldr.name,
self.WdfUsbTargetDeviceRetrieveConfigDescriptor.__apihook__[0])
self.func_table.pfnWdfUsbTargetDeviceRetrieveConfigDescriptor = addr
addr = emu.add_callback(Wdfldr.name,
self.WdfMemoryCreate.__apihook__[0])
self.func_table.pfnWdfMemoryCreate = addr
addr = emu.add_callback(Wdfldr.name,
self.WdfUsbTargetDeviceSelectConfig.__apihook__[0])
self.func_table.pfnWdfUsbTargetDeviceSelectConfig = addr
addr = emu.add_callback(Wdfldr.name,
self.WdfUsbTargetDeviceGetNumInterfaces.__apihook__[0])
self.func_table.pfnWdfUsbTargetDeviceGetNumInterfaces = addr
addr = emu.add_callback(Wdfldr.name,
self.WdfUsbTargetDeviceRetrieveInformation.__apihook__[0])
self.func_table.pfnWdfUsbTargetDeviceRetrieveInformation = addr
addr = emu.add_callback(Wdfldr.name,
self.WdfUsbInterfaceGetNumSettings.__apihook__[0])
self.func_table.pfnWdfUsbInterfaceGetNumSettings = addr
addr = emu.add_callback(Wdfldr.name,
self.WdfUsbInterfaceSelectSetting.__apihook__[0])
self.func_table.pfnWdfUsbInterfaceSelectSetting = addr
addr = emu.add_callback(Wdfldr.name,
self.WdfUsbInterfaceGetNumConfiguredPipes.__apihook__[0])
self.func_table.pfnWdfUsbInterfaceGetNumConfiguredPipes = addr
addr = emu.add_callback(Wdfldr.name,
self.WdfUsbInterfaceGetConfiguredPipe.__apihook__[0])
self.func_table.pfnWdfUsbInterfaceGetConfiguredPipe = addr
addr = emu.add_callback(Wdfldr.name,
self.WdfUsbTargetPipeGetInformation.__apihook__[0])
self.func_table.pfnWdfUsbTargetPipeGetInformation = addr
addr = emu.add_callback(Wdfldr.name,
self.WdfUsbInterfaceGetInterfaceNumber.__apihook__[0])
self.func_table.pfnWdfUsbInterfaceGetInterfaceNumber = addr
self.mem_write(self.func_table_ptr, self.func_table.get_bytes())
def parse_usb_config(self, data):
interfaces = []
# Get the USB config descriptor
cd = usbdefs.USB_CONFIGURATION_DESCRIPTOR().cast(data)
ifaces = cd.bNumInterfaces
data = data[cd.bLength:]
for i in range(ifaces):
endpoints = []
_id = usbdefs.USB_INTERFACE_DESCRIPTOR().cast(data)
data = data[_id.bLength:]
for j in range(_id.bNumEndpoints):
ep = usbdefs.USB_ENDPOINT_DESCRIPTOR().cast(data)
data = data[ep.bLength:]
endpoints.append(ep)
interfaces.append([_id, endpoints])
return interfaces
@apihook('WdfVersionBind', argc=4)
def WdfVersionBind(self, emu, argv, ctx={}):
'''
NTSTATUS
WdfVersionBind(
__in PDRIVER_OBJECT DriverObject,
__in PUNICODE_STRING RegistryPath,
__inout PWDF_BIND_INFO BindInfo,
__out PWDF_COMPONENT_GLOBALS* ComponentGlobals
);
'''
rv = ddk.STATUS_SUCCESS
drv, reg_path, BindInfo, comp_globals = argv
wbi = self.types.WDF_BIND_INFO(emu.get_ptr_size())
wbi = self.mem_cast(wbi, BindInfo)
if not self.func_table_ptr:
size = self.func_table.sizeof()
self.func_table_ptr = self.mem_alloc(size, tag='api.struct.WDFFUNCTIONS')
self.mem_write(wbi.FuncTable, (self.func_table_ptr).to_bytes(emu.get_ptr_size(),
'little'))
if not self.component_globals:
components = self.types.WDF_COMPONENT_GLOBALS(emu.get_ptr_size())
self.component_globals = self.mem_alloc(components.sizeof(),
tag='api.struct.WDF_COMPONENT_GLOBALS')
self.mem_write(comp_globals, (self.component_globals).to_bytes(emu.get_ptr_size(),
'little'))
self.set_func_table(emu)
# For now, just leave the handle open so we can reference it later
return rv
@apihook('WdfDriverCreate', argc=6)
def WdfDriverCreate(self, emu, argv, ctx={}):
'''
NTSTATUS WdfDriverCreate(
PWDF_DRIVER_GLOBALS DriverGlobals,
PDRIVER_OBJECT DriverObject,
PCUNICODE_STRING RegistryPath,
PWDF_OBJECT_ATTRIBUTES DriverAttributes,
PWDF_DRIVER_CONFIG DriverConfig,
WDFDRIVER *Driver
);
'''
DriverGlobals, DriverObject, RegistryPath, DriverAttributes, DriverConfig, Driver = argv
driver = WdfDriver()
driver.reg_path = self.read_unicode_string(RegistryPath)
driver.driver_object_addr = DriverObject
driver.driver_object = self.mem_cast(ntos.DRIVER_OBJECT(emu.get_ptr_size()),
DriverObject)
self.wdf_drivers.update({DriverGlobals: driver})
if DriverConfig:
config = self.mem_cast(self.types.WDF_DRIVER_CONFIG(emu.get_ptr_size()), # noqa
DriverConfig)
rv = 0
return rv
@apihook('WdfDeviceInitSetPnpPowerEventCallbacks', argc=3)
def WdfDeviceInitSetPnpPowerEventCallbacks(self, emu, argv, ctx={}):
'''
void WdfDeviceInitSetPnpPowerEventCallbacks(
PWDFDEVICE_INIT DeviceInit,
PWDF_PNPPOWER_EVENT_CALLBACKS PnpPowerEventCallbacks
);
'''
DriverGlobals, DeviceInit, PnpPowerEventCallbacks = argv
return
@apihook('WdfDeviceInitSetRequestAttributes', argc=3)
def WdfDeviceInitSetRequestAttributes(self, emu, argv, ctx={}):
'''
void WdfDeviceInitSetRequestAttributes(
PWDFDEVICE_INIT DeviceInit,
PWDF_OBJECT_ATTRIBUTES RequestAttributes
);
'''
DriverGlobals, DeviceInit, RequestAttributes = argv
return
@apihook('WdfDeviceInitSetFileObjectConfig', argc=4)
def WdfDeviceInitSetFileObjectConfig(self, emu, argv, ctx={}):
'''
void WdfDeviceInitSetFileObjectConfig(
PWDFDEVICE_INIT DeviceInit,
PWDF_FILEOBJECT_CONFIG FileObjectConfig,
PWDF_OBJECT_ATTRIBUTES FileObjectAttributes
);
'''
DriverGlobals, DeviceInit, FileObjectConfig, FileObjectAttributes = argv
return
@apihook('WdfDeviceInitSetIoType', argc=3)
def WdfDeviceInitSetIoType(self, emu, argv, ctx={}):
'''
void WdfDeviceInitSetIoType(
PWDFDEVICE_INIT DeviceInit,
WDF_DEVICE_IO_TYPE IoType
);
'''
DriverGlobals, DeviceInit, IoType = argv
return
@apihook('WdfDeviceCreate', argc=4)
def WdfDeviceCreate(self, emu, argv, ctx={}):
'''
NTSTATUS WdfDeviceCreate(
PWDFDEVICE_INIT *DeviceInit,
PWDF_OBJECT_ATTRIBUTES DeviceAttributes,
WDFDEVICE *Device
);
'''
DriverGlobals, DeviceInit, DeviceAttributes, Device = argv
rv = ddk.STATUS_SUCCESS
if Device:
handle = self.get_handle()
dev = WdfDevice()
self.wdf_devices.update({handle: dev})
self.mem_write(Device, (handle).to_bytes(emu.get_ptr_size(), 'little'))
do = ntos.DEVICE_OBJECT(emu.get_ptr_size())
dev.device_object_addr = self.mem_alloc(do.sizeof(), tag='api.struct.DEVICE_OBJECT')
dev.device_object = do
driver = self.wdf_drivers.get(DriverGlobals)
if driver:
dev.device_object.DriverObject = driver.driver_object_addr
self.mem_write(dev.device_object_addr, dev.device_object.get_bytes())
return rv
@apihook('WdfObjectGetTypedContextWorker', argc=3, conv=e_arch.CALL_CONV_FASTCALL)
def WdfObjectGetTypedContextWorker(self, emu, argv, ctx={}):
'''
PVOID WdfObjectGetTypedContextWorker(
WDFOBJECT Handle,
PCWDF_OBJECT_CONTEXT_TYPE_INFO TypeInfo
);
'''
DriverGlobals, Handle, TypeInfo = argv
driver = self.wdf_drivers.get(DriverGlobals)
if not driver.typed_context_worker:
size = self.types.WDF_COMPONENT_GLOBALS(emu.get_ptr_size()).sizeof()
driver.typed_context_worker = self.mem_alloc(size,
tag='api.struct.WDF_TYPED_CONTEXT_WORKER')
rv = driver.typed_context_worker
return rv
@apihook('WdfDriverOpenParametersRegistryKey', argc=5)
def WdfDriverOpenParametersRegistryKey(self, emu, argv, ctx={}):
'''
NTSTATUS WdfDriverOpenParametersRegistryKey(
WDFDRIVER Driver,
ACCESS_MASK DesiredAccess,
PWDF_OBJECT_ATTRIBUTES KeyAttributes,
WDFKEY *Key
);
'''
DriverGlobals, Driver, DesiredAccess, KeyAttributes, pKey = argv
rv = ddk.STATUS_OBJECT_NAME_NOT_FOUND
driver = self.wdf_drivers.get(DriverGlobals)
hnd = emu.reg_open_key(driver.reg_path + '\\Parameters')
if hnd:
rv = ddk.STATUS_SUCCESS
if pKey:
self.mem_write(pKey, (hnd).to_bytes(emu.get_ptr_size(), 'little'))
return rv
@apihook('WdfRegistryQueryULong', argc=4)
def WdfRegistryQueryULong(self, emu, argv, ctx={}):
'''
NTSTATUS WdfRegistryQueryULong(
WDFKEY Key,
PCUNICODE_STRING ValueName,
PULONG Value
);
'''
DriverGlobals, Key, ValueName, Value = argv
rv = ddk.STATUS_OBJECT_NAME_NOT_FOUND
wkey = emu.reg_get_key(Key)
if wkey:
val_name = self.read_unicode_string(ValueName)
argv[2] = val_name
value = wkey.get_value(val_name)
if value:
ulong = value.get_data()
self.mem_write(Value, (ulong).to_bytes(4, 'little'))
rv = ddk.STATUS_SUCCESS
return rv
@apihook('WdfRegistryClose', argc=2)
def WdfRegistryClose(self, emu, argv, ctx={}):
'''
void WdfRegistryClose(
WDFKEY Key
);
'''
DriverGlobals, Key = argv
return
@apihook('WdfDeviceSetPnpCapabilities', argc=3)
def WdfDeviceSetPnpCapabilities(self, emu, argv, ctx={}):
'''
void WdfDeviceSetPnpCapabilities(
WDFDEVICE Device,
PWDF_DEVICE_PNP_CAPABILITIES PnpCapabilities
);
'''
DriverGlobals, Device, PnpCapabilities = argv
return
@apihook('WdfIoQueueReadyNotify', argc=4)
def WdfIoQueueReadyNotify(self, emu, argv, ctx={}):
'''
NTSTATUS WdfIoQueueReadyNotify(
WDFQUEUE Queue,
PFN_WDF_IO_QUEUE_STATE QueueReady,
WDFCONTEXT Context
);
'''
DriverGlobals, Queue, QueueReady, Context = argv
rv = ddk.STATUS_SUCCESS
return rv
@apihook('WdfDeviceCreateDeviceInterface', argc=4)
def WdfDeviceCreateDeviceInterface(self, emu, argv, ctx={}):
'''
NTSTATUS WdfDeviceCreateDeviceInterface(
WDFDEVICE Device,
const GUID *InterfaceClassGUID,
PCUNICODE_STRING ReferenceString
);
'''
DriverGlobals, Device, InterfaceClassGUID, ReferenceString = argv
rv = ddk.STATUS_SUCCESS
if InterfaceClassGUID:
guid = self.mem_read(InterfaceClassGUID, 16)
guid = uuid.UUID(bytes_le=guid)
argv[2] = str(guid)
if ReferenceString:
ref = self.read_unicode_string(ReferenceString)
argv[3] = ref
return rv
@apihook('WdfIoQueueCreate', argc=5)
def WdfIoQueueCreate(self, emu, argv, ctx={}):
'''
NTSTATUS WdfIoQueueCreate(
WDFDEVICE Device,
PWDF_IO_QUEUE_CONFIG Config,
PWDF_OBJECT_ATTRIBUTES QueueAttributes,
WDFQUEUE *Queue
);
'''
DriverGlobals, Device, Config, QueueAttributes, Queue = argv
rv = ddk.STATUS_SUCCESS
queue_config = self.types.WDF_IO_QUEUE_CONFIG(emu.get_ptr_size())
queue_config = self.mem_cast(queue_config, Config)
hnd = self.get_handle()
driver = self.wdf_drivers.get(DriverGlobals)
driver.queues.update({hnd: queue_config})
if Queue:
self.mem_write(Queue, (hnd).to_bytes(emu.get_ptr_size(), 'little'))
return rv
@apihook('WdfDeviceWdmGetAttachedDevice', argc=2)
def WdfDeviceWdmGetAttachedDevice(self, emu, argv, ctx={}):
'''
PDEVICE_OBJECT WdfDeviceWdmGetAttachedDevice(
WDFDEVICE Device
);
'''
DriverGlobals, Device = argv
if not self.pnp_device:
do = ntos.DEVICE_OBJECT(emu.get_ptr_size())
self.pnp_device = self.mem_alloc(do.sizeof(), tag='api.struct.DEVICE_OBJECT')
rv = self.pnp_device
return rv
@apihook('WdfUsbTargetDeviceCreateWithParameters', argc=5)
def WdfUsbTargetDeviceCreateWithParameters(self, emu, argv, ctx={}):
'''
NTSTATUS WdfUsbTargetDeviceCreateWithParameters(
WDFDEVICE Device,
PWDF_USB_DEVICE_CREATE_CONFIG Config,
PWDF_OBJECT_ATTRIBUTES Attributes,
WDFUSBDEVICE *UsbDevice
);
'''
DriverGlobals, Device, Config, Attributes, UsbDevice = argv
rv = ddk.STATUS_SUCCESS
handle = self.get_handle()
usb = WdfUsbDevice()
self.usb_devices.update({handle: usb})
self.mem_write(UsbDevice, (handle).to_bytes(emu.get_ptr_size(), 'little'))
return rv
@apihook('WdfDeviceWdmGetDeviceObject', argc=2)
def WdfDeviceWdmGetDeviceObject(self, emu, argv, ctx={}):
'''
PDEVICE_OBJECT WdfDeviceWdmGetDeviceObject(
WDFDEVICE Device
);
'''
DriverGlobals, Device = argv
rv = 0
dev = self.wdf_devices.get(Device)
if dev:
rv = dev.device_object_addr
return rv
@apihook('WdfUsbTargetDeviceGetDeviceDescriptor', argc=3)
def WdfUsbTargetDeviceGetDeviceDescriptor(self, emu, argv, ctx={}):
'''
void WdfUsbTargetDeviceGetDeviceDescriptor(
WDFUSBDEVICE UsbDevice,
PUSB_DEVICE_DESCRIPTOR UsbDeviceDescriptor
);
'''
DriverGlobals, UsbDevice, UsbDeviceDescriptor = argv
dev = self.usb_devices.get(UsbDevice)
if dev:
dd = usbdefs.USB_DEVICE_DESCRIPTOR(emu.get_ptr_size())
self.mem_write(UsbDeviceDescriptor, dd.get_bytes())
return
@apihook('WdfMemoryCreate', argc=7)
def WdfMemoryCreate(self, emu, argv, ctx={}):
'''
NTSTATUS WdfMemoryCreate(
PWDF_OBJECT_ATTRIBUTES Attributes,
POOL_TYPE PoolType,
ULONG PoolTag,
size_t BufferSize,
WDFMEMORY *Memory,
PVOID *Buffer
);
'''
DriverGlobals, Attributes, PoolType, PoolTag, BufferSize, Mem, Buf = argv
rv = ddk.STATUS_SUCCESS
ptr = self.mem_alloc(BufferSize, tag='api.struct.WDFMEMORY')
if Mem:
self.mem_write(Mem, (ptr).to_bytes(emu.get_ptr_size(), 'little'))
if Buf:
self.mem_write(Buf, (ptr).to_bytes(emu.get_ptr_size(), 'little'))
return rv
@apihook('WdfUsbTargetDeviceSelectConfig', argc=4)
def WdfUsbTargetDeviceSelectConfig(self, emu, argv, ctx={}):
'''
NTSTATUS WdfUsbTargetDeviceSelectConfig(
WDFUSBDEVICE UsbDevice,
PWDF_OBJECT_ATTRIBUTES PipeAttributes,
PWDF_USB_DEVICE_SELECT_CONFIG_PARAMS Params
);
'''
DriverGlobals, UsbDevice, PipeAttributes, Params = argv
rv = ddk.STATUS_SUCCESS
cfg_params = self.types.WDF_USB_DEVICE_SELECT_CONFIG_PARAMS(emu.get_ptr_size())
cfg_params = self.mem_cast(cfg_params, Params)
dev = self.usb_devices.get(UsbDevice)
enum = self.types.WdfUsbTargetDeviceSelectConfigType
if cfg_params.Type == enum.WdfUsbTargetDeviceSelectConfigTypeSingleInterface:
dev.num_interfaces = 1
hnd = self.get_handle()
uf = WdfUsbInterface()
uf.config_desc = dev.config_desc
uf.iface_index = 0
self.usb_interfaces.update({hnd: uf})
cfg_params.Types.SingleInterface.ConfiguredUsbInterface = hnd
elif cfg_params.Type == enum.WdfUsbTargetDeviceSelectConfigTypeMultiInterface:
dev.num_interfaces = cfg_params.Types.MultiInterface.NumberInterfaces
elif cfg_params.Type == enum.WdfUsbTargetDeviceSelectConfigTypeInterfacesDescriptor:
dev.num_interfaces = cfg_params.Types.Descriptor.NumInterfaceDescriptors
self.mem_write(Params, cfg_params.get_bytes())
return rv
@apihook('WdfUsbTargetDeviceRetrieveConfigDescriptor', argc=4)
def WdfUsbTargetDeviceRetrieveConfigDescriptor(self, emu, argv, ctx={}):
'''
NTSTATUS WdfUsbTargetDeviceRetrieveConfigDescriptor(
WDFUSBDEVICE UsbDevice,
PVOID ConfigDescriptor,
PUSHORT ConfigDescriptorLength
);
'''
DriverGlobals, UsbDevice, ConfigDescriptor, ConfigDescriptorLength = argv
rv = ddk.STATUS_BUFFER_TOO_SMALL
buf_len = self.mem_read(ConfigDescriptorLength, 2)
buf_len = int.from_bytes(buf_len, 'little')
# For now, we basically assume this function is hooked since it has extremely specific
# value
cd = usbdefs.USB_CONFIGURATION_DESCRIPTOR()
if ConfigDescriptor:
cd = self.mem_cast(cd, ConfigDescriptor)
dev = self.usb_devices.get(UsbDevice)
if dev:
if cd.bLength == 0:
self.mem_write(ConfigDescriptorLength, (cd.sizeof()).to_bytes(2,
'little'))
self.mem_write(ConfigDescriptor, cd.get_bytes())
dev.config_desc = self.mem_read(ConfigDescriptor, buf_len)
rv = ddk.STATUS_SUCCESS
else:
self.mem_write(ConfigDescriptorLength, (cd.sizeof()).to_bytes(2, 'little'))
return rv
@apihook('WdfUsbInterfaceSelectSetting', argc=4)
def WdfUsbInterfaceSelectSetting(self, emu, argv, ctx={}):
'''
NTSTATUS WdfUsbInterfaceSelectSetting(
WDFUSBINTERFACE UsbInterface,
PWDF_OBJECT_ATTRIBUTES PipesAttributes,
PWDF_USB_INTERFACE_SELECT_SETTING_PARAMS Params
);
'''
DriverGlobals, UsbInterface, PipesAttributes, Params = argv
rv = ddk.STATUS_INVALID_HANDLE
uif = self.usb_interfaces.get(UsbInterface)
if uif:
ifparams = self.types.WDF_USB_INTERFACE_SELECT_SETTING_PARAMS(emu.get_ptr_size())
ifparams = self.mem_cast(ifparams, Params)
enum = self.types.WdfUsbTargetDeviceSelectSettingType
if ifparams.Type == enum.WdfUsbInterfaceSelectSettingTypeSetting:
uif.setting_index = ifparams.Types.Interface.SettingIndex
rv = ddk.STATUS_SUCCESS
return rv
@apihook('WdfUsbTargetDeviceGetNumInterfaces', argc=2)
def WdfUsbTargetDeviceGetNumInterfaces(self, emu, argv, ctx={}):
'''
UCHAR WdfUsbTargetDeviceGetNumInterfaces(
WDFUSBDEVICE UsbDevice
);
'''
DriverGlobals, UsbDevice = argv
rv = 0
dev = self.usb_devices.get(UsbDevice)
if dev:
rv = dev.num_interfaces
return rv
@apihook('WdfUsbInterfaceGetNumConfiguredPipes', argc=2)
def WdfUsbInterfaceGetNumConfiguredPipes(self, emu, argv, ctx={}):
'''
BYTE WdfUsbInterfaceGetNumConfiguredPipes(
WDFUSBINTERFACE UsbInterface
);
'''
DriverGlobals, UsbInterface = argv
rv = 0
uif = self.usb_interfaces.get(UsbInterface)
if uif:
interfaces = self.parse_usb_config(uif.config_desc)
for i, eps in interfaces:
if uif.iface_index == i.bInterfaceNumber:
rv = len(eps)
break
return rv
@apihook('WdfUsbInterfaceGetNumSettings', argc=2)
def WdfUsbInterfaceGetNumSettings(self, emu, argv, ctx={}):
'''
BYTE WdfUsbInterfaceGetNumSettings(
WDFUSBINTERFACE UsbInterface
);
'''
DriverGlobals, UsbInterface = argv
rv = 0
uif = self.usb_interfaces.get(UsbInterface)
interfaces = self.parse_usb_config(uif.config_desc)
for i in interfaces:
if uif.iface_index == i[0].bInterfaceNumber:
rv += 1
return rv
@apihook('WdfUsbTargetDeviceRetrieveInformation', argc=3)
def WdfUsbTargetDeviceRetrieveInformation(self, emu, argv, ctx={}):
'''
NTSTATUS WdfUsbTargetDeviceRetrieveInformation(
WDFUSBDEVICE UsbDevice,
PWDF_USB_DEVICE_INFORMATION Information
);
'''
DriverGlobals, UsbDevice, Information = argv
rv = ddk.STATUS_INVALID_HANDLE
dev = self.usb_devices.get(UsbDevice)
if dev:
info = self.types.WDF_USB_DEVICE_INFORMATION(emu.get_ptr_size())
info = self.mem_cast(info, Information)
info.Size = info.sizeof()
info.UsbdVersionInformation.USBDI_Version = 0x600
self.mem_write(Information, info.get_bytes())
rv = ddk.STATUS_SUCCESS
return rv
@apihook('WdfUsbInterfaceGetConfiguredPipe', argc=4)
def WdfUsbInterfaceGetConfiguredPipe(self, emu, argv, ctx={}):
'''
WDFUSBPIPE WdfUsbInterfaceGetConfiguredPipe(
WDFUSBINTERFACE UsbInterface,
UCHAR PipeIndex,
PWDF_USB_PIPE_INFORMATION PipeInfo
);
'''
DriverGlobals, UsbInterface, PipeIndex, PipeInfo = argv
rv = 0
uif = self.usb_interfaces.get(UsbInterface)
interfaces = self.parse_usb_config(uif.config_desc)
if PipeInfo:
for i, eps in interfaces:
if uif.iface_index == i.bInterfaceNumber:
if PipeIndex > len(eps):
break
ep = eps[PipeIndex]
info = self.types.WDF_USB_PIPE_INFORMATION(emu.get_ptr_size())
info = self.mem_cast(info, PipeInfo)
info.Size = info.sizeof()
info.MaximumPacketSize = ep.wMaxPacketSize
info.EndpointAddress = ep.bEndpointAddress
info.Interval = ep.bInterval
if ep.bmAttributes & usbdefs.USB_ENDPOINT_TYPE_ISOCHRONOUS:
info.PipeType = wdf.WdfUsbPipeTypeIsochronous
if ep.bmAttributes & usbdefs.USB_ENDPOINT_TYPE_BULK:
info.PipeType = wdf.WdfUsbPipeTypeBulk
if ep.bmAttributes & usbdefs.USB_ENDPOINT_TYPE_INTERRUPT:
info.PipeType = wdf.WdfUsbPipeTypeInterrupt
info.PipeType = ep.bmAttributes
self.mem_write(PipeInfo, info.get_bytes())
if uif:
hnd = self.get_handle()
up = WdfUsbPipe()
up.interface = uif
up.index = PipeIndex
self.usb_pipes.update({hnd: up})
rv = hnd
return rv
@apihook('WdfUsbTargetPipeGetInformation', argc=3)
def WdfUsbTargetPipeGetInformation(self, emu, argv, ctx={}):
'''
void WdfUsbTargetPipeGetInformation(
WDFUSBPIPE Pipe,
PWDF_USB_PIPE_INFORMATION PipeInformation
);
'''
DriverGlobals, Pipe, PipeInfo = argv
_pipe = self.usb_pipes.get(Pipe)
if _pipe:
uif = _pipe.interface
interfaces = self.parse_usb_config(uif.config_desc)
if PipeInfo:
for i, eps in interfaces:
if uif.iface_index == i.bInterfaceNumber:
if _pipe.index > len(eps):
break
ep = eps[_pipe.index]
info = self.types.WDF_USB_PIPE_INFORMATION(emu.get_ptr_size())
info = self.mem_cast(info, PipeInfo)
info.Size = info.sizeof()
info.MaximumPacketSize = ep.wMaxPacketSize
info.EndpointAddress = ep.bEndpointAddress
info.Interval = ep.bInterval
if ep.bmAttributes & usbdefs.USB_ENDPOINT_TYPE_ISOCHRONOUS:
info.PipeType = wdf.WDF_USB_PIPE_TYPE.WdfUsbPipeTypeIsochronous
if ep.bmAttributes & usbdefs.USB_ENDPOINT_TYPE_BULK:
info.PipeType = wdf.WDF_USB_PIPE_TYPE.WdfUsbPipeTypeBulk
if ep.bmAttributes & usbdefs.USB_ENDPOINT_TYPE_INTERRUPT:
info.PipeType = wdf.WDF_USB_PIPE_TYPE.WdfUsbPipeTypeInterrupt
info.PipeType = ep.bmAttributes
self.mem_write(PipeInfo, info.get_bytes())
return
@apihook('WdfUsbInterfaceGetInterfaceNumber', argc=2)
def WdfUsbInterfaceGetInterfaceNumber(self, emu, argv, ctx={}):
'''
BYTE WdfUsbInterfaceGetInterfaceNumber(
WDFUSBINTERFACE UsbInterface
);
'''
DriverGlobals, UsbInterface = argv
rv = 0
uif = self.usb_interfaces.get(UsbInterface)
if uif:
rv = uif.iface_index
return rv
+913
View File
@@ -0,0 +1,913 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
import speakeasy.winenv.arch as _arch
import speakeasy.winenv.defs.registry.reg as regdefs
import speakeasy.winenv.defs.windows.windows as windefs
import speakeasy.winenv.defs.windows.kernel32 as k32
import speakeasy.winenv.defs.windows.advapi32 as adv32
import speakeasy.windows.objman as objman
from .. import api
import hashlib
class AdvApi32(api.ApiHandler):
"""
Implements exported functions from advapi32.dll
"""
name = 'advapi32'
apihook = api.ApiHandler.apihook
impdata = api.ApiHandler.impdata
def __init__(self, emu):
super(AdvApi32, self).__init__(emu)
self.funcs = {}
self.data = {}
self.hash_objects = {}
self.key_objects = {}
self.k32types = k32
self.win = adv32
self.curr_rand = 0
self.curr_handle = 0x2800
super(AdvApi32, self).__get_hook_attrs__(self)
def get_handle(self):
self.curr_handle += 4
return self.curr_handle
@apihook('RegOpenKey', argc=3, conv=_arch.CALL_CONV_STDCALL)
def RegOpenKey(self, emu, argv, ctx={}):
'''
LSTATUS RegOpenKeyA(
HKEY hKey,
LPCSTR lpSubKey,
PHKEY phkResult
);
'''
hKey, lpSubKey, phkResult = argv
rv = windefs.ERROR_SUCCESS
hnd = 0
hkey_name = regdefs.get_hkey_type(hKey)
if hkey_name:
argv[0] = hkey_name
if not hnd and not lpSubKey:
hnd = hKey
else:
key_obj = emu.regman.get_key_from_handle(hKey)
hkey_name = key_obj.path
cw = self.get_char_width(ctx)
if lpSubKey:
lpSubKey = self.read_mem_string(lpSubKey, cw)
argv[1] = lpSubKey
if hkey_name and lpSubKey:
if not lpSubKey.startswith('\\'):
lpSubKey = '\\' + lpSubKey
lpSubKey = hkey_name + lpSubKey
hnd = self.reg_open_key(lpSubKey, create=True)
if not hnd:
rv = windefs.ERROR_PATH_NOT_FOUND
self.log_registry_access(lpSubKey, 'open_key', handle=hnd)
if phkResult and hnd:
self.mem_write(phkResult, hnd.to_bytes(self.get_ptr_size(), 'little'))
return rv
@apihook('RegOpenKeyEx', argc=5, conv=_arch.CALL_CONV_STDCALL)
def RegOpenKeyEx(self, emu, argv, ctx={}):
'''
LSTATUS RegOpenKeyEx(
HKEY hKey,
LPTSTR lpSubKey,
DWORD ulOptions,
REGSAM samDesired,
PHKEY phkResult
);
'''
hKey, lpSubKey, ulOptions, samDesired, phkResult = argv
rv = windefs.ERROR_SUCCESS
hnd = 0
hkey_name = regdefs.get_hkey_type(hKey)
if hkey_name:
argv[0] = hkey_name
if not hnd and not lpSubKey:
hnd = hKey
cw = self.get_char_width(ctx)
if lpSubKey:
lpSubKey = self.read_mem_string(lpSubKey, cw)
argv[1] = lpSubKey
if hkey_name and lpSubKey:
if not lpSubKey.startswith('\\'):
lpSubKey = '\\' + lpSubKey
lpSubKey = hkey_name + lpSubKey
hnd = self.reg_open_key(lpSubKey, create=True)
if not hnd:
rv = windefs.ERROR_PATH_NOT_FOUND
self.log_registry_access(lpSubKey, 'open_key', handle=hnd)
if phkResult and hnd:
self.mem_write(phkResult, hnd.to_bytes(self.get_ptr_size(), 'little'))
return rv
@apihook('RegQueryValueEx', argc=6, conv=_arch.CALL_CONV_STDCALL)
def RegQueryValueEx(self, emu, argv, ctx={}):
'''
LSTATUS RegQueryValueEx(
HKEY hKey,
LPTSTR lpValueName,
LPDWORD lpReserved,
LPDWORD lpType,
LPBYTE lpData,
LPDWORD lpcbData
);
'''
hKey, lpValueName, lpReserved, lpType, lpData, lpcbData = argv
rv = windefs.ERROR_SUCCESS
cw = self.get_char_width(ctx)
if lpValueName:
lpValueName = self.read_mem_string(lpValueName, cw)
argv[1] = lpValueName
type_name = regdefs.get_value_type(lpType)
if type_name:
argv[3] = type_name
length = 0
if lpcbData:
length = self.mem_read(lpcbData, 4)
length = int.from_bytes(length, 'little')
argv[5] = length
key = self.reg_get_key(hKey)
if key:
val = key.get_value(lpValueName)
if val:
output = b''
if lpcbData:
self.mem_write(lpcbData, len(output).to_bytes(4, 'little'))
if len(output) > length:
rv = windefs.ERROR_INSUFFICIENT_BUFFER
else:
if lpData:
self.mem_write(lpData, output)
# For now, return an empty buffer
else:
output = b'\x00' * length
if lpData:
try:
self.mem_write(lpData, output)
except Exception:
return windefs.ERROR_INVALID_PARAMETER
if lpcbData:
self.mem_write(lpcbData, len(output).to_bytes(4, 'little'))
rv = windefs.ERROR_SUCCESS
kp = key.get_path()
self.log_registry_access(kp, 'read_value', value_name=lpValueName, size=length,
buffer=lpData)
return rv
@apihook('RegCloseKey', argc=1, conv=_arch.CALL_CONV_STDCALL)
def RegCloseKey(self, emu, argv, ctx={}):
'''
LSTATUS RegCloseKey(
HKEY hKey
);
'''
hKey, = argv
rv = windefs.ERROR_SUCCESS
key = self.reg_get_key(hKey)
if not key:
rv = windefs.ERROR_INVALID_HANDLE
return rv
@apihook('RegEnumKey', argc=4, conv=_arch.CALL_CONV_STDCALL)
def RegEnumKey(self, emu, argv, ctx={}):
'''
LSTATUS RegEnumKey(
HKEY hKey,
DWORD dwIndex,
LPTSTR lpName,
DWORD cchName
);
'''
hKey, dwIndex, lpName, cchName = argv
cw = self.get_char_width(ctx)
rv = windefs.ERROR_INVALID_HANDLE
if hKey:
key = self.reg_get_key(hKey)
argv[0] = key.get_path()
if not key:
rv = windefs.ERROR_INVALID_HANDLE
else:
subkeys = self.reg_get_subkeys(key)
if (dwIndex + 1) > len(subkeys):
rv = windefs.ERROR_NO_MORE_ITEMS
else:
if lpName:
sk = subkeys[dwIndex]
name = sk.get_path()
if cw == 2:
name = name.encode('utf-16le')
else:
name = name.encode('utf-8')
self.mem_write(lpName, name)
rv = windefs.ERROR_SUCCESS
return rv
@apihook('RegQueryInfoKey', argc=12, conv=_arch.CALL_CONV_STDCALL)
def RegQueryInfoKey(self, emu, argv, ctx={}):
# TODO: stub
'''
LSTATUS RegQueryInfoKeyA(
HKEY hKey,
LPSTR lpClass,
LPDWORD lpcchClass,
LPDWORD lpReserved,
LPDWORD lpcSubKeys,
LPDWORD lpcbMaxSubKeyLen,
LPDWORD lpcbMaxClassLen,
LPDWORD lpcValues,
LPDWORD lpcbMaxValueNameLen,
LPDWORD lpcbMaxValueLen,
LPDWORD lpcbSecurityDescriptor,
PFILETIME lpftLastWriteTime
);
'''
hKey, lpClass, lpcchClass, _, subkeys, max_subkey_len, max_class_len, \
values, max_value_name_len, max_value_len, sec_desc, last_write = argv
rv = windefs.ERROR_SUCCESS
hkey_name = regdefs.get_hkey_type(hKey)
if hkey_name:
argv[0] = hkey_name
key = self.reg_get_key(hKey)
if not key:
rv = windefs.ERROR_INVALID_HANDLE
return rv
@apihook('OpenProcessToken', argc=3, conv=_arch.CALL_CONV_STDCALL)
def OpenProcessToken(self, emu, argv, ctx={}):
'''
BOOL OpenProcessToken(
HANDLE ProcessHandle,
DWORD DesiredAccess,
PHANDLE pTokenHandle
);
'''
hProcess, DesiredAccess, pTokenHandle = argv
rv = 0
if hProcess == self.get_max_int():
obj = emu.get_current_process()
else:
obj = self.get_object_from_handle(hProcess)
if obj:
token = obj.get_token()
hToken = token.get_handle()
if pTokenHandle:
hnd = (hToken).to_bytes(self.get_ptr_size(), 'little')
self.mem_write(pTokenHandle, hnd)
rv = 1
emu.set_last_error(windefs.ERROR_SUCCESS)
else:
emu.set_last_error(windefs.ERROR_INVALID_PARAMETER)
return rv
@apihook('OpenThreadToken', argc=4, conv=_arch.CALL_CONV_STDCALL)
def OpenThreadToken(self, emu, argv, ctx={}):
'''
BOOL OpenThreadToken(
HANDLE ThreadHandle,
DWORD DesiredAccess,
BOOL OpenAsSelf,
PHANDLE TokenHandle
);
'''
ThreadHandle, DesiredAccess, OpenAsSelf, pTokenHandle = argv
rv = 0
if ThreadHandle == self.get_max_int():
obj = emu.get_current_thread()
else:
obj = self.get_object_from_handle(ThreadHandle)
if obj:
token = obj.get_token()
hToken = token.get_handle()
if pTokenHandle:
hnd = (hToken).to_bytes(self.get_ptr_size(), 'little')
self.mem_write(pTokenHandle, hnd)
rv = 1
emu.set_last_error(windefs.ERROR_SUCCESS)
else:
emu.set_last_error(windefs.ERROR_INVALID_PARAMETER)
return rv
@apihook('DuplicateTokenEx', argc=6, conv=_arch.CALL_CONV_STDCALL)
def DuplicateTokenEx(self, emu, argv, ctx={}):
'''
BOOL DuplicateTokenEx(
HANDLE hExistingToken,
DWORD dwDesiredAccess,
LPSECURITY_ATTRIBUTES lpTokenAttributes,
SECURITY_IMPERSONATION_LEVEL ImpersonationLevel,
TOKEN_TYPE TokenType,
PHANDLE phNewToken
);
'''
(hExistingToken, access, token_attrs, imp_level, toktype,
phNewToken) = argv
rv = 0
obj = self.get_object_from_handle(hExistingToken)
if obj:
new_token = emu.new_object(objman.Token)
hnd_new_token = new_token.get_handle()
if phNewToken:
hnd = (hnd_new_token).to_bytes(self.get_ptr_size(), 'little')
self.mem_write(phNewToken, hnd)
rv = 1
emu.set_last_error(windefs.ERROR_SUCCESS)
else:
emu.set_last_error(windefs.ERROR_INVALID_PARAMETER)
return rv
@apihook('SetTokenInformation', argc=4, conv=_arch.CALL_CONV_STDCALL)
def SetTokenInformation(self, emu, argv, ctx={}):
'''
BOOL SetTokenInformation(
HANDLE TokenHandle,
TOKEN_INFORMATION_CLASS TokenInformationClass,
LPVOID TokenInformation,
DWORD TokenInformationLength
);
'''
handle, info_class, info, info_len = argv
rv = 1
return rv
@apihook('StartServiceCtrlDispatcher', argc=1)
def StartServiceCtrlDispatcher(self, emu, argv, ctx={}):
'''
BOOL StartServiceCtrlDispatcher(
const SERVICE_TABLE_ENTRY *lpServiceStartTable
);
'''
lpServiceStartTable, = argv
cw = self.get_char_width(ctx)
ste = self.win.SERVICE_TABLE_ENTRY(emu.get_ptr_size())
entry = self.mem_cast(ste, lpServiceStartTable)
# Get the service name
name = self.read_mem_string(entry.lpServiceName, cw) # noqa
rv = True
emu.set_last_error(windefs.ERROR_SUCCESS)
return rv
@apihook('OpenSCManager', argc=3)
def OpenSCManager(self, emu, argv, ctx={}):
'''
SC_HANDLE OpenSCManager(
LPCSTR lpMachineName,
LPCSTR lpDatabaseName,
DWORD dwDesiredAccess
);
'''
lpMachineName, lpDatabaseName, dwDesiredAccess = argv
hScm = self.mem_alloc(size=8)
emu.set_last_error(windefs.ERROR_SUCCESS)
return hScm
@apihook('CreateService', argc=13)
def CreateService(self, emu, argv, ctx={}):
'''
SC_HANDLE CreateServiceA(
SC_HANDLE hSCManager,
LPCSTR lpServiceName,
LPCSTR lpDisplayName,
DWORD dwDesiredAccess,
DWORD dwServiceType,
DWORD dwStartType,
DWORD dwErrorControl,
LPCSTR lpBinaryPathName,
LPCSTR lpLoadOrderGroup,
LPDWORD lpdwTagId,
LPCSTR lpDependencies,
LPCSTR lpServiceStartName,
LPCSTR lpPassword
);
'''
(hScm, svc_name, disp_name, access,
svc_type, start_type, error_ctrl, bin_path,
load_group, tag_id, deps, svc_start_name,
password) = argv
cw = self.get_char_width(ctx)
if svc_name:
_sname = self.read_mem_string(svc_name, cw)
argv[1] = _sname
if disp_name:
_dname = self.read_mem_string(disp_name, cw)
argv[2] = _dname
if bin_path:
_bpname = self.read_mem_string(bin_path, cw)
argv[7] = _bpname
hSvc = self.mem_alloc(size=8)
emu.set_last_error(windefs.ERROR_SUCCESS)
return hSvc
@apihook('StartService', argc=3)
def StartService(self, emu, argv, ctx={}):
'''
BOOL StartService(
SC_HANDLE hService,
DWORD dwNumServiceArgs,
LPCSTR *lpServiceArgVectors
);
'''
hService, dwNumServiceArgs, lpServiceArgVectors = argv
rv = 1
emu.set_last_error(windefs.ERROR_SUCCESS)
return rv
@apihook('CloseServiceHandle', argc=1)
def CloseServiceHandle(self, emu, argv, ctx={}):
'''
BOOL CloseServiceHandle(
SC_HANDLE hSCObject
);
'''
CloseServiceHandle, = argv
self.mem_free(CloseServiceHandle)
rv = 1
emu.set_last_error(windefs.ERROR_SUCCESS)
return rv
@apihook('ChangeServiceConfig2', argc=3)
def ChangeServiceConfig2(self, emu, argv, ctx={}):
'''
BOOL ChangeServiceConfig2(
SC_HANDLE hService,
DWORD dwInfoLevel,
LPVOID lpInfo
);
'''
hService, dwInfoLevel, lpInfo = argv
rv = 1
emu.set_last_error(windefs.ERROR_SUCCESS)
return rv
@apihook('CryptAcquireContext', argc=5)
def CryptAcquireContext(self, emu, argv, ctx={}):
'''
BOOL CryptAcquireContext(
HCRYPTPROV *phProv,
LPCSTR szContainer,
LPCSTR szProvider,
DWORD dwProvType,
DWORD dwFlags
);
'''
phProv, szContainer, szProvider, dwProvType, dwFlags = argv
cont_str, prov_str = '', ''
cw = self.get_char_width(ctx)
rv = False
if szContainer:
cont_str = self.read_mem_string(szContainer, cw)
argv[1] = cont_str
if szProvider:
prov_str = self.read_mem_string(szProvider, cw)
argv[2] = prov_str
cm = emu.get_crypt_manager()
hnd = cm.crypt_open(cont_str, prov_str, dwProvType, dwFlags)
if hnd and phProv:
self.mem_write(phProv, hnd.to_bytes(emu.get_ptr_size(), 'little'))
rv = True
emu.set_last_error(windefs.ERROR_SUCCESS)
return rv
@apihook('CryptGenRandom', argc=3)
def CryptGenRandom(self, emu, argv, ctx={}):
'''
BOOL CryptGenRandom(
HCRYPTPROV hProv,
DWORD dwLen,
BYTE *pbBuffer
);
'''
hProv, dwLen, pbBuffer = argv
rv = False
if pbBuffer:
out = b'A' * dwLen
self.mem_write(pbBuffer, out)
rv = True
return rv
@apihook('AllocateAndInitializeSid', argc=11)
def AllocateAndInitializeSid(self, emu, argv, ctx={}):
'''
BOOL AllocateAndInitializeSid(
PSID_IDENTIFIER_AUTHORITY pIdentifierAuthority,
BYTE nSubAuthorityCount,
DWORD nSubAuthority0,
DWORD nSubAuthority1,
DWORD nSubAuthority2,
DWORD nSubAuthority3,
DWORD nSubAuthority4,
DWORD nSubAuthority5,
DWORD nSubAuthority6,
DWORD nSubAuthority7,
PSID *pSid
);
'''
auth, count, sa0, sa1, sa2, sa3, sa4, sa5, sa6, sa7, pSid = argv
rv = False
if pSid:
sid = self.mem_alloc(0x100, tag='api.struct.SID')
self.mem_write(pSid, sid.to_bytes(emu.get_ptr_size(), 'little'))
rv = True
return rv
@apihook('CheckTokenMembership', argc=3)
def CheckTokenMembership(self, emu, argv, ctx={}):
'''
BOOL CheckTokenMembership(
HANDLE TokenHandle,
PSID SidToCheck,
PBOOL IsMember
);
'''
TokenHandle, SidToCheck, IsMember = argv
rv = False
if IsMember:
self.mem_write(IsMember, (1).to_bytes(4, 'little'))
rv = True
return rv
@apihook('FreeSid', argc=1)
def FreeSid(self, emu, argv, ctx={}):
'''
PVOID FreeSid(
PSID pSid
);
'''
pSid, = argv
rv = pSid
if pSid:
self.mem_free(pSid)
rv = 0
return rv
@apihook('CryptReleaseContext', argc=2)
def CryptReleaseContext(self, emu, argv, ctx={}):
'''
BOOL CryptReleaseContext(
HCRYPTPROV hProv,
DWORD dwFlags
);
'''
hProv, dwFlags = argv
rv = True
cm = emu.get_crypt_manager()
cm.crypt_close(hProv)
return rv
@apihook('GetUserName', argc=2)
def GetUserName(self, emu, argv, ctx={}):
'''
BOOL GetUserName(
LPSTR lpBuffer,
LPDWORD pcbBuffer
);
'''
lpBuffer, pcbBuffer = argv
rv = False
cw = self.get_char_width(ctx)
user = emu.get_user()
user_name = user.get('name')
if lpBuffer:
if cw == 2:
out = user_name.encode('utf-16le')
elif cw == 1:
out = user_name.encode('utf-8')
self.mem_write(lpBuffer, out)
rv = True
if pcbBuffer:
self.mem_write(pcbBuffer, (len(user_name)).to_bytes(4, 'little'))
return rv
@apihook('LookupPrivilegeValue', argc=3)
def LookupPrivilegeValue(self, emu, argv, ctx={}):
'''
BOOL LookupPrivilegeValue(
LPCSTR lpSystemName,
LPCSTR lpName,
PLUID lpLuid
);
'''
sysname, name, luid = argv
rv = False
cw = self.get_char_width(ctx)
if sysname:
sysname = self.read_mem_string(sysname, cw)
argv[0] = sysname
if name:
name = self.read_mem_string(name, cw)
argv[1] = name
rv = True
return rv
@apihook('AdjustTokenPrivileges', argc=6)
def AdjustTokenPrivileges(self, emu, argv, ctx={}):
'''
BOOL AdjustTokenPrivileges(
HANDLE TokenHandle,
BOOL DisableAllPrivileges,
PTOKEN_PRIVILEGES NewState,
DWORD BufferLength,
PTOKEN_PRIVILEGES PreviousState,
PDWORD ReturnLength
);
'''
rv = True
return rv
@apihook('GetTokenInformation', argc=5)
def GetTokenInformation(self, emu, argv, ctx={}):
'''
BOOL GetTokenInformation(
HANDLE TokenHandle,
TOKEN_INFORMATION_CLASS TokenInformationClass,
LPVOID TokenInformation,
DWORD TokenInformationLength,
PDWORD ReturnLength
);
'''
hnd, info_class, info, info_len, ret_len = argv
rv = True
if not info_len:
rv = False
emu.set_last_error(windefs.ERROR_INSUFFICIENT_BUFFER)
if ret_len:
self.mem_write(ret_len, (4).to_bytes(4, 'little'))
return rv
@apihook('CreateProcessAsUser', argc=11, conv=_arch.CALL_CONV_STDCALL)
def CreateProcessAsUser(self, emu, argv, ctx={}):
'''
BOOL CreateProcessAsUser(
HANDLE hToken,
LPCSTR lpApplicationName,
LPSTR lpCommandLine,
LPSECURITY_ATTRIBUTES lpProcessAttributes,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
BOOL bInheritHandles,
DWORD dwCreationFlags,
LPVOID lpEnvironment,
LPCSTR lpCurrentDirectory,
LPSTARTUPINFOA lpStartupInfo,
LPPROCESS_INFORMATION lpProcessInformation
);
'''
token, app, cmd, pa, ta, inherit, flags, env, cd, si, ppi = argv
cw = self.get_char_width(ctx)
cmdstr = ''
appstr = ''
if app:
appstr = self.read_mem_string(app, cw)
argv[1] = appstr
if cmd:
cmdstr = self.read_mem_string(cmd, cw)
if not appstr:
appstr = cmdstr.split(' ')[0]
argv[2] = cmdstr
proc = emu.create_process(path=appstr, cmdline=cmdstr)
proc_hnd = self.get_object_handle(proc)
thread = proc.threads[0]
thread_hnd = self.get_object_handle(thread)
_pi = self.k32types.PROCESS_INFORMATION(emu.get_ptr_size())
data = self.mem_cast(_pi, ppi)
_pi.hProcess = proc_hnd
_pi.hThread = thread_hnd
_pi.dwProcessId = proc.pid
_pi.dwThreadId = thread.tid
self.mem_write(ppi, self.get_bytes(data))
rv = 1
self.log_process_event(proc, 'create')
return rv
@apihook('CryptCreateHash', argc=5)
def CryptCreateHash(self, emu, argv, ctx={}):
'''
BOOL CryptCreateHash(
HCRYPTPROV hProv,
ALG_ID Algid,
HCRYPTKEY hKey,
DWORD dwFlags,
HCRYPTHASH *phHash
);
'''
hash_algs = {
0x00008004: ('CALG_SHA1', hashlib.sha1),
0x0000800c: ('CALG_SHA_256', hashlib.sha256),
0x0000800d: ('CALG_SHA_384', hashlib.sha384),
0x0000800e: ('CALG_SHA_512', hashlib.sha512),
0x00008003: ('CALG_MD5', hashlib.md5)
}
hProv, Algid, hKey, dwFlags, phHash = argv
argv[1] = hash_algs.get(Algid, Algid)
if hKey != 0:
return 0
if Algid not in hash_algs:
emu.set_last_error(adv32.NTE_BAD_ALGID)
return 0
hnd = self.get_handle()
self.hash_objects.update({hnd: hash_algs[Algid][1]()})
self.mem_write(phHash, hnd.to_bytes(self.get_ptr_size(), 'little'))
return 1
@apihook('CryptHashData', argc=4)
def CryptHashData(self, emu, argv, ctx={}):
'''
BOOL CryptHashData(
HCRYPTHASH hHash,
const BYTE *pbData,
DWORD dwDataLen,
DWORD dwFlags
);
'''
hHash, pbData, dwDataLen, dwFlags = argv
hnd = self.hash_objects.get(hHash, None)
if hnd is None:
emu.set_last_error(windefs.ERROR_INVALID_HANDLE)
return 0
if dwDataLen <= 0:
return 0
data = self.mem_read(pbData, dwDataLen)
hnd.update(data)
return 1
@apihook('RegGetValueW', argc=7, conv=_arch.CALL_CONV_STDCALL)
def RegGetValueW(self, emu, argv, ctx={}):
'''
LSTATUS RegGetValueW(
HKEY hkey,
LPCWSTR lpSubKey,
LPCWSTR lpValue,
DWORD dwFlags,
LPDWORD pdwType,
PVOID pvData,
LPDWORD pcbData
);
'''
hKey, lpSubKey, lpValue, dwFlags, lpType, lpData, lpcbData = argv
rv = windefs.ERROR_SUCCESS
cw = self.get_char_width(ctx)
if lpSubKey:
lpSubKey = self.read_mem_string(lpSubKey, cw)
argv[1] = lpSubKey
if lpValue:
lpValue = self.read_mem_string(lpValue, cw)
argv[2] = lpValue
type_name = regdefs.get_value_type(lpType)
if type_name:
argv[4] = type_name
length = 0
if lpcbData:
length = self.mem_read(lpcbData, 4)
length = int.from_bytes(length, 'little')
key = self.reg_get_key(hKey)
if key:
val = key.get_value(lpValue)
if val:
output = b''
if lpcbData:
self.mem_write(lpcbData, len(output).to_bytes(4, 'little'))
if len(output) > length:
rv = windefs.ERROR_INSUFFICIENT_BUFFER
else:
self.mem_write(lpData, output)
# For now, return an empty buffer
else:
output = b'\x00' * length
self.mem_write(lpData, output)
rv = windefs.ERROR_SUCCESS
kp = key.get_path()
self.log_registry_access(kp, 'read_value', value_name=lpValue, size=length,
buffer=lpData)
return rv
+29
View File
@@ -0,0 +1,29 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
from .. import api
class Advpack(api.ApiHandler):
"""
Emulates functions from advpack.dll
"""
name = 'advpack'
apihook = api.ApiHandler.apihook
impdata = api.ApiHandler.impdata
def __init__(self, emu):
super(Advpack, self).__init__(emu)
self.funcs = {}
self.data = {}
super(Advpack, self).__get_hook_attrs__(self)
@apihook('IsNTAdmin', argc=0)
def IsNTAdmin(self, emu, argv, ctx={}):
"""
bool IsNTAdmin();
"""
return emu.get_user().get('is_admin', False)
+83
View File
@@ -0,0 +1,83 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
import base64
from .. import api
ERROR_MORE_DATA = 234
CRYPT_STRING_BASE64 = 1
class Crypt32(api.ApiHandler):
"""
Implements exported functions from user32.dll
"""
name = 'crypt32'
apihook = api.ApiHandler.apihook
impdata = api.ApiHandler.impdata
def __init__(self, emu):
super(Crypt32, self).__init__(emu)
self.funcs = {}
self.data = {}
super(Crypt32, self).__get_hook_attrs__(self)
@apihook('CryptStringToBinary', argc=7)
def CryptStringToBinary(self, emu, argv, ctx={}):
'''
BOOL CryptStringToBinaryA(
LPCSTR pszString,
DWORD cchString,
DWORD dwFlags,
BYTE *pbBinary,
DWORD *pcbBinary,
DWORD *pdwSkip,
DWORD *pdwFlags
);
'''
cw = self.get_char_width(ctx)
pszString, cchString, dwFlags, pbBinary, pcbBinary, pdwSkip, pdwFlags = argv
if cchString:
s = self.mem_read(pszString, cchString * cw)
else:
s = self.read_mem_string(pszString, cw)
argv[0] = s
if dwFlags != CRYPT_STRING_BASE64:
# self.logger.info("%s: currently unsupported flags (%08X)" % (api_name, dwFlags))
return 1
if type(s) != str:
s = s.decode('utf8')
try:
decoded = base64.b64decode(s)
except Exception:
return 0
cbBinary = int.from_bytes(self.mem_read(pcbBinary, 4), 'little')
out_len = len(decoded)
if pbBinary == 0:
return out_len
if out_len > cbBinary:
emu.set_last_error(ERROR_MORE_DATA)
return 0
self.mem_write(pbBinary, decoded)
self.mem_write(pcbBinary, out_len.to_bytes(4, 'little'))
if pdwSkip:
self.mem_write(pdwSkip, b"\x00" * 4)
return 1
+95
View File
@@ -0,0 +1,95 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
import ctypes as ct
from speakeasy.struct import EmuStruct, Ptr
import speakeasy.winenv.defs.windows.windows as windefs
from .. import api
DNS_TYPE_TEXT = 0x0010
class _DnsRecord(EmuStruct):
def __init__(self, ptr_size):
super().__init__(ptr_size)
self.pNext = Ptr
self.pName = Ptr
self.wType = ct.c_uint16
self.wDataLength = ct.c_uint16
self.Flags = ct.c_uint32
self.dwTtl = ct.c_uint32
self.dwReserved = ct.c_uint32
class DNS_TXT_DATA(EmuStruct):
def __init__(self, ptr_size):
super().__init__(ptr_size)
self.dwStringCount = Ptr
self.pStringArray = Ptr
class DnsApi(api.ApiHandler):
name = 'dnsapi'
apihook = api.ApiHandler.apihook
impdata = api.ApiHandler.impdata
def __init__(self, emu):
super(DnsApi, self).__init__(emu)
self.funcs = {}
self.data = {}
super(DnsApi, self).__get_hook_attrs__(self)
self.netman = emu.get_network_manager()
self.names = {}
@apihook('DnsQuery_', argc=6)
def DnsQuery_(self, emu, argv, ctx={}):
"""
DNS_STATUS DnsQuery_A(
PCSTR pszName,
WORD wType,
DWORD Options,
PVOID pExtra,
PDNS_RECORD *ppQueryResults,
PVOID *pReserved
);
"""
pszName, wType, Options, pExtra, ppQueryResults, pReserved = argv
rv = windefs.ERROR_INVALID_PARAMETER
cw = self.get_char_width(ctx)
if pszName:
name = self.read_mem_string(pszName, cw)
ip = self.netman.name_lookup(name)
self.log_dns(name, ip)
rec = _DnsRecord(emu.get_ptr_size())
rec.pName = pszName
rec.wType = wType
if wType == DNS_TYPE_TEXT:
argv[1] = 'DNS_TYPE_TEXT'
text = self.netman.get_dns_txt(name)
if not text:
text = b'\x00'*12
ts = DNS_TXT_DATA(emu.get_ptr_size())
size = len(text) + ts.sizeof()
rr = self.mem_alloc(rec.sizeof() + size, tag='api.DnsQuery._DnsRecord')
ts.dwStringCount = 1
ts.pStringArray = rr + rec.sizeof() + ts.sizeof()
rec.wDataLength = size
self.mem_write(rr, rec.get_bytes() + ts.get_bytes())
self.mem_write(ts.pStringArray, text)
if ppQueryResults and rr:
out = rr.to_bytes(self.get_ptr_size(), 'little')
self.mem_write(ppQueryResults, out)
rv = windefs.ERROR_SUCCESS
return rv
+64
View File
@@ -0,0 +1,64 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
from .. import api
class GDI32(api.ApiHandler):
"""
Implements exported functions from gdi32.dll
"""
name = 'gdi32'
apihook = api.ApiHandler.apihook
impdata = api.ApiHandler.impdata
def __init__(self, emu):
super(GDI32, self).__init__(emu)
self.funcs = {}
self.data = {}
self.handle = 0
super(GDI32, self).__get_hook_attrs__(self)
def get_handle(self):
self.handle += 4
hnd = self.handle
return hnd
@apihook('CreateBitmap', argc=5)
def CreateBitmap(self, emu, argv, ctx={}):
'''
HBITMAP CreateBitmap(
int nWidth,
int nHeight,
UINT nPlanes,
UINT nBitCount,
const VOID *lpBits
);
'''
return self.get_handle()
@apihook('MoveToEx', argc=1)
def MoveToEx(self, emu, argv, ctx={}):
"""
BOOL MoveToEx(
HDC hdc,
int x,
int y,
LPPOINT lppt
);
"""
return 1
@apihook('LineTo', argc=1)
def LineTo(self, emu, argv, ctx={}):
"""
BOOL LineTo(
HDC hdc,
int x,
int y
)
"""
return 1
File diff suppressed because it is too large Load Diff
+33
View File
@@ -0,0 +1,33 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
from .. import api
class Mscoree(api.ApiHandler):
"""
Implements exported functions from mscoree.dll.
"""
name = 'mscoree'
apihook = api.ApiHandler.apihook
impdata = api.ApiHandler.impdata
def __init__(self, emu):
super(Mscoree, self).__init__(emu)
self.funcs = {}
self.data = {}
super(Mscoree, self).__get_hook_attrs__(self)
@apihook('CorExitProcess', argc=1)
def CorExitProcess(self, emu, argv, ctx={}):
'''
void STDMETHODCALLTYPE CorExitProcess (
int exitCode
);
'''
return 0
+734
View File
@@ -0,0 +1,734 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
import math
import struct
import speakeasy.winenv.arch as e_arch
import speakeasy.winenv.defs.windows.windows as windef
from .. import api
EINVAL = 22
_TRUNCATE = 0xFFFFFFFF
TIME_BASE = 1576292568
RAND_BASE = 0
class Msvcrt(api.ApiHandler):
"""
Implements functions from various versions of the C runtime on Windows
"""
name = 'msvcrt'
apihook = api.ApiHandler.apihook
impdata = api.ApiHandler.impdata
def __init__(self, emu):
super(Msvcrt, self).__init__(emu)
self.stdin = 0
self.stdout = 1
self.stderr = 2
self.rand_int = RAND_BASE
self.funcs = {}
self.data = {}
self.wintypes = windef
super(Msvcrt, self).__get_hook_attrs__(self)
def hex_to_double(self, x):
x = x.to_bytes(8, 'little')
x = struct.unpack('d', x)[0]
return x
def double_to_hex(self, x):
return struct.unpack('<Q', struct.pack('<d', x))[0]
@impdata('_acmdln')
def _acmdln(self, ptr=0):
"""Command line global CRT variable"""
cmdln = ptr
_argv = self.emu.get_argv()
_argv = " ".join(_argv).encode('utf-8')
ptr_size = self.emu.get_ptr_size()
if not ptr:
cmdln = self.mem_alloc(len(_argv) + ptr_size,
base=None, tag='api.msvcrt._acmdln')
p_cmdln = cmdln + ptr_size
self.emu.mem_write(cmdln, p_cmdln.to_bytes(ptr_size, 'little'))
self.emu.mem_write(p_cmdln, _argv)
return cmdln
# Reference: https://wiki.osdev.org/Visual_C%2B%2B_Runtime
@apihook('_initterm_e', argc=2, conv=e_arch.CALL_CONV_CDECL)
def _initterm_e(self, emu, argv, ctx={}):
"""
static int _initterm_e(_PIFV * pfbegin,
_PIFV * pfend)
"""
pfbegin, pfend = argv
rv = 0
return rv
@apihook('_initterm', argc=2, conv=e_arch.CALL_CONV_CDECL)
def _initterm(self, emu, argv, ctx={}):
"""static void _initterm (_PVFV * pfbegin, _PVFV * pfend)"""
pfbegin, pfend = argv
rv = 0
return rv
@apihook('__getmainargs', argc=5)
def __getmainargs(self, emu, argv, ctx={}):
"""
int __getmainargs(
int * _Argc,
char *** _Argv,
char *** _Env,
int _DoWildCard,
_startupinfo * _StartInfo);
"""
_Argc, _Argv, _Env, _DoWildCard, _StartInfo = argv
rv = 0
return rv
@apihook('__p___wargv', argc=0, conv=e_arch.CALL_CONV_CDECL)
def __p___wargv(self, emu, argv, ctx={}):
"""WCHAR *** __p___wargv ()"""
ptr_size = self.get_ptr_size()
_argv = emu.get_argv()
argv = [(a + '\x00\x00\x00\x00').encode('utf-16le') for a in _argv]
array_size = (ptr_size * (len(argv) + 2))
total = sum([len(a) for a in argv])
total += array_size
sptr = 0
pptr = 0
arg_mem = self.mem_alloc(size=total, tag='api.argv')
pptr = arg_mem + ptr_size
self.mem_write(arg_mem, pptr.to_bytes(ptr_size, 'little'))
sptr = pptr + array_size
for a in argv:
self.mem_write(pptr, sptr.to_bytes(ptr_size, 'little'))
pptr += ptr_size
self.mem_write(sptr, a)
sptr += len(a)
self.mem_write(pptr, b'\x00' * ptr_size)
rv = arg_mem
# TODO: dispatch the VFV function array
return rv
@apihook('__p___argv', argc=0, conv=e_arch.CALL_CONV_CDECL)
def __p___argv(self, emu, argv, ctx={}):
"""char *** __p___argv ()"""
ptr_size = self.get_ptr_size()
_argv = emu.get_argv()
argv = [(a + '\x00\x00\x00\x00').encode('utf-8') for a in _argv]
array_size = (ptr_size * (len(argv) + 1))
total = sum([len(a) for a in argv])
total += array_size
sptr = 0
pptr = 0
arg_mem = self.mem_alloc(size=total, tag='api.argv')
pptr = arg_mem + ptr_size
self.mem_write(arg_mem, pptr.to_bytes(ptr_size, 'little'))
sptr = pptr + array_size
for a in argv:
self.mem_write(pptr, sptr.to_bytes(ptr_size, 'little'))
pptr += ptr_size
self.mem_write(sptr, a)
sptr += len(a)
self.mem_write(pptr, b'\x00' * ptr_size)
rv = arg_mem
return rv
@apihook('__p___argc', argc=0, conv=e_arch.CALL_CONV_CDECL)
def __p___argc(self, emu, argv, ctx={}):
"""int * __p___argc ()"""
_argv = emu.get_argv()
argc = self.mem_alloc(size=4, tag='api.argc')
self.mem_write(argc, len(_argv).to_bytes(4, 'little'))
return argc
@apihook('_get_initial_narrow_environment', argc=0,
conv=e_arch.CALL_CONV_CDECL)
def _get_initial_narrow_environment(self, emu, argv, ctx={}):
"""char** _get_initial_narrow_environment ()"""
ptr_size = self.get_ptr_size()
env = emu.get_env()
total = ptr_size
sptr = total
pptr = 0
fmt_env = []
for k, v in env.items():
envstr = '%s=%s\x00' % (k, v)
envstr = envstr.encode('utf-8')
total += len(envstr)
fmt_env.append(envstr)
total += ptr_size
sptr += ptr_size
envp = self.mem_alloc(size=total, tag='api.envp')
pptr = envp
sptr += envp
for v in fmt_env:
self.mem_write(pptr, sptr.to_bytes(ptr_size, 'little'))
pptr += ptr_size
self.mem_write(sptr, v)
sptr += len(v)
return envp
@apihook('_get_initial_wide_environment', argc=0,
conv=e_arch.CALL_CONV_CDECL)
def _get_initial_wide_environment(self, emu, argv, ctx={}):
"""WCHAR** _get_initial_wide_environment ()"""
ptr_size = self.get_ptr_size()
env = emu.get_env()
total = ptr_size
sptr = total
pptr = 0
fmt_env = []
for k, v in env.items():
envstr = '%s=%s\x00' % (k, v)
envstr = envstr.encode('utf-16le')
total += len(envstr)
fmt_env.append(envstr)
total += ptr_size
sptr += ptr_size
envp = self.mem_alloc(size=total, tag='api.envp')
pptr = envp
sptr += envp
for v in fmt_env:
self.mem_write(pptr, sptr.to_bytes(ptr_size, 'little'))
pptr += ptr_size
self.mem_write(sptr, v)
sptr += len(v)
return envp
@apihook('exit', argc=1, conv=e_arch.CALL_CONV_CDECL)
def exit(self, emu, argv, ctx={}):
"""
void exit(
int const status
);
"""
self.exit_process()
@apihook('_exit', argc=1, conv=e_arch.CALL_CONV_CDECL)
def _exit(self, emu, argv, ctx={}):
"""
void _exit(
int const status
);
"""
self.exit_process()
@apihook('__acrt_iob_func', argc=1, conv=e_arch.CALL_CONV_CDECL)
def __acrt_iob_func(self, emu, argv, ctx={}):
"""FILE * __acrt_iob_func (fd)"""
fd, = argv
return fd
@apihook('pow', argc=2, conv=e_arch.CALL_CONV_FLOAT)
def pow(self, emu, argv, ctx={}):
"""
double pow(
double x,
double y
);
"""
x, y = argv
x = self.hex_to_double(x)
y = self.hex_to_double(y)
z = pow(x, y)
z = self.double_to_hex(z)
return z
@apihook('floor', argc=1, conv=e_arch.CALL_CONV_FLOAT)
def floor(self, emu, argv, ctx={}):
"""
double floor(
double x
);
"""
x, = argv
y = self.hex_to_double(x)
z = math.floor(y)
z = self.double_to_hex(z)
return z
@apihook('strstr', argc=2, conv=e_arch.CALL_CONV_CDECL)
def strstr(self, emu, argv, ctx={}):
"""
char *strstr(
const char *str,
const char *strSearch
);
"""
hay, needle = argv
if hay:
_hay = self.read_mem_string(hay, 1)
argv[0] = _hay
if needle:
needle = self.read_mem_string(needle, 1)
argv[1] = needle
ret = _hay.find(needle)
if ret != -1:
ret = hay + ret
else:
ret = 0
return ret
@apihook('strncat_s', argc=4, conv=e_arch.CALL_CONV_CDECL)
def strncat_s(self, emu, argv, ctx={}):
"""
errno_t strncat_s(
char *strDest,
size_t numberOfElements,
const char *strSource,
size_t count
);
"""
strDest, num, src, count = argv
rv = 0
is_truncated = (0xFFFFFFFF & count)
if is_truncated == _TRUNCATE:
is_truncated = True
else:
is_truncated = False
argv[0] = self.read_mem_string(strDest, 1)
argv[2] = self.read_mem_string(src, 1)
slen1 = self.mem_string_len(strDest, 1)
rem = num - slen1
if is_truncated:
if rem < count:
self.mem_copy(strDest + slen1, src, count-1)
else:
self.mem_copy(strDest + slen1, src, count)
else:
if rem < count:
rv = EINVAL
else:
self.mem_copy(strDest + slen1, src, count)
return rv
@apihook('__stdio_common_vfprintf', argc=e_arch.VAR_ARGS,
conv=e_arch.CALL_CONV_CDECL)
def __stdio_common_vfprintf(self, emu, argv, ctx={}):
arch = emu.get_arch()
if arch == e_arch.ARCH_AMD64:
opts, stream, fmt, _, va_list = \
emu.get_func_argv(e_arch.CALL_CONV_CDECL, 5)[:5]
else:
opts, opts2, stream, fmt, _, va_list = \
emu.get_func_argv(e_arch.CALL_CONV_CDECL, 6)[:6]
rv = 0
fmt_str = self.read_mem_string(fmt, 1)
fmt_cnt = self.get_va_arg_count(fmt_str)
vargs = self.va_args(va_list, fmt_cnt)
fin = self.do_str_format(fmt_str, vargs)
argv[:] = [opts, stream, fin]
rv = len(fin)
return rv
@apihook('memset', argc=3, conv=e_arch.CALL_CONV_CDECL)
def memset(self, emu, argv, ctx={}):
"""
void *memset ( void * ptr,
int value,
size_t num );
"""
ptr, value, num = argv
data = value.to_bytes(1, 'little') * num
self.mem_write(ptr, data)
return ptr
@apihook('time', argc=1, conv=e_arch.CALL_CONV_CDECL)
def time(self, emu, argv, ctx={}):
"""
time_t time( time_t *destTime );
"""
destTime, = argv
out_time = TIME_BASE
if destTime:
self.mem_write(destTime, out_time)
return out_time
@apihook('srand', argc=1, conv=e_arch.CALL_CONV_CDECL)
def srand(self, emu, argv, ctx={}):
"""
void srand (unsigned int seed);
"""
seed, = argv
return
@apihook('sprintf', argc=e_arch.VAR_ARGS, conv=e_arch.CALL_CONV_CDECL)
def sprintf(self, emu, argv, ctx={}):
"""
int sprintf(
char *buffer,
const char *format [,
argument] ...
);
"""
buf, fmt = emu.get_func_argv(e_arch.CALL_CONV_CDECL, 2)
fmt_str = self.read_string(fmt)
fmt_cnt = self.get_va_arg_count(fmt_str)
if not fmt_cnt:
self.write_string(fmt_str, buf)
return len(fmt_str)
_argv = emu.get_func_argv(e_arch.CALL_CONV_CDECL, 2 + fmt_cnt)[2:]
fin = self.do_str_format(fmt_str, _argv)
self.write_string(fin, buf)
argv.clear()
argv.append(fin)
return len(fin)
@apihook('atoi', argc=1, conv=e_arch.CALL_CONV_CDECL)
def atoi(self, emu, argv, ctx={}):
"""
int atoi(
const char *str
);
"""
_str, = argv
i = self.read_string(_str)
argv[0] = i
try:
rv = int(i)
except ValueError:
rv = 0
return rv
@apihook('rand', argc=0, conv=e_arch.CALL_CONV_CDECL)
def rand(self, emu, argv, ctx={}):
"""
int rand( void );
"""
self.rand_int += 1
return self.rand_int
@apihook('__set_app_type', argc=1, conv=e_arch.CALL_CONV_CDECL)
def __set_app_type(self, emu, argv, ctx={}):
"""
void __set_app_type (
int at
)
"""
return
@apihook('__p__fmode', argc=0, conv=e_arch.CALL_CONV_CDECL)
def __p__fmode(self, emu, argv, ctx={}):
"""
int* __p__fmode();
"""
_O_TEXT = 0x4000
ptr = self.mem_alloc(4, tag='api.fmode')
data = _O_TEXT.to_bytes(4, 'little')
self.mem_write(ptr, data)
return ptr
@apihook('__p__commode', argc=0, conv=e_arch.CALL_CONV_CDECL)
def __p__commode(self, emu, argv, ctx={}):
"""
int* __p__commode();
"""
_IOCOMMIT = 0x4000
ptr = self.mem_alloc(4, tag='api.commode')
data = _IOCOMMIT.to_bytes(4, 'little')
self.mem_write(ptr, data)
return ptr
@apihook('_controlfp', argc=2, conv=e_arch.CALL_CONV_CDECL)
def _controlfp(self, emu, argv, ctx={}):
"""
unsigned int _controlfp(unsigned int new,
unsinged int mask)
"""
return 0
@apihook('strcpy', argc=2, conv=e_arch.CALL_CONV_CDECL)
def strcpy(self, emu, argv, ctx={}):
"""
char *strcpy(
char *strDestination,
const char *strSource
);
"""
dest, src = argv
s = self.read_string(src)
self.write_string(s, dest)
argv[1] = s
return len(s)
@apihook('memcpy', argc=3, conv=e_arch.CALL_CONV_CDECL)
def memcpy(self, emu, argv, ctx={}):
"""
void *memcpy(
void *dest,
const void *src,
size_t count
);
"""
dest, src, count = argv
data = self.mem_read(src, count)
self.mem_write(dest, data)
return dest
@apihook('_except_handler4_common', argc=6, conv=e_arch.CALL_CONV_CDECL)
def _except_handler4_common(self, emu, argv, ctx={}):
"""
_CRTIMP __C_specific_handler(
_In_ struct _EXCEPTION_RECORD *ExceptionRecord,
_In_ void *EstablisherFrame,
_Inout_ struct _CONTEXT *ContextRecord,
_Inout_ struct _DISPATCHER_CONTEXT *DispatcherContext
);
"""
# Inferred from the SEH teardowns described here:
# https://bytepointer.com/resources/pietrek_crash_course_depths_of_win32_seh.htm
# http://www.openrce.org/articles/full_view/21
# Two additional arguments are pushed to the function to check security cookies
cookie_ptr, cookie_func, record, frame, context, dispath_ctx = argv
rv = 0
cookie = self.mem_read(cookie_ptr, 4)
cookie = int.from_bytes(cookie, 'little')
thread = emu.get_current_thread()
# Break down the exception records into something more manageable
curr_frame = frame
seh = thread.get_seh()
_ctx = self.wintypes.CONTEXT(emu.get_ptr_size())
_ctx = self.mem_cast(_ctx, context)
seh.set_context(_ctx, address=context)
seh.set_record(record)
seh.clear_frames()
while curr_frame != 0:
reg = self.wintypes.EXCEPTION_REGISTRATION(emu.get_ptr_size())
reg = self.mem_cast(reg, curr_frame)
scope_table = reg.ScopeTable ^ cookie
st = self.wintypes.EH4_SCOPETABLE(emu.get_ptr_size())
st = self.mem_cast(st, scope_table)
rec = self.wintypes.EH4_SCOPETABLE_RECORD(emu.get_ptr_size())
# The trylevel will tell us what scope record to get
scope_record_offset = scope_table + st.sizeof()
scope_record_offset += (rec.sizeof() * reg.TryLevel)
rec = self.mem_cast(rec, scope_record_offset)
seh.add_frame(reg, st, [rec, ])
curr_frame = reg.Next
return rv
@apihook('_seh_filter_exe', argc=2, conv=e_arch.CALL_CONV_CDECL)
def _seh_filter_exe(self, emu, argv, ctx={}):
"""
int __cdecl _seh_filter_exe(
unsigned long _ExceptionNum,
struct _EXCEPTION_POINTERS* _ExceptionPtr
);
"""
except_num, exc_ptr = argv
rv = 1
return rv
@apihook('_seh_filter_dll', argc=2, conv=e_arch.CALL_CONV_CDECL)
def _seh_filter_dll(self, emu, argv, ctx={}):
"""
int __cdecl _seh_filter_dll(
unsigned long _ExceptionNum,
struct _EXCEPTION_POINTERS* _ExceptionPtr
);
"""
except_num, exc_ptr = argv
rv = 1
return rv
@apihook('puts', argc=1, conv=e_arch.CALL_CONV_CDECL)
def puts(self, emu, argv, ctx={}):
"""
int puts(
const char *str
);
"""
s, = argv
string = self.read_mem_string(s, 1)
argv[0] = string
rv = len(string)
return rv
@apihook('_initialize_onexit_table', argc=1, conv=e_arch.CALL_CONV_CDECL)
def _initialize_onexit_table(self, emu, argv, ctx={}):
"""
int _initialize_onexit_table(
_onexit_table_t* table
);
"""
rv = 0
return rv
@apihook('_register_onexit_function', argc=2, conv=e_arch.CALL_CONV_CDECL)
def _register_onexit_function(self, emu, argv, ctx={}):
"""
int _register_onexit_function(
_onexit_table_t* table,
_onexit_t function
);
"""
rv = 0
return rv
@apihook('malloc', argc=1, conv=e_arch.CALL_CONV_CDECL)
def malloc(self, emu, argv, ctx={}):
"""
void *malloc(
size_t size
);
"""
size, = argv
chunk = self.heap_alloc(size, heap='HeapAlloc')
return chunk
@apihook('_beginthreadex', argc=6, conv=e_arch.CALL_CONV_CDECL)
def _beginthreadex(self, emu, argv, ctx={}):
"""
uintptr_t _beginthreadex(
void *security,
unsigned stack_size,
unsigned ( __stdcall *start_address )( void * ),
void *arglist,
unsigned initflag,
unsigned *thrdaddr
);
"""
security, stack_size, start_address, arglist, initflag, thrdaddr = argv
handle, obj = self.create_thread(start_address, arglist, emu.get_current_process())
if thrdaddr:
self.mem_write(thrdaddr, obj.get_id().to_bytes(4, 'little'))
return handle
@apihook('system', argc=1, conv=e_arch.CALL_CONV_CDECL)
def system(self, emu, argv, ctx={}):
"""
int system(
const char *command
);
"""
s, = argv
string = self.read_mem_string(s, 1)
argv[0] = string
rv = len(string)
return rv
@apihook('_set_invalid_parameter_handler', argc=1, conv=e_arch.CALL_CONV_CDECL)
def _set_invalid_parameter_handler(self, emu, argv, ctx={}):
"""
_invalid_parameter_handler _set_invalid_parameter_handler(
_invalid_parameter_handler pNew
);
"""
pNew, = argv
return 0
+116
View File
@@ -0,0 +1,116 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
from .. import api
class Ntdll(api.ApiHandler):
"""
Implements exported native functions from ntdll.dll. If a function is not supported
here, but is supported in the ntoskrnl handler (e.g. NtCreateFile) it will be handled by
the kernel export handler.
"""
name = 'ntdll'
apihook = api.ApiHandler.apihook
impdata = api.ApiHandler.impdata
def __init__(self, emu):
super(Ntdll, self).__init__(emu)
self.funcs = {}
self.data = {}
super(Ntdll, self).__get_hook_attrs__(self)
def normalize_dll_name(self, name):
ret = name
if (name.lower().startswith('api-ms-win-crt') or name.lower().startswith('vcruntime')):
ret = 'msvcrt'
return ret
@apihook('RtlGetLastWin32Error', argc=0)
def RtlGetLastWin32Error(self, emu, argv, ctx={}):
'''DWORD RtlGetLastWin32Error();'''
return emu.get_last_error()
@apihook('RtlAddVectoredExceptionHandler', argc=2)
def RtlAddVectoredExceptionHandler(self, emu, argv, ctx={}):
'''
PVOID AddVectoredExceptionHandler(
ULONG First,
PVECTORED_EXCEPTION_HANDLER Handler
);
'''
First, Handler = argv
emu.add_vectored_exception_handler(First, Handler)
return Handler
@apihook('RtlRemoveVectoredExceptionHandler', argc=1)
def RtlRemoveVectoredExceptionHandler(self, emu, argv, ctx={}):
'''
ULONG RemoveVectoredExceptionHandler(
PVOID Handle
);
'''
Handler, = argv
emu.remove_vectored_exception_handler(Handler)
return Handler
@apihook('LdrLoadDll', argc=4)
def LdrLoadDll(self, emu, argv, ctx={}):
'''NTSTATUS
NTAPI
LdrLoadDll(
IN PWSTR SearchPath OPTIONAL,
IN PULONG LoadFlags OPTIONAL,
IN PUNICODE_STRING Name,
OUT PVOID *BaseAddress OPTIONAL
);'''
SearchPath, LoadFlags, Name, BaseAddress = argv
hmod = 0
req_lib = self.read_unicode_string(Name)
lib = self.normalize_dll_name(req_lib)
hmod = emu.load_library(lib)
flags = {
0x1: 'DONT_RESOLVE_DLL_REFERENCES',
0x10: 'LOAD_IGNORE_CODE_AUTHZ_LEVEL',
0x2: 'LOAD_LIBRARY_AS_DATAFILE',
0x40: 'LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE',
0x20: 'LOAD_LIBRARY_AS_IMAGE_RESOURCE',
0x200: 'LOAD_LIBRARY_SEARCH_APPLICATION_DIR',
0x1000: 'LOAD_LIBRARY_SEARCH_DEFAULT_DIRS',
0x100: 'LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR',
0x800: 'LOAD_LIBRARY_SEARCH_SYSTEM32',
0x400: 'LOAD_LIBRARY_SEARCH_USER_DIRS',
0x8: 'LOAD_WITH_ALTERED_SEARCH_PATH',
}
pretty_flags = ' | '.join([name for bit, name in flags.items()
if LoadFlags & bit])
if SearchPath:
argv[0] = self.read_mem_string(SearchPath, 2)
argv[2] = req_lib
argv[1] = pretty_flags
if not hmod:
STATUS_DLL_NOT_FOUND = 0xC0000135
return STATUS_DLL_NOT_FOUND
if BaseAddress:
self.mem_write(BaseAddress, hmod.to_bytes(self.get_ptr_size(), 'little'))
return 0
+96
View File
@@ -0,0 +1,96 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
from .. import api
class Shell32(api.ApiHandler):
"""
Implements exported functions from shell32.dll
"""
name = 'shell32'
apihook = api.ApiHandler.apihook
impdata = api.ApiHandler.impdata
def __init__(self, emu):
super(Shell32, self).__init__(emu)
self.funcs = {}
self.data = {}
self.window_hooks = {}
self.handle = 0
self.win = None
super(Shell32, self).__get_hook_attrs__(self)
@apihook('SHCreateDirectoryEx', argc=3)
def SHCreateDirectoryEx(self, emu, argv, ctx={}):
'''
int SHCreateDirectoryExA(
HWND hwnd,
LPCSTR pszPath,
const SECURITY_ATTRIBUTES *psa
);
'''
hwnd, pszPath, psa = argv
cw = self.get_char_width(ctx)
dn = ''
if pszPath:
dn = self.read_mem_string(pszPath, cw)
argv[1] = dn
self.log_file_access(dn, 'directory_create')
return 0
@apihook('ShellExecute', argc=6)
def ShellExecute(self, emu, argv, ctx={}):
'''
HINSTANCE ShellExecuteA(
HWND hwnd,
LPCSTR lpOperation,
LPCSTR lpFile,
LPCSTR lpParameters,
LPCSTR lpDirectory,
INT nShowCmd
);
'''
hwnd, lpOperation, lpFile, lpParameters, lpDirectory, nShowCmd = argv
cw = self.get_char_width(ctx)
fn = ''
param = ''
dn = ''
if lpOperation:
op = self.read_mem_string(lpOperation, cw)
argv[1] = op
if lpFile:
fn = self.read_mem_string(lpFile, cw)
argv[2] = fn
if lpParameters:
param = self.read_mem_string(lpParameters, cw)
argv[3] = param
if lpDirectory:
dn = self.read_mem_string(lpDirectory, cw)
argv[4] = dn
if dn and fn:
fn = '%s\\%s' % (dn, fn)
proc = emu.create_process(path=fn, cmdline=param)
self.log_process_event(proc, 'create')
return 33
@apihook('IsUserAnAdmin', argc=0)
def IsUserAnAdmin(self, emu, argv, ctx={}):
"""
BOOL IsUserAnAdmin();
"""
return emu.get_user().get('is_admin', False)
+48
View File
@@ -0,0 +1,48 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
from .. import api
class Shlwapi(api.ApiHandler):
"""
Implements exported functions from shlwapi.dll
"""
name = 'shlwapi'
apihook = api.ApiHandler.apihook
impdata = api.ApiHandler.impdata
def __init__(self, emu):
super(Shlwapi, self).__init__(emu)
self.funcs = {}
self.data = {}
self.window_hooks = {}
self.handle = 0
self.win = None
super(Shlwapi, self).__get_hook_attrs__(self)
@apihook('PathIsRelative', argc=1)
def PathIsRelative(self, emu, argv, ctx={}):
'''
BOOL PathIsRelativeA(
LPCSTR pszPath
);
'''
pszPath, = argv
cw = self.get_char_width(ctx)
pn = ''
rv = False
if pszPath:
pn = self.read_mem_string(pszPath, cw)
if '..' in pn:
rv = True
argv[0] = pn
return rv
+428
View File
@@ -0,0 +1,428 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
import speakeasy.winenv.arch as _arch
import speakeasy.windows.sessman as sessman
import speakeasy.winenv.defs.windows.user32 as windefs
from .. import api
IDCANCEL = 2
class User32(api.ApiHandler):
"""
Implements exported functions from user32.dll
"""
name = 'user32'
apihook = api.ApiHandler.apihook
impdata = api.ApiHandler.impdata
def __init__(self, emu):
super(User32, self).__init__(emu)
self.funcs = {}
self.data = {}
self.window_hooks = {}
self.handle = 0
self.win = None
self.sessman = sessman.SessionManager(config=None)
super(User32, self).__get_hook_attrs__(self)
def get_handle(self):
self.handle += 4
hnd = self.handle
return hnd
def find_string_resource_by_id(self, pe, uID):
for entry in pe.DIRECTORY_ENTRY_RESOURCE.entries:
if entry.id == 6: # "String Table"
for str_entry in entry.directory.entries:
s = str_entry.directory.strings.get(uID, None)
if s is not None:
return s
return None
@apihook('GetDesktopWindow', argc=0)
def GetDesktopWindow(self, emu, argv, ctx={}):
'''HWND GetDesktopWindow();'''
hnd = 0
desk = self.sessman.get_current_desktop()
window = desk.get_desktop_window()
hnd = window.get_handle()
return hnd
@apihook('ShowWindow', argc=2)
def ShowWindow(self, emu, argv, ctx={}):
'''BOOL ShowWindow(
HWND hWnd,
int nCmdShow
);'''
rv = 1
return rv
@apihook('CreateWindowStation', argc=4)
def CreateWindowStation(self, emu, argv, ctx={}):
'''
HWINSTA CreateWindowStation(
LPCSTR lpwinsta,
DWORD dwFlags,
ACCESS_MASK dwDesiredAccess,
LPSECURITY_ATTRIBUTES lpsa
);
'''
winsta, flags, access, sa = argv
return self.get_handle()
@apihook('SetProcessWindowStation', argc=1)
def SetProcessWindowStation(self, emu, argv, ctx={}):
'''
BOOL SetProcessWindowStation(
HWINSTA hWinSta
);
'''
winsta, = argv
rv = False
if winsta:
rv = True
return rv
@apihook('GetDC', argc=1)
def GetDC(self, emu, argv, ctx={}):
'''
HDC GetDC(
HWND hWnd
);
'''
rv = self.sessman.get_device_context()
return rv
@apihook('MessageBox', argc=4)
def MessageBox(self, emu, argv, ctx={}):
'''int MessageBox(
HWND hWnd,
LPCTSTR lpText,
LPCTSTR lpCaption,
UINT uType
);'''
hWnd, lpText, lpCaption, uType = argv
cw = self.get_char_width(ctx)
if lpText:
text = self.read_mem_string(lpText, cw)
argv[1] = text
if lpCaption:
cap = self.read_mem_string(lpCaption, cw)
argv[2] = cap
rv = IDCANCEL
return rv
@apihook('MessageBoxEx', argc=5)
def MessageBoxEx(self, emu, argv, ctx={}):
'''
int MessageBoxExA(
HWND hWnd,
LPCSTR lpText,
LPCSTR lpCaption,
UINT uType,
WORD wLanguageId
);
'''
av = argv[:-1]
rv = self.MessageBox(emu, av, ctx)
argv[:4] = av
return rv
@apihook('LoadString', argc=4)
def LoadString(self, emu, argv, ctx={}):
"""
int LoadStringW(
HINSTANCE hInstance,
UINT uID,
LPWSTR lpBuffer,
int cchBufferMax
);
"""
hInstance, uID, lpBuffer, ccBufferMax = argv
cw = self.get_char_width(ctx)
size = 0
if hInstance == 0:
pe = emu.modules[0][0]
else:
pe = emu.get_mod_from_addr(hInstance)
if pe and hInstance != pe.get_base():
return 0
s = self.find_string_resource_by_id(pe, uID)
if s is None:
# self.logger.info("unable to find resource string id %04X" % uID)
return 0
if cw == 2:
encoded = s.encode('utf-16le')
elif cw == 1:
encoded = s.encode('utf-8')
size = int(len(encoded) / cw)
if size == 0:
# self.logger.debug("resource id %04X not found" % uID)
return 0
if ccBufferMax == 0:
# TODO this should be done properly, but requires more research
offset = pe.get_memory_mapped_image().find(encoded)
emu.mem_write(lpBuffer, (pe.get_base() + offset).to_bytes(emu.get_ptr_size(),
'little'))
return size
if len(encoded) > ccBufferMax:
encoded = encoded[:ccBufferMax * cw]
emu.mem_write(lpBuffer, encoded)
if cw == 1:
argv[2] = s
else:
argv[2] = s
return len(encoded)
@apihook('GetCursorPos', argc=1)
def GetCursorPos(self, emu, argv, ctx={}):
"""
BOOL GetCursorPos(
LPPOINT lpPoint
);
"""
lpPoint, = argv
rv = 0
return rv
@apihook('GetSystemMetrics', argc=1)
def GetSystemMetrics(self, emu, argv, ctx={}):
"""
int GetSystemMetrics(
int nIndex
);
"""
nIndex, = argv
rv = 1
return rv
@apihook('RegisterWindowMessage', argc=1)
def RegisterWindowMessage(self, emu, argv, ctx={}):
'''
UINT RegisterWindowMessageA(
LPCSTR lpString
);
'''
lpString, = argv
rv = 0xc000
cw = self.get_char_width(ctx)
s = self.read_mem_string(lpString, cw)
argv[0] = s
return rv
@apihook('wsprintf', argc=_arch.VAR_ARGS, conv=_arch.CALL_CONV_CDECL)
def wsprintf(self, emu, argv, ctx={}):
"""
int WINAPIV wsprintf(
LPSTR ,
LPCSTR ,
...
);
"""
cw = self.get_char_width(ctx)
buf, fmt = emu.get_func_argv(_arch.CALL_CONV_CDECL, 2)
fmt_str = self.read_mem_string(fmt, cw)
fmt_cnt = self.get_va_arg_count(fmt_str)
if not fmt_cnt:
self.write_mem_string(fmt_str, buf, cw)
return len(fmt_str)
_args = emu.get_func_argv(_arch.CALL_CONV_CDECL, 2 + fmt_cnt)[2:]
fin = self.do_str_format(fmt_str, _args)
self.write_mem_string(fin, buf, cw)
argv.append(fin)
argv.append(fmt_str)
return len(fin)
@apihook('PeekMessage', argc=5)
def PeekMessage(self, emu, argv, ctx={}):
'''
BOOL PeekMessageA(
LPMSG lpMsg,
HWND hWnd,
UINT wMsgFilterMin,
UINT wMsgFilterMax,
UINT wRemoveMsg
);
'''
return False
@apihook('PostMessage', argc=4)
def PostMessage(self, emu, argv, ctx={}):
'''
BOOL PostMessage(
HWND hWnd,
UINT Msg,
WPARAM wParam,
LPARAM lParam
);
'''
return True
@apihook('SetWindowsHookEx', argc=4)
def SetWindowsHookEx(self, emu, argv, ctx={}):
'''
HHOOK SetWindowsHookEx(
int idHook,
HOOKPROC lpfn,
HINSTANCE hmod,
DWORD dwThreadId
);
'''
idHook, lpfn, hmod, dwThreadId = argv
hname = windefs.get_windowhook_flags(idHook)
if hname:
hname = hname[0]
argv[0] = hname
hnd = self.get_handle()
self.window_hooks.update({hnd: (lpfn, hmod)})
return hnd
@apihook('MsgWaitForMultipleObjects', argc=5)
def MsgWaitForMultipleObjects(self, emu, argv, ctx={}):
'''
DWORD MsgWaitForMultipleObjects(
DWORD nCount,
const HANDLE *pHandles,
BOOL fWaitAll,
DWORD dwMilliseconds,
DWORD dwWakeMask
);
'''
return 0
@apihook('GetMessage', argc=4)
def GetMessage(self, emu, argv, ctx={}):
'''
BOOL GetMessage(
LPMSG lpMsg,
HWND hWnd,
UINT wMsgFilterMin,
UINT wMsgFilterMax
);
'''
return 0
@apihook('GetForegroundWindow', argc=0)
def GetForegroundWindow(self, emu, argv, ctx={}):
'''
HWND GetForegroundWindow();
'''
return self.get_handle()
@apihook('FindWindow', argc=2)
def FindWindow(self, emu, argv, ctx={}):
'''
HWND FindWindow(
LPCSTR lpClassName,
LPCSTR lpWindowName
);
'''
lpClassName, lpWindowName = argv
cw = self.get_char_width(ctx)
if lpClassName:
cn = self.read_mem_string(lpClassName, cw)
argv[0] = cn
if lpWindowName:
wn = self.read_mem_string(lpWindowName, cw)
argv[1] = wn
return 0
@apihook('GetWindowText', argc=3)
def GetWindowText(self, emu, argv, ctx={}):
'''
int GetWindowText(
HWND hWnd,
LPSTR lpString,
int nMaxCount
);
'''
hnd, pstr, maxc = argv
cw = self.get_char_width(ctx)
win_text = 'speakeasy window'
if pstr:
if cw == 2:
wt = (win_text).encode('utf-16le')
else:
wt = (win_text).encode('utf-8')
self.mem_write(pstr, wt)
return len(win_text)
@apihook('PaintDesktop', argc=1)
def PaintDesktop(self, emu, argv, ctx={}):
'''
BOOL PaintDesktop(
HDC hdc
);
'''
return 0
@apihook('wvsprintf', argc=_arch.VAR_ARGS, conv=_arch.CALL_CONV_CDECL)
def wvsprintf(self, emu, argv, ctx={}):
buf, fmt, va_list = emu.get_func_argv(_arch.CALL_CONV_CDECL, 3)[:3]
cw = self.get_char_width(ctx)
fmt_str = self.read_mem_string(fmt, cw)
fmt_cnt = self.get_va_arg_count(fmt_str)
vargs = self.va_args(va_list, fmt_cnt)
fin = self.do_str_format(fmt_str, vargs)
self.write_string(fin, buf)
argv.clear()
argv.append(fin)
return len(fin)
@apihook('ReleaseDC', argc=1)
def ReleaseDC(self, emu, argv, ctx={}):
'''
int ReleaseDC(
HWND hWnd,
HDC hDC
);
'''
return 0
+258
View File
@@ -0,0 +1,258 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
from socket import inet_aton
import speakeasy.winenv.arch as _arch
import speakeasy.windows.netman as netman
import speakeasy.winenv.defs.wininet as windefs
from .. import api
def is_ip_address(ip):
try:
inet_aton(ip)
return True
except Exception:
return False
class WinHttp(api.ApiHandler):
"""
Implements HTTP functions from winhttp.dll
"""
name = 'winhttp'
apihook = api.ApiHandler.apihook
impdata = api.ApiHandler.impdata
def __init__(self, emu):
super(WinHttp, self).__init__(emu)
self.funcs = {}
self.data = {}
self.win = None
self.netman = netman.NetworkManager(config=emu.get_network_config())
super(WinHttp, self).__get_hook_attrs__(self)
@apihook('WinHttpOpen', argc=5, conv=_arch.CALL_CONV_STDCALL)
def WinHttpOpen(self, emu, argv, ctx={}):
"""
WINHTTPAPI HINTERNET WinHttpOpen(
LPCWSTR pszAgentW,
DWORD dwAccessType,
LPCWSTR pszProxyW,
LPCWSTR pszProxyBypassW,
DWORD dwFlags
);
"""
ua, access, proxy, bypass, flags = argv
if ua:
ua = self.read_mem_string(ua, 2)
argv[0] = ua
if proxy:
proxy = self.read_mem_string(proxy, 2)
argv[2] = proxy
if bypass:
bypass = self.read_mem_string(bypass, 2)
argv[3] = bypass
conn = self.netman.new_wininet_inst(ua, access, proxy, bypass, flags)
hnd = conn.get_handle()
return hnd
@apihook('WinHttpConnect', argc=4, conv=_arch.CALL_CONV_STDCALL)
def WinHttpConnect(self, emu, argv, ctx={}):
"""
WINHTTPAPI HINTERNET WinHttpConnect(
IN HINTERNET hSession,
IN LPCWSTR pswzServerName,
IN INTERNET_PORT nServerPort,
IN DWORD dwReserved
);
"""
hnd, server, port, reserve = argv
if server:
server = self.read_mem_string(server, 2)
argv[1] = server
wini = self.netman.get_wininet_object(hnd)
if not wini:
return 0
sess = wini.new_session(server, port, None, None,
0, 0, None)
hdl = sess.get_handle()
return hdl
@apihook('WinHttpOpenRequest', argc=7, conv=_arch.CALL_CONV_STDCALL)
def WinHttpOpenRequest(self, emu, argv, ctx={}):
"""
WINHTTPAPI HINTERNET WinHttpOpenRequest(
IN HINTERNET hConnect,
IN LPCWSTR pwszVerb,
IN LPCWSTR pwszObjectName,
IN LPCWSTR pwszVersion,
IN LPCWSTR pwszReferrer,
IN LPCWSTR *ppwszAcceptTypes,
IN DWORD dwFlags
);
"""
hnd, verb, objname, ver, ref, accepts, flags = argv
if verb:
verb = self.read_mem_string(verb, 2)
argv[1] = verb
if objname:
objname = self.read_mem_string(objname, 2)
argv[2] = objname
if ver:
ver = self.read_mem_string(ver, 2)
argv[3] = ver
if ref:
ref = self.read_mem_string(ref, 2)
argv[4] = ref
if accepts:
accepts = self.read_mem_string(accepts, 2)
argv[5] = accepts
defs = windefs.get_flag_defines(flags)
argv[6] = ' | '.join(defs)
sess = self.netman.get_wininet_object(hnd)
req = sess.new_request(verb, objname, ver, ref, accepts, defs, None)
hdl = req.get_handle()
return hdl
@apihook('WinHttpGetIEProxyConfigForCurrentUser', argc=1, conv=_arch.CALL_CONV_STDCALL)
def WinHttpGetIEProxyConfigForCurrentUser(self, emu, argv, ctx={}):
"""
BOOLAPI WinHttpGetIEProxyConfigForCurrentUser(
IN OUT WINHTTP_CURRENT_USER_IE_PROXY_CONFIG *pProxyConfig
);
"""
proxy_config, = argv
if proxy_config:
self.mem_write(proxy_config, (1).to_bytes(4, 'little'))
return True
@apihook('WinHttpGetProxyForUrl', argc=4, conv=_arch.CALL_CONV_STDCALL)
def WinHttpGetProxyForUrl(self, emu, argv, ctx={}):
"""
BOOLAPI WinHttpGetProxyForUrl(
IN HINTERNET hSession,
IN LPCWSTR lpcwszUrl,
IN WINHTTP_AUTOPROXY_OPTIONS *pAutoProxyOptions,
OUT WINHTTP_PROXY_INFO *pProxyInfo
);
"""
hnd, url, proxopts, proxinfo = argv
if url:
url = self.read_mem_string(url, 2)
argv[1] = url
return True
@apihook('WinHttpSetOption', argc=4, conv=_arch.CALL_CONV_STDCALL)
def WinHttpSetOption(self, emu, argv, ctx={}):
"""
BOOLAPI WinHttpSendRequest(
IN HINTERNET hRequest,
LPCWSTR lpszHeaders,
IN DWORD dwHeadersLength,
LPVOID lpOptional,
IN DWORD dwOptionalLength,
IN DWORD dwTotalLength,
IN DWORD_PTR dwContext
);
"""
hnd, option, buff, buflen = argv
return True
@apihook('WinHttpSendRequest', argc=7, conv=_arch.CALL_CONV_STDCALL)
def WinHttpSendRequest(self, emu, argv, ctx={}):
"""
BOOLAPI WinHttpSendRequest(
IN HINTERNET hRequest,
LPCWSTR lpszHeaders,
IN DWORD dwHeadersLength,
LPVOID lpOptional,
IN DWORD dwOptionalLength,
IN DWORD dwTotalLength,
IN DWORD_PTR dwContext
);
"""
hnd, headers, hdrlen, lpOptional, dwOptionalLength, totlen, context = argv
body = b''
if headers:
headers = self.read_mem_string(headers, 2)
argv[1] = headers
if lpOptional and dwOptionalLength:
body = self.mem_read(lpOptional, dwOptionalLength)
req = self.netman.get_wininet_object(hnd)
srv = req.get_server()
port = req.get_port()
if not is_ip_address(srv):
self.log_dns(srv)
rv = 1
req_str = req.format_http_request(headers=headers)
self.log_http(srv, port, headers=req_str,
body=body, secure=req.is_secure())
return rv
@apihook('WinHttpReceiveResponse', argc=2, conv=_arch.CALL_CONV_STDCALL)
def WinHttpReceiveResponse(self, emu, argv, ctx={}):
"""
WINHTTPAPI BOOL WinHttpReceiveResponse(
IN HINTERNET hRequest,
IN LPVOID lpReserved
);
"""
hnd, lpReserved = argv
return True
@apihook('WinHttpReadData', argc=4, conv=_arch.CALL_CONV_STDCALL)
def WinHttpReadData(self, emu, argv, ctx={}):
"""
BOOLAPI WinHttpReadData(
IN HINTERNET hRequest,
LPVOID lpBuffer,
IN DWORD dwNumberOfBytesToRead,
OUT LPDWORD lpdwNumberOfBytesRead
);
"""
hnd, buf, size, bytes_read = argv
rv = 1
req = self.netman.get_wininet_object(hnd)
resp = req.get_response()
data = resp.read(size)
if buf:
self.mem_write(buf, data)
if bytes_read:
self.mem_write(bytes_read, (len(data)).to_bytes(4, 'little'))
return rv
+328
View File
@@ -0,0 +1,328 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
from socket import inet_aton
import speakeasy.winenv.arch as _arch
import speakeasy.windows.netman as netman
import speakeasy.winenv.defs.wininet as windefs
from .. import api
def is_ip_address(ip):
try:
inet_aton(ip)
return True
except Exception:
return False
class Wininet(api.ApiHandler):
"""
Implements network functions from wininet.dll
"""
name = 'wininet'
apihook = api.ApiHandler.apihook
impdata = api.ApiHandler.impdata
def __init__(self, emu):
super(Wininet, self).__init__(emu)
self.funcs = {}
self.data = {}
self.win = None
self.netman = netman.NetworkManager(config=emu.get_network_config())
super(Wininet, self).__get_hook_attrs__(self)
@apihook('InternetOpen', argc=5, conv=_arch.CALL_CONV_STDCALL)
def InternetOpen(self, emu, argv, ctx={}):
"""
void InternetOpenA(
LPTSTR lpszAgent,
DWORD dwAccessType,
LPTSTR lpszProxy,
LPTSTR lpszProxyBypass,
DWORD dwFlags
);
"""
ua, access, proxy, bypass, flags = argv
cw = self.get_char_width(ctx)
if ua:
ua = self.read_mem_string(ua, cw)
argv[0] = ua
if proxy:
proxy = self.read_mem_string(proxy, cw)
argv[2] = proxy
if bypass:
bypass = self.read_mem_string(bypass, cw)
argv[3] = bypass
conn = self.netman.new_wininet_inst(ua, access, proxy, bypass, flags)
hnd = conn.get_handle()
return hnd
@apihook('InternetConnect', argc=8, conv=_arch.CALL_CONV_STDCALL)
def InternetConnect(self, emu, argv, ctx={}):
"""
void InternetConnect(
HINTERNET hInternet,
LPTSTR lpszServerName,
INTERNET_PORT nServerPort,
LPTSTR lpszUserName,
LPTSTR lpszPassword,
DWORD dwService,
DWORD dwFlags,
DWORD_PTR dwContext
);
"""
hnd, server, port, user, password, service, flags, dwctx = argv
cw = self.get_char_width(ctx)
if server:
server = self.read_mem_string(server, cw)
argv[1] = server
if user:
user = self.read_mem_string(user, cw)
argv[3] = user
if password:
password = self.read_mem_string(password, cw)
argv[4] = password
wini = self.netman.get_wininet_object(hnd)
if not wini:
return 0
sess = wini.new_session(server, port, user, password,
service, flags, dwctx)
hdl = sess.get_handle()
return hdl
@apihook('HttpOpenRequest', argc=8, conv=_arch.CALL_CONV_STDCALL)
def HttpOpenRequest(self, emu, argv, ctx={}):
"""
void HttpOpenRequest(
HINTERNET hConnect,
LPTSTR lpszVerb,
LPTSTR lpszObjectName,
LPTSTR lpszVersion,
LPTSTR lpszReferrer,
LPTSTR *lplpszAcceptTypes,
DWORD dwFlags,
DWORD_PTR dwContext
);
"""
hnd, verb, objname, ver, ref, accepts, flags, dwctx = argv
cw = self.get_char_width(ctx)
if verb:
verb = self.read_mem_string(verb, cw)
argv[1] = verb
if objname:
objname = self.read_mem_string(objname, cw)
argv[2] = objname
if ver:
ver = self.read_mem_string(ver, cw)
argv[3] = ver
if ref:
ref = self.read_mem_string(ref, cw)
argv[4] = ref
defs = windefs.get_flag_defines(flags)
argv[6] = ' | '.join(defs)
sess = self.netman.get_wininet_object(hnd)
req = sess.new_request(verb, objname, ver, ref, accepts, defs, dwctx)
hdl = req.get_handle()
return hdl
@apihook('InternetSetOption', argc=4, conv=_arch.CALL_CONV_STDCALL)
def InternetSetOption(self, emu, argv, ctx={}):
"""
BOOLAPI InternetSetOption(
HINTERNET hInternet,
DWORD dwOption,
LPVOID lpBuffer,
DWORD dwBufferLength
);
"""
hnd, option, buf, length = argv
rv = 1
return rv
@apihook('HttpSendRequest', argc=5, conv=_arch.CALL_CONV_STDCALL)
def HttpSendRequest(self, emu, argv, ctx={}):
"""
BOOLAPI HttpSendRequest(
HINTERNET hRequest,
LPTSTR lpszHeaders,
DWORD dwHeadersLength,
LPVOID lpOptional,
DWORD dwOptionalLength
);
"""
hnd, headers, hdrlen, lpOptional, dwOptionalLength = argv
body = b''
cw = self.get_char_width(ctx)
if headers:
headers = self.read_mem_string(headers, cw)
argv[1] = headers
if lpOptional:
body = self.mem_read(lpOptional, dwOptionalLength)
req = self.netman.get_wininet_object(hnd)
srv = req.get_server()
port = req.get_port()
if not is_ip_address(srv):
self.log_dns(srv)
rv = 1
req_str = req.format_http_request(headers=headers)
self.log_http(srv, port, headers=req_str,
body=body, secure=req.is_secure())
return rv
@apihook('InternetErrorDlg', argc=5, conv=_arch.CALL_CONV_STDCALL)
def InternetErrorDlg(self, emu, argv, ctx={}):
"""
void InternetErrorDlg(
HWND hWnd,
HINTERNET hRequest,
DWORD dwError,
DWORD dwFlags,
LPVOID *lppvData
);
"""
hWnd, req, error, flags, data = argv
return
@apihook('InternetQueryOption', argc=4)
def InternetQueryOption(self, emu, argv, ctx={}):
"""
BOOLAPI InternetQueryOption(
HINTERNET hInternet,
DWORD dwOption,
LPVOID lpBuffer,
LPDWORD lpdwBufferLength
);
"""
hInternet, dwOption, lpBuffer, lpdwBufferLength = argv
rv = False
opt = windefs.get_option_define(dwOption)
if opt:
argv[1] = opt
if dwOption == windefs.INTERNET_OPTION_SECURITY_FLAGS:
if lpBuffer:
sec_flags = windefs.SECURITY_FLAG_SECURE
self.mem_write(lpBuffer, (sec_flags).to_bytes(4, 'little'))
rv = True
return rv
@apihook('InternetReadFile', argc=4, conv=_arch.CALL_CONV_STDCALL)
def InternetReadFile(self, emu, argv, ctx={}):
"""
BOOLAPI InternetReadFile(
HINTERNET hFile,
LPVOID lpBuffer,
DWORD dwNumberOfBytesToRead,
LPDWORD lpdwNumberOfBytesRead
);
"""
hFile, buf, size, bytes_read = argv
rv = 1
req = self.netman.get_wininet_object(hFile)
resp = req.get_response()
data = resp.read(size)
if buf:
self.mem_write(buf, data)
if bytes_read:
self.mem_write(bytes_read, (len(data)).to_bytes(4, 'little'))
return rv
@apihook('HttpQueryInfo', argc=5)
def HttpQueryInfo(self, emu, argv, ctx={}):
"""
BOOLAPI HttpQueryInfo(
HINTERNET hRequest,
DWORD dwInfoLevel,
LPVOID lpBuffer,
LPDWORD lpdwBufferLength,
LPDWORD lpdwIndex
);
"""
hRequest, dwInfoLevel, lpBuffer, lpdwBufferLength, lpdwIndex = argv
cw = self.get_char_width(ctx)
rv = False
info_str = windefs.get_header_info(dwInfoLevel)
if info_str:
argv[1] = info_str
if windefs.HTTP_QUERY_STATUS_CODE == dwInfoLevel:
if lpBuffer:
buf_len = self.mem_read(lpdwBufferLength, 4)
buf_len = int.from_bytes(buf_len, 'little')
if cw == 2:
enc = 'utf-16le'
elif cw == 1:
enc = 'utf-8'
out = windefs.HTTP_STATUS_OK.encode(enc)
self.mem_write(lpBuffer, out)
rv = True
return rv
@apihook('InternetQueryDataAvailable', argc=4)
def InternetQueryDataAvailable(self, emu, argv, ctx={}):
"""
BOOLAPI InternetQueryDataAvailable(
HINTERNET hFile,
LPDWORD lpdwNumberOfBytesAvailable,
DWORD dwFlags,
DWORD_PTR dwContext
);
"""
hFile, lpdwNumberOfBytesAvailable, dwFlags, dwContext = argv
rv = False
req = self.netman.get_wininet_object(hFile)
avail = req.get_response_size()
if lpdwNumberOfBytesAvailable:
self.mem_write(lpdwNumberOfBytesAvailable, (avail.to_bytes(4, 'little')))
rv = True
return rv
@apihook('InternetCloseHandle', argc=1)
def InternetCloseHandle(self, emu, argv, ctx={}):
"""
BOOLAPI InternetCloseHandle(
HINTERNET hInternet
);
"""
hInternet, = argv
rv = True
self.netman.close_wininet_object(hInternet)
return rv
+572
View File
@@ -0,0 +1,572 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
from socket import inet_ntoa, ntohs, htons, ntohl, htonl, inet_aton
import speakeasy.winenv.arch as _arch
import speakeasy.winenv.defs.windows.windows as windefs
import speakeasy.winenv.defs.winsock.winsock as winsock
import speakeasy.winenv.defs.winsock.ws2_32 as wstypes
from .. import api
INADDR_NONE = 0xFFFFFFFF
HOST_NOT_FOUND = 11001
WSAENOTSOCK = 10038
class Ws2_32(api.ApiHandler):
"""
Implements winsock functions from ws2_32.dll
"""
name = 'ws2_32'
apihook = api.ApiHandler.apihook
impdata = api.ApiHandler.impdata
def __init__(self, emu):
super(Ws2_32, self).__init__(emu)
self.funcs = {}
self.data = {}
self.addr_bufs = {}
self.last_error = 0
self.win = None
self.netman = emu.get_network_manager()
self.wstypes = wstypes
super(Ws2_32, self).__get_hook_attrs__(self)
@apihook('WSAStartup', argc=2, conv=_arch.CALL_CONV_STDCALL, ordinal=115)
def WSAStartup(self, emu, argv, ctx={}):
"""
int WSAStartup(
WORD wVersionRequired,
LPWSADATA lpWSAData
);
"""
ver, lpWSAData = argv
wsa = self.wstypes.WSAData(emu.get_ptr_size())
data = self.mem_cast(wsa, lpWSAData)
data.wVersion = 0x0101
data.wHighVersion = 0x0202
data.iMaxSockets = 0x1000
data.iMaxUdpDg = 0x1000
self.mem_write(lpWSAData, self.get_bytes(data))
rv = windefs.ERROR_SUCCESS
return rv
@apihook('WSACleanup', argc=0, ordinal=116)
def WSACleanup(self, emu, argv, ctx={}):
"""
int WSACleanup();
"""
return 0
@apihook('WSASocket', argc=6)
def WSASocket(self, emu, argv, ctx={}):
"""
SOCKET WSAAPI WSASocket(
int af,
int type,
int protocol,
LPWSAPROTOCOL_INFO lpProtocolInfo,
GROUP g,
DWORD dwFlags
);
"""
af, typ, protocol, lpProtocolInfo, g, dwFlags = argv
fam_str = winsock.get_addr_family(af)
sock_str = winsock.get_sock_type(typ)
sock = self.netman.new_socket(fam_str, sock_str, protocol, dwFlags)
fd = sock.get_fd()
argv[0] = fam_str
argv[1] = sock_str
return fd
@apihook('socket', argc=3, conv=_arch.CALL_CONV_STDCALL, ordinal=23)
def socket(self, emu, argv, ctx={}):
"""
SOCKET WSAAPI socket(
int af,
int type,
int protocol
);
"""
af, typ, protocol = argv
fam_str = winsock.get_addr_family(af)
sock_str = winsock.get_sock_type(typ)
sock = self.netman.new_socket(fam_str, sock_str, protocol, 0)
fd = sock.get_fd()
argv[0] = fam_str
argv[1] = sock_str
return fd
@apihook('inet_addr', argc=1, ordinal=11)
def inet_addr(self, emu, argv, ctx={}):
"""
unsigned long inet_addr(
_In_ const char *cp
);
"""
a, = argv
if a:
a = self.read_mem_string(a, 1)
argv[0] = a
try:
rv = inet_aton(a)
rv = int.from_bytes(rv, 'little')
except OSError:
rv = INADDR_NONE
return rv
@apihook('htons', argc=1, conv=_arch.CALL_CONV_STDCALL, ordinal=9)
def htons(self, emu, argv, ctx={}):
"""
u_short htons(
u_short hostshort
);
"""
hostshort, = argv
netshort = htons(hostshort)
return netshort
@apihook('ntohs', argc=1, ordinal=15)
def ntohs(self, emu, argv, ctx={}):
"""
u_short ntohs(
u_short netshort
);
"""
netshort, = argv
return ntohs(netshort)
@apihook('ntohl', argc=1, ordinal=14)
def ntohl(self, emu, argv, ctx={}):
"""
u_long ntohl(
u_long netlong
);
"""
netlong, = argv
return ntohl(netlong)
@apihook('setsockopt', argc=5)
def setsockopt(self, emu, argv, ctx={}):
"""
int setsockopt(
SOCKET s,
int level,
int optname,
const char *optval,
int optlen
);
"""
s, level, optname, optval, optlen = argv
return 0
@apihook('WSASetLastError', argc=1, ordinal=112)
def WSASetLastError(self, emu, argv, ctx={}):
"""
void WSASetLastError(
int iError
);
"""
iError, = argv
self.last_error = iError
return
@apihook('gethostname', argc=2, ordinal=57)
def gethostname(self, emu, argv, ctx={}):
"""
int gethostname(
char *name,
int namelen
);
"""
name, namelen, = argv
rv = -1
host = emu.get_hostname()
if name and host:
host += '\x00'
if namelen > len(host):
out = host.encode('utf-8')
self.mem_write(name, out)
rv = 0
return rv
@apihook('gethostbyname', argc=1, conv=_arch.CALL_CONV_STDCALL, ordinal=52)
def gethostbyname(self, emu, argv, ctx={}):
"""
struct hostent * gethostbyname(const char FAR * name);
"""
name, = argv
name = self.read_mem_string(name, 1)
ptr_hostent = 0
hostent = 0
ip = self.netman.name_lookup(name)
if ip:
hostent = self.wstypes.hostent(emu.get_ptr_size())
buflen = len(name)
buflen += 8
buflen += len(ip)
ptr_hostent = self.mem_alloc(buflen, tag='api.struct.HOSTENT')
hostent.h_name = argv[0]
hostent.h_length = len(ip)
ip_bytes = inet_aton(ip)
ptr_h_addr = self.mem_alloc(len(ip_bytes)*3, tag='api.struct.HOSTENT.h_addr')
ptr = (ptr_h_addr + self.get_ptr_size())
self.mem_write(ptr_h_addr, ptr.to_bytes(self.get_ptr_size(), 'little'))
self.mem_write(ptr, ip_bytes)
hostent.h_addr_list = ptr_h_addr
# Write the hostent struct
self.mem_write(ptr_hostent, self.get_bytes(hostent))
else:
ip = ''
argv[0] = name
self.log_dns(name, ip)
return ptr_hostent
@apihook('connect', argc=3, conv=_arch.CALL_CONV_STDCALL, ordinal=4)
def connect(self, emu, argv, ctx={}):
"""
int WSAAPI connect(
SOCKET s,
const sockaddr *name,
int namelen
);
"""
s, pname, namelen = argv
rv = windefs.ERROR_SUCCESS
sockaddr = self.wstypes.sockaddr_in(emu.get_ptr_size())
sa = self.mem_cast(sockaddr, pname)
raddr = inet_ntoa(sa.sin_addr.to_bytes(4, 'little'))
rport = ntohs(sa.sin_port)
socket = self.netman.get_socket(s)
if not socket:
return 0xFFFFFFFF
stype = socket.get_type()
proto = 'unknown'
if stype == 'SOCK_STREAM':
proto = 'tcp'
elif stype == 'SOCK_DGRAM':
proto = 'udp'
elif stype == 'SOCK_RAW':
proto = 'raw'
socket.set_connection_info(raddr, rport)
self.log_network(raddr, rport, typ='connect', proto=proto, method='winsock.connect')
argv[1] = '%s:%d' % (raddr, rport)
return rv
@apihook('bind', argc=3, ordinal=2)
def bind(self, emu, argv, ctx={}):
"""
int bind(
SOCKET s,
const sockaddr *addr,
int namelen
);
"""
s, pname, namelen = argv
rv = windefs.ERROR_SUCCESS
sockaddr = self.wstypes.sockaddr_in(emu.get_ptr_size())
sa = self.mem_cast(sockaddr, pname)
raddr = inet_ntoa(sa.sin_addr.to_bytes(4, 'little'))
rport = ntohs(sa.sin_port)
socket = self.netman.get_socket(s)
stype = socket.get_type()
proto = 'unknown'
if stype == 'SOCK_STREAM':
proto = 'tcp'
elif stype == 'SOCK_DGRAM':
proto = 'udp'
elif stype == 'SOCK_RAW':
proto = 'raw'
socket.set_connection_info(raddr, rport)
self.log_network(raddr, rport, typ='bind', proto=proto, method='winsock.bind')
argv[1] = '%s:%d' % (raddr, rport)
return rv
@apihook('listen', argc=2, ordinal=13)
def listen(self, emu, argv, ctx={}):
"""
int WSAAPI listen(
SOCKET s,
int backlog
);
"""
s, backlog = argv
rv = windefs.ERROR_SUCCESS
return rv
@apihook('select', argc=5, ordinal=18)
def select(self, emu, argv, ctx={}):
"""
int WSAAPI select(
int nfds,
fd_set *readfds,
fd_set *writefds,
fd_set *exceptfds,
const timeval *timeout
);
"""
nfds, readfds, writefds, exceptfds, timeout = argv
fd_count = 0
if readfds:
fds = self.mem_read(readfds, 4)
fds = int.from_bytes(fds, 'little')
fd_count += fds
if writefds:
fds = self.mem_read(writefds, 4)
fds = int.from_bytes(fds, 'little')
fd_count += fds
if exceptfds:
fds = self.mem_read(exceptfds, 4)
fds = int.from_bytes(fds, 'little')
fd_count += fds
return fd_count
@apihook('accept', argc=3, ordinal=1)
def accept(self, emu, argv, ctx={}):
"""
SOCKET WSAAPI accept(
SOCKET s,
sockaddr *addr,
int *addrlen
);
"""
s, addr, addrlen = argv
socket = self.netman.get_socket(s)
stype = socket.get_type()
proto = 'unknown'
if stype == 'SOCK_STREAM':
proto = 'tcp'
elif stype == 'SOCK_DGRAM':
proto = 'udp'
elif stype == 'SOCK_RAW':
proto = 'raw'
new_sock = self.netman.new_socket(socket.family, socket.type, socket.protocol, 0)
aip = self.netman.name_lookup('default')
if not aip:
aip = '127.0.0.1'
port = socket.connected_port
nip = inet_aton(aip)
nip = int.from_bytes(nip, 'little')
new_sock.set_connection_info(aip, port)
self.log_network(aip, port, typ='accept', proto=proto, method='winsock.accept')
if addr:
sockaddr = self.wstypes.sockaddr_in(emu.get_ptr_size())
sockaddr = self.mem_cast(sockaddr, addr)
sockaddr.sin_addr = nip
sockaddr.sin_port = port
self.mem_write(addr, sockaddr.get_bytes())
return new_sock.get_fd()
@apihook('inet_ntoa', argc=1, ordinal=12)
def inet_ntoa(self, emu, argv, ctx={}):
"""
char FAR* inet_ntoa(struct in_addr in);
"""
in_addr, = argv
raddr = inet_ntoa(in_addr.to_bytes(4, 'little'))
rv = self.addr_bufs.get(raddr)
if not rv:
buf = self.mem_alloc(len(raddr), tag='api.ws2_32.inet_ntoa.%s' % (raddr))
self.mem_write(buf, raddr.encode('utf-8'))
self.addr_bufs.update({raddr: buf})
return buf
@apihook('htonl', argc=1, ordinal=8)
def htonl(self, emu, argv, ctx={}):
"""
uint32_t htonl(uint32_t hostlong);
"""
hostlong, = argv
return htonl(hostlong)
@apihook('__WSAFDIsSet', argc=2, ordinal=151)
def __WSAFDIsSet(self, emu, argv, ctx={}):
"""
int __WSAFDIsSet(
SOCKET ,
fd_set *
);
"""
sock, fd_set = argv
return 1
@apihook('shutdown', argc=2, ordinal=22)
def shutdown(self, emu, argv, ctx={}):
"""
int shutdown(
SOCKET s,
int how
);
"""
return 0
@apihook('recv', argc=4, ordinal=16)
def recv(self, emu, argv, ctx={}):
"""
int recv(
SOCKET s,
char *buf,
int len,
int flags
);
"""
s, buf, blen, flags = argv
rv = 0
peek = flags & winsock.MSG_PEEK
sock = self.netman.get_socket(s)
data = sock.get_recv_data(blen, peek)
rv = len(data)
self.mem_write(buf, data)
stype = sock.get_type()
proto = 'unknown'
if stype == 'SOCK_STREAM':
proto = 'tcp'
elif stype == 'SOCK_DGRAM':
proto = 'udp'
elif stype == 'SOCK_RAW':
proto = 'raw'
raddr, rport = sock.get_connection_info()
self.log_network(raddr, rport, typ='data_in', proto=proto, method='winsock.recv',
data=data)
return rv
@apihook('send', argc=4, ordinal=19)
def send(self, emu, argv, ctx={}):
"""
int WSAAPI send(
SOCKET s,
const char *buf,
int len,
int flags
);
"""
s, buf, blen, flags = argv
data = b''
socket = self.netman.get_socket(s)
stype = socket.get_type()
proto = 'unknown'
if stype == 'SOCK_STREAM':
proto = 'tcp'
elif stype == 'SOCK_DGRAM':
proto = 'udp'
elif stype == 'SOCK_RAW':
proto = 'raw'
if buf:
data = self.mem_read(buf, blen)
raddr, rport = socket.get_connection_info()
self.log_network(raddr, rport, typ='data_out', proto=proto, method='winsock.send',
data=data)
return len(data)
@apihook('closesocket', argc=1, ordinal=3)
def closesocket(self, emu, argv, ctx={}):
"""
int closesocket(
IN SOCKET s
);
"""
s, = argv
rv = 0
socket = self.netman.get_socket(s)
if not socket:
# This isnt a valid socket, return invalid
rv = winsock.WSAENOTSOCK
else:
self.netman.close_socket(s)
return rv
@apihook('ioctlsocket', argc=3, ordinal=10)
def ioctlsocket(self, emu, argv, ctx={}):
"""
int ioctlsocket(
SOCKET s,
long cmd,
u_long *argp
);
"""
s, cmd, argp = argv
rv = winsock.WSAENOTSOCK
socket = self.netman.get_socket(s)
if socket:
rv = 0
return rv
+92
View File
@@ -0,0 +1,92 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
import ctypes as ct
import speakeasy.winenv.arch as _arch
from speakeasy.struct import EmuStruct, Ptr
from .. import api
class WTS_SESSION_INFO(EmuStruct):
def __init__(self, ptr_size):
super().__init__(ptr_size)
self.SessionId = ct.c_uint32
self.pWinStationName = Ptr
self.State = ct.c_uint32
class WtsApi32(api.ApiHandler):
name = 'wtsapi32'
apihook = api.ApiHandler.apihook
impdata = api.ApiHandler.impdata
def __init__(self, emu):
super(WtsApi32, self).__init__(emu)
self.funcs = {}
self.data = {}
super(WtsApi32, self).__get_hook_attrs__(self)
@apihook('WTSEnumerateSessions', argc=5, conv=_arch.CALL_CONV_STDCALL)
def WTSEnumerateSessions(self, emu, argv, ctx={}):
"""
BOOL WTSEnumerateSessions(
IN HANDLE hServer,
IN DWORD Reserved,
IN DWORD Version,
PWTS_SESSION_INFO *ppSessionInfo,
DWORD *pCount
);
"""
hServer, res, ver, ppSessionInfo, pCount = argv
rv = 0
fn = ctx['func_name']
cw = self.get_char_width(ctx)
winstatname = 'RDP-Tcp#1' + '\x00'
if cw == 2:
sn = winstatname.encode('utf-16le')
elif cw == 1:
sn = winstatname.encode('utf-8')
wsi = WTS_SESSION_INFO(emu.get_ptr_size())
size = len(sn) + wsi.sizeof()
buf = self.mem_alloc(size=size, tag='api.%s' % (fn))
wsi.SessionId = 1
# Write the string at the end of the structure
wsi.pWinStationName = buf + wsi.sizeof()
wsi.State = 0
# Write the structure into memory
self.mem_write(buf, self.get_bytes(wsi))
# Write the string at the end of the structure
self.mem_write(buf + wsi.sizeof(), sn)
# Write the total session count
if ppSessionInfo and pCount:
self.mem_write(pCount, (1).to_bytes(4, 'little'))
# Write the session buffer
self.mem_write(ppSessionInfo, (buf).to_bytes(self.get_ptr_size(), 'little'))
rv = 1
return rv
@apihook('WTSFreeMemory', argc=1, conv=_arch.CALL_CONV_STDCALL)
def WTSFreeMemory(self, emu, argv, ctx={}):
"""
void WTSFreeMemory(
IN PVOID pMemory
);
"""
pMemory, = argv
rv = 1
self.mem_free(pMemory)
return rv
+92
View File
@@ -0,0 +1,92 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
import speakeasy.winenv.arch as _arch
from speakeasy.errors import ApiEmuError
from speakeasy.winenv.api.kernelmode import ntoskrnl, hal, wdfldr, netio, ndis, fwpkclnt, usbd
from speakeasy.winenv.api.usermode import ws2_32, kernel32, wininet, winhttp, user32, \
advapi32, msvcrt, wtsapi32, mscoree, dnsapi, \
ntdll, crypt32, shell32, shlwapi, advpack, gdi32
API_HANDLERS = (
# Kernel mode
('ntoskrnl', ntoskrnl.Ntoskrnl),
('wdfldr', wdfldr.Wdfldr),
('hal', hal.Hal),
('usbd', usbd.Usbd),
('netio', netio.Netio),
('ndis', ndis.Ndis),
('fwpkclnt', fwpkclnt.Fwpkclnt),
# User mode
('ws2_32', ws2_32.Ws2_32),
('kernel32', kernel32.Kernel32),
('ntdll', ntdll.Ntdll),
('wininet', wininet.Wininet),
('winhttp', winhttp.WinHttp),
('user32', user32.User32),
('msvcrt', msvcrt.Msvcrt),
('wtsapi32', wtsapi32.WtsApi32),
('advapi32', advapi32.AdvApi32),
('dnsapi', dnsapi.DnsApi),
('mscoree', mscoree.Mscoree),
('crypt32', crypt32.Crypt32),
('shell32', shell32.Shell32),
('shlwapi', shlwapi.Shlwapi),
('advpack', advpack.Advpack),
('gdi32', gdi32.GDI32),
)
class WindowsApi:
def __init__(self, emu):
self.mods = {}
self.instances = []
self.data = {}
self.emu = emu
arch = self.emu.get_arch()
if arch == _arch.ARCH_X86:
self.ptr_size = 4
elif arch == _arch.ARCH_AMD64:
self.ptr_size = 8
else:
raise ApiEmuError('Invalid architecture')
def load_api_handler(self, mod_name):
for name, hdl in API_HANDLERS:
name = name.lower()
if mod_name and name == mod_name.lower():
handler = self.mods.get(name)
if not handler:
handler = hdl(self.emu)
self.mods.update({name: handler})
return handler
return None
def get_data_export_handler(self, mod_name, exp_name):
mod = self.mods.get(mod_name)
if not mod:
mod = self.load_api_handler(mod_name)
if not mod:
return None, None
return (mod, mod.get_data_handler(exp_name))
def get_export_func_handler(self, mod_name, exp_name):
mod = self.mods.get(mod_name)
if not mod:
mod = self.load_api_handler(mod_name)
if not mod:
return None, None
return (mod, mod.get_func_handler(exp_name))
def call_api_func(self, mod, func, argv, ctx):
"""
Call the handler to implement the imported API
"""
return func(mod, self.emu, argv, ctx)
def call_data_func(self, mod, func, ptr):
"""
Call the handler to initialize and return imported data variables
"""
return func(mod, ptr)
+109
View File
@@ -0,0 +1,109 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
ARCH_X86 = 32
ARCH_AMD64 = 64
PAGE_SIZE = 0x1000
BITS_32 = 32
BITS_64 = 64
LSTAR = 0xC0000082
X86_REG_CS = 1001
X86_REG_DS = 1002
X86_REG_EAX = 1003
X86_REG_EBP = 1004
X86_REG_EBX = 1005
X86_REG_ECX = 1006
X86_REG_EDI = 1007
X86_REG_EDX = 1008
X86_REG_EFLAGS = 1009
X86_REG_EIP = 1010
X86_REG_EIZ = 1011
X86_REG_ESI = 1012
X86_REG_ESP = 1013
# Segment registers
X86_REG_FS = 1014
X86_REG_GS = 1015
X86_REG_ES = 1016
X86_REG_SS = 1017
X86_REG_CS = 1018
X86_REG_DS = 1019
X86_REG_MSR = 1020
# AMD64
AMD64_REG_RAX = 1021
AMD64_REG_RBP = 1022
AMD64_REG_RBX = 1023
AMD64_REG_RCX = 1024
AMD64_REG_RDI = 1025
AMD64_REG_RDX = 1026
AMD64_REG_RIP = 1027
AMD64_REG_RIZ = 1028
AMD64_REG_RSI = 1029
AMD64_REG_RSP = 1030
AMD64_REG_SIL = 1031
AMD64_REG_DIL = 1032
AMD64_REG_BPL = 1033
AMD64_REG_SPL = 1034
AMD64_REG_R8 = 1035
AMD64_REG_R9 = 1036
AMD64_REG_R10 = 1037
AMD64_REG_R11 = 1038
AMD64_REG_R12 = 1039
AMD64_REG_R13 = 1040
AMD64_REG_R14 = 1041
AMD64_REG_R15 = 1042
# Control Registers
X86_REG_CR0 = 1043
X86_REG_CR1 = 1044
X86_REG_CR2 = 1045
X86_REG_CR3 = 1046
X86_REG_CR4 = 1047
X86_REG_CR5 = 1048
X86_REG_CR6 = 1049
X86_REG_CR7 = 1050
X86_REG_CR8 = 1051
# Debug registers
X86_REG_DR0 = 1052
X86_REG_DR1 = 1053
X86_REG_DR2 = 1054
X86_REG_DR3 = 1055
X86_REG_DR4 = 1056
X86_REG_DR5 = 1057
X86_REG_DR6 = 1058
X86_REG_DR7 = 1059
X86_REG_DR8 = 1060
X86_REG_IDTR = 1061
X86_REG_GDTR = 1062
X86_REG_XMM0 = 1063
X86_REG_XMM1 = 1064
X86_REG_XMM2 = 1065
X86_REG_XMM3 = 1066
X86_REG_XMM4 = 1067
# Calling conventions
CALL_CONV_CDECL = 0
CALL_CONV_STDCALL = 1
CALL_CONV_FASTCALL = 2
CALL_CONV_FLOAT = 3
VAR_ARGS = -1
# Register string constants
REG_LOOKUP = {
# x86 registers
'eax': X86_REG_EAX, 'ebx': X86_REG_EBX, 'ecx': X86_REG_ECX, 'edx': X86_REG_EDX,
'edi': X86_REG_EDI, 'esi': X86_REG_ESI, 'ebp': X86_REG_EBP, 'esp': X86_REG_ESP,
'eip': X86_REG_EIP, 'eflags': X86_REG_EFLAGS,
# amd64 registers
'rax': AMD64_REG_RAX, 'rbx': AMD64_REG_RBX, 'rcx': AMD64_REG_RCX, 'rdx': AMD64_REG_RDX,
'rdi': AMD64_REG_RDI, 'rsi': AMD64_REG_RSI, 'rsp': AMD64_REG_RSP, 'rbp': AMD64_REG_RBP,
'r8': AMD64_REG_R8, 'r9': AMD64_REG_R9, 'r10': AMD64_REG_R10, 'r11': AMD64_REG_R11,
'r12': AMD64_REG_R12, 'r13': AMD64_REG_R13, 'r14': AMD64_REG_R14, 'r15': AMD64_REG_R15,
'rip': AMD64_REG_RIP
}
View File
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,14 @@
{
"file_list": [
{
"module_name": "default_exe",
"file_name": "default_exe.exe",
"size": 880
},
{
"module_name": "default_driver",
"file_name": "default_driver.sys",
"size": 496
}
]
}
Binary file not shown.
Binary file not shown.
+14
View File
@@ -0,0 +1,14 @@
{
"file_list": [
{
"module_name": "default_exe",
"file_name": "default_exe.exe",
"size": 912
},
{
"module_name": "default_driver",
"file_name": "default_driver.sys",
"size": 480
}
]
}
View File
+84
View File
@@ -0,0 +1,84 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
from speakeasy.struct import EmuStruct, Ptr
import ctypes as ct
class NDIS_OBJECT_HEADER(EmuStruct):
def __init__(self, ptr_size):
super().__init__(ptr_size)
self.Type = ct.c_uint8
self.Revision = ct.c_uint8
self.Size = ct.c_uint16
class NDIS_GENERIC_OBJECT(EmuStruct):
def __init__(self, ptr_size):
super().__init__(ptr_size)
self.Header = NDIS_OBJECT_HEADER
self.Caller = Ptr
self.CallersCaller = Ptr
self.DriverObject = Ptr
class NET_BUFFER_LIST_POOL_PARAMETERS(EmuStruct):
def __init__(self, ptr_size):
super().__init__(ptr_size)
self.Header = NDIS_OBJECT_HEADER
self.ProtocolId = ct.c_uint8
self.fAllocateNetBuffer = ct.c_uint8
self.ContextSize = ct.c_uint16
self.PoolTag = ct.c_uint32
self.DataSize = ct.c_uint32
class NET_BUFFER_LIST(EmuStruct):
def __init__(self, ptr_size):
super().__init__(ptr_size)
self.Next = Ptr
self.FirstNetBuffer = Ptr
self.Context = Ptr
self.ParentNetBufferList = Ptr
self.NdisPoolHandle = Ptr
self.NdisReserved = Ptr * 2
self.ProtocolReserved = Ptr * 4
self.MiniportReserved = Ptr * 2
self.Scratch = Ptr
self.SourceHandle = Ptr
self.NblFlags = ct.c_uint32
self.ChildRefCount = ct.c_uint32
self.Flags = ct.c_uint32
self.NetBufferListInfo = Ptr * 11
class NET_BUFFER_DATA(EmuStruct):
def __init__(self, ptr_size):
super().__init__(ptr_size)
self.Next = Ptr
self.CurrentMdl = Ptr
self.CurrentMdlOffset = ct.c_uint32
self.NbDataLength = ct.c_uint32
self.MdlChain = Ptr
self.DataOffset = ct.c_uint32
class NET_BUFFER_HEADER(EmuStruct):
def __init__(self, ptr_size):
super().__init__(ptr_size)
self.NetBufferData = NET_BUFFER_DATA
self.Link = Ptr
class NET_BUFFER(EmuStruct):
def __init__(self, ptr_size):
super().__init__(ptr_size)
self.Link = Ptr
self.NetBufferHeader = NET_BUFFER_HEADER
self.ChecksumBias = Ptr
self.Reserved = Ptr
self.NdisPoolHandle = Ptr
self.NdisReserved = Ptr * 2
self.ProtocolReserved = Ptr * 6
self.MiniportReserved = Ptr * 4
self.DataPhysicalAddress = ct.c_uint64
self.SharedMemoryInfo = Ptr
+235
View File
@@ -0,0 +1,235 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
from speakeasy.struct import Enum
WINDOWS_CONSOLE = 3
# IRQL Levels
PASSIVE_LEVEL = 0 # Passive release level
LOW_LEVEL = 0 # Lowest interrupt level
APC_LEVEL = 1 # APC interrupt level
DISPATCH_LEVEL = 2 # Dispatcher level
CMCI_LEVEL = 5 # CMCI handler level
PROFILE_LEVEL = 27 # timer used for profiling.
CLOCK1_LEVEL = 28 # Interval clock 1 level - Not used on x86
CLOCK2_LEVEL = 28 # Interval clock 2 level
IPI_LEVEL = 29 # Interprocessor interrupt level
POWER_LEVEL = 30 # Power failure level
HIGH_LEVEL = 31 # Highest interrupt level
STATUS_SUCCESS = 0
STATUS_BREAKPOINT = 0x80000003
STATUS_SINGLE_STEP = 0x80000004
STATUS_UNSUCCESSFUL = 0xC0000001
STATUS_INFO_LENGTH_MISMATCH = 0xC0000004
STATUS_ACCESS_VIOLATION = 0xC0000005
STATUS_INVALID_HANDLE = 0xC0000008
STATUS_ILLEGAL_INSTRUCTION = 0xC000001D
STATUS_PRIVILEGED_INSTRUCTION = 0xC0000096
STATUS_BUFFER_TOO_SMALL = 0xC0000023
STATUS_INVALID_CID = 0xC000000B
STATUS_INVALID_PARAMETER = 0xC000000D
STATUS_NOT_SUPPORTED = 0xC00000BB
STATUS_OBJECT_NAME_NOT_FOUND = 0xC0000034
STATUS_DEBUGGER_INACTIVE = 0xC0000354
STATUS_BAD_COMPRESSION_BUFFER = 0xC0000242
STATUS_UNSUPPORTED_COMPRESSION = 0xC000025F
STATUS_INVALID_DEVICE_REQUEST = 0xC0000010
STATUS_OBJECT_TYPE_MISMATCH = 0xC0000024
STATUS_NOINTERFACE = 0xC00002B9
STATUS_PORT_NOT_SET = 0xC0000353
# Device Flags
DO_DIRECT_IO = 0x00000010
DO_BUFFERED_IO = 0x00000004
DO_EXCLUSIVE = 0x00000008
DO_DEVICE_INITIALIZING = 0x00000080
IRP_MJ_CREATE = 0x00
IRP_MJ_CREATE_NAMED_PIPE = 0x01
IRP_MJ_CLOSE = 0x02
IRP_MJ_READ = 0x03
IRP_MJ_WRITE = 0x04
IRP_MJ_QUERY_INFORMATION = 0x05
IRP_MJ_SET_INFORMATION = 0x06
IRP_MJ_QUERY_EA = 0x07
IRP_MJ_SET_EA = 0x08
IRP_MJ_FLUSH_BUFFERS = 0x09
IRP_MJ_QUERY_VOLUME_INFORMATION = 0x0a
IRP_MJ_SET_VOLUME_INFORMATION = 0x0b
IRP_MJ_DIRECTORY_CONTROL = 0x0c
IRP_MJ_FILE_SYSTEM_CONTROL = 0x0d
IRP_MJ_DEVICE_CONTROL = 0x0e
IRP_MJ_INTERNAL_DEVICE_CONTROL = 0x0f
IRP_MJ_SHUTDOWN = 0x10
IRP_MJ_LOCK_CONTROL = 0x11
IRP_MJ_CLEANUP = 0x12
IRP_MJ_CREATE_MAILSLOT = 0x13
IRP_MJ_QUERY_SECURITY = 0x14
IRP_MJ_SET_SECURITY = 0x15
IRP_MJ_POWER = 0x16
IRP_MJ_SYSTEM_CONTROL = 0x17
IRP_MJ_DEVICE_CHANGE = 0x18
IRP_MJ_QUERY_QUOTA = 0x19
IRP_MJ_SET_QUOTA = 0x1a
IRP_MJ_PNP = 0x1b
IRP_MJ_PNP_POWER = IRP_MJ_PNP
IRP_MJ_MAXIMUM_FUNCTION = 0x1b
COMPRESSION_FORMAT_LZNT1 = 0x2
COMPRESSION_FORMAT_XPRESS = 0x3
DELETE = 0x00010000
READ_CONTROL = 0x00020000
WRITE_DAC = 0x00040000
WRITE_OWNER = 0x00080000
SYNCHRONIZE = 0x00100000
GENERIC_READ = 0x80000000
GENERIC_WRITE = 0x40000000
GENERIC_EXECUTE = 0x20000000
GENERIC_ALL = 0x10000000
# Create Dispositions
FILE_SUPERSEDE = 0x00000000
FILE_OPEN = 0x00000001
FILE_CREATE = 0x00000002
FILE_OPEN_IF = 0x00000003
FILE_OVERWRITE = 0x00000004
FILE_OVERWRITE_IF = 0x00000005
# File specific access mask
FILE_READ_DATA = 0x0001 # file & pipe
FILE_WRITE_DATA = 0x0002 # file & pipe
FILE_APPEND_DATA = 0x0004 # file
FILE_READ_EA = 0x0008 # file & directory
FILE_WRITE_EA = 0x0010 # file & directory
FILE_EXECUTE = 0x0020 # file
FILE_DELETE_CHILD = 0x0040 # directory
FILE_READ_ATTRIBUTES = 0x0080 # all
FILE_WRITE_ATTRIBUTES = 0x0100 # all
PROCESSINFOCLASS = Enum()
PROCESSINFOCLASS.ProcessBasicInformation = 0
PROCESSINFOCLASS.ProcessDebugPort = 7
PROCESSINFOCLASS.ProcessWow64Information = 0x1a
PROCESSINFOCLASS.ProcessImageFileName = 0x1b
PROCESSINFOCLASS.ProcessBreakOnTermination = 0x1d
PROCESSINFOCLASS.ProcessDebugObjectHandle = 0x1e
PROCESSINFOCLASS.ProcessProtectionInformation = 0x3d
SYSTEM_INFORMATION_CLASS = Enum()
SYSTEM_INFORMATION_CLASS.SystemBasicInformation = 0x00
SYSTEM_INFORMATION_CLASS.SystemProcessorInformation = 0x01
SYSTEM_INFORMATION_CLASS.SystemPerformanceInformation = 0x02
SYSTEM_INFORMATION_CLASS.SystemTimeOfDayInformation = 0x03
SYSTEM_INFORMATION_CLASS.SystemPathInformation = 0x04
SYSTEM_INFORMATION_CLASS.SystemProcessInformation = 0x05
SYSTEM_INFORMATION_CLASS.SystemCallCountInformation = 0x06
SYSTEM_INFORMATION_CLASS.SystemDeviceInformation = 0x07
SYSTEM_INFORMATION_CLASS.SystemProcessorPerformanceInformation = 0x08
SYSTEM_INFORMATION_CLASS.SystemFlagsInformation = 0x09
SYSTEM_INFORMATION_CLASS.SystemCallTimeInformation = 0x0A
SYSTEM_INFORMATION_CLASS.SystemModuleInformation = 0x0B
SYSTEM_INFORMATION_CLASS.SystemKernelDebuggerInformation = 0x23
FILE_INFORMATION_CLASS = Enum()
FILE_INFORMATION_CLASS.FileDirectoryInformation = 1
FILE_INFORMATION_CLASS.FileFullDirectoryInformation = 2
FILE_INFORMATION_CLASS.FileBothDirectoryInformation = 3
FILE_INFORMATION_CLASS.FileBasicInformation = 4
FILE_INFORMATION_CLASS.FileStandardInformation = 5
FILE_INFORMATION_CLASS.FileInternalInformation = 6
FILE_INFORMATION_CLASS.FileEaInformation = 7
FILE_INFORMATION_CLASS.FileAccessInformation = 8
FILE_INFORMATION_CLASS.FileNameInformation = 9
FILE_INFORMATION_CLASS.FileRenameInformation = 10
FILE_INFORMATION_CLASS.FileLinkInformation = 11
POOL_TYPE = Enum()
POOL_TYPE.NonPagedPool = 0
POOL_TYPE.PagedPool = 1
POOL_TYPE.NonPagedPoolMustSucceed = 2
POOL_TYPE.DontUseThisType = 3
POOL_TYPE.NonPagedPoolCacheAligned = 4
POOL_TYPE.PagedPoolCacheAligned = 5
POOL_TYPE.NonPagedPoolCacheAlignedMustS = 6
POOL_TYPE.MaxPoolType = 7
POOL_TYPE.NonPagedPoolSession = 32
POOL_TYPE.PagedPoolSession = 33
POOL_TYPE.NonPagedPoolMustSucceedSession = 34
POOL_TYPE.DontUseThisTypeSession = 35
POOL_TYPE.NonPagedPoolCacheAlignedSession = 36
POOL_TYPE.PagedPoolCacheAlignedSession = 37
POOL_TYPE.NonPagedPoolCacheAlignedMustSSession = 38
POOL_TYPE.NonPagedPoolNx = 512
MODE = Enum()
MODE.KernelMode = 0
MODE.UserMode = 1
MODE.MaximumMode = 2
IMAGE_DOS_SIGNATURE = b'MZ'
PE32_BIT = 0x0100
PE32_PLUS_BIT = 0x0200
def get_flag_defines(flags, prefix=''):
defs = []
for k, v in globals().items():
if isinstance(v, int):
if v & flags:
if prefix:
if k.startswith(prefix):
defs.append(k)
else:
defs.append(k)
return defs
def get_const_defines(const, prefix=''):
defs = []
for k, v in globals().items():
if isinstance(v, int):
if v == const:
if prefix:
if k.startswith(prefix):
defs.append(k)
else:
defs.append(k)
return defs
def get_access_defines(flags):
defs = []
accesses = ('DELETE', 'READ_CONTROL', 'WRITE_DAC',
'WRITE_OWNER', 'SYNCHRONIZE', 'GENERIC_READ',
'GENERIC_WRITE', 'GENERIC_EXECUTE', 'GENERIC_ALL')
for k, v in [(k, v) for k, v in globals().items() if k in accesses]:
if isinstance(v, int):
if v & flags:
defs.append(k)
return defs
def get_file_access_defines(flags):
defs = get_flag_defines(flags, 'FILE_')
defs = [d for d in defs if d.startswith(('FILE_READ', 'FILE_WRITE',
'FILE_DELETE', 'FILE_APPEND', 'FILE_EXECUTE'))]
return defs
def get_create_disposition(disp):
defs = get_const_defines(disp, 'FILE_')
defs = [d for d in defs if not d.startswith('FILE_SHARE')]
ret = 0
if len(defs):
ret = defs[0]
return ret
+551
View File
@@ -0,0 +1,551 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
from speakeasy.struct import EmuStruct, EmuUnion, Ptr
import ctypes as ct
from speakeasy.winenv.defs import * # noqa
class KSYSTEM_TIME(EmuStruct):
def __init__(self, ptr_size):
super().__init__(ptr_size)
self.LowPart = ct.c_uint32
self.High1Time = ct.c_uint32
self.High2Time = ct.c_uint32
class SSDT(EmuStruct): # KeServiceDescriptorTable
def __init__(self, ptr_size):
super().__init__(ptr_size)
self.pServiceTable = Ptr
self.pCounterTable = Ptr
self.NumberOfServices = ct.c_uint32
self.pArgumentTable = Ptr
class UNICODE_STRING(EmuStruct):
def __init__(self, ptr_size):
super().__init__(ptr_size)
self.Length = ct.c_uint16
self.MaximumLength = ct.c_uint16
self.Buffer = Ptr
class DeviceIoControl(EmuStruct):
def __init__(self, ptr_size):
super().__init__(ptr_size)
self.OutputBufferLength = ct.c_uint32
self.InputBufferLength = ct.c_uint32
self.IoControlCode = ct.c_uint32
self.Type3InputBuffer = Ptr
class STRING(EmuStruct):
def __init__(self, ptr_size):
super().__init__(ptr_size)
self.Length = ct.c_uint16
self.MaximumLength = ct.c_uint16
self.Buffer = Ptr
class SYSTEM_MODULE(EmuStruct):
def __init__(self, ptr_size):
super().__init__(ptr_size)
self.Reserved = Ptr * 2
self.Base = Ptr
self.Size = ct.c_uint32
self.Flags = ct.c_uint32
self.Index = ct.c_uint16
self.Unknown = ct.c_uint16
self.LoadCount = ct.c_uint16
self.ModuleNameOffset = ct.c_uint16
self.ImageName = ct.c_uint8 * 256
class SYSTEM_TIMEOFDAY_INFORMATION(EmuStruct):
def __init__(self, ptr_size):
super().__init__(ptr_size)
self.BootTime = ct.c_uint64
self.CurrentTime = ct.c_uint64
self.TimeZoneBias = ct.c_uint64
self.TimeZoneId = ct.c_uint32
self.Reserved = ct.c_uint32
self.BootTimeBias = ct.c_uint64
self.SleepTimeBias = ct.c_uint64
class SYSTEM_PROCESS_INFORMATION(EmuStruct):
def __init__(self, ptr_size):
super().__init__(ptr_size)
self.NextEntryOffset = ct.c_uint32
self.NumberOfThreads = ct.c_uint32
self.Reserved1 = ct.c_uint8 * 48
self.ImageName = UNICODE_STRING
self.BasePriority = ct.c_uint32
self.UniqueProcessId = Ptr
self.InheritedFromUniqueProcessId = Ptr
self.HandleCount = ct.c_uint32
self.SessionId = ct.c_uint32
self.UniqueProcessKey = Ptr
self.PeakVirtualSize = Ptr
self.VirtualSize = Ptr
self.PageFaultCount = ct.c_uint32
self.PeakWorkingSetSize = Ptr
self.WorkingSetSize = Ptr
self.QuotaPeakPagedPoolUsage = Ptr
self.QuotaPagedPoolUsage = Ptr
self.QuotaPeakNonPagedPoolUsage = Ptr
self.QuotaNonPagedPoolUsage = Ptr
self.PagefileUsage = Ptr
self.PeakPagefileUsage = Ptr
self.PrivatePageCount = Ptr
self.Reserved7 = LARGE_INTEGER * 6
class SYSTEM_THREAD_INFORMATION(EmuStruct):
def __init__(self, ptr_size):
super().__init__(ptr_size)
self.Reserved1 = LARGE_INTEGER * 3
self.Reserved2 = ct.c_uint32
self.StartAddress = Ptr
self.ClientId = CLIENT_ID
self.Priority = ct.c_uint32
self.BasePriority = ct.c_uint32
self.ContextSwitches = ct.c_uint32
self.ThreadState = ct.c_uint32
self.WaitReason = ct.c_uint32
class CLIENT_ID(EmuStruct):
def __init__(self, ptr_size):
super().__init__(ptr_size)
self.UniqueProcess = ct.c_uint32
self.UniqueThread = ct.c_uint32
class MDL(EmuStruct):
def __init__(self, ptr_size):
super().__init__(ptr_size)
self.Next = Ptr
self.Size = ct.c_uint16
self.MdlFlags = ct.c_uint16
self.Process = Ptr
self.MappedSystemVa = Ptr
self.StartVa = Ptr
self.ByteCount = ct.c_uint32
self.ByteOffset = ct.c_uint32
class KIDTENTRY(EmuStruct):
def __init__(self, ptr_size):
super().__init__(ptr_size)
self.OffsetLow = ct.c_uint16
self.Selector = ct.c_uint16
self.Base = ct.c_uint32
class KIDTENTRY64(EmuStruct):
def __init__(self, ptr_size):
super().__init__(ptr_size)
self.OffsetLow = ct.c_uint16
self.Selector = ct.c_uint16
self.Reserved0 = ct.c_uint16
self.OffsetMiddle = ct.c_uint16
self.OffsetHigh = ct.c_uint32
self.Reserved1 = ct.c_uint32
class ETHREAD(EmuStruct):
def __init__(self, ptr_size):
super().__init__(ptr_size)
self.Data = ct.c_uint8 * 4096
class EPROCESS(EmuStruct):
def __init__(self, ptr_size):
super().__init__(ptr_size)
self.Data = ct.c_uint8 * 4096
class KEVENT(EmuStruct):
def __init__(self, ptr_size):
super().__init__(ptr_size)
self.Data = ct.c_uint8 * 4096
class MUTANT(EmuStruct):
def __init__(self, ptr_size):
super().__init__(ptr_size)
self.Data = ct.c_uint8 * 4096
class RTL_OSVERSIONINFOW(EmuStruct):
def __init__(self, ptr_size):
super().__init__(ptr_size)
self.dwOSVersionInfoSize = ct.c_uint32
self.dwMajorVersion = ct.c_uint32
self.dwMinorVersion = ct.c_uint32
self.dwBuildNumber = ct.c_uint32
self.dwPlatformId = ct.c_uint32
self.szCSDVersion = ct.c_uint8 * 256
class RTL_OSVERSIONINFOEXW(EmuStruct):
def __init__(self, ptr_size):
super().__init__(ptr_size)
self.dwOSVersionInfoSize = ct.c_uint32
self.dwMajorVersion = ct.c_uint32
self.dwMinorVersion = ct.c_uint32
self.dwBuildNumber = ct.c_uint32
self.dwPlatformId = ct.c_uint32
self.szCSDVersion = ct.c_uint8 * 256
self.wServicePackMajor = ct.c_uint16
self.wServicePackMinor = ct.c_uint16
self.wSuiteMask = ct.c_uint16
self.wProductType = ct.c_uint8
self.wReserved = ct.c_uint8
class IDT(EmuStruct):
def __init__(self, ptr_size):
super().__init__(ptr_size)
self.Limit = ct.c_uint16
self.Descriptors = Ptr
class KAPC(EmuStruct):
def __init__(self, ptr_size):
super().__init__(ptr_size)
self.Type = ct.c_uint8
self.SpareByte0 = ct.c_uint8
self.Size = ct.c_uint8
self.SpareByte1 = ct.c_uint8
self.SpareLong0 = ct.c_uint32
self.Thread = Ptr
self.ApcListEntry = LIST_ENTRY
self.KernelRoutine = Ptr
self.RundownRoutine = Ptr
self.NormalRoutine = Ptr
self.NormalContext = Ptr
self.SystemArgument1 = Ptr
self.SystemArgument2 = Ptr
self.ApcStateIndex = ct.c_uint8
self.ApcMode = ct.c_uint8
self.Inserted = ct.c_uint8
class OBJECT_ATTRIBUTES(EmuStruct):
def __init__(self, ptr_size):
super().__init__(ptr_size)
self.Length = ct.c_uint32
self.RootDirectory = Ptr
self.ObjectName = Ptr
self.Attributes = ct.c_uint32
self.SecurityDescriptor = Ptr
self.SecurityQualityOfService = Ptr
class FILE_STANDARD_INFORMATION(EmuStruct):
def __init__(self, ptr_size):
super().__init__(ptr_size)
self.AllocationSize = LARGE_INTEGER
self.EndOfFile = LARGE_INTEGER
self.NumberOfLinks = ct.c_uint32
self.DeletePending = ct.c_uint8
self.Directory = ct.c_uint8
class DESCRIPTOR_TABLE(EmuStruct):
def __init__(self, ptr_size):
super().__init__(ptr_size)
if ptr_size == 4:
self.Table = KIDTENTRY * 256
else:
self.Table = KIDTENTRY64 * 256
class DRIVER_OBJECT(EmuStruct):
def __init__(self, ptr_size):
super().__init__(ptr_size)
self.Type = ct.c_uint16
self.Size = ct.c_uint16
self.DeviceObject = Ptr
self.Flags = ct.c_uint32
self.DriverStart = Ptr
self.DriverSize = ct.c_uint32
self.DriverSection = Ptr
self.DriverExtension = Ptr
self.DriverName = UNICODE_STRING
self.HardwareDatabase = Ptr
self.FastIoDispatch = Ptr
self.DriverInit = Ptr
self.DriverStartIo = Ptr
self.DriverUnload = Ptr
self.MajorFunction = Ptr * 28
class KDEVICE_QUEUE(EmuStruct):
def __init__(self, ptr_size):
super().__init__(ptr_size)
self.Type = ct.c_uint16
self.Size = ct.c_uint16
self.DeviceListHead = LIST_ENTRY
self.Lock = ct.c_uint64
self.Busy = ct.c_uint8
class KDPC(EmuStruct):
def __init__(self, ptr_size):
super().__init__(ptr_size)
self.Type = ct.c_uint8
self.Importance = ct.c_uint8
self.Number = ct.c_uint16
self.DpcListEntry = LIST_ENTRY
self.DeferredRoutine = Ptr
self.DeferredContext = Ptr
self.SystemArgument1 = Ptr
self.SystemArgument2 = Ptr
self.DpcData = Ptr
class DEVICE_OBJECT(EmuStruct):
def __init__(self, ptr_size):
super().__init__(ptr_size)
self.Type = ct.c_uint16
self.Size = ct.c_uint16
self.ReferenceCount = ct.c_uint32
self.DriverObject = Ptr
self.NextDevice = Ptr
self.AttachedDevice = Ptr
self.CurrentIrp = Ptr
self.Timer = Ptr
self.Flags = ct.c_uint32
self.Characteristics = ct.c_uint32
self.Vpb = Ptr
self.DeviceExtension = Ptr
self.DeviceType = ct.c_uint32
self.StackSize = ct.c_uint8
self.Queue = LIST_ENTRY
self.AlignmentRequirement = ct.c_uint32
self.DeviceQueue = KDEVICE_QUEUE
self.Dpc = KDPC
self.ActiveThreadCount = ct.c_uint32
self.SecurityDescriptor = Ptr
self.DeviceLock = KEVENT
self.SectorSize = ct.c_uint16
self.Spare1 = ct.c_uint16
self.DeviceObjectExtension = Ptr
self.Reserved = Ptr
class FILE_OBJECT(EmuStruct):
def __init__(self, ptr_size):
super().__init__(ptr_size)
self.Type = ct.c_uint16
self.Size = ct.c_uint16
self.DeviceObject = Ptr
self.Vpb = Ptr
self.FsContext = Ptr
self.FsContext2 = Ptr
self.SectionObjectPointer = Ptr
self.PrivateCacheMap = Ptr
self.FinalStatus = ct.c_uint32
self.RelatedFileObject = Ptr
self.LockOperation = ct.c_uint8
self.DeletePending = ct.c_uint8
self.ReadAccess = ct.c_uint8
self.WriteAccess = ct.c_uint8
self.DeleteAccess = ct.c_uint8
self.SharedRead = ct.c_uint8
self.SharedWrite = ct.c_uint8
self.SharedDelete = ct.c_uint8
self.Flags = ct.c_uint32
self.FileName = UNICODE_STRING
self.CurrentByteOffset = LARGE_INTEGER
self.Waiters = ct.c_uint32
self.Busy = ct.c_uint32
self.LastLock = Ptr
self.Lock = KEVENT
self.Event = KEVENT
self.CompletionContext = Ptr
self.IrpListLock = ct.c_uint32
self.IrpList = LIST_ENTRY
self.FileObjectExtension = Ptr
class IO_PARAMETERS(EmuUnion):
def __init__(self, ptr_size):
super().__init__(ptr_size)
self.DeviceIoControl = DeviceIoControl(ptr_size).get_cstruct()
class IO_STACK_LOCATION(EmuStruct):
def __init__(self, ptr_size):
super().__init__(ptr_size)
self.MajorFunction = ct.c_uint8
self.MinorFunction = ct.c_uint8
self.Flags = ct.c_uint8
self.Control = ct.c_uint8
if ptr_size == 8:
self._padding = ct.c_uint8 * 8
self.Parameters = IO_PARAMETERS
self.DeviceObject = Ptr
self.FileObject = Ptr
self.CompletionRoutine = Ptr
self.Context = Ptr
class IRP_OVERLAY(EmuStruct):
def __init__(self, ptr_size):
super().__init__(ptr_size)
self.UserApcRoutine = Ptr
self.UserApcContext = Ptr
class IRP(EmuStruct):
def __init__(self, ptr_size):
super().__init__(ptr_size)
self.Type = ct.c_uint16
self.Size = ct.c_uint16
self.MdlAddress = Ptr
self.Flags = ct.c_uint32
self.AssociatedIrp = Ptr
self.ThreadListEntry = LIST_ENTRY
self.IoStatus = IO_STATUS_BLOCK
self.RequestorMode = ct.c_uint8
self.PendingReturned = ct.c_uint8
self.StackCount = ct.c_uint8
self.CurrentLocation = ct.c_uint8
self.Cancel = ct.c_uint8
self.CancelIrql = ct.c_uint8
self.ApcEnvironment = ct.c_uint8
self.AllocationFlags = ct.c_uint8
self.UserIosb = Ptr
self.UserEvent = Ptr
self.Overlay = IRP_OVERLAY
self.CancelRoutine = Ptr
self.UserBuffer = Ptr
self.Tail = IRP_TAIL
class IO_STATUS_BLOCK(EmuStruct):
def __init__(self, ptr_size):
super().__init__(ptr_size)
self.Status = Ptr
self.Information = Ptr
class KDEVICE_QUEUE_ENTRY(EmuStruct):
def __init__(self, ptr_size):
super().__init__(ptr_size)
self.DeviceListEntry = LIST_ENTRY
self.SortKey = ct.c_uint32
self.Inserted = ct.c_uint8
class TAIL_OVERLAY(EmuStruct):
def __init__(self, ptr_size):
super().__init__(ptr_size)
self.DeviceQueueEntry = KDEVICE_QUEUE_ENTRY
if ptr_size == 8:
self.padding = ct.c_uint8 * 8
self.Reserved1 = Ptr * 2
self.ListEntry = LIST_ENTRY
self.CurrentStackLocation = Ptr
self.Reserved2 = Ptr
class IRP_TAIL(EmuStruct):
def __init__(self, ptr_size):
super().__init__(ptr_size)
self.Overlay = TAIL_OVERLAY
class LIST_ENTRY(EmuStruct):
def __init__(self, ptr_size):
super().__init__(ptr_size)
self.Flink = Ptr
self.Blink = Ptr
class NT_TIB(EmuStruct):
def __init__(self, ptr_size):
super().__init__(ptr_size)
self.ExceptionList = Ptr
self.StackBase = Ptr
self.StackLimit = Ptr
self.Reserved1 = Ptr
self.Reserved2 = Ptr
self.Reserved3 = Ptr
self.Self = Ptr
class TEB(EmuStruct):
def __init__(self, ptr_size):
super().__init__(ptr_size)
self.NtTib = NT_TIB
self.Reserved1 = Ptr
self.ClientId = CLIENT_ID
if ptr_size == 8:
self.pad0 = Ptr
self.Reserved2 = Ptr
self.Reserved3 = Ptr
self.ProcessEnvironmentBlock = Ptr
self.LastErrorValue = ct.c_uint32
self.Reserved4 = ct.c_uint32
self.Reserved5 = Ptr
self.Reserved6 = Ptr
class PEB(EmuStruct):
def __init__(self, ptr_size):
super().__init__(ptr_size)
self.InheritedAddressSpace = ct.c_uint8
self.ReadImageFileExecOptions = ct.c_uint8
self.BeingDebugged = ct.c_uint8
self.BitField = ct.c_uint8
self.Mutant = Ptr
self.ImageBaseAddress = Ptr
self.Ldr = Ptr
self.ProcessParameters = Ptr
self.SubSystemData = Ptr
self.ProcessHeap = Ptr
class PEB_LDR_DATA(EmuStruct):
def __init__(self, ptr_size):
super().__init__(ptr_size)
self.Length = ct.c_uint32
self.Initialized = ct.c_uint8
self.SsHandle = Ptr
self.InLoadOrderModuleList = LIST_ENTRY
self.InMemoryOrderModuleList = LIST_ENTRY
self.InInitializationOrderModuleList = LIST_ENTRY
self.EntryInProgress = Ptr
self.ShutdownInProgress = ct.c_uint8
self.ShutdownThreadId = Ptr
class LDR_DATA_TABLE_ENTRY(EmuStruct):
def __init__(self, ptr_size):
super().__init__(ptr_size)
self.InLoadOrderLinks = LIST_ENTRY
self.InMemoryOrderLinks = LIST_ENTRY
self.InInitializationOrderLinks = LIST_ENTRY
self.DllBase = Ptr
self.EntryPoint = Ptr
self.SizeOfImage = ct.c_uint32
self.FullDllName = UNICODE_STRING
self.BaseDllName = UNICODE_STRING
self.Flags = ct.c_uint32
self.LoadCount = ct.c_uint16
class LARGE_INTEGER(EmuStruct):
def __init__(self, ptr_size):
super().__init__(ptr_size)
self.LowPart = ct.c_uint32
self.HighPart = ct.c_uint32
+87
View File
@@ -0,0 +1,87 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
from speakeasy.struct import EmuStruct, Enum
import ctypes as ct
REG_NONE = 0
REG_SZ = 1
REG_EXPAND_SZ = 2
REG_BINARY = 3
REG_DWORD = 4
REG_MULTI_SZ = 7
REG_QWORD = 11
RTL_REGISTRY_ABSOLUTE = 0
RTL_REGISTRY_SERVICES = 1
RTL_REGISTRY_CONTROL = 2
RTL_REGISTRY_WINDOWS_NT = 3
RTL_REGISTRY_DEVICEMAP = 4
RTL_REGISTRY_USER = 5
RTL_REGISTRY_MAXIMUM = 6
HKEY_CLASSES_ROOT = 0x80000000
HKEY_CURRENT_USER = 0x80000001
HKEY_LOCAL_MACHINE = 0x80000002
HKEY_USERS = 0x80000003
HKEY_CLASSES_ROOT = 0x80000005
KEY_VALUE_INFORMATION_CLASS = Enum()
KEY_VALUE_INFORMATION_CLASS.KeyValueBasicInformation = 0x00
KEY_VALUE_INFORMATION_CLASS.KeyValueFullInformation = 0x01
KEY_VALUE_INFORMATION_CLASS.KeyValuePartialInformation = 0x02
KEY_VALUE_INFORMATION_CLASS.KeyValueFullInformationAlign64 = 0x03
KEY_VALUE_INFORMATION_CLASS.KeyValuePartialInformationAlign64 = 0x04
KEY_VALUE_INFORMATION_CLASS.KeyValueLayerInformation = 0x05
KEY_VALUE_INFORMATION_CLASS.MaxKeyValueInfoClass = 0x06
class KEY_VALUE_PARTIAL_INFORMATION(EmuStruct):
def __init__(self, ptr_size):
super().__init__(ptr_size)
self.TitleIndex = ct.c_uint32
self.Type = ct.c_uint32
self.DataLength = ct.c_uint32
# Data[1]
class KEY_VALUE_BASIC_INFORMATION(EmuStruct):
def __init__(self, ptr_size):
super().__init__(ptr_size)
self.TitleIndex = ct.c_uint32
self.Type = ct.c_uint32
self.NameLength = ct.c_uint32
# Name[1]
class KEY_VALUE_FULL_INFORMATION(EmuStruct):
def __init__(self, ptr_size):
super().__init__(ptr_size)
self.TitleIndex = ct.c_uint32
self.Type = ct.c_uint32
self.DataOffset = ct.c_uint32
self.DataLength = ct.c_uint32
self.NameLength = ct.c_uint32
def get_defines(define, prefix=''):
for k, v in globals().items():
if not isinstance(v, int) or v != define:
continue
if prefix:
if k.startswith(prefix):
return k
else:
return k
def get_flag_value(flag):
return globals().get(flag)
def get_value_type(define):
return get_defines(define, prefix='REG_')
def get_hkey_type(define):
return get_defines(define, prefix='HKEY_')
+84
View File
@@ -0,0 +1,84 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
from speakeasy.struct import EmuStruct
import ctypes as ct
USB_DEVICE_DESCRIPTOR_TYPE = 0x01
USB_CONFIGURATION_DESCRIPTOR_TYPE = 0x02
USB_STRING_DESCRIPTOR_TYPE = 0x03
USB_INTERFACE_DESCRIPTOR_TYPE = 0x04
USB_ENDPOINT_DESCRIPTOR_TYPE = 0x05
USB_ENDPOINT_TYPE_MASK = 0x03
USB_ENDPOINT_TYPE_CONTROL = 0x00
USB_ENDPOINT_TYPE_ISOCHRONOUS = 0x01
USB_ENDPOINT_TYPE_BULK = 0x02
USB_ENDPOINT_TYPE_INTERRUPT = 0x03
USB_DIR_IN = 0x80
USB_DIR_OUT = 0x00
class USB_DEVICE_DESCRIPTOR(EmuStruct):
def __init__(self):
super().__init__()
self.bLength = ct.c_uint8
self.bDescriptorType = ct.c_uint8
self.bcdUSB = ct.c_uint16
self.bDeviceClass = ct.c_uint8
self.bDeviceSubClass = ct.c_uint8
self.bDeviceProtocol = ct.c_uint8
self.bMaxPacketSize0 = ct.c_uint8
self.idVendor = ct.c_uint16
self.idProduct = ct.c_uint16
self.bcdDevice = ct.c_uint16
self.iManufacturer = ct.c_uint8
self.iProduct = ct.c_uint8
self.iSerialNumber = ct.c_uint8
self.bNumConfigurations = ct.c_uint8
class USB_CONFIGURATION_DESCRIPTOR(EmuStruct):
def __init__(self):
super().__init__()
self.bLength = ct.c_uint8
self.bDescriptorType = ct.c_uint8
self.wTotalLength = ct.c_uint16
self.bNumInterfaces = ct.c_uint8
self.bConfigurationValue = ct.c_uint8
self.iConfiguration = ct.c_uint8
self.bmAttributes = ct.c_uint8
self.MaxPower = ct.c_uint8
class USB_INTERFACE_DESCRIPTOR(EmuStruct):
def __init__(self):
super().__init__()
self.bLength = ct.c_uint8
self.bDescriptorType = ct.c_uint8
self.bInterfaceNumber = ct.c_uint8
self.bAlternateSetting = ct.c_uint8
self.bNumEndpoints = ct.c_uint8
self.bInterfaceClass = ct.c_uint8
self.bInterfaceSubClass = ct.c_uint8
self.bInterfaceProtocol = ct.c_uint8
self.iInterface = ct.c_uint8
class USB_ENDPOINT_DESCRIPTOR(EmuStruct):
def __init__(self):
super().__init__()
self.bLength = ct.c_uint8
self.bDescriptorType = ct.c_uint8
self.bEndpointAddress = ct.c_uint8
self.bmAttributes = ct.c_uint8
self.wMaxPacketSize = ct.c_uint16
self.bInterval = ct.c_uint8
class USBD_VERSION_INFORMATION(EmuStruct):
def __init__(self, ptr_size):
super().__init__(ptr_size)
self.USBDI_Version = ct.c_uint32
self.Supported_USB_Version = ct.c_uint32

Some files were not shown because too many files have changed in this diff Show More