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.