NTKERNELAPI PPEB NTAPI PsGetProcessPeb(IN PEPROCESS Process);
NTKERNELAPI PVOID NTAPI PsGetProcessWow64Process(IN PEPROCESS Process);
NTSYSAPI NTSTATUS NTAPI ZwQueryInformationThread(IN HANDLE ThreadHandle, IN THREADINFOCLASS ThreadInformationClass, OUT PVOID ThreadInformation, IN ULONG ThreadInformationLength, OUT PULONG ReturnLength OPTIONAL);
typedef NTSTATUS(NTAPI* LPFN_NTCREATETHREADEX)(OUT PHANDLE ThreadHandle, IN ACCESS_MASK DesiredAccess, IN PVOID ObjectAttributes, IN HANDLE ProcessHandle, IN PVOID StartAddress, IN PVOID Parameter, IN ULONG Flags, IN SIZE_T StackZeroBits, IN SIZE_T SizeOfStackCommit, IN SIZE_T SizeOfStackReserve, OUT PVOID ByteBuffer);
//Initialize the attributes of the open file
OBJECT_ATTRIBUTES objectAttributes;
InitializeObjectAttributes(&objectAttributes, &uniFileName,
OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE, NULL, NULL);
////Create a file
Status = IoCreateFile(&FileHandle, FILE_READ_ATTRIBUTES | SYNCHRONIZE, &objectAttributes,
&ioStatus, 0, FILE_READ_ATTRIBUTES, FILE_SHARE_READ, FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT,
NULL, 0, CreateFileTypeNone, NULL, IO_NO_PARAMETER_CHECKING);
if (!NT_SUCCESS(Status))
return 0;
//Get file information
Status = ZwQueryInformationFile(FileHandle, &ioStatus, &FileInformation,
sizeof(FILE_STANDARD_INFORMATION), FileStandardInformation);
if (!NT_SUCCESS(Status)) {
ZwClose(FileHandle);
return 0;
}
//Determine whether the file size is too large
if (FileInformation.EndOfFile.HighPart != 0) {
ZwClose(FileHandle);
return 0;
}
//Fetch the file size
ULONG uFileSize = FileInformation.EndOfFile.LowPart;
//Read the file from the beginning
LARGE_INTEGER byteOffset;
byteOffset.LowPart = 0;
byteOffset.HighPart = 0;
Status = ZwReadFile(FileHandle, NULL, NULL, NULL, &ioStatus, pBuffer, uFileSize, &byteOffset, NULL);
if (!NT_SUCCESS(Status)) {
ZwClose(FileHandle);
return 0;
}
//Remove the export table
PIMAGE_DOS_HEADER pDosHeader;
PIMAGE_NT_HEADERS pNtHeaders;
PIMAGE_SECTION_HEADER pSectionHeader;
ULONGLONG FileOffset;//Here are 64 digits, so here is not 32 bytes
PIMAGE_EXPORT_DIRECTORY pExportDirectory;
//The DLL memory data is converted into DOS header structure
pDosHeader = (PIMAGE_DOS_HEADER)pBuffer;
//Remove the PE head structure
pNtHeaders = (PIMAGE_NT_HEADERS)((ULONGLONG)pBuffer + pDosHeader->e_lfanew);
//Determine whether the PE header export table is empty
if (pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress == 0)
return 0;
//Remove the export table offset
FileOffset = pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
//Remove the section header structure
pSectionHeader = (PIMAGE_SECTION_HEADER)((ULONGLONG)pNtHeaders + sizeof(IMAGE_NT_HEADERS));
PIMAGE_SECTION_HEADER pOldSectionHeader = pSectionHeader;
//Traverse the section structure to perform address calculation
for (UINT16 Index = 0; Index < pNtHeaders->FileHeader.NumberOfSections; Index++, pSectionHeader++) {
if (pSectionHeader->VirtualAddress <= FileOffset && FileOffset <= pSectionHeader->VirtualAddress + pSectionHeader->SizeOfRawData)
FileOffset = FileOffset - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
}
//Export table address
pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((ULONGLONG)pBuffer + FileOffset);
//Remove the export table function address
PULONG AddressOfFunctions;
FileOffset = pExportDirectory->AddressOfFunctions;
//Traverse the section structure to perform address calculation
pSectionHeader = pOldSectionHeader;
for (UINT16 Index = 0; Index < pNtHeaders->FileHeader.NumberOfSections; Index++, pSectionHeader++) {
if (pSectionHeader->VirtualAddress <= FileOffset && FileOffset <= pSectionHeader->VirtualAddress + pSectionHeader->SizeOfRawData)
FileOffset = FileOffset - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
}
AddressOfFunctions = (PULONG)((ULONGLONG)pBuffer + FileOffset);//Here we pay attention to foa and rva
//Remove the name of the export table function
PUSHORT AddressOfNameOrdinals;
FileOffset = pExportDirectory->AddressOfNameOrdinals;
//Traverse the section structure to perform address calculation
pSectionHeader = pOldSectionHeader;
for (UINT16 Index = 0; Index < pNtHeaders->FileHeader.NumberOfSections; Index++, pSectionHeader++) {
if (pSectionHeader->VirtualAddress <= FileOffset && FileOffset <= pSectionHeader->VirtualAddress + pSectionHeader->SizeOfRawData)
FileOffset = FileOffset - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
}
AddressOfNameOrdinals = (PUSHORT)((ULONGLONG)pBuffer + FileOffset);//Note foa and rva
//Remove the serial number of the export table function
PULONG AddressOfNames;
FileOffset = pExportDirectory->AddressOfNames;
//Traverse the section structure to perform address calculation
pSectionHeader = pOldSectionHeader;
for (UINT16 Index = 0; Index < pNtHeaders->FileHeader.NumberOfSections; Index++, pSectionHeader++) {
if (pSectionHeader->VirtualAddress <= FileOffset && FileOffset <= pSectionHeader->VirtualAddress + pSectionHeader->SizeOfRawData)
FileOffset = FileOffset - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
}
AddressOfNames = (PULONG)((ULONGLONG)pBuffer + FileOffset);//Note that foa and rva
LDR_DATA_TABLE_ENTRY *pDataTableEntry, *pTempDataTableEntry;
//Double cycle list definition
PLIST_ENTRY pList;
//DriverSection pointing to the drive object
pDataTableEntry = (LDR_DATA_TABLE_ENTRY*)pDriverObject->DriverSection;
// judge whether it is empty
if (!pDataTableEntry)
return 0;
/*
Start traversing the linked list of driven objects
*/
//Get the linked list address
pList = pDataTableEntry->InLoadOrderLinks.Flink;
//Determine whether it is equal to the head
while (pList != &pDataTableEntry->InLoadOrderLinks) {
pTempDataTableEntry = (LDR_DATA_TABLE_ENTRY *)pList;
//DbgPrint("name :%wZ , dll:%llx\n", &pTempDataTableEntry->BaseDllName, pTempDataTableEntry->DllBase);
if (RtlEqualUnicodeString(&pTempDataTableEntry->BaseDllName, &osName, TRUE))
return (ULONGLONG)pTempDataTableEntry->DllBase;
pList = pList->Flink;
}
return 0;
}
//Get the address of the ssdt exported function
typedef struct _SYSTEM_SERVICE_TABLE {
PVOID ServiceTableBase;
PVOID ServiceCounterTableBase;
ULONGLONG NumberOfServices;
PVOID ParamTableBase;
} SYSTEM_SERVICE_TABLE, *PSYSTEM_SERVICE_TABLE;
PSYSTEM_SERVICE_TABLE KeServiceDescriptorTable;
//Get the base address of the ssdt table
ULONGLONG GetKeServiceDescriptorTable64(PDRIVER_OBJECT DriverObject) {
ULONGLONG CodeScanStart = GetSSDTRVA((UCHAR *)"_stricmp") + GetOsBaseAddress(DriverObject);//This is the address of the real function
ULONGLONG i, tbl_address, b;
for (i = 0; i < 0x50000; i++) {
if (!memcmp((char*)(ULONGLONG)CodeScanStart + i,
(char*)KiSystemServiceStart_pattern, 13)) {
for (b = 0; b < 50; b++) {
tbl_address = ((ULONGLONG)CodeScanStart + i + b);
if (*(USHORT*)((ULONGLONG)tbl_address) == (USHORT)0x8d4c)
return ((LONGLONG)tbl_address + 7) + *(LONG*)(tbl_address + 3);
}
}
}
return 0;
}
ULONG GetIndexByName(UCHAR *sdName) {
//Initialize the attributes of the open file
OBJECT_ATTRIBUTES objectAttributes;
InitializeObjectAttributes(&objectAttributes, &uniFileName,
OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE, NULL, NULL);
////Create a file
Status = IoCreateFile(&FileHandle, FILE_READ_ATTRIBUTES | SYNCHRONIZE, &objectAttributes,
&ioStatus, 0, FILE_READ_ATTRIBUTES, FILE_SHARE_READ, FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT,
NULL, 0, CreateFileTypeNone, NULL, IO_NO_PARAMETER_CHECKING);
if (!NT_SUCCESS(Status))
return 0;
//Get file information
Status = ZwQueryInformationFile(FileHandle, &ioStatus, &FileInformation,
sizeof(FILE_STANDARD_INFORMATION), FileStandardInformation);
if (!NT_SUCCESS(Status)) {
ZwClose(FileHandle);
return 0;
}
//Determine whether the file size is too large
if (FileInformation.EndOfFile.HighPart != 0) {
ZwClose(FileHandle);
return 0;
}
//Fetch the file size
ULONG uFileSize = FileInformation.EndOfFile.LowPart;
//Read the file from the beginning
LARGE_INTEGER byteOffset;
byteOffset.LowPart = 0;
byteOffset.HighPart = 0;
Status = ZwReadFile(FileHandle, NULL, NULL, NULL, &ioStatus, pBuffer, uFileSize, &byteOffset, NULL);
if (!NT_SUCCESS(Status)) {
ZwClose(FileHandle);
return 0;
}
//Remove the export table
PIMAGE_DOS_HEADER pDosHeader;
PIMAGE_NT_HEADERS pNtHeaders;
PIMAGE_SECTION_HEADER pSectionHeader;
ULONGLONG FileOffset;//Here are 64 digits, so here is not 32 bytes
PIMAGE_EXPORT_DIRECTORY pExportDirectory;
//The DLL memory data is converted into DOS header structure
pDosHeader = (PIMAGE_DOS_HEADER)pBuffer;
//Remove the PE head structure
pNtHeaders = (PIMAGE_NT_HEADERS)((ULONGLONG)pBuffer + pDosHeader->e_lfanew);
//Determine whether the PE header export table is empty
if (pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress == 0)
return 0;
//Remove the export table offset
FileOffset = pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
//Remove the section header structure
pSectionHeader = (PIMAGE_SECTION_HEADER)((ULONGLONG)pNtHeaders + sizeof(IMAGE_NT_HEADERS));
PIMAGE_SECTION_HEADER pOldSectionHeader = pSectionHeader;
//Traverse the section structure to perform address calculation
for (UINT16 Index = 0; Index < pNtHeaders->FileHeader.NumberOfSections; Index++, pSectionHeader++) {
if (pSectionHeader->VirtualAddress <= FileOffset && FileOffset <= pSectionHeader->VirtualAddress + pSectionHeader->SizeOfRawData)
FileOffset = FileOffset - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
}
//Export table address
pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((ULONGLONG)pBuffer + FileOffset);
//Remove the export table function address
PULONG AddressOfFunctions;
FileOffset = pExportDirectory->AddressOfFunctions;
//Traverse the section structure to perform address calculation
pSectionHeader = pOldSectionHeader;
for (UINT16 Index = 0; Index < pNtHeaders->FileHeader.NumberOfSections; Index++, pSectionHeader++) {
if (pSectionHeader->VirtualAddress <= FileOffset && FileOffset <= pSectionHeader->VirtualAddress + pSectionHeader->SizeOfRawData)
FileOffset = FileOffset - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
}
AddressOfFunctions = (PULONG)((ULONGLONG)pBuffer + FileOffset);//Here we pay attention to foa and rva
//Remove the name of the export table function
PUSHORT AddressOfNameOrdinals;
FileOffset = pExportDirectory->AddressOfNameOrdinals;
//Traverse the section structure to perform address calculation
pSectionHeader = pOldSectionHeader;
for (UINT16 Index = 0; Index < pNtHeaders->FileHeader.NumberOfSections; Index++, pSectionHeader++) {
if (pSectionHeader->VirtualAddress <= FileOffset && FileOffset <= pSectionHeader->VirtualAddress + pSectionHeader->SizeOfRawData)
FileOffset = FileOffset - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
}
AddressOfNameOrdinals = (PUSHORT)((ULONGLONG)pBuffer + FileOffset);//Note foa and rva
//Remove the serial number of the export table function
PULONG AddressOfNames;
FileOffset = pExportDirectory->AddressOfNames;
//Traverse the section structure to perform address calculation
pSectionHeader = pOldSectionHeader;
for (UINT16 Index = 0; Index < pNtHeaders->FileHeader.NumberOfSections; Index++, pSectionHeader++) {
if (pSectionHeader->VirtualAddress <= FileOffset && FileOffset <= pSectionHeader->VirtualAddress + pSectionHeader->SizeOfRawData)
FileOffset = FileOffset - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
}
AddressOfNames = (PULONG)((ULONGLONG)pBuffer + FileOffset);//Note that foa and rva
ExFreePoolWithTag(pBuffer, (ULONG)"Ntdl");
ZwClose(FileHandle);
return 0;
}
ULONGLONG GetSSDTFuncCurAddr(ULONG id) {
/*
mov rax, rcx; rcx=Native API index
lea r10,[rdx] ;rdx=ssdt base address
mov edi,eax ;index
shr edi,7
and edi,20h
mov r10, qword ptr [r10+rdi]//Get ServiceTableBase here
movsxd r11,dword ptr [r10+rax]//I get the address of the fake ssdt without right shift here
mov rax,r11
sar r11,4
add r10,r11
mov rax,r10
ret
*/
LONG dwtmp = 0;
PULONG ServiceTableBase = NULL;
ServiceTableBase = (PULONG)KeServiceDescriptorTable->ServiceTableBase;
dwtmp = ServiceTableBase[id];
dwtmp = dwtmp >> 4;
return (LONGLONG)dwtmp + (ULONGLONG)ServiceTableBase;//You need to shift right by 4 bits and add the base address to get the address of ssdt
}
//----------------------------------------------------------------------------------------------------------
//Start thread
NTSTATUS NTAPI SeCreateThreadEx(OUT PHANDLE ThreadHandle, IN ACCESS_MASK DesiredAccess, IN PVOID ObjectAttributes, IN HANDLE ProcessHandle, IN PVOID StartAddress, IN PVOID Parameter, IN ULONG Flags, IN SIZE_T StackZeroBits, IN SIZE_T SizeOfStackCommit, IN SIZE_T SizeOfStackReserve, IN PNT_PROC_THREAD_ATTRIBUTE_LIST AttributeList){
NTSTATUS Status = STATUS_SUCCESS;
LPFN_NTCREATETHREADEX NtCreateThreadEx = (LPFN_NTCREATETHREADEX)(GetSSDTFuncCurAddr(GetIndexByName((UCHAR *)"NtCreateThreadEx")));
Status = ZwQueryInformationThread(ThreadHandle, ThreadBasicInformation, &ThreadBasicInfo, sizeof(ThreadBasicInfo), &ReturnLength);
if (NT_SUCCESS(Status) && ExitStatus)
*ExitStatus = ThreadBasicInfo.ExitStatus;//Here is to query whether the current dll injection is successful
else if (!NT_SUCCESS(Status))
DbgPrint("%s: ZwQueryInformationThread failed with status 0x%X\n", __FUNCTION__, Status);
}
else
DbgPrint("%s: ZwWaitForSingleObject failed with status 0x%X\n", __FUNCTION__, Status);
}
else
DbgPrint("%s: ZwCreateThreadEx failed with status 0x%X\n", __FUNCTION__, Status);
if (ThreadHandle)
ZwClose(ThreadHandle);
return Status;
}
//Create injection code 64-bit target process
PINJECT_BUFFER GetNativeCode(IN PVOID LdrLoadDll, IN PUNICODE_STRING DllFullPath) {
NTSTATUS Status = STATUS_SUCCESS;
PINJECT_BUFFER InjectBuffer = NULL;//This structure is customized
SIZE_T Size = PAGE_SIZE;
// Fill stubs
*(ULONG*)((PUCHAR)InjectBuffer + 1) = (ULONG)(ULONG_PTR)&InjectBuffer->ModuleHandle;//This parameter is an output parameter, return handle
*(ULONG*)((PUCHAR)InjectBuffer + 6) = (ULONG)(ULONG_PTR)pUserPath;
*(ULONG*)((PUCHAR)InjectBuffer + 15) = (ULONG)((ULONG_PTR)LdrLoadDll - ((ULONG_PTR)InjectBuffer + 15) - 5 + 1);
*(ULONG*)((PUCHAR)InjectBuffer + 20) = (ULONG)(ULONG_PTR)&InjectBuffer->Complete;//This is useless, this is a fixed value, it may be to check whether the current execution is here, integrity check ? From the name
*(ULONG*)((PUCHAR)InjectBuffer + 31) = (ULONG)(ULONG_PTR)&InjectBuffer->Status;
for (ULONG i = 0; i < ImageExportDirectory->NumberOfFunctions; ++i) {
USHORT OrdIndex = 0xFFFF;
PCHAR pName = NULL;
// Find by index
if ((ULONG_PTR)FunctionName <= 0xFFFF) {
OrdIndex = (USHORT)i;
}
// Find by name
else if ((ULONG_PTR)FunctionName > 0xFFFF && i < ImageExportDirectory->NumberOfNames) {
pName = (PCHAR)(pAddressOfNames + (ULONG_PTR)ModuleBase);
OrdIndex = pAddressOfOrds;
}
// Weird params
else
return NULL;
if (((ULONG_PTR)FunctionName <= 0xFFFF && (USHORT)((ULONG_PTR)FunctionName) == OrdIndex + ImageExportDirectory->Base) ||
((ULONG_PTR)FunctionName > 0xFFFF && strcmp(pName, FunctionName) == 0)) {
FunctionAddress = pAddressOfFuncs[OrdIndex] + (ULONG_PTR)ModuleBase;
break;
}
}
return (PVOID)FunctionAddress;
}
//Get user module
PVOID GetUserModule(IN PEPROCESS EProcess, IN PUNICODE_STRING ModuleName, IN BOOLEAN IsWow64) {
if (EProcess == NULL)
return NULL;
__try {
LARGE_INTEGER Time = { 0 };
Time.QuadPart = -250ll * 10 * 1000; //250 ms.
if (IsWow64) {
PPEB32 Peb32 = (PPEB32)PsGetProcessWow64Process(EProcess);
if (Peb32 == NULL) {
DbgPrint("%s: No PEB present. Aborting\n", __FUNCTION__);
return NULL;
}
for (INT i = 0; !Peb32->Ldr && i <10; i++) {//This should be the reason for lazy loading, it may not be updated in time, so it will wait
DbgPrint(" %s: Loader not intialiezd, waiting\n", __FUNCTION__);
KeDelayExecutionThread(KernelMode, TRUE, &Time);
}
// Still no loader
if (!Peb32->Ldr) {
DbgPrint(" %s: Loader was not intialiezd in time. Aborting\n", __FUNCTION__);
return NULL;
}
// Search in InLoadOrderModuleList
for (PLIST_ENTRY32 ListEntry = (PLIST_ENTRY32)((PPEB_LDR_DATA32)Peb32->Ldr)->InLoadOrderModuleList.Flink;
ListEntry != &((PPEB_LDR_DATA32)Peb32->Ldr)->InLoadOrderModuleList;
ListEntry = (PLIST_ENTRY32)ListEntry->Flink) {
UNICODE_STRING UnicodeString;
PLDR_DATA_TABLE_ENTRY32 LdrDataTableEntry32 = CONTAINING_RECORD(ListEntry, LDR_DATA_TABLE_ENTRY32, InLoadOrderLinks);
RtlUnicodeStringInit(&UnicodeString, (PWCH)LdrDataTableEntry32->BaseDllName.Buffer);
if (RtlCompareUnicodeString(&UnicodeString, ModuleName, TRUE) == 0)
return (PVOID)LdrDataTableEntry32->DllBase;
}
}
else {
PPEB Peb = PsGetProcessPeb(EProcess);
if (!Peb) {
DbgPrint("%s: No PEB present. Aborting\n", __FUNCTION__);
return NULL;
}
for (INT i = 0; !Peb->Ldr && i < 10; i++) {
DbgPrint("%s: Loader not intialiezd, waiting\n", __FUNCTION__);
KeDelayExecutionThread(KernelMode, TRUE, &Time);
}
if (!Peb->Ldr) {
DbgPrint("%s: Loader was not intialiezd in time. Aborting\n", __FUNCTION__);
return NULL;
}
//Switch to the target process to create a kernel thread for injection, mainly the switch of cr3
NTSTATUS AttachAndInjectProcess(IN HANDLE ProcessID) {
PEPROCESS EProcess = NULL;
KAPC_STATE ApcState;
NTSTATUS Status = STATUS_SUCCESS;
if (ProcessID == NULL) {
Status = STATUS_UNSUCCESSFUL;
return Status;
}
//Get EProcess
Status = PsLookupProcessByProcessId(ProcessID, &EProcess);
if (Status != STATUS_SUCCESS) {
DbgPrint("PsLookupProcessByProcessId Failed\n");
return Status;
}
//Judge the target process x86 or x64
BOOLEAN IsWow64 = (PsGetProcessWow64Process(EProcess) != NULL) ? TRUE : FALSE;
//KeStackAttachProcess routine connects the current thread to the address space of the target process.
KeStackAttachProcess((PRKPROCESS)EProcess, &ApcState);
__try {
PVOID NtdllAddress = NULL;
PVOID LdrLoadDll = NULL;
UNICODE_STRING NtdllUnicodeString = { 0 };
UNICODE_STRING DllFullPath = { 0 };
//Get the base address of the ntdll module
RtlInitUnicodeString(&NtdllUnicodeString, L"Ntdll.dll");
NtdllAddress = GetUserModule(EProcess, &NtdllUnicodeString, IsWow64);//This mainly gets the base address of the specified string dll
if (!NtdllAddress) {
DbgPrint("%s: Failed to get Ntdll base\n", __FUNCTION__);
Status = STATUS_NOT_FOUND;
}
//Get LdrLoadDll
if (NT_SUCCESS(Status)) {
LdrLoadDll = GetModuleExport(NtdllAddress, "LdrLoadDll", EProcess);
if (!LdrLoadDll) {
DbgPrint("%s: Failed to get LdrLoadDll address\n", __FUNCTION__);
Status = STATUS_NOT_FOUND;
}
}
PINJECT_BUFFER InjectBuffer = NULL;
if (IsWow64) {//Write your own dll path here
RtlInitUnicodeString(&DllFullPath, L"G:\\vs2013\\testdll\\Debug\\testdll32.dll");//<------- here is changed to the dll you want to inject
InjectBuffer = GetWow64Code(LdrLoadDll, &DllFullPath);
}
else {
RtlInitUnicodeString(&DllFullPath, L"G:\\vs2013\\testdll\\x64\\Debug\\testdll64.dll");//<------- here is changed to the dll you want to inject
InjectBuffer = GetNativeCode(LdrLoadDll, &DllFullPath);//Build your own shellcode here, write to memory to load dll
}
ExecuteInNewThread(InjectBuffer, NULL, THREAD_CREATE_FLAGS_HIDE_FROM_DEBUGGER, TRUE, &Status);//Create a thread and execute the constructed shellcode
if (!NT_SUCCESS(Status)) {
DbgPrint("ExecuteInNewThread Failed\n");
}