ApplyCalleeType β Reborn for IDA Pro 9.3 (compatible IDA Pro 8.x β 9.3+)
A single-file IDA Pro plugin that applies a known function prototype to an indirect CALL instruction β unlocking correct decompiler and disassembler output for runtime-resolved APIs with no named global involved.
The original Mandiant FLARE apply_callee_type plugin is, in my opinion, one of the most practically useful IDA plugins ever written. For anyone doing daily malware analysis or vulnerability research, indirect calls with unknown prototypes are a constant friction point β and the FLARE team's plugin removes that friction with a single keypress. The elegance of wrapping apply_callee_tinfo() in a clean workflow is something I have used and appreciated for years.
When IDA 9.x landed with breaking API changes, the original plugin stopped loading entirely. Because I rely on it so heavily, and because many in the community were still running IDA 8.x, I decided to fully port and modernise it β preserving 100% of the original functionality, fixing every broken API, and adding the features I always wished it had. The result is a single drop-in file with no dependencies, compatible from IDA 8.x through 9.3+.
Full credit and deep respect to the Mandiant FLARE team for the original idea and implementation.
When reversing binaries that resolve API addresses at runtime β for example via PEB walk, GetProcAddress, LdrGetProcedureAddress, custom hash-based resolution, or any other technique β the resolved pointer is typically stored in a local stack variable, not a named global. IDA has no symbol to anchor type information to, so the decompiler and disassembler render the call with completely wrong or missing argument types:
Before β Disassembly:
mov [rsp+68h+var_40], 5
mov [rsp+68h+var_48], 0
xor r9d, r9d
lea r8, aCalcExe ; "calc.exe"
lea rdx, aOpen ; "open"
xor ecx, ecx
call [rsp+68h+var_18]Before β Pseudocode:
v5(0, "open", "calc.exe", 0, 0, 5);
// Type: int (__fastcall *v5)(_QWORD, const char *, const char *, _QWORD, _QWORD, int)Place the cursor anywhere on the CALL line in the disassembly view, or on the pointer variable (e.g. v5) or the call expression ( in the pseudocode view β any of these work. Apply the known prototype and IDA's apply_callee_tinfo() propagates argument types, names, and stack layout immediately:
After β Disassembly:
mov [rsp+68h+nShowCmd], 5 ; nShowCmd
mov [rsp+68h+lpDirectory], 0 ; lpDirectory
xor r9d, r9d ; lpParameters
lea r8, File ; "calc.exe"
lea rdx, Operation ; "open"
xor ecx, ecx ; hwnd
call [rsp+68h+ShellExecuteA]After β Pseudocode:
ShellExecuteA(0, "open", "calc.exe", 0, 0, SW_SHOW);
// Type: void (__stdcall *ShellExecuteA)(HWND hwnd, LPCSTR lpOperation, LPCSTR lpFile, LPCSTR lpParameters, LPCSTR lpDirectory, INT nShowCmd)The key reason a plugin is necessary: if the function pointer were stored in a named global, a simple rename in IDA would propagate the type automatically β no plugin needed. With a local stack variable, IDA has no anchor, and apply_callee_tinfo() is the only correct fix.
Reference: Function Prototypes and Indirect Calls β Mandiant FLARE blog
The original FLARE plugin was a functional but minimal proof of concept. This port goes significantly further:
| Feature | Original FLARE | ApplyCalleeTypeEx |
|---|---|---|
| IDA 9.x compatibility | β Crashes on load (removed APIs) | β Full support |
| IDA 8.x compatibility | β Yes | β Yes β backward compatible |
| Package dependency | β Requires flare package installed |
β Single file, zero dependencies |
| Legacy API shims | β IDA 6.x / 7.x / 8.x version branching | β Removed entirely |
| Right-click context menu | β Not present | β Disassembly + Pseudocode views |
| Pseudocode cursor support | β Not present | β Variable ref, call expression, or call line |
| Prototype editor | β Single-line plain text input | β Multi-line editor with live preprocessing preview |
| Preprocessing | β Strips 3 basic MSDN macros | β
Full SAL, ntdoc, Windows SDK headers, __declspec, extern "C", CC macros |
VOID macro handling |
β Not handled | β
Normalised to void before parsing |
| Parse failure feedback | β Silent | β Reports to IDA output window |
| Hotkey | β Alt+J β conflicts with IDA internals |
β
Shift+A β conflict-free on 8.x and 9.x |
| Test suite | β None | β 210 checks, verified on IDA 8.4 and 9.3 |
ApplyCalleeTypeEx_Demo.mp4
Pseudocode view β Standard Type: applying MessageBoxA + Manual entry: pasting LdrGetProcedureAddress from ntdoc β SAL annotations stripped automatically

