Random Windows API code snippets

This post is a repository of functions that implement key functionalities on Windows. Written in C/C++ and extensive usage of the Windows API.

Any feedback is welcome. See the Contact page for contact information.

Index

In this page you can find implementations and wrappers for the following Windows functionalities:

List AntiVirus and AntiSpyware Product via WMI and COM

The code snippet prints the name of the AntiVirus and AntiSpyware product, leveraging WMI and COM. Inspired by Red Team Tips.

// WMI
#include <wbemcli.h>
#pragma comment(lib, "wbemuuid.lib")

INT EnumAntiVirusProduct()
{
	// PowerShell equivalent:
	// Get-CimInstance -Namespace root/SecurityCenter2 -ClassName AntiVirusProduct
	//
	// WMI equivalent:
	// WMIC /Node:localhost /Namespace:\root\SecurityCenter2 Path AntiVirusProduct Get displayName /Format:List
	//
	// reference: https://redteamer.tips/red-team-tips/
	
		HRESULT hr;
	hr = CoInitializeEx(0, COINIT_MULTITHREADED);
	if (FAILED(hr))
	{
		::wprintf(L"[-] CoInitializeEx has failed\n");
		return 0;
	}

	hr = CoInitializeSecurity(NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, NULL);
	if (FAILED(hr))
	{
		::wprintf(L"[-] CoInitializeSecurity has failed\n");
		CoUninitialize();
		return 0;
	}

	IWbemLocator* pLoc = 0;
	hr = CoCreateInstance(CLSID_WbemLocator, 0, CLSCTX_INPROC_SERVER, IID_IWbemLocator, (LPVOID*)&pLoc);
	if (FAILED(hr))
	{
		::wprintf(L"[-] CoCreateInstance has failed\n");
		CoUninitialize();
		return 0;
	}

	IWbemServices* pSvc = 0;
	hr = pLoc->ConnectServer(BSTR(L"ROOT\\SecurityCenter2"), NULL, NULL, 0, NULL, 0, 0, &pSvc);
	if (FAILED(hr))
	{
		::wprintf(L"[-] ConnectServer has failed\n");
		pLoc->Release();
		CoUninitialize();
		return 0;
	}

	BSTR Language = BSTR(L"WQL");
	BSTR Query = BSTR(L"SELECT * FROM AntiVirusProduct");
	IEnumWbemClassObject* pEnum = 0;
	hr = pSvc->ExecQuery(Language, Query, WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, 0, &pEnum);
	if (FAILED(hr))
	{
		::wprintf(L"[-] ExecQuery has failed\n");
		pSvc->Release();
		pLoc->Release();
		CoUninitialize();
		return 0;
	}

	IWbemClassObject* pObj = 0;
	ULONG uRet = 0;
	VARIANT v;
	CIMTYPE ctype = 0;
	while (pEnum)
	{
		hr = pEnum->Next(WBEM_INFINITE, 1, &pObj, &uRet);

		if (uRet == 0) { break; }

		hr = pObj->Get(L"displayName", 0, &v, &ctype, NULL);
		if (FAILED(hr))
		{
			::wprintf(L"[-] IWbemClassObject::Get has failed\n");
			pObj->Release();
			pLoc->Release();
			pSvc->Release();
			pEnum->Release();
			CoUninitialize();
			return 0;
		}

		wprintf(L"[*] AntiVirus product name: %s\n", V_BSTR(&v));

		hr = pObj->Get(L"pathToSignedReportingExe", 0, &v, &ctype, NULL);
		if (FAILED(hr))
		{
			::wprintf(L"[-] IWbemClassObject::Get has failed\n");
			pObj->Release();
			pLoc->Release();
			pSvc->Release();
			pEnum->Release();
			CoUninitialize();
			return 0;
		}

		wprintf(L"[*] AntiVirus product executable path: %s\n", V_BSTR(&v));
	}

	Query = BSTR(L"SELECT * FROM AntiSpywareProduct");
	pEnum = 0;
	hr = pSvc->ExecQuery(Language, Query, WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, 0, &pEnum);
	if (FAILED(hr))
	{
		::wprintf(L"[-] ExecQuery has failed\n");
		pSvc->Release();
		pLoc->Release();
		CoUninitialize();
		return 0;
	}

	ctype = 0;
	while (pEnum)
	{
		hr = pEnum->Next(WBEM_INFINITE, 1, &pObj, &uRet);

		if (uRet == 0) { break; }

		hr = pObj->Get(L"displayName", 0, &v, &ctype, NULL);
		if (FAILED(hr))
		{
			::wprintf(L"[-] IWbemClassObject::Get has failed\n");
			pObj->Release();
			pLoc->Release();
			pSvc->Release();
			pEnum->Release();
			CoUninitialize();
			return 0;
		}

		wprintf(L"[*] AntiSpyware product name: %s\n", V_BSTR(&v));

		hr = pObj->Get(L"pathToSignedReportingExe", 0, &v, &ctype, NULL);
		if (FAILED(hr))
		{
			::wprintf(L"[-] IWbemClassObject::Get has failed\n");
			pObj->Release();
			pLoc->Release();
			pSvc->Release();
			pEnum->Release();
			CoUninitialize();
			return 0;
		}

		wprintf(L"[*] AntiSpyware product executable path: %s\n", V_BSTR(&v));
	}

	return 1;	
}

List Named Pipes

C/C++ code to list named pipes and identify named pipes with empty DACL. The code is unstable, as it creates a large number of handles, which do not close. Additionally, the function does not end from time to time. The code was inspired by Matt Nelson’s work on https://posts.specterops.io/avira-optimizer-local-privilege-escalation-af109b7df5b1

#include <psapi.h>
#include <shlwapi.h>

#pragma comment(lib, "Shlwapi.lib")

#define NtCurrentProcess() ((HANDLE)-1)
#define NT_ERROR(Status) (((NTSTATUS)(Status)) >= (unsigned long)0xc0000000)

#define DUPLICATE_SAME_ACCESS 0x00000002
#define DUPLICATE_SAME_ATTRIBUTES 0x00000004

// https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ex/sysinfo/handle_table_entry.htm
typedef struct _SYSTEM_HANDLE_TABLE_ENTRY_INFO
{
	USHORT UniqueProcessId;
	USHORT CreatorBackTraceIndex;
	UCHAR ObjectTypeIndex;
	UCHAR HandleAttributes;
	USHORT HandleValue;
	PVOID Object;
	ULONG GrantedAccess;
} SYSTEM_HANDLE_TABLE_ENTRY_INFO, * PSYSTEM_HANDLE_TABLE_ENTRY_INFO;

// https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ex/sysinfo/handle.htm
typedef struct _SYSTEM_HANDLE_INFORMATION {
	ULONG NumberOfHandles;
	SYSTEM_HANDLE_TABLE_ENTRY_INFO Handles[1];
} SYSTEM_HANDLE_INFORMATION, * PSYSTEM_HANDLE_INFORMATION;

typedef enum _POOL_TYPE {
	NonPagedPool,
	NonPagedPoolExecute = NonPagedPool,
	PagedPool,
	NonPagedPoolMustSucceed = NonPagedPool + 2,
	DontUseThisType,
	NonPagedPoolCacheAligned = NonPagedPool + 4,
	PagedPoolCacheAligned,
	NonPagedPoolCacheAlignedMustS = NonPagedPool + 6,
	MaxPoolType,
	NonPagedPoolBase = 0,
	NonPagedPoolBaseMustSucceed = NonPagedPoolBase + 2,
	NonPagedPoolBaseCacheAligned = NonPagedPoolBase + 4,
	NonPagedPoolBaseCacheAlignedMustS = NonPagedPoolBase + 6,
	NonPagedPoolSession = 32,
	PagedPoolSession = NonPagedPoolSession + 1,
	NonPagedPoolMustSucceedSession = PagedPoolSession + 1,
	DontUseThisTypeSession = NonPagedPoolMustSucceedSession + 1,
	NonPagedPoolCacheAlignedSession = DontUseThisTypeSession + 1,
	PagedPoolCacheAlignedSession = NonPagedPoolCacheAlignedSession + 1,
	NonPagedPoolCacheAlignedMustSSession = PagedPoolCacheAlignedSession + 1,
	NonPagedPoolNx = 512,
	NonPagedPoolNxCacheAligned = NonPagedPoolNx + 4,
	NonPagedPoolSessionNx = NonPagedPoolNx + 32,
} POOL_TYPE;

typedef struct _OBJECT_TYPE_INFORMATION {
	UNICODE_STRING Name;
	ULONG TotalNumberOfObjects;
	ULONG TotalNumberOfHandles;
	ULONG TotalPagedPoolUsage;
	ULONG TotalNonPagedPoolUsage;
	ULONG TotalNamePoolUsage;
	ULONG TotalHandleTableUsage;
	ULONG HighWaterNumberOfObjects;
	ULONG HighWaterNumberOfHandles;
	ULONG HighWaterPagedPoolUsage;
	ULONG HighWaterNonPagedPoolUsage;
	ULONG HighWaterNamePoolUsage;
	ULONG HighWaterHandleTableUsage;
	ULONG InvalidAttributes;
	GENERIC_MAPPING GenericMapping;
	ULONG ValidAccess;
	BOOLEAN SecurityRequired;
	BOOLEAN MaintainHandleCount;
	USHORT MaintainTypeList;
	POOL_TYPE PoolType;
	ULONG PagedPoolUsage;
	ULONG NonPagedPoolUsage;
} OBJECT_TYPE_INFORMATION, * POBJECT_TYPE_INFORMATION;

typedef struct _OBJECT_NAME_INFORMATION {
	UNICODE_STRING Name;
	WCHAR NameBuffer[1];
} OBJECT_NAME_INFORMATION, * POBJECT_NAME_INFORMATION;

#define SystemHandleInformation 0x10

typedef NTSTATUS(*fnNtQuerySystemInformation)(INT, PVOID, ULONG, PULONG);
typedef NTSTATUS(*fnNtDuplicateObject)(HANDLE, HANDLE, HANDLE, PHANDLE, ACCESS_MASK, ULONG, ULONG);
typedef NTSTATUS(*fnNtQueryObject)(HANDLE, INT, PVOID, ULONG, PULONG);
typedef NTSTATUS(*fnNtMakeTemporaryObject)(HANDLE);

fnNtQueryObject NtQueryObject = (fnNtQueryObject)GetProcAddress(GetModuleHandleW(L"ntdll"), "NtQueryObject");

typedef struct _THREAD_IT {
	HANDLE ObjHandle;
	ULONG length;
	HANDLE hProcess;
} THREAD_IT, * PTHREAD_IT;

DWORD ThreadIt(LPVOID lpParam)
{
	THREAD_IT* threadParams = (THREAD_IT*)lpParam;
	NTSTATUS status = NtQueryObject(threadParams->ObjHandle, 1, NULL, NULL, &threadParams->length);
	WCHAR ProcName[MAX_PATH] = { 0 };
	OBJECT_NAME_INFORMATION* ObjTypeInformation = (OBJECT_NAME_INFORMATION*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, threadParams->length);
	DWORD status_GetSecurityInfo = 0;
	PSECURITY_DESCRIPTOR ppSecurityDescriptor = NULL;
	ACL* pDacl = NULL;
	

	if (NT_ERROR(status))
	{
		//::wprintf(L"[-] NtQueryObject first run has failed: 0x%x\n", status);
		if (status != 0xc0000004)
		{
			//::wprintf(L"[-] Exiting...\n");
			HeapFree(GetProcessHeap(), HEAP_ZERO_MEMORY, ObjTypeInformation);
			return 1;
		}

		status = NtQueryObject(threadParams->ObjHandle, 1, ObjTypeInformation, threadParams->length, &threadParams->length);
		if (NT_ERROR(status))
		{
			//::wprintf(L"[-] NtQueryObject second run has failed: 0x%x\n", status);
			HeapFree(GetProcessHeap(), HEAP_ZERO_MEMORY, ObjTypeInformation);
			return 1;
		}

		// printf only the type of the object
		if (StrStrW(ObjTypeInformation->NameBuffer, L"\\Device\\NamedPipe\\") != NULL)
		{
			GetProcessImageFileNameW(threadParams->hProcess, ProcName, MAX_PATH);
			::wprintf(L"[+] Process: %s\n\tObjectType: %s\n", ProcName, ObjTypeInformation->NameBuffer);
		}

		// look for empty DACL
		status_GetSecurityInfo = GetSecurityInfo(threadParams->ObjHandle, SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION, NULL, NULL, &pDacl, NULL, &ppSecurityDescriptor);
		if (status_GetSecurityInfo != ERROR_SUCCESS)
		{
			//::wprintf(L"[-] GetSecurityInfo has failed: %d\n", status_GetSecurityInfo);
		}
		else
		{
			if (pDacl == NULL)
			{
				::wprintf(L"[!] Null DACL was found!\n");
			}
		}

		HeapFree(GetProcessHeap(), HEAP_ZERO_MEMORY, ObjTypeInformation);
	}	

	return 0;
}

