From 44ff87565c27d718a684f5e3b0f7ea8de42a18d4 Mon Sep 17 00:00:00 2001 From: Devin Date: Fri, 24 Apr 2026 01:49:39 +0000 Subject: [PATCH 1/2] chore: add LICENSE, PR CI workflow, Cargo metadata; fix README placeholders --- .github/workflows/ci.yml | 40 ++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 7 +++++++ LICENSE | 21 +++++++++++++++++++++ README.md | 18 ++++++++++++++---- 4 files changed, 82 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 LICENSE diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..6958c48 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,40 @@ +name: CI + +on: + push: + branches: [main, master] + pull_request: + +env: + CARGO_TERM_COLOR: always + +jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install build deps + run: | + sudo apt-get update + sudo apt-get install -y pkg-config libclang-dev clang + + - name: Install Rust (stable) + uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt, clippy + + - name: Cache cargo + uses: Swatinem/rust-cache@v2 + + - name: Format check + run: cargo fmt --all -- --check + + - name: Clippy (warnings allowed; fails only on errors) + run: cargo clippy --all-targets + + - name: Build + run: cargo build + + - name: Tests + run: cargo test --all diff --git a/Cargo.toml b/Cargo.toml index cd8f5df..6efaeac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,13 @@ version = "0.2.2" edition = "2021" description = "A strong dynamic debugger CLI tool based on Frida" license = "MIT" +rust-version = "1.70" +authors = ["eunhhu"] +repository = "https://github.com/eunhhu/vlitz" +homepage = "https://github.com/eunhhu/vlitz" +readme = "README.md" +keywords = ["frida", "debugger", "cli", "reverse-engineering", "instrumentation"] +categories = ["command-line-utilities", "development-tools::debugging"] [dependencies] clap = { version = "4.5.38", features = ["derive", "unstable-styles"] } diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..cf4d330 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 eunhhu + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 3995e34..3148bc4 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,16 @@ +
+ # Vlitz -A strong dynamic debugger CLI tool based on [Frida](https://frida.re/) +**A strong, cross-platform dynamic debugger CLI tool built on [Frida](https://frida.re/).** + +[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) +[![crates.io](https://img.shields.io/crates/v/vlitz.svg?logo=rust&logoColor=white&color=dea584)](https://crates.io/crates/vlitz) +[![Rust](https://img.shields.io/badge/Rust-1.70+-DEA584?logo=rust&logoColor=white)](https://www.rust-lang.org/) + +
+ +--- ## Overview @@ -25,7 +35,7 @@ Vlitz is a powerful command-line interface tool that leverages Frida's dynamic i ### From Source ```bash -git clone https://github.com/your-username/vlitz.git +git clone https://github.com/eunhhu/vlitz.git cd vlitz cargo build --release ``` @@ -172,7 +182,7 @@ cargo clippy ## License -[Add your license information here] +`vlitz` is released under the [MIT License](LICENSE). ## Acknowledgments @@ -183,7 +193,7 @@ cargo clippy If you encounter any issues or have questions, please: -1. Check the [Issues](https://github.com/your-username/vlitz/issues) page +1. Check the [Issues](https://github.com/eunhhu/vlitz/issues) page 2. Create a new issue with detailed information about your problem 3. Include your operating system, Rust version, and steps to reproduce From cbf574cd2006e53894a66b81080b1ced183edcd5 Mon Sep 17 00:00:00 2001 From: Devin Date: Fri, 24 Apr 2026 01:49:49 +0000 Subject: [PATCH 2/2] style: apply cargo fmt to entire tree --- src/gum/commands/disasm_cmds.rs | 32 +++++++++--------- src/gum/commands/hook_cmds.rs | 19 ++++++----- src/gum/commands/memory_cmds.rs | 7 ++-- src/gum/commands/mod.rs | 20 +++++------ src/gum/commands/nav_cmds.rs | 10 ++++-- src/gum/commands/scan_cmds.rs | 36 +++++++++++++------- src/gum/memory.rs | 60 +++++++++++++++++++++++++++------ src/gum/store.rs | 5 +-- src/gum/vzdata.rs | 10 +++--- 9 files changed, 129 insertions(+), 70 deletions(-) diff --git a/src/gum/commands/disasm_cmds.rs b/src/gum/commands/disasm_cmds.rs index 8660f06..9b2401f 100644 --- a/src/gum/commands/disasm_cmds.rs +++ b/src/gum/commands/disasm_cmds.rs @@ -9,22 +9,24 @@ pub(crate) fn build() -> Vec { cmds.push(Command::new( "disas", "Disassemble instructions at address", - vec!["dis", "u"], // 'u' is common in debuggers for unassemble + vec!["dis", "u"], // 'u' is common in debuggers for unassemble vec![ CommandArg::optional("target", "Address, selector, or function name"), - CommandArg::optional("count", "Number of instructions to disassemble (default 20)"), - ], - vec![ - SubCommand::new( - "func", - "Disassemble an entire function until return", - vec![ - CommandArg::optional("target", "Address or selector of function"), - ], - |c, a| Commander::disas_function(c, a), - ) - .alias("f"), + CommandArg::optional( + "count", + "Number of instructions to disassemble (default 20)", + ), ], + vec![SubCommand::new( + "func", + "Disassemble an entire function until return", + vec![CommandArg::optional( + "target", + "Address or selector of function", + )], + |c, a| Commander::disas_function(c, a), + ) + .alias("f")], Some(|c, a| Commander::disas(c, a)), )); @@ -54,9 +56,7 @@ pub(crate) fn build() -> Vec { patch_subs.push(SubCommand::new( "restore", "Restore original bytes at address", - vec![ - CommandArg::required("target", "Address or selector"), - ], + vec![CommandArg::required("target", "Address or selector")], |c, a| Commander::patch_restore(c, a), )); diff --git a/src/gum/commands/hook_cmds.rs b/src/gum/commands/hook_cmds.rs index 52de234..be95403 100644 --- a/src/gum/commands/hook_cmds.rs +++ b/src/gum/commands/hook_cmds.rs @@ -13,7 +13,10 @@ pub(crate) fn build() -> Vec { "Add a hook to target address or function", vec![ CommandArg::required("target", "Address, selector, or function name"), - CommandArg::optional("options", "Hook options: -e (enter) -l (leave) -a (args) -r (retval) -b (backtrace)"), + CommandArg::optional( + "options", + "Hook options: -e (enter) -l (leave) -a (args) -r (retval) -b (backtrace)", + ), ], |c, a| Commander::hook_add(c, a), ) @@ -24,19 +27,19 @@ pub(crate) fn build() -> Vec { SubCommand::new( "remove", "Remove a hook by ID", - vec![CommandArg::required("id", "Hook ID to remove (e.g., hook_0)")], + vec![CommandArg::required( + "id", + "Hook ID to remove (e.g., hook_0)", + )], |c, a| Commander::hook_remove(c, a), ) .alias("rm"), ); hook_subs.push( - SubCommand::new( - "list", - "List all active hooks", - vec![], - |c, a| Commander::hook_list(c, a), - ) + SubCommand::new("list", "List all active hooks", vec![], |c, a| { + Commander::hook_list(c, a) + }) .alias("ls"), ); diff --git a/src/gum/commands/memory_cmds.rs b/src/gum/commands/memory_cmds.rs index 9be947a..85b2cb7 100644 --- a/src/gum/commands/memory_cmds.rs +++ b/src/gum/commands/memory_cmds.rs @@ -10,7 +10,10 @@ pub(crate) fn build() -> Vec { list_subs.push(SubCommand::new( "modules", "List process modules into Field store", - vec![CommandArg::optional("filter", "Optional name filter (substring)")], + vec![CommandArg::optional( + "filter", + "Optional name filter (substring)", + )], |c, a| Commander::list_modules(c, a), )); list_subs.push(SubCommand::new( @@ -18,7 +21,7 @@ pub(crate) fn build() -> Vec { "List memory ranges into Field store", vec![ CommandArg::optional("protect", "Protection filter, e.g. r-x, rw-"), - CommandArg::optional("filter", "Optional name filter (substring)") + CommandArg::optional("filter", "Optional name filter (substring)"), ], |c, a| Commander::list_ranges(c, a), )); diff --git a/src/gum/commands/mod.rs b/src/gum/commands/mod.rs index 2a2c74a..022ce4c 100644 --- a/src/gum/commands/mod.rs +++ b/src/gum/commands/mod.rs @@ -1,14 +1,14 @@ // src/gum/commands/mod.rs +pub mod disasm_cmds; +pub mod hook_cmds; pub mod memory_cmds; pub mod nav_cmds; -pub mod store_cmds; -pub mod hook_cmds; -pub mod disasm_cmds; pub mod scan_cmds; +pub mod store_cmds; -use crate::gum::commander::{Command, CommandArg, SubCommand}; use crate::gum::commander::Commander; +use crate::gum::commander::{Command, CommandArg, SubCommand}; pub fn build_all() -> Vec { let mut cmds: Vec = Vec::new(); @@ -54,12 +54,12 @@ pub fn build_all() -> Vec { )); // Grouped commands by category - cmds.extend(nav_cmds::build()); // Navigation: select, deselect, add, sub, goto - cmds.extend(store_cmds::build()); // Stores: field, lib - cmds.extend(memory_cmds::build()); // Memory: list, view, read, write - cmds.extend(hook_cmds::build()); // Hooking: hook add/remove/list/enable/disable - cmds.extend(disasm_cmds::build()); // Disassembly: disas, patch, nop - cmds.extend(scan_cmds::build()); // Scanning: scan, thread + cmds.extend(nav_cmds::build()); // Navigation: select, deselect, add, sub, goto + cmds.extend(store_cmds::build()); // Stores: field, lib + cmds.extend(memory_cmds::build()); // Memory: list, view, read, write + cmds.extend(hook_cmds::build()); // Hooking: hook add/remove/list/enable/disable + cmds.extend(disasm_cmds::build()); // Disassembly: disas, patch, nop + cmds.extend(scan_cmds::build()); // Scanning: scan, thread cmds } diff --git a/src/gum/commands/nav_cmds.rs b/src/gum/commands/nav_cmds.rs index d12fb85..b370c26 100644 --- a/src/gum/commands/nav_cmds.rs +++ b/src/gum/commands/nav_cmds.rs @@ -43,7 +43,10 @@ pub(crate) fn build() -> Vec { "sub", "Subtract an offset from the current navigator address", vec![], - vec![CommandArg::required("offset", "Offset to subtract (hex or dec)")], + vec![CommandArg::required( + "offset", + "Offset to subtract (hex or dec)", + )], vec![], Some(|c, a| Commander::sub(c, a)), )); @@ -53,7 +56,10 @@ pub(crate) fn build() -> Vec { "goto", "Jump navigator to an absolute address", vec![], - vec![CommandArg::required("address", "Address to jump to (hex or dec)")], + vec![CommandArg::required( + "address", + "Address to jump to (hex or dec)", + )], vec![], Some(|c, a| Commander::goto(c, a)), )); diff --git a/src/gum/commands/scan_cmds.rs b/src/gum/commands/scan_cmds.rs index c082554..1afe524 100644 --- a/src/gum/commands/scan_cmds.rs +++ b/src/gum/commands/scan_cmds.rs @@ -12,7 +12,10 @@ pub(crate) fn build() -> Vec { "Scan for byte pattern (hex with ?? wildcards: 'AA BB ?? CC')", vec![ CommandArg::required("pattern", "Byte pattern to search"), - CommandArg::optional("protection", "Memory protection filter (e.g., 'r--', 'rw-')"), + CommandArg::optional( + "protection", + "Memory protection filter (e.g., 'r--', 'rw-')", + ), ], |c, a| Commander::scan_bytes(c, a), )); @@ -47,7 +50,10 @@ pub(crate) fn build() -> Vec { "Refine scan results with new value", vec![ CommandArg::required("value", "New value to filter by"), - CommandArg::optional("comparison", "Comparison: eq, ne, gt, lt, ge, le (default: eq)"), + CommandArg::optional( + "comparison", + "Comparison: eq, ne, gt, lt, ge, le (default: eq)", + ), ], |c, a| Commander::scan_next(c, a), ) @@ -92,9 +98,10 @@ pub(crate) fn build() -> Vec { SubCommand::new( "list", "List scan results and load into Field store", - vec![ - CommandArg::optional("limit", "Max results to load (default 100)"), - ], + vec![CommandArg::optional( + "limit", + "Max results to load (default 100)", + )], |c, a| Commander::scan_list(c, a), ) .alias("ls"), @@ -120,12 +127,9 @@ pub(crate) fn build() -> Vec { let mut thread_subs: Vec = Vec::new(); thread_subs.push( - SubCommand::new( - "list", - "List all threads in the process", - vec![], - |c, a| Commander::thread_list(c, a), - ) + SubCommand::new("list", "List all threads in the process", vec![], |c, a| { + Commander::thread_list(c, a) + }) .alias("ls"), ); @@ -133,7 +137,10 @@ pub(crate) fn build() -> Vec { SubCommand::new( "regs", "Show registers for a thread", - vec![CommandArg::optional("thread_id", "Thread ID (default: current)")], + vec![CommandArg::optional( + "thread_id", + "Thread ID (default: current)", + )], |c, a| Commander::thread_regs(c, a), ) .alias("r"), @@ -156,7 +163,10 @@ pub(crate) fn build() -> Vec { SubCommand::new( "backtrace", "Show backtrace for a thread", - vec![CommandArg::optional("thread_id", "Thread ID (default: current)")], + vec![CommandArg::optional( + "thread_id", + "Thread ID (default: current)", + )], |c, a| Commander::thread_backtrace(c, a), ) .alias("bt"), diff --git a/src/gum/memory.rs b/src/gum/memory.rs index 35bfaa9..504f1a2 100644 --- a/src/gum/memory.rs +++ b/src/gum/memory.rs @@ -801,7 +801,11 @@ fn determine_endianness( } // Decode a value of the given type from a byte slice into a plain string (without colors) -fn decode_value_to_string_from_bytes(value_type: &VzValueType, slice: &[u8], little_endian: bool) -> String { +fn decode_value_to_string_from_bytes( + value_type: &VzValueType, + slice: &[u8], + little_endian: bool, +) -> String { // Helper to safely copy bytes into arrays fn bytes_to_array(slice: &[u8]) -> [u8; N] { let mut arr = [0u8; N]; @@ -820,42 +824,74 @@ fn decode_value_to_string_from_bytes(value_type: &VzValueType, slice: &[u8], lit } VzValueType::Short | VzValueType::Int16 => { let arr = bytes_to_array::<2>(slice); - let v = if little_endian { i16::from_le_bytes(arr) } else { i16::from_be_bytes(arr) }; + let v = if little_endian { + i16::from_le_bytes(arr) + } else { + i16::from_be_bytes(arr) + }; v.to_string() } VzValueType::UShort | VzValueType::UInt16 => { let arr = bytes_to_array::<2>(slice); - let v = if little_endian { u16::from_le_bytes(arr) } else { u16::from_be_bytes(arr) }; + let v = if little_endian { + u16::from_le_bytes(arr) + } else { + u16::from_be_bytes(arr) + }; v.to_string() } VzValueType::Int | VzValueType::Int32 => { let arr = bytes_to_array::<4>(slice); - let v = if little_endian { i32::from_le_bytes(arr) } else { i32::from_be_bytes(arr) }; + let v = if little_endian { + i32::from_le_bytes(arr) + } else { + i32::from_be_bytes(arr) + }; v.to_string() } VzValueType::UInt | VzValueType::UInt32 => { let arr = bytes_to_array::<4>(slice); - let v = if little_endian { u32::from_le_bytes(arr) } else { u32::from_be_bytes(arr) }; + let v = if little_endian { + u32::from_le_bytes(arr) + } else { + u32::from_be_bytes(arr) + }; v.to_string() } VzValueType::Long | VzValueType::Int64 => { let arr = bytes_to_array::<8>(slice); - let v = if little_endian { i64::from_le_bytes(arr) } else { i64::from_be_bytes(arr) }; + let v = if little_endian { + i64::from_le_bytes(arr) + } else { + i64::from_be_bytes(arr) + }; v.to_string() } VzValueType::ULong | VzValueType::UInt64 => { let arr = bytes_to_array::<8>(slice); - let v = if little_endian { u64::from_le_bytes(arr) } else { u64::from_be_bytes(arr) }; + let v = if little_endian { + u64::from_le_bytes(arr) + } else { + u64::from_be_bytes(arr) + }; v.to_string() } VzValueType::Float | VzValueType::Float32 => { let arr = bytes_to_array::<4>(slice); - let v = if little_endian { f32::from_le_bytes(arr) } else { f32::from_be_bytes(arr) }; + let v = if little_endian { + f32::from_le_bytes(arr) + } else { + f32::from_be_bytes(arr) + }; v.to_string() } VzValueType::Double | VzValueType::Float64 => { let arr = bytes_to_array::<8>(slice); - let v = if little_endian { f64::from_le_bytes(arr) } else { f64::from_be_bytes(arr) }; + let v = if little_endian { + f64::from_le_bytes(arr) + } else { + f64::from_be_bytes(arr) + }; v.to_string() } VzValueType::Bool | VzValueType::Boolean => { @@ -864,7 +900,11 @@ fn decode_value_to_string_from_bytes(value_type: &VzValueType, slice: &[u8], lit } VzValueType::Pointer => { let arr = bytes_to_array::<8>(slice); - let v = if little_endian { u64::from_le_bytes(arr) } else { u64::from_be_bytes(arr) }; + let v = if little_endian { + u64::from_le_bytes(arr) + } else { + u64::from_be_bytes(arr) + }; format!("{:#018x}", v) } // For these types, view uses hex-bytes mode; fallback to single byte display string diff --git a/src/gum/store.rs b/src/gum/store.rs index 2e272a6..f4e2b6d 100644 --- a/src/gum/store.rs +++ b/src/gum/store.rs @@ -377,10 +377,7 @@ impl Store { false } - fn get_field_value_for_filtering( - vz_data_item: &VzData, - key: &str, - ) -> Option { + fn get_field_value_for_filtering(vz_data_item: &VzData, key: &str) -> Option { match key.to_lowercase().as_str() { "name" => match vz_data_item { VzData::Module(m) => Some(FilterValue::String(m.name.clone())), diff --git a/src/gum/vzdata.rs b/src/gum/vzdata.rs index bf49c2d..5d03a81 100644 --- a/src/gum/vzdata.rs +++ b/src/gum/vzdata.rs @@ -478,7 +478,8 @@ pub struct VzInstruction { impl fmt::Display for VzInstruction { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let bytes_hex = self.bytes + let bytes_hex = self + .bytes .iter() .map(|b| format!("{:02x}", b)) .collect::>() @@ -554,7 +555,8 @@ pub struct VzImport { impl fmt::Display for VzImport { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let addr_str = self.address + let addr_str = self + .address .map(|a| format!("{:#x}", a)) .unwrap_or_else(|| "?".to_string()); write!( @@ -597,9 +599,7 @@ pub struct VzSymbol { impl fmt::Display for VzSymbol { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let size_str = self.size - .map(|s| format!("({:#x})", s)) - .unwrap_or_default(); + let size_str = self.size.map(|s| format!("({:#x})", s)).unwrap_or_default(); let global_str = if self.is_global { "G" } else { "L" }; write!( f,