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.
Differences from the original PacXplorer
This is a fork of Cellebrite's PacXplorer with the following changes:
- Code structure improvements
- Moved to a more modern IDA plugin structure.
- Added a stub for the plugin, so it can be installed as a python package.
- Split the code into several files.
- Added a UV build system.
- Drop python 2 and IDA 8 support.
- Features
- Rewrite of the engine to use capstone - 60% improvement in performance.
- Pseudocode support - now can use the plugin directly from C.
- Remote invocation support from other plugins.
Installation
- Install this package using your IDA's python pip:
pip install pacxplorer-ng - copy
ida-plugin.jsonandida_plugin_stub.pyto your IDA's plugins folder:~/.idapro/plugins/pacxplorer-ng. - Restart IDA.
Usage
Preliminary Analysis
- 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 - from the menu select
Edit -> Plugins -> PacXplorer-NG -> Analyse IDB... - 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
- place the cursor on a
MOVKinstruction near the call site to a virtual function (marked with a comment). - press the hotkey or activate the menu entry
- a list of possible virtual methods called will open
- if the same virtual function is called from several vtables, the
classcolumn will show<multiple classes>instead of a class nameRight click -> PAC: toggle unique function namestoggles this grouping
From virtual function to call site
- place the cursors either on a vtable entry or at the start of a virtual function
- press the hotkey or activate the menu entry
- 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, follow the following steps:
- Copy
src/pacxplorerng/definisions.pyto your project. - Use the following code:
# Replace it with import of your definisions.py file
from pacxplorerng.definitions import get_explorer_instance, VtablePacEntry, ExplorerProtocol
# Use the global instance of the PAC XREFs explorer
explorer: ExplorerProtocol = get_explorer_instance()
# Analyze the project if not already done
explorer.analyze(only_cached=False)
# Get possible function for a movk instruction
movk_address = 0x12345678
xrefs: list[VtablePacEntry] = explorer.movk_to_functions(movk_address)
# Get the PAC XREFs for a specific function
func_address = 0x87654321
possible_callsites: list[int] = explorer.function_to_movks(func_address)
You can use idahelper's pac for more utils.
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
- Works only on ARM64e binaries that conform to the ABI
- 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
Tested for IDA 9.0+, iOS 18.4 - 26 beta.
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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pacxplorer_ng-1.0.2.tar.gz.
File metadata
- Download URL: pacxplorer_ng-1.0.2.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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8896745e96bae4a885e8b17ec5507235995fbb5c2226e941d5b8ec02796dfdc7
|
|
| MD5 |
11dbf0954c84f47e353ca142a5085edf
|
|
| BLAKE2b-256 |
53495b55c21716e28a72d18189e64b76b1af4882b568ba100a583511d74617bf
|
Provenance
The following attestation bundles were made for pacxplorer_ng-1.0.2.tar.gz:
Publisher:
pypi.yaml on yoavst/pacxplorer-ng
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pacxplorer_ng-1.0.2.tar.gz -
Subject digest:
8896745e96bae4a885e8b17ec5507235995fbb5c2226e941d5b8ec02796dfdc7 - Sigstore transparency entry: 405615815
- Sigstore integration time:
-
Permalink:
yoavst/pacxplorer-ng@7e5562e2cb35e0eb25c8a916d58c69c10b5ce8ba -
Branch / Tag:
refs/tags/1.0.2 - Owner: https://github.com/yoavst
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
pypi.yaml@7e5562e2cb35e0eb25c8a916d58c69c10b5ce8ba -
Trigger Event:
push
-
Statement type:
File details
Details for the file pacxplorer_ng-1.0.2-py3-none-any.whl.
File metadata
- Download URL: pacxplorer_ng-1.0.2-py3-none-any.whl
- Upload date:
- Size: 30.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b835e6851608cbd37dd91128017d508cf4b79d02a6fb1a4c7a3bbffacadcac27
|
|
| MD5 |
8f4417f4672d856110f821789d4f6077
|
|
| BLAKE2b-256 |
8e07fbf3cef8257408a78f208d1d4198bf3ddf62bd9ad9584dd004b145680f21
|
Provenance
The following attestation bundles were made for pacxplorer_ng-1.0.2-py3-none-any.whl:
Publisher:
pypi.yaml on yoavst/pacxplorer-ng
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pacxplorer_ng-1.0.2-py3-none-any.whl -
Subject digest:
b835e6851608cbd37dd91128017d508cf4b79d02a6fb1a4c7a3bbffacadcac27 - Sigstore transparency entry: 405615820
- Sigstore integration time:
-
Permalink:
yoavst/pacxplorer-ng@7e5562e2cb35e0eb25c8a916d58c69c10b5ce8ba -
Branch / Tag:
refs/tags/1.0.2 - Owner: https://github.com/yoavst
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
pypi.yaml@7e5562e2cb35e0eb25c8a916d58c69c10b5ce8ba -
Trigger Event:
push
-
Statement type: