Skip to content

Spectre Rootkit Design

Bill Demirkapi edited this page Aug 5, 2020 · 1 revision

This document will go in detail about how the Spectre Rootkit abuses legitimate network communications. If you're curious into how we gain visibility into legitimate network communications, see Hooking IOCTL Communication via Hooking File Objects.

Here is a high level overview of how the Spectre Rootkit passes information via legitimate ports:

  1. The Spectre Rootkit (on the victim machine) hooks network communications to gain visibility into the content of incoming packets over any port. This means that the rootkit has visibility into packets sent to any legitimate port on the machine.
  2. The Spectre CLI (on the C2) crafts a "malicious" packet and places a special indicator in this packet, a "magic constant".
  3. The Spectre CLI (on the C2) sends this malicious packet to any legitimate port open on the victim machine.
  4. The Spectre Rootkit (on the victim machine) scans incoming packets for this "magic constant" and parses out a structure using the constant as a reference point.

Packet Structure

Let's dive into more detail. I'll start with the structure of malicious packets.

Spectre Rootkit Packet Structure
Any data except for the magic constant
Magic constant
BASE_PACKET structure
Optional custom structure
Any data
typedef struct _BASE_PACKET
{
	ULONG PacketLength;	// The length of the packet. *Does not contain the size of the MAGIC.*
	PACKET_TYPE Type;	// Indicates the type of packet.
} BASE_PACKET, *PBASE_PACKET;

Almost any data can come before the magic constant, except obviously the magic constant itself. Next comes the magic constant, this will act as a reference point for the rest of the data. There is the BASE_PACKET structure right after, which is mandatory for all packets. Next is the optional custom structure, which will change depending on the Type specified in the base packet.

This model of allowing any* data before and after the core malicious structures was designed to allow for flexibility across protocols. For example, you could prepend a valid HTTP request and have the malicious structures act as a binary file being sent to a web server.

Packet Handlers

Once a packet has been determined to be malicious and the relevant structures have been parsed out, the packet is dispatched to a "packet handler". Every Type in the BASE_PACKET structure has its own "packet handler" class. Here is the generic packet handler class:

typedef class PacketHandler
{
protected:
	//
	// The packet dispatcher is used for sending and receiving network messages.
	// It can also be used to dispatch a new packet.
	//
	PPACKET_DISPATCH PacketDispatch;
public:
	PacketHandler (
		_In_ PPACKET_DISPATCH Dispatcher
		);
	
	virtual NTSTATUS ProcessPacket (
		_In_ PBASE_PACKET FullPacket
		) = 0;
} PACKET_HANDLER, *PPACKET_HANDLER;

Other packet handlers inherit this base class. Two key things to note in the class above is that the dispatcher will not only pass a pointer to itself in the handlers' constructor, but the actual packet will be passed to the ProcessPacket function of the packet handler.

Let's take a look at an example packet handler, the PingPacketHandler. This packet type/handler is used to determine if a machine/port is infected with the Spectre Rootkit. This packet type has no optional custom structure. Instead, it consists of only the magic constant and base packet structure. All the PingPacketHandler does is send back a BASE_PACKET structure with its Type set to Ping. Here is the PingPacketHandler.cpp in case you'd like to see the code.

Pretty simple, right? Now for a slightly more complex example, the XorPacketHandler. The XorPacketHandler takes the following optional custom structure:

typedef struct _XOR_PACKET
{
	BASE_PACKET Base;	// Contains standard information about the packet.
	BYTE XorKey;		// The XOR key used to obfuscate the packet.
	BYTE XorContent[1];	// The XOR'd packet to dispatch.
} XOR_PACKET, *PXOR_PACKET;

The XorPacketHandler does not perform a malicious operation, instead it acts as an encapsulating packet. Say the C2 wants to send one operation multiple times to the victim machine, but it does not want the core packet content to be the same.

What the C2 can do is place a BASE_PACKET structure and an optional custom structure (for another operation) in the XorContent array, generate a random XorKey, and then obfuscate the XorContent array by performing a XOR operation on every byte using the XorKey. The XorPacketHandler will then deobfuscate the XorContent array and recursively dispatch the packet inside by using the dispatcher pointer it received in its constructor. This model that the rootkit uses allows for effectively infinite layers of encapsulation and obfuscation. Here is the XorPacketHandler.cpp in case you'd like to see the code.