Created
January 2, 2022 10:25
-
-
Save olliencc/9f4bb9535c4f0ef0e54eac7912ab49c0 to your computer and use it in GitHub Desktop.
Enumerates processes which use VEH via their PEB and then counts the number of VEHs present
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
/* | |
VEH misuse detector for Microsoft Windows | |
Released as open source by NCC Group Plc - http://www.nccgroup.com/ | |
Developed by Ollie Whitehouse, ollie dot whitehouse at nccgroup dot com | |
Released under AGPL see LICENSE for more information | |
*/ | |
// | |
// WARNING this is very much experimental and work in process and won't work on your host | |
// Reasons for this include: | |
// - this doesn't come with all the headers | |
// - GetVEHOffset is still bodged to use a static address | |
// | |
// Sources | |
// https://dimitrifourny.github.io/2020/06/11/dumping-veh-win10.html | |
// https://www.unknowncheats.me/forum/c-and-c-/160827-internals-addvectoredexceptionhandler.html | |
// http://rinseandrepeatanalysis.blogspot.com/p/peb-structure.html | |
// https://bytepointer.com/resources/tebpeb32.htm | |
// https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/pebteb/peb/crossprocessflags.htm | |
// https://github.com/processhacker/processhacker/blob/master/phnt/include/ntpsapi.h | |
// https://github.com/cradiator/CrMisc/blob/master/VEH/VEH/VEH.cpp | |
// https://github.com/DarthTon/Blackbone/blob/master/src/BlackBone/ManualMap/MExcept.cpp | |
// Includes | |
#include "stdafx.h" | |
// Globals | |
TCHAR strErrMsg[1024]; | |
DWORD dwModuleRelocs = 0; | |
DWORD dwCountError = 0; | |
DWORD dwCountOK = 0; | |
DWORD dwVEH = 0; | |
DWORD dwVCH = 0; | |
DWORD dwOpen = 0; | |
// Structures to hold process information | |
#pragma pack(push, 1) | |
struct procNfoStuct { | |
DWORD PID; | |
TCHAR Name[MAX_PATH]; | |
unsigned long long TotalExecMem = 0; | |
}; | |
#pragma pack(pop) | |
procNfoStuct Procs[4098]; | |
DWORD NumOfProcs = 0; | |
// Manual imports | |
_NtQueryInformationProcess __NtQueryInformationProcess = (_NtQueryInformationProcess)GetProcAddress(GetModuleHandle(_T("ntdll.dll")), "NtQueryInformationProcess"); | |
_MyNtQueryInformationProcess __MyNtQueryInformationProcess = (_MyNtQueryInformationProcess)GetProcAddress(GetModuleHandle(_T("ntdll.dll")), "NtQueryInformationProcess"); | |
typedef BOOL(WINAPI* LPFN_ISWOW64PROCESS) (HANDLE, PBOOL); | |
LPFN_ISWOW64PROCESS fnIsWow64Process = fnIsWow64Process = (LPFN_ISWOW64PROCESS)GetProcAddress(GetModuleHandle(TEXT("kernel32")), "IsWow64Process"); | |
// | |
// Function : SetDebugPrivilege | |
// Role : Gets privs for our process | |
// Notes : | |
// | |
BOOL SetPrivilege(HANDLE hProcess, LPCTSTR lPriv) | |
{ | |
LUID luid; | |
TOKEN_PRIVILEGES privs; | |
HANDLE hToken = NULL; | |
DWORD dwBufLen = 0; | |
char buf[1024]; | |
ZeroMemory(&luid, sizeof(luid)); | |
if (!LookupPrivilegeValue(NULL, lPriv, &luid)) return false; | |
privs.PrivilegeCount = 1; | |
privs.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; | |
memcpy(&privs.Privileges[0].Luid, &luid, sizeof(privs.Privileges[0].Luid)); | |
if (!OpenProcessToken(hProcess, TOKEN_ALL_ACCESS, &hToken)) | |
return false; | |
if (!AdjustTokenPrivileges(hToken, FALSE, &privs, | |
sizeof(buf), (PTOKEN_PRIVILEGES)buf, &dwBufLen)) | |
return false; | |
CloseHandle(hProcess); | |
CloseHandle(hToken); | |
return true; | |
} | |
// | |
// | |
// | |
template<class T> | |
static T ror(T x, unsigned int moves) | |
{ | |
return (x >> moves) | (x << (sizeof(T) * 8 - moves)); | |
} | |
template<class T> | |
static T rol(T x, unsigned int moves) | |
{ | |
return (x << moves) | (x >> (sizeof(T) * 8 - moves)); | |
} | |
// | |
// Decodes encoded pointers | |
// original: https://github.com/x64dbg/x64dbg/blob/4ee4a51e43cc5d1d11316098e925676046f28a86/src/dbg/memory.cpp | |
// | |
bool MemDecodePointer(unsigned long* Pointer, ULONG cookie) | |
{ | |
*Pointer = ror(*Pointer, (0x40 - (cookie & 0x3F)) & 0xFF); | |
// XOR pointer with key | |
*Pointer ^= cookie; | |
return true; | |
} | |
// | |
// Get the Process Cookie to allow the decoding for pointers | |
// - the cookies returned have been validated | |
// | |
ULONG GetProcessCookie(HANDLE hProcess) { | |
NTSTATUS Status; | |
ULONG dwProcCookie = 0; | |
Status = __MyNtQueryInformationProcess(hProcess, myProcessCookie, (DWORD_PTR*)&dwProcCookie, sizeof(ULONG), NULL); | |
if (Status != 0) | |
{ | |
return 0; | |
} | |
return dwProcCookie; | |
} | |
// | |
// Get theh VEH list from a process | |
// | |
BOOL GetVEHfromProc(HANDLE hProcess, ULONGLONG VEHAddress, TCHAR* cProcess, DWORD dwPID, ULONG Cookie) { | |
DWORD dwVEHs = 0; | |
VECTORED_HANDLER_LIST_OW handler_list; | |
SIZE_T dwRead = 0; | |
// Debug - print the process cookie | |
//fwprintf(stdout, _TEXT("[i] [%d][%s] Process cookie: 0x%lx\n"), dwPID, cProcess, GetProcessCookie(hProcess)); | |
// Read the VEH hander list | |
if (ReadProcessMemory(hProcess, (LPCVOID)VEHAddress, &handler_list, sizeof(handler_list), &dwRead) == FALSE) { | |
return 0; | |
} | |
//fwprintf(stdout, _TEXT("[d] [%d][%s] VEH end: 0x%.16llX\n"), dwPID, cProcess, VEHAddress + sizeof(DWORD64)); | |
//fwprintf(stdout, _TEXT("[d] [%d][%s] First VEH entry: 0x%p\n"), dwPID, cProcess, handler_list.first_exception_handler); | |
//fwprintf(stdout, _TEXT("[d] [%d][%s] Last VEH entry: 0x%p\n"), dwPID, cProcess, handler_list.last_exception_handler); | |
// Check if it is empty | |
if ((DWORD64)handler_list.first_exception_handler == VEHAddress + sizeof(DWORD64)) { | |
fwprintf(stdout, _TEXT("[d] [%d][%s] VEH list is empty\n"), dwPID, cProcess); | |
return 0; | |
} | |
VEH_HANDLER_ENTRY entry; | |
// fprintf(stdout, "Reading %p for %zu bytes - %zu %zu %zu %zu\n", (LPCVOID)handler_list.first_exception_handler,sizeof(entry),sizeof(LIST_ENTRY),sizeof(DWORD),sizeof(PVECTORED_EXCEPTION_HANDLER),(sizeof(entry), sizeof(LIST_ENTRY)+ sizeof(DWORD)+ sizeof(PVECTORED_EXCEPTION_HANDLER))); | |
// Read the first entry | |
if (ReadProcessMemory(hProcess, (LPCVOID)handler_list.first_exception_handler, &entry, sizeof(entry), &dwRead) == FALSE) { | |
fprintf(stdout, "Failed to read\n"); | |
return 0; | |
} | |
if (dwRead != sizeof(entry)) { | |
fprintf(stdout, "Failed to read 2\n"); | |
return 0; | |
} | |
while (true) { | |
dwVEHs++; | |
// Decode the pointer using the cookie we previously got | |
//fwprintf(stdout, _TEXT("[d] [%d][%s] VEH handler.count %u\n"), dwPID, cProcess, entry.Count); | |
//fwprintf(stdout, _TEXT("[d] [%d][%s] VEH handler.next 0x%p\n"), dwPID, cProcess, (void*)entry.Entry.Flink); | |
//fwprintf(stdout, _TEXT("[d] [%d][%s] VEH handler.previous 0x%p\n"), dwPID, cProcess, (void*)entry.Entry.Blink); | |
/* | |
TODO - this doesn't work | |
fwprintf(stdout, _TEXT("[d] [%d][%s] VEH handler.handler 0x%p\n"), dwPID, cProcess, (void*)entry.VectoredHandler); | |
fwprintf(stdout, _TEXT("[d] [%d][%s] VEH handler.handler 0x%p\n"), dwPID, cProcess, (void*)VectorHandler); | |
unsigned long dwPtr = (unsigned long)entry.VectoredHandler; | |
MemDecodePointer(&dwPtr, Cookie); | |
fwprintf(stdout, _TEXT("[d] [%d][%s] VEH handler(decoded) 0x%p\n"), dwPID, cProcess, dwPtr); | |
dwPtr = (unsigned long)VectorHandler; | |
MemDecodePointer(&dwPtr, Cookie); | |
fwprintf(stdout, _TEXT("[d] [%d][%s] VEH handler(decoded)20x%p\n"), dwPID, cProcess, dwPtr); | |
*/ | |
if ((DWORD64)(entry.Entry.Flink) == VEHAddress + sizeof(DWORD64)) { | |
fwprintf(stdout, _TEXT("[d] [%d][%s] # of VEH: %d\n"), dwPID, cProcess, dwVEHs); | |
break; | |
} | |
// Read the next entry | |
if (ReadProcessMemory(hProcess, (LPCVOID)entry.Entry.Flink, &entry, sizeof(entry), &dwRead) == FALSE) { | |
return 0; | |
} | |
} | |
return TRUE; | |
} | |
// | |
// Get's the VEH offset | |
// - this is the address of LdrpVectorHandlerList | |
// - at the moment it is retrieve once per boot | |
// | |
ULONGLONG GetVEHOffset() { | |
HMODULE ntdll = LoadLibraryA("ntdll.dll"); | |
ULONGLONG procAddress = (ULONGLONG)GetProcAddress(ntdll, "RtlAddVectoredExceptionHandler"); | |
BYTE* add_exception_handler = reinterpret_cast<BYTE*>(GetProcAddress(ntdll, "RtlAddVectoredExceptionHandler")); | |
// | |
// TODO - this is not finished | |
// | |
//fwprintf(stdout, _TEXT("[i] [%llu]\n"), (0x07FFE4764F3E8 - procAddress)); | |
// return base + offset | |
return 0x07FFE4764F3E8; // got via the debugger for now | |
} | |
// | |
// Get the PEB for the process we are interested in and then return just the CrossProcessFlags | |
// | |
// | |
DWORD GetPEB(HANDLE hProcess, PEB* outPEB, DWORD64* CrossProcessFlags) { | |
NTSTATUS Status; | |
PROCESS_BASIC_INFORMATION ProcessInformation; | |
Status = __NtQueryInformationProcess(hProcess, ProcessBasicInformation, (DWORD_PTR*)&ProcessInformation, sizeof(ProcessInformation), NULL); | |
if (Status != 0) | |
{ | |
return 0; | |
} | |
SIZE_T dwRead = 0; | |
if (ReadProcessMemory(hProcess, ProcessInformation.PebBaseAddress, outPEB, sizeof(PEB), &dwRead) == FALSE) { | |
return 0; | |
} | |
PPEB pPEB = (PPEB)ProcessInformation.PebBaseAddress; | |
if (ReadProcessMemory(hProcess, (PBYTE)pPEB + 0x50, (LPVOID)CrossProcessFlags, sizeof(DWORD64), &dwRead) == FALSE) { | |
return 0; | |
} | |
return (DWORD)dwRead; | |
} | |
/// <summary> | |
/// Analyze the process and its memory regions | |
/// </summary> | |
/// <param name="dwPID">Process ID</param> | |
void AnalyzeProc(DWORD dwPID) | |
{ | |
DWORD dwRet, dwMods; | |
HANDLE hProcess; | |
HMODULE hModule[4096]; | |
TCHAR cProcess[MAX_PATH]; // Process name | |
BOOL bIsWow64 = FALSE; | |
BOOL bIsWow64Other = FALSE; | |
DWORD dwRES = 0; | |
// Get process handle by hook or by crook | |
hProcess = OpenProcess(PROCESS_ALL_ACCESS | PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, dwPID); | |
if (hProcess == NULL) | |
{ | |
if (GetLastError() == 5) { | |
hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, dwPID); | |
if (hProcess == NULL) { | |
hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, dwPID); | |
if (hProcess == NULL) { | |
fwprintf(stderr, _TEXT("[!] [%d][UNKNOWN] Failed to OpenProcess - %d\n"), dwPID, GetLastError()); | |
dwCountError++; | |
return; | |
} | |
} | |
} | |
else { | |
fwprintf(stderr, _TEXT("[!] [%d][UNKNOWN] Failed to OpenProcess - %d\n"), dwPID, GetLastError()); | |
dwCountError++; | |
return; | |
} | |
} | |
// Enumerate the process modules | |
if (EnumProcessModules(hProcess, hModule, 4096 * sizeof(HMODULE), &dwRet) == FALSE) | |
{ | |
DWORD dwSz = MAX_PATH; | |
if (QueryFullProcessImageName(hProcess, 0, cProcess, &dwSz) == TRUE) { | |
fwprintf(stdout, _TEXT("[i] [%d][%s] not analysed %d\n"), dwPID, cProcess, GetLastError()); | |
dwOpen++; | |
} | |
else { | |
fwprintf(stdout, _TEXT("[i] [%d][%s] not analysed %d\n"), dwPID, _TEXT("UNKNOWN"), GetLastError()); | |
dwOpen++; | |
} | |
if (GetLastError() == 299) { | |
//fprintf(stderr, "64bit process and we're 32bit - sad panda! skipping PID %d\n", dwPID); | |
} | |
else { | |
//fprintf(stderr, "Error in EnumProcessModules(%d),%d\n", dwPID, GetLastError()); | |
} | |
dwCountError++; | |
return; | |
} | |
dwMods = dwRet / sizeof(HMODULE); | |
// Get the processes name from the first module returned by the above | |
GetModuleBaseName(hProcess, hModule[0], cProcess, MAX_PATH); | |
Procs[NumOfProcs].PID = dwPID; | |
_tcscpy_s(Procs[NumOfProcs].Name, MAX_PATH, cProcess); | |
//fwprintf(stdout, _TEXT("[i] [%d][%s] analyzing\n"), dwPID, cProcess); | |
NumOfProcs++; | |
PEB myPEB; | |
DWORD64 CrossProcessFlags = -1; | |
if (GetPEB(hProcess, &myPEB, &CrossProcessFlags) > 0) { | |
if (myPEB.BeingDebugged == 1) { | |
fwprintf(stdout, _TEXT("[i] [%d][%s] is being debugged\n"), dwPID, cProcess); | |
} | |
if (CrossProcessFlags & 0x4) { | |
fwprintf(stdout, _TEXT("[i] [%d][%s] is using VEH - Vectored Exception Handler\n"), dwPID, cProcess); | |
dwVEH++; | |
} | |
if (CrossProcessFlags & 0x8) { | |
fwprintf(stdout, _TEXT("[i] [%d][%s] is using VCH - Vectored Continue Handler\n"), dwPID, cProcess); | |
dwVCH++; | |
} | |
if (CrossProcessFlags & 0x80) { | |
fwprintf(stdout, _TEXT("[i] [%d][%s] is hot patched\n"), dwPID, cProcess); | |
} | |
if ((CrossProcessFlags & 0x4) or (CrossProcessFlags & 0x8)) { | |
// LdrpVectorHandlerList | |
ULONGLONG dwVEHAddress = GetVEHOffset(); | |
//fwprintf(stdout, _TEXT("[i] [%d][%s] VEH address: 0x%p\n"), dwPID, cProcess, (void*)dwVEHAddress); | |
// Now dump the VEH | |
GetVEHfromProc(hProcess, dwVEHAddress,cProcess, dwPID, GetProcessCookie(hProcess)); | |
} | |
} | |
else { | |
fwprintf(stderr, _TEXT("[!] [%d][UNKNOWN] Failed to get PEB\n"), dwPID); | |
} | |
dwCountOK++; | |
CloseHandle(hProcess); | |
} | |
/// <summary> | |
/// Enumerate all the processes on the system and | |
/// pass off to the analysis function | |
/// </summary> | |
void EnumerateProcesses() | |
{ | |
DWORD dwPIDArray[4096], dwRet, dwPIDS, intCount; | |
NumOfProcs = 0; | |
// Privs | |
SetPrivilege(GetCurrentProcess(), SE_DEBUG_NAME); | |
// Be clean | |
memset(Procs, 0x00, sizeof(Procs)); | |
// | |
// Enumerate | |
// | |
if (EnumProcesses(dwPIDArray, 4096 * sizeof(DWORD), &dwRet) == 0) | |
{ | |
DWORD dwRet = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, GetLastError(), 0, strErrMsg, 1023, NULL); | |
if (dwRet != 0) { | |
_ftprintf(stderr, TEXT("[!] EnumProcesses() failed - %s"), strErrMsg); | |
} | |
else | |
{ | |
_ftprintf(stderr, TEXT("[!] EnumProcesses() - Error: %d\n"), GetLastError()); | |
} | |
return; | |
} | |
// Total nuber of process IDs | |
dwPIDS = dwRet / sizeof(DWORD); | |
// | |
// Analyze | |
// | |
for (intCount = 0; intCount < dwPIDS; intCount++) | |
{ | |
//fwprintf(stdout, _TEXT("[i] Analyzing PID %d\n"), dwPIDArray[intCount]); | |
AnalyzeProc(dwPIDArray[intCount]); | |
} | |
fwprintf(stdout, _TEXT("[i] Total of %d processes %d use VEH and %d use VCH - didn't open %d \n"), dwPIDS, dwVEH, dwVCH, dwOpen); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment