mirror of
https://github.com/mandiant/speakeasy.git
synced 2026-10-12 02:18:47 +00:00
Initial commit
This commit is contained in:
+106
@@ -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
@@ -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
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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).
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
|
||||
@@ -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": []
|
||||
}
|
||||
]
|
||||
|
||||
```
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -0,0 +1,5 @@
|
||||
pefile
|
||||
capstone
|
||||
lznt1
|
||||
unicorn==1.0.2rc4
|
||||
jsonschema
|
||||
@@ -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)
|
||||
@@ -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',
|
||||
)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
|
||||
|
||||
__all__ = ["unicorn_eng"]
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
瑞瑞瑞瑞瑞瑞瑞瑞フ
|
||||
@@ -0,0 +1 @@
|
||||
GIF89a��������������Ã
|
||||
|
After Width: | Height: | Size: 21 B |
@@ -0,0 +1 @@
|
||||
瑞瑞瑞瑞瑞瑞静
|
||||
@@ -0,0 +1 @@
|
||||
瑞瑞瑞瑞瑞瑞瑞瑞昔フフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフ
|
||||
Binary file not shown.
@@ -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)
|
||||
@@ -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))
|
||||
@@ -0,0 +1 @@
|
||||
__version__ = "1.4.4"
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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())
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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
|
||||
}
|
||||
Binary file not shown.
BIN
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
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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_')
|
||||
@@ -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
Reference in New Issue
Block a user