INT GetHandlesWrapper()
{
	fnNtDuplicateObject NtDuplicateObject = (fnNtDuplicateObject)GetProcAddress(GetModuleHandleW(L"ntdll"), "NtDuplicateObject");
	fnNtQuerySystemInformation NtQuerySystemInformation = (fnNtQuerySystemInformation)GetProcAddress(GetModuleHandleW(L"ntdll"), "NtQuerySystemInformation");
	fnNtMakeTemporaryObject NtMakeTemporaryObject = (fnNtMakeTemporaryObject)GetProcAddress(GetModuleHandleW(L"ntdll"), "NtMakeTemporaryObject");

	PSYSTEM_HANDLE_INFORMATION SystemInformation = NULL;
	SIZE_T szBuffer = 0xffffff;
	SystemInformation = (PSYSTEM_HANDLE_INFORMATION)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, szBuffer);

	ULONG ReturnLength = 0;
	NTSTATUS status = NtQuerySystemInformation(SystemHandleInformation, SystemInformation, (ULONG)szBuffer, &ReturnLength);
	if (status != 0)
	{
		::wprintf(L"[-] NtQuerySystemInformation status code: 0x%x\n", status);
		if (status != 0xc0000004)
		{
			::wprintf(L"[-] Exiting...\n");
			HeapFree(GetProcessHeap(), HEAP_ZERO_MEMORY, SystemInformation);
			return 0;
		}
		SystemInformation = (PSYSTEM_HANDLE_INFORMATION)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, ReturnLength);
		status = NtQuerySystemInformation(SystemHandleInformation, SystemInformation, ReturnLength, &ReturnLength);
		::wprintf(L"[-] NtQuerySystemInformation second run status code: 0x%x\n", status);
	}
		
	// enable SeDebugPrivilege
	EnablePrivilege(L"SeDebugPrivilege"); // custom function
	
	HANDLE tmphandle = 0;
	HANDLE dupObjHandle = 0;
	HANDLE hThread = 0;
	HANDLE hProcess = 0;
	PTHREAD_IT lpParameter;
	OBJECT_TYPE_INFORMATION* ObjTypeInformation = { 0 };
	DWORD SingleObj = 0;

	for (ULONG i = 0; i < SystemInformation->NumberOfHandles; i++)
	{
		hProcess = OpenProcess(
			PROCESS_QUERY_INFORMATION | PROCESS_DUP_HANDLE,
			TRUE,
			(DWORD)SystemInformation->Handles[i].UniqueProcessId
		);
		if (hProcess == NULL)
		{
			//::wprintf(L"[-] OpenProcess %hu has failed: %d\n", SystemInformation->Handles[i].UniqueProcessId, GetLastError());
			continue;
		}

		//NtMakeTemporaryObject(hProcess);
		// duplicate the handle in question
		status = NtDuplicateObject(
			hProcess, 
			(HANDLE)SystemInformation->Handles[i].HandleValue, 
			NtCurrentProcess(), 
			&dupObjHandle, 
			PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, 
			0, 
			DUPLICATE_SAME_ATTRIBUTES | DUPLICATE_SAME_ACCESS
		);
		if (NT_ERROR(status))
		{
			//::wprintf(L"[-] NtDuplicateObject has failed: 0x%x\n", status);
			CloseHandle(hProcess);
			continue;
		}

		DWORD blue = GetFileType(dupObjHandle);
		if (blue != 0x3)
		{
			//::wprintf(L"--> namedpipe %d\n", blue);
			continue;
		}

		lpParameter = (THREAD_IT*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(THREAD_IT));
		lpParameter->ObjHandle = dupObjHandle; // duplicate object handle
		lpParameter->length = 0;				// oobject information length
		lpParameter->hProcess = hProcess;		// process handle
		hThread = CreateThread(NULL, 0, ThreadIt, lpParameter, 0, NULL);
		if (hThread == NULL)
		{
			::wprintf(L"[--] CreateThread has failed: %d\n", GetLastError());
		}
		
		SingleObj = WaitForSingleObject(hThread, 50);
		if (SingleObj == WAIT_TIMEOUT)
		{
			TerminateThread(hThread, 0);
			HeapFree(GetProcessHeap(), HEAP_ZERO_MEMORY, lpParameter);
			CloseHandle(dupObjHandle);
			CloseHandle(hProcess);
			continue;
		}

		HeapFree(GetProcessHeap(), HEAP_ZERO_MEMORY, lpParameter);

		// close handles
		CloseHandle(hThread);
		CloseHandle(dupObjHandle);
		CloseHandle(hProcess);
	}

	HeapFree(GetProcessHeap(), HEAP_ZERO_MEMORY, SystemInformation);

	return 1;
}

Add Defender Exclusion via WMI and COM

Sample code to add an exclusion path to Windows Defender using WMI and COM.

INT AddDefenderExclussion(WCHAR* exclpath)
{
	/*
	WCHAR path[] = L"C:\\Temp";
	INT res = AddDefenderExclussion(path);
	if (!res)
	{
		::wprintf(L"[-] AddDefenderExclussion has failed\n");
	}
	*/

	HRESULT hr;
	hr = CoInitializeEx(0, COINIT_MULTITHREADED);
	if (FAILED(hr))
	{
		::wprintf(L"[-] CoInitializeEx has failed\n");
		return 0;
	}

	hr = CoInitializeSecurity(
		NULL, 
		-1, 
		NULL, 
		NULL, 
		RPC_C_AUTHN_LEVEL_DEFAULT, 
		RPC_C_IMP_LEVEL_IMPERSONATE, 
		NULL, 
		EOAC_NONE, 
		NULL
	);
	if (FAILED(hr))
	{
		::wprintf(L"[-] CoInitializeSecurity has failed\n");
		CoUninitialize();
		return 0;
	}

	IWbemLocator* pLoc = 0;
	hr = CoCreateInstance(CLSID_WbemLocator, 0, CLSCTX_INPROC_SERVER, IID_IWbemLocator, (LPVOID*)&pLoc);
	if (FAILED(hr))
	{
		::wprintf(L"[-] CoCreateInstance has failed\n");
		CoUninitialize();
		return 0;
	}

	IWbemServices* pSvc = 0;
	hr = pLoc->ConnectServer(BSTR(L"ROOT\\Microsoft\\Windows\\Defender"), NULL, NULL, 0, NULL, 0, 0, &pSvc);
	if (FAILED(hr))
	{
		::wprintf(L"[-] ConnectServer has failed\n");
		pLoc->Release();
		CoUninitialize();
		return 0;
	}

	hr = CoSetProxyBlanket(
		pSvc, 
		RPC_C_AUTHN_WINNT, 
		RPC_C_AUTHZ_NONE, 
		NULL, 
		RPC_C_AUTHN_LEVEL_CALL, 
		RPC_C_IMP_LEVEL_IMPERSONATE, 
		NULL, 
		EOAC_NONE
	);
	if (FAILED(hr))
	{
		::wprintf(L"[-] CoSetProxyBlanket has failed\n");
		pSvc->Release();
		pLoc->Release();
		CoUninitialize();
		return 0;
	}

	IWbemClassObject* pClass = 0;
	BSTR Clname = BSTR(L"MSFT_MpPreference");
	hr = pSvc->GetObject(Clname, 0, NULL, &pClass, NULL);

	BSTR MethodName = BSTR(L"Add");
	IWbemClassObject* pInSignature = 0;
	hr = pClass->GetMethod(MethodName, 0, &pInSignature, NULL);
	if (FAILED(hr))
	{
		::wprintf(L"[-] GetMethod has failed\n");
		pInSignature->Release();
		pClass->Release();
		pSvc->Release();
		pLoc->Release();
		CoUninitialize();
		return 0;
	}

	IWbemClassObject* pClassInstance = NULL;
	hr = pInSignature->SpawnInstance(0, &pClassInstance);
	if (FAILED(hr))
	{
		::wprintf(L"[-] SpawnInstance has failed\n");
		pClassInstance->Release();
		pInSignature->Release();
		pClass->Release();
		pSvc->Release();
		pLoc->Release();
		CoUninitialize();
		return 0;
	}

	// Create an array
	SAFEARRAYBOUND rgsaBounds[1];
	rgsaBounds[0].cElements = 1;
	rgsaBounds[0].lLbound = 0;
	SAFEARRAY* psaStrings;
	psaStrings = SafeArrayCreate(VT_BSTR, 1, rgsaBounds);

	// Add a string to the array
	VARIANT vString;
	VariantInit(&vString);
	V_VT(&vString) = VT_BSTR;
	V_BSTR(&vString) = _bstr_t(exclpath);
	LONG lArrayIndex = 0;
	SafeArrayPutElement(psaStrings, &lArrayIndex, V_BSTR(&vString));
	VariantClear(&vString);

	// variant array
	VARIANT vStringList;
	VariantInit(&vStringList);
	V_VT(&vStringList) = VT_ARRAY | VT_BSTR;
	V_ARRAY(&vStringList) = psaStrings;
	
	// Store the value for the in parameters
	hr = pClassInstance->Put(L"ExclusionPath", 0, &vStringList, CIM_STRING|CIM_FLAG_ARRAY);
	if (FAILED(hr))
	{
		::wprintf(L"[-] Put has failed %x\n", hr);
		VariantClear(&vStringList);
		pClassInstance->Release();
		pInSignature->Release();
		pClass->Release();
		pSvc->Release();
		pLoc->Release();
		CoUninitialize();
		return 0;
	}

	IWbemClassObject* pOutParams = NULL;
	hr = pSvc->ExecMethod(Clname, MethodName, 0, NULL, pClassInstance, NULL, NULL);
	if (FAILED(hr))
	{
		::wprintf(L"[-] ExecMethod has failed %x\n", hr);
		VariantClear(&vStringList);
		pClassInstance->Release();
		pInSignature->Release();
		pClass->Release();
		pSvc->Release();
		pLoc->Release();
		CoUninitialize();
		return 0;
	}

	VariantClear(&vStringList);
	pClassInstance->Release();
	pInSignature->Release();
	pClass->Release();
	pLoc->Release();
	pSvc->Release();
	CoUninitialize();

	return 1;
}

Enumerate Windows Defender ExclusionPath

The following function prints the path or paths that are excluded from Windows Defender scans. Requires Administrator privileges.

Use case: In Simulated Attack engagements, this function can be converted into a Beacon Object File and be executed with inline-execute to enumerate exclusion paths that can be used to drop additional payloads.

INT EnumDefenderExclussions()
{
	// PowerShell equivalent:
	// Get-MpPreference | Select-Object -Property ExclusionPath
	//
	// References:
	//  - https://social.msdn.microsoft.com/Forums/vstudio/en-US/74e8dca7-e557-47df-94a2-016822494f78/wmi-defender-exploring-and-configuration-through-com-in-c?forum=windowsgeneraldevelopmentissues
	//  - https://docs.microsoft.com/en-us/windows/win32/wmisdk/example--getting-wmi-data-from-the-local-computer
	
	HRESULT hr;
	hr = CoInitializeEx(0, COINIT_MULTITHREADED);
	if (FAILED(hr))
	{
		::wprintf(L"[-] CoInitializeEx has failed\n");
		return 0;
	}

	hr = CoInitializeSecurity(NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, NULL);
	if (FAILED(hr))
	{
		::wprintf(L"[-] CoInitializeSecurity has failed\n");
		CoUninitialize();
		return 0;
	}

	IWbemLocator *pLoc = 0;
	hr = CoCreateInstance(CLSID_WbemLocator, 0, CLSCTX_INPROC_SERVER, IID_IWbemLocator, (LPVOID*)&pLoc);
	if (FAILED(hr))
	{
		::wprintf(L"[-] CoCreateInstance has failed\n");
		CoUninitialize();
		return 0;
	}

	IWbemServices* pSvc = 0;
	hr = pLoc->ConnectServer(BSTR(L"ROOT\\Microsoft\\Windows\\Defender"), NULL, NULL, 0, NULL, 0, 0, &pSvc);
	if (FAILED(hr))
	{
		::wprintf(L"[-] ConnectServer has failed\n");
		pLoc->Release();
		CoUninitialize();
		return 0;
	}

	hr = CoSetProxyBlanket(pSvc, RPC_C_AUTHN_WINNT,	RPC_C_AUTHZ_NONE, NULL,	RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE);
	if (FAILED(hr))
	{
		::wprintf(L"[-] CoSetProxyBlanket has failed\n");
		pSvc->Release();
		pLoc->Release();
		CoUninitialize();
		return 0;
	}
	
	BSTR Language = BSTR(L"WQL");
	BSTR Query = BSTR(L"SELECT * FROM MSFT_MpPreference");
	IEnumWbemClassObject* pEnum = 0;
	hr = pSvc->ExecQuery(Language, Query, WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, 0, &pEnum);
	if (FAILED(hr))
	{
		::wprintf(L"[-] ExecQuery has failed\n");
		pSvc->Release();
		pLoc->Release();
		CoUninitialize();
		return 0;
	}
	
	IWbemClassObject* pObj = 0;
	ULONG uRet = 0;

	while (pEnum)
	{
		hr = pEnum->Next(WBEM_INFINITE, 1, &pObj, &uRet);
				
		if (uRet == 0) { break; }
		
		VARIANT v;
		CIMTYPE ctype = 0;
		hr = pObj->Get(L"ExclusionPath", 0, &v, &ctype, NULL);

		SAFEARRAY* sa = V_ARRAY(&v);
		LONG lstart, lend;
		hr = SafeArrayGetLBound(sa, 1, &lstart);
		if (FAILED(hr)) { ::wprintf(L"[-] SafeArrayGetLBound has failed\n"); }

		hr = SafeArrayGetUBound(sa, 1, &lend);
		if (FAILED(hr)) { ::wprintf(L"[-] SafeArrayGetUBound has failed\n"); }

		BSTR* pexpath;
		hr = SafeArrayAccessData(sa, (void HUGEP**)&pexpath);
		if (SUCCEEDED(hr))
		{
			::wprintf(L"[+] Exclusion path:\n");
			for (LONG idx = lstart; idx <= lend; idx++)
			{
				::wprintf(L"\t%s\n", pexpath[idx]);
			}
			SafeArrayUnaccessData(sa);
		}

		VariantClear(&v);
		pObj->Release();
	}

	pLoc->Release();
	pSvc->Release();
	pEnum->Release();
	CoUninitialize();

	return 1;
}

Enumerate Windows Defender ExclusionPath (Registry)

Enumerate Windows Defender ExclusionPath by querying the Registry. It doesn’t require Administrator privileges.

#define MAX_KEY_LENGTH 255
#define MAX_VALUE_NAME 16383

INT RegEnumDefenderExclusions()
{
	HKEY hKey;
	LSTATUS res = ::RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows Defender\\Exclusions\\Paths", 0, KEY_READ, &hKey);
	if (res)
	{
		::wprintf(L"[-] RegOpenKeyExW has failed\n");
		return 0;
	}

	WCHAR Class[MAX_VALUE_NAME];
	DWORD cValues = 0;
	res = ::RegQueryInfoKeyW(hKey, NULL, NULL, NULL, NULL, NULL, NULL, &cValues, NULL, NULL, NULL, NULL);
	if (res)
	{
		::wprintf(L"[-] RegQueryInfoKey has failed\n");
		return 0;
	}

	::wprintf(L"[+] Number of exclusion paths: %d\n", cValues);
	WCHAR achValue[MAX_VALUE_NAME];
	DWORD cchValue = MAX_VALUE_NAME;
	for (DWORD i=0; i<cValues; i++)
	{
		achValue[0] = '\0';
		res = RegEnumValueW(hKey, i, achValue, &cchValue, NULL, NULL, NULL, NULL);
		if (res)
		{
			::wprintf(L"[-] RegEnumValueW has failed\n");
			return 0;
		}
		::wprintf(L"\t%s\n", achValue);
	}

	::RegCloseKey(hKey);

	return 1;
}

Identify Antivirus Status

A function that identifies the status of Antivirus (Enabled/Disabled). Requires Administrator privileges. Equivalent to PowerShell’s:

Get-MpComputerStatus | Select-Object AntivirusEnabled.

