EvilVM Documentation Site

Architectural Introduction

The EvilVM architecture presents several major components. This post describes each, in order to provide a general overview of how the system works.

Architecture Diagram

Agent

The agent is an executable object, either as a position independent shellcode or an executable. The agent bootstraps using rudiments drawn from KERNEL32.DLL, and thus has no dependencies other than what should always be present in any process on a standard Windows machine. After bootstrapping the compiler, the agent enters an infinite loop processing incoming messages on its input stream, and returning data on its output stream.

Agents are configurable, and may vary depending on choices made when they are built. For example, agents may use any of several transport layers for moving data on the input and output streams. One agent may use an HTTP transport, while another a TCP socket, and yet another might interact with the console STDIN and STDOUT streams. Above the transport layer, however, all agents run the same compiler and use the same scheme for interacting with the user.

Agents are built using the build.rb script provided in the project’s root directory.

Server

The user runs a server process, which will receive connections from deployed agents. This server provides functionality for maintaining multiple, concurrent sessions. There is also facility for delivering code to a running agent, as well as interaction with the agent’s outer interpreter (REPL or “shell”).

The server is designed to receive connections only via a TCP socket. Other transports require use of server shims. At initial release, the only such shim is for supporting the HTTP transport – it manages sessions with remote agents, and tunnels their I/O streams through TCP sockets to the server component.

In general, you can conceive of communications with the server as a simple stream of bytes in and out. However, the server does have some implicit handling of special cases to handle the agents more effectively. Most of these behaviors involve use of ASCII control codes, and are mostly in place to simplify the user’s experience.

Core API

The agent embeds only a very small dictionary of Forth words, as small size is one of its key objectives. For example, the language as embedded in the compiler is not even aware of key syntax such as if ... else ... then conditionals, loops, and comments. These, and a variety of other important primitives are intended to be provided to the freshly bootstrapped compiler on initial connection. This core API is implemented in the api/core.fth file.

The contents of this core API are intended to remain fairly static, and serve as a consistent foundation for building more complex behaviors. It should typically not be modified in regular use.

Code Samples

A number of code modules that implement different capabilities on top of the core API are provided. These use a simple notation in header comments for preloading dependencies, and server as a system of programs providing deployment capabilities for the system. Some modules may alter the language in incompatible ways, and this codebase should be considered fluid and malleable.

Some of these samples are more like libraries (such as the content in strings.fth or parsers.fth), while others are end-user functionality (such as keylogger.fth or procdump.fth). The user is expected to use these either for their functionality, or as reference to implement new modules for desired behavior.

Encapsulation Methods

Generation of the agent can include additional customizations on top of simple assembly of a configured agent. Encapsulation modules are wrappers for encoding, compressing, or disguising the payload. At initial release, these are few in number, but will probably grow over time.

Last updated on 26 Apr 2019