|
- #include <cstdio>
- #include <ntdll.h>
- #include <cstdint>
-
- #include "structs.h"
- #include "wow64ext.h"
- #include "misc.h"
- #include "syscall64.h"
- #include "get_syscall64_ids.h"
-
- /**
- \file
- */
-
- /**
- \brief All functions doing direct syscalls are saved with a hash of their name
- and the accompying syscall ID in the table (API_TO_INDEX*) ID_table.
- To get the ID for a given hash the table can be searched.
- (To speed up the search the table is sorted and a binary search is used)
- */
- struct API_TO_INDEX
- {
- DWORD hash;
- DWORD id;
- } *ID_table = NULL;
- DWORD ID_table_count = 0; //!< Count of entries in ID_table
-
- /**
- \brief Parses the ID out of a x64 function.
- \return Returns the ID or INVALID_SYSCALL_ID on err
- */
- static DWORD get_syscall_id(LPBYTE function)
- {
- if(!function)
- return INVALID_SYSCALL_ID;
-
- /*
- 00000000`77b61800 4c8bd1 mov r10,rcx
- 00000000`77b61803 b852000000 mov eax,52h
- 00000000`77b61808 0f05 syscall
-
- On Windows 10 it's:
- 0:000> u NtOpenFile
- ntdll!NtOpenFile:
- 00007ffe`62bf6720 4c8bd1 mov r10,rcx
- 00007ffe`62bf6723 b833000000 mov eax,33h
- 00007ffe`62bf6728 f604250803fe7f01 test byte ptr [SharedUserData+0x308 (00000000`7ffe0308)],1
- 00007ffe`62bf6730 7503 jne ntdll!NtOpenFile+0x15 (00007ffe`62bf6735)
- 00007ffe`62bf6732 0f05 syscall
- 00007ffe`62bf6734 c3 ret
- 00007ffe`62bf6735 cd2e int 2Eh
- 00007ffe`62bf6737 c3 ret
- */
-
- const unsigned char MOV_R10_RCX_OPCODE[] = {0x4c, 0x8b, 0xd1};
- if(memcmp(function, MOV_R10_RCX_OPCODE, sizeof(MOV_R10_RCX_OPCODE)))
- return INVALID_SYSCALL_ID;
-
- const unsigned char SYSCALL_OPCODE[] = {0x0f, 0x05};
- const size_t SYSCALL_OFFSET_OLD = 8,
- SYSCALL_OFFSET_10 = 0x12;
- uint8_t* old = function + SYSCALL_OFFSET_OLD;
- uint8_t* win10 = function + SYSCALL_OFFSET_10;
- if (memcmp(old, SYSCALL_OPCODE, sizeof(SYSCALL_OPCODE)) &&
- memcmp(win10, SYSCALL_OPCODE, sizeof(SYSCALL_OPCODE)))
- {
- return INVALID_SYSCALL_ID;
- }
-
- const unsigned char MOV_EAX_OPCODE = 0xB8;
- const size_t MOV_EAX_ID_OFFSET = 3;
- if(MOV_EAX_OPCODE != *(function + MOV_EAX_ID_OFFSET))
- return INVALID_SYSCALL_ID;
-
- return *(PDWORD)(function + MOV_EAX_ID_OFFSET + 1);
- }
-
-
- /**
- \brief Parses the given image to get the ID or just count them.
- \return Number of entries found / populated
- \param imageBase Image base of the DLL the info will be taken from
- \param ids The struct the info is put into. If NULL - won't be touched
- */
- static DWORD get_syscall_ids(LPVOID imageBase, API_TO_INDEX* ids)
- {
- if(!imageBase)
- return 0;
-
- PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)imageBase;
- // not a valid DOS header
- if(IMAGE_DOS_SIGNATURE != dos->e_magic)
- return 0;
-
- PIMAGE_NT_HEADERS64 nt = (PIMAGE_NT_HEADERS64)((LPBYTE)imageBase + dos->e_lfanew);
- // not a valid PE or not the correct architecture
- if(IMAGE_NT_SIGNATURE != nt->Signature || IMAGE_FILE_MACHINE_AMD64 != nt->FileHeader.Machine)
- {
- return 0;
- }
-
- // No exports?
- if(!nt->OptionalHeader.DataDirectory->Size)
- return 0;
-
- DWORD count = 0; //! Number of functions who do direct syscalls
- IMAGE_EXPORT_DIRECTORY *exportDir = (IMAGE_EXPORT_DIRECTORY *)((LPBYTE)imageBase + nt->OptionalHeader.DataDirectory->VirtualAddress);
- PDWORD nameRef = (DWORD *)((LPBYTE)imageBase + exportDir->AddressOfNames);
- WORD* ordinal = (WORD *)((LPBYTE)imageBase + exportDir->AddressOfNameOrdinals);
- DWORD* addressOfFunctions = (DWORD*)((LPBYTE)imageBase + exportDir->AddressOfFunctions);
-
- printf("Total functions: %d\n", exportDir->NumberOfNames);
- for(DWORD i = 0; i < exportDir->NumberOfNames; i++, nameRef++)
- {
- const char* name = (const char*)((LPBYTE)imageBase + (*nameRef));
- LPBYTE address = (LPBYTE)imageBase + addressOfFunctions[ordinal[i]];
-
- DWORD id = get_syscall_id(address);
- if(INVALID_SYSCALL_ID == id)
- continue;
-
- // Put it into the table
- if(ids)
- {
- ids[count].hash = hash(name);
- ids[count].id = id;
- }
- // Print info
- else
- {
- printf("%s (%X) = %X\n", name, hash(name), id);
- }
- count++;
- }
-
- return count;
- }
-
- /**
- \brief (Bubble)Sorts the ID_table so it can be searched via binary search
- */
- static void sort_ID_table()
- {
- DWORD n = ID_table_count;
-
- bool swapped = false;
- do
- {
- for(unsigned int i = 0; i < n - 1; ++i)
- {
- if(ID_table[i].hash > ID_table[i + 1].hash)
- {
- API_TO_INDEX tmp = {ID_table[i].hash, ID_table[i].id};
-
- ID_table[i].hash = ID_table[i + 1].hash;
- ID_table[i].id = ID_table[i + 1].id;
-
- ID_table[i + 1].hash = tmp.hash;
- ID_table[i + 1].id = tmp.id;
-
- swapped = true;
- }
- }
- n = n - 1;
- } while(swapped == true && n);
- }
-
- /**
- \brief Gets the path to the x64 ntdll in native format
- \param path Output buffer
- */
- static void get_ntdll_path(wchar_t* path)
- {
- wchar_t systemDirectory[MAX_PATH];
-
- if(!path)
- return;
-
- GetSystemDirectory(systemDirectory, _countof(systemDirectory));
-
- wsprintfW(path, L"\\??\\%s\\ntdll.dll", systemDirectory);
- }
- /**
- */
- static BOOL read_ntdll(LPBYTE& content, DWORD& size)
- {
- wchar_t ntdllPath[MAX_PATH]; //! Path in the native format
- get_ntdll_path(ntdllPath);
- printf("%ls\n", ntdllPath);
-
- _UNICODE_STRING_T<DWORD64> filename = {lstrlenW(ntdllPath) * sizeof(WCHAR), MAX_PATH * sizeof(WCHAR), (DWORD64)ntdllPath};
- _OBJECT_ATTRIBUTES_T<DWORD64> obja = {sizeof(_OBJECT_ATTRIBUTES_T<DWORD64>), NULL, (DWORD64)&filename, OBJ_CASE_INSENSITIVE, NULL, NULL};
- _IO_STATUS_BLOCK_T<DWORD64> iostatusblock = {0};
-
- _HANDLE_T<DWORD64> fileHandle = {(DWORD64)INVALID_HANDLE_VALUE};
- NTSTATUS stat = DO_SYSCALL(get_basic_syscall_ID(NTOPENFILE), &fileHandle,
- FILE_READ_DATA | SYNCHRONIZE, &obja, &iostatusblock, FILE_SHARE_READ,
- FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT);
- if(STATUS_SUCCESS != stat)
- {
- printf("NTSTATUS: %X\tHandle: %llX\n", stat, fileHandle.h);
- return FALSE;
- }
- printf("Handle: %llX\n", fileHandle.h);
-
- if(!(size = GetFileSize((HANDLE)fileHandle.h, NULL)))
- {
- DO_SYSCALL(get_basic_syscall_ID(NTCLOSE), fileHandle.h);
- return FALSE;
- }
- printf("Size of ntdll: %dkb\n", size / 1024);
-
- /* As no code is actually executed in the new ntdll - but only
- a few bytes are read from it - rw is enough */
- if(!(content = (LPBYTE)VirtualAlloc(NULL, size, MEM_COMMIT | MEM_RESERVE,
- PAGE_READWRITE)))
- {
- DO_SYSCALL(get_basic_syscall_ID(NTCLOSE), fileHandle.h);
- return FALSE;
- }
- printf("content %p\n", content);
-
- printf("ios ptr %X", &iostatusblock);
- LARGE_INTEGER offset = {0};
- stat = DO_SYSCALL(get_basic_syscall_ID(NTREADFILE),
- (HANDLE)fileHandle.h,
- NULL, // event
- NULL, // ApcRoutine
- NULL, // ApcContext
- &iostatusblock,
- content,
- size,
- &offset,
- NULL); // key
- if(STATUS_SUCCESS != stat)
- {
- VirtualFree(content, 0, MEM_RELEASE);
- content = NULL;
- DO_SYSCALL(get_basic_syscall_ID(NTCLOSE), fileHandle.h);
- printf("Reading failed: %X\t%X != %X\n", stat, size, offset.LowPart);
- return FALSE;
- }
- printf("ios: %X\toffset: %X\n", iostatusblock.Status, offset.LowPart);
-
- DO_SYSCALL(get_basic_syscall_ID(NTCLOSE), fileHandle.h);
- return TRUE;
- }
-
- /**
- \brief (Partly) maps the image.
-
- This does NOT fix relocations, imports, TLS, sets section protections yadda yadda.
- As this image is only used to parse out the IDs that is no problem.
- \param content Raw data of the file to be mapped
- \param mappedImage The finished image
- */
- static BOOL map_PE(LPBYTE content, LPBYTE* mappedImage)
- {
- PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)content;
- if(IMAGE_DOS_SIGNATURE != dos->e_magic)
- {
- printf("Not a valid DOS header: %s\n", content);
- return FALSE;
- }
-
- PIMAGE_NT_HEADERS64 nt = (PIMAGE_NT_HEADERS64)(content + dos->e_lfanew);
- if(IMAGE_NT_SIGNATURE != nt->Signature)
- {
- printf("Not a valid PE header\n");
- return FALSE;
- }
-
- /* Must be mapped properly (Actually almost -
- only the code section would be needed) so the IDs can be be
- parsed properly */
- if(!(*mappedImage = (LPBYTE)VirtualAlloc(NULL, nt->OptionalHeader.SizeOfImage, MEM_COMMIT | MEM_RESERVE,
- PAGE_READWRITE)))
- {
- printf("Can't alloc\n");
- return FALSE;
- }
-
- printf("Let's map that shit @ %p. %X sections. %X imageSize\n", *mappedImage,
- nt->FileHeader.NumberOfSections, nt->OptionalHeader.SizeOfImage);
-
- // Copy up to the first section (Size of optional header + delta optional header to start of image)
- memcpy(*mappedImage, content, nt->FileHeader.SizeOfOptionalHeader + ((LPBYTE)&nt->OptionalHeader - (LPBYTE)content));
-
- // Copy sections
- #define IMAGE_FIRST_SECTION64( ntheader ) ((PIMAGE_SECTION_HEADER) \
- ((ULONG_PTR)(ntheader)+\
- FIELD_OFFSET(IMAGE_NT_HEADERS64, OptionalHeader) + \
- ((ntheader))->FileHeader.SizeOfOptionalHeader \
- ))
- PIMAGE_SECTION_HEADER pish = IMAGE_FIRST_SECTION64(nt);
- for(unsigned int i = 0; i < nt->FileHeader.NumberOfSections; i++)
- {
- printf("Copy %Xbytes @ RVA %X From %X\n", pish->SizeOfRawData, pish->VirtualAddress, pish->PointerToRawData);
- memcpy((*mappedImage + pish->VirtualAddress), (content + pish->PointerToRawData),
- pish->SizeOfRawData);
- pish++;
- }
-
- return TRUE;
- }
-
- /**
- \brief Initalize the ID_table.
- */
- BOOL initalize_ID_table()
- {
- LPBYTE ntdll = NULL, imageBase = NULL;
- DWORD ntdllSize = 0;
- if(!read_ntdll(ntdll, ntdllSize))
- return FALSE;
- if(!map_PE(ntdll, &imageBase))
- return FALSE;
-
- ID_table_count = get_syscall_ids(imageBase, NULL);
- printf("%i functions who do direct syscalls\n", ID_table_count);
- if(!ID_table_count)
- return FALSE;
-
- if(!(ID_table = (API_TO_INDEX*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, ID_table_count * sizeof(API_TO_INDEX))))
- return FALSE;
-
- if(!get_syscall_ids(imageBase, ID_table))
- return FALSE;
-
- VirtualFree(ntdll, 0, MEM_RELEASE);
- VirtualFree(imageBase, 0, MEM_RELEASE);
-
- sort_ID_table();
-
- return TRUE;
- }
-
- /**
- \brief Has a few hardcoded IDs and returns the correct one for the current OS
-
- Which IDs exactly are hardcoded can be seen in the enum.
- Table made mostly with data from:
- x86: http://j00ru.vexillium.org/ntapi/
- WOW64: http://j00ru.vexillium.org/ntapi_64/
-
- \fixme >=Win8 Support?
- */
- DWORD get_basic_syscall_ID(SYSCALL_IDS func)
- {
- _KUSER_SHARED_DATA* _kuser_s_d = GET_KUSER_SHARED_DATA();
- ULONG majorVersion = _kuser_s_d->NtMajorVersion;
- ULONG minorVersion = _kuser_s_d->NtMinorVersion;
- NT_PRODUCT_TYPE productType = _kuser_s_d->NtProductType;
- _PEB* p = (_PEB*)__readfsdword(0x30);
- ULONG buildID = p->NtBuildNumber;
-
- switch(majorVersion)
- {
- case 5:
- // XP
- if(1 == minorVersion ||
- (2 == minorVersion && VER_NT_WORKSTATION == productType))
- {
- if(NTOPENFILE == func)
- return 0x30;
- else if(NTCREATEFILE == func)
- return 0x52;
- else if(NTREADFILE == func)
- return 0x03;
- else if(NTCLOSE == func)
- return 0x0C;
- }
- // Server 2003
- else
- {
- printf("SRV03 unsupported\n"); // fixme
- if(NTOPENFILE == func)
- return 0x7a;
- else if(NTCREATEFILE == func)
- return 0x26;
- else if(NTREADFILE == func)
- return 0xbf;
- else if(NTCLOSE == func)
- return 0x1b;
- }
- break;
- case 6:
- switch(minorVersion)
- {
- // Vista SP0-2 & Server 2008
- case 0:
- printf("Vista unsupported\n"); // fixme
- if(NTOPENFILE == func)
- return 0xba;
- else if(NTCREATEFILE == func)
- return 0x3c;
- else if(NTREADFILE == func)
- return 0x102;
- else if(NTCLOSE == func)
- return 0x30;
- break;
- // Win7
- case 1:
- if(NTOPENFILE == func)
- return 0x30;
- else if(NTCREATEFILE == func)
- return 0x52;
- else if(NTREADFILE == func)
- return 0x03;
- else if(NTCLOSE == func)
- return 0x0C;
- // Win 8
- case 2:
- printf("Win8 unsupported\n"); // fixme
- if(NTOPENFILE == func)
- return 0xe7;
- else if(NTCREATEFILE == func)
- return 0x15f;
- else if(NTREADFILE == func)
- return 0x86;
- else if(NTCLOSE == func)
- return 0x0171;
- break;
- }
- break;
- // Win 10
- case 10:
- if (NTOPENFILE == func)
- return 0x0033;
- else if (NTCREATEFILE == func)
- return 0x55;
- else if (NTREADFILE == func)
- return 0x06;
- else if (NTCLOSE == func)
- return 0x0f;
- }
-
- return INVALID_SYSCALL_ID;
- }
-
- DWORD get_syscall_ID(DWORD func)
- {
- DWORD left = 0, right = ID_table_count;
-
- while(left != right)
- {
- DWORD middle = (left + right) / 2;
-
- if(func == ID_table[middle].hash)
- return ID_table[middle].id;
- if(func > ID_table[middle].hash)
- left = middle + 1;
- else
- right = middle - 1;
- }
-
- if(func == ID_table[left].hash)
- {
- return ID_table[left].id;
- }
-
- return INVALID_SYSCALL_ID;
- }
-
- VOID destroy_ID_table()
- {
- HeapFree(GetProcessHeap(), 0, ID_table);
- }
-
- #if 0
- BOOL test_id_table()
- {
- LPVOID imageBase = GetModuleHandle(TEXT("ntdll.dll"));
-
- PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)imageBase;
- // not a valid DOS header
- if(IMAGE_DOS_SIGNATURE != dos->e_magic)
- return FALSE;
-
- PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)((LPBYTE)imageBase + dos->e_lfanew);
- // not a valid PE or not the correct architecture
- if(IMAGE_NT_SIGNATURE != nt->Signature || IMAGE_FILE_MACHINE_I386 != nt->FileHeader.Machine)
- {
- return FALSE;
- }
-
- // No exports?
- if(!nt->OptionalHeader.DataDirectory->Size)
- return FALSE;
-
- IMAGE_EXPORT_DIRECTORY *exportDir = (IMAGE_EXPORT_DIRECTORY *)((LPBYTE)imageBase + nt->OptionalHeader.DataDirectory->VirtualAddress);
- PDWORD nameRef = (DWORD *)((LPBYTE)imageBase + exportDir->AddressOfNames);
- WORD* ordinal = (WORD *)((LPBYTE)imageBase + exportDir->AddressOfNameOrdinals);
- DWORD* addressOfFunctions = (DWORD*)((LPBYTE)imageBase + exportDir->AddressOfFunctions);
-
- for(DWORD i = 0; i < exportDir->NumberOfNames; i++, nameRef++)
- {
- const char* name = (const char*)((LPBYTE)imageBase + (*nameRef));
- LPBYTE address = (LPBYTE)imageBase + addressOfFunctions[ordinal[i]];
-
- DWORD id = get_syscall_id(address);
- if(INVALID_SYSCALL_ID == id)
- continue;
-
- // At address is a function doing direct syscalls - check if can be found
- if(INVALID_SYSCALL_ID == get_syscall_ID(hash(name)))
- {
- printf("%s not found", name);
- return FALSE;
- }
- }
-
- return TRUE;
- }
-
- #endif
|