diff --git a/idalib-build/Cargo.toml b/idalib-build/Cargo.toml index 0bbd13f9b2..71910c6a82 100644 --- a/idalib-build/Cargo.toml +++ b/idalib-build/Cargo.toml @@ -16,3 +16,5 @@ build = "build.rs" [dependencies] anyhow = "1" idalib-sys = { version = "0.7", path = "../idalib-sys" } +serde = { version = "1", features = ["derive"] } +serde_json = "1" diff --git a/idalib-build/src/lib.rs b/idalib-build/src/lib.rs index becdec25bf..97dcdf64c3 100644 --- a/idalib-build/src/lib.rs +++ b/idalib-build/src/lib.rs @@ -1,7 +1,61 @@ use std::env; +use std::fs; use std::path::{Path, PathBuf}; +use serde::Deserialize; + +#[derive(Deserialize)] +struct IdaConfig { + #[serde(rename = "Paths")] + paths: Option, +} + +#[derive(Deserialize)] +struct IdaConfigPaths { + #[serde(rename = "ida-install-dir")] + ida_install_dir: Option, +} + +/// Try to read the IDA installation path from the IDA configuration file +fn read_ida_config() -> Option { + let config_dir = if let Ok(ida_usr) = env::var("IDAUSR") { + PathBuf::from(ida_usr) + } else { + #[cfg(target_os = "windows")] + { + PathBuf::from(env::var("APPDATA").ok()?) + .join("Hex-Rays") + .join("IDA Pro") + } + #[cfg(any(target_os = "linux", target_os = "macos"))] + { + PathBuf::from(env::var("HOME").ok()?).join(".idapro") + } + #[cfg(not(any(target_os = "windows", target_os = "linux", target_os = "macos")))] + { + return None; + } + }; + + let config_path = config_dir.join("ida-config.json"); + let contents = fs::read_to_string(&config_path).ok()?; + let config: IdaConfig = serde_json::from_str(&contents).ok()?; + let path_str = config.paths?.ida_install_dir?; + Some(PathBuf::from(path_str)) +} + fn link_path() -> PathBuf { + // First try to read from ida-config.json + if let Some(mut path) = read_ida_config() { + #[cfg(target_os = "macos")] + if path.extension().map_or(false, |ext| ext == "app") { + path.push("Contents"); + path.push("MacOS"); + } + return path; + } + + // Fall back to platform-specific defaults #[cfg(target_os = "macos")] return PathBuf::from("/Applications/IDA Professional 9.2.app/Contents/MacOS"); diff --git a/idalib-sys/src/bytes_extras.h b/idalib-sys/src/bytes_extras.h index 562062484f..39ec7fd677 100644 --- a/idalib-sys/src/bytes_extras.h +++ b/idalib-sys/src/bytes_extras.h @@ -1,6 +1,7 @@ #pragma once #include "bytes.hpp" +#include "segment.hpp" #include "cxx.h" @@ -17,3 +18,24 @@ std::size_t idalib_get_bytes(ea_t ea, rust::Vec &buf) { return 0; } } + +bool idalib_is_loaded(ea_t ea) { + return is_loaded(ea); +} + +bool idalib_is_mapped(ea_t ea) { + return getseg(ea) != nullptr; +} + +bool idalib_is_stkvar(flags64_t flags, int operand_index) { + if (operand_index == 0) { + return is_stkvar0(flags); + } else if (operand_index == 1) { + return is_stkvar1(flags); + } + return false; +} + +bool idalib_is_off(flags64_t flags, int operand_index) { + return is_off(flags, operand_index); +} diff --git a/idalib-sys/src/entry_extras.h b/idalib-sys/src/entry_extras.h index b4d0a70122..6032e04421 100644 --- a/idalib-sys/src/entry_extras.h +++ b/idalib-sys/src/entry_extras.h @@ -14,3 +14,14 @@ rust::String idalib_entry_name(uval_t ord) { return rust::String(); } } + +rust::String idalib_entry_forwarder(uval_t ord) { + auto forwarder = qstring(); + + ssize_t result = get_entry_forwarder(&forwarder, ord); + if (result > 0 && !forwarder.empty()) { + return rust::String(forwarder.c_str()); + } else { + return rust::String(); + } +} diff --git a/idalib-sys/src/inf_extras.h b/idalib-sys/src/inf_extras.h index bf9d2ebfea..9df2b2b403 100644 --- a/idalib-sys/src/inf_extras.h +++ b/idalib-sys/src/inf_extras.h @@ -393,3 +393,5 @@ rust::String idalib_inf_get_strlit_pref() { bool idalib_inf_get_cc(compiler_info_t *out) { return inf_get_cc(out); } bool idalib_inf_get_privrange(range_t *out) { return inf_get_privrange(out); } + +ea_t idalib_inf_get_imagebase() { return get_imagebase(); } diff --git a/idalib-sys/src/insn_extras.h b/idalib-sys/src/insn_extras.h new file mode 100644 index 0000000000..5a50cb820c --- /dev/null +++ b/idalib-sys/src/insn_extras.h @@ -0,0 +1,67 @@ +#pragma once + +#include "intel.hpp" +#include "ua.hpp" +#include "idp.hpp" + +#include "cxx.h" + +// SIB Decoding Functions (from intel.hpp) +// These extract individual fields from the SIB byte + +int idalib_sib_base(const insn_t *insn, const op_t *op) { + return sib_base(*insn, *op); +} + +int idalib_sib_index(const insn_t *insn, const op_t *op) { + return sib_index(*insn, *op); +} + +int idalib_sib_scale(const op_t *op) { + return sib_scale(*op); +} + +// High-level Helper Functions (also from intel.hpp) +// These handle both SIB and non-SIB cases automatically + +int idalib_x86_base_reg(const insn_t *insn, const op_t *op) { + return x86_base_reg(*insn, *op); +} + +int idalib_x86_index_reg(const insn_t *insn, const op_t *op) { + return x86_index_reg(*insn, *op); +} + +int idalib_x86_scale(const op_t *op) { + return x86_scale(*op); +} + +bool idalib_has_displ(const op_t *op) { + return has_displ(*op); +} + +// Check for SIB byte presence +bool idalib_has_sib(const op_t *op) { + return op->hasSIB; +} + +// Get the raw SIB byte value +uint8_t idalib_get_sib_byte(const op_t *op) { + return op->sib; +} + +// Get instruction features from processor using itype +uint32_t idalib_get_canon_feature(uint16_t itype) { + processor_t *ph = (processor_t*)get_ph(); + return ph->get_canon_feature(itype); +} + +// Check if instruction modifies operand (wrapper for has_cf_chg) +bool idalib_has_cf_chg(uint32_t feature, uint32_t opnum) { + return has_cf_chg(feature, opnum); +} + +// Check if instruction uses operand (wrapper for has_cf_use) +bool idalib_has_cf_use(uint32_t feature, uint32_t opnum) { + return has_cf_use(feature, opnum); +} diff --git a/idalib-sys/src/lib.rs b/idalib-sys/src/lib.rs index 43f889f5e9..0593b16492 100644 --- a/idalib-sys/src/lib.rs +++ b/idalib-sys/src/lib.rs @@ -109,6 +109,25 @@ include_cpp! { generate!("is_indirect_jump_insn") generate!("is_ret_insn") + + // instruction feature flags (CF_*) + generate!("CF_STOP") + generate!("CF_CHG1") + generate!("CF_CHG2") + generate!("CF_CHG3") + generate!("CF_CHG4") + generate!("CF_CHG5") + generate!("CF_CHG6") + generate!("CF_USE1") + generate!("CF_USE2") + generate!("CF_USE3") + generate!("CF_USE4") + generate!("CF_USE5") + generate!("CF_USE6") + generate!("CF_JUMP") + generate!("CF_SHFT") + generate!("CF_HLL") + generate!("IRI_EXTENDED") generate!("IRI_RET_LITERALLY") generate!("IRI_SKIP_RETTARGET") @@ -281,6 +300,7 @@ include_cpp! { // ua (we use insn_t, op_t, etc. from pod) generate!("decode_insn") + generate!("print_insn_mnem") extern_cpp_type!("insn_t", crate::pod::insn_t) extern_cpp_type!("op_t", crate::pod::op_t) @@ -686,7 +706,7 @@ pub mod inf { idalib_inf_strlit_names, idalib_inf_strlit_savecase, idalib_inf_strlit_serial_names, idalib_inf_test_mode, idalib_inf_trace_flow, idalib_inf_truncate_on_del, idalib_inf_unicode_strlits, idalib_inf_use_allasm, idalib_inf_use_flirt, - idalib_inf_use_gcc_layout, + idalib_inf_use_gcc_layout, idalib_inf_get_imagebase, }; } @@ -734,9 +754,11 @@ mod ffix { include!("loader_extras.h"); include!("nalt_extras.h"); include!("ph_extras.h"); + include!("name_extras.h"); include!("segm_extras.h"); include!("search_extras.h"); include!("strings_extras.h"); + include!("insn_extras.h"); type c_short = autocxx::c_short; type c_int = autocxx::c_int; @@ -756,6 +778,8 @@ mod ffix { type qflow_chart_t = super::ffi::qflow_chart_t; type qbasic_block_t = super::ffi::qbasic_block_t; type segment_t = super::ffi::segment_t; + type insn_t = super::pod::insn_t; + type op_t = super::pod::op_t; // cfuncptr_t type qrefcnt_t_cfunc_t_AutocxxConcrete = super::ffi::qrefcnt_t_cfunc_t_AutocxxConcrete; @@ -780,6 +804,7 @@ mod ffix { // NOTE: we can't use uval_t here due to it resolving to c_ulonglong, // which causes `verify_extern_type` to fail... unsafe fn idalib_entry_name(e: c_ulonglong) -> Result; + unsafe fn idalib_entry_forwarder(ord: c_ulonglong) -> Result; unsafe fn idalib_func_flags(f: *const func_t) -> u64; unsafe fn idalib_func_name(f: *const func_t) -> Result; @@ -981,12 +1006,38 @@ mod ffix { unsafe fn idalib_inf_get_strlit_pref() -> String; unsafe fn idalib_inf_get_cc(out: *mut compiler_info_t) -> bool; unsafe fn idalib_inf_get_privrange(out: *mut range_t) -> bool; + unsafe fn idalib_inf_get_imagebase() -> c_ulonglong; unsafe fn idalib_ph_id(ph: *const processor_t) -> i32; unsafe fn idalib_ph_short_name(ph: *const processor_t) -> String; unsafe fn idalib_ph_long_name(ph: *const processor_t) -> String; unsafe fn idalib_is_thumb_at(ph: *const processor_t, ea: c_ulonglong) -> bool; + unsafe fn idalib_get_insn_mnem(ea: c_ulonglong) -> String; + unsafe fn idalib_get_disasm_line(ea: c_ulonglong) -> String; + unsafe fn idalib_get_insn_operand(ea: c_ulonglong, n: c_int) -> String; + unsafe fn idalib_tag_remove(input: &str) -> String; + + // SIB (Scale-Index-Base) decoding functions for x86 operands + unsafe fn idalib_sib_base(insn: *const insn_t, op: *const op_t) -> c_int; + unsafe fn idalib_sib_index(insn: *const insn_t, op: *const op_t) -> c_int; + unsafe fn idalib_sib_scale(op: *const op_t) -> c_int; + + // High-level x86 operand helpers + unsafe fn idalib_x86_base_reg(insn: *const insn_t, op: *const op_t) -> c_int; + unsafe fn idalib_x86_index_reg(insn: *const insn_t, op: *const op_t) -> c_int; + unsafe fn idalib_x86_scale(op: *const op_t) -> c_int; + unsafe fn idalib_has_displ(op: *const op_t) -> bool; + + // SIB byte accessors + unsafe fn idalib_has_sib(op: *const op_t) -> bool; + unsafe fn idalib_get_sib_byte(op: *const op_t) -> u8; + + // Instruction feature functions + unsafe fn idalib_get_canon_feature(itype: u16) -> u32; + unsafe fn idalib_has_cf_chg(feature: u32, opnum: u32) -> bool; + unsafe fn idalib_has_cf_use(feature: u32, opnum: u32) -> bool; + unsafe fn idalib_qflow_graph_getn_block( f: *const qflow_chart_t, n: usize, @@ -1001,6 +1052,8 @@ mod ffix { unsafe fn idalib_segm_perm(s: *const segment_t) -> u8; unsafe fn idalib_segm_bitness(s: *const segment_t) -> u8; unsafe fn idalib_segm_type(s: *const segment_t) -> u8; + unsafe fn idalib_get_fileregion_offset(ea: c_ulonglong) -> i64; + unsafe fn idalib_get_fileregion_ea(offset: i64) -> c_ulonglong; unsafe fn idalib_get_cmt(ea: c_ulonglong, rptble: bool) -> String; @@ -1018,9 +1071,15 @@ mod ffix { unsafe fn idalib_find_text(ea: c_ulonglong, text: *const c_char) -> c_ulonglong; unsafe fn idalib_find_imm(ea: c_ulonglong, imm: c_uint) -> c_ulonglong; unsafe fn idalib_find_defined(ea: c_ulonglong) -> c_ulonglong; + + unsafe fn idalib_bin_search(start_ea: c_ulonglong, end_ea: c_ulonglong, pattern: *const c_char, flags: c_int) -> c_ulonglong; + unsafe fn idalib_parse_binpat_str(pattern: *const c_char, out_bytes: &mut Vec, out_mask: &mut Vec) -> bool; + unsafe fn idalib_find_binary(start_ea: c_ulonglong, end_ea: c_ulonglong, bytes: *const u8, mask: *const u8, len: usize) -> c_ulonglong; unsafe fn idalib_get_strlist_item_addr(index: usize) -> c_ulonglong; unsafe fn idalib_get_strlist_item_length(index: usize) -> usize; + unsafe fn idalib_get_strlit_contents(ea: c_ulonglong, len: usize, strtype: i32) -> String; + unsafe fn idalib_get_max_strlit_length(ea: c_ulonglong, strtype: i32) -> usize; unsafe fn idalib_ea2str(ea: c_ulonglong) -> String; @@ -1029,9 +1088,20 @@ mod ffix { unsafe fn idalib_get_dword(ea: c_ulonglong) -> u32; unsafe fn idalib_get_qword(ea: c_ulonglong) -> u64; unsafe fn idalib_get_bytes(ea: c_ulonglong, buf: &mut Vec) -> Result; + unsafe fn idalib_is_loaded(ea: c_ulonglong) -> bool; + unsafe fn idalib_is_mapped(ea: c_ulonglong) -> bool; + unsafe fn idalib_is_stkvar(flags: c_ulonglong, operand_index: i32) -> bool; + unsafe fn idalib_is_off(flags: c_ulonglong, operand_index: i32) -> bool; unsafe fn idalib_get_input_file_path() -> String; + unsafe fn idalib_get_imports( + module_name: &mut Vec, + import_names: &mut Vec, + addresses: &mut Vec, + ordinals: &mut Vec, + ) -> bool; + unsafe fn idalib_plugin_version(p: *const plugin_t) -> u64; unsafe fn idalib_plugin_flags(p: *const plugin_t) -> u64; @@ -1040,6 +1110,7 @@ mod ffix { minor: *mut c_int, build: *mut c_int, ) -> bool; + unsafe fn idalib_get_ea_name(ea: c_ulonglong) -> String; } } @@ -1058,7 +1129,7 @@ pub const fn from_ea(v: ea_t) -> u64 { pub mod entry { pub use super::ffi::{get_entry, get_entry_ordinal, get_entry_qty, uval_t}; - pub use super::ffix::idalib_entry_name; + pub use super::ffix::{idalib_entry_forwarder, idalib_entry_name}; } pub mod insn { @@ -1160,8 +1231,9 @@ pub mod segment { }; pub use super::ffix::{ - idalib_segm_align, idalib_segm_bitness, idalib_segm_bytes, idalib_segm_name, - idalib_segm_perm, idalib_segm_type, + idalib_get_fileregion_ea, idalib_get_fileregion_offset, idalib_segm_align, + idalib_segm_bitness, idalib_segm_bytes, idalib_segm_name, idalib_segm_perm, + idalib_segm_type, }; } @@ -1169,13 +1241,17 @@ pub mod bytes { pub use super::ffi::{flags64_t, get_flags, is_code, is_data}; pub use super::ffix::{ idalib_get_byte, idalib_get_bytes, idalib_get_dword, idalib_get_qword, idalib_get_word, + idalib_is_loaded, idalib_is_mapped, idalib_is_off, idalib_is_stkvar, }; } pub mod util { pub use super::ffi::{ is_align_insn, is_basic_block_end, is_call_insn, is_indirect_jump_insn, is_ret_insn, - next_head, prev_head, str2reg, + next_head, prev_head, print_insn_mnem, str2reg, + }; + pub use super::ffix::{ + idalib_get_disasm_line, idalib_get_insn_mnem, idalib_get_insn_operand, idalib_tag_remove, }; } @@ -1204,12 +1280,18 @@ pub mod bookmarks { } pub mod search { - pub use super::ffix::{idalib_find_defined, idalib_find_imm, idalib_find_text}; + pub use super::ffix::{ + idalib_find_defined, idalib_find_imm, idalib_find_text, + idalib_bin_search, idalib_parse_binpat_str, idalib_find_binary, + }; } pub mod strings { pub use super::ffi::{build_strlist, clear_strlist, get_strlist_qty}; - pub use super::ffix::{idalib_get_strlist_item_addr, idalib_get_strlist_item_length}; + pub use super::ffix::{ + idalib_get_max_strlit_length, idalib_get_strlist_item_addr, idalib_get_strlist_item_length, + idalib_get_strlit_contents, + }; } pub mod loader { @@ -1228,7 +1310,7 @@ pub mod nalt { pub use super::ffi::{ retrieve_input_file_md5, retrieve_input_file_sha256, retrieve_input_file_size, }; - pub use super::ffix::idalib_get_input_file_path; + pub use super::ffix::{idalib_get_imports, idalib_get_input_file_path}; } pub mod name { @@ -1236,6 +1318,23 @@ pub mod name { get_nlist_ea, get_nlist_idx, get_nlist_name, get_nlist_size, is_in_nlist, is_public_name, is_weak_name, }; + pub use super::ffix::idalib_get_ea_name; +} + +pub mod x86 { + pub use super::ffix::{ + idalib_sib_base, idalib_sib_index, idalib_sib_scale, idalib_x86_base_reg, + idalib_x86_index_reg, idalib_x86_scale, idalib_has_displ, idalib_has_sib, idalib_get_sib_byte, + }; +} + +pub mod insn_features { + pub use super::ffi::{ + CF_STOP, CF_CHG1, CF_CHG2, CF_CHG3, CF_CHG4, CF_CHG5, CF_CHG6, + CF_USE1, CF_USE2, CF_USE3, CF_USE4, CF_USE5, CF_USE6, + CF_JUMP, CF_SHFT, CF_HLL, + }; + pub use super::ffix::{idalib_get_canon_feature, idalib_has_cf_chg, idalib_has_cf_use}; } pub mod ida { diff --git a/idalib-sys/src/nalt_extras.h b/idalib-sys/src/nalt_extras.h index 9f8cf47673..30810882d9 100644 --- a/idalib-sys/src/nalt_extras.h +++ b/idalib-sys/src/nalt_extras.h @@ -2,8 +2,10 @@ #include "nalt.hpp" #include "pro.h" +#include "loader.hpp" #include "cxx.h" +#include rust::String idalib_get_input_file_path() { char path[QMAXPATH] = {0}; @@ -15,3 +17,35 @@ rust::String idalib_get_input_file_path() { return rust::String(); } } + +struct import_ctx { + qstring current_module_name; + rust::Vec &module_names; + rust::Vec &import_names; + rust::Vec &addresses; + rust::Vec &ordinals; +}; + +static int import_enum_callback(ea_t ea, const char *name, uval_t ordinal, void *param) { + import_ctx* ctx = static_cast(param); + + ctx->module_names.push_back(rust::String(ctx->current_module_name.c_str())); + ctx->import_names.push_back(rust::String(name ? name : "")); + ctx->addresses.push_back(ea); + ctx->ordinals.push_back(static_cast(ordinal)); + + return 1; +} + +bool idalib_get_imports(rust::Vec &module_names, rust::Vec &import_names, rust::Vec &addresses, rust::Vec &ordinals) { + for (uint32_t idx = 0; idx < get_import_module_qty(); idx++) { + qstring module_name; + if (!get_import_module_name(&module_name, idx)) { + return false; + } + + import_ctx ctx{module_name, module_names, import_names, addresses, ordinals}; + enum_import_names(idx, import_enum_callback, static_cast(&ctx)); + } + return true; +} diff --git a/idalib-sys/src/name_extras.h b/idalib-sys/src/name_extras.h new file mode 100644 index 0000000000..dd1533e932 --- /dev/null +++ b/idalib-sys/src/name_extras.h @@ -0,0 +1,14 @@ +#pragma once + +#include "name.hpp" + +#include "cxx.h" + +rust::String idalib_get_ea_name(ea_t ea) { + qstring name; + if (get_ea_name(&name, ea)) { + return rust::String(name.c_str()); + } else { + return rust::String(); + } +} \ No newline at end of file diff --git a/idalib-sys/src/ph_extras.h b/idalib-sys/src/ph_extras.h index df95f5858e..25d2442e68 100644 --- a/idalib-sys/src/ph_extras.h +++ b/idalib-sys/src/ph_extras.h @@ -3,6 +3,8 @@ #include "pro.h" #include "idp.hpp" #include "segregs.hpp" +#include "ua.hpp" +#include "lines.hpp" #include "cxx.h" @@ -29,3 +31,33 @@ bool idalib_is_thumb_at(const processor_t *ph, ea_t ea) { } return false; } + +rust::String idalib_get_insn_mnem(ea_t ea) { + qstring buf; + if (print_insn_mnem(&buf, ea)) { + return rust::String(buf.c_str()); + } + return rust::String(); +} + +rust::String idalib_get_disasm_line(ea_t ea) { + qstring buf; + if (generate_disasm_line(&buf, ea, GENDSM_REMOVE_TAGS)) { + return rust::String(buf.c_str()); + } + return rust::String(); +} + +rust::String idalib_get_insn_operand(ea_t ea, int n) { + qstring buf; + if (print_operand(&buf, ea, n)) { + return rust::String(buf.c_str()); + } + return rust::String(); +} + +rust::String idalib_tag_remove(rust::Str input) { + qstring buf(input.data(), input.size()); + tag_remove(&buf); + return rust::String(buf.c_str()); +} diff --git a/idalib-sys/src/search_extras.h b/idalib-sys/src/search_extras.h index 4157ec9a8a..c5faa3a8a8 100644 --- a/idalib-sys/src/search_extras.h +++ b/idalib-sys/src/search_extras.h @@ -1,6 +1,7 @@ #pragma once #include "search.hpp" +#include "bytes.hpp" #include "cxx.h" @@ -15,3 +16,49 @@ ea_t idalib_find_imm(ea_t start_ea, uint32 imm) { ea_t idalib_find_defined(ea_t start_ea) { return find_defined(start_ea, SEARCH_DOWN | SEARCH_NEXT); } + +ea_t idalib_bin_search(ea_t start_ea, ea_t end_ea, const char *pattern, int flags) { + compiled_binpat_vec_t bbv; + if (!parse_binpat_str(&bbv, start_ea, pattern, 16, PBSENC_DEF1BPU, nullptr)) { + return BADADDR; + } + return bin_search(start_ea, end_ea, bbv, flags, nullptr); +} + +bool idalib_parse_binpat_str(const char *pattern, rust::Vec &out_bytes, rust::Vec &out_mask) { + compiled_binpat_vec_t bbv; + if (!parse_binpat_str(&bbv, 0, pattern, 16, PBSENC_DEF1BPU, nullptr)) { + return false; + } + + if (bbv.empty()) { + return false; + } + + const compiled_binpat_t &bp = bbv[0]; + + out_bytes.clear(); + out_bytes.reserve(bp.bytes.size()); + for (size_t i = 0; i < bp.bytes.size(); ++i) { + out_bytes.push_back(bp.bytes[i]); + } + + out_mask.clear(); + if (!bp.mask.empty()) { + out_mask.reserve(bp.mask.size()); + for (size_t i = 0; i < bp.mask.size(); ++i) { + out_mask.push_back(bp.mask[i]); + } + } else { + out_mask.reserve(bp.bytes.size()); + for (size_t i = 0; i < bp.bytes.size(); ++i) { + out_mask.push_back(0xFF); + } + } + + return true; +} + +ea_t idalib_find_binary(ea_t start_ea, ea_t end_ea, const uint8_t *bytes, const uint8_t *mask, size_t len) { + return bin_search(start_ea, end_ea, (const uchar *)bytes, (const uchar *)mask, len, BIN_SEARCH_FORWARD); +} diff --git a/idalib-sys/src/segm_extras.h b/idalib-sys/src/segm_extras.h index 4649bb656d..4ab64afbc5 100644 --- a/idalib-sys/src/segm_extras.h +++ b/idalib-sys/src/segm_extras.h @@ -43,3 +43,11 @@ std::uint8_t idalib_segm_perm(const segment_t *s) { std::uint8_t idalib_segm_type(const segment_t *s) { return s->type; } + +std::int64_t idalib_get_fileregion_offset(ea_t ea) { + return get_fileregion_offset(ea); +} + +ea_t idalib_get_fileregion_ea(std::int64_t offset) { + return get_fileregion_ea(offset); +} diff --git a/idalib-sys/src/strings_extras.h b/idalib-sys/src/strings_extras.h index d05e14d331..5b02fecb87 100644 --- a/idalib-sys/src/strings_extras.h +++ b/idalib-sys/src/strings_extras.h @@ -1,6 +1,7 @@ #pragma once #include "strlist.hpp" +#include "bytes.hpp" #include "cxx.h" @@ -15,3 +16,16 @@ size_t idalib_get_strlist_item_length(size_t n) { get_strlist_item(&si, n); return (size_t)si.length; } + +rust::String idalib_get_strlit_contents(ea_t ea, size_t len, int32_t strtype) { + qstring result; + if (get_strlit_contents(&result, ea, len, strtype)) { + return rust::String(result.c_str()); + } else { + return rust::String(); + } +} + +size_t idalib_get_max_strlit_length(ea_t ea, int32_t strtype) { + return get_max_strlit_length(ea, strtype); +} diff --git a/idalib/Cargo.toml b/idalib/Cargo.toml index 6e99139499..a6d63937d5 100644 --- a/idalib/Cargo.toml +++ b/idalib/Cargo.toml @@ -20,6 +20,64 @@ bitflags = "2" cxx = "1" idalib-sys = { version = "0.7", path = "../idalib-sys" } thiserror = "1" + +[dev-dependencies] +tempdir = "0.3" [build-dependencies] idalib-build = { version = "0.7", path = "../idalib-build" } + +# Tests that load idalib must be run on the main thread; +# however, the default test harness runs tests in threads, +# even if you set `--test-threads=1`. +# So we have to use custom test binaries to run our IDB tests. +# see: https://stackoverflow.com/a/43458688 +[[test]] +name = "test_bytes" +path = "tests/test_bytes.rs" +harness = false + +[[test]] +name = "test_heads" +path = "tests/test_heads.rs" +harness = false + +[[test]] +name = "test_imports" +path = "tests/test_imports.rs" +harness = false + +[[test]] +name = "test_strings" +path = "tests/test_strings.rs" +harness = false + +[[test]] +name = "test_xrefs" +path = "tests/test_xrefs.rs" +harness = false + +[[test]] +name = "test_instructions" +path = "tests/test_instructions.rs" +harness = false + +[[test]] +name = "test_search" +path = "tests/test_search.rs" +harness = false + +[[test]] +name = "test_segments" +path = "tests/test_segments.rs" +harness = false + +[[test]] +name = "test_x86_operands" +path = "tests/test_x86_operands.rs" +harness = false + +[[test]] +name = "test_metadata" +path = "tests/test_metadata.rs" +harness = false diff --git a/idalib/src/idb.rs b/idalib/src/idb.rs index d2cf34078d..eb07078505 100644 --- a/idalib/src/idb.rs +++ b/idalib/src/idb.rs @@ -1,6 +1,7 @@ use std::ffi::CString; use std::marker::PhantomData; use std::mem::MaybeUninit; +use std::ops::{Bound, RangeBounds}; use std::path::{Path, PathBuf}; use crate::ffi::BADADDR; @@ -17,15 +18,24 @@ use crate::ffi::ida::{ }; use crate::ffi::insn::decode; use crate::ffi::loader::find_plugin; +use crate::ffi::name::idalib_get_ea_name; use crate::ffi::processor::get_ph; -use crate::ffi::search::{idalib_find_defined, idalib_find_imm, idalib_find_text}; -use crate::ffi::segment::{get_segm_by_name, get_segm_qty, getnseg, getseg}; +use crate::ffi::search::{ + idalib_bin_search, idalib_find_binary, idalib_find_defined, idalib_find_imm, idalib_find_text, + idalib_parse_binpat_str, +}; +use crate::ffi::segment::{ + get_segm_by_name, get_segm_qty, getnseg, getseg, idalib_get_fileregion_ea, + idalib_get_fileregion_offset, +}; +use crate::ffi::strings::{idalib_get_max_strlit_length, idalib_get_strlit_contents}; use crate::ffi::util::{is_align_insn, next_head, prev_head, str2reg}; use crate::ffi::xref::{xrefblk_t, xrefblk_t_first_from, xrefblk_t_first_to}; use crate::bookmarks::Bookmarks; use crate::decompiler::CFunction; use crate::func::{Function, FunctionId}; +use crate::import::ImportIterator; use crate::insn::{Insn, Register}; use crate::meta::{Metadata, MetadataMut}; use crate::name::NameList; @@ -33,7 +43,7 @@ use crate::plugin::Plugin; use crate::processor::Processor; use crate::segment::{Segment, SegmentId}; use crate::strings::StringList; -use crate::xref::{XRef, XRefQuery}; +use crate::xref::{XRef, XRefFromIterator, XRefQuery, XRefToIterator}; use crate::{Address, AddressFlags, IDAError, IDARuntimeHandle, prepare_library}; pub struct IDB { @@ -176,6 +186,10 @@ impl IDB { MetadataMut::new() } + pub fn imagebase(&self) -> Address { + self.meta().imagebase() + } + pub fn processor(&self) -> Processor<'_> { let ptr = unsafe { get_ph() }; Processor::from_ptr(ptr) @@ -268,6 +282,29 @@ impl IDB { unsafe { get_func_qty() } } + fn bounds_to_range(&self, range: impl RangeBounds
) -> (Address, Address) { + let start = match range.start_bound() { + Bound::Included(&s) => s, + Bound::Excluded(&s) => s.saturating_add(1), + Bound::Unbounded => 0, + }; + let end = match range.end_bound() { + Bound::Included(&e) => e.saturating_add(1), + Bound::Excluded(&e) => e, + Bound::Unbounded => BADADDR.into(), + }; + (start, end) + } + + pub fn heads<'a>(&'a self, range: impl RangeBounds
) -> HeadsIterator<'a> { + let (start, end) = self.bounds_to_range(range); + HeadsIterator { + idb: self, + current: Some(start), + end, + } + } + pub fn segment_at(&self, ea: Address) -> Option> { let ptr = unsafe { getseg(ea.into()) }; @@ -307,6 +344,21 @@ impl IDB { unsafe { get_segm_qty().0 as _ } } + /// Get file offset corresponding to the given address. + /// + /// Returns -1 if the address can't be mapped to a file offset. + pub fn get_fileregion_offset(&self, ea: Address) -> i64 { + unsafe { idalib_get_fileregion_offset(ea.into()) } + } + + /// Get linear address corresponding to the given file offset. + /// + /// Returns BADADDR if the offset can't be mapped to an address. + pub fn get_fileregion_ea(&self, offset: i64) -> Address { + let result = unsafe { idalib_get_fileregion_ea(offset) }; + result.into() + } + pub fn register_by_name(&self, name: impl AsRef) -> Option { let s = CString::new(name.as_ref()).ok()?; let id = unsafe { str2reg(s.as_ptr()).0 }; @@ -343,6 +395,20 @@ impl IDB { } } + pub fn xrefs_to<'a>(&'a self, ea: Address) -> impl Iterator> + 'a { + let first_xref = self.first_xref_to(ea, XRefQuery::ALL); + XRefToIterator { + current: first_xref, + } + } + + pub fn xrefs_from<'a>(&'a self, ea: Address) -> impl Iterator> + 'a { + let first_xref = self.first_xref_from(ea, XRefQuery::ALL); + XRefFromIterator { + current: first_xref, + } + } + pub fn get_cmt(&self, ea: Address) -> Option { self.get_cmt_with(ea, false) } @@ -510,6 +576,80 @@ impl IDB { } } + pub fn find_bytes(&self, pattern: impl AsRef) -> Option
{ + self.find_bytes_range(.., pattern) + } + + pub fn find_bytes_range( + &self, + range: impl RangeBounds
, + pattern: impl AsRef, + ) -> Option
{ + let (start_ea, end_ea) = self.bounds_to_range(range); + let s = CString::new(pattern.as_ref()).ok()?; + let addr = + unsafe { idalib_bin_search(start_ea.into(), end_ea.into(), s.as_ptr(), 0.into()) }; + if addr == BADADDR { + None + } else { + Some(addr.into()) + } + } + + pub fn find_bytes_iter<'a>( + &'a self, + pattern: impl AsRef + 'a, + ) -> impl Iterator + 'a { + let mut cur = 0u64; + std::iter::from_fn(move || { + cur = self.find_bytes_range(cur.., pattern.as_ref())?; + let found = cur; + cur = self.find_defined(cur).unwrap_or(BADADDR.into()); + Some(found) + }) + } + + pub fn parse_binary_pattern(&self, pattern: impl AsRef) -> Option<(Vec, Vec)> { + let s = CString::new(pattern.as_ref()).ok()?; + let mut bytes = Vec::new(); + let mut mask = Vec::new(); + + let success = unsafe { idalib_parse_binpat_str(s.as_ptr(), &mut bytes, &mut mask) }; + if success { Some((bytes, mask)) } else { None } + } + + pub fn find_binary(&self, bytes: &[u8], mask: &[u8]) -> Option
{ + self.find_binary_range(.., bytes, mask) + } + + pub fn find_binary_range( + &self, + range: impl RangeBounds
, + bytes: &[u8], + mask: &[u8], + ) -> Option
{ + let (start_ea, end_ea) = self.bounds_to_range(range); + if bytes.len() != mask.len() { + return None; + } + + let addr = unsafe { + idalib_find_binary( + start_ea.into(), + end_ea.into(), + bytes.as_ptr(), + mask.as_ptr(), + bytes.len(), + ) + }; + + if addr == BADADDR { + None + } else { + Some(addr.into()) + } + } + pub fn strings(&self) -> StringList<'_> { StringList::new(self) } @@ -528,6 +668,14 @@ impl IDB { AddressFlags::new(unsafe { get_flags(ea.into()) }) } + pub fn is_code(&self, ea: Address) -> bool { + self.flags_at(ea).is_code() + } + + pub fn is_data(&self, ea: Address) -> bool { + self.flags_at(ea).is_data() + } + pub fn get_byte(&self, ea: Address) -> u8 { unsafe { idalib_get_byte(ea.into()) } } @@ -558,6 +706,33 @@ impl IDB { buf } + pub fn get_string_at(&self, ea: Address) -> Option { + let max_len = unsafe { idalib_get_max_strlit_length(ea.into(), -1) }; + if max_len == 0 { + return None; + } + + let contents = unsafe { idalib_get_strlit_contents(ea.into(), max_len, -1) }; + if contents.is_empty() { + None + } else { + Some(contents) + } + } + + pub fn get_name(&self, ea: Address) -> Option { + let name = unsafe { idalib_get_ea_name(ea.into()) }; + if name.is_empty() { None } else { Some(name) } + } + + pub fn is_loaded(&self, ea: Address) -> bool { + unsafe { idalib_is_loaded(ea.into()) } + } + + pub fn is_mapped(&self, ea: Address) -> bool { + unsafe { idalib_is_mapped(ea.into()) } + } + pub fn find_plugin( &self, name: impl AsRef, @@ -579,6 +754,10 @@ impl IDB { pub fn load_plugin(&self, name: impl AsRef) -> Result, IDAError> { self.find_plugin(name, true) } + + pub fn imports(&self) -> ImportIterator<'_> { + ImportIterator::new() + } } impl Drop for IDB { @@ -592,6 +771,30 @@ impl Drop for IDB { } } +pub struct HeadsIterator<'a> { + idb: &'a IDB, + current: Option
, + end: Address, +} + +impl<'a> Iterator for HeadsIterator<'a> { + type Item = Address; + + fn next(&mut self) -> Option { + let current = self.current?; + + if current >= self.end { + self.current = None; + return None; + } + + let next_addr = self.idb.next_head_with(current, self.end); + self.current = next_addr; + + Some(current) + } +} + pub struct EntryPointIter<'a> { index: usize, limit: usize, diff --git a/idalib/src/import.rs b/idalib/src/import.rs new file mode 100644 index 0000000000..24c5b3e494 --- /dev/null +++ b/idalib/src/import.rs @@ -0,0 +1,90 @@ +use std::marker::PhantomData; + +use crate::Address; +use crate::ffi::nalt::idalib_get_imports; +use crate::idb::IDB; + +#[derive(Debug, Clone)] +pub struct Import { + module_name: String, + function_name: String, + address: Address, + ordinal: u32, +} + +impl Import { + pub fn module_name(&self) -> &str { + &self.module_name + } + + pub fn function_name(&self) -> &str { + &self.function_name + } + + pub fn address(&self) -> Address { + self.address + } + + pub fn ordinal(&self) -> u32 { + self.ordinal + } +} + +pub struct ImportIterator<'a> { + imports: Vec, + current_index: usize, + _marker: PhantomData<&'a IDB>, +} + +impl<'a> ImportIterator<'a> { + pub(crate) fn new() -> Self { + let mut module_names = Vec::new(); + let mut import_names = Vec::new(); + let mut addresses = Vec::new(); + let mut ordinals = Vec::new(); + + unsafe { + idalib_get_imports( + &mut module_names, + &mut import_names, + &mut addresses, + &mut ordinals, + ); + } + + let imports = module_names + .into_iter() + .zip(import_names) + .zip(addresses) + .zip(ordinals) + .map( + |(((module_name, function_name), address), ordinal)| Import { + module_name, + function_name, + address, + ordinal, + }, + ) + .collect(); + + Self { + imports, + current_index: 0, + _marker: PhantomData, + } + } +} + +impl<'a> Iterator for ImportIterator<'a> { + type Item = Import; + + fn next(&mut self) -> Option { + if self.current_index < self.imports.len() { + let import = self.imports[self.current_index].clone(); + self.current_index += 1; + Some(import) + } else { + None + } + } +} diff --git a/idalib/src/insn.rs b/idalib/src/insn.rs index 87cc341323..928758b4e2 100644 --- a/idalib/src/insn.rs +++ b/idalib/src/insn.rs @@ -1,10 +1,11 @@ -use std::mem; +use std::{fmt, mem}; use bitflags::bitflags; use crate::ffi::insn::insn_t; use crate::ffi::insn::op::*; -use crate::ffi::util::{is_basic_block_end, is_call_insn, is_indirect_jump_insn, is_ret_insn}; +use crate::ffi::insn::op::op_t; +use crate::ffi::util::{is_basic_block_end, is_call_insn, is_indirect_jump_insn, is_ret_insn, idalib_get_disasm_line, idalib_get_insn_mnem, idalib_get_insn_operand}; pub use crate::ffi::insn::{arm, mips, x86}; @@ -20,9 +21,34 @@ pub struct Insn { } #[derive(Clone, Copy)] -#[repr(transparent)] pub struct Operand { inner: op_t, + ea: Address, +} + +impl fmt::Display for Insn { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}", + unsafe { idalib_get_disasm_line(autocxx::c_ulonglong(self.inner.ea)) } + ) + } +} + +impl fmt::Display for Operand { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}", + unsafe { + idalib_get_insn_operand( + autocxx::c_ulonglong(self.ea), + autocxx::c_int(self.inner.n as i32), + ) + } + ) + } } #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -88,6 +114,35 @@ bitflags! { } } +bitflags! { + #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] + pub struct CanonFeature: u32 { + const STOP = crate::ffi::insn_features::CF_STOP; + const CHG1 = crate::ffi::insn_features::CF_CHG1; + const CHG2 = crate::ffi::insn_features::CF_CHG2; + const CHG3 = crate::ffi::insn_features::CF_CHG3; + const CHG4 = crate::ffi::insn_features::CF_CHG4; + const CHG5 = crate::ffi::insn_features::CF_CHG5; + const CHG6 = crate::ffi::insn_features::CF_CHG6; + const USE1 = crate::ffi::insn_features::CF_USE1; + const USE2 = crate::ffi::insn_features::CF_USE2; + const USE3 = crate::ffi::insn_features::CF_USE3; + const USE4 = crate::ffi::insn_features::CF_USE4; + const USE5 = crate::ffi::insn_features::CF_USE5; + const USE6 = crate::ffi::insn_features::CF_USE6; + const JUMP = crate::ffi::insn_features::CF_JUMP; + const SHFT = crate::ffi::insn_features::CF_SHFT; + const HLL = crate::ffi::insn_features::CF_HLL; + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[repr(i8)] +pub enum AddressingMode { + Base = 0, + Sib = 1, +} + pub type InsnType = u16; impl Insn { @@ -107,7 +162,10 @@ impl Insn { let op = self.inner.ops.get(n)?; if op.type_ != o_void { - Some(Operand { inner: *op }) + Some(Operand { + inner: *op, + ea: self.inner.ea, + }) } else { None } @@ -148,6 +206,101 @@ impl Insn { pub fn is_ret_with(&self, iri: IsReturnFlags) -> bool { unsafe { is_ret_insn(&self.inner, iri.bits()) } } + + pub fn mnemonic(&self) -> String { + unsafe { idalib_get_insn_mnem(autocxx::c_ulonglong(self.inner.ea)) } + } + + pub fn x86_base_reg(&self, operand: &Operand) -> Option { + let base = unsafe { crate::ffi::x86::idalib_x86_base_reg(self.inner_ptr(), operand.inner_ptr()).0 }; + if base >= 0 { + Some(base as u32 as Register) + } else { + None + } + } + + pub fn x86_index_reg(&self, operand: &Operand) -> Option { + let index = unsafe { crate::ffi::x86::idalib_x86_index_reg(self.inner_ptr(), operand.inner_ptr()).0 }; + if index >= 0 { + Some(index as u32 as Register) + } else { + None + } + } + + pub fn x86_scale(&self, operand: &Operand) -> Option { + let scale_bits = unsafe { crate::ffi::x86::idalib_x86_scale(operand.inner_ptr()).0 }; + if scale_bits >= 0 { + Some(1u32 << (scale_bits as u32)) + } else { + None + } + } + + pub fn sib_base(&self, operand: &Operand) -> Option { + let base = unsafe { crate::ffi::x86::idalib_sib_base(self.inner_ptr(), operand.inner_ptr()).0 }; + if base >= 0 { + Some(base as u32 as Register) + } else { + None + } + } + + pub fn sib_index(&self, operand: &Operand) -> Option { + let index = unsafe { crate::ffi::x86::idalib_sib_index(self.inner_ptr(), operand.inner_ptr()).0 }; + if index >= 0 { + Some(index as u32 as Register) + } else { + None + } + } + + pub fn sib_scale(&self, operand: &Operand) -> Option { + let scale_bits = unsafe { crate::ffi::x86::idalib_sib_scale(operand.inner_ptr()).0 }; + if scale_bits >= 0 { + Some(1u32 << (scale_bits as u32)) + } else { + None + } + } + + /// Get canonical instruction features (CF_STOP, CF_CHG*, CF_USE*, etc.) + pub fn canon_feature(&self) -> CanonFeature { + CanonFeature::from_bits_retain(unsafe { + crate::ffi::insn_features::idalib_get_canon_feature(self.inner.itype as u16) + }) + } + + /// Check if instruction breaks sequential flow (CF_STOP) + pub fn breaks_flow(&self) -> bool { + self.canon_feature().contains(CanonFeature::STOP) + } + + /// Check if instruction modifies the given operand + pub fn modifies_operand(&self, operand_index: usize) -> bool { + unsafe { + crate::ffi::insn_features::idalib_has_cf_chg( + self.canon_feature().bits(), + operand_index as u32, + ) + } + } + + /// Check if instruction uses (reads) the given operand + pub fn uses_operand(&self, operand_index: usize) -> bool { + unsafe { + crate::ffi::insn_features::idalib_has_cf_use( + self.canon_feature().bits(), + operand_index as u32, + ) + } + } + + fn inner_ptr(&self) -> *const insn_t { + &self.inner as *const insn_t + } + } impl Operand { @@ -225,7 +378,7 @@ impl Operand { if self.is_processor_specific() || matches!( self.type_(), - OperandType::Mem | OperandType::Displ | OperandType::Far | OperandType::Near + OperandType::Phrase | OperandType::Mem | OperandType::Displ | OperandType::Far | OperandType::Near ) { Some(unsafe { self.inner.__bindgen_anon_3.addr }) @@ -274,6 +427,22 @@ impl Operand { } } + /// Get addressing mode for phrase/displ operands (used for x86). + /// Returns: + /// - Base: standard [base+offset] or [base] addressing + /// - Sib: SIB byte present, use specflag2 for base + pub fn addressing_mode(&self) -> AddressingMode { + match self.inner.specflag1 { + 1 => AddressingMode::Sib, + _ => AddressingMode::Base, + } + } + + /// Get specflag2 for phrase/displ operands (used for x86 SIB byte base extraction). + pub fn specflag2(&self) -> i8 { + self.inner.specflag2 + } + pub fn processor_specific_flag3(&self) -> Option { if self.is_processor_specific() { Some(self.inner.specflag3) @@ -301,4 +470,20 @@ impl Operand { | OperandType::IdpSpec5 ) } + + pub fn has_sib(&self) -> bool { + unsafe { crate::ffi::x86::idalib_has_sib(self.inner_ptr()) } + } + + pub fn sib_byte(&self) -> u8 { + unsafe { crate::ffi::x86::idalib_get_sib_byte(self.inner_ptr()) } + } + + pub fn has_displacement(&self) -> bool { + unsafe { crate::ffi::x86::idalib_has_displ(self.inner_ptr()) } + } + + fn inner_ptr(&self) -> *const op_t { + &self.inner as *const op_t + } } diff --git a/idalib/src/lib.rs b/idalib/src/lib.rs index f905d92d71..9749258c98 100644 --- a/idalib/src/lib.rs +++ b/idalib/src/lib.rs @@ -81,6 +81,7 @@ pub mod bookmarks; pub mod decompiler; pub mod func; pub mod idb; +pub mod import; pub mod insn; pub mod license; pub mod meta; @@ -91,6 +92,9 @@ pub mod segment; pub mod strings; pub mod xref; +#[cfg(test)] +pub mod tests; + pub use idalib_sys as ffi; pub use ffi::IDAError; @@ -118,6 +122,14 @@ impl<'a> AddressFlags<'a> { pub fn is_data(&self) -> bool { unsafe { ffi::bytes::is_data(self.flags) } } + + pub fn is_operand_stack_var(&self, operand_index: usize) -> bool { + unsafe { ffi::bytes::idalib_is_stkvar(self.flags, operand_index as i32) } + } + + pub fn is_operand_offset(&self, operand_index: usize) -> bool { + unsafe { ffi::bytes::idalib_is_off(self.flags, operand_index as i32) } + } } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -182,3 +194,7 @@ pub fn version() -> Result { build, }) } + +pub fn tag_remove(input: &str) -> String { + unsafe { ffi::util::idalib_tag_remove(input) } +} diff --git a/idalib/src/meta.rs b/idalib/src/meta.rs index e0da82cbac..53172e9589 100644 --- a/idalib/src/meta.rs +++ b/idalib/src/meta.rs @@ -120,6 +120,29 @@ pub enum Compiler { UNSURE = COMP_UNSURE as _, } +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[repr(u16)] +pub enum OSType { + LINUX = 0, + WIN32 = 1, + MAC = 2, + UNIX = 3, +} + +impl TryFrom for OSType { + type Error = u16; + + fn try_from(value: u16) -> Result { + match value { + 0 => Ok(OSType::LINUX), + 1 => Ok(OSType::WIN32), + 2 => Ok(OSType::MAC), + 3 => Ok(OSType::UNIX), + _ => Err(value), + } + } +} + pub struct Metadata<'a> { _marker: PhantomData<&'a IDB>, } @@ -243,8 +266,9 @@ impl<'a> Metadata<'a> { unsafe { mem::transmute(idalib_inf_get_filetype()) } } - pub fn ostype(&self) -> u16 { - unsafe { idalib_inf_get_ostype() } + pub fn ostype(&self) -> Option { + let value = unsafe { idalib_inf_get_ostype() }; + OSType::try_from(value).ok() } pub fn apptype(&self) -> u16 { @@ -412,6 +436,10 @@ impl<'a> Metadata<'a> { if ea != BADADDR { Some(ea.into()) } else { None } } + pub fn imagebase(&self) -> Address { + unsafe { idalib_inf_get_imagebase().into() } + } + pub fn start_stack_segment(&self) -> Option
{ let ea = unsafe { idalib_inf_get_start_ss() }; if ea != BADADDR { Some(ea.into()) } else { None } diff --git a/idalib/src/segment.rs b/idalib/src/segment.rs index 10e6a857ec..57a69d4428 100644 --- a/idalib/src/segment.rs +++ b/idalib/src/segment.rs @@ -329,4 +329,28 @@ impl<'a> Segment<'a> { pub fn is_visible(&self) -> bool { unsafe { (*self.ptr).is_visible_segm() } } + + pub fn is_extern(&self) -> bool { + self.r#type().is_extern() + } + + pub fn is_code_segment(&self) -> bool { + self.r#type().is_code() + } + + pub fn is_data_segment(&self) -> bool { + self.r#type().is_data() + } + + pub fn is_import_segment(&self) -> bool { + self.r#type().is_import() + } + + pub fn is_bss_segment(&self) -> bool { + self.r#type().is_bss() + } + + pub fn is_normal_segment(&self) -> bool { + self.r#type().is_normal() + } } diff --git a/idalib/src/tests.rs b/idalib/src/tests.rs new file mode 100644 index 0000000000..0278d5f70a --- /dev/null +++ b/idalib/src/tests.rs @@ -0,0 +1,14 @@ +use std::path::PathBuf; + +/// Fetch the file system path of the given test file. +/// +/// Found in idalib-root/tests/ +/// Files include: +/// - Practical Malware Analysis Lab 01-01.dll_ +pub fn get_test_file_path(filename: &str) -> PathBuf { + let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + d.push(".."); + d.push("tests"); + d.push(filename); + d +} diff --git a/idalib/src/xref.rs b/idalib/src/xref.rs index b004f36e14..ba6fba2199 100644 --- a/idalib/src/xref.rs +++ b/idalib/src/xref.rs @@ -146,3 +146,31 @@ impl<'a> XRef<'a> { } } } + +pub struct XRefToIterator<'a> { + pub(crate) current: Option>, +} + +impl<'a> Iterator for XRefToIterator<'a> { + type Item = XRef<'a>; + + fn next(&mut self) -> Option { + let current = self.current.take()?; + self.current = current.next_to(); + Some(current) + } +} + +pub struct XRefFromIterator<'a> { + pub(crate) current: Option>, +} + +impl<'a> Iterator for XRefFromIterator<'a> { + type Item = XRef<'a>; + + fn next(&mut self) -> Option { + let current = self.current.take()?; + self.current = current.next_from(); + Some(current) + } +} diff --git a/idalib/tests/test_bytes.rs b/idalib/tests/test_bytes.rs new file mode 100644 index 0000000000..6c7b704613 --- /dev/null +++ b/idalib/tests/test_bytes.rs @@ -0,0 +1,59 @@ +use tempdir::TempDir; + +use idalib::idb::IDB; +#[path = "../src/tests.rs"] +mod tests; + +// .text:10001000 sub_10001000 proc near +// .text:10001000 arg_0= dword ptr 4 +// .text:10001000 +// .text:10001000 8B C1 mov eax, ecx +// .text:10001002 8B 4C 24 04 mov ecx, [esp+arg_0] + +// 10001000 8B C1 8B 4C 24 04 8A 11 88 10 C2 04 00 90 90 90 ...L$........... +// 10001010 B8 F8 11 00 00 E8 06 02 00 00 8B 84 24 00 12 00 ............$... +// 10001020 00 53 55 56 83 F8 01 57 0F 85 BA 01 00 00 A0 54 .SUV...W.......T + +fn test_get_byte() { + const FILENAME: &str = "Practical Malware Analysis Lab 01-01.dll_"; + let dir = TempDir::new("idalib-rs-tests").unwrap(); + let dst = dir.path().join(FILENAME); + let src = tests::get_test_file_path(FILENAME); + std::fs::copy(&src, &dst).unwrap(); + + let idb = IDB::open(dst).unwrap(); + + assert!(idb.is_loaded(0x10001000)); + assert!(idb.is_mapped(0x10001000)); + assert_eq!(idb.get_byte(0x10001000 + 0), 0x8B); + assert_eq!(idb.get_byte(0x10001000 + 1), 0xC1); + assert_eq!(idb.get_word(0x10001000), 0xC18B); + assert_eq!(idb.get_dword(0x10001000), 0x4C8BC18B); + assert_eq!(idb.get_qword(0x10001000), 0x118A04244C8BC18B); + assert_eq!(idb.get_bytes(0x10001000, 2), vec![0x8B, 0xC1]); +} + +fn test_is_operand_offset() { + const FILENAME: &str = "Practical Malware Analysis Lab 01-01.dll_"; + let dir = TempDir::new("idalib-rs-tests").unwrap(); + let dst = dir.path().join(FILENAME); + let src = tests::get_test_file_path(FILENAME); + std::fs::copy(&src, &dst).unwrap(); + + let idb = IDB::open(dst).unwrap(); + + // 0x10001000: mov eax, ecx + let flags = idb.flags_at(0x10001000); + assert!(!flags.is_operand_offset(0), "operand 0 should not be an offset for reg-to-reg mov"); + assert!(!flags.is_operand_offset(1), "operand 1 should not be an offset for reg-to-reg mov"); + + // 0x1000102e: mov al, byte_10026054 + let flags = idb.flags_at(0x1000102E); + assert!(!flags.is_operand_offset(0), "operand 0 should not be an offset for off-to-reg mov"); + assert!(flags.is_operand_offset(1), "operand 1 should be an offset for off-to-reg mov"); +} + +fn main() { + test_get_byte(); + test_is_operand_offset(); +} diff --git a/idalib/tests/test_heads.rs b/idalib/tests/test_heads.rs new file mode 100644 index 0000000000..0f0f4d253f --- /dev/null +++ b/idalib/tests/test_heads.rs @@ -0,0 +1,50 @@ +use tempdir::TempDir; + +use idalib::idb::IDB; +#[path = "../src/tests.rs"] +mod tests; + +// .text:10001000 sub_10001000 proc near +// .text:10001000 arg_0= dword ptr 4 +// .text:10001000 +// .text:10001000 8B C1 mov eax, ecx +// .text:10001002 8B 4C 24 04 mov ecx, [esp+arg_0] +// .text:10001006 8A 11 mov dl, [ecx] +// .text:10001008 88 10 mov [eax], dl +// .text:1000100A C2 04 00 retn 4 +// .text:1000100A sub_10001000 endp +// +// 10001000 8B C1 8B 4C 24 04 8A 11 88 10 C2 04 00 90 90 90 ...L$........... +// 10001010 B8 F8 11 00 00 E8 06 02 00 00 8B 84 24 00 12 00 ............$... +// 10001020 00 53 55 56 83 F8 01 57 0F 85 BA 01 00 00 A0 54 .SUV...W.......T + +fn test_heads_iterator() { + const FILENAME: &str = "Practical Malware Analysis Lab 01-01.dll_"; + let dir = TempDir::new("idalib-rs-tests").unwrap(); + let dst = dir.path().join(FILENAME); + let src = tests::get_test_file_path(FILENAME); + std::fs::copy(&src, &dst).unwrap(); + + let idb = IDB::open(dst).unwrap(); + + let start_addr = 0x10001000u64; + let end_addr = 0x10001020u64; + + let heads: Vec<_> = idb.heads(start_addr..end_addr).collect(); + + assert!(!heads.is_empty()); + assert!(heads.len() >= 3); + + assert_eq!(heads[0], start_addr); + assert_eq!(heads[1], 0x10001002_u64); + assert_eq!(heads[2], 0x10001006_u64); + + for head in &heads { + assert!((*head as u64) >= start_addr); + assert!((*head as u64) < end_addr); + } +} + +fn main() { + test_heads_iterator(); +} diff --git a/idalib/tests/test_imports.rs b/idalib/tests/test_imports.rs new file mode 100644 index 0000000000..022d6c9173 --- /dev/null +++ b/idalib/tests/test_imports.rs @@ -0,0 +1,74 @@ +use tempdir::TempDir; + +use idalib::idb::IDB; +#[path = "../src/tests.rs"] +mod tests; + +fn test_import_enumeration() { + const FILENAME: &str = "Practical Malware Analysis Lab 01-01.dll_"; + let dir = TempDir::new("idalib-rs-tests").unwrap(); + let dst = dir.path().join(FILENAME); + let src = tests::get_test_file_path(FILENAME); + std::fs::copy(&src, &dst).unwrap(); + + let idb = IDB::open(dst).unwrap(); + + const EXPECTED_IMPORTS: &[(&str, &str, u32, u64)] = &[ + ("KERNEL32", "Sleep", 0, 0x10002000), + ("KERNEL32", "CreateProcessA", 0, 0x10002004), + ("KERNEL32", "CreateMutexA", 0, 0x10002008), + ("KERNEL32", "OpenMutexA", 0, 0x1000200c), + ("KERNEL32", "CloseHandle", 0, 0x10002010), + ("WS2_32", "", 3, 0x1000204c), + ("WS2_32", "", 4, 0x1000203c), + ("WS2_32", "", 9, 0x10002054), + ("WS2_32", "", 11, 0x10002038), + ("WS2_32", "", 16, 0x10002048), + ("WS2_32", "", 19, 0x10002040), + ("WS2_32", "", 22, 0x10002044), + ("WS2_32", "", 23, 0x10002030), + ("WS2_32", "", 115, 0x10002034), + ("WS2_32", "", 116, 0x10002050), + ("MSVCRT", "_adjust_fdiv", 0, 0x10002018), + ("MSVCRT", "malloc", 0, 0x1000201c), + ("MSVCRT", "_initterm", 0, 0x10002020), + ("MSVCRT", "free", 0, 0x10002024), + ("MSVCRT", "strncmp", 0, 0x10002028), + ]; + + let imports: Vec<_> = idb.imports().collect(); + + let expected_set: std::collections::HashSet<(&str, &str, u32, u64)> = EXPECTED_IMPORTS.iter().cloned().collect(); + let actual_set: std::collections::HashSet<(&str, &str, u32, u64)> = imports + .iter() + .map(|i| (i.module_name(), i.function_name(), i.ordinal(), i.address())) + .collect(); + + for &expected_import in &expected_set { + assert!( + actual_set.contains(&expected_import), + "Missing expected import: {:?}", + expected_import + ); + } + + for &actual_import in &actual_set { + assert!( + expected_set.contains(&actual_import), + "Unexpected import found: {:?}", + actual_import + ); + } + + assert_eq!( + actual_set.len(), + expected_set.len(), + "Import count mismatch: expected {}, got {}", + expected_set.len(), + actual_set.len() + ); +} + +fn main() { + test_import_enumeration(); +} diff --git a/idalib/tests/test_instructions.rs b/idalib/tests/test_instructions.rs new file mode 100644 index 0000000000..59cc9a116b --- /dev/null +++ b/idalib/tests/test_instructions.rs @@ -0,0 +1,157 @@ +use tempdir::TempDir; + +use idalib::idb::IDB; +#[path = "../src/tests.rs"] +mod tests; + +// .text:10001000 sub_10001000 proc near +// .text:10001000 arg_0= dword ptr 4 +// .text:10001000 +// .text:10001000 8B C1 mov eax, ecx +// .text:10001002 8B 4C 24 04 mov ecx, [esp+arg_0] +// .text:10001006 8A 11 mov dl, [ecx] +// .text:10001008 88 10 mov [eax], dl +// .text:1000100A C2 04 00 retn 4 + +// 10001000 8B C1 8B 4C 24 04 8A 11 88 10 C2 04 00 90 90 90 ...L$........... + +fn test_instruction_mnemonics() { + const FILENAME: &str = "Practical Malware Analysis Lab 01-01.dll_"; + let dir = TempDir::new("idalib-rs-tests").unwrap(); + let dst = dir.path().join(FILENAME); + let src = tests::get_test_file_path(FILENAME); + std::fs::copy(&src, &dst).unwrap(); + + let idb = IDB::open(dst).unwrap(); + + const EXPECTED_MNEMONICS: &[(u64, &str); 5] = &[ + (0x10001000, "mov"), + (0x10001002, "mov"), + (0x10001006, "mov"), + (0x10001008, "mov"), + (0x1000100A, "retn"), + ]; + + for &(addr, expected_mnemonic) in EXPECTED_MNEMONICS { + assert!(idb.is_code(addr)); + + let insn = idb.insn_at(addr).unwrap(); + let mnemonic = insn.mnemonic(); + + assert_eq!(expected_mnemonic, mnemonic); + } +} + +fn test_disasm_line() { + const FILENAME: &str = "Practical Malware Analysis Lab 01-01.dll_"; + let dir = TempDir::new("idalib-rs-tests").unwrap(); + let dst = dir.path().join(FILENAME); + let src = tests::get_test_file_path(FILENAME); + std::fs::copy(&src, &dst).unwrap(); + + let idb = IDB::open(dst).unwrap(); + + const EXPECTED_DISASM: &[(u64, &str); 5] = &[ + (0x10001000, "mov eax, ecx"), + (0x10001002, "mov ecx, [esp+arg_0]"), + (0x10001006, "mov dl, [ecx]"), + (0x10001008, "mov [eax], dl"), + (0x1000100A, "retn 4"), + ]; + + for &(addr, expected_disasm) in EXPECTED_DISASM { + assert!(idb.is_code(addr)); + + let insn = idb.insn_at(addr).unwrap(); + let disasm = insn.to_string(); + + assert_eq!(expected_disasm, disasm); + } +} + +fn test_print_operand() { + const FILENAME: &str = "Practical Malware Analysis Lab 01-01.dll_"; + let dir = TempDir::new("idalib-rs-tests").unwrap(); + let dst = dir.path().join(FILENAME); + let src = tests::get_test_file_path(FILENAME); + std::fs::copy(&src, &dst).unwrap(); + + let idb = IDB::open(dst).unwrap(); + + const EXPECTED_OPERANDS: &[(u64, &[&str]); 5] = &[ + (0x10001000, &["eax", "ecx"]), + (0x10001002, &["ecx", "[esp+arg_0]"]), + (0x10001006, &["dl", "[ecx]"]), + (0x10001008, &["[eax]", "dl"]), + (0x1000100A, &["4"]), + ]; + + for &(addr, expected_operands) in EXPECTED_OPERANDS { + assert!(idb.is_code(addr)); + + let insn = idb.insn_at(addr).unwrap(); + + for (i, &expected_operand) in expected_operands.iter().enumerate() { + let operand = insn.operand(i).expect("operand should exist"); + let operand_str = operand.to_string(); + let operand_clean = idalib::tag_remove(&operand_str); + assert_eq!(expected_operand, operand_clean); + } + } +} + +fn test_tag_remove() { + let input_with_tags = "\x01\x03mov\x02\x03 eax, ecx"; + let expected_clean = "mov eax, ecx"; + + let cleaned = idalib::tag_remove(input_with_tags); + assert_eq!(expected_clean, cleaned); + + let input_no_tags = "mov eax, ecx"; + let cleaned_no_tags = idalib::tag_remove(input_no_tags); + assert_eq!(input_no_tags, cleaned_no_tags); + + let empty_input = ""; + let cleaned_empty = idalib::tag_remove(empty_input); + assert_eq!(empty_input, cleaned_empty); +} + +fn test_insn_features() { + const FILENAME: &str = "Practical Malware Analysis Lab 01-01.dll_"; + let dir = TempDir::new("idalib-rs-tests").unwrap(); + let dst = dir.path().join(FILENAME); + let src = tests::get_test_file_path(FILENAME); + std::fs::copy(&src, &dst).unwrap(); + + let idb = IDB::open(dst).unwrap(); + + // Test mov eax, ecx at 0x10001000 + // This instruction should modify operand 0 (eax) and use operand 1 (ecx) + let mov_insn = idb.insn_at(0x10001000).unwrap(); + assert!(mov_insn.modifies_operand(0), "mov should modify first operand"); + assert!(mov_insn.uses_operand(1), "mov should use second operand"); + assert!(!mov_insn.breaks_flow(), "mov should not break flow"); + + // Test retn at 0x1000100A + // This instruction should break flow (CF_STOP) + let retn_insn = idb.insn_at(0x1000100A).unwrap(); + assert!(retn_insn.breaks_flow(), "retn should break flow"); + + // Verify canon_feature returns non-zero for valid instructions + assert!( + !mov_insn.canon_feature().is_empty(), + "mov should have non-zero features" + ); + assert!( + !retn_insn.canon_feature().is_empty(), + "retn should have non-zero features" + ); +} + +fn main() { + test_instruction_mnemonics(); + test_disasm_line(); + test_print_operand(); + test_tag_remove(); + test_insn_features(); +} diff --git a/idalib/tests/test_metadata.rs b/idalib/tests/test_metadata.rs new file mode 100644 index 0000000000..a77717dcc6 --- /dev/null +++ b/idalib/tests/test_metadata.rs @@ -0,0 +1,77 @@ +use tempdir::TempDir; + +use idalib::idb::IDB; +#[path = "../src/tests.rs"] +mod tests; + +fn test_imagebase() { + const FILENAME: &str = "Practical Malware Analysis Lab 01-01.dll_"; + let dir = TempDir::new("idalib-rs-tests").unwrap(); + let dst = dir.path().join(FILENAME); + let src = tests::get_test_file_path(FILENAME); + std::fs::copy(&src, &dst).unwrap(); + + let idb = IDB::open(dst).unwrap(); + + let imagebase = idb.imagebase(); + assert_eq!( + imagebase, 0x10000000, + "Expected imagebase 0x10000000, got 0x{:x}", + imagebase + ); +} + +fn test_imagebase_via_metadata() { + const FILENAME: &str = "Practical Malware Analysis Lab 01-01.dll_"; + let dir = TempDir::new("idalib-rs-tests").unwrap(); + let dst = dir.path().join(FILENAME); + let src = tests::get_test_file_path(FILENAME); + std::fs::copy(&src, &dst).unwrap(); + + let idb = IDB::open(dst).unwrap(); + + let metadata = idb.meta(); + let imagebase = metadata.imagebase(); + assert_eq!( + imagebase, 0x10000000, + "Expected imagebase 0x10000000 via metadata, got 0x{:x}", + imagebase + ); +} + +fn test_imagebase_consistency() { + const FILENAME: &str = "Practical Malware Analysis Lab 01-01.dll_"; + let dir = TempDir::new("idalib-rs-tests").unwrap(); + let dst = dir.path().join(FILENAME); + let src = tests::get_test_file_path(FILENAME); + std::fs::copy(&src, &dst).unwrap(); + + let idb = IDB::open(dst).unwrap(); + + let imagebase_via_idb = idb.imagebase(); + let imagebase_via_metadata = idb.meta().imagebase(); + assert_eq!( + imagebase_via_idb, imagebase_via_metadata, + "Imagebase should be consistent between IDB and metadata access methods" + ); +} + +fn test_ostype() { + const FILENAME: &str = "Practical Malware Analysis Lab 01-01.dll_"; + let dir = TempDir::new("idalib-rs-tests").unwrap(); + let dst = dir.path().join(FILENAME); + let src = tests::get_test_file_path(FILENAME); + std::fs::copy(&src, &dst).unwrap(); + + let idb = IDB::open(dst).unwrap(); + + let ostype = idb.meta().ostype(); + assert!(ostype.is_some(), "OSType should be recognized for test binary"); +} + +fn main() { + test_imagebase(); + test_imagebase_via_metadata(); + test_imagebase_consistency(); + test_ostype(); +} diff --git a/idalib/tests/test_search.rs b/idalib/tests/test_search.rs new file mode 100644 index 0000000000..a63000df30 --- /dev/null +++ b/idalib/tests/test_search.rs @@ -0,0 +1,121 @@ +use tempdir::TempDir; + +use idalib::idb::IDB; +#[path = "../src/tests.rs"] +mod tests; + +// .text:10001000 sub_10001000 proc near +// .text:10001000 arg_0= dword ptr 4 +// .text:10001000 +// .text:10001000 8B C1 mov eax, ecx +// .text:10001002 8B 4C 24 04 mov ecx, [esp+arg_0] +// .text:10001006 8A 11 mov dl, [ecx] +// .text:10001008 88 10 mov [eax], dl +// .text:1000100A C2 04 00 retn 4 +// +// 10001000 8B C1 8B 4C 24 04 8A 11 88 10 C2 04 00 90 90 90 ...L$........... +// 10001010 B8 F8 11 00 00 E8 06 02 00 00 8B 84 24 00 12 00 ............$... +// 10001020 00 53 55 56 83 F8 01 57 0F 85 BA 01 00 00 A0 54 .SUV...W.......T + +fn test_find_text() { + const FILENAME: &str = "Practical Malware Analysis Lab 01-01.dll_"; + let dir = TempDir::new("idalib-rs-tests").unwrap(); + let dst = dir.path().join(FILENAME); + let src = tests::get_test_file_path(FILENAME); + std::fs::copy(&src, &dst).unwrap(); + + let idb = IDB::open(dst).unwrap(); + + assert_eq!(idb.find_text(0x10001000, "mov"), Some(0x10001000)); + assert_eq!(idb.find_text(0x10001000, "eax"), Some(0x10001000)); + assert_eq!(idb.find_text(0x10001000, "foo"), None); +} + +fn test_find_imm() { + const FILENAME: &str = "Practical Malware Analysis Lab 01-01.dll_"; + let dir = TempDir::new("idalib-rs-tests").unwrap(); + let dst = dir.path().join(FILENAME); + let src = tests::get_test_file_path(FILENAME); + std::fs::copy(&src, &dst).unwrap(); + + let idb = IDB::open(dst).unwrap(); + + assert_eq!(idb.find_imm(0x10001000, 0x4), Some(0x10001002)); + assert_eq!(idb.find_imm(0x10001000, 0x99999), None); +} + +fn test_find_bytes() { + const FILENAME: &str = "Practical Malware Analysis Lab 01-01.dll_"; + let dir = TempDir::new("idalib-rs-tests").unwrap(); + let dst = dir.path().join(FILENAME); + let src = tests::get_test_file_path(FILENAME); + std::fs::copy(&src, &dst).unwrap(); + + let idb = IDB::open(dst).unwrap(); + + assert_eq!( + idb.find_bytes_range(0x10001000..0x10002000, "8B C1 8B 4C"), + Some(0x10001000) + ); + + assert_eq!( + idb.find_bytes_range(0x10001000..0x10002000, "8B ?? 8B ??"), + Some(0x10001000) + ); + + assert_eq!( + idb.find_bytes_range(0x10001000..0x10002000, "B8 F8 11"), + Some(0x10001010) + ); +} + +fn test_parse_binary_pattern() { + const FILENAME: &str = "Practical Malware Analysis Lab 01-01.dll_"; + let dir = TempDir::new("idalib-rs-tests").unwrap(); + let dst = dir.path().join(FILENAME); + let src = tests::get_test_file_path(FILENAME); + std::fs::copy(&src, &dst).unwrap(); + + let idb = IDB::open(dst).unwrap(); + + let (bytes, mask) = idb.parse_binary_pattern("8B C1 8B 4C").unwrap(); + assert_eq!(bytes, vec![0x8B, 0xC1, 0x8B, 0x4C]); + assert_eq!(mask, vec![0xFF, 0xFF, 0xFF, 0xFF]); + + let (bytes2, mask2) = idb.parse_binary_pattern("8B ?? 8B ??").unwrap(); + assert_eq!(bytes2, vec![0x8B, 0xFF, 0x8B, 0xFF]); + assert_eq!(mask2, vec![0xFF, 0x00, 0xFF, 0x00]); +} + +fn test_find_binary() { + const FILENAME: &str = "Practical Malware Analysis Lab 01-01.dll_"; + let dir = TempDir::new("idalib-rs-tests").unwrap(); + let dst = dir.path().join(FILENAME); + let src = tests::get_test_file_path(FILENAME); + std::fs::copy(&src, &dst).unwrap(); + + let idb = IDB::open(dst).unwrap(); + + let bytes = vec![0x8B, 0xC1, 0x8B, 0x4C]; + let mask = vec![0xFF, 0xFF, 0xFF, 0xFF]; + let result = idb.find_binary_range(0x10001000..0x10002000, &bytes, &mask); + assert_eq!(result, Some(0x10001000)); + + let bytes2 = vec![0x8B, 0x00, 0x8B, 0x00]; + let mask2 = vec![0xFF, 0x00, 0xFF, 0x00]; + let result2 = idb.find_binary_range(0x10001000..0x10002000, &bytes2, &mask2); + assert_eq!(result2, Some(0x10001000)); + + let bytes3 = vec![0xB8, 0xF8, 0x11]; + let mask3 = vec![0xFF, 0xFF, 0xFF]; + let result3 = idb.find_binary_range(0x10001000..0x10002000, &bytes3, &mask3); + assert_eq!(result3, Some(0x10001010)); +} + +fn main() { + test_find_text(); + test_find_imm(); + test_find_bytes(); + test_parse_binary_pattern(); + test_find_binary(); +} diff --git a/idalib/tests/test_segments.rs b/idalib/tests/test_segments.rs new file mode 100644 index 0000000000..7ead03f840 --- /dev/null +++ b/idalib/tests/test_segments.rs @@ -0,0 +1,214 @@ +use tempdir::TempDir; + +use idalib::idb::IDB; +use idalib::segment::{self, SegmentPermissions, SegmentType}; +#[path = "../src/tests.rs"] +mod tests; + +#[derive(Debug, Clone, PartialEq)] +struct ExpectedSegment { + name: String, + start_addr: u64, + end_addr: u64, + segment_type: SegmentType, + permissions: SegmentPermissions, + bitness: usize, + is_code: bool, + is_data: bool, + is_executable: bool, + is_writable: bool, + is_readable: bool, + is_visible: bool, +} + +fn test_segments() { + const FILENAME: &str = "Practical Malware Analysis Lab 01-01.dll_"; + let dir = TempDir::new("idalib-rs-tests").unwrap(); + let dst = dir.path().join(FILENAME); + let src = tests::get_test_file_path(FILENAME); + std::fs::copy(&src, &dst).unwrap(); + + let idb = IDB::open(dst).unwrap(); + + let expected_segments: Vec = vec![ + ExpectedSegment { + name: ".text".to_string(), + start_addr: 0x10001000, + end_addr: 0x10002000, + segment_type: SegmentType::CODE, + permissions: SegmentPermissions::EXEC | SegmentPermissions::READ, + bitness: 1, + is_code: true, + is_data: false, + is_executable: true, + is_writable: false, + is_readable: true, + is_visible: true, + }, + ExpectedSegment { + name: ".idata".to_string(), + start_addr: 0x10002000, + end_addr: 0x1000205C, + segment_type: SegmentType::XTRN, + permissions: SegmentPermissions::READ, + bitness: 1, + is_code: false, + is_data: false, + is_executable: false, + is_writable: false, + is_readable: true, + is_visible: true, + }, + ExpectedSegment { + name: ".rdata".to_string(), + start_addr: 0x1000205C, + end_addr: 0x10026000, + segment_type: SegmentType::DATA, + permissions: SegmentPermissions::READ, + bitness: 1, + is_code: false, + is_data: true, + is_executable: false, + is_writable: false, + is_readable: true, + is_visible: true, + }, + ExpectedSegment { + name: ".data".to_string(), + start_addr: 0x10026000, + end_addr: 0x10027000, + segment_type: SegmentType::DATA, + permissions: SegmentPermissions::WRITE | SegmentPermissions::READ, + bitness: 1, + is_code: false, + is_data: true, + is_executable: false, + is_writable: true, + is_readable: true, + is_visible: true, + }, + ]; + + let mut actual_segments = Vec::new(); + for (i, (_, segment)) in idb.segments().enumerate() { + let name = segment.name().unwrap_or(format!("unnamed_{}", i)); + actual_segments.push(ExpectedSegment { + name: name.clone(), + start_addr: segment.start_address(), + end_addr: segment.end_address(), + segment_type: segment.r#type(), + permissions: segment.permissions(), + bitness: segment.bitness(), + is_code: segment.is_code_segment(), + is_data: segment.is_data_segment(), + is_executable: segment.permissions().is_executable(), + is_writable: segment.permissions().is_writable(), + is_readable: segment.permissions().is_readable(), + is_visible: segment.is_visible(), + }); + } + + assert_eq!( + expected_segments.len(), + actual_segments.len(), + "Expected {} segments, found {}", + expected_segments.len(), + actual_segments.len() + ); + + for (expected, actual) in expected_segments.iter().zip(actual_segments.iter()) { + assert_eq!(expected.name, actual.name, "Segment name mismatch"); + assert_eq!( + expected.start_addr, actual.start_addr, + "Start address mismatch for segment {}", + expected.name + ); + assert_eq!( + expected.end_addr, actual.end_addr, + "End address mismatch for segment {}", + expected.name + ); + assert_eq!( + expected.segment_type, actual.segment_type, + "Segment type mismatch for segment {}", + expected.name + ); + assert_eq!( + expected.permissions, actual.permissions, + "Permissions mismatch for segment {}", + expected.name + ); + assert_eq!( + expected.bitness, actual.bitness, + "Bitness mismatch for segment {}", + expected.name + ); + assert_eq!( + expected.is_code, actual.is_code, + "is_code mismatch for segment {}", + expected.name + ); + assert_eq!( + expected.is_data, actual.is_data, + "is_data mismatch for segment {}", + expected.name + ); + assert_eq!( + expected.is_executable, actual.is_executable, + "is_executable mismatch for segment {}", + expected.name + ); + assert_eq!( + expected.is_writable, actual.is_writable, + "is_writable mismatch for segment {}", + expected.name + ); + assert_eq!( + expected.is_readable, actual.is_readable, + "is_readable mismatch for segment {}", + expected.name + ); + assert_eq!( + expected.is_visible, actual.is_visible, + "is_visible mismatch for segment {}", + expected.name + ); + } +} + +fn test_fileregion_functions() { + const FILENAME: &str = "Practical Malware Analysis Lab 01-01.dll_"; + let dir = TempDir::new("idalib-rs-tests").unwrap(); + let dst = dir.path().join(FILENAME); + let src = tests::get_test_file_path(FILENAME); + std::fs::copy(&src, &dst).unwrap(); + + let idb = IDB::open(dst).unwrap(); + + // Test get_fileregion_offset for a known address + // .text segment starts at 0x10001000 + let offset = idb.get_fileregion_offset(0x10001000); + assert!( + offset >= 0, + "get_fileregion_offset should return a non-negative offset for valid address" + ); + + // Test get_fileregion_ea with the offset we just got + let ea = idb.get_fileregion_ea(offset); + assert_eq!( + ea, 0x10001000, + "get_fileregion_ea should map back to original address" + ); + + // Test with unmappable address (should return -1) + let invalid_offset = idb.get_fileregion_offset(0x99999999); + assert_eq!( + invalid_offset, -1, + "get_fileregion_offset should return -1 for unmappable address" + ); +} + +fn main() { + test_segments(); + test_fileregion_functions(); +} diff --git a/idalib/tests/test_strings.rs b/idalib/tests/test_strings.rs new file mode 100644 index 0000000000..f10017b854 --- /dev/null +++ b/idalib/tests/test_strings.rs @@ -0,0 +1,53 @@ +use tempdir::TempDir; + +use idalib::idb::IDB; +#[path = "../src/tests.rs"] +mod tests; + +// .data:10026038 ; CHAR Name[] +// .data:10026038 Name db 'SADFHUHF',0 ; DATA XREF: DllMain(x,x,x)+38↑o +// .data:10026038 ; DllMain(x,x,x)+57↑o + +fn test_get_strings() { + const FILENAME: &str = "Practical Malware Analysis Lab 01-01.dll_"; + let dir = TempDir::new("idalib-rs-tests").unwrap(); + let dst = dir.path().join(FILENAME); + let src = tests::get_test_file_path(FILENAME); + std::fs::copy(&src, &dst).unwrap(); + + let idb = IDB::open(dst).unwrap(); + + assert!(idb.is_loaded(0x10026038)); + assert!(idb.is_mapped(0x10026038)); + + let string_content = idb.get_string_at(0x10026038); + assert!(string_content.is_some()); + assert_eq!(string_content.unwrap(), "SADFHUHF"); + + let strings: Vec<_> = idb.strings().iter().map(|(_, s)| s).collect(); + assert_eq!( + strings, + vec![ + "CloseHandle\0", + "Sleep\0", + "CreateProcessA\0", + "CreateMutexA\0", + "OpenMutexA\0", + "KERNEL32.dll\0", + "WS2_32.dll\0", + "strncmp\0", + "MSVCRT.dll\0", + "_initterm\0", + "malloc\0", + "_adjust_fdiv\0", + "sleep\0", + "hello\0", + "127.26.152.13\0", + "SADFHUHF\0" + ] + ); +} + +fn main() { + test_get_strings(); +} diff --git a/idalib/tests/test_x86_operands.rs b/idalib/tests/test_x86_operands.rs new file mode 100644 index 0000000000..9325d88d0b --- /dev/null +++ b/idalib/tests/test_x86_operands.rs @@ -0,0 +1,255 @@ +use tempdir::TempDir; + +use idalib::idb::IDB; +use idalib::insn::AddressingMode; +#[path = "../src/tests.rs"] +mod tests; + +// .text:10001000 sub_10001000 proc near +// .text:10001000 arg_0= dword ptr 4 +// .text:10001000 +// .text:10001000 8B C1 mov eax, ecx +// .text:10001002 8B 4C 24 04 mov ecx, [esp+arg_0] +// .text:10001006 8A 11 mov dl, [ecx] +// .text:10001008 88 10 mov [eax], dl +// .text:1000100A C2 04 00 retn 4 +// +// 10001000 8B C1 8B 4C 24 04 8A 11 88 10 C2 04 00 90 90 90 ...L$........... + +fn test_operand_has_sib() { + const FILENAME: &str = "Practical Malware Analysis Lab 01-01.dll_"; + let dir = TempDir::new("idalib-rs-tests").unwrap(); + let dst = dir.path().join(FILENAME); + let src = tests::get_test_file_path(FILENAME); + std::fs::copy(&src, &dst).unwrap(); + + let idb = IDB::open(dst).unwrap(); + + // mov ecx, [esp+arg_0] - this has SIB byte (0x24) + let insn_with_sib = idb.insn_at(0x10001002).unwrap(); + let op_with_sib = insn_with_sib.operand(1).unwrap(); + assert!(op_with_sib.has_sib(), "Operand should have SIB byte"); + assert_eq!(op_with_sib.addressing_mode(), AddressingMode::Sib); + + // mov eax, ecx - no SIB + let insn_no_sib = idb.insn_at(0x10001000).unwrap(); + let op_no_sib = insn_no_sib.operand(1).unwrap(); + assert!(!op_no_sib.has_sib(), "Operand should not have SIB byte"); + assert_eq!(op_no_sib.addressing_mode(), AddressingMode::Base); +} + +fn test_operand_sib_byte() { + const FILENAME: &str = "Practical Malware Analysis Lab 01-01.dll_"; + let dir = TempDir::new("idalib-rs-tests").unwrap(); + let dst = dir.path().join(FILENAME); + let src = tests::get_test_file_path(FILENAME); + std::fs::copy(&src, &dst).unwrap(); + + let idb = IDB::open(dst).unwrap(); + + // mov ecx, [esp+arg_0] + // SIB byte is 0x24: scale=0, index=4, base=4 + let insn = idb.insn_at(0x10001002).unwrap(); + let op = insn.operand(1).unwrap(); + + if op.has_sib() { + let sib_byte = op.sib_byte(); + assert_eq!(sib_byte, 0x24, "SIB byte should be 0x24"); + } +} + +fn test_operand_has_displacement() { + const FILENAME: &str = "Practical Malware Analysis Lab 01-01.dll_"; + let dir = TempDir::new("idalib-rs-tests").unwrap(); + let dst = dir.path().join(FILENAME); + let src = tests::get_test_file_path(FILENAME); + std::fs::copy(&src, &dst).unwrap(); + + let idb = IDB::open(dst).unwrap(); + + // mov ecx, [esp+arg_0] - has displacement (0x04) + let insn_with_displ = idb.insn_at(0x10001002).unwrap(); + let op_with_displ = insn_with_displ.operand(1).unwrap(); + assert!( + op_with_displ.has_displacement(), + "Operand should have displacement" + ); + + // mov dl, [ecx] - no displacement + let insn_no_displ = idb.insn_at(0x10001006).unwrap(); + let op_no_displ = insn_no_displ.operand(1).unwrap(); + assert!( + !op_no_displ.has_displacement(), + "Operand should not have displacement" + ); +} + +fn test_x86_base_reg() { + const FILENAME: &str = "Practical Malware Analysis Lab 01-01.dll_"; + let dir = TempDir::new("idalib-rs-tests").unwrap(); + let dst = dir.path().join(FILENAME); + let src = tests::get_test_file_path(FILENAME); + std::fs::copy(&src, &dst).unwrap(); + + let idb = IDB::open(dst).unwrap(); + + // mov ecx, [esp+arg_0] + // Base should be ESP (register 4) + let insn = idb.insn_at(0x10001002).unwrap(); + let op = insn.operand(1).unwrap(); + + match insn.x86_base_reg(&op) { + Some(base_reg) => { + // ESP is typically register 4 + assert_eq!(base_reg, 4, "Base register should be ESP (4)"); + } + None => panic!("Should have found a base register"), + } +} + +fn test_x86_index_reg() { + const FILENAME: &str = "Practical Malware Analysis Lab 01-01.dll_"; + let dir = TempDir::new("idalib-rs-tests").unwrap(); + let dst = dir.path().join(FILENAME); + let src = tests::get_test_file_path(FILENAME); + std::fs::copy(&src, &dst).unwrap(); + + let idb = IDB::open(dst).unwrap(); + + // mov ecx, [esp+arg_0] + // Index might be none or ESP depending on how IDA interprets the SIB byte + // ESP cannot be used as an index register in x86, so this is valid to be None + let insn = idb.insn_at(0x10001002).unwrap(); + let op = insn.operand(1).unwrap(); + + // Just verify the function doesn't crash; the result may be None or Some(4) + let _index_reg = insn.x86_index_reg(&op); +} + +fn test_x86_scale() { + const FILENAME: &str = "Practical Malware Analysis Lab 01-01.dll_"; + let dir = TempDir::new("idalib-rs-tests").unwrap(); + let dst = dir.path().join(FILENAME); + let src = tests::get_test_file_path(FILENAME); + std::fs::copy(&src, &dst).unwrap(); + + let idb = IDB::open(dst).unwrap(); + + // mov ecx, [esp+arg_0] + // Scale should be 1 (scale bits = 00) + let insn = idb.insn_at(0x10001002).unwrap(); + let op = insn.operand(1).unwrap(); + + match insn.x86_scale(&op) { + Some(scale) => { + assert_eq!(scale, 1, "Scale should be 1"); + } + None => panic!("Should have found a scale value"), + } +} + +fn test_sib_base() { + const FILENAME: &str = "Practical Malware Analysis Lab 01-01.dll_"; + let dir = TempDir::new("idalib-rs-tests").unwrap(); + let dst = dir.path().join(FILENAME); + let src = tests::get_test_file_path(FILENAME); + std::fs::copy(&src, &dst).unwrap(); + + let idb = IDB::open(dst).unwrap(); + + // mov ecx, [esp+arg_0] + // SIB base should be ESP (4) + let insn = idb.insn_at(0x10001002).unwrap(); + let op = insn.operand(1).unwrap(); + + if op.has_sib() { + match insn.sib_base(&op) { + Some(base) => { + assert_eq!(base, 4, "SIB base should be ESP (4)"); + } + None => panic!("Should have found SIB base register"), + } + } +} + +fn test_sib_index() { + const FILENAME: &str = "Practical Malware Analysis Lab 01-01.dll_"; + let dir = TempDir::new("idalib-rs-tests").unwrap(); + let dst = dir.path().join(FILENAME); + let src = tests::get_test_file_path(FILENAME); + std::fs::copy(&src, &dst).unwrap(); + + let idb = IDB::open(dst).unwrap(); + + // mov ecx, [esp+arg_0] + // SIB index might be None or ESP depending on IDA's interpretation + // ESP cannot be used as an index in x86 + let insn = idb.insn_at(0x10001002).unwrap(); + let op = insn.operand(1).unwrap(); + + if op.has_sib() { + // Just verify the function doesn't crash + let _index = insn.sib_index(&op); + } +} + +fn test_sib_scale() { + const FILENAME: &str = "Practical Malware Analysis Lab 01-01.dll_"; + let dir = TempDir::new("idalib-rs-tests").unwrap(); + let dst = dir.path().join(FILENAME); + let src = tests::get_test_file_path(FILENAME); + std::fs::copy(&src, &dst).unwrap(); + + let idb = IDB::open(dst).unwrap(); + + // mov ecx, [esp+arg_0] + // SIB scale should be 1 (scale bits = 00) + let insn = idb.insn_at(0x10001002).unwrap(); + let op = insn.operand(1).unwrap(); + + if op.has_sib() { + match insn.sib_scale(&op) { + Some(scale) => { + assert_eq!(scale, 1, "SIB scale should be 1"); + } + None => panic!("Should have found SIB scale value"), + } + } +} + +fn test_operand_displacement() { + const FILENAME: &str = "Practical Malware Analysis Lab 01-01.dll_"; + let dir = TempDir::new("idalib-rs-tests").unwrap(); + let dst = dir.path().join(FILENAME); + let src = tests::get_test_file_path(FILENAME); + std::fs::copy(&src, &dst).unwrap(); + + let idb = IDB::open(dst).unwrap(); + + // mov ecx, [esp+arg_0] + // Displacement should be 0x04 (arg_0) + let insn = idb.insn_at(0x10001002).unwrap(); + let op = insn.operand(1).unwrap(); + + if op.has_displacement() { + match op.addr() { + Some(addr) => { + assert_eq!(addr, 0x04, "Displacement should be 0x04"); + } + None => panic!("Should have found displacement address"), + } + } +} + +fn main() { + test_operand_has_sib(); + test_operand_sib_byte(); + test_operand_has_displacement(); + test_x86_base_reg(); + test_x86_index_reg(); + test_x86_scale(); + test_sib_base(); + test_sib_index(); + test_sib_scale(); + test_operand_displacement(); +} diff --git a/idalib/tests/test_xrefs.rs b/idalib/tests/test_xrefs.rs new file mode 100644 index 0000000000..7a60d1ab2a --- /dev/null +++ b/idalib/tests/test_xrefs.rs @@ -0,0 +1,29 @@ +use tempdir::TempDir; + +use idalib::idb::IDB; +#[path = "../src/tests.rs"] +mod tests; + +fn test_xrefs() { + const FILENAME: &str = "Practical Malware Analysis Lab 01-01.dll_"; + let dir = TempDir::new("idalib-rs-tests").unwrap(); + let dst = dir.path().join(FILENAME); + let src = tests::get_test_file_path(FILENAME); + std::fs::copy(&src, &dst).unwrap(); + + let idb = IDB::open(dst).unwrap(); + + let xrefs_to: Vec<_> = idb.xrefs_to(0x10026038).map(|x| x.from()).collect(); + assert_eq!(xrefs_to, vec![0x10001048, 0x10001067]); + // 0. instruction at 0x10001048 + // 1. instruction at 0x10001067 + + let xrefs_from: Vec<_> = idb.xrefs_from(0x10001048).map(|x| x.to()).collect(); + assert_eq!(xrefs_from, vec![0x1000104d, 0x10026038]); + // 0. next instruction + // 1. mutex +} + +fn main() { + test_xrefs(); +} diff --git a/tests/Practical Malware Analysis Lab 01-01.dll_ b/tests/Practical Malware Analysis Lab 01-01.dll_ new file mode 100644 index 0000000000..5a65e79a95 Binary files /dev/null and b/tests/Practical Malware Analysis Lab 01-01.dll_ differ