From f574fb2160ace857ac60e1a569c288405686b707 Mon Sep 17 00:00:00 2001 From: samyyc Date: Thu, 30 Oct 2025 19:56:18 +0800 Subject: [PATCH] Support finding networkvar statechanged vfunc --- Cargo.toml | 2 +- s2binlib.h | 117 +++++++++++++++++++++++++++ src/c_bindings.rs | 199 ++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 11 +-- src/s2binlib.rs | 42 +++++++++- 5 files changed, 362 insertions(+), 9 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 88a868c..009b419 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2024" [lib] -crate-type = ["staticlib", "dylib"] +crate-type = ["staticlib"] [profile.release] opt-level = 3 diff --git a/s2binlib.h b/s2binlib.h index 1020378..4cc5156 100644 --- a/s2binlib.h +++ b/s2binlib.h @@ -389,6 +389,70 @@ extern "C" */ int s2binlib_find_vtable_nested_2(const char *binary_name, const char *class1_name, const char *class2_name, void **result); + /** + * Get the number of virtual functions in a vtable + * + * Returns the count of virtual functions (vfuncs) in the specified vtable. + * This counts valid function pointers in the vtable until it encounters a null + * or invalid pointer. + * + * If the binary is not yet loaded, it will be loaded automatically. + * + * @param binary_name Name of the binary to search (e.g., "server", "client") + * @param vtable_name Name of the vtable/class to search for + * @param result Pointer to store the resulting count of virtual functions + * + * @return 0 - Success, result contains the vfunc count + * -1 - S2BinLib not initialized + * -2 - Invalid input (null pointer or invalid UTF-8) + * -4 - Operation failed (vtable not found or other error) + * -5 - Failed to acquire lock + * + * @example + * size_t vfunc_count; + * int result = s2binlib_get_vtable_vfunc_count("server", "CBaseEntity", &vfunc_count); + * if (result == 0) { + * printf("VTable has %zu virtual functions\n", vfunc_count); + * } + */ + int s2binlib_get_vtable_vfunc_count(const char *binary_name, const char *vtable_name, size_t *result); + + /** + * Get the number of virtual functions in a vtable by virtual address + * + * Returns the count of virtual functions (vfuncs) in a vtable at the specified + * virtual address. This counts valid function pointers in the vtable until it + * encounters a null or invalid pointer. + * + * Unlike s2binlib_get_vtable_vfunc_count, this function takes a virtual address + * directly instead of looking up the vtable by name. + * + * If the binary is not yet loaded, it will be loaded automatically. + * + * @param binary_name Name of the binary (e.g., "server", "client") + * @param vtable_va Virtual address of the vtable + * @param result Pointer to store the resulting count of virtual functions + * + * @return 0 - Success, result contains the vfunc count + * -1 - S2BinLib not initialized + * -2 - Invalid input (null pointer or invalid UTF-8) + * -4 - Operation failed (invalid address or other error) + * -5 - Failed to acquire lock + * + * @example + * void* vtable_va; + * // First get the vtable virtual address + * s2binlib_find_vtable_va("server", "CBaseEntity", &vtable_va); + * + * // Then count its virtual functions + * size_t vfunc_count; + * int result = s2binlib_get_vtable_vfunc_count_by_va("server", (uint64_t)vtable_va, &vfunc_count); + * if (result == 0) { + * printf("VTable has %zu virtual functions\n", vfunc_count); + * } + */ + int s2binlib_get_vtable_vfunc_count_by_va(const char *binary_name, uint64_t vtable_va, size_t *result); + /** * Find a symbol by name in the specified binary * @@ -1123,6 +1187,59 @@ extern "C" */ int s2binlib_follow_xref_va_to_va(const char *binary_name, uint64_t va, uint64_t *target_va_out); + /** + * @brief Find the NetworkVar_StateChanged vtable index by virtual address + * + * This function scans the vtable at the given virtual address to find the + * index of the NetworkVar_StateChanged virtual function. It analyzes each + * virtual function in the vtable looking for the specific instruction pattern + * that identifies the StateChanged function (cmp dword ptr [reg+56], 0xFF). + * + * @param vtable_va Virtual address of the vtable to analyze + * @param result Pointer to store the resulting index (as uint64_t) + * + * @return 0 on success (index written to result) + * -1 if S2BinLib not initialized + * -2 if invalid parameters + * -4 if NetworkVar_StateChanged not found in vtable + * -5 if internal error + * + * @example + * uint64_t index; + * int result = s2binlib_find_networkvar_vtable_statechanged_va(0x140001000, &index); + * if (result == 0) { + * printf("StateChanged index: %llu\n", index); + * } + */ + int s2binlib_find_networkvar_vtable_statechanged_va(uint64_t vtable_va, uint64_t *result); + + /** + * @brief Find the NetworkVar_StateChanged vtable index by memory address + * + * This function converts the runtime memory address to a virtual address, + * then scans the vtable to find the index of the NetworkVar_StateChanged + * virtual function. It analyzes each virtual function in the vtable looking + * for the specific instruction pattern that identifies the StateChanged function. + * + * @param vtable_mem_address Runtime memory address of the vtable + * @param result Pointer to store the resulting index (as uint64_t) + * + * @return 0 on success (index written to result) + * -1 if S2BinLib not initialized + * -2 if invalid parameters + * -3 if address conversion failed + * -4 if NetworkVar_StateChanged not found in vtable + * -5 if internal error + * + * @example + * uint64_t index; + * int result = s2binlib_find_networkvar_vtable_statechanged(vtable_ptr, &index); + * if (result == 0) { + * printf("StateChanged index: %llu\n", index); + * } + */ + int s2binlib_find_networkvar_vtable_statechanged(uint64_t vtable_mem_address, uint64_t *result); + #ifdef __cplusplus } #endif diff --git a/src/c_bindings.rs b/src/c_bindings.rs index 6197e8f..6c9e505 100644 --- a/src/c_bindings.rs +++ b/src/c_bindings.rs @@ -1215,6 +1215,86 @@ pub extern "C" fn s2binlib_get_vtable_vfunc_count( } } +/// Get the number of virtual functions in a vtable by virtual address +/// +/// Returns the count of virtual functions (vfuncs) in a vtable at the specified +/// virtual address. This counts valid function pointers in the vtable until it +/// encounters a null or invalid pointer. +/// +/// Unlike get_vtable_vfunc_count, this function takes a virtual address directly +/// instead of looking up the vtable by name. +/// +/// If the binary is not yet loaded, it will be loaded automatically. +/// +/// # Parameters +/// * `binary_name` - Name of the binary (e.g., "server", "client") (null-terminated C string) +/// * `vtable_va` - Virtual address of the vtable +/// * `result` - Pointer to store the resulting count of virtual functions +/// +/// # Returns +/// * `0` - Success, result contains the vfunc count +/// * `-1` - S2BinLib not initialized +/// * `-2` - Invalid input (null pointer or invalid UTF-8) +/// * `-4` - Operation failed (invalid address or other error) +/// * `-5` - Failed to acquire lock +/// +/// # Safety +/// This function is unsafe because it dereferences raw pointers. +/// The caller must ensure that the pointers are valid. +/// +/// # Example +/// ```c +/// void* vtable_va; +/// // First get the vtable virtual address +/// s2binlib_find_vtable_va("server", "CBaseEntity", &vtable_va); +/// +/// // Then count its virtual functions +/// size_t vfunc_count; +/// int result = s2binlib_get_vtable_vfunc_count_by_va("server", (uint64_t)vtable_va, &vfunc_count); +/// if (result == 0) { +/// printf("VTable has %zu virtual functions\n", vfunc_count); +/// } +/// ``` +#[unsafe(no_mangle)] +pub extern "C" fn s2binlib_get_vtable_vfunc_count_by_va( + binary_name: *const c_char, + vtable_va: u64, + result: *mut usize, +) -> i32 { + unsafe { + if binary_name.is_null() || result.is_null() { + return -2; + } + + let binary_name_str = match CStr::from_ptr(binary_name).to_str() { + Ok(s) => s, + Err(_) => return -2, + }; + + let mut s2binlib_guard = match S2BINLIB.lock() { + Ok(guard) => guard, + Err(_) => return -5, + }; + + let s2binlib = match s2binlib_guard.as_mut() { + Some(lib) => lib, + None => return -1, + }; + + if !s2binlib.is_binary_loaded(binary_name_str) { + s2binlib.load_binary(binary_name_str); + } + + match s2binlib.get_vtable_vfunc_count_by_va(binary_name_str, vtable_va) { + Ok(count) => { + *result = count; + 0 + } + Err(_) => -4, + } + } +} + /// Pattern scan and return the virtual address /// /// Scans for a byte pattern in the specified binary and returns the virtual address (VA) @@ -2961,3 +3041,122 @@ pub extern "C" fn s2binlib_follow_xref_va_to_va( } } +/// Find the NetworkVar_StateChanged vtable index by virtual address +/// +/// This function scans the vtable at the given virtual address to find the +/// index of the NetworkVar_StateChanged virtual function. It analyzes each +/// virtual function in the vtable looking for the specific instruction pattern +/// that identifies the StateChanged function (cmp dword ptr [reg+56], 0xFF). +/// +/// # Parameters +/// * `vtable_va` - Virtual address of the vtable to analyze +/// * `result` - Pointer to store the resulting index (as u64) +/// +/// # Returns +/// * 0 on success (index written to result) +/// * -1 if S2BinLib not initialized +/// * -2 if invalid parameters +/// * -4 if NetworkVar_StateChanged not found in vtable +/// * -5 if internal error +/// +/// # Safety +/// This function is unsafe because it dereferences raw pointers. +/// +/// # Example +/// ```c +/// uint64_t index; +/// int result = s2binlib_find_networkvar_vtable_statechanged_va(0x140001000, &index); +/// if (result == 0) { +/// printf("StateChanged index: %llu\n", index); +/// } +/// ``` +#[unsafe(no_mangle)] +pub extern "C" fn s2binlib_find_networkvar_vtable_statechanged_va( + vtable_va: u64, + result: *mut u64, +) -> i32 { + unsafe { + if result.is_null() { + return -2; + } + + let s2binlib_guard = match S2BINLIB.lock() { + Ok(guard) => guard, + Err(_) => return -5, + }; + + let s2binlib = match s2binlib_guard.as_ref() { + Some(lib) => lib, + None => return -1, + }; + + match s2binlib.find_networkvar_vtable_statechanged_va(vtable_va) { + Ok(index) => { + *result = index; + 0 + } + Err(_) => -4, + } + } +} + +/// Find the NetworkVar_StateChanged vtable index by memory address +/// +/// This function converts the runtime memory address to a virtual address, +/// then scans the vtable to find the index of the NetworkVar_StateChanged +/// virtual function. It analyzes each virtual function in the vtable looking +/// for the specific instruction pattern that identifies the StateChanged function. +/// +/// # Parameters +/// * `vtable_mem_address` - Runtime memory address of the vtable +/// * `result` - Pointer to store the resulting index (as u64) +/// +/// # Returns +/// * 0 on success (index written to result) +/// * -1 if S2BinLib not initialized +/// * -2 if invalid parameters +/// * -3 if address conversion failed +/// * -4 if NetworkVar_StateChanged not found in vtable +/// * -5 if internal error +/// +/// # Safety +/// This function is unsafe because it dereferences raw pointers. +/// +/// # Example +/// ```c +/// uint64_t index; +/// int result = s2binlib_find_networkvar_vtable_statechanged(vtable_ptr, &index); +/// if (result == 0) { +/// printf("StateChanged index: %llu\n", index); +/// } +/// ``` +#[unsafe(no_mangle)] +pub extern "C" fn s2binlib_find_networkvar_vtable_statechanged( + vtable_mem_address: u64, + result: *mut u64, +) -> i32 { + unsafe { + if result.is_null() { + return -2; + } + + let s2binlib_guard = match S2BINLIB.lock() { + Ok(guard) => guard, + Err(_) => return -5, + }; + + let s2binlib = match s2binlib_guard.as_ref() { + Some(lib) => lib, + None => return -1, + }; + + match s2binlib.find_networkvar_vtable_statechanged(vtable_mem_address) { + Ok(index) => { + *result = index; + 0 + } + Err(_) => -4, + } + } +} + diff --git a/src/lib.rs b/src/lib.rs index d34112a..c310980 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -33,12 +33,13 @@ mod tests { use std::time::Instant; use anyhow::Result; + use iced_x86::{Code, Decoder, DecoderOptions, Mnemonic, OpKind}; use super::*; #[test] fn test_s2binlib() -> Result<()> { - let mut s2binlib = S2BinLib::new("F:/cs2server/game", "csgo", "windows"); + let mut s2binlib = S2BinLib::new("F:/cs2server/game", "csgo", "linux"); s2binlib.load_binary("server"); @@ -47,14 +48,14 @@ mod tests { // s2binlib.load_binary("tier0"); // println!("1"); - // println!("{:X}", s2binlib.find_vtable_va("server", "CTraceFilter")?); + let start = Instant::now(); - let xref = s2binlib.pattern_scan_va("server", "4C 8D 35 ? ? ? ? 77")?; + // let xref = s2binlib.pattern_scan_va("server", "4C 8D 35 ? ? ? ? 77")?; - let duration = start.elapsed(); - println!("Time taken: {:?}", duration); + // let duration = start.elapsed(); + // println!("Time taken: {:?}", duration); // println!("xref {:X}", xref); // println!("follow xref {:X}", s2binlib.follow_xref_va_to_va("server", xref)?); diff --git a/src/s2binlib.rs b/src/s2binlib.rs index 0d52489..3d890ec 100644 --- a/src/s2binlib.rs +++ b/src/s2binlib.rs @@ -21,7 +21,7 @@ use std::{collections::{btree_map::VacantEntry, HashMap}, fs, path::PathBuf}; use anyhow::{anyhow, Result}; use object::{read::pe::ImageOptionalHeader, Object, ObjectSection, ObjectSymbol}; -use iced_x86::{Decoder, DecoderOptions, Instruction, OpKind, Register}; +use iced_x86::{Code, Decoder, DecoderOptions, Instruction, OpKind, Register}; use crate::{find_pattern_simd, is_executable, jit::JitTrampoline, memory::{get_module_base_from_pointer, set_mem_access} }; @@ -537,11 +537,15 @@ impl S2BinLib { pub fn get_vtable_vfunc_count(&self, binary_name: &str, vtable_name: &str) -> Result { - let vtable = self.find_vtable_va(binary_name, vtable_name)?; + let vtable_va = self.find_vtable_va(binary_name, vtable_name)?; + self.get_vtable_vfunc_count_by_va(binary_name, vtable_va) + } + + pub fn get_vtable_vfunc_count_by_va(&self, binary_name: &str, vtable_va: u64) -> Result { let mut offset = 0; loop { - let vfunc_va = self.read_by_va(binary_name, vtable + offset, 8)?; + let vfunc_va = self.read_by_va(binary_name, vtable_va + offset, 8)?; let vfunc_va = u64::from_le_bytes(vfunc_va.try_into().unwrap()); if vfunc_va == 0 || !self.is_valid_executable_va(binary_name, vfunc_va)? { @@ -1008,5 +1012,37 @@ impl S2BinLib { )) } + pub fn find_networkvar_vtable_statechanged_va(&self, vtable_va: u64) -> Result { + + let vfunc_count = self.get_vtable_vfunc_count_by_va("server", vtable_va)?; + + for i in 0..vfunc_count { + let vfunc_va = self.read_by_va("server", vtable_va + i as u64 * 8, 8)?; + let vfunc_va = u64::from_le_bytes(vfunc_va.try_into().unwrap()); + + let bytes = self.read_by_va("server", vfunc_va, 32)?; + + let mut iced = Decoder::with_ip(64, &bytes, 0, DecoderOptions::NONE); + + while iced.can_decode() { + let inst = iced.decode(); + if inst.code() == Code::Cmp_rm32_imm8 { + let mem_displ = inst.memory_displacement32(); + let immediate = inst.immediate8(); + // 56 is NetworkStateChangedData->m_nPathIndex + if mem_displ == 56 && immediate == 255 { + return Ok(i as u64); + } + } + } + + } + Err(anyhow::anyhow!("NetworkVar_StateChanged not found")) + } + + pub fn find_networkvar_vtable_statechanged(&self, vtable_mem_address: u64) -> Result { + let vtable_va = self.mem_address_to_va("server", vtable_mem_address)?; + self.find_networkvar_vtable_statechanged_va(vtable_va) + } } \ No newline at end of file