Under the Hood: SCYTHE Architectural Overview (Part 1)

Hey, this is Ateeq  Sharfuddin, head of the engineering team at SCYTHE. Our team has spent the better part of the past year developing significant improvements for version 3 of the SCYTHE platform. As the threat landscape, including adversary tactics, techniques, and procedures (TTPs), constantly evolves, developing an adversary emulation platform must be similarly agile and updated. The v3 release offered a good opportunity for  a series of posts to cover SCYTHE's overall architecture in totality and provide a technical deeper dive under the hood. SCYTHE was designed to emulate [1]   any  malware [2] , and in these series of posts the engineering team will describe how the architecture accomplishes this goal. In part 1, we focus on SCYTHE's client architecture. We aim to be precise in our statements and in doing so, support you in making implementation decisions. Much of the information is presented abstractly: we only delve into specifics where important.

Quick Overall Architecture

The overall SCYTHE architecture consists of a database, servers, clients, and droppers [3] . The architecture (both client and server) was designed in 2016 by me and Benjamin Dagana , who provides technical leadership at GRIMM . We designed the architecture with the intent to augment it over time. In this post, we only cover the client. As a simplification, we will sometimes use the term SCYTHE  to also refer to some server-side component. For example: the client transmitted a message back to SCYTHE , SCYTHE refers to some server-side component in this architecture. Other times, SCYTHE refers to the entire platform.

Abstract Client

A client is a program [5]  generated with a 3-tuple ( I , R , M ) at the request of an operator using the Management Console, where I  is a sequence [6]  of instructions, R  is a sequence of runtimes [7] , and M  is a sequence of modules [8] . When the client starts, the client performs the sequence of work as set forth in the instructions I , starting with instruction i 0 , then proceeding to i 1 , i 2 , i 3 , and so forth, for instructions i n  in   I  where n  is a non-negative integer . Based on the instructions in I , the client may load runtimes r  in R  or modules m  in M , and these runtimes and modules may insert additional instructions I r k  or I m l  into I .

Abstract runtime

A runtime is an abstract machine that translates and dispatches runtime instructions into native instructions run as the client. In other words, some instructions in I  may be runtime instructions I r k . These instructions would need to run in runtime r , which translates i r k  into a native instruction, executes it, and captures the result. This result can be used as an input to another runtime instruction. In this manner, entire modules may be implemented in runtime instructions whereby the modules run entirely on top of some chosen runtime: These are non-native modules.

Abstract module

A module is a sequence of instructions that, given inputs, performs work requested by the inputs and produces some output. A module can run entirely on top of a runtime or run entirely natively. A module is a 2-tuple ( C , F ), where C  is configuration and F  is a set of functions { init , deinit , getinfo , and run }. Each function may accept zero or more inputs and returns an output. The Module Development Guides provided in the SDK  cover module design extensively.

Notice that any number of malware fit the above definition of an abstract client. Specifically, if you wrote a relay that translates instructions from SCYTHE into instructions a malware can process and vice versa, this malware is  a client by the definition above. Therefore, SCYTHE can emulate this malware.

SCYTHE Client

SCYTHE provides clients that implement the above abstractions. Consider a SCYTHE client as a stage 2 program instantiated with the 3-tuple ( I , R , M ), with the expectation that a stage 1 dropper landed on the computing device first. This dropper has three responsibilities: setup the environment and check if it is safe to continue, retrieve the client into memory and start the client in memory without  producing any artifacts on disk, and then tear down and clean up stage 1. The purpose for a stage 1 dropper is to be discardable, if detected: There may be many implementations of these droppers.

The stage 2 SCYTHE client performs instructions as set forth in I . Specifically, what exists in I  is a check to see if it landed on the intended computing device (e.g., environment-keying), and if so, runs the rest of the instructions (which may or may not be encrypted as per environment-keying). If runtimes R  is non-empty, the SCYTHE client loads these runtimes. Assume modules M  is non-empty, and that at a minimum, M  = (loader, controller).

Additional modules and instructions may be available in I  in standalone  mode, where the client does not communicate with the server. An alternative approach is the connected  mode, where only enough instructions exist in I  to load the runtimes and modules with the expectation that the operator or the Threat Orchestrator generates additional instructions for the client and in this case, M  also contains at least one communications module .

Runtimes

SCYTHE v3 introduces runtimes to support developers who have reservations about writing native modules. The in-memory CPython interpreter available with SCYTHE v3 is a concrete implementation of the abstract runtime model described above. The SCYTHE SDK post  offers more details.

Modules

Modules can be native or dependent on a runtime as covered in the abstract module section. Modules can be CATEGORY_INPUT , CATEGORY_OUTPUT , or CATEGORY_WORKER.

Communications

A communication module may be CATEGORY_INPUT , which can only receive requests from SCYTHE; CATEGORY_OUTPUT , which only transmit responses to SCYTHE; or both CATEGORY_INPUT  or CATEGORY_OUTPUT , which means it can receive requests and  reply with responses (for example, an HTTPS beaconing module).

