Overview
The parser::signature policy resolves syscall numbers by scanning function prologues for the signature mov r10, rcx; mov eax, <syscall_number> (x64) or mov eax, <syscall_number> (x86). It includes hook detection and can resolve syscall numbers from neighboring functions when a target is hooked.
Method Signature
parse
static std::vector<SyscallEntry_t>
Parses a module to extract syscall numbers via signature scanning static std :: vector < SyscallEntry_t > parse ( const ModuleInfo_t & module )
module
const ModuleInfo_t&
required
Module information structure containing base address, NT headers, and export directory
return
std::vector<SyscallEntry_t>
Vector of syscall entries with keys, syscall numbers, and offsets
Implementation
Signature Scanning
From syscall.hpp:455-551:
static std :: vector < SyscallEntry_t > parse ( const ModuleInfo_t & module )
{
std ::vector < SyscallEntry_t > vecFoundSyscalls;
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 );
for ( uint32_t i = 0 ; i < module . m_pExportDir -> NumberOfNames ; i ++ )
{
const char * szName = reinterpret_cast < const char *> (
module . m_pModuleBase + pNamesRVA [i]);
// Only process Nt* functions
if ( hashing :: calculateHashRuntime (szName, 2 ) != hashing :: calculateHash ( "Nt" ))
continue ;
uint16_t uOrdinal = pOrdinalsRva [i];
uint32_t uFunctionRva = pFunctionsRVA [uOrdinal];
// Skip forwarders
auto pExportSectionStart = module . m_pNtHeaders -> OptionalHeader
. DataDirectory [IMAGE_DIRECTORY_ENTRY_EXPORT]. VirtualAddress ;
auto pExportSectionEnd = pExportSectionStart +
module . m_pNtHeaders -> OptionalHeader
. DataDirectory [IMAGE_DIRECTORY_ENTRY_EXPORT]. Size ;
if (uFunctionRva >= pExportSectionStart && uFunctionRva < pExportSectionEnd)
continue ;
uint8_t * pFunctionStart = module .m_pModuleBase + uFunctionRva;
uint32_t uSyscallNumber = 0 ;
bool bSyscallFound = false ;
// x64 signature: 4C 8B D1 B8 <syscall_number>
if constexpr ( platform ::isWindows64)
{
if ( * reinterpret_cast < uint32_t *> (pFunctionStart) == 0x B8D18B4C )
{
uSyscallNumber = * reinterpret_cast < uint32_t *> (pFunctionStart + 4 );
bSyscallFound = true ;
}
}
// x86 signature: B8 <syscall_number>
if constexpr ( platform ::isWindows32)
{
if ( * pFunctionStart == 0x B8 )
{
uSyscallNumber = * reinterpret_cast < uint32_t *> (pFunctionStart + 1 );
bSyscallFound = true ;
}
}
// Hook detection and halo gates (x64 only)
if constexpr ( platform ::isWindows64)
{
if ( isFunctionHooked (pFunctionStart) && ! bSyscallFound)
{
// Search neighboring functions...
}
}
if (bSyscallFound)
{
const SyscallKey_t key = SYSCALL_ID_RT (szName);
vecFoundSyscalls . push_back ( SyscallEntry_t {
key, uSyscallNumber, 0
});
}
}
return vecFoundSyscalls;
}
Hook Detection
The isFunctionHooked method detects common hooking patterns:
From syscall.hpp:554-598:
static bool isFunctionHooked ( const uint8_t* pFunctionStart )
{
const uint8_t * pCurrent = pFunctionStart;
// Skip leading NOPs
while ( * pCurrent == 0x 90 )
pCurrent ++ ;
switch ( pCurrent [ 0 ])
{
// JMP rel32
case 0x E9 :
// JMP rel8
case 0x EB :
// PUSH imm32
case 0x 68 :
return true ;
// JMP [mem] / JMP [rip + offset]
case 0x FF :
if ( pCurrent [ 1 ] == 0x 25 )
return true ;
break ;
// INT3
case 0x CC :
return true ;
// UD2
case 0x 0F :
if ( pCurrent [ 1 ] == 0x 0B )
return true ;
break ;
// INT 0x3
case 0x CD :
if ( pCurrent [ 1 ] == 0x 03 )
return true ;
break ;
default :
break ;
}
return false ;
}
Detected patterns:
Pattern Bytes Description JMP rel32 E9 <offset>Relative jump (far) JMP rel8 EB <offset>Relative jump (short) PUSH + RET 68 <addr>Trampoline hook JMP [mem] FF 25 <offset>Indirect jump INT3 CCBreakpoint UD2 0F 0BIllegal instruction INT 3 CD 03Software interrupt
Halo Gates (x64 Only)
When a function is hooked, the signature parser uses halo gates (also known as HalosGate or TartarusGate ) to resolve the syscall number from neighboring functions:
From syscall.hpp:502-539:
if ( isFunctionHooked (pFunctionStart) && ! bSyscallFound)
{
// Search up (previous functions)
for ( int j = 1 ; j < 20 ; ++ j)
{
uint8_t * pNeighborFunc = pFunctionStart - (j * 0x 20 );
if ( reinterpret_cast < uintptr_t > (pNeighborFunc) <
reinterpret_cast < uintptr_t > ( module . m_pModuleBase ))
break ;
if ( * reinterpret_cast < uint32_t *> (pNeighborFunc) == 0x B8D18B4C )
{
uint32_t uNeighborSyscall = * reinterpret_cast < uint32_t *> (pNeighborFunc + 4 );
uSyscallNumber = uNeighborSyscall + j; // Add offset
bSyscallFound = true ;
break ;
}
}
// Search down (next functions)
if ( ! bSyscallFound)
{
for ( int j = 1 ; j < 20 ; ++ j)
{
uint8_t * pNeighborFunc = pFunctionStart + (j * 0x 20 );
if ( reinterpret_cast < uintptr_t > (pNeighborFunc) >
( reinterpret_cast < uintptr_t > ( module . m_pModuleBase ) +
module . m_pNtHeaders -> OptionalHeader . SizeOfImage ))
break ;
if ( * reinterpret_cast < uint32_t *> (pNeighborFunc) == 0x B8D18B4C )
{
uint32_t uNeighborSyscall = * reinterpret_cast < uint32_t *> (pNeighborFunc + 4 );
uSyscallNumber = uNeighborSyscall - j; // Subtract offset
bSyscallFound = true ;
break ;
}
}
}
}
How it works:
If a function is hooked (signature not found), search neighboring functions
Search upward : Check the previous 20 functions (at 0x20-byte intervals)
If an unhooked neighbor is found, calculate: syscall_number = neighbor_syscall + distance
Search downward : If upward search fails, check the next 20 functions
Calculate: syscall_number = neighbor_syscall - distance
This technique assumes syscalls are laid out sequentially in memory at 0x20-byte intervals, which is true for ntdll.dll on x64.
Advantages
Hook Resilience Can resolve syscall numbers even when the target function is hooked, using neighboring unhooked functions.
Direct Reading Reads syscall numbers directly from function prologues, no inference needed.
Platform Support Works on both x86 and x64 (though halo gates are x64-only).
Fallback Parser Ideal as a fallback when directory-based parsing fails.
Usage
This parser is used in the DefaultParserChain as a fallback:
using DefaultParserChain = syscall :: ParserChain_t <
syscall :: policies :: parser ::directory, // Primary
syscall :: policies :: parser ::signature // Fallback
> ;
You can also use it as the primary parser:
using SignatureOnlyManager = syscall ::Manager <
syscall :: policies :: allocator ::section,
syscall :: policies :: generator ::direct,
syscall :: policies :: parser ::signature
> ;
SignatureOnlyManager manager;
manager . initialize ();
Limitations
x86 Halo Gates: The halo gates technique is only implemented for x64. On x86, if a function is hooked and the signature is not found, that syscall will be skipped.Heavy Hooking: If many consecutive functions are hooked (more than 20 in a row), halo gates may fail to find an unhooked neighbor.
Trade-offs
Aspect Description Resilience High (hook detection + halo gates) Performance Slower (scans all exports) Reliability Medium (depends on function layout) Platform x86 and x64 (halo gates x64 only)
See Also
parser::directory Directory-based parser (primary method)
ParserChain_t Learn about parser fallback chains