INT IsAVEnabled()
{
	// PowerShell equivalent
	// Get-MpComputerStatus | Select-Object AntivirusEnabled
	HRESULT hr;
	hr = CoInitializeEx(0, COINIT_MULTITHREADED);
	if (FAILED(hr))
	{
		::wprintf(L"[-] CoInitializeEx has failed\n");
		return 0;
	}

	hr = CoInitializeSecurity(NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, NULL);
	if (FAILED(hr))
	{
		::wprintf(L"[-] CoInitializeSecurity has failed\n");
		CoUninitialize();
		return 0;
	}

	IWbemLocator* pLoc = 0;
	hr = CoCreateInstance(CLSID_WbemLocator, 0, CLSCTX_INPROC_SERVER, IID_IWbemLocator, (LPVOID*)&pLoc);
	if (FAILED(hr))
	{
		::wprintf(L"[-] CoCreateInstance has failed\n");
		CoUninitialize();
		return 0;
	}

	IWbemServices* pSvc = 0;
	hr = pLoc->ConnectServer(BSTR(L"ROOT\\Microsoft\\Windows\\Defender"), NULL, NULL, 0, NULL, 0, 0, &pSvc);
	if (FAILED(hr))
	{
		::wprintf(L"[-] ConnectServer has failed\n");
		pLoc->Release();
		CoUninitialize();
		return 0;
	}

	hr = CoSetProxyBlanket(pSvc, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL, RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE);
	if (FAILED(hr))
	{
		::wprintf(L"[-] CoSetProxyBlanket has failed\n");
		pSvc->Release();
		pLoc->Release();
		CoUninitialize();
		return 0;
	}

	BSTR Language = BSTR(L"WQL");
	BSTR Query = BSTR(L"SELECT * FROM MSFT_MpComputerStatus");
	IEnumWbemClassObject* pEnum = 0;
	hr = pSvc->ExecQuery(Language, Query, WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, 0, &pEnum);
	if (FAILED(hr))
	{
		::wprintf(L"[-] ExecQuery has failed\n");
		pSvc->Release();
		pLoc->Release();
		CoUninitialize();
		return 0;
	}

	IWbemClassObject* pObj = 0;
	ULONG uRet = 0;

	while (pEnum)
	{
		hr = pEnum->Next(WBEM_INFINITE, 1, &pObj, &uRet);

		if (uRet == 0) { break; }

		VARIANT v;
		CIMTYPE ctype = 0;
		hr = pObj->Get(L"AntivirusEnabled", 0, &v, &ctype, NULL);
		if (FAILED(hr))
		{
			::wprintf(L"[-] IWbemClassObject::Get has failed\n");
			pObj->Release();
			pLoc->Release();
			pSvc->Release();
			pEnum->Release();
			CoUninitialize();
			return 0;
		}

		BOOL fValue;
		VariantToBoolean(v, &fValue);
		if (fValue) ::wprintf(L"[*] Antivirus is Enabled\n");
		else ::wprintf(L"[*] Antivirus is Disabled\n");
		
		pObj->Release();
	}

	pLoc->Release();
	pSvc->Release();
	pEnum->Release();
	CoUninitialize();

	return 1;
}

List handles using NtQuerySystemInformation

// https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ex/sysinfo/handle_table_entry.htm
typedef struct _SYSTEM_HANDLE_TABLE_ENTRY_INFO
{
	USHORT UniqueProcessId;
	USHORT CreatorBackTraceIndex;
	UCHAR ObjectTypeIndex;
	UCHAR HandleAttributes;
	USHORT HandleValue;
	PVOID Object;
	ULONG GrantedAccess;
} SYSTEM_HANDLE_TABLE_ENTRY_INFO, * PSYSTEM_HANDLE_TABLE_ENTRY_INFO;

// https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ex/sysinfo/handle.htm
typedef struct _SYSTEM_HANDLE_INFORMATION {
	ULONG NumberOfHandles;
	SYSTEM_HANDLE_TABLE_ENTRY_INFO Handles[1];
} SYSTEM_HANDLE_INFORMATION, * PSYSTEM_HANDLE_INFORMATION;

#define SystemHandleInformation 0x10

typedef NTSTATUS(*fNtQuerySystemInformation)(INT, PVOID, ULONG, PULONG);

INT GetHandlesWrapper()
{
	fNtQuerySystemInformation NtQuerySystemInformation = (fNtQuerySystemInformation)GetProcAddress(GetModuleHandleW(L"ntdll"), "NtQuerySystemInformation");
	PSYSTEM_HANDLE_INFORMATION SystemInformation = NULL;
	SIZE_T szBuffer = 0xffffff;
	SystemInformation = (PSYSTEM_HANDLE_INFORMATION)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, szBuffer);

	ULONG ReturnLength = 0;
	NTSTATUS status = NtQuerySystemInformation(SystemHandleInformation, SystemInformation, (ULONG)szBuffer, &ReturnLength);
	if (status != 0)
	{
		::wprintf(L"[-] NtQuerySystemInformation status code: 0x%x\n", status);
		if (status != 0xc0000004)
		{
			::wprintf(L"[-] Exiting...\n");
			return 0;
		}
		SystemInformation = (PSYSTEM_HANDLE_INFORMATION)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, ReturnLength);
		status = NtQuerySystemInformation(SystemHandleInformation, SystemInformation, ReturnLength, &ReturnLength);
		::wprintf(L"[-] NtQuerySystemInformation second run status code: 0x%x\n", status);
	}
	
	::wprintf(L"[*] NumberOfHandles: %lu\n", SystemInformation->NumberOfHandles);

	for (ULONG i = 0; i < SystemInformation->NumberOfHandles; i++)
	{
		::wprintf(L"\tPID: %hu, Handle: 0x%x, Object Address: 0x%p, \n", SystemInformation->Handles[i].UniqueProcessId, SystemInformation->Handles[i].HandleValue, SystemInformation->Handles[i].Object);
	}

	HeapFree(GetProcessHeap(), HEAP_ZERO_MEMORY, SystemInformation);

	return 1;
}

Create Scheduled Task Triggered on Boot

Function to create scheduled task that triggers on boot.

DWORD CreateScheduledTaskBootTrigger(WCHAR* TaskAuthor, WCHAR* TaskName, WCHAR* UserName, WCHAR* wstrExePath)
{
	HRESULT hr = S_OK;

	// initialize the COM library
	hr = ::CoInitializeEx(NULL, COINIT_MULTITHREADED);
	if ((hr != S_OK) && hr != S_FALSE)
	{
		::wprintf(L"[-] CoInitializeEx has failed\n");
		return 0;
	}
	else if (hr == S_FALSE)
	{
		// informational
		::wprintf(L"[*] COM library has been already initialized\n");
	}

	// set security for the process
	PSECURITY_DESCRIPTOR pSecDesc = { 0 };
	hr = ::CoInitializeSecurity(pSecDesc, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_PKT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, 0, NULL);
	if (hr != S_OK)
	{
		::wprintf(L"[-] CoInitializeSecurity has failed\n");
		::CoUninitialize();
		return 0;
	}

	// create and initiliaze an object of the class associated with the specified CLSID
	ITaskService* pService = NULL;
	hr = ::CoCreateInstance(CLSID_TaskScheduler, NULL, CLSCTX_INPROC_SERVER, IID_ITaskService, (void**)& pService);
	if (hr != S_OK)
	{
		::wprintf(L"[-] CoCreateInstance has failed\n");
		::CoUninitialize();
		return 0;
	}

	// connect to the task service
	hr = pService->Connect(VARIANT(), VARIANT(), VARIANT(), VARIANT());
	if (FAILED(hr))
	{
		::wprintf(L"[-] ITaskService::Connect has failed\n");
		pService->Release();
		::CoUninitialize();
		return 0;
	}

	// get root task folder
	ITaskFolder* pRootFolder = NULL;
	hr = pService->GetFolder(_bstr_t(L"\\"), &pRootFolder);
	if (FAILED(hr))
	{
		::wprintf(L"[-] ITaskService::GetFolder has failed\n");
		pService->Release();
		::CoUninitialize();
		return 0;
	}

	// task builder object
	ITaskDefinition* pTask = NULL;
	hr = pService->NewTask(0, &pTask);
	pService->Release();
	if (FAILED(hr))
	{
		::wprintf(L"[-] ITaskService::NewTask has failed\n");
		pService->Release();
		::CoUninitialize();
		return 0;
	}

	// set identification
	IRegistrationInfo* pRegInfo = NULL;
	hr = pTask->get_RegistrationInfo(&pRegInfo);
	if (FAILED(hr))
	{
		::wprintf(L"[-] get_RegistrationInfo has failed\n");
		pRootFolder->Release();
		pTask->Release();
		::CoUninitialize();
		return 0;
	}

	// get AuthorName from user
	hr = pRegInfo->put_Author(_bstr_t(TaskAuthor));
	pRegInfo->Release();
	if (FAILED(hr))
	{
		::wprintf(L"[-] put_Author has failed\n");
		pRootFolder->Release();
		pTask->Release();
		::CoUninitialize();
		return 0;
	}

	// create settings for the task
	ITaskSettings* pSettings = NULL;
	hr = pTask->get_Settings(&pSettings);
	if (FAILED(hr))
	{
		::wprintf(L"[-] Cannot get settings pointer: %x", hr);
		pRootFolder->Release();
		pTask->Release();
		CoUninitialize();
		return 0;
	}

	hr = pSettings->put_StartWhenAvailable(VARIANT_TRUE);
	pSettings->Release();
	if (FAILED(hr))
	{
		::wprintf(L"[-] Cannot put setting info: %x", hr);
		pRootFolder->Release();
		pTask->Release();
		CoUninitialize();
		return 0;
	}

	// add time trigger
	ITriggerCollection* pTriggerCollection = NULL;
	hr = pTask->get_Triggers(&pTriggerCollection);
	if (FAILED(hr))
	{
		::wprintf(L"[-] get_Triggers has failed\n");
		pRootFolder->Release();
		pTask->Release();
		::CoUninitialize();
		return 0;
	}

	ITrigger* pTrigger = NULL;
	hr = pTriggerCollection->Create(TASK_TRIGGER_BOOT, &pTrigger);
	pTriggerCollection->Release();
	if (FAILED(hr))
	{
		::wprintf(L"[-] Trigger Create has failed\n");
		pRootFolder->Release();
		pTask->Release();
		::CoUninitialize();
		return 0;
	}

	IBootTrigger* pBootTrigger = NULL;
	hr = pTrigger->QueryInterface(IID_IBootTrigger, (void**)& pBootTrigger);
	pTrigger->Release();
	if (FAILED(hr))
	{
		::wprintf(L"[-] QueryInterface has failed\n");
		pRootFolder->Release();
		pTask->Release();
		::CoUninitialize();
		return 0;
	}

	hr = pBootTrigger->put_Id(_bstr_t(L"TestTrigger"));
	if (FAILED(hr))
	{
		::wprintf(L"[-] Trigger put_Id has failed\n");
	}

	hr = pBootTrigger->put_StartBoundary(_bstr_t(L"1992-01-05T15:00:00"));
	if (FAILED(hr))
	{
		::wprintf(L"[-] Trigger put_StartBoundary has failed\n");
	}

	// set task action
	IActionCollection* pActionCollection = NULL;
	hr = pTask->get_Actions(&pActionCollection);
	if (FAILED(hr))
	{
		::wprintf(L"[-] get_Actions has failed\n");
		pRootFolder->Release();
		pTask->Release();
		::CoUninitialize();
		return 0;
	}

	IAction* pAction = NULL;
	hr = pActionCollection->Create(TASK_ACTION_EXEC, &pAction);
	pActionCollection->Release();
	if (FAILED(hr))
	{
		::wprintf(L"[-] Create action has failed\n");
		pRootFolder->Release();
		pTask->Release();
		::CoUninitialize();
		return 0;
	}

	IExecAction* pExecAction = NULL;
	hr = pAction->QueryInterface(IID_IExecAction, (void**)& pExecAction);
	pAction->Release();
	if (FAILED(hr))
	{
		::wprintf(L"[-] QueryInterface action has failed\n");
		pRootFolder->Release();
		pTask->Release();
		::CoUninitialize();
		return 0;
	}

	hr = pExecAction->put_Path(_bstr_t(wstrExePath));
	pExecAction->Release();
	if (FAILED(hr))
	{
		::wprintf(L"[-] QueryInterface action has failed\n");
		pRootFolder->Release();
		pTask->Release();
		::CoUninitialize();
		return 0;
	}

	// Register the task
	IRegisteredTask* pRegisteredTask = NULL;
	hr = pRootFolder->RegisterTaskDefinition(
		_bstr_t(TaskName),
		pTask,
		TASK_CREATE_OR_UPDATE,
		_variant_t(L"Local Service"),
		_variant_t(),
		TASK_LOGON_SERVICE_ACCOUNT,
		_variant_t(L""),
		&pRegisteredTask
	);
	if (FAILED(hr))
	{
		::wprintf(L"[-] RegisterTaskDefinition has failed\n");
		pRootFolder->Release();
		pTask->Release();
		::CoUninitialize();
		return 0;
	}

	::wprintf(L"[+] Scheduled task has been created\n");
	// close the COM library
	pRootFolder->Release();
	pTask->Release();
	pRegisteredTask->Release();
	::CoUninitialize();

	return 1;
}

Create Scheduled Task Triggered on User Logon

Function to set up a scheduled task that triggers on user logon. Requires Administrator level privileges.

#include <comdef.h>
#include <taskschd.h>

#pragma comment(lib, "taskschd.lib")

