Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ int main() {
void* vtable_addr;
s2binlib_find_vtable("server", "CBaseEntity", &vtable_addr);

// Dump every printable ASCII string and its RVA to a JSON file
s2binlib_dump_strings_to_json("server", "server_strings.json");

// Free after use, this will only release the file bytes in memory
// Dumped xref and other information will still be cached
s2binlib_unload_all_binaries();
Expand Down Expand Up @@ -139,4 +142,4 @@ Or use the `CreateInterface` function for dynamic loading:
// windows example
S2CreateInterfaceFn createInterface = (S2CreateInterfaceFn)GetProcAddress(hDll, "S2BinLib_CreateInterface");
auto s2binlib = createInterface(S2BINLIB_INTERFACE_NAME);
```
```
6 changes: 6 additions & 0 deletions headers/s2binlib_static.h
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,12 @@ int s2binlib_find_string_rva(const char* binary_name, const char* string, void**
/// @return 0 on success, negative error code on failure
int s2binlib_find_string(const char* binary_name, const char* string, void** result);

/// Dump all printable ASCII strings and their relative virtual addresses to a JSON file
/// @param binary_name Name of the binary to scan
/// @param output_path UTF-8 path of the JSON file to create or overwrite
/// @return 0 on success, negative error code on failure
int s2binlib_dump_strings_to_json(const char* binary_name, const char* output_path);

// ============================================================================
// Module Base Address Functions
// ============================================================================
Expand Down
1 change: 1 addition & 0 deletions s2binlib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ cpp_demangle = "0.4"
msvc-demangler = "0.10"
serde = { version = "1.0", optional = true, features = ["derive"] }
hashbrown = "0.16.0"
serde_json = "1.0"


[target.'cfg(target_os = "windows")'.dependencies]
Expand Down
105 changes: 95 additions & 10 deletions s2binlib/src/s2binlib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,14 @@
use anyhow::{Result, bail};
use hashbrown::HashMap;
use iced_x86::{Code, Decoder, DecoderOptions, Instruction, OpKind, Register};
use object::{
Object, ObjectSection, ObjectSymbol, SectionKind, read::pe::ImageOptionalHeader,
use object::{Object, ObjectSection, ObjectSymbol, SectionKind, read::pe::ImageOptionalHeader};
use std::{
cell::Cell,
collections::BTreeMap,
fs::{self, File},
io::{BufWriter, Write},
path::{Path, PathBuf},
};
use std::{cell::Cell, fs, path::PathBuf};

use crate::{
VTableInfo, find_pattern_simd, is_executable,
Expand Down Expand Up @@ -58,7 +62,7 @@ pub struct S2BinLib<'a> {
pub(crate) custom_binary_paths_linux: HashMap<String, String>,
pub(crate) vtables: HashMap<String, Vec<VTableInfo>>,
pub(crate) name_to_vtables: HashMap<String, &'a VTableInfo>,
/// Cached ASCII strings: binary_name -> (string_rva -> string)
/// Cached ASCII strings: binary_name -> (string -> string_rva)
pub(crate) strings_cache: HashMap<String, HashMap<String, u64>>,
pub(crate) calls_targets_cache: HashMap<String, Vec<u64>>,
}
Expand All @@ -79,6 +83,22 @@ fn read_int64(data: &[u8], offset: u64) -> i64 {
rvalue
}

fn write_strings_to_json<P: AsRef<Path>>(
strings: &HashMap<String, u64>,
output_path: P,
) -> Result<()> {
let ordered_strings: BTreeMap<&str, u64> = strings
.iter()
.map(|(string, rva)| (string.as_str(), *rva))
.collect();

let mut output = BufWriter::new(File::create(output_path)?);
serde_json::to_writer_pretty(&mut output, &ordered_strings)?;
output.flush()?;

Ok(())
}

impl<'a> S2BinLib<'a> {
fn get_os_name(&self) -> String {
match self.os.as_str() {
Expand Down Expand Up @@ -1106,6 +1126,24 @@ impl<'a> S2BinLib<'a> {
Ok(())
}

/// Dump all printable ASCII strings and their RVAs to a JSON file.
///
/// The binary is scanned before every write so the output always reflects
/// the currently loaded binary. JSON object keys are sorted to make dumps
/// deterministic across runs.
pub fn dump_strings_to_json<P: AsRef<Path>>(
&mut self,
binary_name: &str,
output_path: P,
) -> Result<()> {
self.dump_strings(binary_name)?;

let strings = self
.get_strings(binary_name)
.ok_or_else(|| anyhow::anyhow!("Strings were not cached."))?;
write_strings_to_json(strings, output_path)
}

pub fn get_strings(&self, binary_name: &str) -> Option<&HashMap<String, u64>> {
self.strings_cache.get(binary_name)
}
Expand Down Expand Up @@ -1522,12 +1560,18 @@ impl<'a> S2BinLib<'a> {
}

pub fn find_func_start_rva(&self, binary_name: &str, include_rva: u64) -> Result<u64> {
Ok(std::cmp::max(
self.find_xref_func_start_rva(binary_name, include_rva)?,
self.find_vfunc_start_rva(binary_name, include_rva)
.unwrap()
.2,
))
let xref_start = self.find_xref_func_start_rva(binary_name, include_rva)?;
let vfunc_start = self
.find_vfunc_start_rva(binary_name, include_rva)
.map(|(_, _, rva)| rva)
.unwrap_or_default();
let func_start = std::cmp::max(xref_start, vfunc_start);

if func_start == 0 {
bail!("No function found.");
}

Ok(func_start)
}

pub fn find_func_start(&self, binary_name: &str, include_rva: u64) -> Result<u64> {
Expand Down Expand Up @@ -1612,3 +1656,44 @@ impl<'a> S2BinLib<'a> {
)
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn strings_json_is_sorted_and_contains_rvas() {
let mut strings = HashMap::new();
strings.insert("zeta".to_string(), 0x200);
strings.insert("alpha".to_string(), 0x100);
let output_path =
std::env::temp_dir().join(format!("s2binlib-strings-{}.json", std::process::id()));

write_strings_to_json(&strings, &output_path).unwrap();

let output = fs::read_to_string(&output_path).unwrap();
let parsed: BTreeMap<String, u64> = serde_json::from_str(&output).unwrap();
assert_eq!(parsed.get("alpha"), Some(&0x100));
assert_eq!(parsed.get("zeta"), Some(&0x200));
assert!(output.find("alpha").unwrap() < output.find("zeta").unwrap());

fs::remove_file(output_path).unwrap();
}

#[test]
fn find_func_start_rva_falls_back_to_xref_without_vtable_match() {
let mut library = S2BinLib::new(".", "csgo", "windows");
library
.calls_targets_cache
.insert("server".to_string(), vec![0x100]);

assert_eq!(library.find_func_start_rva("server", 0x200).unwrap(), 0x100);
}

#[test]
fn find_func_start_rva_returns_error_without_candidates() {
let library = S2BinLib::new(".", "csgo", "windows");

assert!(library.find_func_start_rva("server", 0x200).is_err());
}
}
32 changes: 30 additions & 2 deletions s2binlib_binding/src/c_bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
***********************************************************************************/

use crate::compat::s2binlib003::{PatternScanCallback, S2BinLib003};
use std::ffi::{c_char, c_void};
use std::ffi::{CStr, c_char, c_void};
use std::sync::Mutex;

// Safety: S2BinLib003 contains a raw pointer to a vtable, but the vtable is static
Expand Down Expand Up @@ -370,6 +370,34 @@ wrap_method_mut!(
result: *mut *mut c_void
);

/// Dump all printable ASCII strings and their RVAs to a JSON file.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn s2binlib_dump_strings_to_json(
binary_name: *const c_char,
output_path: *const c_char,
) -> i32 {
if binary_name.is_null() || output_path.is_null() {
return -2;
}

let binary_name = unsafe {
match CStr::from_ptr(binary_name).to_str() {
Ok(value) => value,
Err(_) => return -2,
}
};
let output_path = unsafe {
match CStr::from_ptr(output_path).to_str() {
Ok(value) => value,
Err(_) => return -2,
}
};

with_global_instance!(|instance: &mut S2BinLib003| {
instance.dump_strings_to_json(binary_name, output_path)
})
}

// ============================================================================
// Module Base Address Functions
// ============================================================================
Expand Down Expand Up @@ -693,4 +721,4 @@ wrap_method_mut!(
func_rva: u64,
result_out: *mut c_char,
result_out_size: usize
);
);
19 changes: 19 additions & 0 deletions s2binlib_binding/src/compat/s2binlib003.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,25 @@ impl S2BinLib003 {
s2binlib: None,
}
}

pub(crate) fn dump_strings_to_json(&mut self, binary_name: &str, output_path: &str) -> i32 {
let Some(s2binlib) = self.s2binlib.as_mut() else {
c_debug!("Error -1: S2BinLib003 is not initialized");
return -1;
};

if !s2binlib.is_binary_loaded(binary_name) {
s2binlib.load_binary(binary_name);
}

match s2binlib.dump_strings_to_json(binary_name, output_path) {
Ok(()) => 0,
Err(error) => {
c_debug!("Error -4: Failed to dump strings to JSON: {}", error);
-4
}
}
}
}

#[repr(C)]
Expand Down
Loading