Obfuscating C2 Traffic with Google Cloud Functions
Introduction
In a previous article Maldoc Transfers in the Google Cloud, I wrote about using a Google Cloud Provider serverless function to serve malicious documents from a Google controlled domain. After writing that article, I continued to play explore what Google Cloud could provide to offensive security practitioners.
We've previously published a blog post as well as a Proof-of-Concept for using Azure serverless functions as C2 redirectors. Using this previous research as a jumping-off point, I've ported this redirector function to Google Cloud Provider as well. The PoC is available from the FunctionalC2 github repository.
Function Configuration
This walkthrough assumes the reader has already created a Google Cloud account. First, create a project in the Google Cloud Console.
The most important thing to note for red team operators is that your project's name and region will be displayed in the function's public-facing URL, so pick a project name and region that fits your pretext.
Also be sure to enable the necessary Google Cloud APIs for the project. These are:
- Cloud Functions API
- Cloud Build API
- Artifact Registry API
- Cloud Run API
- Logging API
Once those are enabled, install the gcloud
CLI tool and log in. You will need to select the current project we will be deploying the functions to with the following command:
gcloud config set project <PROJECT ID>
Clone the FunctionalC2 repository to your local machine and open the folder in your favorite IDE or text editor. cd
into the GoogleCloud
directory, where you'll see get
and post
. We have to modify the main.py
files in both of these directories.
The get
and post
main.py
files are nearly identical. We have to change three lines of each:
- The function's name from
c2Get
orc2Post
. Remember, these functions will be the URI endpoint for redirecting traffic to your C2 server! Accordingly, choose function names that will be visible to anything or anybody monitoring outbound HTTP traffic. teamserver_url
's value to a publicly accessible HTTP URL for your teamserver. This could be the server itself, a proxy, or another redirector.beacon_endpoint
's value to the function's name.
Now, run the following command once in each the get
and post
directories. Be sure to change <your function name>
to the case-sensitive name of the function in main.py
.
gcloud functions deploy <your function name> --runtime python310 --trigger-http --allow-unauthenticated
If everything goes smoothly, the output will look something like this:
Note the returned URL: we will need to point our C2 infrastructure and listeners to it. When both the get
and post
functions are deployed we can continue to set up the Command and Control infrastructure.
C2 Configuration
I used Cobalt Strike for this research but we could likely use other C2 frameworks that support HTTP implants and listeners with minimal modifications to the serverless function. This section assumes the reader has already spun up a machine to host the C2 teamserver. The teamserver host used for this PoC was a Digital Ocean Droplet running Debian 11.
Before starting the teamserver we have to generate our Malleable C2 Profile. [This sample profile](ADD URL HERE) included in the FunctionalC2 GitHub repository was initially generated with C2Concealer. However, we have to modify this profile to ensure it will work with our function. First, we include set host_stage "false";
in the initial settings block, as we don't use stagers for our payloads and leaving it on is likely to get our teamserver flagged as malicious.
We also have to change the set uri
attribute for the http-get
and http-post
blocks of the profile to point to our GCP function endpoints as shown below.
After saving the file, we ran the c2lint
tool included with Cobalt Strike to make sure we didn't introduce any syntax issues when we modified the profile.
Finally, we can start the teamserver with this profile:
./teamserver <teamserver_ip_address> <password> <malleableC2profile>
Once we verify the server has started, we can log into it from a Cobalt Strike client and set up a listener pointing with the profile we just made. We have to point it to the HTTP URL of our cloud function.
Cobalt Strike will alert us with a pop up message that the listener has started successfully. We create a beacon payload, run it on a Windows 10 VM, and we get a beacon back. To test, we ran a few commands to be sure it's correctly grabbing output for common functions.
We can confirm in our Google Cloud logs of the c2Get
function that the function is executing. First, click on the "Cloud Functions" page under "Serverless" in the cloud console's sidebar.
Then click on the c2Get
function and enter the "Logs" tab as shown below:
As we can see, the logs have confirmed that the function ran. These logs are extremely useful for troubleshooting, as any any output to STDOUT
will print to this log.
This PoC demonstrates how serverless functions can be used as a C2 transport for Cobalt Strike beacons. Again, the same technique should theoretically work for other C2 frameworks, but further testing is required.
Happy Hacking!