DWORD CreateScheduledTaskLogonTrigger(WCHAR* TaskAuthor, WCHAR* TaskName, WCHAR* UserName, WCHAR* wstrExePath)
{
	/*
		WCHAR TaskAuthor[] = L"Microsoft Corporation";
		WCHAR TaskName[] = L"OneDrive Standalone Update Task-S-1-5-21-4162225321-4122752593-2322023677-001";
		WCHAR path[] = L"C:\\Windows\\System32\\notepad.exe";
		WCHAR UserName[] = L"john";
		DWORD res_sch = CreateScheduledTaskLogonTrigger(TaskAuthor, TaskName, UserName, path);
	*/

	// create scheduled task that triggers on system boot
	HRESULT hr = S_OK;

	// initialize the COM library
	hr = ::CoInitializeEx(NULL, COINIT_MULTITHREADED);
	if ((hr != S_OK) && hr != S_FALSE)
	{
		::wprintf(L"[-] CoInitializeEx has failed\n");
		return 0;
	}
	else if (hr == S_FALSE)
	{
		// informational
		::wprintf(L"[*] COM library has been already initialized\n");
	}

	// set security for the process
	PSECURITY_DESCRIPTOR pSecDesc = { 0 };
	hr = ::CoInitializeSecurity(pSecDesc, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_PKT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, 0, NULL);
	if (hr != S_OK)
	{
		::wprintf(L"[-] CoInitializeSecurity has failed\n");
		::CoUninitialize();
		return 0;
	}

	// create and initiliaze an object of the class associated with the specified CLSID
	ITaskService* pService = NULL;
	hr = ::CoCreateInstance(CLSID_TaskScheduler, NULL, CLSCTX_INPROC_SERVER, IID_ITaskService, (void**)& pService);
	if (hr != S_OK)
	{
		::wprintf(L"[-] CoCreateInstance has failed\n");
		::CoUninitialize();
		return 0;
	}

	// connect to the task service
	hr = pService->Connect(VARIANT(), VARIANT(), VARIANT(), VARIANT());
	if (FAILED(hr))
	{
		::wprintf(L"[-] ITaskService::Connect has failed\n");
		pService->Release();
		::CoUninitialize();
		return 0;
	}

	// get root task folder
	ITaskFolder* pRootFolder = NULL;
	hr = pService->GetFolder(_bstr_t(L"\\"), &pRootFolder);
	if (FAILED(hr))
	{
		::wprintf(L"[-] ITaskService::GetFolder has failed\n");
		pService->Release();
		::CoUninitialize();
		return 0;
	}

	// task builder object
	ITaskDefinition* pTask = NULL;
	hr = pService->NewTask(0, &pTask);
	pService->Release();
	if (FAILED(hr))
	{
		::wprintf(L"[-] ITaskService::NewTask has failed\n");
		pService->Release();
		::CoUninitialize();
		return 0;
	}

	// set identification
	IRegistrationInfo* pRegInfo = NULL;
	hr = pTask->get_RegistrationInfo(&pRegInfo);
	if (FAILED(hr))
	{
		::wprintf(L"[-] get_RegistrationInfo has failed\n");
		pRootFolder->Release();
		pTask->Release();
		::CoUninitialize();
		return 0;
	}

	// get AuthorName from user
	hr = pRegInfo->put_Author(_bstr_t(TaskAuthor));
	pRegInfo->Release();
	if (FAILED(hr))
	{
		::wprintf(L"[-] put_Author has failed\n");
		pRootFolder->Release();
		pTask->Release();
		::CoUninitialize();
		return 0;
	}

	// create settings for the task
	ITaskSettings* pSettings = NULL;
	hr = pTask->get_Settings(&pSettings);
	if (FAILED(hr))
	{
		::wprintf(L"[-] Cannot get settings pointer: %x", hr);
		pRootFolder->Release();
		pTask->Release();
		CoUninitialize();
		return 0;
	}

	hr = pSettings->put_StartWhenAvailable(VARIANT_TRUE);
	pSettings->Release();
	if (FAILED(hr))
	{
		::wprintf(L"[-] Cannot put setting info: %x", hr);
		pRootFolder->Release();
		pTask->Release();
		CoUninitialize();
		return 0;
	}

	// add time trigger
	ITriggerCollection* pTriggerCollection = NULL;
	hr = pTask->get_Triggers(&pTriggerCollection);
	if (FAILED(hr))
	{
		::wprintf(L"[-] get_Triggers has failed\n");
		pRootFolder->Release();
		pTask->Release();
		::CoUninitialize();
		return 0;
	}

	ITrigger* pTrigger = NULL;
	hr = pTriggerCollection->Create(TASK_TRIGGER_LOGON, &pTrigger);
	pTriggerCollection->Release();
	if (FAILED(hr))
	{
		::wprintf(L"[-] Trigger Create has failed\n");
		pRootFolder->Release();
		pTask->Release();
		::CoUninitialize();
		return 0;
	}

	ILogonTrigger* pLogonTrigger = NULL;
	hr = pTrigger->QueryInterface(IID_ILogonTrigger, (void**)&pLogonTrigger);
	pTrigger->Release();
	if (FAILED(hr))
	{
		::wprintf(L"[-] QueryInterface has failed\n");
		pRootFolder->Release();
		pTask->Release();
		::CoUninitialize();
		return 0;
	}

	hr = pLogonTrigger->put_Id(_bstr_t(L"TestTrigger"));
	if (FAILED(hr))
	{
		::wprintf(L"[-] Trigger put_Id has failed\n");
	}

	hr = pLogonTrigger->put_StartBoundary(_bstr_t(L"1992-01-05T15:00:00"));
	if (FAILED(hr))
	{
		::wprintf(L"[-] Trigger put_StartBoundary has failed\n");
	}

	hr = pLogonTrigger->put_UserId(_bstr_t(UserName));
	if (FAILED(hr))
	{
		::wprintf(L"[-] Cannot add user ID to logon trigger: %x", hr);
		pRootFolder->Release();
		pTask->Release();
		CoUninitialize();
		return 0;
	}

	// set task action
	IActionCollection* pActionCollection = NULL;
	hr = pTask->get_Actions(&pActionCollection);
	if (FAILED(hr))
	{
		::wprintf(L"[-] get_Actions has failed\n");
		pRootFolder->Release();
		pTask->Release();
		::CoUninitialize();
		return 0;
	}

	IAction* pAction = NULL;
	hr = pActionCollection->Create(TASK_ACTION_EXEC, &pAction);
	pActionCollection->Release();
	if (FAILED(hr))
	{
		::wprintf(L"[-] Create action has failed\n");
		pRootFolder->Release();
		pTask->Release();
		::CoUninitialize();
		return 0;
	}

	IExecAction* pExecAction = NULL;
	hr = pAction->QueryInterface(IID_IExecAction, (void**)& pExecAction);
	pAction->Release();
	if (FAILED(hr))
	{
		::wprintf(L"[-] QueryInterface action has failed\n");
		pRootFolder->Release();
		pTask->Release();
		::CoUninitialize();
		return 0;
	}

	hr = pExecAction->put_Path(_bstr_t(wstrExePath));
	pExecAction->Release();
	if (FAILED(hr))
	{
		::wprintf(L"[-] QueryInterface action has failed\n");
		pRootFolder->Release();
		pTask->Release();
		::CoUninitialize();
		return 0;
	}

	// Register the task
	IRegisteredTask* pRegisteredTask = NULL;
	hr = pRootFolder->RegisterTaskDefinition(
		_bstr_t(TaskName),
		pTask,
		TASK_CREATE_OR_UPDATE,
		_variant_t(L"S-1-5-32-544"),
		_variant_t(),
		TASK_LOGON_GROUP,
		_variant_t(L""),
		&pRegisteredTask
	);
	if (FAILED(hr))
	{
		::wprintf(L"[-] RegisterTaskDefinition has failed\n");
		pRootFolder->Release();
		pTask->Release();
		::CoUninitialize();
		return 0;
	}

	::wprintf(L"[+] Scheduled task has been created\n");
	// close the COM library
	pRootFolder->Release();
	pTask->Release();
	pRegisteredTask->Release();
	::CoUninitialize();

	return 1;
}

Create Scheduled Task (Execute Once Every Day)

A function to create a scheduled task. It accepts the task author (e.g “Microsoft Corporation”), the task name (e.g “OneDrive Standalone Update Task-S-1-5-21-4162225321-4122752593-2322023677-001”) and the executable path. By default it is configured to execute once every day. However, can be easily altered.

DWORD CreateScheduledTask(WCHAR* TaskAuthor, WCHAR* TaskName, WCHAR* wstrExePath)
{
	/*
		WCHAR TaskAuthor[] = L"Microsoft Corporation";
		WCHAR TaskName[] = L"OneDrive Standalone Update Task-S-1-5-21-4162225321-4122752593-2322023677-001";
		WCHAR path[] = L"C:\\Windows\\System32\\notepad.exe";
		DWORD res_sch = CreateScheduledTask(TaskAuthor, TaskName, path);
	*/

	HRESULT hr = S_OK;

	// initialize the COM library
	hr = ::CoInitializeEx(NULL, COINIT_MULTITHREADED);
	if ((hr != S_OK) && hr != S_FALSE)
	{
		::wprintf(L"[-] CoInitializeEx has failed\n");
		return 0;
	}
	else if (hr == S_FALSE)
	{
		// informational
		::wprintf(L"[*] COM library has been already initialized\n");
	}

	// set security for the process
	PSECURITY_DESCRIPTOR pSecDesc = { 0 };
	hr = ::CoInitializeSecurity(pSecDesc, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_PKT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, 0, NULL);
	if (hr != S_OK)
	{
		::wprintf(L"[-] CoInitializeSecurity has failed\n");
		::CoUninitialize();
		return 0;
	}

	// create and initiliaze an object of the class associated with the specified CLSID
	ITaskService* pService = NULL;
	hr = ::CoCreateInstance(CLSID_TaskScheduler, NULL, CLSCTX_INPROC_SERVER, IID_ITaskService, (void **)&pService);
	if (hr != S_OK)
	{
		::wprintf(L"[-] CoCreateInstance has failed\n");
		::CoUninitialize();
		return 0;
	}

	// connect to the task service
	hr = pService->Connect(VARIANT(), VARIANT(), VARIANT(), VARIANT());
	if (FAILED(hr))
	{
		::wprintf(L"[-] ITaskService::Connect has failed\n");
		pService->Release();
		::CoUninitialize();
		return 0;
	}

	// get root task folder
	ITaskFolder* pRootFolder = NULL;
	hr = pService->GetFolder(_bstr_t(L"\\"), &pRootFolder);
	if (FAILED(hr))
	{
		::wprintf(L"[-] ITaskService::GetFolder has failed\n");
		pService->Release();
		::CoUninitialize();
		return 0;
	}

	// task builder object
	ITaskDefinition* pTask = NULL;
	hr = pService->NewTask(0, &pTask);
	pService->Release();
	if (FAILED(hr))
	{
		::wprintf(L"[-] ITaskService::NewTask has failed\n");
		pService->Release();
		::CoUninitialize();
		return 0;
	}

	// set identification
	IRegistrationInfo* pRegInfo = NULL;
	hr = pTask->get_RegistrationInfo(&pRegInfo);
	if (FAILED(hr))
	{
		::wprintf(L"[-] get_RegistrationInfo has failed\n");
		pRootFolder->Release();
		pTask->Release();
		::CoUninitialize();
		return 0;
	}

	// get AuthorName from user
	hr = pRegInfo->put_Author(_bstr_t(TaskAuthor));
	pRegInfo->Release();
	if (FAILED(hr))
	{
		::wprintf(L"[-] get_RegistrationInfo has failed\n");
		pRootFolder->Release();
		pTask->Release();
		::CoUninitialize();
		return 0;
	}

	// principal for the task
	IPrincipal* pPrincipal = NULL;
	hr = pTask->get_Principal(&pPrincipal);
	if (FAILED(hr))
	{
		::wprintf(L"[-] get_Principal has failed\n");
		pRootFolder->Release();
		pTask->Release();
		::CoUninitialize();
		return 0;
	}

	// principal logon type INTERACTIVE LOGON
	hr = pPrincipal->put_LogonType(TASK_LOGON_INTERACTIVE_TOKEN);
	pPrincipal->Release();
	if (FAILED(hr))
	{
		::wprintf(L"[-] put_LogonType has failed\n");
		pRootFolder->Release();
		pTask->Release();
		::CoUninitialize();
		return 0;
	}

	// add time trigger
	ITriggerCollection* pTriggerCollection = NULL;
	hr = pTask->get_Triggers(&pTriggerCollection);
	if (FAILED(hr))
	{
		::wprintf(L"[-] get_Triggers has failed\n");
		pRootFolder->Release();
		pTask->Release();
		::CoUninitialize();
		return 0;
	}

	ITrigger* pTrigger = NULL;
	hr = pTriggerCollection->Create(TASK_TRIGGER_DAILY, &pTrigger);
	pTriggerCollection->Release();
	if (FAILED(hr))
	{
		::wprintf(L"[-] Trigger Create has failed\n");
		pRootFolder->Release();
		pTask->Release();
		::CoUninitialize();
		return 0;
	}

	IDailyTrigger* pDailyTrigger = NULL;
	hr = pTrigger->QueryInterface(IID_IDailyTrigger, (void**)& pDailyTrigger);
	pTrigger->Release();
	if (FAILED(hr))
	{
		::wprintf(L"[-] QueryInterface has failed\n");
		pRootFolder->Release();
		pTask->Release();
		::CoUninitialize();
		return 0;
	}

	hr = pDailyTrigger->put_Id(_bstr_t(L"TestTrigger"));
	if (FAILED(hr))
	{
		::wprintf(L"[-] Trigger put_Id has failed\n");
	}

	/*
	hr = pDailyTrigger->put_EndBoundary(_bstr_t(L"2022-05-02T08:00:00"));
	if (FAILED(hr))
	{
		::wprintf(L"[-] Trigger put_EndBoundary has failed\n");
	}
	*/

	hr = pDailyTrigger->put_StartBoundary(_bstr_t(L"1992-01-05T15:00:00"));
	if (FAILED(hr))
	{
		::wprintf(L"[-] Trigger put_StartBoundary has failed\n");
		return 0;
	}

	hr = pDailyTrigger->put_DaysInterval((short)1);
	if (FAILED(hr))
	{
		::wprintf(L"[-] QueryInterface has failed\n");
		pRootFolder->Release();
		pDailyTrigger->Release();
		pTask->Release();
		::CoUninitialize();
		return 0;
	}

	// repetition to the trigger
	IRepetitionPattern* pRepetitionPattern = NULL;
	hr = pDailyTrigger->get_Repetition(&pRepetitionPattern);
	pDailyTrigger->Release();
	if (FAILED(hr))
	{
		::wprintf(L"[-] QueryInterface has failed\n");
		pRootFolder->Release();
		pTask->Release();
		::CoUninitialize();
		return 0;
	}

	/*
	hr = pRepetitionPattern->put_Duration(_bstr_t(L"PD4M"));
	if (FAILED(hr))
	{
		::wprintf(L"[-] QueryInterface has failed\n");
		pRootFolder->Release();
		pRepetitionPattern->Release();
		pTask->Release();
		::CoUninitialize();
		return 0;
	}
	*/

	// repeat task every day
	// P<days>D<hours>H<minutes>M<seconds>S
	hr = pRepetitionPattern->put_Interval(_bstr_t(L"P1D"));
	pRepetitionPattern->Release();
	if (FAILED(hr))
	{
		::wprintf(L"[-] QueryInterface has failed\n");
		pRootFolder->Release();
		pTask->Release();
		::CoUninitialize();
		return 0;
	}

	// set task action
	IActionCollection* pActionCollection = NULL;
	hr = pTask->get_Actions(&pActionCollection);
	if (FAILED(hr))
	{
		::wprintf(L"[-] get_Actions has failed\n");
		pRootFolder->Release();
		pTask->Release();
		::CoUninitialize();
		return 0;
	}

	IAction* pAction = NULL;
	hr = pActionCollection->Create(TASK_ACTION_EXEC, &pAction);
	pActionCollection->Release();
	if (FAILED(hr))
	{
		::wprintf(L"[-] Create action has failed\n");
		pRootFolder->Release();
		pTask->Release();
		::CoUninitialize();
		return 0;
	}

	IExecAction* pExecAction = NULL;
	hr = pAction->QueryInterface(IID_IExecAction, (void**)&pExecAction);
	pAction->Release();
	if (FAILED(hr))
	{
		::wprintf(L"[-] QueryInterface action has failed\n");
		pRootFolder->Release();
		pTask->Release();
		::CoUninitialize();
		return 0;
	}

	hr = pExecAction->put_Path(_bstr_t(wstrExePath));
	pExecAction->Release();
	if (FAILED(hr))
	{
		::wprintf(L"[-] QueryInterface action has failed\n");
		pRootFolder->Release();
		pTask->Release();
		::CoUninitialize();
		return 0;
	}

	// Register the task
	IRegisteredTask* pRegisteredTask = NULL;
	hr = pRootFolder->RegisterTaskDefinition(
							_bstr_t(TaskName), 
							pTask, 
							TASK_CREATE_OR_UPDATE, 
							_variant_t(), 
							_variant_t(), 
							TASK_LOGON_INTERACTIVE_TOKEN, 
							_variant_t(L""), 
							&pRegisteredTask
						);
	if (FAILED(hr))
	{
		::wprintf(L"[-] RegisterTaskDefinition has failed\n");
		pRootFolder->Release();
		pTask->Release();
		::CoUninitialize();
		return 0;
	}

	::wprintf(L"[+] Scheduled task has been created\n");
	// close the COM library
	pRootFolder->Release();
	pTask->Release();
	pRegisteredTask->Release();
	::CoUninitialize();

	return 1;
}

