#include "StdAfx.h" _NT_BEGIN //#define _PRINT_CPP_NAMES_ #include "../inc/asmfunc.h" LONG gFlags; extern "C" { PDRIVER_OBJECT g_DriverObject; } enum{ flImageNotifySet, }; #pragma intrinsic(_lrotr, _lrotl) #define RUN_ONCE_STATUS_TO_CONTEXT(status) ((PVOID)(ULONG_PTR)(_lrotl(status, 4) & ~((1 << RTL_RUN_ONCE_CTX_RESERVED_BITS) - 1))) #define RUN_ONCE_CONTEXT_TO_STATUS(Context) ((NTSTATUS)_lrotr((ULONG)(ULONG_PTR)Context, 4)) /*++ NTSTATUS DLL_INFORMATION::MapSection(PVOID& hmod); if DLL section mapped not at preferred base (STATUS_IMAGE_NOT_AT_BASE, STATUS_IMAGE_AT_DIFFERENT_BASE): 1.) system need relocate image to new base, as result modify it pages (allocate new private physical pages for modification), after this pages already not shared 2.) win10 1703+: system by unknown reason unload it, insert task to LdrpRetryQueue and return STATUS_RETRY and then tryed load it again (by using full path (!!) instead known section handle). so mapping DLL not at preferred base have serious penalty for avoid this - map our shellcode at different base - for not occupying preferred base note that because our shellcode is based-independent - any base (STATUS_IMAGE_NOT_AT_BASE) is ok for us. we not need relocated this mapping for force shellcode not occupy preferred base: 1.) before map section - reserve preffered memory range with ZwAllocateVirtualMemory (MEM_RESERVE, PAGE_NOACCESS) 2.) map section (waited return status - STATUS_IMAGE_NOT_AT_BASE) 3.) free reserved range however if load native 64-bit dll in wow64 process - preferred DLL base almost certainly will be in wow reserved memory range ------------------------------------------------------------------------------- difference between STATUS_IMAGE_NOT_AT_BASE and STATUS_IMAGE_AT_DIFFERENT_BASE: if section can not be mapped at base: when in ZwMapViewOfSection: AllocationType containing MEM_DIFFERENT_IMAGE_BASE_OK flag(used inside LdrLoadDll) - section RELOCATED and returned STATUS_IMAGE_AT_DIFFERENT_BASE otherwise - section NOT relocated and STATUS_IMAGE_NOT_AT_BASE returned. when STATUS_IMAGE_NOT_AT_BASE returned need process relocs when STATUS_IMAGE_AT_DIFFERENT_BASE relocs already applied (in kernel) - not need relocate we use base-independed code in section, we not need relocation so better not use MEM_DIFFERENT_IMAGE_BASE_OK and got unrelocated view --*/ struct DLL_INFORMATION { RTL_RUN_ONCE SectionStatus; PVOID Section, PreferredAddress; ULONG rva_1, SizeOfImage; NTSTATUS CreateDllSection(); NTSTATUS GetSection(PVOID* pSection) { union { PVOID Context; NTSTATUS status; }; if (RtlRunOnceBeginInitialize(&SectionStatus, 0, &Context) == STATUS_PENDING) { KAPC_STATE as; KeStackAttachProcess(PsInitialSystemProcess, &as); Context = RUN_ONCE_STATUS_TO_CONTEXT(CreateDllSection()); KeUnstackDetachProcess(&as); RtlRunOnceComplete(&SectionStatus, 0, Context); } status = RUN_ONCE_CONTEXT_TO_STATUS(Context); *pSection = Section; return status; } NTSTATUS MapSection(PVOID& hmod) { PVOID ReservedAddress = PreferredAddress, BaseAddress = 0; SIZE_T RegionSize = SizeOfImage, ViewSize = 0; NTSTATUS status = ZwAllocateVirtualMemory(NtCurrentProcess(), &ReservedAddress, 0, &RegionSize, MEM_RESERVE, PAGE_NOACCESS); bool bFreeMemory = false; switch (status) { case STATUS_SUCCESS: bFreeMemory = true; case STATUS_CONFLICTING_ADDRESSES: static LARGE_INTEGER ZeroOffset; if (0 <= (status = MmMapViewOfSection(Section, IoGetCurrentProcess(), &BaseAddress, 0, 0, &ZeroOffset, &ViewSize, ViewUnmap, 0, PAGE_EXECUTE))) { hmod = BaseAddress; } break; } DbgPrint("MapSection[%x]: %x %p\n", status, bFreeMemory, BaseAddress); if (bFreeMemory) { ZwFreeVirtualMemory(NtCurrentProcess(), &ReservedAddress, &RegionSize, MEM_RELEASE); } return status; } }; namespace DLL_32 { #include "../dlldemo/md5_32.h" // autogenerated file, on post-build step for DllDemo DLL_INFORMATION di {}; } #ifdef _WIN64 namespace DLL_64 { #include "../dlldemo/md5_64.h" // autogenerated file, on post-build step for DllDemo DLL_INFORMATION di {}; } #endif #ifdef _WIN64 #define NATIVE_DLL DLL_64 #define WOW_DLL DLL_32 #else #define NATIVE_DLL DLL_32 #endif // RtlAddressInSectionTable not exported in kernel PVOID AddressInSectionTable ( PIMAGE_NT_HEADERS NtHeaders, PVOID Base, ULONG Rva ) { if (ULONG NumberOfSections = NtHeaders->FileHeader.NumberOfSections) { PIMAGE_SECTION_HEADER pish = IMAGE_FIRST_SECTION(NtHeaders); do { ULONG o = Rva - pish->VirtualAddress; if (o < pish->Misc.VirtualSize) { return (PBYTE)Base + pish->PointerToRawData + o; } } while (pish++, --NumberOfSections); } return 0; } NTSTATUS MapPeAsData(HANDLE hFile, PVOID* BaseAddress, PSIZE_T ViewSize) { HANDLE hSection; NTSTATUS status = ZwCreateSection(&hSection, SECTION_MAP_READ, 0, 0, PAGE_READONLY, SEC_COMMIT, hFile); if (0 <= status) { status = ZwMapViewOfSection(hSection, NtCurrentProcess(), BaseAddress, 0, 0, 0, ViewSize, ViewUnmap, 0, PAGE_READONLY); } ZwClose(hSection); return status; } NTSTATUS IsFileOk(HANDLE hFile, PVOID _md5, ULONG FileSize, PULONG rva, PULONG AddressOfEntryPoint, PULONG SizeOfImage) { NTSTATUS status; BCRYPT_ALG_HANDLE hAlgorithm; if (0 <= (status = BCryptOpenAlgorithmProvider(&hAlgorithm, BCRYPT_MD5_ALGORITHM, 0, 0))) { union { ULONG64 align; UCHAR md5[16]; }; BCRYPT_HASH_HANDLE hHash; if (0 <= (status = BCryptCreateHash(hAlgorithm, &hHash, 0, 0, 0, 0, 0))) { PVOID BaseAddress = 0; SIZE_T ViewSize = 0; if (0 <= (status = MapPeAsData(hFile, &BaseAddress, &ViewSize))) { status = FileSize > ViewSize ? STATUS_INVALID_IMAGE_HASH : BCryptHashData(hHash, (PUCHAR)BaseAddress, FileSize, 0); if (0 <= status && 0 <= (status = BCryptFinishHash(hHash, md5, sizeof(md5), 0))) { DbgPrint("md5_0: %016I64x%016I64x\nmd5_1: %016I64x%016I64x\n", *(1 + (PULONG64)_md5), *(PULONG64)_md5, *(1 + (PULONG64)md5), *(PULONG64)md5); if (memcmp(md5, _md5, sizeof(md5))) { status = STATUS_INVALID_IMAGE_HASH; } else { status = STATUS_INVALID_IMAGE_FORMAT; if (PIMAGE_NT_HEADERS pinth = RtlImageNtHeader(BaseAddress)) { status = STATUS_PROCEDURE_NOT_FOUND; ULONG size, Ordinal = 1; PIMAGE_EXPORT_DIRECTORY pied = (PIMAGE_EXPORT_DIRECTORY) RtlImageDirectoryEntryToData(BaseAddress, FALSE, IMAGE_DIRECTORY_ENTRY_EXPORT, &size); if (pied && size >= sizeof(IMAGE_EXPORT_DIRECTORY) && (Ordinal -= pied->Base) < pied->NumberOfFunctions) { if (PULONG AddressOfFunctions = (PULONG)AddressInSectionTable(pinth, BaseAddress, pied->AddressOfFunctions)) { *rva = AddressOfFunctions[Ordinal]; *AddressOfEntryPoint = pinth->OptionalHeader.AddressOfEntryPoint; *SizeOfImage = pinth->OptionalHeader.SizeOfImage; status = STATUS_SUCCESS; } } } } } ZwUnmapViewOfSection(NtCurrentProcess(), BaseAddress); } BCryptDestroyHash(hHash); } BCryptCloseAlgorithmProvider(hAlgorithm, 0); } DbgPrint("IsFileOk=%x\n", status); return status; } NTSTATUS CreateKnownSection(HANDLE hFile, PCOBJECT_ATTRIBUTES poaKernel32, PCUNICODE_STRING My, PVOID* pTransferAddress, PVOID *pSection) { ULONG cb = 0, rcb = 256; static volatile UCHAR guz; PVOID stack = alloca(guz); HANDLE hSection; OBJECT_ATTRIBUTES oa = { sizeof(oa), 0, const_cast(My), OBJ_CASE_INSENSITIVE|OBJ_PERMANENT }; // look for system (smss.exe) assigned SD for known dlls NTSTATUS status = ZwOpenSection(&hSection, READ_CONTROL, const_cast(poaKernel32)); if (0 <= status) { do { if (cb < rcb) { cb = RtlPointerToOffset(oa.SecurityDescriptor = alloca(rcb - cb), stack); } status = ZwQuerySecurityObject(hSection, PROCESS_TRUST_LABEL_SECURITY_INFORMATION| DACL_SECURITY_INFORMATION|LABEL_SECURITY_INFORMATION|OWNER_SECURITY_INFORMATION, oa.SecurityDescriptor, cb, &rcb); } while (status == STATUS_BUFFER_TOO_SMALL); ZwClose(hSection); if (0 <= status) { status = ZwCreateSection(&hSection, SECTION_MAP_EXECUTE|SECTION_QUERY, &oa, 0, PAGE_EXECUTE, SEC_IMAGE, hFile); if (0 <= status) { SECTION_IMAGE_INFORMATION sii; status = ZwQuerySection(hSection, SectionImageInformation, &sii, sizeof(sii), 0); DbgPrint("[%08x]:%wZ<%p> at %p\n", status, My, hSection, sii.TransferAddress); PVOID Section = 0; if (0 <= status) { status = ObReferenceObjectByHandle(hSection, 0, 0, KernelMode, &Section, 0); } ZwClose(hSection); if (0 <= status) { *pTransferAddress = sii.TransferAddress; *pSection = Section; } } } } return status; } NTSTATUS DLL_INFORMATION::CreateDllSection() { ULONG FileSize; PVOID md5; PCOBJECT_ATTRIBUTES poaKernel32, poaDLL; PCUNICODE_STRING pMy; #ifdef _WIN64 if (this == &WOW_DLL::di) { STATIC_UNICODE_STRING(My, "\\KnownDlls32\\{EBB50DDB-F6AA-492d-94E3-1D51B299F627}.DLL"); STATIC_OBJECT_ATTRIBUTES(oaKernel32, "\\KnownDlls32\\kernel32.dll"); STATIC_OBJECT_ATTRIBUTES(oaDLL, "\\systemroot\\syswow64\\{EBB50DDB-F6AA-492d-94E3-1D51B299F627}.DLL"); poaKernel32 = &oaKernel32, pMy = &My, poaDLL = &oaDLL; FileSize = WOW_DLL::FileSize; md5 = &WOW_DLL::md5; } else #endif { STATIC_UNICODE_STRING(My, "\\KnownDlls\\{EBB50DDB-F6AA-492d-94E3-1D51B299F627}.DLL"); STATIC_OBJECT_ATTRIBUTES(oaKernel32, "\\KnownDlls\\kernel32.dll"); STATIC_OBJECT_ATTRIBUTES(oaDLL, "\\systemroot\\system32\\{EBB50DDB-F6AA-492d-94E3-1D51B299F627}.DLL"); poaKernel32 = &oaKernel32, pMy = &My, poaDLL = &oaDLL; FileSize = NATIVE_DLL::FileSize; md5 = &NATIVE_DLL::md5; } HANDLE hFile; IO_STATUS_BLOCK iosb; NTSTATUS status = ZwOpenFile(&hFile, FILE_GENERIC_READ|FILE_EXECUTE, const_cast(poaDLL), &iosb, FILE_SHARE_READ, FILE_SYNCHRONOUS_IO_NONALERT); DbgPrint("OpenFile(%wZ)=%x %p\n", poaDLL->ObjectName, status, hFile); if (0 <= status) { FILE_STANDARD_INFORMATION fsi; if (0 <= (status = ZwQueryInformationFile(hFile, &iosb, &fsi, sizeof(fsi), FileStandardInformation))) { DbgPrint("EndOfFile=%I64x\n_FileSize=%x\n", fsi.EndOfFile.QuadPart, FileSize); if (fsi.EndOfFile.QuadPart == FileSize) { ULONG AddressOfEntryPoint; if (0 <= (status = IsFileOk(hFile, md5, FileSize, &rva_1, &AddressOfEntryPoint, &SizeOfImage))) { PVOID TransferAddress; status = CreateKnownSection(hFile, poaKernel32, pMy, &TransferAddress, &Section); PreferredAddress = (PBYTE)TransferAddress - AddressOfEntryPoint; } } else { status = STATUS_INVALID_IMAGE_HASH; } } ZwClose(hFile); } return status; } VOID CALLBACK RundownRoutine(PKAPC )ASM_FUNCTION; VOID CALLBACK KernelRoutine(PKAPC , PKNORMAL_ROUTINE *, PVOID * , PVOID * ,PVOID * )ASM_FUNCTION; VOID CALLBACK NormalRoutine(PVOID , PVOID ,PVOID )ASM_FUNCTION; VOID CALLBACK _RundownRoutine(PKAPC Apc) { CPP_FUNCTION; DbgPrint("--Apc<%p>\n", Apc); delete Apc; } extern "C" NTSYSAPI BOOLEAN NTAPI KeTestAlertThread ( IN KPROCESSOR_MODE AlertMode ); VOID CALLBACK _NormalRoutine ( PKAPC Apc, PVOID Section, DLL_INFORMATION* pdi ) { CPP_FUNCTION; DbgPrint("NormalRoutine(%p, %p, %p)\n", Apc, Section, pdi); PVOID BaseAddress; NTSTATUS status = pdi->MapSection(BaseAddress); ObfDereferenceObject(Section); if (status != STATUS_IMAGE_NOT_AT_BASE) { DbgPrint("!!!!! MapViewOfSection = %x, %p\n", status, BaseAddress); } if (0 <= status) { union { PVOID pvNormalRoutine; PKNORMAL_ROUTINE NormalRoutine; }; PVOID NormalContext = BaseAddress; pvNormalRoutine = (PBYTE)BaseAddress + pdi->rva_1; // pvNormalRoutine is valid for CFG because this is exported address from image, // which have IMAGE_GUARD_CF_FUNCTION_TABLE_PRESENT and // pvNormalRoutine was in GuardCFFunctionTable (__guard_fids_table) #ifdef _WIN64 if (pdi == &WOW_DLL::di) PsWrapApcWow64Thread(&NormalContext, &pvNormalRoutine); #endif KeInitializeApc(Apc, KeGetCurrentThread(), OriginalApcEnvironment, KernelRoutine, RundownRoutine, NormalRoutine, UserMode, NormalContext); ObfReferenceObject(g_DriverObject); if (KeInsertQueueApc(Apc, NtCurrentProcess(), BaseAddress, IO_NO_INCREMENT)) { DbgPrint("InsertQueueApc(%p, %p, %p)\n", BaseAddress, NormalContext, NormalRoutine); // force call user mode apc KeTestAlertThread(UserMode); return ; } ObfDereferenceObject(g_DriverObject); MmUnmapViewOfSection(IoGetCurrentProcess(), BaseAddress); } DbgPrint("!!!!!!!!!!!!!!!!!!! _NormalRoutine\n"); // delete Apc; _RundownRoutine(Apc); } VOID CALLBACK _KernelRoutine( PKAPC Apc, PKNORMAL_ROUTINE * /*NormalRoutine*/, PVOID * /*NormalContext*/, PVOID * /*SystemArgument1*/, PVOID * /*SystemArgument2*/ ) { CPP_FUNCTION; DbgPrint("_KernelRoutine(%p, %x)\n", Apc, Apc->ApcMode); if (Apc->ApcMode == KernelMode) { // stage #1 - kernel mode apc ObfReferenceObject(g_DriverObject);//NormalRoutine will be called return ; } // stage #2 - user mode apc, free Apc object _RundownRoutine(Apc); } BOOLEAN SuffixUnicodeString(PCUNICODE_STRING FullName, PCUNICODE_STRING ShortName) { if (ShortName->Length < FullName->Length) { UNICODE_STRING us = { ShortName->Length, us.Length, (PWSTR)RtlOffsetToPointer(FullName->Buffer, FullName->Length - us.Length) }; return RtlEqualUnicodeString(&us, ShortName, TRUE); } return FALSE; } /*++ we called because somebody call ZwMapViewOfSection with SEC_IMAGE but this can be not true load library: (ArbitraryUserPointer -> L"*\\kernel32.dll" in true load lib) smss.exe map kernel32.dll during create \\KnownDlls (ArbitraryUserPointer == 0 in this case) wow64 process several time map kernel32.dll (32 and 64 bit) with WOW64_IMAGE_SECTION or NOT_AN_IMAGE --*/ BOOLEAN IsByLdrLoadDll(PCUNICODE_STRING ShortName) { UNICODE_STRING Name; __try { PNT_TIB Teb = (PNT_TIB)PsGetCurrentThreadTeb(); if (!Teb || !(Name.Buffer = (PWSTR)Teb->ArbitraryUserPointer)) { return FALSE; } ProbeForRead(Name.Buffer, sizeof(WCHAR), __alignof(WCHAR)); Name.Length = (USHORT)wcsnlen(Name.Buffer, MAXSHORT); if (Name.Length == MAXSHORT) { return FALSE; } Name.MaximumLength = Name.Length <<= 1; DbgPrint("ArbitraryUserPointer=%wZ\n", &Name); return SuffixUnicodeString(&Name, ShortName); } __except(EXCEPTION_EXECUTE_HANDLER) { DbgPrint("!!! IsByLdrLoadDll\n"); } return FALSE; } BOOLEAN IsTargetProcess() { PEPROCESS Process = IoGetCurrentProcess(); PCSTR name = PsGetProcessImageFileName(Process); return PsIsProcessBeingDebugged(Process) || !_stricmp("notepad.exe", name); } void BeginInject(DLL_INFORMATION* pdi) { PVOID Section; if (0 <= pdi->GetSection(&Section)) { // for do main job out of critical region if (PKAPC Apc = new(NonPagedPool) KAPC) { KeInitializeApc(Apc, KeGetCurrentThread(), OriginalApcEnvironment, KernelRoutine, RundownRoutine, NormalRoutine, KernelMode, Apc); DbgPrint("++Apc<%p> \n", Apc); ObfReferenceObject(g_DriverObject); ObfReferenceObject(Section); if (!KeInsertQueueApc(Apc, Section, pdi, IO_NO_INCREMENT)) { ObfDereferenceObject(Section); RundownRoutine(Apc); } } } } VOID CALLBACK OnLoadImage( IN PUNICODE_STRING FullImageName, IN HANDLE ProcessId, // where image is mapped IN PIMAGE_INFO ImageInfo ) { #if 0 DbgPrint("%x %p(%p) %p %x %wZ\n", #ifdef _WIN64 IoIs32bitProcess(0), #else 0, #endif ProcessId, PsGetCurrentProcessId(), ImageInfo->ImageBase, ImageInfo->ImageSize, FullImageName); #endif STATIC_UNICODE_STRING(kernel32, "\\kernel32.dll"); if ( !ImageInfo->SystemModeImage && ProcessId == PsGetCurrentProcessId() && // section can be "remote" mapped from another process SuffixUnicodeString(FullImageName, &kernel32) && IsByLdrLoadDll(&kernel32) //&& IsTargetProcess() ) { #if 0 // insert native or wow dll in process DLL_INFORMATION* pdi; #ifdef _WIN64 if (IoIs32bitProcess(0)) { pdi = &WOW_DLL::di; } else #endif { pdi = &NATIVE_DLL::di; } BeginInject(pdi); #else // insert both native and wow dll in wow64 process (just for demo) BeginInject(&NATIVE_DLL::di); #ifdef _WIN64 if (IoIs32bitProcess(0)) BeginInject(&WOW_DLL::di); #endif #endif } } void FreeLoadImageData() { if (_bittestandreset(&gFlags, flImageNotifySet)) PsRemoveLoadImageNotifyRoutine(OnLoadImage); PVOID Context; if (STATUS_SUCCESS == RtlRunOnceBeginInitialize(&NATIVE_DLL::di.SectionStatus, RTL_RUN_ONCE_CHECK_ONLY, &Context)) { if (0 <= RUN_ONCE_CONTEXT_TO_STATUS(Context)) { // we access di.Section only inside OnLoadImage // but it already completed and no more will be called (PsRemoveLoadImageNotifyRoutine) // NormalRoutine yet can be executing, but it have own referenced Section pointer DbgPrint("delete section %p\n", NATIVE_DLL::di.Section); ObMakeTemporaryObject(NATIVE_DLL::di.Section); ObfDereferenceObject(NATIVE_DLL::di.Section); } } #ifdef _WIN64 if (STATUS_SUCCESS == RtlRunOnceBeginInitialize(&WOW_DLL::di.SectionStatus, RTL_RUN_ONCE_CHECK_ONLY, &Context)) { if (0 <= RUN_ONCE_CONTEXT_TO_STATUS(Context)) { // we access di.Section only inside OnLoadImage // but it already completed and no more will be called (PsRemoveLoadImageNotifyRoutine) // NormalRoutine yet can be executing, but it have own referenced Section pointer DbgPrint("delete section %p\n", WOW_DLL::di.Section); ObMakeTemporaryObject(WOW_DLL::di.Section); ObfDereferenceObject(WOW_DLL::di.Section); } } #endif } void NTAPI DriverUnload(PDRIVER_OBJECT DriverObject) { FreeLoadImageData(); DbgPrint("DriverUnload(%p)\n", DriverObject); } extern "C" NTSTATUS NTAPI DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) { DbgPrint("DriverLoad(%p, %wZ)\n", DriverObject, RegistryPath); g_DriverObject = DriverObject; DriverObject->DriverUnload = DriverUnload; NTSTATUS status = PsSetLoadImageNotifyRoutine(OnLoadImage); if (0 <= status) { _bittestandset(&gFlags, flImageNotifySet); } return status; } _NT_END