Repository: gamozolabs/mempeek
Branch: main
Commit: 4fbd53d9bd17
Files: 7
Total size: 34.6 KB
Directory structure:
gitextract_wfbx1cem/
├── .gitignore
├── Cargo.toml
├── LICENSE
├── README.md
└── src/
├── constraint.rs
├── int.rs
└── main.rs
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
/target
/.peekieboi
================================================
FILE: Cargo.toml
================================================
[package]
name = "mempeek"
version = "0.1.5"
edition = "2021"
license = "BSD-2-Clause"
description = "A command line tool that resembles a debugger as well as Cheat Engine, to search for values in memory"
documentation = "https://docs.rs/mempeek"
repository = "https://github.com/gamozolabs/mempeek"
keywords = ["proc", "mem", "maps"]
categories = ["os::linux-apis", "command-line-utilities"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
libprocmem = "0.1"
quoted_strings = "0.1.0"
rustyline = "9.1"
================================================
FILE: LICENSE
================================================
BSD 2-Clause License
Copyright (c) 2022, gamozolabs
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
================================================
FILE: README.md
================================================
# Summary
This is a small command-line tool designed to peek around memory of a running
Linux process. It also provides filtering mechanisms similar to Cheat Engine
to scan for memory of certain values.
It uses `rustyline` to maintain a history of command line arguments which is
persisted in the `.peekieboi` file. Allowing "up-arrow" to work across
different runs of the tool!
# Installing
Simply run `cargo install mempeek` to install this tool! Then invoke it by
running `mempeek <pid of process to introspect>`
# Commands
## Expression support
I've added extremely basic support for expressions of various radix as well as
add, subtract, multiply, and divide. No support for parenthesis (yet).
This allows you to use an expression like `0x13370000+0o100*4` in any argument
to a command which expects a constant value. _The default radix for numbers
is 16, thus, hex unless you use an `0b`, `0o`, or `0d` prefix_
## Types
Types may be one of the following:
- `b` - `u8`
- `w` - `u16`
- `d` - `u32`
- `q` - `u64`
- `B` - `i8`
- `W` - `i16`
- `D` - `i32`
- `Q` - `i64`
- `f` - `f32`
- `F` - `f64`
## Constraints
Constraints may be any one of the following:
- `=[val]` - Equal to `[val]`
- `![val]` - Not equal to `[val]`
- `>[val]` - Greater than `[val]`
- `>=[val]` - Greater than or equal to `[val]`
- `<[val]` - Less than `[val]`
- `<=[val]` - Less than or equal to `[val]`
Currently this only supports a few commands
## `q` | `exit` | `quit`
Exit the program
## `h <query index | l>`
Get the results from a previous memory scan. Takes the query index of the query
to retrieve. Optionally, you can use `l` in place of the query index to get the
most recent query results
## `s[bwdqBWDQfF] <addr> <length> [constraints]`
Scan memory for a value of a given type starting at `addr` for `length` bytes
using `constraints`
## `u[bwdqBWDQfFo] <query #> [constraints]`
Using the address list from a previous query, interpret the pointed-to-value as
the specified type `o` implies that the update should use the type of the
original query.
## `d[bwdqBWDQfF] <addr> [<number of bytes>]`
Dump memory interpreted as a given type for a given number of bytes
## `ss <addr> <length> <string>`
Search for a `string` in a region of memory specified by `addr` and `length`
(in bytes)
## `m`
Dump memory regions and their permissions.
# Example

_Green values in the dump output indicate that the value is a valid pointer
when cast to a `u64`_
================================================
FILE: src/constraint.rs
================================================
///! Basic constraints
use crate::{Error, Result, Value};
/// Different constraints we can apply on a value
#[derive(Debug, Clone, Copy)]
pub enum Constraint {
/// `=`
Equal(Value),
/// `!`
NotEqual(Value),
/// `>`
Greater(Value),
/// `>=`
GreaterEqual(Value),
/// `<`
Less(Value),
/// `<=`
LessEqual(Value),
}
impl Constraint {
/// Replace the value with a new one
pub fn update_val(&mut self, val: Value) {
match self {
Self::Greater(ref mut rhs) |
Self::GreaterEqual(ref mut rhs) |
Self::Less(ref mut rhs) |
Self::LessEqual(ref mut rhs) |
Self::Equal(ref mut rhs) => *rhs = val,
Self::NotEqual(ref mut rhs) => *rhs = val,
}
}
/// Check a condition
pub fn check(&self, lhs: Value) -> bool {
match *self {
Self::Greater(rhs) => lhs > rhs,
Self::GreaterEqual(rhs) => lhs >= rhs,
Self::Less(rhs) => lhs < rhs,
Self::LessEqual(rhs) => lhs <= rhs,
Self::Equal(rhs) => lhs == rhs,
Self::NotEqual(rhs) => lhs != rhs,
}
}
/// Create a new constraint from `s` using `val` as the type for the value
pub fn from_str_value(s: &str, val: Option<Value>) -> Result<Self> {
if s.starts_with("=") {
if let Some(mut val) = val {
val.update_str(&s[1..])?;
Ok(Constraint::Equal(val))
} else {
Ok(Constraint::Equal(Value::U8(0)))
}
} else if s.starts_with("!") {
if let Some(mut val) = val {
val.update_str(&s[1..])?;
Ok(Constraint::NotEqual(val))
} else {
Ok(Constraint::NotEqual(Value::U8(0)))
}
} else if s.starts_with(">=") {
if let Some(mut val) = val {
val.update_str(&s[2..])?;
Ok(Constraint::GreaterEqual(val))
} else {
Ok(Constraint::GreaterEqual(Value::U8(0)))
}
} else if s.starts_with(">") {
if let Some(mut val) = val {
val.update_str(&s[1..])?;
Ok(Constraint::Greater(val))
} else {
Ok(Constraint::Greater(Value::U8(0)))
}
} else if s.starts_with("<=") {
if let Some(mut val) = val {
val.update_str(&s[2..])?;
Ok(Constraint::LessEqual(val))
} else {
Ok(Constraint::LessEqual(Value::U8(0)))
}
} else if s.starts_with("<") {
if let Some(mut val) = val {
val.update_str(&s[1..])?;
Ok(Constraint::Less(val))
} else {
Ok(Constraint::Less(Value::U8(0)))
}
} else {
Err(Error::InvalidConstraint)
}
}
}
================================================
FILE: src/int.rs
================================================
//! Integer parsing helper libraries
use crate::Error;
macro_rules! impl_expr {
($name:ident, $name_int:ident, $ty:ty) => {
pub fn $name(s: &str) -> crate::Result<$ty> {
#[derive(Clone, Copy, Debug)]
enum Expr {
Add,
Sub,
Mul,
Div,
Val($ty),
}
// Split expression into components
let mut components = Vec::new();
let mut cur = String::new();
for chr in s.chars() {
if matches!(chr, '+' | '-' | '*' | '/') {
// Push the current component
components.push(Expr::Val($name_int(&cur)?));
match chr {
'+' => components.push(Expr::Add),
'-' => components.push(Expr::Sub),
'*' => components.push(Expr::Mul),
'/' => components.push(Expr::Div),
_ => unreachable!(),
}
cur.clear();
continue;
}
cur.push(chr);
}
// Flush remaining data
if !cur.is_empty() {
components.push(Expr::Val($name_int(&cur)?));
}
let mut ii = 0;
while ii < components.len().saturating_sub(2) {
let (left, op, right) = (
components[ii + 0],
components[ii + 1],
components[ii + 2],
);
let res = match (left, op, right) {
(Expr::Val(l), Expr::Mul, Expr::Val(r)) =>
l.wrapping_mul(r),
(Expr::Val(l), Expr::Div, Expr::Val(r)) =>
l.wrapping_div(r),
_ => {
ii += 1;
continue;
}
};
// Replace the expression with the result
components[ii] = Expr::Val(res);
// Remove the operation and right side as we've "consumed" them
components.drain(ii + 1..ii + 3);
}
let mut ii = 0;
while ii < components.len().saturating_sub(2) {
let (left, op, right) = (
components[ii + 0],
components[ii + 1],
components[ii + 2],
);
let res = match (left, op, right) {
(Expr::Val(l), Expr::Add, Expr::Val(r)) =>
l.wrapping_add(r),
(Expr::Val(l), Expr::Sub, Expr::Val(r)) =>
l.wrapping_sub(r),
_ => {
ii += 1;
continue;
}
};
// Replace the expression with the result
components[ii] = Expr::Val(res);
// Remove the operation and right side as we've "consumed" them
components.drain(ii + 1..ii + 3);
}
match components.as_slice() {
[Expr::Val(ret)] => Ok(*ret),
_ => Err(Error::InvalidExpression),
}
}
}
}
macro_rules! int_parse {
($name:ident, $name_int:ident, $ty:ty) => {
/// Parse an integer with optional base override prefix
pub fn $name_int(mut s: &str) -> crate::Result<$ty> {
// Default base
let mut base = 16;
// Invert sign
let mut inv = false;
// Check for a prefix
match s.get(0..3) {
Some("-0x" | "-0X") => { base = 16; s = &s[3..]; inv = true; }
Some("-0d" | "-0D") => { base = 10; s = &s[3..]; inv = true; }
Some("-0o" | "-0O") => { base = 8; s = &s[3..]; inv = true; }
Some("-0b" | "-0B") => { base = 2; s = &s[3..]; inv = true; }
_ => {
// Check for a prefix
match s.get(0..2) {
Some("0x" | "0X") => { base = 16; s = &s[2..]; }
Some("0d" | "0D") => { base = 10; s = &s[2..]; }
Some("0o" | "0O") => { base = 8; s = &s[2..]; }
Some("0b" | "0B") => { base = 2; s = &s[2..]; }
_ => {}
}
}
}
// Parse the integer
<$ty>::from_str_radix(s, base).map_err(crate::Error::ParseSigned)
.map(|x| if inv { -x } else { x })
}
impl_expr!($name, $name_int, $ty);
}
}
macro_rules! uint_parse {
($name:ident, $name_int:ident, $ty:ty) => {
/// Parse an integer with optional base override prefix
pub fn $name_int(mut s: &str) -> crate::Result<$ty> {
// Default base
let mut base = 16;
// Check for a prefix
match s.get(0..2) {
Some("0x" | "0X") => { base = 16; s = &s[2..]; }
Some("0d" | "0D") => { base = 10; s = &s[2..]; }
Some("0o" | "0O") => { base = 8; s = &s[2..]; }
Some("0b" | "0B") => { base = 2; s = &s[2..]; }
_ => {}
}
// Parse the integer
<$ty>::from_str_radix(s, base).map_err(crate::Error::ParseUnsigned)
}
impl_expr!($name, $name_int, $ty);
}
}
uint_parse!(parse_u8, parse_u8_int, u8);
uint_parse!(parse_u16, parse_u16_int, u16);
uint_parse!(parse_u32, parse_u32_int, u32);
uint_parse!(parse_u64, parse_u64_int, u64);
uint_parse!(parse_usize, parse_usize_int, usize);
int_parse!(parse_i8, parse_i8_int, i8);
int_parse!(parse_i16, parse_i16_int, i16);
int_parse!(parse_i32, parse_i32_int, i32);
int_parse!(parse_i64, parse_i64_int, i64);
int_parse!(parse_isize, parse_isize_int, isize);
================================================
FILE: src/main.rs
================================================
#![feature(array_chunks)]
pub mod int;
pub mod constraint;
use std::str::FromStr;
use crate::int::*;
use crate::constraint::*;
use libprocmem::Memory;
use rustyline::Editor;
use rustyline::error::ReadlineError;
use quoted_strings::QuotedParts;
/// Wrapper for `Result`
type Result<T> = std::result::Result<T, Error>;
/// Error type
#[derive(Debug)]
pub enum Error {
/// Error interacting with libprocmem
Memory(libprocmem::Error),
/// Failed to parse a signed value
ParseSigned(std::num::ParseIntError),
/// Failed to parse an unsigned value
ParseUnsigned(std::num::ParseIntError),
/// Failed to read command
Readline(ReadlineError),
/// Integer truncation happened when converting a `u64` to a `usize`
TooBig,
/// Failed to parse a floating point value
ParseFloat(std::num::ParseFloatError),
/// Invalid constraint
InvalidConstraint,
/// Got an invalid PID on the command line
InvalidPid(std::num::ParseIntError),
/// An invalid expression was used
///
/// Currently we just support add, sub, mul, and div. No spaces. Numbers
/// can be any base (with the correct override)
InvalidExpression,
}
/// Different values
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
pub enum Value {
F32(f32),
F64(f64),
U8(u8),
U16(u16),
U32(u32),
U64(u64),
I8(i8),
I16(i16),
I32(i32),
I64(i64),
}
impl Value {
/// Create a new (zero) value with the format specified by `chr`
pub fn default_from_letter(chr: char) -> Value {
// Get the value
match chr {
'f' => Value::F32(0.),
'F' => Value::F64(0.),
'b' => Value::U8(0),
'w' => Value::U16(0),
'd' => Value::U32(0),
'q' => Value::U64(0),
'B' => Value::I8(0),
'W' => Value::I16(0),
'D' => Value::I32(0),
'Q' => Value::I64(0),
_ => unreachable!(),
}
}
/// Interpret the value as a `u64` through casting
pub fn as_u64(&self) -> u64 {
match self {
Self::F32(x) => x.to_bits() as u64,
Self::F64(x) => x.to_bits(),
Self::U8 (x) => *x as u64,
Self::U16(x) => *x as u64,
Self::U32(x) => *x as u64,
Self::U64(x) => *x,
Self::I8 (x) => *x as u64,
Self::I16(x) => *x as u64,
Self::I32(x) => *x as u64,
Self::I64(x) => *x as u64,
}
}
/// Get number of bytes per `self`
pub fn bytes(&self) -> usize {
match self {
Self::F32(_) => 4,
Self::F64(_) => 8,
Self::U8 (_) => 1,
Self::U16(_) => 2,
Self::U32(_) => 4,
Self::U64(_) => 8,
Self::I8 (_) => 1,
Self::I16(_) => 2,
Self::I32(_) => 4,
Self::I64(_) => 8,
}
}
/// Get number of bytes per `self` when `Display`ed
pub fn display(&self) -> usize {
match self {
Self::F32(_) => 25,
Self::F64(_) => 25,
Self::U8 (_) => 2,
Self::U16(_) => 4,
Self::U32(_) => 8,
Self::U64(_) => 16,
Self::I8 (_) => 4,
Self::I16(_) => 6,
Self::I32(_) => 11,
Self::I64(_) => 21,
}
}
/// Update value from little-endian bytes
pub fn from_le_bytes(&mut self, bytes: &[u8]) {
match self {
Self::F32(ref mut val) =>
*val = f32::from_le_bytes(bytes.try_into().unwrap()),
Self::F64(ref mut val) =>
*val = f64::from_le_bytes(bytes.try_into().unwrap()),
Self::U8 (ref mut val) =>
*val = u8::from_le_bytes(bytes.try_into().unwrap()),
Self::U16(ref mut val) =>
*val = u16::from_le_bytes(bytes.try_into().unwrap()),
Self::U32(ref mut val) =>
*val = u32::from_le_bytes(bytes.try_into().unwrap()),
Self::U64(ref mut val) =>
*val = u64::from_le_bytes(bytes.try_into().unwrap()),
Self::I8 (ref mut val) =>
*val = i8::from_le_bytes(bytes.try_into().unwrap()),
Self::I16(ref mut val) =>
*val = i16::from_le_bytes(bytes.try_into().unwrap()),
Self::I32(ref mut val) =>
*val = i32::from_le_bytes(bytes.try_into().unwrap()),
Self::I64(ref mut val) =>
*val = i64::from_le_bytes(bytes.try_into().unwrap()),
}
}
/// Update `self` to a new value of the same type from `s`
pub fn update_str(&mut self, s: &str) -> Result<()> {
match self {
Self::F32(ref mut val) => {
*val = f32::from_str(s).map_err(Error::ParseFloat)?;
}
Self::F64(ref mut val) => {
*val = f64::from_str(s).map_err(Error::ParseFloat)?;
}
Self::U8 (ref mut val) => *val = parse_u8 (s)?,
Self::U16(ref mut val) => *val = parse_u16(s)?,
Self::U32(ref mut val) => *val = parse_u32(s)?,
Self::U64(ref mut val) => *val = parse_u64(s)?,
Self::I8 (ref mut val) => *val = parse_i8 (s)?,
Self::I16(ref mut val) => *val = parse_i16(s)?,
Self::I32(ref mut val) => *val = parse_i32(s)?,
Self::I64(ref mut val) => *val = parse_i64(s)?,
}
Ok(())
}
}
impl std::fmt::Display for Value {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::F32(val) =>
f.write_fmt(format_args!("{:25.6}", val)),
Self::F64(val) =>
f.write_fmt(format_args!("{:25.6}", val)),
Self::U8 (val) => f.write_fmt(format_args!("{:02x}", val)),
Self::U16(val) => f.write_fmt(format_args!("{:04x}", val)),
Self::U32(val) => f.write_fmt(format_args!("{:08x}", val)),
Self::U64(val) => f.write_fmt(format_args!("{:016x}", val)),
Self::I8 (val) => f.write_fmt(format_args!("{:4}", val)),
Self::I16(val) => f.write_fmt(format_args!("{:6}", val)),
Self::I32(val) => f.write_fmt(format_args!("{:11}", val)),
Self::I64(val) => f.write_fmt(format_args!("{:21}", val)),
}
}
}
impl std::fmt::LowerHex for Value {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::F32(val) =>
f.write_fmt(format_args!("{:08x}", val.to_bits())),
Self::F64(val) =>
f.write_fmt(format_args!("{:016x}", val.to_bits())),
Self::U8 (val) => f.write_fmt(format_args!("{:02x}", val)),
Self::U16(val) => f.write_fmt(format_args!("{:04x}", val)),
Self::U32(val) => f.write_fmt(format_args!("{:08x}", val)),
Self::U64(val) => f.write_fmt(format_args!("{:016x}", val)),
Self::I8 (val) => f.write_fmt(format_args!("{:02x}", val)),
Self::I16(val) => f.write_fmt(format_args!("{:04x}", val)),
Self::I32(val) => f.write_fmt(format_args!("{:08x}", val)),
Self::I64(val) => f.write_fmt(format_args!("{:016x}", val)),
}
}
}
/// A memory scanner!
struct Scan {
/// Memory
memory: Memory,
/// Searches which yielded results
matches: Vec<(Vec<Constraint>, Vec<(u64, Value)>)>,
}
impl Scan {
/// Handle a command to the scanner
fn handle_command(&mut self, command: &[String]) -> Result<()> {
// Nothing to do
if command.len() == 0 { return Ok(()); }
match command[0].as_str() {
"exit" | "q" | "quit" => {
// Exit the program
std::process::exit(0);
}
"h" => {
// History, displays old query results
if command.len() != 2 {
println!("h <query #>");
return Ok(());
}
// Get the query number
let query = if command[1] == "l" {
// 'l' is a shortcut for "last" command
self.matches.len() - 1
} else {
parse_usize(&command[1])?
};
if let Some((constraints, matches)) = self.matches.get(query) {
for &(addr, old) in matches.iter() {
// Read the new value
let tmp =
self.memory.read_slice::<u8>(addr, old.bytes())
.map_err(Error::Memory)?;
// Get the new value with the same typing as the old
// value
let mut new = old;
new.from_le_bytes(&tmp);
if old != new {
println!("{:016x} query: {} -> {}",
addr, old, new);
} else {
println!("{:016x} query: {}", addr, old);
}
}
println!(
"Query #{} had {} matches with constraints:\n{:#?}",
query, matches.len(), constraints);
} else {
println!("No matching query");
}
}
"m" => {
// Print address maps
if let Ok(maps) = self.memory.query_address_space() {
for region in maps {
println!("{:016x}-{:016x} {}{}{}",
region.base, region.end,
if region.r { "r" } else { " " },
if region.w { "w" } else { " " },
if region.x { "x" } else { " " });
}
} else {
println!("Failed to query address map.");
}
}
"uo" |
"ub" | "uw" | "ud" | "uq" |
"uB" | "uW" | "uD" | "uQ" | "uf" | "uF" => {
// Re-query only the addresses of a previous query with new
// constraints
if command.len() < 3 {
println!("u[bwdqBWDQfF] <query #> [constraints]");
return Ok(());
}
// Create value associated with type
let value = if command[0] == "uo" {
None
} else {
Some(Value::default_from_letter(
command[0].as_bytes()[1] as char))
};
// Create list of constraints
let mut constraints = Vec::new();
for constraint in &command[2..] {
constraints.push(
Constraint::from_str_value(constraint, value)?);
}
// Get query ID
let query = if command[1] == "l" {
self.matches.len() - 1
} else {
parse_usize(&command[1])?
};
if let Some((_, old_matches)) = self.matches.get(query) {
// Search through old query matches
let mut matches = Vec::new();
for &(addr, old_value) in old_matches {
// Check if we have a concrete value, if not, compare
// against the last observed value
let mut value = if let Some(value) = value {
value
} else {
for constraint in constraints.iter_mut() {
constraint.update_val(old_value);
}
old_value
};
// Read the new value
let tmp = self.memory.read_slice::<u8>(
addr, value.bytes())
.map_err(Error::Memory)?;
value.from_le_bytes(&tmp);
// Check constraints
if constraints.iter().all(|x| x.check(value)) {
matches.push((addr, value));
}
}
if !matches.is_empty() {
println!("Got {} matches, saving as query #{:x}",
matches.len(), self.matches.len());
if matches.len() < 100 {
for (addr, value) in &matches {
println!("{:016x} {}", addr, value);
}
}
self.matches.push((constraints, matches));
} else {
println!("No matches.");
}
} else {
println!("No matching query");
}
}
"ss" => {
// Search for a string
if command.len() != 4 {
println!("ss <addr> <length> <string>");
return Ok(());
}
// Get the address and size
let addr = parse_u64(&command[1])?;
let size = parse_usize(&command[2])?;
// Read the memory
let tmp = self.memory.read_slice::<u8>(addr, size)
.map_err(Error::Memory)?;
// String search
tmp.windows(command[3].len()).enumerate()
.for_each(|(ii, window)| {
if window == command[3].as_bytes() {
println!("{:016x} {}", addr + ii as u64, command[3]);
}
});
}
"sb" | "sw" | "sd" | "sq" |
"sB" | "sW" | "sD" | "sQ" | "sf" | "sF" => {
// Search for a value
if command.len() <= 2 {
println!("s[bwdqBWDQfF] <addr> <length> [constraints]");
return Ok(());
}
// Create value associated with type
let value =
Value::default_from_letter(
command[0].as_bytes()[1] as char);
// Create list of constraints
let mut constraints = Vec::new();
for constraint in &command[3..] {
constraints.push(
Constraint::from_str_value(constraint, Some(value))?);
}
// Get the address and size
let addr = parse_u64(&command[1])?;
let size = parse_usize(&command[2])?;
let end = addr + size as u64;
let mut matches = Vec::new();
for mapping in self.memory.query_address_space()
.map_err(Error::Memory)? {
// Skip ranges not in bounds
if mapping.end <= addr || mapping.base >= end {
continue;
}
let start = addr.max(mapping.base);
let end = end.min(mapping.end);
self.search_memory(value, start, (end - start) as usize,
&constraints, &mut matches)?;
}
// If we got matches, save them off
if !matches.is_empty() {
println!("Got {} matches, saving as query #{:x}",
matches.len(), self.matches.len());
if matches.len() < 100 {
for (addr, value) in &matches {
println!("{:016x} {}", addr, value);
}
}
self.matches.push((constraints, matches));
} else {
println!("No matches.");
}
}
"db" | "dw" | "dd" | "dq" |
"dB" | "dW" | "dD" | "dQ" | "df" | "dF" =>{
// Display memory
if !matches!(command.len(), 2 | 3) {
println!("d[bwdqBWDQfF] <addr> [<number of bytes>]");
return Ok(());
}
// Get the letter used with this command and use it to create a
// dummy expected value
let mut value =
Value::default_from_letter(
command[0].as_bytes()[1] as char);
// Parse integer address
let addr = parse_u64(&command[1])?;
// Determine number of bytes to read
let bytes = if let Some(x) = command.get(2) {
parse_usize(x)?
} else {
64
};
// Read memory
let tmp = self.memory.read_slice::<u8>(addr, bytes)
.map_err(Error::Memory)?;
// Print the new line header
print!("\x1b[0;34m{:016x}\x1b[0m: ", addr);
// Display all the values
let mut output_used = 0;
let mut iter = tmp.chunks_exact(
value.bytes()).map(|x| Some(x)).chain(
std::iter::repeat(None)).enumerate().peekable();
while let Some((ii, val)) = iter.next() {
if let Some(val) = val {
// Print the value
value.from_le_bytes(val);
// Convert the value to a `u64` and try to use it as
// an address to see if it is a pointer
let maybe_addr = value.as_u64();
let valid_ptr =
self.memory.read::<u8>(maybe_addr).is_ok();
if valid_ptr {
print!("\x1b[0;32m{}\x1b[0m ", value);
} else {
print!("{} ", value);
}
} else {
// Clean
for _ in 0..value.display() {
print!("?");
}
print!(" ");
}
// Update output used for this line
output_used += 1;
let vals_per_line = 16 / value.bytes();
if output_used == vals_per_line {
// Before we make the newline, print the ASCII
let ascii = ii / vals_per_line * vals_per_line *
value.bytes();
for byte in tmp[ascii..].iter().take(16) {
if byte.is_ascii_graphic() {
print!("{}", *byte as char);
} else {
print!(".");
}
}
println!();
// If we have nothing more to print after this, we're
// done
if matches!(iter.peek(), Some((_, None))) {
return Ok(());
}
// Print the new line header
print!("\x1b[34m{:016x}\x1b[0m: ",
addr + (ii as u64 + 1) * value.bytes() as u64);
// Update state
output_used = 0;
}
}
}
_ => {
println!("Unknown command: {:?}", command);
}
}
Ok(())
}
fn search_memory(&mut self, mut value: Value, addr: u64, size: usize,
constraints: &[Constraint],
matches: &mut Vec<(u64, Value)>) -> Result<()> {
// Read the memory
let tmp = self.memory.read_slice::<u8>(addr, size)
.map_err(Error::Memory)?;
// Go through memory
for (ii, chunk) in tmp.chunks_exact(value.bytes()).enumerate(){
// Update value
value.from_le_bytes(chunk);
// Check constraints
if constraints.iter().all(|x| x.check(value)) {
let addr = addr + ii as u64 * value.bytes() as u64;
matches.push((addr, value));
}
}
Ok(())
}
}
fn main() -> Result<()> {
// Get the arguments
let args = std::env::args().collect::<Vec<_>>();
if args.len() != 2 {
println!("Usage: peek <pid>");
return Ok(());
}
// Get the PID
let pid = usize::from_str_radix(&args[1], 10)
.map_err(Error::InvalidPid)?;
// Create a readline handler
let mut rl = Editor::<()>::new();
let _ = rl.load_history(".peekieboi");
// Create a memory scanner
let mut scan = Scan {
memory: Memory::pid(pid).map_err(Error::Memory)?,
matches: Vec::new(),
};
// Wait for commands
loop {
// Get command
let command = match rl.readline(">> ") {
Ok(x) => x,
Err(ReadlineError::Interrupted) => {
// Ctrl+c
break;
}
Err(x) => return Err(Error::Readline(x)),
};
rl.add_history_entry(command.as_str());
let _ = rl.save_history(".peekieboi");
// Split command
let command = QuotedParts::from(command.trim()).collect::<Vec<_>>();
if let Err(err) = scan.handle_command(&command) {
println!("Failed to execute command: {:?}", err);
}
}
Ok(())
}
gitextract_wfbx1cem/
├── .gitignore
├── Cargo.toml
├── LICENSE
├── README.md
└── src/
├── constraint.rs
├── int.rs
└── main.rs
SYMBOL INDEX (19 symbols across 2 files)
FILE: src/constraint.rs
type Constraint (line 7) | pub enum Constraint {
method update_val (line 29) | pub fn update_val(&mut self, val: Value) {
method check (line 41) | pub fn check(&self, lhs: Value) -> bool {
method from_str_value (line 53) | pub fn from_str_value(s: &str, val: Option<Value>) -> Result<Self> {
FILE: src/main.rs
type Result (line 15) | type Result<T> = std::result::Result<T, Error>;
type Error (line 19) | pub enum Error {
type Value (line 53) | pub enum Value {
method default_from_letter (line 68) | pub fn default_from_letter(chr: char) -> Value {
method as_u64 (line 86) | pub fn as_u64(&self) -> u64 {
method bytes (line 102) | pub fn bytes(&self) -> usize {
method display (line 118) | pub fn display(&self) -> usize {
method from_le_bytes (line 134) | pub fn from_le_bytes(&mut self, bytes: &[u8]) {
method update_str (line 160) | pub fn update_str(&mut self, s: &str) -> Result<()> {
method fmt (line 183) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
method fmt (line 202) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
type Scan (line 221) | struct Scan {
method handle_command (line 231) | fn handle_command(&mut self, command: &[String]) -> Result<()> {
method search_memory (line 545) | fn search_memory(&mut self, mut value: Value, addr: u64, size: usize,
function main (line 567) | fn main() -> Result<()> {
Condensed preview — 7 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (37K chars).
[
{
"path": ".gitignore",
"chars": 20,
"preview": "/target\n/.peekieboi\n"
},
{
"path": "Cargo.toml",
"chars": 570,
"preview": "[package]\nname = \"mempeek\"\nversion = \"0.1.5\"\nedition = \"2021\"\nlicense = \"BSD-2-Clause\"\ndescription = \"A command line too"
},
{
"path": "LICENSE",
"chars": 1320,
"preview": "BSD 2-Clause License\n\nCopyright (c) 2022, gamozolabs\nAll rights reserved.\n\nRedistribution and use in source and binary f"
},
{
"path": "README.md",
"chars": 2506,
"preview": "# Summary\n\nThis is a small command-line tool designed to peek around memory of a running\nLinux process. It also provides"
},
{
"path": "src/constraint.rs",
"chars": 2974,
"preview": "///! Basic constraints\n\nuse crate::{Error, Result, Value};\n\n/// Different constraints we can apply on a value\n#[derive(D"
},
{
"path": "src/int.rs",
"chars": 6064,
"preview": "//! Integer parsing helper libraries\n\nuse crate::Error;\n\nmacro_rules! impl_expr {\n ($name:ident, $name_int:ident, $ty"
},
{
"path": "src/main.rs",
"chars": 21992,
"preview": "#![feature(array_chunks)]\n\npub mod int;\npub mod constraint;\n\nuse std::str::FromStr;\nuse crate::int::*;\nuse crate::constr"
}
]
About this extraction
This page contains the full source code of the gamozolabs/mempeek GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 7 files (34.6 KB), approximately 8.4k tokens, and a symbol index with 19 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.