From Handle to Object Type

The following function accepts a handle of an object and returns the type of that object. It is definitely something that does not make sense, as when you have a handle you are already aware of the object type anyway.

DWORD GetSEObjectType(HANDLE handle, WCHAR** objecttype)
{
	/*
		WCHAR* objtype = NULL;
		DWORD status = GetSEObjectType(hFile, &objtype);
		if (status == 0)
		{
			::wprintf(L"[-] GetSEObjectType has failed\n");
		}
		::wprintf(L"[+] object type: %s\n", objtype);
	*/

	DWORD status_GetSecurityInfo = NULL;
	SE_OBJECT_TYPE obj = (SE_OBJECT_TYPE)0xff;
	SECURITY_INFORMATION SecurityInfo = NULL;
	PSECURITY_DESCRIPTOR ppSecurityDescriptor = NULL;
	INT i;
	for (i = 0; i < 15; i++)
	{
		status_GetSecurityInfo = GetSecurityInfo(handle, (SE_OBJECT_TYPE)i, SecurityInfo, NULL, NULL, NULL, NULL, &ppSecurityDescriptor);
		if (status_GetSecurityInfo == ERROR_SUCCESS)
		{
			obj = (SE_OBJECT_TYPE)i;
			break;
		}
	}

	if (i == 15)
	{
		return 0;
	}

	*objecttype = new WCHAR[16];
	switch (obj)
	{
		case 0x0:
			wcscpy_s(*objecttype, 16, L"UNKNOWN OBJECT");
			break;
		case 0x1:
			wcscpy_s(*objecttype, 16, L"FILE");
			break;
		case (SE_OBJECT_TYPE)0x2:
			wcscpy_s(*objecttype, 16, L"SERVICE");
			break;
		case 0x3:
			wcscpy_s(*objecttype, 16, L"PRINTER");
			break;
		case 0x4:
			wcscpy_s(*objecttype, 16, L"REG KEY");
			break;
		case 0x5:
			wcscpy_s(*objecttype, 16, L"SHARE");
			break;
		case 0x6:
			wcscpy_s(*objecttype, 16, L"KERNEL OBJ");
			break;
		case 0x7:
			wcscpy_s(*objecttype, 16, L"WINDOW OBJ");
			break;
		case 0x8:
			wcscpy_s(*objecttype, 16, L"AD OBJ");
			break;
		case 0x9:
			wcscpy_s(*objecttype, 16, L"AD OBJ ALL");
			break;
		case 0xA:
			wcscpy_s(*objecttype, 16, L"PROVIDER OBJ");
			break;
		case 0xB:
			wcscpy_s(*objecttype, 16, L"WMI OBJ");
			break;
		case 0xC:
			wcscpy_s(*objecttype, 16, L"REG WOW64_32");
			break;
		case 0xD:
			wcscpy_s(*objecttype, 16, L"REG WOW64_64");
			break;
	}

	return 1;
}

Retrieve owner of a specified file

The following function prints the owner of the specified file.

DWORD GetFileOwner(CONST WCHAR* filename, WCHAR** owner)
{
	/*
		CONST WCHAR* filepath = new WCHAR[MAX_PATH];
		WCHAR* fileowner = NULL;
		
		filepath = L"C:\\Windows\\System32\\MapsStore.dll";
		DWORD status = GetFileOwner(filepath, &fileowner);
		if (status == 0)
		{
			::wprintf(L"[-] GetFileOwner has failed!\n");
			return 0;
		}
		::wprintf(L"Owner name: %s\n", fileowner);
	*/

	HANDLE hFile = ::CreateFileW(filename, READ_CONTROL, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	if (hFile == INVALID_HANDLE_VALUE)
	{
		::wprintf(L"[-] CreateFileA has failed: %d\n", ::GetLastError());
		if (::GetLastError() == ERROR_FILE_NOT_FOUND)
		{
			::wprintf(L"[-] The file you specified doesn't exist. Exiting...\n");
		}
		return 0;
	}

	PSID ppsidOwner = NULL, ppsidGroup = NULL;
	PACL ppDacl, ppSacl;
	PSECURITY_DESCRIPTOR ppSecurityDescriptor;

	DWORD status_GetSecurityInfo = GetSecurityInfo(hFile, SE_FILE_OBJECT, OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION, &ppsidOwner, &ppsidGroup, &ppDacl, &ppSacl, &ppSecurityDescriptor);
	if (status_GetSecurityInfo != ERROR_SUCCESS)
	{
		::wprintf(L"[-] GetSecurityInfo has failed: %d\n", status_GetSecurityInfo);
		return 0;
	}

	// first run get buffer size for owner name
	WCHAR* ReferencedDomainName = NULL;
	DWORD cchName = 1, cchReferencedDomainName = 1;
	SID_NAME_USE peUse = SidTypeUnknown;
	BOOL status_LookupAccountSidW = ::LookupAccountSidW(NULL, ppsidOwner, NULL, &cchName, NULL, &cchReferencedDomainName, &peUse);
	if ((status_LookupAccountSidW == NULL) && (GetLastError() != 122))
	{
		::wprintf(L"[-] 1st run LookupAccountSidW has failed: %d\n", GetLastError());
		return 0;
	}

	// second run to get owner name
	*owner = new WCHAR[cchName];
	ReferencedDomainName = new WCHAR[cchReferencedDomainName];
	status_LookupAccountSidW = ::LookupAccountSidW(NULL, ppsidOwner, *owner, &cchName, ReferencedDomainName, &cchReferencedDomainName, &peUse);
	if (status_LookupAccountSidW == NULL)
	{
		::wprintf(L"[-] 2nd run LookupAccountSidW has failed: %d\n", GetLastError());
		return 0;
	}

	return 1;
}

Enumerate running WIN32 processes

A function that prints a list processes with their respective process IDs (PID).

BYTE ProcessListing(void)
{
	// take a snapshot of all processes in the system
	HANDLE hProcessSnap;
	hProcessSnap = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
	if (hProcessSnap == INVALID_HANDLE_VALUE)
	{
		wprintf(L"[-] CreateToolhelp32Snaphost has failed!\n");
		return 0;
	}

	PROCESSENTRY32 pe32;
	pe32.dwSize = sizeof(PROCESSENTRY32);

	if (!::Process32First(hProcessSnap, &pe32))
	{
		wprintf(L"[-] Process32First has failed!\n");
		::CloseHandle(hProcessSnap);
		return 0;
	}
	
	do
	{
		wprintf(L"\t%d, %s\n", pe32.th32ProcessID, pe32.szExeFile);
	} while (::Process32Next(hProcessSnap, &pe32));

	::CloseHandle(hProcessSnap);

	return 1;
}

Enumerate running WIN32 services using EnumServicesStatusExW

The function listed below is the implementation of the net start in Windows API. It lists the active WIN32 services - driver services are excluded. The function dynamically resolves the APIs with the wrapper function GetModuleHandleWrapper, which is implemented later on this page.

typedef SC_HANDLE (*fnOpenSCManagerW)(LPCWSTR, LPCWSTR, DWORD);
typedef BOOL (*fnEnumServicesStatusExW)(SC_HANDLE, SC_ENUM_TYPE, DWORD, DWORD, LPBYTE, DWORD, LPDWORD, LPDWORD, LPDWORD, LPCWSTR);
typedef BOOL (*fnCloseServiceHandle)(SC_HANDLE);

DWORD EnumServicesStatusExWWrapper() {
	CHAR module[] = "advapi32";
	HMODULE hModule = GetModuleHandleWrapper(module);
	fnOpenSCManagerW OpenSCManagerW = (fnOpenSCManagerW)GetProcAddress(hModule, "OpenSCManagerW");
	SC_HANDLE hSCManager = OpenSCManagerW(NULL, SERVICES_ACTIVE_DATABASE, SC_MANAGER_ENUMERATE_SERVICE);
	if (hSCManager == NULL){
		::wprintf(L"[-] OpenSCManagerW has failed: %d\n", ::GetLastError());
		return 0;
	}

	fnEnumServicesStatusExW EnumServicesStatusExW = (fnEnumServicesStatusExW)GetProcAddress(hModule, "EnumServicesStatusExW");
	DWORD pcbBytesNeeded = 0, lpServicesReturned = 0, lpResumeHandle = 0;
	EnumServicesStatusExW(hSCManager, SC_ENUM_PROCESS_INFO, SERVICE_DRIVER | SERVICE_WIN32, SERVICE_ACTIVE, NULL, 0, &pcbBytesNeeded, &lpServicesReturned, &lpResumeHandle, NULL);
	if (GetLastError() != ERROR_MORE_DATA) {
		::wprintf(L"[-] EnumServicesStatusExW has failed: %d\n", ::GetLastError());
		return 0;
	}
	ENUM_SERVICE_STATUS_PROCESSW* lpServices = new ENUM_SERVICE_STATUS_PROCESSW[pcbBytesNeeded];
	DWORD cbBufferSize = pcbBytesNeeded;
	BOOL bResult = EnumServicesStatusExW(hSCManager, SC_ENUM_PROCESS_INFO, SERVICE_WIN32, SERVICE_ACTIVE, (LPBYTE)lpServices, cbBufferSize, &pcbBytesNeeded, &lpServicesReturned, &lpResumeHandle, NULL);
	if (bResult == FALSE) {
		::wprintf(L"[-] EnumServicesStatusExW second run has failed: %d\n", ::GetLastError());
		return 0;
	}

	::wprintf(L"[+] Number of enumerated services: %d\n", lpServicesReturned);
	auto tmplpServices = lpServices;
	::wprintf(L"index | Service Name | Display Name\n");
	for (UINT i{ 0 }; i < lpServicesReturned; i++){
		if (tmplpServices->ServiceStatusProcess.dwCurrentState == SERVICE_RUNNING){
			::wprintf(L"\t%d | %ws | %ws\n", i+1, tmplpServices->lpServiceName, tmplpServices->lpDisplayName);
		}
		tmplpServices++;
	}

	fnCloseServiceHandle CloseServiceHandle = (fnCloseServiceHandle)GetProcAddress(hModule, "CloseServiceHandle");
	BOOL sCloseSCHandle = CloseServiceHandle(hSCManager);
	if (sCloseSCHandle == FALSE){
		::wprintf(L"[+] CloseServiceHandle has failed %d\n", ::GetLastError());
		return 0;
	}

	delete[] lpServices;
	
	return 1;
}

Generate UUID using UuidCreate

The function listed below is a wrapper that generates a UUID. It’s equivalent to PowerShell’s [guid]::NewGuid() and the Windows native utility uuidgen. The function dynamically resolves the APIs with the wrapper function GetModuleHandleWrapper, which is implemented later on this page.

typedef RPC_STATUS (*fnUuidCreate)(UUID*);
typedef RPC_STATUS (*fnUuidToStringA)(const UUID*, RPC_CSTR*);

RPC_STATUS UuidCreateWrapper(UUID* uuid){
	CHAR module[] = "rpcrt4";
	HMODULE hModule = GetModuleHandleWrapper(module);
	if (hModule == 0){
		::wprintf(L"[-] Could not get module handle\n");
		return 0;
	}

	fnUuidCreate UuidCreate = (fnUuidCreate)GetProcAddress(hModule, "UuidCreate");
	RPC_STATUS rpcstatus = UuidCreate(uuid);
	if (rpcstatus != 0){
		::wprintf(L"[-] Failed to create UUID\n");
		return 0;
	}

	fnUuidToStringA UuidToStringA = (fnUuidToStringA)GetProcAddress(hModule, "UuidToStringA");
	RPC_CSTR sUUID = NULL;
	RPC_STATUS uuidstringstatus = UuidToStringA(uuid, &sUUID);
	if (uuidstringstatus != 0){
		::wprintf(L"[-] Failed to convert UUID to string\n");
		return 0;
	}
	::wprintf(L"[+] %hs\n", sUUID);
	return rpcstatus;
}

net user using NetUserEnum

A wrapper around NetUserEnum that prints local accounts of the host it’s running on. The function is equivalent to the well known command net user. The function makes use of the wrapper function GetModuleHandleWrapper, which you can find later in this page.

BYTE NetUserEnumWrapper()
{
	DWORD nSize = 60;
	WCHAR lpBuffer[nSize];
	BOOL ComputerName = GetComputerNameW(lpBuffer, &nSize);
	if (ComputerName == NULL){
		::wprintf(L"[-] GetComputerNameW has failed: %d\n", ::GetLastError());
		return 0;
	}

	typedef struct _USER_INFO_0 {
		LPWSTR usri0_name;
	} USER_INFO_0, * PUSER_INFO_0, * LPUSER_INFO_0;

	CHAR modname[] = "netapi32";
	HMODULE hModule = GetModuleHandleWrapper(modname);
	if (hModule == NULL){
		::wprintf(L"[-] GetModuleHandleWrapper has failed\n");
		return 0;
	}

	fnNetUserEnum NetUserEnum = (fnNetUserEnum)GetProcAddress(hModule, "NetUserEnum");
	if (NetUserEnum == NULL){
		::wprintf(L"[-] GetProcAddress has failed: %d\n", ::GetLastError());
		return 0;
	}

	LPUSER_INFO_0 bufr = NULL;
	DWORD entriesread = 0, totalentries = 0;
	DWORD res = NetUserEnum(NULL, 0, 0, (LPBYTE*)&bufr, 0xFFFFFFFF, &entriesread, &totalentries, NULL);
	if (res != 0){
		::wprintf(L"[-] NetUserEnum has failed: %d\n", res);
		return 0;
	}
	
	::wprintf(L"[+] Local User Accounts for %ws\n\n", lpBuffer);
	for (DWORD i{ 0 }; i < entriesread; i++) {
		::wprintf(L"\t%ws\n", bufr++->usri0_name);
	}

	return 1;
}

dir listing using FindFirstFile and FindNextFile

The function FileListing offers functionality similar to dir command implemented on WinApi.

void FileAttributeMapping(DWORD* dwFileAttributes, WCHAR* attribute)
{
	switch (*dwFileAttributes)
	{
		case 0x2:
			wcscpy_s(attribute, 256, L"HIDDEN");
			break;
		case 0x10:
			wcscpy_s(attribute, 256, L"DIR");
			break;
		case 0x20:
			wcscpy_s(attribute, 256, L"ARCHIVE");
			break;
		case 0x40:
			wcscpy_s(attribute, 256, L"DEVICE");
			break;
		case 0x800:
			wcscpy_s(attribute, 256, L"COMPRESSED");
			break;
	}
}

BYTE FileListing(const WCHAR* path)
{
	WIN32_FIND_DATAW FindFileData{ 0 };
	HANDLE hFindFile = ::FindFirstFileW(path, &FindFileData);
	if (hFindFile == INVALID_HANDLE_VALUE)
	{
		::wprintf(L"[-] FindFirstFileW has failed: %d", GetLastError());
		return 0;
	}

	WCHAR attribute[256] = {};
	do {
		FileAttributeMapping(&FindFileData.dwFileAttributes, attribute);
		::wprintf(L"%ws\t%ws\n", attribute, FindFileData.cFileName);
	} while (::FindNextFileW(hFindFile, &FindFileData) != FALSE);
	
	DWORD dwError = GetLastError();
	if (dwError != ERROR_NO_MORE_FILES)
	{
		::wprintf(L"[-] FindNextFileW has failed: %d\n", dwError);
		return 0;
	}
	::FindClose(hFindFile);

	return 1;
}

whoami using GetUserNameA

The following function is a wrapper of GetUserNameA that implements functionality similar to whoami

BOOL GetUserNameAWrapper(CHAR** lpBuffer, DWORD* pcbBuffer)
{
	// get buffer size
	GetUserNameA(NULL, pcbBuffer);
	
	*lpBuffer = new CHAR[*pcbBuffer];
	return GetUserNameA(*lpBuffer, pcbBuffer);
}

whoami using GetUserNameExW

Getting the username with GetUserNameW API doesn’t show the domain the user account belongs to. If you’re after this information, you better have to use GetUserNameExW. However, it’s not as straightforward as GetUserNameW is. Additionally you need to define the SECURITY_WIN32, include the header Security.h and link the library Secur32.dll. With dynamic API resolution you can circumvent all these steps. The wrapper function uses the custom function GetModuleHandleWrapper implemented elsewhere on this page.

typedef enum {
	NameUnknown,
	NameFullyQualifiedDN,
	NameSamCompatible,
	NameDisplay,
	NameUniqueId,
	NameCanonical,
	NameUserPrincipal,
	NameCanonicalEx,
	NameServicePrincipal,
	NameDnsDomain,
	NameGivenName,
	NameSurname
} EXTENDED_NAME_FORMAT, * PEXTENDED_NAME_FORMAT;

typedef BOOLEAN (*fnGetUserNameExW)(EXTENDED_NAME_FORMAT, LPWSTR, PULONG);

BOOL GetUserNameExAWrapper(WCHAR* lpBuffer)
{
	// NameFormat should be: NameSameCompatible in order the output be the same with whoami
	CHAR module[] = "Secur32";
	HMODULE hModule = GetModuleHandleWrapper(module);
	fnGetUserNameExW GetUserNameExW = (fnGetUserNameExW)GetProcAddress(hModule, "GetUserNameExW");
	ULONG cbBuffer = 0;
	GetUserNameExW(NameSamCompatible, NULL, &cbBuffer);
	
	return GetUserNameExW(NameSamCompatible, lpBuffer, &cbBuffer);
}

Check Elevate Privilege

The following function checks if the current process runs with Elevated Privilege.

API calling sequence/chain: GetCurrentProcess -> OpenProcessToken -> GetTokenInformation -> CloseHandle

BOOL CheckElevation()
{
	BOOL result = FALSE;
	HANDLE TokenHandle = NULL;
	if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &TokenHandle))
	{
		TOKEN_ELEVATION TokenInformation;
		DWORD ReturnLength = sizeof(TOKEN_ELEVATION);
		if (GetTokenInformation(TokenHandle, TokenElevation, &TokenInformation, ReturnLength, &ReturnLength))
		{
			result = TokenInformation.TokenIsElevated;
		}
	}
	if (TokenHandle) CloseHandle(TokenHandle);
	return result;
}

