Skip to content

Dump-GUY/ApplyCalleeTypeEx

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

9 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

ApplyCalleeTypeEx πŸ€™

ApplyCalleeType β€” Reborn for IDA Pro 9.3 (compatible IDA Pro 8.x β†’ 9.3+)

Python 3 IDA Pro License

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.


A Note of Appreciation

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.


Background

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


What's Different from the Original

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

Screenshots


ApplyCalleeTypeEx_Demo.mp4

Pseudocode view β€” Standard Type: applying MessageBoxA + Manual entry: pasting LdrGetProcedureAddress from ntdoc β€” SAL annotations stripped automatically


Type Source Dialog
Type source selection dialog β€” three input paths with tooltips


Manual Prototype Editor
Multi-line prototype editor with live preprocessing preview β€” paste any real-world prototype format directly


Demo β€” Applying LdrGetProcedureAddress in Pseudocode
Applying LdrGetProcedureAddress prototype to a runtime-resolved indirect call β€” disassembler and decompiler update immediately


Installation

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

IDA Plugin Manager (HCLI)

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

Usage

  1. Load any binary in IDA Pro 8.x or 9.3+
  2. Navigate to an indirect CALL instruction β€” call rax, call [rsp+var_18], etc.
  3. Trigger the plugin via any of three entry points:
    • Shift+A hotkey
    • Edit β†’ Operand type β†’ ApplyCalleeTypeEx
    • Right-click in the disassembly or pseudocode view
  4. 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
  5. The prototype is applied; both the disassembly and decompiler views refresh immediately

Cursor placement β€” all of the following work:

  • Anywhere on the CALL instruction line in the disassembly view
  • On the pointer variable (e.g. v5) in the pseudocode view
  • On the call expression ( in the pseudocode view

Supported Prototype Formats

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)

Test Suite

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)

PoC Test Binary

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.

Resolution Techniques

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)

Build

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).


Repository Structure

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

License

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)

Credits

Original plugin: Mandiant FLARE team
IDA 9.3 port & enhancements: JiΕ™Γ­ Vinopal
Β Β Β Β X / Twitter: @vinopaljiri
Β Β Β Β GitHub: Dump-GUY

About

ApplyCalleeType IDA Plugin πŸ€™ β€” Reborn. Single-file port to IDA Pro 9.3 with right-click menu, live prototype editor, and full SAL/MSDN preprocessing. Compatible IDA 8.x β†’ 9.3+.

Resources

License

Stars

Watchers

Forks

Contributors