FIXfu$cated C2

Disguising C2 traffic as FIX-spec stock trading

╔═══╦═══╦═══╦═══╦═══╦═══╗
║╔═╗║╔═╗║╔═╗║╔═╗║╔═╗║╔═╗║
╚══╝╚══╝╚══╝║╚══╝╚══╝╚══╝

Intro

Have you ever wondered how to disguise C2 traffic as quant trading network chatter?

Probably not...

Is such an endeavor actually practical for anything?

Again, probably not...

I had this idea late one night and wanted to explore it though, so here it is.

Protocol Overview

Financial Information eXchange (FIX) is an open standard for exchanging real-time trade/market-related information between financial institutions and trade clients. This includes things like stock prices, transaction requests, and other market data. FIX is distinct from other finance related protocols like SWIFT in that SWIFT is used internally between banks for sending actual payments, whereas FIX is used for market data/requests and is generally used as an interface by external parties. For example, a quantitative trading firm might use a FIX API provided by a financial institution to quickly and dynamically consume market info and execute trades based on a trading algorithm. I imagine there are probably more niche protocols used in various parts of the quant world, but FIX is a widely adopted standard.

In general, FIX messages are composed of a header, body, and trailer in which data is encoded as tag=value pairs separated by a start-of-heading (SOH) character (ASCII 0x01). Tags are decimal numbers, where 1-4999 are defined by the protocol specification. 5000+ are user defined but have blocks reserved by various institutions for fields specific to them.

Depending on the FIX version being used, headers may vary. The basic required elements are:

Body

The fields in the body of a FIX message will be dependent on the message type. For example, if you were placing a limit order for 100 shares of McDonald’s stock at a price of $250 (MsgType 35=D), the message body would include the following:

Trailer

The only required field in the trailer is a checksum (tag 10) (there are also optional fields for Signature/SignatureLength if the SecureData (tag 91) field is being used). The checksum is calculated by summing the ASCII value of all characters in the message modulo 256 (including the SOH separators but excluding the checksum field itself).

Example: 10=132 (note this checksum probably isn’t right for this example)

On the Wire

FIX conversations exist as a session/presentation/application layer on top of an underlying TCP connection. Putting the header/body/trailer parts together, the human readable form of the limit buy example would look like the following (replacing the SOH character with | for readability):

8=FIX.4.2|9=126|35=D|49=ID123|56=ID456|34=1|52=20060102-15:04:05.000|11=ORD001|21=1|55=MCD|54=1|38=100|60=20060102-15:04:05.000|40=2|44=250.00|10=132|

(again note I didn’t feel like calculating the actual checksum for this example so just pretend it’s correct)

Disguising C2

Ok, one glaring thing to address here: historically, FIX was sent in plaintext, but modern FIX conversations are generally going to be TLS encrypted (at least as far as I can tell). Practicality aside, we’re just focusing on the plaintext protocol itself for the hell of it. Obviously if you were using TLS encrypted C2 there wouldn’t be much point to using FIX underneath it. This is all assuming you’re trying to conceal stuff in naked FIX messages going across the wire. This whole idea is a midnight intrigue and I’m not trying to justify the end practicality of it.

With that out of the way, there are two primary considerations:

  1. We’re hiding C2, so there’s a general dynamic of commands being issued to one side and output sent back as a result. What is the best way to mimic that in a plausible FIX dialogue?
  2. What’s the best way to embed the actual C2 payloads in the particular message types used in this dynamic? If the dynamic of choice is sending buy requests and receiving execution reports back, then the way we embed the underlying C2 data needs to make sense for those message types.

There are a ton of valid ways you can approach this, which is part of what made using the FIX protocol an interesting exercise.

Dialogue Dynamic

As a baseline, there are generally going to be 2 parties involved in a FIX conversation: an executor (the financial institution providing market data and executing trades), and a trade client (the party consuming market data and submitting trade orders). I’m sure there are a bunch of different dynamics that exist in real life, but this is the general dynamic we need to be aware of. For example, it doesn’t make sense for both the executor and the trade client to submit buy orders to eachother. Additionally, the trade client is the one that would be initiating the logon request to the executor, not the other way around. This is all theater with the assumption that someone is watching the messages going across the wire and would be discerning about suspicious/anomalous dialogue dynamics. Probably not realistic, but that’s the assumption for what we’re trying to do here.

Bind Shell Model