Read/Load file in memory

The following snippet is intended to be compiled as a console application which takes as an argument the path of the file we want to load in memory.

API calling sequence/chain: CreateFileA -> GetFileSize -> ReadFile

LPVOID ReadFileInMemory(WCHAR* pathtofile)
{
	// if return = 0, this indicates an error
	// if return is an address, it shows where the file has been loaded
	HANDLE hFile = ::CreateFileW(pathtofile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	if (hFile == INVALID_HANDLE_VALUE)
	{
		::wprintf(L"[-] CreateFileA has failed: %d\n", ::GetLastError());
		if (::GetLastError() == ERROR_FILE_NOT_FOUND)
		{
			::wprintf(L"[-] The file you specified doesn't exist. Exiting...\n");
		}
		return 0;
	}

	DWORD lpFileSizeHigh{ 0 };
	DWORD nNumberOfBytesToRead = ::GetFileSize(hFile, &lpFileSizeHigh);
	LPVOID lpBuffer = ::HeapAlloc(::GetProcessHeap(), HEAP_ZERO_MEMORY, nNumberOfBytesToRead);
	if (lpBuffer == NULL)
	{
		::wprintf(L"[-] HeapAlloc has failed!\n");
		return 0;
	}

	BOOL ReadStatus = ::ReadFile(hFile, lpBuffer, nNumberOfBytesToRead, NULL, NULL);
	if (ReadStatus == FALSE)
	{
		::wprintf(L"[-] FileRead has failed: %d\n", ::GetLastError());
		return 0;
	}

	return lpBuffer;
}

Convert character literals/ASCII strings to wide character literals/UNICODE strings

This function will help you convert CHAR to WCHAR.

// MultiByteToWideChar requires stringapiset.h

WCHAR* CHARToWCHAR(CHAR* inputchar)
{
	// get the length of the input string including the null character
	DWORD inputlength = MultiByteToWideChar(CP_UTF8, 0, inputchar, -1, NULL, 0);

	WCHAR* outputwchar = new WCHAR[inputlength];
	MultiByteToWideChar(CP_UTF8, 0, inputchar, -1, outputwchar, inputlength);

	return outputwchar;
}

Convert wide character literals/UNICODE strings to character literals/ASCII strings

This function will help you convert WCHAR to CHAR.

// WideCharToMultiByte requires stringapiset.h

CHAR* WCHARToCHAR(WCHAR* inputwchar)
{
	// get the length of the input wide string including the null character
	DWORD inputlength = WideCharToMultiByte(CP_UTF8, 0, inputwchar, -1, NULL, 0, NULL, NULL);
	
	CHAR* outputchar = new CHAR[inputlength];
	WideCharToMultiByte(CP_UTF8, 0, inputwchar, -1, outputchar, inputlength, NULL, NULL);

	return outputchar;
}

Find the first occurence of a keyword in a string

This function finds the first occurence of the provided substring(keyword) in the string(inputstr), but it ignores the case of both arguments(just like strcasestr() does). It returns the offset of the matched string within the provided input string or -1 if not matched.

DWORD strcasestrWin(CHAR* input, CHAR* keyword)
{
	DWORD offset = -1;
	CHAR* slidingwindow = new CHAR[strlen(keyword)+1];
	DWORD limit = strlen(input) - strlen(keyword) + 1;

	for (size_t i = 0; i < limit; i++)
	{
		strncpy_s(slidingwindow, strlen(keyword) + 1, input, strlen(keyword));
		if (!(_stricmp(slidingwindow, keyword)))
		{
			offset = i;
			break;
		}
		input++;
	}
	return offset;
}

Create a registry run key

Function to create a registry run key.

API calling sequence/chain: RegOpenKeyA -> RegSetKeyValueA -> RegCloseKey

int CreateRegistryRunKey()
{
	LSTATUS status = -1;
	LPCSTR lpSubKey = "Software\\Microsoft\\Windows\\CurrentVersion\\Run";
	HKEY phkResult = 0;

	status = RegOpenKeyA(HKEY_CURRENT_USER, lpSubKey, &phkResult);
	if (status)
	{
		printf("[-] RegOpenKeyA has failed: %d\n", GetLastError());
		return status;
	}

	LPCSTR lpValueName = "Purple Rain";
	LPCSTR lpData = "C:\\Users\\path\\to\\exe\\test.exe";

	status = RegSetKeyValueA(HKEY_CURRENT_USER, lpSubKey, lpValueName, REG_SZ, lpData, strlen(lpData) + 1);
	if (status)
	{
		printf("[-] RegSetKeyValueA has failed: %d\n", GetLastError());
		return status;
	}

	status = RegCloseKey(phkResult);
	if (status)
	{
		printf("[-] RegCloseKey has failed: %d\n", GetLastError());
		return status;
	}

	return status;
}

Enable Privileges

Administrator users may have some of the Privileges associated with this account disabled by default. The following code shows how a Privilege can be enabled for the process that executes the code.

BYTE EnablePrivilege(LPCWSTR privilege)
{
	TOKEN_PRIVILEGES tp;
	tp.PrivilegeCount = 1;
	tp.Privileges[0].Luid;
	tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;

	if (!::LookupPrivilegeValueW(NULL, privilege, &tp.Privileges[0].Luid))
	{
		::wprintf(L"[-] LookupPrivilegeVlueA has failed: %d\n", GetLastError());
		return 0;
	}

	HANDLE token = NULL;
	HANDLE hProcess = ::GetCurrentProcess();

	if (!::OpenProcessToken(hProcess, TOKEN_ALL_ACCESS, &token))
	{
		::wprintf(L"[-] OpenProcessToken has failed: %d\n", GetLastError());
		return 0;
	}

	if (!::AdjustTokenPrivileges(token, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), NULL, NULL))
	{
		::wprintf(L"[-] AdjustTokenPrivilege has failed: %d\n", GetLastError());
		return 0;
	}

	return 1;
}

The function listed below prints the owner of the token for the process that executes it.

void PrintTokenOwner()
{
	HANDLE hToken = NULL;
	if (!OpenProcessToken(GetCurrentProcess(),TOKEN_QUERY, &hToken))
	{
		printf("[-] OpenProcessToken has failed: %d\n", GetLastError());
		return 0;
	}

	DWORD ReturnLength = 0;
	if (!GetTokenInformation(hToken, TokenOwner, NULL, 0, &ReturnLength))
	{
		if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
		{
			printf("[-] GetTokenInformation1 has failed: %d\n", GetLastError());
			return 0;
		}
	}

	TOKEN_OWNER* pTokenOwner = (TOKEN_OWNER*)GlobalAlloc(GPTR, ReturnLength);
	if (!GetTokenInformation(hToken, TokenOwner, pTokenOwner, ReturnLength, &ReturnLength))
	{
		printf("[-] GetTokenInformation2 has failed: %d\n", GetLastError());
		return 0;
	}
	
	CHAR lpName[MAX_NAME];
	CHAR lpDomain[MAX_NAME];
	DWORD dwSize = MAX_NAME;
	SID_NAME_USE SidType;
	if (!LookupAccountSidA(NULL, pTokenOwner->Owner, lpName, &dwSize, lpDomain, &dwSize, &SidType))
	{
		printf("[-] LookupAccountSidA has failed: %d\n", GetLastError());
		return 0;
	}

	printf("[+] TokenOwner: %s\n", lpName);
	return 1;
}

Process name to process ID

The following function get a process name (for example explorer.exe) and returns the PID associated with this process. Many thanks to @am0nsec.

DWORD ProcessPIDCraving(const wchar_t* PName)
{
	// get a process name and return the PID of this process
	// if process belongs to a different session result is 0

	// take a snapshot of all processes in the system
	HANDLE hProcessSnap;
	hProcessSnap = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
	if (hProcessSnap == INVALID_HANDLE_VALUE)
	{
		wprintf(L"[-] CreateToolhelp32Snaphost has failed!\n");
		return 0;
	}

	PROCESSENTRY32 pe32;
	pe32.dwSize = sizeof(PROCESSENTRY32);

	if (!::Process32First(hProcessSnap, &pe32))
	{
		wprintf(L"[-] Process32First has failed!\n");
		::CloseHandle(hProcessSnap);
		return 0;
	}

	DWORD dwCurrentProcessId = ::GetCurrentProcessId();
	DWORD dwRunningProcessId = 0;
	DWORD dwCurrentProcessSessionId = 0;
	DWORD dwRunningProcessSessionId = 0;
	DWORD PID = 0;
	do
	{
		if (::wcscmp(pe32.szExeFile, PName) == 0)
		{
			dwRunningProcessId = pe32.th32ProcessID;

			::ProcessIdToSessionId(dwCurrentProcessId, &dwCurrentProcessSessionId);
			::ProcessIdToSessionId(dwRunningProcessId, &dwRunningProcessSessionId);

			if (dwCurrentProcessSessionId != dwRunningProcessSessionId)
			{
				wprintf(L"[-] Belongs to a different session\n");
				break;
			}

			PID = pe32.th32ProcessID;
			if (!::OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION | PROCESS_VM_WRITE, FALSE, PID))
			{
				// didn't get a token for the process, keep looking other active processes
				continue;
			}
			else
			{
				break;
			}
		}
	} while (::Process32Next(hProcessSnap, &pe32));

	::CloseHandle(hProcessSnap);

	// return the victim process PID or 0
	return PID;
}

ipconfig using GetAdaptersInfo

The following function is an implemention of the Windows utility ipconfig with GetAdaptersInfo. Many thanks to @am0nsec

