Overview
xAnalyzer’s function tracking feature enables the plugin to predict and identify indirect function calls that don’t directly name their target. By tracking MOV instructions that manipulate function pointers, xAnalyzer can resolve calls to registers and dynamic pointers, providing full API information even for obfuscated calling patterns.
This feature is controlled by the “Track Undefined Functions” option in the configuration menu. When enabled, it dramatically improves analysis accuracy for code using indirect calls.
What Are Indirect Calls?
Indirect calls don’t specify the target function directly in the instruction:
Direct Call (Easy)
Indirect Call via Register
Indirect Call via Memory
Indirect Call with Offset
call kernel32.CreateFileW ; Target is explicit
How Function Tracking Works
xAnalyzer maintains a vector of MOV instructions that manipulate potential function pointers:
MOV Instruction Detection
During analysis, xAnalyzer identifies MOV instructions that could involve function pointers: if ( conf . track_undef_functions && IsMovFunctPointer ( & bii, CurrentAddress)) {
INSTRUCTIONSTACK mov_inst = { 0 };
GuiGetDisassembly (CurrentAddress, szDisasmText);
strcpy_s ( mov_inst . Instruction , bii . instruction );
strcpy_s ( mov_inst . GuiInstruction , szDisasmText);
DecomposeMovInstruction ( mov_inst . GuiInstruction ,
mov_inst . DestinationRegister ,
mov_inst . SourceRegister );
vectorFunctPointer . insert ( vectorFunctPointer . begin (), mov_inst);
}
Pointer Vector Storage
Valid MOV instructions are stored in a vector (limited to 100 entries): vector < INSTRUCTIONSTACK > vectorFunctPointer; // Global stack
#define INSTRUCTION_FUNCT_POINTER_MAXSIZE 100
Indirect Call Resolution
When a CALL to a register or pointer is encountered, xAnalyzer searches the vector: bool FindFunctionFromPointer ( char * szDisasmText , char * szFunctionName ) {
duint index = 0 ;
while ( ! found && index < vectorFunctPointer . size ()) {
INSTRUCTIONSTACK inst = vectorFunctPointer . at (index);
if ( strcmp ( inst . DestinationRegister , addr) == 0 ) {
if ( strstr ( inst . SourceRegister , "<" ) != nullptr ) {
string funct = StripFunctionNamePointer ( inst . SourceRegister );
szAPIFunction = funct;
found = true ;
}
}
index ++ ;
}
return found;
}
API Definition Lookup
Once the function name is resolved, normal API analysis proceeds: if ( conf . track_undef_functions &&
FindFunctionFromPointer (szDisasmText, szFunctionName) &&
SearchApiFileForDefinition (szFunctionName, szAPIDefinition, true )) {
if ( SetFunctionParams ( & ai, szFunctionName))
SetAutoCommentIfCommentIsEmpty ( & inst, szLabelAPIFunction, ...);
}
Tracked Call Patterns
xAnalyzer can resolve various indirect calling conventions:
Register Calls
Memory Pointer Calls
Register + Displacement
Dynamic Pointer
; Store function pointer in register
mov eax , user32.MessageBoxA
push 0
push offset caption
push offset text
push 0
call eax ; ← Resolved to MessageBoxA
; xAnalyzer comments:
; HANDLE hWnd = NULL
; LPCTSTR lpText
; LPCTSTR lpCaption
; UINT uType = MB_OK
; MessageBoxA
; IAT-style indirect call
mov eax , dword ptr [ 00403000 ] ; IAT entry
call eax ; ← Function resolved from MOV
; Or with function pointer table:
mov ecx , offset func_table
mov eax , [ ecx + ebx * 4 ] ; Index into table
call eax ; ← Tracked through pointer
; Virtual function table call
mov ecx , [ ebp + 8 ] ; 'this' pointer
mov eax , [ ecx ] ; vtable pointer
call [ eax + 12 ] ; ← Virtual function call
; xAnalyzer attempts to resolve the vtable function
; GetProcAddress pattern
push offset funcName
push hModule
call GetProcAddress
; ...
call eax ; ← Dynamically loaded function
; xAnalyzer tracks the eax assignment
MOV Instruction Filtering
Not all MOV instructions are tracked. xAnalyzer filters based on:
Exclusion Criteria
bool IsMovFunctPointer ( const BASIC_INSTRUCTION_INFO * bii , duint CurrentAddress )
{
bool isMov = strstr ( bii -> instruction , "mov" ) != nullptr ;
// Exclude:
// 1. Non-MOV instructions
// 2. Function prologues/epilogues
// 3. Stack frame setup instructions
return (isMov &&
! IsProlog (bii, CurrentAddress) &&
! IsEpilog (bii));
}
✅ Tracked
mov eax, kernel32.CreateFileW
mov ecx, [00403000]
mov edx, [eax+4]
mov ebx, offset sub_401000
❌ Ignored
push ebp (prolog)
mov ebp, esp (prolog)
mov esp, ebp (epilog)
pop ebp (epilog)
Configuration
Function tracking is controlled by a configuration option:
Enabling/Disabling
Function tracking is enabled by default as of xAnalyzer 2.5.0. This feature was introduced to improve analysis of modern code that frequently uses indirect calls.
Data Structures
The tracking system uses these structures:
typedef struct stINSTRUCTIONSTACK {
duint Address; // Instruction address
char Instruction [GUI_MAX_DISASSEMBLY_SIZE]; // Raw instruction
char GuiInstruction [GUI_MAX_DISASSEMBLY_SIZE]; // Formatted display
char DestinationRegister [GUI_MAX_DISASSEMBLY_SIZE]; // mov dest
char SourceRegister [GUI_MAX_DISASSEMBLY_SIZE]; // mov source
} INSTRUCTIONSTACK ;
// Global tracking vector
vector < INSTRUCTIONSTACK > vectorFunctPointer;
// Maximum tracked instructions
#define INSTRUCTION_FUNCT_POINTER_MAXSIZE 100
Vector Size Limiting
To prevent memory issues, the tracking vector is limited:
if ( vectorFunctPointer . size () < INSTRUCTION_FUNCT_POINTER_MAXSIZE) {
vectorFunctPointer . insert ( vectorFunctPointer . begin (), mov_inst);
} else {
ClearVector (vectorFunctPointer); // Reset when full
}
Clearing Strategy
The vector is cleared at function boundaries:
// Clear at function end or epilog
if ( ! selectionAnal && (prolog || epilog)) {
ClearStack (stackInstructions);
ClearVector (vectorFunctPointer);
}
This ensures:
Memory usage stays bounded
Function pointers don’t leak across function boundaries
Analysis remains accurate per-function
Example: Resolving Complex Calls
Consider this obfuscated code:
; Function pointer manipulation
00401000 : mov ebx , kernel32.GetModuleHandleA
00401006 : xor eax , eax
00401008 : push eax
0040100A: call ebx ; ← Indirect call
; Without tracking:
; call ebx
; With function tracking enabled:
; push NULL ; LPCSTR lpModuleName = NULL
; call ebx ; GetModuleHandleA
xAnalyzer:
Detects mov ebx, kernel32.GetModuleHandleA at 401000
Stores vector entry: Address 401000, DestinationRegister “ebx”, SourceRegister “kernel32.GetModuleHandleA”
When analyzing call ebx at 40100A, searches vector for “ebx”
Finds the MOV instruction, extracts “GetModuleHandleA”
Looks up API definition and applies parameters
Benefits for Reverse Engineering
Obfuscation Resistance Defeats simple obfuscation that uses indirect calls to hide API usage
Dynamic Loading Analysis Track runtime-resolved functions loaded with GetProcAddress
Object-Oriented Code Resolve virtual function calls in C++ code with vtables
Callback Analysis Understand callback mechanisms and function pointer patterns
Malware Analysis Use Case
Many malware samples use indirect calls to evade detection:
API Hiding
; Store critical APIs in registers
mov esi , kernel32.VirtualAlloc
mov edi , kernel32.CreateThread
; ... benign code ...
call esi ; ← Allocate memory (tracked)
call edi ; ← Create thread (tracked)
Import Table Obfuscation
; Resolve imports at runtime
mov eax , [IAT_VirtualProtect]
; ... setup arguments ...
call eax ; ← Change memory protections (tracked)
Polymorphic Decryption
; Function pointer changes at runtime
mov ecx , decrypt_routine_1
test al , al
jz skip
mov ecx , decrypt_routine_2
skip:
call ecx ; ← Decrypt data (tracked to possible routines)
With function tracking, xAnalyzer reveals the true behavior despite obfuscation.
Implementation History
This feature was introduced in xAnalyzer 2.5.0 (Version History from README):
xAnalyzer 2.5.0
- Added function smart tracking feature (Smart prediction and recognition of indirect function calls like: CALL , CALL )
- Added name of function pointers as parameters (the entire function name, if detected, will be used instead of just the address)
Source Code References
Function pointer tracking: xanalyzer.cpp:474-488
Indirect call resolution: xanalyzer.cpp:401-407, 2612-2652
MOV instruction filtering: xanalyzer.cpp:1406-1422
Configuration: xanalyzer.cpp:2692, 2708
Data structures: xanalyzer.h:35-41
Vector management: xanalyzer.cpp:42, 512-513, 551
Known Limitations
Function tracking has some constraints:
Limited to 100 stored MOV instructions per function
Cannot track pointer arithmetic that modifies addresses
Register aliasing may cause missed detections
Dynamic runtime modifications won’t be tracked
Cross-function pointer tracking is not supported
Function tracking adds minimal overhead:
Memory : ~100 instructions × ~1KB per entry = ~100KB max
CPU : Vector search is O(n) with n ≤ 100
Analysis time : Typically less than 5% increase
The benefits far outweigh the cost for most reverse engineering tasks.