summaryrefslogtreecommitdiff
path: root/docs/dev/cookbook.md
blob: e4740a1e3a6e0d0a1d258d7450fa848c9d77db13 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
# Cookbook

One of the best ways to learn a complicated API is to simply find the right example! First, here's a huge number of other sources of larger programs if you don't find what you're looking for in the below simple examples:

 - [Official Plugins](https://github.com/vector35/official-plugins): Official Plugins written and maintained by Vector 35
 - [Community Plugins](https://github.com/vector35/community-plugins): Over 100 plugins contributed by the Binary Ninja community
 - [Gist Collection](https://gist.github.com/psifertex/6fbc7532f536775194edd26290892ef7): Jordan's collection of python examples usually created for (or contributed by) customers
 - [Offline examples](https://github.com/Vector35/binaryninja-api/tree/dev/python/examples): These examples are especially useful because they're included in your offline install as well, just look in the examples/python subfolder wherever Binary Ninja installed

 That said, most of those examples tend to be more complex and so the following recipes are meant to be simple but useful building-blocks with which to learn useful techniques. Many of them also make use of the built-in Python console's [magic variables](../guide/index.md#magic-console-variables):

## Loading Files & Databases

When scripting from the Binary Ninja UI, the `bv` magic variable is already defined and available in the Python console or scripts loaded via `File -> Run Script...`. You can directly use `bv` to access the currently open binary.

If you have Binary Ninja Commercial and above (Commercial, Ultimate, and Enterprise), you can also use Binary Ninja headlessly as a library. This allows you to write standalone scripts that load and analyze files without the UI.

!!! note "Headless Usage Requirement"
    Using Binary Ninja as a library (headlessly) is only available in Binary Ninja Commercial and above. This feature is not available in the Personal edition.

### Basic file loading

```python
from binaryninja import load

# Using context manager (recommended)
with load('/bin/ls') as bv:
    if bv is not None:
        print(f"{bv.arch.name}: {hex(bv.entry_point)}")

# Without context manager - must close manually
bv = load('/bin/ls')
if bv is not None:
    print(f"Loaded {bv.file.filename}")
    bv.file.close()  # Important: prevents memory leaks
```

### Loading with options

```python
from binaryninja import load

bv = load('/bin/ls', options={
    'loader.imageBase': 0xfffffff0000,
    'loader.macho.processFunctionStarts': False,
    'analysis.mode': 'basic'
})
```

### Loading a database

```python
from binaryninja import load

# .bndb files use the same API
bv = load('/path/to/analysis.bndb')
```

### Controlling analysis

```python
from binaryninja import load

# Load without running analysis
bv = load('/bin/ls', update_analysis=False)
if bv is not None:
    bv.update_analysis_and_wait()  # Run analysis manually
    bv.file.close()
```

## Navigation / Search

### Getting all functions in a binary

```python
for func in bv.functions:
  print(func.name)
  print(func.start)
  print(func.parameter_vars)
  print(func.return_type)
```

### Getting a specific function

```python
func = bv.get_functions_by_name(here)[0]  # Multiple functions can share the same name!
func = bv.get_function_at(here)      # Shortcut for the next one
func = bv.get_functions_at(here)[0]  # Binary Ninja support functions that overlap!
func = bv.get_function_containing(here)  # Functions that contain the given address
# Just a note that using address to work with functions is fine
# But when working with ILs, addresses are approximate and can change for any given instruction
```

### Finding the largest function (by most bytes)

```python
max(bv.functions, key=lambda x: x.total_bytes)
```

### Search for a good nop-slide

```python
bv.find_next_data(0, b"\x90" * 10)
```

## IL & Decompilation

### All forms of a function

```python
for func in bv.functions:
  low_level_il        = func.llil
  low_level_il_ssa    = func.llil.ssa_form

  medium_level_il     = func.mlil
  medium_level_il_ssa = func.mlil.ssa_form

  # Decompilation:
  high_level_il       = func.hlil
  high_level_il_ssa   = func.hlil.ssa_form

  base_function       = <any>_level_il.source_function # Some helpers are only on the base function object!
```

### All decompiled instructions in a binary

```python
for func in bv.functions:
  for inst in func.hlil.instructions:
    print(f"{inst.address} : {inst}")
```

or

```python
for func in bv.functions:
  for bb in func.hlil:
    for inst in bb:
      print(f"{inst.address} : {inst}")
```

or

```python
for inst in bv.hlil_instructions:
  print(f"{inst.address} : {inst}")
```

### Getting the decompiled instruction at an address

```python
func = bv.get_functions_containing(here)[0]  # You should probably be more robust than this
llil_inst = func.get_llil_at(here)  # LLIL have the "closest" mapping to actual addresses, but you should still consider this volatile/fuzzy
hlil_inst = llil_inst.hlil          # This is also very approximate

# What's "more correct" walking down instead:
hlil_inst.mlil   # Approximate "direct" mapping down
hlil_inst.mlils  # All mlil instructions that contributed to this hlil instruction - most correct!
hlil_inst.llil   # Approximate "direct" mapping down
hlil_inst.llils  # All llil instructions that contributed to this hlil instruction - most correct!
# Be careful when working with address and mappings! We try to make them work as well as possible
# (and in most cases using the direct mapping is _fine_)
# But you should always be aware that they are approximate and can change!
```


## Call Graph Analysis

### All callers of a function

```python
current_function.callers
```

### All locations where a function is called

```python
for site in current_function.caller_sites:
  addr = site.address
  inst = site.hlil
```

### All calls and call instructions in a function:

```python
for site in current_function.call_sites:
  addr = site.address
  inst = site.hlil
```

### Finding the most "connected" function

As defined by having the highest sum of incoming and outgoing calls. Adjust accordingly.

```python
max(bv.functions, key=lambda x: len(x.callers + x.callees))
```
j
### Accessing cross references

This recipe is useful for iterating over all of the HLIL cross-references of a given interesting function:

```python
for ref in current_function.caller_sites:
	print(ref.hlil)
```

## Variables & Parameters

### Common variable APIs

```python
for func in bv.functions:
  all_vars          = func.vars               # This isn't the most meaningful thing to do, because....
  hlil_vars         = func.hlil.vars          # ...you probably only want the variables used in the IL you're looking at
  hlil_aliased_vars = func.hlil.aliased_vars  # ...but don't forget about aliased variables!
  parameter_vars    = func.parameter_vars     # ...or parameter variables!

  var = hlil_vars[0]
  if var.source_type == StackVariableSourceType:
    print(var.storage)  # var.storage is the variables stack offset, but ONLY IF the source type is `StackVariableSourceType`

    # There are many ways to *estimate* the size of a variable on the stack
    print(var.offset_to_next_variable)  # Distance to the next variable that Binary Ninja has identified on the stack
    print(abs(var.storage))  # Absolute maximum size the variable can be until it overwrites the saved return pointer!
    print(abs(var.type.width))  # If Binary Ninja gave the variable a type, or you manually applied a type, then you can get the size from that type

  # SSA
  hlil_ssa_vars = func.hlil.ssa_vars                                           # You can also get ssa variables
  def_inst      = func.hlil.ssa_form.get_ssa_var_definition(ssa_vars[0])  # But if you want definitions, you need to use the ssa form
  use_insts     = func.hlil.ssa_form.get_ssa_var_uses(ssa_vars[0])        # There's only ever one ssa definition, but potentially many uses
```

### Querying possible values of a function parameter

Is that memcpy length a bit too big?

```python
for ref in current_function.caller_sites:
	if isinstance(ref.hlil, Call) and len(ref.hlil.params) >= 3:
		print(ref.hlil.params[2])
		# For bonus points, query the range analysis using .possible_values
```

### Find a variable's definition and all uses using SSA

```python
>>> print(current_il_instruction)
x0_2 = 0x100007750(x0_1)
>>> findMe = current_il_instruction.params[0]
>>> findMe.ssa_form.function.get_ssa_var_definition(findMe.ssa_form.src)
<mlil: x0_1#5 = ϕ(x0_1#1, x0_1#2, x0_1#4)>
>>> findMe.ssa_form.function.get_ssa_var_uses(findMe.ssa_form.src)
[<mlil: x0_2#6, mem#3 = 0x100007750(x0_1#5) @ mem#2>]
```

If the result is a PHI, you'll want to either recursively search each version as well, or (more likely) use a queue to process all parameters until you find the source which could be an argument, global variable, immediate, or some other transformed data (which would require handling more types of IL instructions such as math operations, etc):

```python
>>> findMe.ssa_form.function.get_ssa_var_definition(findMe.ssa_form.src).src
[<ssa x0_1 version 1>, <ssa x0_1 version 2>, <ssa x0_1 version 4>]
>>> findMe2 = findMe.ssa_form.function.get_ssa_var_definition(findMe.ssa_form.src).src[0]
>>> current_il_function.get_ssa_var_definition(findMe2)
<mlil: x0_1#1 = "%*lld ">
```

Note, don't forget the difference between an MLIL Variable Instruction and the actual variable itself (use .src to get the later from the former)

```python
>>> findMe.ssa_form
<mlil: x0#3>
>>> findMe.ssa_form.src
<ssa x0 version 3>
>>> type(findMe.ssa_form.src)
<class 'binaryninja.mediumlevelil.SSAVariable'>
>>> type(findMe.ssa_form)
<class 'binaryninja.mediumlevelil.MediumLevelILVarSsa'>
```

## Annotations / Types

### Change a function's type signature

Make sure to check out the much more in-depth [applying annotations](annotation.md) as well.

```python
current_function.type = Type.function(Type.void(), [])
```

### Working with Tags

```python
# Data tags
bv.add_tag(here, "Crashes", "Description")

# Function tags
current_function.add_tag("Important", "Look at this later!")

# Function address tags
current_function.add_tag("Bug", "I think there's an overflow here?", here)
```

## Plugin Development & UI

### Apply a hotkey to a register_ plugin

There are basically two plugin systems in Binary Ninja. The first and simplest is the [PluginCommand](https://api.binary.ninja/binaryninja.plugin-module.html#binaryninja.plugin.PluginCommand) type. These plugins are very easy to register and are fairly separate from the QT code that powers the UI. Conversely, UIActions can have much more power over the interface. Unfortunately, not only are they not documented in the Python API (instead you have to poke into the [C++ docs](https://api.binary.ninja/cpp/group__action.html#struct_u_i_action)), but they also require a bit more work to get up and running. Here's a simple example showing how to convert a simple `register_for_range` plugin that just logs the selected address and size to one that is triggered via hotkey as a UIAction:

```python
from binaryninja import log_info, PluginCommand, mainthread
from binaryninjaui import UIAction, UIActionHandler, Menu
from PySide6.QtGui import QKeySequence

def old_range_action(bv, start, length):
    log_info(f"{bv} {start} {length}")

PluginCommand.register_for_range("Old Range Action", "Old Range Action", old_range_action)

def new_range_action_with_hotkey(ctx):
    bv = ctx.binaryView
    start = ctx.address
    length = ctx.length
    log_info(f"{bv} {start} {length}")

UIAction.registerAction("Trigger Range", QKeySequence("F3"))
UIActionHandler.globalActions().bindAction("Trigger Range", UIAction(new_range_action_with_hotkey))

# Unlike the PluginCommand above, you must manually add a UIAction to menus including the right-click menu and plugin menu:

Menu.mainMenu("Plugins").addAction("Trigger Range", "Plugins")
```

### Invoke plugin from the API
```python
from binaryninja import BinaryView, PluginCommand, PluginCommandContext


def invoke_plugin(name: str, bv: BinaryView, address=0, length = 0,function=None, instruction=None):
    ctx = PluginCommandContext(bv)
    ctx.address = address
    ctx.length = length
    ctx.function = function
    ctx.instruction = instruction

    cmds = PluginCommand.get_valid_list(ctx)
    cmds[name].execute(ctx)
```

### Add variable to the Python Console
```python
from binaryninja import PythonScriptingProvider, PythonScriptingInstance

def get_my_foo(instance: PythonScriptingInstance):
    # See scriptingprovider.py for details and implementations of other variables
    return instance.interpreter.active_addr & 0xffffff

PythonScriptingProvider.register_magic_variable(
    "my_foo",
    get_my_foo
)
```

### Opening a new tab

This example also shows how to create a new BinaryView from scratch for that tab.

```python
from binaryninja.binaryview import BinaryView
from binaryninjaui import FileContext, UIContext

data = BinaryView.new(b'\x00\x00\x01')
context = FileContext(data.file, data, '')
execute_on_main_thread(lambda: UIContext.activeContext().openFileContext(context))
```

## Version Checking

### Checking Binary Ninja version

Plugins often need to check the Binary Ninja version to ensure compatibility or conditionally enable features. Use [`core_version_info()`](https://api.binary.ninja/#binaryninja.core_version_info) and [`CoreVersionInfo`](https://api.binary.ninja/#binaryninja.CoreVersionInfo) for clean version comparisons:

```python
from binaryninja import core_version_info, CoreVersionInfo

# Check minimum version requirement
if core_version_info() >= CoreVersionInfo(5, 1, 8104):
    # Use API only available in 5.1.8104 and later
    print("New API is available")
else:
    # Fall back to older API
    print("Using legacy API")

# Parse and compare against a version string
if core_version_info() >= CoreVersionInfo("4.2.0"):
    print("Version 4.2.0 or later detected")

# Access individual version components
version = core_version_info()
print(f"Running Binary Ninja {version.major}.{version.minor}.{version.build}-{version.channel}")
```

## Debuging & Logging

### Logging

```python
log.log_debug("Debug logs are hidden by default")
log.log_info("Info logs are displayed in the console")
log.log_warn("Warning logs will print in yellow text")
log.log_error("Errors are red!")
log.log_alert("This pops up a dialogue box!")

log.log_error("You can add your own filter group easily to any of these APIs", "My Log Group")
```