BOOLEAN IpConfig() 
{
	ULONG uSizePointer = 0;
	ULONG res = ::GetAdaptersInfo(nullptr, &uSizePointer);
	if (res != ERROR_BUFFER_OVERFLOW || uSizePointer == 0)
	{
		::wprintf(L"[-] Error while getting the size of IP_ADAPTER_INFO\n");
		return FALSE;
	}

	HANDLE hProcessHeap = ::GetProcessHeap();
	PIP_ADAPTER_INFO pAdapterInfo = static_cast<PIP_ADAPTER_INFO>(::HeapAlloc(hProcessHeap, HEAP_ZERO_MEMORY, uSizePointer));
	res = ::GetAdaptersInfo(pAdapterInfo, &uSizePointer);
	if (res != 0 || pAdapterInfo == nullptr)
	{
		::HeapFree(hProcessHeap, 0, pAdapterInfo);
		::wprintf(L"[-] Error while getting the array IP_ADAPTER_INFO\n");
		return FALSE;
	}

	PIP_ADAPTER_INFO pCurrentAdapter = pAdapterInfo;
	do {
		::wprintf(L"\tAdapter Name: %hs\n\tAdapter Type: ", pCurrentAdapter->AdapterName);
		switch (pCurrentAdapter->Type)
		{
			case MIB_IF_TYPE_ETHERNET:
				::wprintf(L"Ethernet\n");
				break;
			case IF_TYPE_IEEE80211:
				::wprintf(L"Wireless\n");
				break;
			case MIB_IF_TYPE_OTHER:
				::wprintf(L"Other Type\n");
				break;
			case IF_TYPE_ISO88025_TOKENRING:
				::wprintf(L"Token Ring\n");
				break;
			case MIB_IF_TYPE_PPP:
				::wprintf(L"PPP\n");
				break;
			case MIB_IF_TYPE_LOOPBACK:
				::wprintf(L"Loopback\n");
				break;
			case MIB_IF_TYPE_SLIP:
				::wprintf(L"ATM\n");
				break;
		}
		::wprintf(L"\tDescription: %hs\n", pCurrentAdapter->Description);
		::wprintf(L"\tIP Address: %hs\n", pCurrentAdapter->IpAddressList.IpAddress.String);
		::wprintf(L"\tMask: %hs\n", pCurrentAdapter->IpAddressList.IpMask.String);
		::wprintf(L"\tGateway: %hs\n", pCurrentAdapter->GatewayList.IpAddress.String);
		::wprintf(L"\tAdapter MAC: ");
		for (WORD i = 0; i < pCurrentAdapter->AddressLength; i++)
		{
			if (i == (pCurrentAdapter->AddressLength - 1))
				::wprintf(L"%.2X\n", static_cast<INT>(pCurrentAdapter->Address[i]));
			else
				::wprintf(L"%.2X-", static_cast<INT>(pCurrentAdapter->Address[i]));
		}

		if (pCurrentAdapter->DhcpEnabled)
			::wprintf(L"\tDHCP Server: %hs\n", pCurrentAdapter->DhcpServer.IpAddress.String);
		
		pCurrentAdapter = pCurrentAdapter->Next;
		printf("\n");
	} while (pCurrentAdapter != nullptr);

	::HeapFree(hProcessHeap, 0, pAdapterInfo);
	return TRUE;
}

Wrapper around GetModuleHandle and LoadLibrary to get handle of a module

The following function is a wrapper to get handle of a module. The rational is to first try acquire the handle with GetModuleHandle. If it fails with ERROR_MOD_NOT_FOUND (which indicates that the module is not loaded in the process), try with LoadLibrary.

HMODULE GetModuleHandleWrapper(CHAR* module)
{
	HMODULE hModule = ::GetModuleHandleA(module);
	if (hModule == NULL) lastError = ::GetLastError();
	DWORD lastError = ::GetLastError();
	switch (lastError)
	{
		case 0:
			break;
		case ERROR_MOD_NOT_FOUND:
			//::wprintf(L"[-] GetModuleHandleW has failed to find a modue. Trying LoadLibraryW...\n");
			hModule = ::LoadLibraryA(module);
			if (hModule == NULL)
			{
				//::wprintf(L"[-] LoadLibrary has failed\n");
				return 0;
			}
			::wprintf(L"[+] Module handle acquired\n");
			break;
		default:
			//::wprintf(L"[-] GetModuleHandle has failed: %d\n", ::GetLastError());
			return 0;
	}
			
	return hModule;
}

Convert input to NT hash using Windows Cryptography Next Generation API

The function implemented in this section takes an input string and converts it to a MD4 hash. The idea came while reading about Pass the hash attacks. Windows store a hash of the user account passwords in a hashed value derived from this formula:

An interesting reading for more information:

typedef NTSTATUS (*fnBCryptOpenAlgorithmProvider)(BCRYPT_ALG_HANDLE, LPCWSTR, LPCWSTR, ULONG);
typedef NTSTATUS (*fnBCryptGetProperty)(BCRYPT_HANDLE, LPCWSTR, PUCHAR, ULONG, ULONG*, ULONG);
typedef NTSTATUS (*fnBCryptCreateHash)(BCRYPT_ALG_HANDLE, BCRYPT_HASH_HANDLE, PUCHAR, ULONG, PUCHAR, ULONG, ULONG);
typedef NTSTATUS (*fnBCryptHashData)(BCRYPT_HASH_HANDLE, PUCHAR, ULONG, ULONG);
typedef NTSTATUS (*fnBCryptFinishHash)(BCRYPT_HASH_HANDLE, PUCHAR, ULONG, ULONG);

PBYTE NThasher(WCHAR* password)
{
	// variable password: password to hash
	// which DLL to load in process memory
	CHAR module[] = "bcrypt";
	HMODULE hModule = GetModuleHandleWrapper(module);

	// call function
	fnBCryptOpenAlgorithmProvider BCryptOpenAlgorithmProvider = (fnBCryptOpenAlgorithmProvider)GetProcAddress(hModule, "BCryptOpenAlgorithmProvider");
	BCRYPT_ALG_HANDLE hAlgorithm{ NULL };
	NTSTATUS StatusOpenAlgorithmProvider = BCryptOpenAlgorithmProvider(&hAlgorithm, BCRYPT_MD4_ALGORITHM, MS_PRIMITIVE_PROVIDER, 0);
	// function error handling
	if (StatusOpenAlgorithmProvider != 0)
	{
		::wprintf(L"[-] BCryptOpenAlgorithmProvider has failed: %d\n", StatusOpenAlgorithmProvider);
		return 0;
	}

	fnBCryptGetProperty BCryptGetProperty = (fnBCryptGetProperty)GetProcAddress(hModule, "BCryptGetProperty");
	DWORD HashObjectLength = 0, ResultLength = 0;
	NTSTATUS StatusGetProperty = BCryptGetProperty(hAlgorithm, BCRYPT_OBJECT_LENGTH, (PBYTE)&HashObjectLength, sizeof(DWORD), &ResultLength, 0);
	if (StatusGetProperty != 0)
	{
		::wprintf(L"[-] BCryptGetProperty has failed: %x\n", StatusGetProperty);
		return 0;
	}

	PBYTE HashObjectBuffer = (PBYTE)::HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, HashObjectLength);
	if (HashObjectBuffer == NULL)
	{
		::wprintf(L"[-] HeapAlloc has failed: %d\n", ::GetLastError());
		return 0;
	}

	fnBCryptCreateHash BCryptCreateHash = (fnBCryptCreateHash)GetProcAddress(hModule, "BCryptCreateHash");
	BCRYPT_HASH_HANDLE hHash = NULL;
	NTSTATUS StatusCreateHash = BCryptCreateHash(hAlgorithm, &hHash, HashObjectBuffer, HashObjectLength, NULL, 0, 0);
	if (StatusCreateHash != 0)
	{
		::wprintf(L"[-] BCryptCreateHash has failed: %x\n", StatusCreateHash);
		return 0;
	}

	fnBCryptHashData BCryptHashData = (fnBCryptHashData)GetProcAddress(hModule, "BCryptHashData");
	NTSTATUS StatusHashData = BCryptHashData(hHash, (PBYTE)password, (ULONG)(wcslen(password)*2), 0);
	if (StatusHashData != 0)
	{
		::wprintf(L"[-] BCryptHashData has failed: %x\n", StatusHashData);
		return 0;
	}

	DWORD HashLength = 0;
	NTSTATUS StatusGetPropertyHashLength = BCryptGetProperty(hAlgorithm, BCRYPT_HASH_LENGTH, (PUCHAR)&HashLength, sizeof(DWORD), &ResultLength, 0);
	if (StatusGetPropertyHashLength != 0)
	{
		::wprintf(L"[-] BCryptGetProperty for hash length has failed: %x\n", StatusGetPropertyHashLength);
		return 0;
	}

	PBYTE HashBuffer = (PBYTE)::HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, HashLength);
	if (HashBuffer == NULL)
	{
		::wprintf(L"[-] HeapAlloc has failed: %d\n", ::GetLastError());
		return 0;
	}

	fnBCryptFinishHash BCryptFinishHash = (fnBCryptFinishHash)GetProcAddress(hModule, "BCryptFinishHash");
	NTSTATUS StatusFinishHash = BCryptFinishHash(hHash, HashBuffer, HashLength, 0);
	if (StatusFinishHash != 0)
	{
		::wprintf(L"[-] BCryptFinishHash has failed: %x\n", StatusFinishHash);
		return 0;
	}

	return HashBuffer;
}

Wrapper around RtlCompressBuffer for buffer compression

Compression can be useful in reducing the size of a buffer as well as evading Anti-Virus detection. Malware authors use compression as a means of signature evasion. The function implemented in this section is a wrapper around RtlCompressBuffer that assists in buffer compression. The function dynamically resolves the APIs with the wrapper function GetModuleHandleWrapper, which is implemented on this page. In the next section, a wrapper around RtlDecompressBuffer is implemented. This is a tribute to @schrodinger.

NTSTATUS fnRtlCompressBufferWrapper(PUCHAR UncompressedBuffer, ULONG addspace, PUCHAR* CompressedBuffer, PULONG szFinalCompressed)
{
	// wrapper function for RtlCompressBuffer
	// addspace: additional buffer space that may be used
	fpRtlCompressBuffer RtlCompressBuffer = (fpRtlCompressBuffer)GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "RtlCompressBuffer");
	fpRtlGetCompressionWorkSpaceSize RtlGetCompressionWorkSpaceSize = (fpRtlGetCompressionWorkSpaceSize)::GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "RtlGetCompressionWorkSpaceSize");

	if (addspace == NULL) addspace = 1024;

	if (RtlCompressBuffer == NULL){
		::wprintf(L"[-] GetProcAddress for RtlCompressBuffer has failed\n");
		return -1;
	}
	if (RtlGetCompressionWorkSpaceSize == NULL){
		::wprintf(L"[-] GetProcAddress for RtlGetCompressionWorkSpaceSize has failed\n");
		return -1;
	}
	
	// workspace
	ULONG szWorkSpace = 0, szFragmentWorkSpace = 0;
	NTSTATUS WorkSpaceStatus = RtlGetCompressionWorkSpaceSize(COMPRESSION_FORMAT_LZNT1 | COMPRESSION_ENGINE_STANDARD, &szWorkSpace, &szFragmentWorkSpace);
	if (WorkSpaceStatus >= 0x80000000){
		::wprintf(L"[-] RtlGetCompressionWorkSpaceSize has failed\n");
		return WorkSpaceStatus;
	}

	HANDLE hHeap = GetProcessHeap();
	PVOID WorkSpace = (PVOID)HeapAlloc(hHeap, HEAP_GENERATE_EXCEPTIONS, (SIZE_T)szWorkSpace);
	if (WorkSpace == NULL){
		::wprintf(L"[-] Memory allocation for WorkSpace has failed: 0x%x\n", GetLastError());
		return -1;
	}

	// if not string, manually define the size
	ULONG szUncompressedBuffer = 0;
	PUCHAR tmpUncompressedBuffer = UncompressedBuffer;
	while (*tmpUncompressedBuffer++ != '\0') szUncompressedBuffer++;
	ULONG szCompressedBuffer = szUncompressedBuffer + 1 + addspace;
	*CompressedBuffer = new UCHAR[szCompressedBuffer];

	NTSTATUS CompressStatus = RtlCompressBuffer(COMPRESSION_FORMAT_LZNT1 | COMPRESSION_ENGINE_STANDARD, UncompressedBuffer, szUncompressedBuffer + 1, *CompressedBuffer, szCompressedBuffer, 4096, szFinalCompressed, WorkSpace);
	if (CompressStatus >= 0x80000000){
		::wprintf(L"[-] RtlComperssBuffer has failed, status code: %x\n", CompressStatus);
		return CompressStatus;
	}

	// debug
	::wprintf(L"[+] UncompressedBuffer size: %ld\n", szUncompressedBuffer);

	return 0;
}

Wrapper around RtlDecompressBuffer for buffer decompression

The function implemented in this section is a wrapper around RtlDecompressBuffer and assists in decompression of a previously compressed buffer. The function dynamically resolves the APIs with the wrapper function GetModuleHandleWrapper, which is implemented on this page.

NTSTATUS fnRtlDeCompressBufferWrapper(PUCHAR* uUncompressedBuffer, ULONG addspace, PUCHAR CompressedBuffer, ULONG szCompressedBuffer, PULONG szFinalUncompressed)
{
	fpRtlDecompressBuffer RtlDecompressBuffer = (fpRtlDecompressBuffer)GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "RtlDecompressBuffer");
	if (RtlDecompressBuffer == NULL)
	{
		::wprintf(L"[-] GetProcAddress for RtlDecompressBuffer has failed\n");
		return -1;
	}

	// decompression
	if (addspace == NULL) addspace = 1024;
	ULONG szUncompressedBuffer = szCompressedBuffer + 1 + addspace;
	*uUncompressedBuffer = new UCHAR[szUncompressedBuffer];
	NTSTATUS DecompressStatus = RtlDecompressBuffer(COMPRESSION_FORMAT_LZNT1, *uUncompressedBuffer, szUncompressedBuffer, CompressedBuffer, szCompressedBuffer, szFinalUncompressed);
	if (DecompressStatus >= 0x80000000)
	{
		::wprintf(L"[-] RtlDecomperssBuffer has failed\n");
		return DecompressStatus;
	}

	return 0;
}

Get the filepath of an image using K32GetProcessImageFileNameW

A wrapper around K32GetProcessImageFileNameW (likewise K32GetProcessImageFileNameA) to retrieve the path of an image. Useful when you land in the address space of a process and you want to identify the process name or the image path or when you want to convert a process handle into a process name/path.

typedef DWORD (*fnK32GetProcessImageFileNameW)(HANDLE, LPWSTR, DWORD);