Capabilities

These modules are of type CATEGORY_WORKER , and each capability module is expected to provide a specific capability or functionality. For example, the clipboard module's sole purpose is to capture what's in the clipboard, not to perform keylogging.

We list two additional important modules, which are always available in a SCYTHE client: loader and controller.

Loader

The loader module is a CATEGORY_WORKER  module and enables transmission of additional modules over the network to the client and loading them directly into memory (akin to reflective loading). Similarly, the loader module may also transmit runtimes to load directly into memory. The loader module also permits unloading of modules and runtimes, and listing modules and runtimes.

Controller

The controller allows for graceful shutdown and cleanup of the client. The controller can query the client environment, e.g., integrity, privileges, etc.

Messaging Subsystem

The messaging subsystem is a critical feature in the SCYTHE client and deserves a summary. A module can send a request or response to another module or to the server. The module does not directly send this but posts a message to the messaging subsystem. The subsystem determines how to route this message to its intended recipient. As an example, the elevate module can send a message to the controller module to gracefully shutdown after performing Escalation-of-Privilege (EoP). Implementation-wise, what elevate did was post  a shutdown request for the controller to the messaging subsystem (e.g., SourceId=ElevateId, DestinationId=ControllerId, Message=SHUTDOWN). The subsystem generates a SCYTHE_MESSAGE  with this request, and calls the controller's run  function with this SCYTHE_MESSAGE . The controller replies back to the sender by posting  the response to the messaging subsystem (SourceId=ControllerId, DestinationId=OriginalMessage.SourceId, Message=SHUTDOWN_RESPONSE), and now the subsystem generates a SCYTHE_MESSAGE  and routes this as a response back to the elevate module's run . The messaging subsystem is asynchronous. In Windows, the underlying implementation uses an IO Completion Port [9]  that worker threads wait on. Please refer to the Module Development Guides in the SDK for more details.

To recap, SCYTHE aims to emulate any malware. You can write native modules or modules that run entirely on top of a runtime.  The aim for all client components is that they run entirely from memory and not produce forensic artifacts [10] . As you write components to run on top of this platform, refer to the development guides. And if you have additional questions about this post, please reach out to us at info@scythe.io

About SCYTHE

SCYTHE  provides an advanced attack emulation platform for the enterprise and cybersecurity consulting market. The SCYTHE platform enables Red, Blue, and Purple teams to build and emulate real-world adversarial campaigns in a matter of minutes. Customers are in turn enabled to validate the risk posture and exposure of their business and employees and the performance of enterprise security teams and existing security solutions. Based in Arlington, VA, the company is privately held and is funded by Gula Tech Adventures, Paladin Capital, Evolution Equity, and private industry investors. For more information email info@scythe.io, visit https://scythe.io , or follow on Twitter @scythe_io .


[1]  Emulate vs Simulate. There is a post by Tim Malcomvetter (https://medium.com/@malcomvetter/emulation-simulation-false-flags-b8f660734482). To me, simulate means running a series of tests to see if you're vulnerable to a specific set of CVEs, for example. Emulate is trying to be  the malware. Note: Callback IPs or configuration data are NETDEF and not emulation. For example, you could change a malware's callback IP (aka defanging ) and it is still functionally the same malware.

[6]  A sequence  of objects is a list of these objects in some order-- Michael Sipser, Introduction to the Theory of Computation , PWS: 1997.

[2]  “Malware refers to any unwanted software and executable code that is used to perform an unauthorized, often harmful, action on a computing device. It is an umbrella-term for various types of harmful software, including viruses, worms, trojans, rootkits, and botnets.”-- Lorenzo Cavallaro. Malicious Software and its Underground Economy: Two Sides to Every Story.  University of London: Information Security Group, 2013.

[7]  “Runtime system.” Wikipedia , https://en.wikipedia.org/wiki/Runtime_system . Accessed 11 May 2020.

[3]  “Dropper (malware).” Wikipedia , https://en.wikipedia.org/wiki/Dropper_(malware) . Accessed 11 May 2020.

[8]  “Dynamic linker.” Wikipedia ,   https://en.wikipedia.org/wiki/Dynamic_linker . Accessed 11 May 2020.

[4]  Babbin, Jacon et al. (2006). Security Log Management: Identifying Patterns in Chaos . Rockland: Syngress, pp. 91.

[9]  “I/O Completion Ports.” Windows Dev Center , May 31 2018, https://docs.microsoft.com/en-us/windows/win32/fileio/i-o-completion-ports . Accessed 11 May 2020.

[5]  A client isn't necessarily malware but over time it could perform behaviors that make it so. A computer program  is more appropriate here.

[10]  By forensic artifacts we mean those elements that are left over on the computing device and have not been cleaned up after the client program has exited.