Read/Write complicated structures in memory easily.
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:
- To communicate with memory easily.
- To collect all common structures for debugging/learning.
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
- Linux
- (TODO) Windows
- (TODO) MacOS
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/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/c_str
: A null-terminated string. Also known as::c_str
cpp/string
: Thestd::string
class in C++11. Also known as::string
Available on RubyGems.org!
$ gem install memory_io
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"
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!!'
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"]
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'
TBA