binaryninja/
lib.rs

1// Copyright 2021-2026 Vector 35 Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15// TODO: These clippy-allow are bad and needs to be removed
16#![allow(clippy::missing_safety_doc)]
17#![allow(clippy::result_unit_err)]
18#![allow(clippy::type_complexity)]
19#![allow(clippy::too_many_arguments)]
20#![allow(clippy::needless_doctest_main)]
21#![doc(html_root_url = "https://dev-rust.binary.ninja/")]
22#![doc(html_favicon_url = "/brand/favicon-32x32.png")]
23#![doc(html_logo_url = "/brand/logo-vertical-dark.svg")]
24#![doc(issue_tracker_base_url = "https://github.com/Vector35/binaryninja-api/issues/")]
25#![doc = include_str!("../README.md")]
26
27#[macro_use]
28mod ffi;
29
30pub mod architecture;
31pub mod background_task;
32pub mod base_detection;
33pub mod basic_block;
34pub mod binary_view;
35pub mod calling_convention;
36pub mod collaboration;
37pub mod command;
38pub mod component;
39pub mod confidence;
40pub mod data_buffer;
41pub mod data_notification;
42pub mod data_renderer;
43pub mod database;
44pub mod debuginfo;
45pub mod demangle;
46pub mod disassembly;
47pub mod download;
48pub mod enterprise;
49pub mod external_library;
50pub mod file_accessor;
51pub mod file_metadata;
52pub mod flowgraph;
53pub mod function;
54pub mod function_recognizer;
55pub mod headless;
56pub mod high_level_il;
57pub mod interaction;
58pub mod language_representation;
59pub mod line_formatter;
60pub mod linear_view;
61pub mod llvm;
62pub mod logger;
63pub mod low_level_il;
64pub mod main_thread;
65pub mod medium_level_il;
66pub mod metadata;
67pub mod object_destructor;
68pub mod platform;
69pub mod progress;
70pub mod project;
71pub mod qualified_name;
72pub mod rc;
73pub mod references;
74pub mod relocation;
75pub mod render_layer;
76pub mod repository;
77pub mod secrets_provider;
78pub mod section;
79pub mod segment;
80pub mod settings;
81pub mod similarity;
82pub mod string;
83pub mod string_detection;
84pub mod symbol;
85pub mod tags;
86pub mod tracing;
87pub mod transform;
88pub mod types;
89pub mod update;
90pub mod variable;
91pub mod websocket;
92pub mod worker_thread;
93pub mod workflow;
94
95use crate::progress::{NoProgressCallback, ProgressCallback};
96use crate::string::raw_to_string;
97use binary_view::BinaryView;
98use binaryninjacore_sys::*;
99use rc::Ref;
100use std::cmp;
101use std::collections::HashMap;
102use std::ffi::{c_char, c_void, CStr};
103use std::fmt::{Display, Formatter};
104use std::path::{Path, PathBuf};
105use string::BnString;
106use string::IntoCStr;
107use string::IntoJson;
108
109use crate::project::file::ProjectFile;
110pub use binaryninjacore_sys::BNDataFlowQueryOption as DataFlowQueryOption;
111pub use binaryninjacore_sys::BNEndianness as Endianness;
112pub use binaryninjacore_sys::BNILBranchDependence as ILBranchDependence;
113
114pub const BN_FULL_CONFIDENCE: u8 = u8::MAX;
115pub const BN_INVALID_EXPR: usize = usize::MAX;
116
117/// Fuzzy match a string against a query string. Returns a score that is higher for
118/// a more confident match, or `None` if the query does not match the target string.
119pub fn fuzzy_match_single(target: &str, query: &str) -> Option<usize> {
120    let target = target.to_cstr();
121    let query = query.to_cstr();
122    let score = unsafe { BNFuzzyMatchSingle(target.as_ptr(), query.as_ptr()) };
123    (score != 0).then_some(score)
124}
125
126/// Fuzzy match a string against a query string. Returns a score that is higher for
127/// a more confident match, or None if the query does not match the target string.
128/// Same algorithm as [`fuzzy_match_single`] but with extra heuristics based on
129/// word boundaries and match offsets.
130pub fn fuzzy_match_contextual(target: &str, query: &str) -> Option<usize> {
131    let target = target.to_cstr();
132    let query = query.to_cstr();
133    let score = unsafe { BNFuzzyMatchContextual(target.as_ptr(), query.as_ptr()) };
134    (score != 0).then_some(score)
135}
136
137/// The main way to open and load files into Binary Ninja. Make sure you've properly initialized the core before calling this function. See [`crate::headless::init()`]
138pub fn load(file_path: impl AsRef<Path>) -> Option<Ref<BinaryView>> {
139    load_with_progress(file_path, NoProgressCallback)
140}
141
142/// Equivalent to [`load`] but with a progress callback.
143///
144/// NOTE: The progress callback will _only_ be called when loading BNDBs.
145pub fn load_with_progress<P: ProgressCallback>(
146    file_path: impl AsRef<Path>,
147    mut progress: P,
148) -> Option<Ref<BinaryView>> {
149    let file_path = file_path.as_ref().to_cstr();
150    let options = c"";
151    let handle = unsafe {
152        BNLoadFilename(
153            file_path.as_ptr() as *mut _,
154            true,
155            options.as_ptr() as *mut c_char,
156            Some(P::cb_progress_callback),
157            &mut progress as *mut P as *mut c_void,
158        )
159    };
160
161    if handle.is_null() {
162        None
163    } else {
164        Some(unsafe { BinaryView::ref_from_raw(handle) })
165    }
166}
167
168/// The main way to open and load files (with options) into Binary Ninja. Make sure you've properly initialized the core before calling this function. See [`crate::headless::init()`]
169///
170/// <div class="warning">Strict JSON doesn't support single quotes for strings, so you'll need to either use a raw strings (<code>f#"{"setting": "value"}"#</code>) or escape double quotes (<code>"{\"setting\": \"value\"}"</code>). Or use <code>serde_json::json</code>.</div>
171///
172/// ```no_run
173/// # // Mock implementation of json! macro for documentation purposes
174/// # macro_rules! json {
175/// #   ($($arg:tt)*) => {
176/// #     stringify!($($arg)*)
177/// #   };
178/// # }
179/// use binaryninja::{metadata::Metadata, rc::Ref};
180/// use std::collections::HashMap;
181///
182/// let bv = binaryninja::load_with_options("/bin/cat", true, Some(json!("analysis.linearSweep.autorun": false).to_string()))
183///     .expect("Couldn't open `/bin/cat`");
184/// ```
185pub fn load_with_options<O>(
186    file_path: impl AsRef<Path>,
187    update_analysis_and_wait: bool,
188    options: Option<O>,
189) -> Option<Ref<BinaryView>>
190where
191    O: IntoJson,
192{
193    load_with_options_and_progress(
194        file_path,
195        update_analysis_and_wait,
196        options,
197        NoProgressCallback,
198    )
199}
200
201/// Equivalent to [`load_with_options`] but with a progress callback.
202///
203/// NOTE: The progress callback will _only_ be called when loading BNDBs.
204pub fn load_with_options_and_progress<O, P>(
205    file_path: impl AsRef<Path>,
206    update_analysis_and_wait: bool,
207    options: Option<O>,
208    mut progress: P,
209) -> Option<Ref<BinaryView>>
210where
211    O: IntoJson,
212    P: ProgressCallback,
213{
214    let file_path = file_path.as_ref().to_cstr();
215    let options_or_default = if let Some(opt) = options {
216        opt.get_json_string()
217            .ok()?
218            .to_cstr()
219            .to_bytes_with_nul()
220            .to_vec()
221    } else {
222        "{}".to_cstr().to_bytes_with_nul().to_vec()
223    };
224    let handle = unsafe {
225        BNLoadFilename(
226            file_path.as_ptr() as *mut _,
227            update_analysis_and_wait,
228            options_or_default.as_ptr() as *mut c_char,
229            Some(P::cb_progress_callback),
230            &mut progress as *mut P as *mut c_void,
231        )
232    };
233
234    if handle.is_null() {
235        None
236    } else {
237        Some(unsafe { BinaryView::ref_from_raw(handle) })
238    }
239}
240
241pub fn load_view<O>(
242    bv: &BinaryView,
243    update_analysis_and_wait: bool,
244    options: Option<O>,
245) -> Option<Ref<BinaryView>>
246where
247    O: IntoJson,
248{
249    load_view_with_progress(bv, update_analysis_and_wait, options, NoProgressCallback)
250}
251
252/// Equivalent to [`load_view`] but with a progress callback.
253pub fn load_view_with_progress<O, P>(
254    bv: &BinaryView,
255    update_analysis_and_wait: bool,
256    options: Option<O>,
257    mut progress: P,
258) -> Option<Ref<BinaryView>>
259where
260    O: IntoJson,
261    P: ProgressCallback,
262{
263    let options_or_default = if let Some(opt) = options {
264        opt.get_json_string()
265            .ok()?
266            .to_cstr()
267            .to_bytes_with_nul()
268            .to_vec()
269    } else {
270        "{}".to_cstr().to_bytes_with_nul().to_vec()
271    };
272    let handle = unsafe {
273        BNLoadBinaryView(
274            bv.handle as *mut _,
275            update_analysis_and_wait,
276            options_or_default.as_ptr() as *mut c_char,
277            Some(P::cb_progress_callback),
278            &mut progress as *mut P as *mut c_void,
279        )
280    };
281
282    if handle.is_null() {
283        None
284    } else {
285        Some(unsafe { BinaryView::ref_from_raw(handle) })
286    }
287}
288
289pub fn load_project_file<O>(
290    file: &ProjectFile,
291    update_analysis_and_wait: bool,
292    options: Option<O>,
293) -> Option<Ref<BinaryView>>
294where
295    O: IntoJson,
296{
297    load_project_file_with_progress(file, update_analysis_and_wait, options, NoProgressCallback)
298}
299
300/// Equivalent to [`load_project_file`] but with a progress callback.
301pub fn load_project_file_with_progress<O, P>(
302    file: &ProjectFile,
303    update_analysis_and_wait: bool,
304    options: Option<O>,
305    mut progress: P,
306) -> Option<Ref<BinaryView>>
307where
308    O: IntoJson,
309    P: ProgressCallback,
310{
311    let options_or_default = if let Some(opt) = options {
312        opt.get_json_string()
313            .ok()?
314            .to_cstr()
315            .to_bytes_with_nul()
316            .to_vec()
317    } else {
318        "{}".to_cstr().to_bytes_with_nul().to_vec()
319    };
320    let handle = unsafe {
321        BNLoadProjectFile(
322            file.handle.as_ptr(),
323            update_analysis_and_wait,
324            options_or_default.as_ptr() as *mut c_char,
325            Some(P::cb_progress_callback),
326            &mut progress as *mut P as *mut c_void,
327        )
328    };
329
330    if handle.is_null() {
331        None
332    } else {
333        Some(unsafe { BinaryView::ref_from_raw(handle) })
334    }
335}
336
337pub fn install_directory() -> PathBuf {
338    let install_dir_ptr: *mut c_char = unsafe { BNGetInstallDirectory() };
339    assert!(!install_dir_ptr.is_null());
340    let install_dir_str = unsafe { BnString::into_string(install_dir_ptr) };
341    PathBuf::from(install_dir_str)
342}
343
344pub fn bundled_plugin_directory() -> Result<PathBuf, ()> {
345    let s: *mut c_char = unsafe { BNGetBundledPluginDirectory() };
346    if s.is_null() {
347        return Err(());
348    }
349    Ok(PathBuf::from(unsafe { BnString::into_string(s) }))
350}
351
352pub fn set_bundled_plugin_directory(new_dir: impl AsRef<Path>) {
353    let new_dir = new_dir.as_ref().to_cstr();
354    unsafe { BNSetBundledPluginDirectory(new_dir.as_ptr()) };
355}
356
357pub fn user_directory() -> PathBuf {
358    let user_dir_ptr: *mut c_char = unsafe { BNGetUserDirectory() };
359    assert!(!user_dir_ptr.is_null());
360    let user_dir_str = unsafe { BnString::into_string(user_dir_ptr) };
361    PathBuf::from(user_dir_str)
362}
363
364pub fn user_plugin_directory() -> Result<PathBuf, ()> {
365    let s: *mut c_char = unsafe { BNGetUserPluginDirectory() };
366    if s.is_null() {
367        return Err(());
368    }
369    let user_plugin_dir_str = unsafe { BnString::into_string(s) };
370    Ok(PathBuf::from(user_plugin_dir_str))
371}
372
373pub fn repositories_directory() -> Result<PathBuf, ()> {
374    let s: *mut c_char = unsafe { BNGetRepositoriesDirectory() };
375    if s.is_null() {
376        return Err(());
377    }
378    let repo_dir_str = unsafe { BnString::into_string(s) };
379    Ok(PathBuf::from(repo_dir_str))
380}
381
382pub fn settings_file_path() -> PathBuf {
383    let settings_file_name_ptr: *mut c_char = unsafe { BNGetSettingsFileName() };
384    assert!(!settings_file_name_ptr.is_null());
385    let settings_file_path_str = unsafe { BnString::into_string(settings_file_name_ptr) };
386    PathBuf::from(settings_file_path_str)
387}
388
389/// Write the installation directory of the currently running core instance to disk.
390///
391/// This is used to select the most recent installation for running scripts.
392pub fn save_last_run() {
393    unsafe { BNSaveLastRun() };
394}
395
396pub fn path_relative_to_bundled_plugin_directory(path: impl AsRef<Path>) -> Result<PathBuf, ()> {
397    let path_raw = path.as_ref().to_cstr();
398    let s: *mut c_char = unsafe { BNGetPathRelativeToBundledPluginDirectory(path_raw.as_ptr()) };
399    if s.is_null() {
400        return Err(());
401    }
402    Ok(PathBuf::from(unsafe { BnString::into_string(s) }))
403}
404
405pub fn path_relative_to_user_plugin_directory(path: impl AsRef<Path>) -> Result<PathBuf, ()> {
406    let path_raw = path.as_ref().to_cstr();
407    let s: *mut c_char = unsafe { BNGetPathRelativeToUserPluginDirectory(path_raw.as_ptr()) };
408    if s.is_null() {
409        return Err(());
410    }
411    Ok(PathBuf::from(unsafe { BnString::into_string(s) }))
412}
413
414pub fn path_relative_to_user_directory(path: impl AsRef<Path>) -> Result<PathBuf, ()> {
415    let path_raw = path.as_ref().to_cstr();
416    let s: *mut c_char = unsafe { BNGetPathRelativeToUserDirectory(path_raw.as_ptr()) };
417    if s.is_null() {
418        return Err(());
419    }
420    Ok(PathBuf::from(unsafe { BnString::into_string(s) }))
421}
422
423/// Returns if the running thread is the "main thread"
424///
425/// If there is no registered main thread than this will always return true.
426pub fn is_main_thread() -> bool {
427    unsafe { BNIsMainThread() }
428}
429
430pub fn memory_info() -> HashMap<String, u64> {
431    let mut count = 0;
432    let mut usage = HashMap::new();
433    unsafe {
434        let info_ptr = BNGetMemoryUsageInfo(&mut count);
435        let info_list = std::slice::from_raw_parts(info_ptr, count);
436        for info in info_list {
437            let info_name = CStr::from_ptr(info.name).to_str().unwrap().to_string();
438            usage.insert(info_name, info.value);
439        }
440        BNFreeMemoryUsageInfo(info_ptr, count);
441    }
442    usage
443}
444
445pub fn version() -> String {
446    unsafe { BnString::into_string(BNGetVersionString()) }
447}
448
449pub fn build_id() -> u32 {
450    unsafe { BNGetBuildId() }
451}
452
453#[derive(Clone, PartialEq, Eq, Hash, Debug)]
454pub struct VersionInfo {
455    pub major: u32,
456    pub minor: u32,
457    pub build: u32,
458    pub channel: String,
459}
460
461impl VersionInfo {
462    pub(crate) fn from_raw(value: &BNVersionInfo) -> Self {
463        Self {
464            major: value.major,
465            minor: value.minor,
466            build: value.build,
467            // NOTE: Because of plugin manager the channel might not be filled.
468            channel: raw_to_string(value.channel).unwrap_or_default(),
469        }
470    }
471
472    pub(crate) fn from_owned_raw(value: BNVersionInfo) -> Self {
473        let owned = Self::from_raw(&value);
474        Self::free_raw(value);
475        owned
476    }
477
478    pub(crate) fn into_owned_raw(value: &Self) -> BNVersionInfo {
479        BNVersionInfo {
480            major: value.major,
481            minor: value.minor,
482            build: value.build,
483            channel: value.channel.as_ptr() as *mut c_char,
484        }
485    }
486
487    pub(crate) fn free_raw(value: BNVersionInfo) {
488        unsafe { BnString::free_raw(value.channel) };
489    }
490}
491
492impl TryFrom<&str> for VersionInfo {
493    type Error = ();
494
495    fn try_from(value: &str) -> Result<Self, Self::Error> {
496        let string = value.to_cstr();
497        let result = unsafe { BNParseVersionString(string.as_ptr()) };
498        if result.build == 0 && result.channel.is_null() && result.major == 0 && result.minor == 0 {
499            return Err(());
500        }
501        Ok(Self::from_owned_raw(result))
502    }
503}
504
505impl PartialOrd for VersionInfo {
506    fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
507        Some(self.cmp(other))
508    }
509}
510
511impl Ord for VersionInfo {
512    fn cmp(&self, other: &Self) -> cmp::Ordering {
513        if self == other {
514            return cmp::Ordering::Equal;
515        }
516        let bn_version_0 = VersionInfo::into_owned_raw(self);
517        let bn_version_1 = VersionInfo::into_owned_raw(other);
518        if unsafe { BNVersionLessThan(bn_version_0, bn_version_1) } {
519            cmp::Ordering::Less
520        } else {
521            cmp::Ordering::Greater
522        }
523    }
524}
525
526impl Display for VersionInfo {
527    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
528        if self.channel.is_empty() {
529            write!(f, "{}.{}.{}", self.major, self.minor, self.build)
530        } else {
531            write!(
532                f,
533                "{}.{}.{}-{}",
534                self.major, self.minor, self.build, self.channel
535            )
536        }
537    }
538}
539
540pub fn version_info() -> VersionInfo {
541    let info_raw = unsafe { BNGetVersionInfo() };
542    VersionInfo::from_owned_raw(info_raw)
543}
544
545pub fn serial_number() -> String {
546    unsafe { BnString::into_string(BNGetSerialNumber()) }
547}
548
549pub fn is_license_validated() -> bool {
550    unsafe { BNIsLicenseValidated() }
551}
552
553pub fn licensed_user_email() -> String {
554    unsafe { BnString::into_string(BNGetLicensedUserEmail()) }
555}
556
557pub fn license_path() -> PathBuf {
558    user_directory().join("license.dat")
559}
560
561pub fn license_count() -> i32 {
562    unsafe { BNGetLicenseCount() }
563}
564
565#[derive(Clone, Debug, Eq, PartialEq)]
566pub struct LicenseAddon {
567    pub id: String,
568    pub license_serial: String,
569    pub product: String,
570    pub created: String,
571    pub created_timestamp: u64,
572    pub expiration: String,
573    pub expiration_timestamp: u64,
574    pub signature: String,
575}
576
577pub fn license_addons() -> Vec<LicenseAddon> {
578    let mut count = 0;
579    let addons = unsafe { BNGetLicenseAddons(&mut count) };
580    if addons.is_null() {
581        return Vec::new();
582    }
583
584    let result = unsafe { std::slice::from_raw_parts(addons, count) }
585        .iter()
586        .map(|addon| LicenseAddon {
587            id: unsafe { CStr::from_ptr(addon.id).to_string_lossy().into_owned() },
588            license_serial: unsafe {
589                CStr::from_ptr(addon.licenseSerial)
590                    .to_string_lossy()
591                    .into_owned()
592            },
593            product: unsafe { CStr::from_ptr(addon.product).to_string_lossy().into_owned() },
594            created: unsafe { CStr::from_ptr(addon.created).to_string_lossy().into_owned() },
595            created_timestamp: addon.createdTimestamp,
596            expiration: unsafe {
597                CStr::from_ptr(addon.expiration)
598                    .to_string_lossy()
599                    .into_owned()
600            },
601            expiration_timestamp: addon.expirationTimestamp,
602            signature: unsafe {
603                CStr::from_ptr(addon.signature)
604                    .to_string_lossy()
605                    .into_owned()
606            },
607        })
608        .collect();
609    unsafe { BNFreeLicenseAddons(addons, count) };
610    result
611}
612
613/// Set the license that will be used once the core initializes. You can reset the license by passing `None`.
614///
615/// If not set, the normal license retrieval will occur:
616/// 1. Check the BN_LICENSE environment variable
617/// 2. Check the Binary Ninja user directory for license.dat
618#[cfg(not(feature = "demo"))]
619pub fn set_license(license: Option<&str>) {
620    let license = license.unwrap_or_default().to_cstr();
621    unsafe { BNSetLicense(license.as_ptr()) }
622}
623
624#[cfg(feature = "demo")]
625pub fn set_license(_license: Option<&str>) {}
626
627pub fn product() -> String {
628    unsafe { BnString::into_string(BNGetProduct()) }
629}
630
631pub fn product_type() -> String {
632    unsafe { BnString::into_string(BNGetProductType()) }
633}
634
635pub fn license_expiration_time() -> std::time::SystemTime {
636    let m = std::time::Duration::from_secs(unsafe { BNGetLicenseExpirationTime() });
637    std::time::UNIX_EPOCH + m
638}
639
640pub fn is_ui_enabled() -> bool {
641    unsafe { BNIsUIEnabled() }
642}
643
644pub fn is_database(file: &Path) -> bool {
645    let filename = file.to_cstr();
646    unsafe { BNIsDatabase(filename.as_ptr()) }
647}
648
649pub fn plugin_abi_version() -> u32 {
650    BN_CURRENT_CORE_ABI_VERSION
651}
652
653pub fn plugin_abi_minimum_version() -> u32 {
654    BN_MINIMUM_CORE_ABI_VERSION
655}
656
657pub fn core_abi_version() -> u32 {
658    unsafe { BNGetCurrentCoreABIVersion() }
659}
660
661pub fn core_abi_minimum_version() -> u32 {
662    unsafe { BNGetMinimumCoreABIVersion() }
663}
664
665pub fn plugin_ui_abi_version() -> u32 {
666    BN_CURRENT_UI_ABI_VERSION
667}
668
669pub fn plugin_ui_abi_minimum_version() -> u32 {
670    BN_MINIMUM_UI_ABI_VERSION
671}
672
673pub fn add_required_plugin_dependency(name: &str) {
674    let raw_name = name.to_cstr();
675    unsafe { BNAddRequiredPluginDependency(raw_name.as_ptr()) };
676}
677
678pub fn add_optional_plugin_dependency(name: &str) {
679    let raw_name = name.to_cstr();
680    unsafe { BNAddOptionalPluginDependency(raw_name.as_ptr()) };
681}
682
683/// Exported function to tell the core what core ABI version this plugin was compiled against.
684#[cfg(not(feature = "no_exports"))]
685#[no_mangle]
686#[allow(non_snake_case)]
687pub extern "C" fn CorePluginABIVersion() -> u32 {
688    plugin_abi_version()
689}
690
691/// Exported function to tell the core what UI ABI version this plugin was compiled against.
692#[cfg(not(feature = "no_exports"))]
693#[no_mangle]
694pub extern "C" fn UIPluginABIVersion() -> u32 {
695    plugin_ui_abi_version()
696}