The parser::directory policy resolves syscall numbers by analyzing ntdll.dll’s metadata structures. On x64, it uses the exception directory (.pdata) mapped to the export table. On x86, it sorts Zw* functions by their memory addresses.
if constexpr (platform::isWindows64){ auto uExceptionDirRva = module.m_pNtHeaders->OptionalHeader .DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION].VirtualAddress; if (!uExceptionDirRva) return vecFoundSyscalls; auto pRuntimeFunctions = reinterpret_cast<PIMAGE_RUNTIME_FUNCTION_ENTRY>( module.m_pModuleBase + uExceptionDirRva); auto uExceptionDirSize = module.m_pNtHeaders->OptionalHeader .DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION].Size; auto uFunctionCount = uExceptionDirSize / sizeof(IMAGE_RUNTIME_FUNCTION_ENTRY); auto pFunctionsRVA = reinterpret_cast<uint32_t*>( module.m_pModuleBase + module.m_pExportDir->AddressOfFunctions); auto pNamesRVA = reinterpret_cast<uint32_t*>( module.m_pModuleBase + module.m_pExportDir->AddressOfNames); auto pOrdinalsRva = reinterpret_cast<uint16_t*>( module.m_pModuleBase + module.m_pExportDir->AddressOfNameOrdinals); // Build RVA -> Name mapping std::unordered_map<uint32_t, const char*> mapRvaToName; for (uint32_t i = 0; i < module.m_pExportDir->NumberOfNames; ++i) { const char* szName = reinterpret_cast<const char*>( module.m_pModuleBase + pNamesRVA[i]); uint16_t uOrdinal = pOrdinalsRva[i]; uint32_t uFunctionRva = pFunctionsRVA[uOrdinal]; mapRvaToName[uFunctionRva] = szName; } // Iterate exception directory in order uint32_t uSyscallNumber = 0; for (DWORD i = 0; i < uFunctionCount; ++i) { auto pFunction = &pRuntimeFunctions[i]; if (pFunction->BeginAddress == 0) break; auto it = mapRvaToName.find(pFunction->BeginAddress); if (it != mapRvaToName.end()) { const char* szName = it->second; // Check if name starts with "Zw" if (hashing::calculateHashRuntime(szName, 2) == hashing::calculateHash("Zw")) { // Convert Zw* to Nt* char szNtName[128]; std::copy_n(szName, sizeof(szNtName)-1, szNtName); szNtName[0] = 'N'; szNtName[1] = 't'; const SyscallKey_t key = SYSCALL_ID_RT(szNtName); vecFoundSyscalls.push_back(SyscallEntry_t{ key, uSyscallNumber, 0 }); uSyscallNumber++; } } }}
How it works:
Access the exception directory (PDATA section)
Build a map of function RVAs to export names
Iterate through the runtime function entries in order
Match each function RVA to its export name
Filter for Zw* functions (user-mode syscall stubs)
Assign incrementing syscall numbers based on order in exception directory
Convert Zw* names to Nt* for consistency
The exception directory on x64 Windows is ordered by syscall number, making this method highly reliable and resilient to hooks (since it doesn’t read function code).