using System; using System.Runtime.InteropServices; namespace ScreenConnect; public class WindowsMemoryNativeLibrary : INativeLibrary { private struct DATA { public unsafe byte* LoadedImageBase; public int LoadedImageSize; public uint ExportDirectoryRelativeVirtualAddress; public uint EntryPointRelativeVirtualAddress; } private delegate bool DllEntryProc(IntPtr hinstDLL, int fdwReason, IntPtr lpReserved); private struct IMAGE_NT_HEADERS { public uint Signature; public IMAGE_FILE_HEADER FileHeader; public IMAGE_OPTIONAL_HEADER OptionalHeader; } private struct IMAGE_FILE_HEADER { public short Machine; public short NumberOfSections; public int TimeDateStamp; public int PointerToSymbolTable; public int NumberOfSymbols; public short SizeOfOptionalHeader; public short Characteristics; } private struct IMAGE_OPTIONAL_HEADER { public short Magic; public byte MajorLinkerVersion; public byte MinorLinkerVersion; public int SizeOfCode; public int SizeOfInitializedData; public int SizeOfUninitializedData; public uint AddressOfEntryPoint; public uint BaseOfCode; public uint BaseOfData; public uint ImageBase; public int SectionAlignment; public int FileAlignment; public short MajorOperatingSystemVersion; public short MinorOperatingSystemVersion; public short MajorImageVersion; public short MinorImageVersion; public short MajorSubsystemVersion; public short MinorSubsystemVersion; public int Win32VersionValue; public int SizeOfImage; public int SizeOfHeaders; public int CheckSum; public short Subsystem; public short DllCharacteristics; public IntPtr SizeOfStackReserve; public IntPtr SizeOfStackCommit; public IntPtr SizeOfHeapReserve; public IntPtr SizeOfHeapCommit; public int LoaderFlags; public int NumberOfRvaAndSizes; public IMAGE_DATA_DIRECTORY ExportDataDirectory; public IMAGE_DATA_DIRECTORY ImportDataDirectory; public IMAGE_DATA_DIRECTORY ResourceDataDirectory; public IMAGE_DATA_DIRECTORY ExceptionDataDirectory; public IMAGE_DATA_DIRECTORY SecurityDataDirectory; public IMAGE_DATA_DIRECTORY BaserelocDataDirectory; public IMAGE_DATA_DIRECTORY DebugDataDirectory; public IMAGE_DATA_DIRECTORY ArchitectureDataDirectory; public IMAGE_DATA_DIRECTORY GlobalptrDataDirectory; public IMAGE_DATA_DIRECTORY TlsDataDirectory; public IMAGE_DATA_DIRECTORY LoadConfigDataDirectory; public IMAGE_DATA_DIRECTORY BoundImportDataDirectory; public IMAGE_DATA_DIRECTORY IatDataDirectory; public IMAGE_DATA_DIRECTORY DelayImportDataDirectory; public IMAGE_DATA_DIRECTORY ComDescriptorDataDirectory; } public struct IMAGE_DATA_DIRECTORY { public uint VirtualAddress; public uint Size; } private struct IMAGE_DOS_HEADER { public short e_magic; public short e_cblp; public short e_cp; public short e_crlc; public short e_cparhdr; public short e_minalloc; public short e_maxalloc; public short e_ss; public short e_sp; public short e_csum; public short e_ip; public short e_cs; public short e_lfarlc; public short e_ovno; public unsafe fixed short e_res[4]; public short e_oemid; public short e_oeminfo; public unsafe fixed short e_res2[10]; public int e_lfanew; } private struct IMAGE_SECTION_HEADER { public unsafe fixed byte Name[8]; public int PhysicalAddressUnionedWithVirtualSize; public int VirtualAddress; public int SizeOfRawData; public int PointerToRawData; public int PointerToRelocations; public int PointerToLinenumbers; public short NumberOfRelocations; public short NumberOfLinenumbers; public WindowsNative.IMAGE_SCN Characteristics; } private struct IMAGE_BASE_RELOCATION { public uint VirtualAddress; public uint SizeOfBlock; } private struct IMAGE_IMPORT_DESCRIPTOR { public uint CharacteristicsUnionedWithOriginalFirstThunk; public uint TimeDateStamp; public uint ForwarderChain; public uint Name; public uint FirstThunk; } private struct IMAGE_IMPORT_BY_NAME { public ushort Hint; public unsafe fixed byte Name[1]; } public struct IMAGE_EXPORT_DIRECTORY { public uint Characteristics; public uint TimeDateStamp; public ushort MajorVersion; public ushort MinorVersion; public uint Name; public uint Base; public uint NumberOfFunctions; public uint NumberOfNames; public uint AddressOfFunctions; public uint AddressOfNames; public uint AddressOfNameOrdinals; } private DATA data; private WindowsMemoryNativeLibrary(DATA data) { this.data = data; } public void Dispose() { } public unsafe IntPtr TryGetProcedureAddress(string procedureName) { int num = TryGetProcedureIndex(procedureName); if (num == -1) { return IntPtr.Zero; } IMAGE_EXPORT_DIRECTORY* address = (IMAGE_EXPORT_DIRECTORY*)GetAddress(data.ExportDirectoryRelativeVirtualAddress); uint* address2 = (uint*)GetAddress(address->AddressOfFunctions); return (IntPtr)GetAddress(address2[num]); } private unsafe int TryGetProcedureIndex(string procedureName) { IMAGE_EXPORT_DIRECTORY* address = (IMAGE_EXPORT_DIRECTORY*)GetAddress(data.ExportDirectoryRelativeVirtualAddress); uint* address2 = (uint*)GetAddress(address->AddressOfNames); short* address3 = (short*)GetAddress(address->AddressOfNameOrdinals); for (int i = 0; i < address->NumberOfNames; i++) { if (Marshal.PtrToStringAnsi((IntPtr)GetAddress(address2[i])) == procedureName) { return address3[i]; } } return -1; } private unsafe byte* GetAddress(uint relativeVirtualAddress) { return data.LoadedImageBase + relativeVirtualAddress; } public unsafe static WindowsMemoryNativeLibrary Load(byte* libraryAddress) { using (WindowsExtensions.EnterMutex("WindowsMemoryNativeLibrary" + WindowsNative.GetCurrentProcessId())) { IMAGE_DOS_HEADER* ptr = (IMAGE_DOS_HEADER*)libraryAddress; if (ptr->e_magic != 23117) { throw new InvalidOperationException("Invalid library signature: " + ptr->e_magic); } IMAGE_NT_HEADERS* ntHeader = (IMAGE_NT_HEADERS*)(libraryAddress + ptr->e_lfanew); byte* ptr2 = (byte*)((IntPtr.Size == 4) ? ntHeader->OptionalHeader.ImageBase : (((ulong)ntHeader->OptionalHeader.ImageBase << 32) | ntHeader->OptionalHeader.BaseOfData)); WindowsNative.SYSTEM_INFO Info = default(WindowsNative.SYSTEM_INFO); WindowsNative.GetSystemInfo(ref Info); Random random = new Random(ntHeader->FileHeader.TimeDateStamp); IntPtr dwSize = new IntPtr(ntHeader->OptionalHeader.SizeOfImage).AlignTo(Info.dwPageSize, alignUpOrDown: true); byte* ptr3 = default(byte*); try { byte* attemptImageBase = ptr2; while (true) { ptr3 = WindowsNative.VirtualAlloc(attemptImageBase, dwSize, WindowsNative.MEM.MEM_COMMIT | WindowsNative.MEM.MEM_RESERVE, WindowsNative.PAGE.PAGE_READWRITE); if (ptr3 == null && Marshal.GetLastWin32Error() == 487 && Extensions.TryGet(() => Extensions.CompareMemory(attemptImageBase, libraryAddress, ntHeader->OptionalHeader.SizeOfHeaders))) { ptr3 = attemptImageBase; break; } if (ptr3 != null) { Extensions.CopyMemory(libraryAddress, ptr3, ntHeader->OptionalHeader.SizeOfHeaders); CopySections(libraryAddress, ntHeader, ptr3); PerformRelocations(ntHeader, ptr2, ptr3); BuildImportTable(ntHeader, ptr3); FixupSections(ntHeader, ptr3); if (!AttachOrDetach(ptr3, ntHeader->OptionalHeader.AddressOfEntryPoint, attachOrDetach: true)) { throw new InvalidOperationException("Error attaching to dll"); } break; } attemptImageBase = (byte*)(void*)random.NextIntPtr(Info.lpMinimumApplicationAddress, Info.lpMaximumApplicationAddress).AlignTo(Info.dwAllocationGranularity); } } catch { if (ptr3 != null) { WindowsNative.VirtualFree(ptr3, IntPtr.Zero, WindowsNative.MEM.MEM_RELEASE); } throw; } return new WindowsMemoryNativeLibrary(new DATA { LoadedImageBase = ptr3, LoadedImageSize = ntHeader->OptionalHeader.SizeOfImage, ExportDirectoryRelativeVirtualAddress = ntHeader->OptionalHeader.ExportDataDirectory.VirtualAddress, EntryPointRelativeVirtualAddress = ntHeader->OptionalHeader.AddressOfEntryPoint }); } } private unsafe static void BuildImportTable(IMAGE_NT_HEADERS* ntHeader, byte* loadedImageBase) { if (ntHeader->OptionalHeader.ImportDataDirectory.Size == 0) { return; } IMAGE_IMPORT_DESCRIPTOR* ptr = (IMAGE_IMPORT_DESCRIPTOR*)(loadedImageBase + ntHeader->OptionalHeader.ImportDataDirectory.VirtualAddress); long num = ntHeader->OptionalHeader.ImportDataDirectory.Size / sizeof(IMAGE_IMPORT_DESCRIPTOR); for (int i = 0; i < num && ptr[i].Name != 0; i++) { IntPtr intPtr = WindowsNative.LoadLibrary(loadedImageBase + ptr[i].Name); if (intPtr == IntPtr.Zero) { throw new InvalidOperationException("Unable to load module from import table"); } if ((ptr[i].CharacteristicsUnionedWithOriginalFirstThunk & 0x80000000u) != 0) { throw new InvalidOperationException("Import by ordinal not supported"); } IntPtr* ptr2 = (IntPtr*)(loadedImageBase + ((ptr[i].CharacteristicsUnionedWithOriginalFirstThunk != 0) ? ptr[i].CharacteristicsUnionedWithOriginalFirstThunk : ptr[i].FirstThunk)); IntPtr* ptr3 = (IntPtr*)(loadedImageBase + ptr[i].FirstThunk); while (*ptr2 != IntPtr.Zero) { IMAGE_IMPORT_BY_NAME* ptr4 = (IMAGE_IMPORT_BY_NAME*)(loadedImageBase + (uint)(int)(*ptr2)); byte* ptr5 = ptr4->Name; *ptr3 = WindowsNative.GetProcAddress(intPtr, ptr5); if (*ptr3 == IntPtr.Zero) { throw new InvalidOperationException("Imported function not found: " + Marshal.PtrToStringAnsi((IntPtr)ptr5)); } ptr2++; ptr3++; } } } private unsafe static void PerformRelocations(IMAGE_NT_HEADERS* ntHeader, byte* specifiedImageBase, byte* loadedImageBase) { if (loadedImageBase == specifiedImageBase || ntHeader->OptionalHeader.BaserelocDataDirectory.Size == 0) { return; } for (IMAGE_BASE_RELOCATION* ptr = (IMAGE_BASE_RELOCATION*)(loadedImageBase + ntHeader->OptionalHeader.BaserelocDataDirectory.VirtualAddress); ptr->VirtualAddress != 0; ptr = (IMAGE_BASE_RELOCATION*)((byte*)ptr + ptr->SizeOfBlock)) { byte* ptr2 = loadedImageBase + ptr->VirtualAddress; ushort* ptr3 = (ushort*)(ptr + 1); long num = (ptr->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / 2; for (int i = 0; i < num; i++) { int num2 = ptr3[i] >> 12; int num3 = ptr3[i] & 0xFFF; switch (num2) { case 3: if (loadedImageBase < specifiedImageBase) { *(int*)(ptr2 + num3) -= (int)specifiedImageBase - (int)loadedImageBase; } else { *(int*)(ptr2 + num3) += (int)loadedImageBase - (int)specifiedImageBase; } break; case 10: if (loadedImageBase < specifiedImageBase) { *(long*)(ptr2 + num3) -= (long)specifiedImageBase - (long)loadedImageBase; } else { *(long*)(ptr2 + num3) += (long)loadedImageBase - (long)specifiedImageBase; } break; default: throw new InvalidOperationException("Invalid relocation type: " + num2); case 0: break; } } } } private unsafe static void CopySections(byte* libraryAddress, IMAGE_NT_HEADERS* ntHeader, byte* loadedImageBase) { IMAGE_SECTION_HEADER* sectionHeaders = GetSectionHeaders(ntHeader); for (int i = 0; i < ntHeader->FileHeader.NumberOfSections; i++) { int paddedSize = GetPaddedSize(ntHeader, sectionHeaders + i); byte* ptr = loadedImageBase + sectionHeaders[i].VirtualAddress; Extensions.CopyMemory(libraryAddress + sectionHeaders[i].PointerToRawData, ptr, sectionHeaders[i].SizeOfRawData); Extensions.ZeroMemory(ptr + sectionHeaders[i].SizeOfRawData, paddedSize - sectionHeaders[i].SizeOfRawData); } } private unsafe static IMAGE_SECTION_HEADER* GetSectionHeaders(IMAGE_NT_HEADERS* ntHeader) { return (IMAGE_SECTION_HEADER*)((byte*)(&ntHeader->OptionalHeader) + ntHeader->FileHeader.SizeOfOptionalHeader); } private unsafe static int GetPaddedSize(IMAGE_NT_HEADERS* ntHeader, IMAGE_SECTION_HEADER* sectionHeader) { return Math.Max(1, Extensions.DivUp(sectionHeader->SizeOfRawData, ntHeader->OptionalHeader.SectionAlignment)); } private unsafe static void FixupSections(IMAGE_NT_HEADERS* ntHeader, byte* loadedImageBase) { IMAGE_SECTION_HEADER* sectionHeaders = GetSectionHeaders(ntHeader); WindowsNative.PAGE pAGE = default(WindowsNative.PAGE); for (int i = 0; i < ntHeader->FileHeader.NumberOfSections; i++) { if ((sectionHeaders[i].Characteristics & WindowsNative.IMAGE_SCN.IMAGE_SCN_MEM_DISCARDABLE) != 0) { int paddedSize = GetPaddedSize(ntHeader, sectionHeaders + i); WindowsNative.VirtualFree(loadedImageBase + sectionHeaders[i].VirtualAddress, (IntPtr)paddedSize, WindowsNative.MEM.MEM_DECOMMIT); continue; } bool flag = (sectionHeaders[i].Characteristics & WindowsNative.IMAGE_SCN.IMAGE_SCN_MEM_READ) != 0; bool flag2 = ((uint)sectionHeaders[i].Characteristics & 0x80000000u) != 0; bool flag3 = (sectionHeaders[i].Characteristics & WindowsNative.IMAGE_SCN.IMAGE_SCN_MEM_EXECUTE) != 0; WindowsNative.PAGE flNewProtect = ((flag & flag2 & flag3) ? WindowsNative.PAGE.PAGE_EXECUTE_READWRITE : ((flag & flag2) ? WindowsNative.PAGE.PAGE_READWRITE : ((flag & flag3) ? WindowsNative.PAGE.PAGE_EXECUTE_READ : (flag ? WindowsNative.PAGE.PAGE_READONLY : ((flag2 & flag3) ? WindowsNative.PAGE.PAGE_EXECUTE_WRITECOPY : (flag2 ? WindowsNative.PAGE.PAGE_WRITECOPY : ((!flag3) ? WindowsNative.PAGE.PAGE_NOACCESS : WindowsNative.PAGE.PAGE_EXECUTE))))))); int num = sectionHeaders[i].SizeOfRawData; if (num == 0 && (sectionHeaders[i].Characteristics & WindowsNative.IMAGE_SCN.IMAGE_SCN_CNT_INITIALIZED_DATA) != 0) { num = ntHeader->OptionalHeader.SizeOfInitializedData; } if (num == 0 && (sectionHeaders[i].Characteristics & WindowsNative.IMAGE_SCN.IMAGE_SCN_CNT_UNINITIALIZED_DATA) != 0) { num = ntHeader->OptionalHeader.SizeOfUninitializedData; } if (num != 0 && !WindowsNative.VirtualProtect(loadedImageBase + sectionHeaders[i].VirtualAddress, (IntPtr)num, flNewProtect, &pAGE)) { throw new InvalidOperationException("Unable to set memory protection for section: " + i); } } } private unsafe static bool AttachOrDetach(byte* loadedImageBase, uint entryPointRelativeVirtualAddress, bool attachOrDetach) { if (entryPointRelativeVirtualAddress == 0) { return true; } return ((DllEntryProc)Marshal.GetDelegateForFunctionPointer((IntPtr)(loadedImageBase + entryPointRelativeVirtualAddress), typeof(DllEntryProc)))((IntPtr)loadedImageBase, attachOrDetach ? 1 : 0, IntPtr.Zero); } }