Communication Protocol Design

So while HTTPS is the overall “vehicle” we’ll use to ensure secure communication, we still need to define how the agent asks for the payload and how the server responds. In other words, we need to also work out some of the specifics, including of course the information that will be used for key derivation.

Agent Request

Once we run the loader it will immediately reach out to the server by sending an HTTPS GET to the /update endpoint, the idea here (though perhaps somewhat simplistic) is that using a common method like GET and a generic-sounding endpoint like /update can help the request seem less suspicious than unusual HTTP methods or paths.

Further, the request does not contain any query parameters in the URL or data in the request body, the idea again being to emulate simple status checks for background services.

Covert Channel via User-Agent

As we learned in Module 07, for the dynamic component of our key derivation the client will send two pieces of information - the current timestamp and a unique client ID. We’re going to embed both of these directly into the HTTP User-Agent header string for the sake of simplicity, but you could certainly get more creative and perhaps even create custom HTTP headers if you’d like - I encourage you to experiment with this!

So we’ll create a User-Agent string that starts with a standard browser identifier (like Mozilla/5.0 … Chrome/… Safari/…) and appends a custom suffix containing the dynamic data, formatted similarly to rv:TIMESTAMP-CLIENTID, to serve as our simple covert channel to transmit the necessary key derivation parameters without making the request structure itself look overtly suspicious.

Server Response Logic

That’s it as far as the client (loader) goes, on the server’s side we of course have to have our route defined to accept the GET requests on the /update path. When a request arrives, we’ll extract the User-Agent header, and parse it using a regular expression to locate and extract the embedded TIMESTAMP and CLIENTID values from the custom suffix.

Our server then performs basic validation, primarily checking if the extracted timestamp is within a reasonable time window (e.g., within 30 minutes of the server’s current time) and potentially validating the format of the client ID. This acts as a simple authentication mechanism to reject potentially stale or malformed requests, but of course if you’re target host is in a different time zone this will break, so take this authentication mechanism for what it is.

If the validation passes, our server then uses the extracted timestamp and clientID, along with the shared secret generated via generatePEValidationKey, to derive the session-specific key using the exact same deriveKeyFromParams function that the client will use. Again, both sides have to arrive at the same key for this transaction.

Once the key has been generated our server reads the raw DLL payload from its local storage, and obfuscates it using using the key and rolling XOR function we developed in Lab 7.1.

Once the payload has been obfuscated the server will send it as a byte slice back to the agent as the body of the HTTPS response. In a further attempt at blending in with regular traffic we’ll also set a few innocuous-looking HTTP headers, such as Content-Type: application/octet-stream and perhaps a Content-Disposition header suggesting a generic filename like update.dat, further mimicking a legitimate file download.

Conclusion

So we’ve now defined our specific implementation of the protocol to ensure both how our key will be derived to obfuscate our payload, as well as how the payload will be transferred. In the next section I just want to go into a bit more depth on how the client ID will be generated by the agent, whereafter we can then go and implement our client + server model.


|TOC| |PREV| |NEXT|