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.

Create Project Button in 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.pyfiles are nearly identical. We have to change three lines of each:

  1. The function's name from c2Get or c2Post. 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.
  2. teamserver_url's value to a publicly accessible HTTP URL for your teamserver. This could be the server itself, a proxy, or another redirector.
  3. beacon_endpoint's value to the function's name.
c2Get Values Needing to be Changed

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:

Screenshot of Successful Deployment Output Highlighting the Function's Public URL

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.

Malleable C2 Profile Initial Settings Block Showing host_stage set to false

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.

http-get block uri attribute set to our redirector function's name

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.

c2lint Output

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.

Listener Setup

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.

Output from an unmanaged PowerShell command

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.

Cloud Functions Button in Google Cloud Console

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!