Full Code of gamozolabs/mempeek for AI

main 4fbd53d9bd17 cached
7 files
34.6 KB
8.4k tokens
19 symbols
1 requests
Download .txt
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

![Example of mempeek](/screenshot.png)

_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(())
}

Download .txt
gitextract_wfbx1cem/

├── .gitignore
├── Cargo.toml
├── LICENSE
├── README.md
└── src/
    ├── constraint.rs
    ├── int.rs
    └── main.rs
Download .txt
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.

Copied to clipboard!