/// <summary> /// Initializes a new instance of the GruntCommand class. /// </summary> public GruntCommand(string command, System.DateTime commandTime, CovenantUser user, int?id = default(int?), int?commandOutputId = default(int?), CommandOutput commandOutput = default(CommandOutput), int?gruntId = default(int?), Grunt grunt = default(Grunt), int?gruntTaskingId = default(int?), GruntTasking gruntTasking = default(GruntTasking)) { Id = id; Command = command; CommandTime = commandTime; CommandOutputId = commandOutputId; CommandOutput = commandOutput; User = user; GruntId = gruntId; Grunt = grunt; GruntTaskingId = gruntTaskingId; GruntTasking = gruntTasking; CustomInit(); }
/// <summary> /// Validate the object. /// </summary> /// <exception cref="ValidationException"> /// Thrown if validation fails /// </exception> public virtual void Validate() { if (Name == null) { throw new ValidationException(ValidationRules.CannotBeNull, "Name"); } if (Grunt != null) { Grunt.Validate(); } if (GruntTask != null) { GruntTask.Validate(); } }
/// <summary> /// Initializes a new instance of the GruntTasking class. /// </summary> /// <param name="type">Possible values include: 'Assembly', 'SetDelay', /// 'SetJitter', 'SetConnectAttempts', 'SetKillDate', 'Exit', /// 'Connect', 'Disconnect', 'Tasks', 'TaskKill'</param> /// <param name="status">Possible values include: 'Uninitialized', /// 'Tasked', 'Progressed', 'Completed', 'Aborted'</param> public GruntTasking(string name, int gruntId, int gruntTaskId, int?id = default(int?), Grunt grunt = default(Grunt), GruntTask gruntTask = default(GruntTask), GruntTaskingType?type = default(GruntTaskingType?), IList <string> parameters = default(IList <string>), GruntTaskingStatus?status = default(GruntTaskingStatus?), System.DateTime?taskingTime = default(System.DateTime?), System.DateTime?completionTime = default(System.DateTime?), int?gruntCommandId = default(int?)) { Id = id; Name = name; GruntId = gruntId; Grunt = grunt; GruntTaskId = gruntTaskId; GruntTask = gruntTask; Type = type; Parameters = parameters; Status = status; TaskingTime = taskingTime; CompletionTime = completionTime; GruntCommandId = gruntCommandId; CustomInit(); }
/// <summary> /// Validate the object. /// </summary> /// <exception cref="ValidationException"> /// Thrown if validation fails /// </exception> public virtual void Validate() { if (Command == null) { throw new ValidationException(ValidationRules.CannotBeNull, "Command"); } if (UserId == null) { throw new ValidationException(ValidationRules.CannotBeNull, "UserId"); } if (CommandOutput != null) { CommandOutput.Validate(); } if (Grunt != null) { Grunt.Validate(); } if (GruntTasking != null) { GruntTasking.Validate(); } }
public async Task <string> Write(string guid, string data) { try { ModelUtilities.GruntEncryptedMessage message = null; try { string inverted = Common.CovenantEncoding.GetString(this._utilities.ProfileInvert(_transform, data)); message = JsonConvert.DeserializeObject <ModelUtilities.GruntEncryptedMessage>(inverted); } catch (Exception) { // Request not formatted correctly. May not be legitimate Grunt request, respond NotFound this.PushCache(guid, new GruntMessageCacheInfo { Status = GruntMessageCacheStatus.NotFound, Message = "", Tasking = null }); return(guid); } APIModels.Grunt egressGrunt; try { egressGrunt = guid == null ? null : await _client.ApiGruntsGuidByGuidGetAsync(guid); } catch (HttpOperationException) { egressGrunt = null; } APIModels.Grunt targetGrunt = null; try { targetGrunt = await _client.ApiGruntsGuidByGuidGetAsync(message.GUID); } catch (HttpOperationException) { targetGrunt = null; // Stage0 Guid is OriginalServerGuid + Guid if (message.GUID.Length == 20) { string originalServerGuid = message.GUID.Substring(0, 10); string newGuid = message.GUID.Substring(10); targetGrunt = await _client.ApiGruntsOriginalguidByServerguidGetAsync(originalServerGuid); guid = newGuid; await this.PostStage0(egressGrunt, targetGrunt, message, guid); return(guid); } else { this.PushCache(guid, new GruntMessageCacheInfo { Status = GruntMessageCacheStatus.NotFound, Message = "", Tasking = null }); return(guid); } } switch (targetGrunt.Status) { case APIModels.GruntStatus.Uninitialized: await this.PostStage0(egressGrunt, targetGrunt, message, guid); return(guid); case APIModels.GruntStatus.Stage0: await this.PostStage1(egressGrunt, targetGrunt, message, message.GUID); return(message.GUID); case APIModels.GruntStatus.Stage1: await this.PostStage2(egressGrunt, targetGrunt, message, message.GUID); return(message.GUID); case APIModels.GruntStatus.Stage2: await this.RegisterGrunt(egressGrunt, targetGrunt, message, message.GUID); return(message.GUID); case APIModels.GruntStatus.Active: await this.PostTask(egressGrunt, targetGrunt, message, egressGrunt.Guid); return(guid); case APIModels.GruntStatus.Lost: await this.PostTask(egressGrunt, targetGrunt, message, egressGrunt.Guid); return(guid); default: this.PushCache(guid, new GruntMessageCacheInfo { Status = GruntMessageCacheStatus.NotFound, Message = "", Tasking = null }); return(guid); } } catch { this.PushCache(guid, new GruntMessageCacheInfo { Status = GruntMessageCacheStatus.NotFound, Message = "", Tasking = null }); return(guid); } }
private async Task InternalRead(string guid) { try { APIModels.Grunt grunt = await CheckInGrunt(await GetGruntForGuid(guid)); if (grunt == null) { // Invalid GUID. May not be legitimate Grunt request, respond Ok this.PushCache(guid, new GruntMessageCacheInfo { Status = GruntMessageCacheStatus.Ok, Message = "" }); } else { IList <APIModels.GruntTasking> gruntTaskings = await _client.ApiGruntsByIdTaskingsSearchUninitializedGetAsync(grunt.Id ?? default); if (gruntTaskings == null || gruntTaskings.Count == 0) { // No GruntTasking assigned. Respond with empty template this.PushCache(guid, new GruntMessageCacheInfo { Status = GruntMessageCacheStatus.Ok, Message = "" }); } else { foreach (APIModels.GruntTasking tasking in gruntTaskings) { APIModels.GruntTasking gruntTasking = tasking; if (gruntTasking.Type == APIModels.GruntTaskingType.Assembly && gruntTasking.GruntTask == null) { // Can't find corresponding task. Should never reach this point. Will just respond NotFound. this.PushCache(guid, new GruntMessageCacheInfo { Status = GruntMessageCacheStatus.NotFound, Message = "", Tasking = gruntTasking }); } else { gruntTasking.Grunt = gruntTasking.GruntId == grunt.Id ? grunt : await _client.ApiGruntsByIdGetAsync(gruntTasking.GruntId); ModelUtilities.GruntEncryptedMessage message = null; try { message = this.CreateMessageForGrunt(grunt, gruntTasking.Grunt, this.GetGruntTaskingMessage(gruntTasking, gruntTasking.Grunt.DotNetFrameworkVersion)); // Transform response string transformed = this._utilities.ProfileTransform(_transform, Common.CovenantEncoding.GetBytes(JsonConvert.SerializeObject(message))); this.PushCache(guid, new GruntMessageCacheInfo { Status = GruntMessageCacheStatus.Ok, Message = transformed, Tasking = gruntTasking }); } catch (HttpOperationException) { gruntTasking.Status = APIModels.GruntTaskingStatus.Aborted; await _client.ApiTaskingsPutAsync(gruntTasking); this.PushCache(guid, new GruntMessageCacheInfo { Status = GruntMessageCacheStatus.NotFound, Message = "", Tasking = null }); } } } } } } catch (Exception) { this.PushCache(guid, new GruntMessageCacheInfo { Status = GruntMessageCacheStatus.NotFound, Message = "" }); } }
private ModelUtilities.GruntEncryptedMessage CreateMessageForGrunt(APIModels.Grunt grunt, APIModels.Grunt targetGrunt, byte[] message) { List <string> path = _client.ApiGruntsByIdPathByCidGet(grunt.Id ?? default, targetGrunt.Id ?? default).ToList(); path.Reverse(); ModelUtilities.GruntEncryptedMessage finalMessage = null; ModelUtilities.GruntEncryptedMessageType messageType = ModelUtilities.GruntEncryptedMessageType.Tasking; foreach (string guid in path) { APIModels.Grunt thisGrunt = _client.ApiGruntsGuidByGuidGet(guid); finalMessage = ModelUtilities.GruntEncryptedMessage.Create( thisGrunt, message, messageType ); message = Common.CovenantEncoding.GetBytes(JsonConvert.SerializeObject(finalMessage)); messageType = ModelUtilities.GruntEncryptedMessageType.Routing; } return(finalMessage); }
private ModelUtilities.GruntEncryptedMessage CreateMessageForGrunt(APIModels.Grunt grunt, APIModels.Grunt targetGrunt, APIModels.GruntTaskingMessage taskingMessage) { return(this.CreateMessageForGrunt(grunt, targetGrunt, Common.CovenantEncoding.GetBytes(JsonConvert.SerializeObject(taskingMessage)))); }
// Convenience method for decrypting a GruntEncryptedMessage public byte[] GruntSessionDecrypt(APIModels.Grunt grunt, GruntEncryptedMessage gruntEncryptedMessage) { return(this.GruntSessionDecrypt(grunt, Convert.FromBase64String(gruntEncryptedMessage.IV) .Concat(Convert.FromBase64String(gruntEncryptedMessage.EncryptedMessage)).ToArray())); }
// Data should be of format: IV (16 bytes) + EncryptedBytes public byte[] GruntSessionDecrypt(APIModels.Grunt grunt, byte[] data) { return(EncryptUtilities.AesDecrypt(data, Convert.FromBase64String(grunt.GruntNegotiatedSessionKey))); }
public static byte[] GruntRSAEncrypt(APIModels.Grunt grunt, byte[] toEncrypt) { return(EncryptUtilities.RSAEncrypt(toEncrypt, Common.CovenantEncoding.GetString(Convert.FromBase64String(grunt.GruntRSAPublicKey)))); }
private async Task PostStage0(APIModels.Grunt egressGrunt, APIModels.Grunt targetGrunt, ModelUtilities.GruntEncryptedMessage gruntStage0Response, string guid) { if (targetGrunt == null || !gruntStage0Response.VerifyHMAC(Convert.FromBase64String(targetGrunt.GruntSharedSecretPassword))) { // Always return NotFound, don't give away unnecessary info this.PushCache(guid, new GruntMessageCacheInfo { Status = GruntMessageCacheStatus.NotFound, Message = "", Tasking = null }); return; } bool egressGruntExists = egressGrunt != null; if (targetGrunt.Status != APIModels.GruntStatus.Uninitialized) { // We create a new Grunt if this one is not uninitialized APIModels.Grunt tempModel = new APIModels.Grunt { Id = 0, Name = Utilities.CreateShortGuid(), Guid = guid, OriginalServerGuid = Utilities.CreateShortGuid(), Status = APIModels.GruntStatus.Stage0, ListenerId = targetGrunt.ListenerId, Listener = targetGrunt.Listener, ImplantTemplateId = targetGrunt.ImplantTemplateId, ImplantTemplate = targetGrunt.ImplantTemplate, GruntSharedSecretPassword = targetGrunt.GruntSharedSecretPassword, SmbPipeName = targetGrunt.SmbPipeName, Delay = targetGrunt.Delay, JitterPercent = targetGrunt.JitterPercent, KillDate = targetGrunt.KillDate, ConnectAttempts = targetGrunt.ConnectAttempts, DotNetFrameworkVersion = targetGrunt.DotNetFrameworkVersion, LastCheckIn = DateTime.UtcNow }; targetGrunt = await _client.ApiGruntsPostAsync(tempModel); } else { targetGrunt.Status = APIModels.GruntStatus.Stage0; targetGrunt.Guid = guid; targetGrunt.LastCheckIn = DateTime.UtcNow; targetGrunt = await _client.ApiGruntsPutAsync(targetGrunt); } if (!egressGruntExists) { egressGrunt = targetGrunt; } // EncryptedMessage is the RSA Public Key targetGrunt.GruntRSAPublicKey = Convert.ToBase64String(EncryptUtilities.AesDecrypt( gruntStage0Response, Convert.FromBase64String(targetGrunt.GruntSharedSecretPassword) )); // Generate negotiated session key using (Aes newAesKey = Aes.Create()) { newAesKey.GenerateKey(); targetGrunt.GruntNegotiatedSessionKey = Convert.ToBase64String(newAesKey.Key); await _client.ApiGruntsPutAsync(targetGrunt); } if (egressGruntExists) { // Add this as Child grunt to Grunt that connects it List <APIModels.GruntTasking> taskings = _client.ApiTaskingsGet().ToList(); // TODO: Finding the connectTasking this way could cause race conditions, should fix w/ guid of some sort? APIModels.GruntTasking connectTasking = taskings.Where(GT => GT.Type == APIModels.GruntTaskingType.Connect && GT.Status == APIModels.GruntTaskingStatus.Progressed).Reverse().FirstOrDefault(); if (connectTasking == null) { this.PushCache(guid, new GruntMessageCacheInfo { Status = GruntMessageCacheStatus.NotFound, Message = "", Tasking = null }); return; } APIModels.GruntTaskingMessage tmessage = this.GetGruntTaskingMessage(connectTasking, targetGrunt.DotNetFrameworkVersion); targetGrunt.Hostname = tmessage.Message.Split(",")[0]; await _client.ApiGruntsPutAsync(targetGrunt); connectTasking.Status = APIModels.GruntTaskingStatus.Completed; await _client.ApiTaskingsPutAsync(connectTasking); } byte[] rsaEncryptedBytes = EncryptUtilities.GruntRSAEncrypt(targetGrunt, Convert.FromBase64String(targetGrunt.GruntNegotiatedSessionKey)); ModelUtilities.GruntEncryptedMessage message = null; try { message = this.CreateMessageForGrunt(egressGrunt, targetGrunt, rsaEncryptedBytes); } catch (HttpOperationException) { this.PushCache(guid, new GruntMessageCacheInfo { Status = GruntMessageCacheStatus.NotFound, Message = "", Tasking = null }); return; } // Transform response // Stage0Response: "Id,Name,Base64(IV),Base64(AES(RSA(SessionKey))),Base64(HMAC)" string transformed = this._utilities.ProfileTransform(_transform, Common.CovenantEncoding.GetBytes(JsonConvert.SerializeObject(message))); this.PushCache(guid, new GruntMessageCacheInfo { Status = GruntMessageCacheStatus.Ok, Message = transformed, Tasking = null }); return; }
private async Task PostTask(APIModels.Grunt egressGrunt, APIModels.Grunt targetGrunt, ModelUtilities.GruntEncryptedMessage outputMessage, string guid) { if (targetGrunt == null || egressGrunt == null || egressGrunt.Guid != guid) { // Invalid GUID. May not be legitimate Grunt request, respond NotFound this.PushCache(guid, new GruntMessageCacheInfo { Status = GruntMessageCacheStatus.NotFound, Message = "", Tasking = null }); return; } string TaskName = outputMessage.Meta; if (string.IsNullOrWhiteSpace(TaskName)) { // Invalid task response. This happens on post-register write this.PushCache(guid, new GruntMessageCacheInfo { Status = GruntMessageCacheStatus.NotFound, Message = "", Tasking = null }); return; } APIModels.GruntTasking gruntTasking; try { gruntTasking = await _client.ApiGruntsTaskingsByTaskingnameGetAsync(TaskName); } catch (HttpOperationException) { // Invalid taskname. May not be legitimate Grunt request, respond NotFound this.PushCache(guid, new GruntMessageCacheInfo { Status = GruntMessageCacheStatus.NotFound, Message = "", Tasking = null }); return; } if (targetGrunt == null) { // Invalid Grunt. May not be legitimate Grunt request, respond NotFound this.PushCache(guid, new GruntMessageCacheInfo { Status = GruntMessageCacheStatus.NotFound, Message = "", Tasking = null }); return; } if (!outputMessage.VerifyHMAC(Convert.FromBase64String(targetGrunt.GruntNegotiatedSessionKey))) { // Invalid signature. Almost certainly not a legitimate Grunt request, respond NotFound this.PushCache(guid, new GruntMessageCacheInfo { Status = GruntMessageCacheStatus.NotFound, Message = "", Tasking = null }); return; } string taskOutput = Common.CovenantEncoding.GetString(_utilities.GruntSessionDecrypt(targetGrunt, outputMessage)); gruntTasking.GruntCommand.CommandOutput = new APIModels.CommandOutput { Id = 0, GruntCommandId = gruntTasking.GruntCommandId, Output = taskOutput }; gruntTasking.GruntCommand.CommandOutputId = 0; gruntTasking.Status = APIModels.GruntTaskingStatus.Completed; gruntTasking.CompletionTime = DateTime.UtcNow; gruntTasking.GruntCommand = await _client.ApiCommandsPutAsync(gruntTasking.GruntCommand); await _client.ApiTaskingsPutAsync(gruntTasking); lock (_hashCodesLock) { this.CacheTaskHashCodes.Remove(GetTaskingHashCode(gruntTasking)); } await CheckInGrunt(targetGrunt); return; }