DWORD GetProcessImageFileNameWWrapper(HANDLE hProcess, WCHAR* ImgPath)
{
	auto K32GetProcessImageFileNameW = (fnK32GetProcessImageFileNameW)GetProcAddress(GetModuleHandle(TEXT("kernel32")), "K32GetProcessImageFileNameW");
	auto PathLength = K32GetProcessImageFileNameW(hProcess, ImgPath, MAX_PATH);
	if (PathLength == 0)
	{
		::wprintf(L"[-] K32GetProcessImageFileNameW has failed: %d\n", ::GetLastError());
		return 0;
	}

	return PathLength;
}

// to call it do something like:
// WCHAR ImgPath = 0;
// GetProcessImageFileNameWWrapper(GetCurrentProcess(), &ImgPath);
// ::wprintf(L"[+] Image Path: %s\n", &ImgPath);

Get module file path using GetModuleFileNameW

This wrapper function converts a module handle to filepath. Useful in cases we acquire a handle and want to determine the path of the module.

typedef DWORD(*fnGetModuleFileNameW)(HMODULE, LPWSTR, DWORD);

DWORD GetModuleFileNameWWrapper(HMODULE hModule, WCHAR* ModPath)
{
	auto GetModuleFileNameW = (fnGetModuleFileNameW)GetProcAddress(GetModuleHandle(TEXT("kernel32")), "GetModuleFileNameW");
	auto ModPathLen = GetModuleFileNameW(hModule, ModPath, MAX_PATH);
	if (ModPathLen == 0)
	{
		::wprintf(L"[-] GetModuleFileNameW has failed: %d\n", ModPath);
		return 0;
	}

	return ModPathLen;
}

// to call it do something like:
// WCHAR ModPath = 0;
// GetModuleFileNameWWrapper(GetModuleHandle(TEXT("kernelbase")), &ModPath);
// ::wprintf(L"[+] Module Path: %s\n", &ModPath);

Check Token Privilege

This function checks whether the provided privilege is enabled.

In order to determine if a process has the SE_DEBUG_PRIVILEGE (SeDebugPrivilege) the function is called like this:

CheckTokenPrivilege(TEXT(“SeDebugPrivilege”)) ? ::wprintf(L”[+] SeDebugPrivilege is enabled\n”) : ::wprintf(L”[+] SeDebugPrivilege is not enabled\n”);

BYTE CheckTokenPrivilege(const WCHAR* PrivilegeConstant)
{
	// privilege constants: docs.microsoft.com/en-us/windows/win32/secauthz/privilege-constants
	HANDLE token = NULL;
	HANDLE hProcess = ::GetCurrentProcess();
	if (!::OpenProcessToken(hProcess, TOKEN_ALL_ACCESS, &token))
	{
		::wprintf(L"[-] OpenProcessToken has failed: %d\n", ::GetLastError());
		return 0;
	}

	DWORD ReturnLength = 0;
	::GetTokenInformation(token, TokenPrivileges, NULL, 0, &ReturnLength);
	PTOKEN_PRIVILEGES TokenInformation;
	TokenInformation = (PTOKEN_PRIVILEGES)::HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, ReturnLength);
	if (TokenInformation == NULL)
	{
		::wprintf(L"[-] HeapAlloc in CheckTokenPrivilege has failed: %d\n", ::GetLastError());
		return 0;
	}

	DWORD TokenInformationLength = ReturnLength;
	if (!::GetTokenInformation(token, TokenPrivileges, (LPVOID)TokenInformation, TokenInformationLength, &ReturnLength))
	{
		::wprintf(L"[-] GetTokenInformation has failed: %d\n", ::GetLastError());
		return 0;
	}

	WCHAR lpName[MAX_PATH];
	for (DWORD i = 0; i < TokenInformation->PrivilegeCount; i++)
	{
		DWORD cchName = MAX_PATH;
		auto res = ::LookupPrivilegeNameW(NULL, &TokenInformation->Privileges[i].Luid, lpName, &cchName);
		if (res == 0)
		{
			::wprintf(L"[-] LookupPrivilegeNameW has failed: %d\n", ::GetLastError());
			return 0;
		}
		if (wcscmp(lpName, PrivilegeConstant) == 0)
		{
			//::wprintf(L"[+] Found SeDebugPrivilege\n");
			// check if it's enabled
			if ((TokenInformation->Privileges[i].Attributes != SE_PRIVILEGE_ENABLED) && (TokenInformation->Privileges[i].Attributes != SE_PRIVILEGE_ENABLED_BY_DEFAULT))
			{
				return 0;
			}
			break;
		}
	}
	
	return 1;
}

Check WDigest status enabled/disabled

The following function checks whether WDigest is enabled or not. If WDigest is enabled, credentials are stored in memory in plaintext format making possible to dump them using tools like mimikatz.

BYTE CheckWDigest()
{
	// check registry key: HKLM\SYSTEM\CurrentControlSet\Control\SecurityProviders\Wdigest\UseLogonCredential

	WCHAR lpSubKey[] = L"SYSTEM\\CurrentControlSet\\Control\\SecurityProviders\\Wdigest";
	HKEY hkResult = 0;
	auto RegStatus = ::RegOpenKeyExW(HKEY_LOCAL_MACHINE, lpSubKey, 0, KEY_QUERY_VALUE, &hkResult);
	if (RegStatus != 0)
	{
		::wprintf(L"[-] RegOpenKeyExW has failed: %d\n", RegStatus);
		return 0;
	}
	
	DWORD Type = 0;
	DWORD Data = 0;
	DWORD cbData = 256;
	RegStatus = ::RegQueryValueExW(hkResult, L"UseLogonCredential", NULL, &Type, (BYTE*)&Data, &cbData);
	if ((RegStatus != 0) && (RegStatus != 2))
	{
		::wprintf(L"[-] RegQueryValueExW has failed: %d\n", RegStatus);
		return 0;
	}

	if ((BYTE)Data != 1) return 0;

	return 1;
}

Enable WDigest via Windows API

A function that enables WDigest (via registry modification) using Windows API. If WDigest is enabled, credentials are stored in memory in plaintext format and tools like mimikatz can be used to dump them from memory.

BYTE EnableWDigest()
{
	WCHAR lpSubKey[] = L"SYSTEM\\CurrentControlSet\\Control\\SecurityProviders\\Wdigest";
	HKEY hkResult = 0;
	auto RegStatus = ::RegOpenKeyExW(HKEY_LOCAL_MACHINE, lpSubKey, 0, KEY_ALL_ACCESS, &hkResult);
	if (RegStatus != 0)
	{
		::wprintf(L"[-] RegOpenKeyExW has failed: %d\n", RegStatus);
		return 0;
	}

	DWORD Data = 1;
	RegStatus = ::RegSetValueEx(hkResult, L"UseLogonCredential", 0, REG_DWORD, (BYTE *)&Data, sizeof(REG_DWORD));
	if (RegStatus != 0)
	{
		::wprintf(L"[-] RegSetValueExW has failed: %d\n", RegStatus);
		return 0;
	}

	::RegCloseKey(hkResult);

	return 1;
}

Query Service Status via Windows API

A wrapper function that queries the status of a service (stopped or running) via Windows API. Returns 1 if service is running, 0 if not.

BYTE IsServiceRunning(_In_ WCHAR* ServiceName)
{
	// returns 1 if VSS is running or 0 is not
	BYTE flag = 0;
	CHAR module[] = "advapi32";
	HMODULE hModule = GetModuleHandleWrapper(module);
	fnOpenSCManagerW OpenSCManagerW = (fnOpenSCManagerW)GetProcAddress(hModule, "OpenSCManagerW");
	SC_HANDLE hSCManager = OpenSCManagerW(NULL, SERVICES_ACTIVE_DATABASE, SC_MANAGER_ENUMERATE_SERVICE);
	if (hSCManager == NULL)
	{
		::wprintf(L"[-] OpenSCManagerW has failed: %d\n", ::GetLastError());
		return flag;
	}

	fnOpenServiceW OpenServiceW = (fnOpenServiceW)GetProcAddress(hModule, "OpenServiceW");
	auto hService = OpenServiceW(hSCManager, ServiceName, SC_MANAGER_ALL_ACCESS);
	if (hService == NULL)
	{
		::wprintf(L"[-] OpenServiceW has failed: %d\n", ::GetLastError());
		return flag;
	}

	fnQueryServiceStatusEx QueryServiceStatusEx = (fnQueryServiceStatusEx)GetProcAddress(hModule, "QueryServiceStatusEx");
	DWORD cbBytesNeeded = 0;
	QueryServiceStatusEx(hService, SC_STATUS_PROCESS_INFO, NULL, 0, &cbBytesNeeded);

	SERVICE_STATUS_PROCESS status;
	DWORD cbBufSize = cbBytesNeeded;
	auto QServiceStatus = QueryServiceStatusEx(hService, SC_STATUS_PROCESS_INFO, (LPBYTE)&status, cbBufSize, &cbBytesNeeded);
	if (QServiceStatus == 0)
	{
		::wprintf(L"[-] QueryServiceStatusEx has failed: %d\n", ::GetLastError());
		return flag;
	}

	fnCloseServiceHandle CloseServiceHandle = (fnCloseServiceHandle)GetProcAddress(hModule, "CloseServiceHandle");
	BOOL sCloseSCHandle = CloseServiceHandle(hSCManager);
	if (sCloseSCHandle == FALSE)
	{
		::wprintf(L"[-] CloseServiceHandle has failed %d\n", ::GetLastError());
		return flag;
	}

	sCloseSCHandle = CloseServiceHandle(hService);
	if (sCloseSCHandle == FALSE)
	{
		::wprintf(L"[-] CloseServiceHandle has failed %d\n", ::GetLastError());
		return flag;
	}

	//stopped
	if (status.dwCurrentState == 0x01) flag = 0;
	//running
	else if (status.dwCurrentState == 0x04) flag = 1;

	return flag;
}

Enable Service via Windows API

The following function enables the provided service.

API chain: OpenSCManagerW -> OpenServiceW -> StartServiceW

typedef SC_HANDLE(*fnOpenSCManagerW)(LPCWSTR, LPCWSTR, DWORD);
typedef SC_HANDLE(*fnOpenServiceW)(SC_HANDLE, LPCWSTR, DWORD);
typedef SC_HANDLE(*fnStartServiceW)(SC_HANDLE, DWORD, LPCWSTR);

BYTE EnableService(_In_ const WCHAR* ServiceName)
{
	CHAR module[] = "advapi32";
	HMODULE hModule = GetModuleHandleWrapper(module);
	fnOpenSCManagerW OpenSCManagerW = (fnOpenSCManagerW)GetProcAddress(hModule, "OpenSCManagerW");
	auto hSCManager = OpenSCManagerW(NULL, NULL, SC_MANAGER_ALL_ACCESS);
	if (hSCManager == NULL)
	{
		::wprintf(L"[-] OpenSCManagerW in EnableService has failed: %d\n", ::GetLastError());
		return 0;
	}

	fnOpenServiceW OpenServiceW = (fnOpenServiceW)GetProcAddress(hModule, "OpenServiceW");
	auto hService = OpenServiceW(hSCManager, ServiceName, SC_MANAGER_ALL_ACCESS);
	if (hService == NULL)
	{
		::wprintf(L"[-] OpenServiceW in EnableService has failed: %d\n", ::GetLastError());
		return 0;
	}

	fnStartServiceW StartServiceW = (fnStartServiceW)GetProcAddress(hModule, "StartServiceW");
	auto StartServiceStatus = StartServiceW(hService, 0, NULL);
	if (StartServiceStatus == 0)
	{
		::wprintf(L"[-] StartServiceW in EnableService has failed: %d\n", ::GetLastError());
		return 0;
	}

	CloseServiceHandle(hService);
	CloseServiceHandle(hSCManager);

	return 1;
}

DNS request bypassing resolver cache

A wrapper function that accepts an FQDN and the IP of a nameserver and makes a DNS request bypassing the resolver chache on the lookup

#include <windns.h>

typedef DNS_STATUS (*fnDnsQuery_W)(WCHAR*, WORD, DWORD, PVOID, PDNS_RECORD, PVOID);
typedef unsigned long (*fninet_addr)(const char*);

BYTE DnsQueryWrapper(WCHAR* fqdn, const char* nameserver)
{
	// fqdn: domain to resolve
	// nameserver: IP address of the DNS server

	CHAR module[] = "Dnsapi";
	HMODULE hModule = GetModuleHandleWrapper(module);
	fnDnsQuery_W DnsQuery_W = (fnDnsQuery_W)GetProcAddress(hModule, "DnsQuery_W");

	CHAR modul[] = "Ws2_32";
	hModule = GetModuleHandleWrapper(modul);
	fninet_addr inet_addr = (fninet_addr)GetProcAddress(hModule, "inet_addr");
	
	IP4_ARRAY* DnsSrv = (IP4_ARRAY*)malloc(sizeof(IP4_ARRAY));
	DnsSrv->AddrCount = 1;
	DnsSrv->AddrArray[0] = inet_addr(nameserver);
	DNS_RECORDW DnsRecord;
	DNS_STATUS res = DnsQuery_W(fqdn, 0x1, 0x00000008, DnsSrv, &DnsRecord, NULL);
	if (res)
	{
		wprintf(L"[-] DnsQuery_W has failed:\n");
		return 0;
	}

	return 1;
}

Check if a computer is joined to a domain using NetGetJoinInformation

A wrapper function around NetGetJoinInformation to check if a computer is joined to a domain. It has been used in malware to detect a potential sandbox, assuming the sandbox is not joined to any domain.

Idea is borrowed from https://0xpat.github.io/Malware_development_part_2/

BOOL IsDomainJoined()
{
	PWSTR lpNameBuffer = NULL;
	NETSETUP_JOIN_STATUS BufferType;
	NET_API_STATUS status = NetGetJoinInformation(NULL, &lpNameBuffer, &BufferType);
	if (status != 0)
	{
		::wprintf(L"[-] NetGetJoinInformation has failed: 0x%x\n", status);
		return FALSE;
	}

	switch (BufferType)
	{
		case 0x0:
			::wprintf(L"[*] The status is unknown\n");
			return FALSE;
		case 0x1:
			::wprintf(L"[*] The computer is not domain joined\n");
			return FALSE;
		case 0x2:
			::wprintf(L"[*] The computer is joined to a workgroup\n");
			return FALSE;
		case 0x3:
			::wprintf(L"[+] The computer is joined to the domain: %s\n", lpNameBuffer);
			return TRUE;
		default:
			return FALSE;
	}
}

Count display monitors

The number of display monitors may indicate a sandbox. An increasing number of users are working with multiple display monitors. Sandboxes usually do not have multiple display monitors connected. Therefore, this check may be used to detect a sandbox.

INT cMonitors = GetSystemMetrics(80);
if (cMonitors == 0)
{
	wprintf(L"[-] GetSystemMetrics has failed: 0x%x\n", GetLastError());
	return 1;
}
wprintf(L"[+] %d monitors detected!\n", cMonitors);
tags: #Windows API