Binary Ninja Query Language¶
The Binary Ninja Query Language (BNQL) is designed to retrieve Binary Ninja API objects that represent the functions, variables, sections, strings, symbols, and various other entities of a binary. Queries make use of relationships between these entities to efficiently find the right objects. Queries may also access object indexes, which contain lists of objects pertaining to user-defined topics.
Queries do not replace the need to analyze decompiled code or data. A typical analysis involves:
- Writing queries to find relevant code, data, or metadata
- Examining the decompiled code and data returned by the query
- Based on the findings, deciding what to query next
Query Structure¶
A single step query has the following pattern:
Object Types¶
The <object_type>
part selects the type of object. A wildcard *
matches any object type. The complete list of objects types and their corresponding API classes are:
function
: Functions defined in the binary (Function)datavar
: Variables stored in the data sections (DataVariable)variable
: Local variables and parameters, when querying within a function (Variable)string
: String literals which may or may not be referenced by code or data (StringReference)symbol
: Named entities (Symbol or CoreSymbol)callable
: Entities that can be called by a function (Function, Symbol, CoreSymbol)block
: Basic blocks (BasicBlock)instruction
: IL instructions (LowLevelILInstruction, MediumLevelILInstruction, HighLevelILInstruction)type
: Data types and structures (Type and its subclasses)segment
: Memory segments (Segment)section
: Named sections (Section)external-library
: External library references (ExternalLibrary)external-location
: Symbol that refers to an external location (ExternalLocation)component
: Logical grouping of functions and data (Component)comment
: User comments at specific addresses (N/A)index
: User-defined collections of the objects along with per-object metadata (N/A)
Predicates¶
The [<predicate>]
part of the query filters selected objects with a logical expression. The logical operators are and
, or
, and not
, and parentheses are used to group expressions to control the order of evaluation. Predicates test the attributes of an object using relational operators:
<
: Less than>
: Greater than<=
: Less than or equal to>=
: Greater than or equal to==
: Equal to!=
: Not equal to~=
: Matches (regular expression matching)
Object attributes are denoted using @
. The available attributes are determined by the corresponding Binary Ninja API object. For example, an instruction object has an @operation
attribute and a function object has a @name
attribute.
For simple queries we can omit the relationship part. For example:
- To find a function named "main":
/function[@name == 'main']
- To find strings containing "error":
/string[@value ~= 'error']
- To find data variables at a given address:
/data[@address == 0xbaa800]
Predicate expressions may also include relationship tests. But first we must introduce relationships.
Relationships¶
The /<relationship>::
part of the query is used to step from a set of source objects to a set of target objects by traversing the specified relationship. For example, the following query selects the callees of the source function.
Relationships may also be used inside predicates where they filter objects based on the existence of relationships. For relationships relative to the object, we omit the leading /
. The following example selects strings using relationships: 1) the string must be used by functions with “server” in the name and 2) not used by functions with “log” in the name:
Note: The first step of a query begins at the BinaryView object. Thus, a query like /symbol
says “given the current binary view, select symbol objects contained in the binary view”.
Here is the complete list of the available relationships for each object type:
(BinaryView)
contains
: Get all top-level objects in the binaryhlil
: Get high-level IL instructionsmlil
: Get medium-level IL instructionsllil
: Get low-level IL instructions
function
calls
: Callables ('function', 'symbol', or 'callable') called by this functioncalled-by
: Functions that call this functioncontains
: Get specific basic blocks, local variables (including parameters), and IL instructions; using predicates to specify criteriadefined-in
: None (functions are top-level)hlil
: Get high-level IL instructionsmlil
: Get medium-level IL instructionsllil
: Get low-level IL instructionsuses
: Get data variables and strings used in the functionused-by
: Get functions using this functionreads
: Get variables read by the functionwrites
: Get variables written by the functionreferences
: Get address-taken variables and type referenceshas-type
: Get function typehas-symbol
: Get function symbolnavigated-to
: What the user has navigated to from this function
block
next
: Get successor blocksprev
: Get predecessor blocksdefined-in
: Get containing functioncontains
: Get instructionsuses
: Get data variables and strings used in the blockreferences
: Get address-taken variables
type
references
: Get referenced types (e.g., struct members)referenced-by
: Get objects using this typeinherits
: Get base typesimplements
: Get implemented interfaces
data
has-type
: Get variable typeuses
: Get data variables referenced by the data variableused-by
: Get functions/variables using this datahas-symbol
: Get associated symbolreferences
: Get type and symbol references
string
used-by
: Get functions using this stringreferences
: None (strings don't reference other objects)
instruction For MLIL and HLIL instructions:
reads
: Get variables readwrites
: Get variables writtenuses
: Get all variables accessedreferences
: Get address-taken variables
variable
has-type
: Get variable typedefined-in
: Get containing functionused-by
: Get instructions using this variable
component
contains
: Get child componentsdefined-in
: Get parent component
segment
contains
: Get functions, data, and strings in this region
section
contains
: Get functions, data, and strings in this region
symbol
has-symbol
: Get objects with this symbolcalled-by
: Functions that call this symbol
callable
calls
: Get callables called by this callablecalled-by
: Get callables that call this callable
index
contains
: Get indexed objects
Complex Queries¶
A query may consist of multiple steps and nested predicate expressions. Steps are used to traverse and select the destination objects. Predicates are used to filter objects selected at the current step.
The following type of query returns objects at step C:
The following type of query returns objects at step A, which satisfied B, which in turn satisfied C:
The Default Relationship¶
If the /<relationship>::
part of a query step is omitted, the default relationship is contains
. In other words, /
is syntactically equivalent to /contains::
. The contains
relationship is useful for navigating the hierarchy of a decompiled binary. For example, sections contain functions and data, functions contains basic blocks, basic blocks contain instructions, etc.
Local Variables versus Data Variables¶
In Binary Ninja, local variables include function parameters, register variables, and stack variables. Data variables are values stored in the data sections and may be used anywhere in the binary. Functions can read/write both local and global variables.
Callables¶
A callable is an object that can be called by a function. Callables include functions and symbols. The callable
object type is used to refer to both functions and symbols.
To find a call target by name, use the callable
object type (or the *
wildcard) which will find any function or symbol that matches. Otherwise, you can limit the search to functions defined in the binary or to symbols imported from external libraries using the function
or symbol
object types.
In contrast, the called-by
relationship always selects functions because only functions defined in the binary have explicit call sites.
Common Queries¶
Finding Suspicious Functions:
/function[@name ~= '(crypt|decrypt|encode|decode)']
/function[calls::*[@name == 'malloc'] and calls::*[@name == 'memcpy']]
/function[uses::string[@value ~= 'password|secret|key']]
Analyzing Data Flow:
/function[@name == 'main']/reads::variable[has-type::type[@name ~= 'char\*']]
/data[@address >= 0x400000 and @address < 0x401000]/used-by::function
/string[used-by::function[@name ~= 'network|socket|connect']]
Vulnerability Research:
/function[calls::function[@name ~= '(strcpy|strcat|sprintf)']]
/function[uses::data[@value ~= '(%s|%x|%p)']]
/function[not calls::function[@name ~= 'validate|check|verify']]
Code Structure Analysis:
/function[@size > 1000]/block[@outgoing_edge_count > 5]
/function[calls::function[@name == 'malloc'] and not calls::function[@name == 'free']]
/function[@name ~= 'init|setup']/calls::function
Cross-References: