Build Status Gem Version Maintainability Test Coverage Inline docs MIT License

MemoryIO

Read/Write complicated structures in memory easily.

Motivation

I usually need to dump a structure, say string in C++, from memory for debugging. This is not hard if using gdb. However, gdb doesn't support writing Ruby scripts (unless you use gdb-ruby, which has dependency of MemoryIO). So I create this repo and want to make the debug procedure much easier.

This repository has two main goals:

  1. To communicate with memory easily.
  2. To collect all common structures for debugging/learning.

Why

It's not hard to read/write a process's memory (simply open the file /proc/$PID/mem), but it still worth to wrap it.

This repo also targets to collect all common structures, such as how to parse a C++/Rust/Python object from memory. Therefore, Pull Requests of adding new structures are welcome :D

Supported Platform

  • Linux
  • (TODO) Windows
  • (TODO) MacOS

Implemented Structures

Following is the list of supported structures. Each type has a full-name and an alias. For example,

require 'memory_io'

process = MemoryIO.attach(`pidof victim`.to_i)
# read a 64-bit unsigned integer
process.read(0x601000, 1, as: 'basic/u64')
# is equivalent to
process.read(0x601000, 1, as: :u64)

Go to the online document for more details of each type.

BASIC

  • basic/u8: An unsigned 8-bit integer. Also known as: :u8
  • basic/u16: An unsigned 16-bit integer. Also known as: :u16
  • basic/u32: An unsigned 32-bit integer. Also known as: :u32
  • basic/u64: An unsigned 64-bit integer. Also known as: :u64
  • basic/s8: A signed 8-bit integer. Also known as: :s8
  • basic/s16: A signed 16-bit integer. Also known as: :s16
  • basic/s32: A signed 32-bit integer. Also known as: :s32
  • basic/s64: A signed 64-bit integer. Also known as: :s64
  • basic/float: IEEE-754 32-bit floating number. Also known as: :float
  • basic/double: IEEE-754 64-bit floating number. Also known as: :double

CLANG

  • clang/c_str: A null-terminated string. Also known as: :c_str

CPP

  • cpp/string: The std::string class in C++11. Also known as: :string

Installation

Available on RubyGems.org!

$ gem install memory_io

Usage

Read Process's Memory

require 'memory_io'

process = MemoryIO.attach(`pidof victim`.to_i)
puts process.read('heap', 4, as: :u64).map { |c| '0x%016x' % c }
# 0x0000000000000000
# 0x0000000000000021
# 0x00000000deadbeef
# 0x0000000000000000
#=> nil

process.read('heap+0x10', 4, as: :u8).map { |c| '0x%x' % c }
#=> ['0xef', '0xbe', '0xad', '0xde']

process.read('libc', 4)
#=> "\x7fELF"

Write Process's Memory

require 'memory_io'

process = MemoryIO.attach('self') # Hack! Write memory of this process directly!
string = 'A' * 16
pos = string.object_id * 2 + 16
process.read(pos, 16)
#=> 'AAAAAAAAAAAAAAAA'

process.write(pos, 'memory_changed!!')
string
#=> 'memory_changed!!'

Customize Read

require 'memory_io'
process = MemoryIO.attach(`pidof victim`.to_i)

# An example that read a chunk of pt-malloc.
read_chunk = lambda do |stream|
  _prev_size = stream.read(8)
  size = (stream.read(8).unpack('Q').first & -16) - 8
  [size, stream.read(size)]
end
process.read('heap', 1, as: read_chunk)
#=> [24, "\xef\xbe\xad\xde\x00\x00...\x00"]

Define Own Structure

require 'memory_io'
process = MemoryIO.attach(`pidof victim`.to_i)

class MyType < MemoryIO::Types::Type
  def self.read(stream)
    self.new(stream.read(1))
  end

  # Define this if you need to 'write' to memory
  def self.write(stream, my_type)
    stream.write(my_type.val)
  end

  attr_accessor :val
  def initialize(val)
    @val = val
  end
end

# Use snake-case symbol.
process.read('libc', 4, as: :my_type)
#=> [#<MyType @val="\x7F">,
# #<MyType @val="E">,
# #<MyType @val="L">,
# #<MyType @val="F">]

process.write('libc', MyType.new('MEOW'), as: :my_type)

# See if memory changed
process.read('libc', 4)
#=> 'MEOW'

Developing

To Add a New Structure

TBA