From bb39df05d3968e028ef05103d8363ee20910812c Mon Sep 17 00:00:00 2001 From: pop-ecx Date: Mon, 1 Dec 2025 13:44:20 +0300 Subject: [PATCH 1/6] feat: python handler for file and directory deletion --- .../rango/agent_functions/delete_directory.py | 75 ++++++++++++++++++ .../rango/agent_functions/delete_file.py | 78 +++++++++++++++++++ 2 files changed, 153 insertions(+) create mode 100644 Payload_Type/rango/rango/agent_functions/delete_directory.py create mode 100644 Payload_Type/rango/rango/agent_functions/delete_file.py diff --git a/Payload_Type/rango/rango/agent_functions/delete_directory.py b/Payload_Type/rango/rango/agent_functions/delete_directory.py new file mode 100644 index 0000000..eb56451 --- /dev/null +++ b/Payload_Type/rango/rango/agent_functions/delete_directory.py @@ -0,0 +1,75 @@ +from mythic_container.MythicCommandBase import * +from mythic_container.MythicRPC import * +from mythic_container.MythicGoRPC import * + +class DeleteDirectoryArguments(TaskArguments): + def __init__(self, command_line, **kwargs): + super().__init__(command_line, **kwargs) + self.args = [ + PathParameter(name="dirpath", display_name="Delete directory", type=ParameterType.String, + description="Path of the directory to delete"), + ] + async def parse_arguments(self): + if len(self.command_line) == 0: + raise ValueError("Must supply a path to run") + self.add_arg("command", self.command_line) + + async def parse_dictionary(self, dictionary_arguments): + self.load_args_from_dictionary(dictionary_arguments) + +class DeleteDirectoryCommand(CommandBase): + cmd = "delete_directory" + needs_admin = False + help_cmd = "delete_directory /path/to/directory" + description = """This deletes the specified directory""" + version = 1 + author = "@pop-ecx" + attackmapping = ["T1070"] + argument_class = DeleteDirectoryArguments + attributes = CommandAttributes( + suggested_command=True + ) + + async def opsec_pre(self, taskData: PTTaskMessageAllData) -> PTTTaskOPSECPreTaskMessageResponse: + response = PTTTaskOPSECPreTaskMessageResponse( + TaskID=taskData.Task.ID, + Success=True, + OpsecPreBlocked=False, + OpsecPreBypassRole="other_operator", + OpsecPreMessage="Delete file command allowed to execute.", + ) + return response + async def opsec_post(self, taskData: PTTaskMessageAllData) -> PTTTaskOPSECPostTaskMessageResponse: + response = PTTTaskOPSECPostTaskMessageResponse( + TaskID=taskData.Task.ID, + Success=True, + OpsecPostBlocked=False, + OpsecPostBypassRole="other_operator", + OpsecPostMessage="Delete file command post-processing allowed.", + ) + return response + async def create_go_tasking(self, taskData: MythicCommandBase.PTTaskMessageAllData) -> MythicCommandBase.PTTaskCreateTaskingMessageResponse: + response = MythicCommandBase.PTTaskCreateTaskingMessageResponse( + TaskID=taskData.Task.ID, + Success=True, + Parameters=taskData.args.get_arg("dirpath"), + ) + await SendMythicRPCArtifactCreate(MythicRPCArtifactCreateMessage( + TaskID=taskData.Task.ID, ArtifactMessage="{}".format(taskData.args.get_arg("dirpath")), + BaseArtifactType="Process Create" + )) + response.DisplayParams = taskData.args.get_arg("dirpath") + return response + async def process_response(self, task: PTTaskMessageAllData, response: any) -> PTTaskProcessResponseMessageResponse: + resp = PTTaskProcessResponseMessageResponse(TaskID=task.Task.ID, Success=True) + if isinstance(response, dict) and "user_output" in response: + resp.Output = response["user_output"] + if "status" in response and response["status"] == "error": + resp.Success = False + elif isinstance(response, str): + resp.Output = response + else: + resp.Output = "Unexpected response format: " + str(response) + + resp.completed = True + return resp diff --git a/Payload_Type/rango/rango/agent_functions/delete_file.py b/Payload_Type/rango/rango/agent_functions/delete_file.py new file mode 100644 index 0000000..8a2d857 --- /dev/null +++ b/Payload_Type/rango/rango/agent_functions/delete_file.py @@ -0,0 +1,78 @@ +from mythic_container.MythicCommandBase import * +from mythic_container.MythicRPC import * +from mythic_container.MythicGoRPC import * + +class DeleteFileArguments(TaskArguments): + def __init__(self, command_line, **kwargs): + super().__init__(command_line, **kwargs) + self.args = [ + PathParameter(name="path", display_name="Delete file", type=ParameterType.String, + description="Path of the file to delete"), + ] + + async def parse_arguments(self): + if len(self.command_line) == 0: + raise ValueError("Must supply a path to run") + self.add_arg("command", self.command_line) + + async def parse_dictionary(self, dictionary_arguments): + self.load_args_from_dictionary(dictionary_arguments) + +class DeleteFileCommand(CommandBase): + cmd = "deletefile" + needs_admin = False + help_cmd = "deletefile /path/to/file" + description = """This deletes the specified file""" + version = 1 + author = "@pop-ecx" + attackmapping = ["T1070"] + argument_class = DeleteFileArguments + attributes = CommandAttributes( + suggested_command=True + ) + + async def opsec_pre(self, taskData: PTTaskMessageAllData) -> PTTTaskOPSECPreTaskMessageResponse: + response = PTTTaskOPSECPreTaskMessageResponse( + TaskID=taskData.Task.ID, + Success=True, + OpsecPreBlocked=False, + OpsecPreBypassRole="other_operator", + OpsecPreMessage="Delete file command allowed to execute.", + ) + return response + async def opsec_post(self, taskData: PTTaskMessageAllData) -> PTTTaskOPSECPostTaskMessageResponse: + response = PTTTaskOPSECPostTaskMessageResponse( + TaskID=taskData.Task.ID, + Success=True, + OpsecPostBlocked=False, + OpsecPostBypassRole="other_operator", + OpsecPostMessage="Delete file command post-processing allowed.", + ) + return response + async def create_go_tasking(self, taskData: MythicCommandBase.PTTaskMessageAllData) -> MythicCommandBase.PTTaskCreateTaskingMessageResponse: + response = MythicCommandBase.PTTaskCreateTaskingMessageResponse( + TaskID=taskData.Task.ID, + Success=True, + Parameters=taskData.args.get_arg("path"), + ) + await SendMythicRPCArtifactCreate(MythicRPCArtifactCreateMessage( + TaskID=taskData.Task.ID, ArtifactMessage="{}".format(taskData.args.get_arg("path")), + BaseArtifactType="Process Create" + )) + + response.DisplayParams = taskData.args.get_arg("path") + return response + + async def process_response(self, task: PTTaskMessageAllData, response: any) -> PTTaskProcessResponseMessageResponse: + resp = PTTaskProcessResponseMessageResponse(TaskID=task.Task.ID, Success=True) + if isinstance(response, dict) and "user_output" in response: + resp.Output = response["user_output"] + if "status" in response and response["status"] == "error": + resp.Success = False + elif isinstance(response, str): + resp.Output = response + else: + resp.Output = "Unexpected response format: " + str(response) + + resp.completed = True + return resp From bc39a98ee6d3a398e7f28530e7462941ff105bc6 Mon Sep 17 00:00:00 2001 From: pop-ecx Date: Mon, 1 Dec 2025 13:49:32 +0300 Subject: [PATCH 2/6] feat: delete files and directories --- .../rango/rango/agent_code/src/commands.zig | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/Payload_Type/rango/rango/agent_code/src/commands.zig b/Payload_Type/rango/rango/agent_code/src/commands.zig index 3417cea..d4f9f7c 100644 --- a/Payload_Type/rango/rango/agent_code/src/commands.zig +++ b/Payload_Type/rango/rango/agent_code/src/commands.zig @@ -31,6 +31,10 @@ pub const CommandExecutor = struct { return try self.executeDownload(task); } else if (std.mem.eql(u8, task.command, "upload")) { return try self.executeUpload(task); + } else if (std.mem.eql(u8, task.command, "deletefile")) { + return try self.deleteFile(task); + } else if (std.mem.eql(u8, task.command, "deletedirectory")) { + return try self.deleteDirectory(task); } else { return MythicResponse{ .task_id = task.id, @@ -248,4 +252,66 @@ pub const CommandExecutor = struct { .status = "completed", }; } + + fn deleteFile(self: *CommandExecutor, task: MythicTask) !MythicResponse { + const Parameters = struct { + path: []const u8, + }; + const parsed = try json.parseFromSlice(Parameters, self.allocator, task.parameters, .{}); + defer parsed.deinit(); + const path = parsed.value.path; + if (path.len == 0) { + return MythicResponse { + .task_id = task.id, + .user_output = "No path provided", + .completed = true, + .status = "error", + }; + } + std.fs.deleteFileAbsolute(path) catch |err| { + return MythicResponse { + .task_id = task.id, + .user_output = try std.fmt.allocPrint(self.allocator, "Failed to delete file: {}", .{err}), + .completed = true, + .status = "error", + }; + }; + return MythicResponse { + .task_id = task.id, + .user_output = try std.fmt.allocPrint(self.allocator, "Deleted file: {s}", .{path}), + .completed = true, + .status = "completed", + }; + } + + fn deleteDirectory(self: *CommandExecutor, task: MythicTask) !MythicResponse { + const Parameters = struct { + path: []const u8, + }; + const parsed = try json.parseFromSlice(Parameters, self.allocator, task.parameters, .{}); + defer parsed.deinit(); + const path = parsed.value.path; + if (path.len == 0) { + return MythicResponse { + .task_id = task.id, + .user_output = "No path provided", + .completed = true, + .status = "error", + }; + } + std.fs.deleteTreeAbsolute(path) catch |err| { + return MythicResponse { + .task_id = task.id, + .user_output = try std.fmt.allocPrint(self.allocator, "Failed to delete directory: {}", .{err}), + .completed = true, + .status = "error", + }; + }; + return MythicResponse { + .task_id = task.id, + .user_output = try std.fmt.allocPrint(self.allocator, "Deleted directory: {s}", .{path}), + .completed = true, + .status = "completed", + }; + } }; From 6d77a00502474a394bcb3351e5d04ea17b854c19 Mon Sep 17 00:00:00 2001 From: pop-ecx Date: Mon, 1 Dec 2025 13:51:52 +0300 Subject: [PATCH 3/6] fix: zig fmt --- Payload_Type/rango/rango/agent_code/src/commands.zig | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Payload_Type/rango/rango/agent_code/src/commands.zig b/Payload_Type/rango/rango/agent_code/src/commands.zig index d4f9f7c..3c6a0df 100644 --- a/Payload_Type/rango/rango/agent_code/src/commands.zig +++ b/Payload_Type/rango/rango/agent_code/src/commands.zig @@ -261,7 +261,7 @@ pub const CommandExecutor = struct { defer parsed.deinit(); const path = parsed.value.path; if (path.len == 0) { - return MythicResponse { + return MythicResponse{ .task_id = task.id, .user_output = "No path provided", .completed = true, @@ -269,14 +269,14 @@ pub const CommandExecutor = struct { }; } std.fs.deleteFileAbsolute(path) catch |err| { - return MythicResponse { + return MythicResponse{ .task_id = task.id, .user_output = try std.fmt.allocPrint(self.allocator, "Failed to delete file: {}", .{err}), .completed = true, .status = "error", }; }; - return MythicResponse { + return MythicResponse{ .task_id = task.id, .user_output = try std.fmt.allocPrint(self.allocator, "Deleted file: {s}", .{path}), .completed = true, @@ -292,7 +292,7 @@ pub const CommandExecutor = struct { defer parsed.deinit(); const path = parsed.value.path; if (path.len == 0) { - return MythicResponse { + return MythicResponse{ .task_id = task.id, .user_output = "No path provided", .completed = true, @@ -300,14 +300,14 @@ pub const CommandExecutor = struct { }; } std.fs.deleteTreeAbsolute(path) catch |err| { - return MythicResponse { + return MythicResponse{ .task_id = task.id, .user_output = try std.fmt.allocPrint(self.allocator, "Failed to delete directory: {}", .{err}), .completed = true, .status = "error", }; }; - return MythicResponse { + return MythicResponse{ .task_id = task.id, .user_output = try std.fmt.allocPrint(self.allocator, "Deleted directory: {s}", .{path}), .completed = true, From 5869fa6150cdb0670b8769cbe0dc7df7b4ad2d63 Mon Sep 17 00:00:00 2001 From: pop-ecx Date: Tue, 2 Dec 2025 10:54:57 +0300 Subject: [PATCH 4/6] fix: make parameter names match between command and agent --- .../rango/rango/agent_functions/delete_directory.py | 8 ++++---- Payload_Type/rango/rango/agent_functions/delete_file.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Payload_Type/rango/rango/agent_functions/delete_directory.py b/Payload_Type/rango/rango/agent_functions/delete_directory.py index eb56451..d0edc2a 100644 --- a/Payload_Type/rango/rango/agent_functions/delete_directory.py +++ b/Payload_Type/rango/rango/agent_functions/delete_directory.py @@ -6,7 +6,7 @@ class DeleteDirectoryArguments(TaskArguments): def __init__(self, command_line, **kwargs): super().__init__(command_line, **kwargs) self.args = [ - PathParameter(name="dirpath", display_name="Delete directory", type=ParameterType.String, + CommandParameter(name="path", display_name="Delete directory", type=ParameterType.String, description="Path of the directory to delete"), ] async def parse_arguments(self): @@ -52,13 +52,13 @@ async def create_go_tasking(self, taskData: MythicCommandBase.PTTaskMessageAllDa response = MythicCommandBase.PTTaskCreateTaskingMessageResponse( TaskID=taskData.Task.ID, Success=True, - Parameters=taskData.args.get_arg("dirpath"), + Parameters=taskData.args.get_arg("path"), ) await SendMythicRPCArtifactCreate(MythicRPCArtifactCreateMessage( - TaskID=taskData.Task.ID, ArtifactMessage="{}".format(taskData.args.get_arg("dirpath")), + TaskID=taskData.Task.ID, ArtifactMessage="{}".format(taskData.args.get_arg("path")), BaseArtifactType="Process Create" )) - response.DisplayParams = taskData.args.get_arg("dirpath") + response.DisplayParams = taskData.args.get_arg("path") return response async def process_response(self, task: PTTaskMessageAllData, response: any) -> PTTaskProcessResponseMessageResponse: resp = PTTaskProcessResponseMessageResponse(TaskID=task.Task.ID, Success=True) diff --git a/Payload_Type/rango/rango/agent_functions/delete_file.py b/Payload_Type/rango/rango/agent_functions/delete_file.py index 8a2d857..20a03b8 100644 --- a/Payload_Type/rango/rango/agent_functions/delete_file.py +++ b/Payload_Type/rango/rango/agent_functions/delete_file.py @@ -6,7 +6,7 @@ class DeleteFileArguments(TaskArguments): def __init__(self, command_line, **kwargs): super().__init__(command_line, **kwargs) self.args = [ - PathParameter(name="path", display_name="Delete file", type=ParameterType.String, + CommandParameter(name="path", display_name="Delete file", type=ParameterType.String, description="Path of the file to delete"), ] From 2110f33d79842c11fdc30d534fa3ba4270cd9a75 Mon Sep 17 00:00:00 2001 From: pop-ecx Date: Tue, 2 Dec 2025 10:55:20 +0300 Subject: [PATCH 5/6] fix: Change command name --- Payload_Type/rango/rango/agent_functions/delete_directory.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Payload_Type/rango/rango/agent_functions/delete_directory.py b/Payload_Type/rango/rango/agent_functions/delete_directory.py index d0edc2a..ffd40df 100644 --- a/Payload_Type/rango/rango/agent_functions/delete_directory.py +++ b/Payload_Type/rango/rango/agent_functions/delete_directory.py @@ -18,7 +18,7 @@ async def parse_dictionary(self, dictionary_arguments): self.load_args_from_dictionary(dictionary_arguments) class DeleteDirectoryCommand(CommandBase): - cmd = "delete_directory" + cmd = "deletedirectory" needs_admin = False help_cmd = "delete_directory /path/to/directory" description = """This deletes the specified directory""" From 8c0110bc5352776332d70f2c9b62844a174ec707 Mon Sep 17 00:00:00 2001 From: pop-ecx Date: Tue, 2 Dec 2025 11:02:56 +0300 Subject: [PATCH 6/6] docs: documentation for deletefile and deletedirectory --- .../rango/commands/deletedirectory.md | 20 +++++++++++++++++++ .../rango/commands/deletefile.md | 20 +++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 documentation-payload/rango/commands/deletedirectory.md create mode 100644 documentation-payload/rango/commands/deletefile.md diff --git a/documentation-payload/rango/commands/deletedirectory.md b/documentation-payload/rango/commands/deletedirectory.md new file mode 100644 index 0000000..55b838e --- /dev/null +++ b/documentation-payload/rango/commands/deletedirectory.md @@ -0,0 +1,20 @@ +## Description +deletedirectory is used to delete a specified directory from the file system. + +## Usage + +``` +deleledirectory +``` + +### Examples + +``` +> for linux +deletedirectory /home/user/important +``` + +``` +> for windows +deletedirectory C:\Users\user\important +``` diff --git a/documentation-payload/rango/commands/deletefile.md b/documentation-payload/rango/commands/deletefile.md new file mode 100644 index 0000000..f29cac2 --- /dev/null +++ b/documentation-payload/rango/commands/deletefile.md @@ -0,0 +1,20 @@ +## Description +deletefile is used to delete a specified file from the file system. + +## Usage + +``` +deletefile +``` + +### Examples + +``` +> for linux +deletefile /home/user/important.txt +``` + +``` +> for windows +deletefile C:\Users\user\important.txt +```