Skip to main content

IDA Plugin that adds XREFs between virtual functions and their call sites

Project description

PacXplorer-NG

PacXplorer is an IDA plugin that adds XREFs between virtual functions and their call sites.
This is accomplished by leveraging PAC codes in ARM64e binaries.

Installation

  1. Install this package using your IDA's python pip: pip install pacxplorer-ng
  2. copy ida-plugin.json and ida_plugin_stub.py to your IDA's plugins folder: ~/.idapro/plugins/pacxplorer-ng .
  3. Restart IDA.

Usage

Preliminary Analysis

  1. open an IDB and make sure autoanalysis has finished
    1.1. KernelCache only: make sure to run ida_kernelcache-ng. This defines the `vtable for' symbols
  2. from the menu select Edit -> Plugins -> PacXplorer-NG -> Analyse IDB...
  3. done!

Continuous Use

TL;DR

⌘-x or Right Click -> Jump to PAC XREFs at suitable locations will open a selection window

From call site to virtual function

  1. place the cursor on a MOVK instruction near the call site to a virtual function (marked with a comment).
  2. press the hotkey or activate the menu entry
  3. a list of possible virtual methods called will open
  4. if the same virtual function is called from several vtables, the class column will show <multiple classes> instead of a class name
    • Right click -> PAC: toggle unique function names toggles this grouping

From virtual function to call site

  1. place the cursors either on a vtable entry or at the start of a virtual function
  2. press the hotkey or activate the menu entry
  3. a list of possible call sites will open

Principals of Operation

PAC codes sign pointers with a key and a context.
LLVM ABI specifies that the context of vtable entries is a mix between the entry's address and a hash of the function prototype.

Consider the following vtable at address 0x00000001 abcdef00:

offset method hash
0 foo() 0x1234
8 bar() 0x9876

The formula for calculating the context is:

addr_part = (addr_of_vtable + offset_of_method) & 0x0000ffffffffffff
hash_part = (hash & 0xffff) << 48
context   =  addr_part | hash_part

Hence when calling bar(), the context will be: 0x98760001 abcdef08.

Out of these factors, the offset of the method and the hash are known at compile time, but the actual address of the vtable is only known at runtime, through the this ptr (heck, this is the whole purpose of vtables to begin with).

Therefore, a typical (simplified) code snippet might look as follows:

LDR     X8, [X0]            ; load vtable address
LDRA    X9, [X8,#0x18]!     ; X8 = vtbl + offset
MOVK    X8, #0x68DA,LSL#48  ; set the hash 
BLRAA   X9, X8              ; virtual call

PacXplorer looks for similar MOVK instructions in all of the defined functions and analyses the code leading up to them, noting the offset in the table and the hash value, and constructs PAC tuple mappings of {(offset, hash): address of call}

On the other hand, PacXplorer iterates over all of the defined vtables, which it finds using symbol names.
In the binary, each vtable contains tagged pointers, which will have been untagged by IDA. The pointer tags embed the hash values that are used for PAC.
PacXplorer looks for the original pointer tags, which will have either been preserved in IDA's patched bytes window, or by opening the actual original binary. Using that, it creates the same PAC tuple for each virtual call, and construct a mapping of {(offset, hash): entry in vtable}.

At runtime, a simple matching of these two mappings is performed.

External usage

If you want to query PAC Xrefs from your own plugin, you can use the following code:

from netnode import Netnode
import idaapi
import json

n = Netnode("$ pacxplorer_io")

# From call site to virtual function 
n["input"] = 0x1234 # address of movk
idaapi.load_and_run_plugin("pacxplorer", 5)
r = n.get("output")
if r is not None:
    # List of dicts with keys: method_addr, vtable_addr, vtable_entry_addr, offset, pac
    res = json.loads(r)

# From virtual function to call site
n["input"] = 0x1234 # address of virtual functions
idaapi.load_and_run_plugin("pacxplorer", 6)
r = n.get("output")
if r is not None:
    # Possible call sites
    res = json.loads(r)

Q&A

Q: Why are there several virtual methods in the XREFs window?
A: This is the inherent ambiguity which is an intrinsic limitation of this method.
For every class inheritance tree, when calling a virtual method that's present in the parent class and overloaded in some of the children, there is no knowing at compile time which overloaded version will actually get called.
Obviously only what's known at compile time can be statically analysed.

Q: Why use a special window and not add the XREFs to the regular XREF list?
A: Due to the inherent ambiguity of which virtual function is called, I decided not to add (potentially many) bogus XREFs to the regular list, but keep them separated.

Q: Could there be false positives?
A: Yes. Besides the inherent ambiguity, there could also be cases where two functions in unrelated vtables generate the same (offset, hash) tuple.
When trying out using the hash value alone (disregarding the offset in the vtable), I've encountered many such false positives. Using the combination of (offset, hash) I'm yet to observe any such false positives.

Q: Why is the XREF from the MOVK instruction and not the BLRAA call?
A: I've encountered instances of several virtual calls using the same BLRAA.
Think of a function that selects a command handler with a switch-case, and all the cases jump to the same exit node that performs the call.

Limitations

  1. Works only on ARM64e binaries that conform to the ABI
  2. Vtable symbols need to be present in the binary ( `vtable for'). In the case of the Kernel, ida_kernelcache-ng needs to have been run.

Meta

This is a fork of Cellebrite's fork. I've done the following changes:

  • Split the code into several files and added a build system.
  • Changed the installation method to python package and a stub.
  • Rewrite the engine to be based on Capstone (instead of IDA disassembler), which led to ~60% performance improvements.
  • Pseudocode support.
  • Remote invocation support.

Original meta from Cellebrite: Authored by Ouri Lipner of Cellebrite Security Research Labs.
Currently maintained by Omer Porzecanski of Cellebrite Security Research Labs.
Developed and tested for IDA 7.5 - 7.7 on OS X, iOS 12.x - 15.4 beta

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pacxplorer_ng-1.0.0.tar.gz (4.4 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pacxplorer_ng-1.0.0-py3-none-any.whl (30.3 kB view details)

Uploaded Python 3

File details

Details for the file pacxplorer_ng-1.0.0.tar.gz.

File metadata

  • Download URL: pacxplorer_ng-1.0.0.tar.gz
  • Upload date:
  • Size: 4.4 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.12.9

File hashes

Hashes for pacxplorer_ng-1.0.0.tar.gz
Algorithm Hash digest
SHA256 9092160528f9ae838101d6ac2e44bff4e1db30b6ad77a3ae426a2ad70aa69b93
MD5 cb39f2ed5b41e1c9f871362f4bbecea4
BLAKE2b-256 11076ebda1161c2fae7234f4ac0215a6c14a19eb44b6cf4abd995bb143489f4b

See more details on using hashes here.

Provenance

The following attestation bundles were made for pacxplorer_ng-1.0.0.tar.gz:

Publisher: pypi.yaml on yoavst/pacxplorer-ng

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pacxplorer_ng-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: pacxplorer_ng-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 30.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.12.9

File hashes

Hashes for pacxplorer_ng-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 616ebb063c77c1f85b5b7de4197e6ddf8593d97deb4ec86eec62c3503c39aeb4
MD5 8a784dc3ed219b7e092a90e0287db247
BLAKE2b-256 a33ccfd8c21a1e6067b92bf0a831adc92c6c4948615b42d42907f2d6672df6b5

See more details on using hashes here.

Provenance

The following attestation bundles were made for pacxplorer_ng-1.0.0-py3-none-any.whl:

Publisher: pypi.yaml on yoavst/pacxplorer-ng

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page