Type source selection dialog β three input paths with tooltips

Multi-line prototype editor with live preprocessing preview β paste any real-world prototype format directly

Applying LdrGetProcedureAddress prototype to a runtime-resolved indirect call β disassembler and decompiler update immediately
Copy the plugin file to your IDA plugins directory. Two locations are supported:
Per-user β recommended, survives IDA reinstalls:
%APPDATA%\Hex-Rays\IDA Pro\plugins\apply_callee_type_ex.py
System-wide:
%IDADIR%\plugins\apply_callee_type_ex.py
Restart IDA to activate the plugin. On first load the output window confirms:
[ApplyCalleeTypeEx] Ready | Shift+A | right-click: disasm + pseudocode | Qt: pyside6
The plugin supports the IDA Plugin Manager via ida-plugin.json included in this repository. Once the plugin is published to plugins.hex-rays.com, it can be installed with:
hcli plugin install ApplyCalleeTypeEx
- Load any binary in IDA Pro 8.x or 9.3+
- Navigate to an indirect
CALLinstruction βcall rax,call [rsp+var_18], etc. - Trigger the plugin via any of three entry points:
Shift+Ahotkey- Edit β Operand type β ApplyCalleeTypeEx
- Right-click in the disassembly or pseudocode view
- Select a prototype source:
- Enter Manuallyβ¦ β paste any real-world prototype; MSDN brackets, SAL annotations,
__declspec,extern "C", and Windows API macros are stripped automatically - Standard Type (TIL) β browse loaded type library types (Windows SDK, POSIX, ntdll, etc.)
- Local Type (IDB) β browse types defined in the current IDB
- Enter Manuallyβ¦ β paste any real-world prototype; MSDN brackets, SAL annotations,
- The prototype is applied; both the disassembly and decompiler views refresh immediately
Cursor placement β all of the following work:
- Anywhere on the
CALLinstruction line in the disassembly view - On the pointer variable (e.g.
v5) in the pseudocode view - On the call expression
(in the pseudocode view
The manual entry path accepts prototypes copied directly from any common source without editing:
// Plain C
UINT WinExec(LPCSTR lpCmdLine, UINT uCmdShow);
// Windows SDK header (MSDN / WINBASEAPI / SAL)
WINBASEAPI
UINT
WINAPI
WinExec(
_In_ LPCSTR lpCmdLine,
_In_ UINT uCmdShow
);
// ntdoc / ntdll (NTSYSAPI / NTAPI / SAL)
NTSYSAPI
NTSTATUS
NTAPI
LdrGetProcedureAddress(
_In_ PVOID DllHandle,
_In_opt_ PCANSI_STRING ProcedureName,
_In_opt_ ULONG ProcedureNumber,
_Out_ PVOID *ProcedureAddress
);
// typedef funcptr
typedef UINT (WINAPI *PWINEXEC)(LPCSTR lpCmdLine, UINT uCmdShow);
// Bare funcptr
UINT (__stdcall *)(LPCSTR, UINT)A comprehensive test script is provided in Tests/test_apply_callee_type_ex.py. It runs entirely inside IDA via File β Script fileβ¦ and validates the full plugin API surface without requiring a specific binary.
IDA Pro 8.4 (x86 / x64): 210 passed, 0 failed
IDA Pro 9.3 (x86 / x64): 210 passed, 0 failed
| Section | What is tested |
|---|---|
| A | Preprocessor β all 30 strip/map rules |
| B | parse_type_from_string β 38 prototype formats + failure cases |
| C | TIL named type retrieval |
| D | _resolve_to_func_type β func / funcptr / typedef / scalar |
| E | apply_type_to_call β live binary + rejection cases |
| F | Automatic func β funcptr conversion |
| G | Full API surface parity + forbidden API scan |
| H | Preprocessor edge cases |
| I | Parse round-trip fidelity |
| Z | Diagnostics (Qt layer, TIL name, flag values) |
Tests/ApplyCalleeTypeEx_Test_VS/ contains a Visual Studio solution demonstrating three runtime API resolution techniques that produce indirect call sites with unknown prototypes β exactly the scenario this plugin is designed for.
Each technique stores the resolved function address in a local stack variable, not a named global. This is intentional: if a named global were used, a simple rename in IDA would suffice. With a local variable, apply_callee_tinfo() is the only correct fix.
| Function | Resolution Method | Target API | Prototype to Apply |
|---|---|---|---|
technique_peb_winexec() |
PEB walk + export directory parsing | WinExec |
UINT WinExec(LPCSTR lpCmdLine, UINT uCmdShow) |
technique_getprocaddress_shellexecute() |
GetProcAddress via opaque local pointer |
ShellExecuteA |
HINSTANCE ShellExecuteA(HWND, LPCSTR, LPCSTR, LPCSTR, LPCSTR, INT) |
technique_ldr_messagebox() |
ntdll!LdrGetProcedureAddress (not in IAT) |
MessageBoxA |
int MessageBoxA(HWND, LPCSTR, LPCSTR, UINT) |
Open ApplyCalleeTypeEx_Test_VS.sln in Visual Studio and build Release | x86 or Release | x64.
Required settings (differ from VS defaults):
| Setting | Path | Value |
|---|---|---|
| Optimization | C/C++ β Optimization | Disabled (/Od) |
| Inline expansion | C/C++ β Optimization β Inline Function Expansion | Disabled (/Ob0) |
| Debug info | C/C++ β General β Debug Information Format | Program Database (/Zi) |
Pre-built binaries are included in Tests/ApplyCalleeTypeEx_Test_VS/Release/ (x86) and Tests/ApplyCalleeTypeEx_Test_VS/x64/Release/ (x64).
ApplyCalleeTypeEx/
β apply_callee_type_ex.py β plugin (drop into IDA plugins directory)
β ida-plugin.json β IDA Plugin Manager metadata
β LICENSE
β README.md
β
ββββMedia
β image1.png β type source dialog screenshot
β image2.png β manual prototype editor screenshot
β image3.gif β demo β applying LdrGetProcedureAddress in pseudocode
β
ββββTests
β test_apply_callee_type_ex.py β test suite (run via File β Script fileβ¦)
β
ββββApplyCalleeTypeEx_Test_VS
β ApplyCalleeTypeEx_Test_VS.sln
β
ββββApplyCalleeTypeEx_Test_VS
β ApplyCalleeTypeEx_Test_VS.cpp
β ApplyCalleeTypeEx_Test_VS.vcxproj
β ApplyCalleeTypeEx_Test_VS.vcxproj.filters
β ApplyCalleeTypeEx_Test_VS.vcxproj.user
β
ββββRelease
β ApplyCalleeTypeEx_Test_VS.exe β x86 pre-built
β
ββββx64
ββββRelease
ApplyCalleeTypeEx_Test_VS.exe β x64 pre-built
Licensed under the Apache License 2.0 β see the LICENSE file for the full text.
This project is a derivative work of the original Mandiant FLARE apply_callee_type plugin.
Original license: Apache License 2.0 β Copyright (C) 2019 Mandiant, Inc. All Rights Reserved.
If you fork or further modify this project, you must retain:
- The original Mandiant copyright notice
- Attribution to this port: JiΕΓ Vinopal (Dump-GUY)
Original plugin: Mandiant FLARE team
IDA 9.3 port & enhancements: JiΕΓ Vinopal
Β Β Β Β X / Twitter: @vinopaljiri
Β Β Β Β GitHub: Dump-GUY