462 lines
14 KiB
C
462 lines
14 KiB
C
/*
|
|
* Copyright 2022 David Xanatos, xanasoft.com
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#define WIN32_NO_STATUS
|
|
#include <windows.h>
|
|
#include "win32_ntddk.h"
|
|
|
|
// ntimage.h
|
|
|
|
typedef struct _IMAGE_CHPE_METADATA_X86 {
|
|
ULONG Version;
|
|
ULONG CHPECodeAddressRangeOffset;
|
|
ULONG CHPECodeAddressRangeCount;
|
|
ULONG WowA64ExceptionHandlerFunctionPointer;
|
|
ULONG WowA64DispatchCallFunctionPointer;
|
|
ULONG WowA64DispatchIndirectCallFunctionPointer;
|
|
ULONG WowA64DispatchIndirectCallCfgFunctionPointer;
|
|
ULONG WowA64DispatchRetFunctionPointer;
|
|
ULONG WowA64DispatchRetLeafFunctionPointer;
|
|
ULONG WowA64DispatchJumpFunctionPointer;
|
|
ULONG CompilerIATPointer; // Present if Version >= 2
|
|
ULONG WowA64RdtscFunctionPointer; // Present if Version >= 3
|
|
} IMAGE_CHPE_METADATA_X86, * PIMAGE_CHPE_METADATA_X86;
|
|
|
|
typedef struct _IMAGE_CHPE_RANGE_ENTRY {
|
|
union {
|
|
ULONG StartOffset;
|
|
struct {
|
|
ULONG NativeCode : 1;
|
|
ULONG AddressBits : 31;
|
|
} DUMMYSTRUCTNAME;
|
|
} DUMMYUNIONNAME;
|
|
|
|
ULONG Length;
|
|
} IMAGE_CHPE_RANGE_ENTRY, * PIMAGE_CHPE_RANGE_ENTRY;
|
|
|
|
typedef struct _IMAGE_ARM64EC_METADATA {
|
|
ULONG Version;
|
|
ULONG CodeMap;
|
|
ULONG CodeMapCount;
|
|
ULONG CodeRangesToEntryPoints;
|
|
ULONG RedirectionMetadata;
|
|
ULONG tbd__os_arm64x_dispatch_call_no_redirect;
|
|
ULONG tbd__os_arm64x_dispatch_ret;
|
|
ULONG tbd__os_arm64x_dispatch_call;
|
|
ULONG tbd__os_arm64x_dispatch_icall;
|
|
ULONG tbd__os_arm64x_dispatch_icall_cfg;
|
|
ULONG AlternateEntryPoint;
|
|
ULONG AuxiliaryIAT;
|
|
ULONG CodeRangesToEntryPointsCount;
|
|
ULONG RedirectionMetadataCount;
|
|
ULONG GetX64InformationFunctionPointer;
|
|
ULONG SetX64InformationFunctionPointer;
|
|
ULONG ExtraRFETable;
|
|
ULONG ExtraRFETableSize;
|
|
ULONG __os_arm64x_dispatch_fptr;
|
|
ULONG AuxiliaryIATCopy;
|
|
} IMAGE_ARM64EC_METADATA;
|
|
|
|
typedef struct _IMAGE_ARM64EC_REDIRECTION_ENTRY {
|
|
ULONG Source;
|
|
ULONG Destination;
|
|
} IMAGE_ARM64EC_REDIRECTION_ENTRY;
|
|
|
|
//
|
|
|
|
typedef NTSTATUS(__stdcall* P_NtQueryVirtualMemory64)(
|
|
IN HANDLE ProcessHandle,
|
|
IN DWORD64 BaseAddress,
|
|
IN MEMORY_INFORMATION_CLASS MemoryInformationClass,
|
|
OUT PVOID MemoryInformation,
|
|
IN SIZE_T MemoryInformationLength,
|
|
OUT PSIZE_T ReturnLength OPTIONAL);
|
|
|
|
typedef NTSTATUS(__stdcall* P_NtReadVirtualMemory64)(
|
|
IN HANDLE ProcessHandle,
|
|
IN DWORD64 BaseAddress,
|
|
OUT PVOID Buffer,
|
|
IN SIZE_T BufferSize,
|
|
OUT PSIZE_T NumberOfBytesRead OPTIONAL);
|
|
|
|
#ifdef _WIN64
|
|
P_NtQueryVirtualMemory64 NtQueryVirtualMemory64 = (P_NtQueryVirtualMemory64)NtQueryVirtualMemory;
|
|
P_NtReadVirtualMemory64 NtReadVirtualMemory64 = (P_NtReadVirtualMemory64)NtReadVirtualMemory;
|
|
#else
|
|
P_NtQueryVirtualMemory64 NtQueryVirtualMemory64 = NULL;
|
|
P_NtReadVirtualMemory64 NtReadVirtualMemory64 = NULL;
|
|
#endif
|
|
|
|
DWORD64 FindDllBase64(HANDLE hProcess, const WCHAR* dll)
|
|
{
|
|
char buffer[512];
|
|
ULONG len = wcslen(dll);
|
|
|
|
if (!NtQueryVirtualMemory64)
|
|
return -1;
|
|
|
|
for (DWORD64 baseAddress = 0;;)
|
|
{
|
|
MEMORY_BASIC_INFORMATION64 basicInfo;
|
|
if (!NT_SUCCESS(NtQueryVirtualMemory64(hProcess, baseAddress, MemoryBasicInformation, &basicInfo, sizeof(MEMORY_BASIC_INFORMATION64), NULL)))
|
|
break;
|
|
|
|
baseAddress = baseAddress + basicInfo.RegionSize;
|
|
if (NT_SUCCESS(NtQueryVirtualMemory64(hProcess, basicInfo.AllocationBase, MemoryMappedFilenameInformation, buffer, sizeof(buffer), NULL)))
|
|
{
|
|
UNICODE_STRING64* FullImageName = (UNICODE_STRING*)buffer;
|
|
if (FullImageName->Length > len * sizeof(WCHAR)) {
|
|
|
|
WCHAR* path = (WCHAR*)((DWORD64)FullImageName->Buffer + FullImageName->Length - len * sizeof(WCHAR));
|
|
if (_wcsicmp(path, dll) == 0) {
|
|
|
|
return (DWORD64)basicInfo.AllocationBase;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
BYTE* MapRemoteDll(HANDLE hProcess, DWORD64 DllBase)
|
|
{
|
|
DWORD64 MaxSize = 0;
|
|
|
|
if (!NtQueryVirtualMemory64 || !NtReadVirtualMemory64)
|
|
return (BYTE*)-1;
|
|
|
|
for (DWORD64 baseAddress = 0;;)
|
|
{
|
|
MEMORY_BASIC_INFORMATION64 basicInfo;
|
|
if (!NT_SUCCESS(NtQueryVirtualMemory64(hProcess, baseAddress, MemoryBasicInformation, &basicInfo, sizeof(MEMORY_BASIC_INFORMATION64), NULL)))
|
|
break;
|
|
|
|
baseAddress = baseAddress + basicInfo.RegionSize;
|
|
if (basicInfo.AllocationBase == DllBase)
|
|
{
|
|
DWORD64 CurrentSize = (basicInfo.BaseAddress + basicInfo.RegionSize) - basicInfo.AllocationBase;
|
|
if (MaxSize < CurrentSize)
|
|
MaxSize = CurrentSize;
|
|
}
|
|
}
|
|
|
|
if (MaxSize == 0)
|
|
return NULL;
|
|
|
|
BYTE* buffer = HeapAlloc(GetProcessHeap(), 0, (SIZE_T)MaxSize);
|
|
if (!buffer)
|
|
return NULL;
|
|
|
|
BYTE* ptr = buffer;
|
|
for (DWORD64 pos = DllBase; pos < (DllBase + MaxSize); pos += 0x1000)
|
|
{
|
|
NtReadVirtualMemory64(hProcess, pos, ptr, 0x1000, NULL);
|
|
ptr += 0x1000;
|
|
}
|
|
|
|
return buffer;
|
|
}
|
|
|
|
IMAGE_SECTION_HEADER* FindImageSection(DWORD rva, PIMAGE_NT_HEADERS32 pNTHeader)
|
|
{
|
|
PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION(pNTHeader);
|
|
for (ULONG i = 0; i < pNTHeader->FileHeader.NumberOfSections; i++, section++) {
|
|
DWORD size = section->Misc.VirtualSize;
|
|
if (size == 0)
|
|
size = section->SizeOfRawData;
|
|
if ((rva >= section->VirtualAddress) && (rva < (section->VirtualAddress + size)))
|
|
return section;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
DWORD64 FindImagePosition(DWORD rva, void* pNTHeader, DWORD64 imageBase)
|
|
{
|
|
if (imageBase != 0) // live image
|
|
return imageBase + rva;
|
|
// file on disk
|
|
IMAGE_SECTION_HEADER* pSectionHdr = FindImageSection(rva, pNTHeader);
|
|
if (!pSectionHdr)
|
|
return 0;
|
|
DWORD delta = pSectionHdr->VirtualAddress - pSectionHdr->PointerToRawData;
|
|
//return imageBase + rva - delta;
|
|
return rva - delta;
|
|
}
|
|
|
|
DWORD64 FindDllExport2(P_NtReadVirtualMemory64 ReadDll, HANDLE hProcess, DWORD64 DllBase, IMAGE_DATA_DIRECTORY *dir0, const char* ProcName, void* pNTHeader)
|
|
{
|
|
NTSTATUS status;
|
|
BYTE* buffer;
|
|
DWORD64 proc = 0;
|
|
|
|
DWORD64 dir0Address = FindImagePosition(dir0->VirtualAddress, pNTHeader, DllBase);
|
|
|
|
buffer = HeapAlloc(GetProcessHeap(), 0, dir0->Size);
|
|
status = ReadDll(hProcess, dir0Address, buffer, dir0->Size, NULL);
|
|
|
|
IMAGE_EXPORT_DIRECTORY* exports = buffer;
|
|
|
|
ULONG* names = (ULONG*)((DWORD64)buffer + exports->AddressOfNames - dir0->VirtualAddress);
|
|
USHORT* ordinals = (USHORT*)((DWORD64)buffer + exports->AddressOfNameOrdinals - dir0->VirtualAddress);
|
|
ULONG* functions = (ULONG*)((DWORD64)buffer + exports->AddressOfFunctions - dir0->VirtualAddress);
|
|
|
|
for (ULONG i = 0; i < exports->NumberOfNames; ++i) {
|
|
|
|
//BYTE* name = (BYTE*)DllBase + names[i];
|
|
char* name = (char*)((DWORD64)exports + names[i] - dir0->VirtualAddress);
|
|
|
|
if(strcmp(name, ProcName) == 0)
|
|
{
|
|
if (ordinals[i] < exports->NumberOfFunctions) {
|
|
|
|
proc = DllBase + functions[ordinals[i]];
|
|
|
|
// Note: if this is an arm32 image the real address has a 0x01 appended to indicate it uses the thumb instruction set
|
|
//if (((PIMAGE_NT_HEADERS32)pNTHeader)->FileHeader.Machine == IMAGE_FILE_MACHINE_ARMNT)
|
|
// proc &= ~1;
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (proc && proc >= dir0Address && proc < dir0Address + dir0->Size) {
|
|
|
|
//
|
|
// if the export points inside the export table, then it is a
|
|
// forwarder entry. we don't handle these, because none of the
|
|
// exports we need is a forwarder entry. if this changes, we
|
|
// might have to scan LDR tables to find the target dll
|
|
//
|
|
|
|
proc = 0;
|
|
}
|
|
|
|
HeapFree(GetProcessHeap(), 0, buffer);
|
|
|
|
return proc;
|
|
}
|
|
|
|
DWORD64 ResolveWoWRedirection64(P_NtReadVirtualMemory64 ReadDll, HANDLE hProcess, DWORD64 DllBase, DWORD64 proc, DWORD64 CHPEMetadataPointer, void* pNTHeader)
|
|
{
|
|
NTSTATUS status;
|
|
|
|
IMAGE_ARM64EC_METADATA MetaData;
|
|
status = ReadDll(hProcess, CHPEMetadataPointer, &MetaData, sizeof(MetaData), NULL);
|
|
|
|
ULONG size = MetaData.RedirectionMetadataCount * sizeof(IMAGE_ARM64EC_REDIRECTION_ENTRY);
|
|
BYTE* buffer = HeapAlloc(GetProcessHeap(), 0, size);
|
|
status = ReadDll(hProcess, FindImagePosition(MetaData.RedirectionMetadata, pNTHeader, DllBase), buffer, size, NULL);
|
|
IMAGE_ARM64EC_REDIRECTION_ENTRY* RedirectionMetadata = buffer;
|
|
|
|
for (ULONG i = 0; i < MetaData.RedirectionMetadataCount; i++) {
|
|
if ((proc - DllBase) == RedirectionMetadata[i].Source) {
|
|
proc = DllBase + RedirectionMetadata[i].Destination;
|
|
break;
|
|
}
|
|
}
|
|
|
|
HeapFree(GetProcessHeap(), 0, buffer);
|
|
|
|
return proc;
|
|
}
|
|
|
|
DWORD64 FindDllExport_impl(P_NtReadVirtualMemory64 ReadDll, HANDLE hProcess, DWORD64 DllBase, const char* ProcName)
|
|
{
|
|
NTSTATUS status;
|
|
DWORD64 proc = 0;
|
|
|
|
IMAGE_DOS_HEADER* dos_hdr;
|
|
IMAGE_NT_HEADERS* nt_hdrs;
|
|
|
|
BYTE Buffer[0x10000];
|
|
BYTE Buffer2[0x10000];
|
|
|
|
status = ReadDll(hProcess, DllBase, Buffer, sizeof(Buffer), NULL);
|
|
|
|
BOOLEAN resolve_ec = ProcName[0] == '#';
|
|
if (resolve_ec)
|
|
ProcName++;
|
|
|
|
BOOLEAN resolve_exp = memcmp(ProcName, "EXP+", 4) == 0;
|
|
if (resolve_exp)
|
|
ProcName += 4;
|
|
|
|
dos_hdr = Buffer;
|
|
|
|
if (dos_hdr->e_magic != 'MZ' && dos_hdr->e_magic != 'ZM')
|
|
return 0;
|
|
nt_hdrs = (IMAGE_NT_HEADERS*)((BYTE*)dos_hdr + dos_hdr->e_lfanew);
|
|
if (nt_hdrs->Signature != IMAGE_NT_SIGNATURE) // 'PE\0\0'
|
|
return 0;
|
|
|
|
if (nt_hdrs->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) {
|
|
|
|
IMAGE_NT_HEADERS32* nt_hdrs_32 = (IMAGE_NT_HEADERS32*)nt_hdrs;
|
|
IMAGE_OPTIONAL_HEADER32* opt_hdr_32 = &nt_hdrs_32->OptionalHeader;
|
|
|
|
if (opt_hdr_32->NumberOfRvaAndSizes) {
|
|
|
|
IMAGE_DATA_DIRECTORY* dir0 = &opt_hdr_32->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
|
|
proc = FindDllExport2(ReadDll, hProcess, DllBase, dir0, ProcName, nt_hdrs_32);
|
|
}
|
|
}
|
|
|
|
else if (nt_hdrs->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) {
|
|
|
|
IMAGE_NT_HEADERS64* nt_hdrs_64 = (IMAGE_NT_HEADERS64*)nt_hdrs;
|
|
IMAGE_OPTIONAL_HEADER64* opt_hdr_64 = &nt_hdrs_64->OptionalHeader;
|
|
|
|
if (opt_hdr_64->NumberOfRvaAndSizes) {
|
|
|
|
IMAGE_LOAD_CONFIG_DIRECTORY64 LoadConfig;
|
|
|
|
IMAGE_DATA_DIRECTORY* dir10 = &opt_hdr_64->DataDirectory[IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG];
|
|
if (resolve_ec && dir10->VirtualAddress && dir10->Size >= FIELD_OFFSET(IMAGE_LOAD_CONFIG_DIRECTORY64, CHPEMetadataPointer) + sizeof(ULONGLONG)) {
|
|
|
|
status = ReadDll(hProcess, FindImagePosition(dir10->VirtualAddress, nt_hdrs_64, DllBase), &LoadConfig, min(sizeof(LoadConfig), dir10->Size), NULL);
|
|
}
|
|
|
|
typedef struct _DYN_RELOC_TABLE {
|
|
ULONG Unknown1;
|
|
ULONG Unknown2;
|
|
ULONG Unknown3;
|
|
ULONG Unknown4;
|
|
ULONG TableSize;
|
|
UCHAR Entries[];
|
|
} DYN_RELOC_TABLE;
|
|
|
|
DYN_RELOC_TABLE* DynamicValueRelocTable = NULL;
|
|
|
|
if (DllBase == 0 && (resolve_ec || resolve_exp)) { // only for images on disk, on live images we take the actually used export directory
|
|
|
|
PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION(nt_hdrs);
|
|
nt_hdrs->FileHeader.NumberOfSections;
|
|
|
|
section += (LoadConfig.DynamicValueRelocTableSection - 1);
|
|
|
|
DWORD64 pos = FindImagePosition(section->VirtualAddress, nt_hdrs_64, DllBase);
|
|
status = ReadDll(hProcess, pos, Buffer2, min(sizeof(Buffer2), section->Misc.VirtualSize), NULL);
|
|
|
|
DynamicValueRelocTable = (DYN_RELOC_TABLE*)(Buffer2 + LoadConfig.DynamicValueRelocTableOffset);
|
|
|
|
|
|
for (UCHAR* TablePtr = DynamicValueRelocTable->Entries; TablePtr < DynamicValueRelocTable->Entries + DynamicValueRelocTable->TableSize; ) {
|
|
|
|
struct {
|
|
ULONG Offset;
|
|
ULONG Size;
|
|
} *Section = TablePtr;
|
|
TablePtr += 8;
|
|
Section->Size -= 8;
|
|
|
|
for (UCHAR* EntryPtr = TablePtr; TablePtr < EntryPtr + Section->Size; ) {
|
|
|
|
struct {
|
|
USHORT
|
|
RVA : 12,
|
|
Unknown : 1,
|
|
Size : 3;
|
|
} *Entry = TablePtr;
|
|
TablePtr += 2;
|
|
|
|
ULONGLONG Value = 0;
|
|
memcpy(&Value, TablePtr, Entry->Size);
|
|
TablePtr += Entry->Size;
|
|
|
|
ULONG RVA = Section->Offset + Entry->RVA;
|
|
|
|
// Apply the value relocs to our header buffer
|
|
if (RVA < sizeof(Buffer)) {
|
|
|
|
//DbgPrintf("%08x -> %08x\n", RVA, (ULONG)Value);
|
|
memcpy(Buffer + RVA, &Value, Entry->Size);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
IMAGE_DATA_DIRECTORY* dir0 = &opt_hdr_64->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
|
|
|
|
proc = FindDllExport2(ReadDll, hProcess, DllBase, dir0, ProcName, nt_hdrs_64);
|
|
|
|
if(resolve_ec && LoadConfig.CHPEMetadataPointer) {
|
|
if (DllBase == 0) // file on disk
|
|
LoadConfig.CHPEMetadataPointer = FindImagePosition((DWORD)(LoadConfig.CHPEMetadataPointer - opt_hdr_64->ImageBase), nt_hdrs_64, DllBase);
|
|
proc = ResolveWoWRedirection64(ReadDll, hProcess, DllBase, proc, LoadConfig.CHPEMetadataPointer, nt_hdrs_64);
|
|
}
|
|
}
|
|
}
|
|
|
|
return proc;
|
|
}
|
|
|
|
NTSTATUS __stdcall ReadLocalMemory(
|
|
_In_ HANDLE Unused,
|
|
_In_opt_ DWORD64 BaseAddress,
|
|
_Out_writes_bytes_(BufferSize) PVOID Buffer,
|
|
_In_ SIZE_T BufferSize,
|
|
_Out_opt_ PSIZE_T pNumberOfBytesRead)
|
|
{
|
|
memcpy(Buffer, (void*)BaseAddress, BufferSize);
|
|
if (pNumberOfBytesRead) *pNumberOfBytesRead = BufferSize;
|
|
return 0;
|
|
}
|
|
|
|
DWORD64 FindDllExport(DWORD64 DllBase, const char* ProcName)
|
|
{
|
|
return FindDllExport_impl(ReadLocalMemory, NULL, DllBase, ProcName);
|
|
}
|
|
|
|
DWORD64 FindRemoteDllExport(HANDLE hProcess, DWORD64 DllBase, const char* ProcName)
|
|
{
|
|
if (NtReadVirtualMemory64 == NULL)
|
|
return -1;
|
|
|
|
return FindDllExport_impl(NtReadVirtualMemory64, hProcess, DllBase, ProcName);
|
|
}
|
|
|
|
NTSTATUS __stdcall ReadDllFile(
|
|
_In_ HANDLE FileHandle,
|
|
_In_opt_ DWORD64 BaseAddress,
|
|
_Out_writes_bytes_(BufferSize) PVOID Buffer,
|
|
_In_ SIZE_T BufferSize,
|
|
_Out_opt_ PSIZE_T pNumberOfBytesRead)
|
|
{
|
|
LARGE_INTEGER pos;
|
|
pos.QuadPart = BaseAddress;
|
|
SetFilePointerEx(FileHandle, pos, NULL, FILE_BEGIN);
|
|
DWORD NumberOfBytesRead;
|
|
BOOL ret = ReadFile(FileHandle, Buffer, (DWORD)BufferSize, &NumberOfBytesRead, NULL);
|
|
if (pNumberOfBytesRead) *pNumberOfBytesRead = NumberOfBytesRead;
|
|
return ret ? 0 : 0xC0000001L;
|
|
}
|
|
|
|
DWORD64 FindDllExportFromFile(const WCHAR* dll, const char* ProcName)
|
|
{
|
|
DWORD64 proc;
|
|
HANDLE hFile = CreateFileW(dll, GENERIC_READ, (FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE), NULL, OPEN_EXISTING, 0, NULL);
|
|
if (hFile == INVALID_HANDLE_VALUE)
|
|
return 0;
|
|
|
|
proc = FindDllExport_impl(ReadDllFile, hFile, 0, ProcName);
|
|
|
|
CloseHandle(hFile);
|
|
return proc;
|
|
}
|