Assuming you keep to some realistic dialogue dynamic, you have a lot of flexibility in how you implement the C2 model. You can have the C2 implant act as the trade client and the C2 server act as executor (mimicking the dynamic of a reverse shell) or vice versa (mimicking the way a bind shell would be reached out to by a C2 server rather than the implant initiating a callback). When first messing around with how I wanted implement a basic proof-of-concept, I kept flip flopping between different approaches. For basic demonstration, I’m going to use the example dynamic used thus far and tailor the C2 implementation around it: a client making trade requests to an executor and receiving execution reports back. This is going to take the form of a bind shell. Here’s the basic outline of the exchange model:

  1. The C2 implant acts as the executor, and is executed first to create a bind shell by listening for incoming connections.
  2. The C2 “server” (the side issuing commands, but not necessarily acting as a typical server) acts as the trade client. It reaches out to the C2 implant upon execution with a FIX Logon message to initiate a connection.
  3. The C2 implant responds with a confirmation Logon message. The FIX protocol supports a username/password for authentication in the logon exchange, but before I knew about that I came up with the following for authentication…
  4. The C2 server (trade client) sends a “magic” trade request for authentication (same idea as a port knock). This could be a trade request for a specific ticker, at a specific price, for a specific quantity, which is configured ahead of time.
  5. The C2 implant responds with a regular execution report to confirm (if an invalid trade “knock” is received, the session is killed)
  6. After auth is established, the C2 server sends trade requests with embedded commands.
  7. The C2 implant responds to trade requests with execution reports containing embedded command output. This can be chunked across multiple messages, since multiple execution reports might accompany a single trade order as the order is gradually filled.
  8. A periodic heartbeat message is exchanged to keep with spec.
  9. When the C2 server or implant wants to kill the session, a Logout message is sent.

Reverse Shell Model

Here’s a basic example of a reverse shell model (the typical C2 implant calling back to a C2 server):

  1. The C2 server act as the executor, and listens for incoming connections.
  2. The C2 implant acts as the trade client, and initiates a connection to the C2 server with a Logon request along with whatever authentication scheme you might want.
  3. The C2 server responds with a corresponding Logon confirmation (accepting/rejecting based on the authentication scheme)
  4. After the session/auth are established, the C2 server can send commands embedded in News messages (message type B).
  5. The C2 implant can respond with the command output embedded in “indicator of interest” (IOI) messages (message type 6).

Inserting C2 Payloads

FIX is a highly extensible protocol, and there are many ways we could choose to embed the data involved in C2 exchange. One important consideration to guide this is placement of C2 payloads in such a way that they would make sense (at least as much as possible) to be sent in the FIX message types they’re being sent in. After looking into what the protocol supports, a few distinct options seem like viable methods for embedding C2 data:

Protocol-Spec Data Fields

The FIX protocol spec includes a few fields where we can place arbitrary data blobs. These include:

User-Defined Fields

As was briefly mentioned earlier, field tags 1-4999 are reserved by the protocol spec itself. Tags 5000+ are user defined, with the added caveat of various blocks being officially reserved by different institutions for fields unique to exchanging with them. We could encode or encrypt C2 payloads and either place it all in one user defined field, or chunk it across multiple.

Chunked Bytes in Unassuming Fields

Instead of using fields supporting free-form data which might be scrutinized if our dialogue was investigated, we could chunk C2 data across unassuming fields spanning multiple messages. This could also invite scrutiny if not done carefully (such as putting raw bytes in fields where that should only carry a small integer, or packing raw bytes in a field that should be a timestamp). This approach also adds more encoding/parsing complexity.

Basic Implementation

There are a bunch of libraries out there you can use for making apps that communicate with FIX. I probably should have used one of these but ended up going down the painful route of making my own basic parser for a few message types. It uses the bind shell model and supports logon/logout, authentication with an order “knock”, and C2 exchange via trade orders and execution reports. Payloads are XOR encrypted using the randomized orderID/execID sent with the message, base64 encoded, and embedded in the EncodedText field. The basic proof of concept is hosted here.

In wireshark, trade orders with embedded commands from the trade client look like this:

The command output sent back as execution reports by the executor (the C2 implant) look like this:

A basic connection, brief command exchange, and logout looks like the following in a wireshark capture:

Outro

Using plaintext FIX as a disguise for C2 exchange probably isn’t practical for anything and I spent way too much time implementing this. The vast majority of networks aren’t going to have plaintext FIX flying around, so using this exact approach would probably make you quite suspicious. The concept of using a “benign” message type for embedding C2 traffic, however, can be extremely useful depending on the network you’re trying to blend in with. A lot of R&D + creativity goes into designing evasive malware, but having a communication protocol that disguises how you talk to it is just as important. Opaque TLS traffic is generally the most practical, but designing comms around an overlooked and under-scrutinized protocol can be just as useful.

References