memory_io 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +39 -0
- data/lib/memory_io/io.rb +126 -0
- data/lib/memory_io/process.rb +113 -0
- data/lib/memory_io/types/c_str.rb +34 -0
- data/lib/memory_io/types/number.rb +60 -0
- data/lib/memory_io/types/type.rb +37 -0
- data/lib/memory_io/types/types.rb +56 -0
- data/lib/memory_io/util.rb +101 -0
- data/lib/memory_io/version.rb +4 -0
- data/lib/memory_io.rb +21 -0
- metadata +158 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 23fd90a4ba0906c5264a4149f8992c1ca22ed251
|
4
|
+
data.tar.gz: d6414406dac7f68362f6d1aafb0d31966f8e21b0
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: cc05ccbf5888b182138ff6112f8979e48875a8f3ab68730bf72e3cb5391e9205c32d7494e2d792196eda735eeac7e851e45fadcd54cdf51b9624d779b85e6492
|
7
|
+
data.tar.gz: b7ee99af0626e2486f8fab0da2b3c2bef47d877396a796118fc8ae8b4bfa456df609b0703d4da400b5a147e66a041ae557d69df2212b181f9d073353cf0e4bf6
|
data/README.md
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
[![Build Status](https://travis-ci.org/david942j/memory_io.svg?branch=master)](https://travis-ci.org/david942j/memory_io)
|
2
|
+
[![Gem Version](https://badge.fury.io/rb/memory_io.svg)](https://badge.fury.io/rb/memory_io)
|
3
|
+
[![Maintainability](https://api.codeclimate.com/v1/badges/dc8da34c5a8ab0095530/maintainability)](https://codeclimate.com/github/david942j/memory_io/maintainability)
|
4
|
+
[![Test Coverage](https://api.codeclimate.com/v1/badges/dc8da34c5a8ab0095530/test_coverage)](https://codeclimate.com/github/david942j/memory_io/test_coverage)
|
5
|
+
[![Inline docs](https://inch-ci.org/github/david942j/memory_io.svg?branch=master)](https://inch-ci.org/github/david942j/memory_io)
|
6
|
+
[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](http://choosealicense.com/licenses/mit/)
|
7
|
+
|
8
|
+
# Memory IO
|
9
|
+
|
10
|
+
Read/Write complicated structures in memory easily.
|
11
|
+
|
12
|
+
## Installation
|
13
|
+
|
14
|
+
Available on RubyGems.org!
|
15
|
+
|
16
|
+
```bash
|
17
|
+
$ gem install memory_io
|
18
|
+
```
|
19
|
+
|
20
|
+
## Usage
|
21
|
+
|
22
|
+
### Read Process's Memory
|
23
|
+
```
|
24
|
+
require 'memory_io'
|
25
|
+
|
26
|
+
process = MemoryIO.attach(`pidof victim`.to_i)
|
27
|
+
puts process.read('heap', 4, as: :u64).map { |c| '0x%016x' % c }
|
28
|
+
# 0x0000000000000000
|
29
|
+
# 0x0000000000000021
|
30
|
+
# 0x00000000deadbeef
|
31
|
+
# 0x0000000000000000
|
32
|
+
#=> nil
|
33
|
+
|
34
|
+
process.read('heap+0x10', 4, as: :u8).map { |c| '0x%x' % c }
|
35
|
+
#=> ['0xef', '0xbe', '0xad', '0xde']
|
36
|
+
|
37
|
+
process.read('libc', 4)
|
38
|
+
#=> "\x7fELF"
|
39
|
+
```
|
data/lib/memory_io/io.rb
ADDED
@@ -0,0 +1,126 @@
|
|
1
|
+
require 'memory_io/types/types'
|
2
|
+
|
3
|
+
module MemoryIO
|
4
|
+
# Main class to use {MemoryIO}.
|
5
|
+
class IO
|
6
|
+
attr_reader :stream # @return [#pos=, #read, #write]
|
7
|
+
|
8
|
+
# Instantiate an {IO} object.
|
9
|
+
#
|
10
|
+
# @param [#pos=, #read, #write] stream
|
11
|
+
# The file-like object to be read/written.
|
12
|
+
# +file+ can be unwritable if you will not invoke any write-related method.
|
13
|
+
#
|
14
|
+
# If +stream.read(*)+ returns empty string or +nil+, it would be seen as reaching EOF.
|
15
|
+
def initialize(stream)
|
16
|
+
@stream = stream
|
17
|
+
end
|
18
|
+
|
19
|
+
# Read and convert result into custom type/stucture.
|
20
|
+
#
|
21
|
+
# @param [Integer] num_elements
|
22
|
+
# Number of elements to be read.
|
23
|
+
# This parameter must be positive and larger than zero.
|
24
|
+
#
|
25
|
+
# This parameter may effect the return type,
|
26
|
+
# see documents of return value.
|
27
|
+
# @param [Integer?] from
|
28
|
+
# Invoke +stream.pos = from+ before starting to read.
|
29
|
+
# +nil+ for not changing current position of stream.
|
30
|
+
# @param [nil, Symbol, MemoryIO::Types, Proc] as
|
31
|
+
# Decide the type/structure when reading.
|
32
|
+
# See {MemoryIO::Types} for all supported types.
|
33
|
+
#
|
34
|
+
# A +Proc+ is allowed, which should accept +stream+ as the first argument.
|
35
|
+
# The return value of the proc would be the return objects of this method.
|
36
|
+
#
|
37
|
+
# If +nil+ is given, this method returns a string and has same behavior as +::IO#read+.
|
38
|
+
# @param [Boolean] force_array
|
39
|
+
# When +num_elements+ equals to 1, the read +Object+ would be returned.
|
40
|
+
# Pass +true+ to this parameter to force this method returning an array.
|
41
|
+
#
|
42
|
+
# @return [String, Object, Array<Object>]
|
43
|
+
# There're multiple possible return types,
|
44
|
+
# which dependes on the value of parameter +num_elements+, +as+, and +force_array+.
|
45
|
+
#
|
46
|
+
# See examples for clear usage. The rule of return type is listed as following:
|
47
|
+
#
|
48
|
+
# * +as = nil+:
|
49
|
+
# A +String+ with length +num_elements+ is returned.
|
50
|
+
# * +as != nil+ and +num_elements = 1+ and +force_array = false+:
|
51
|
+
# An +Object+ is returned. The type of +Object+ depends on parameter +as+.
|
52
|
+
# * +as != nil+ and +num_elements = 1+ and +force_array = true+:
|
53
|
+
# An array with one element is returned.
|
54
|
+
# * +as != nil+ and +num_elements > 1+:
|
55
|
+
# An array with length +num_elements+ is returned.
|
56
|
+
#
|
57
|
+
# If EOF is occured, object(s) read will be returned.
|
58
|
+
#
|
59
|
+
# @example
|
60
|
+
# stream = StringIO.new('A' * 8 + 'B' * 8)
|
61
|
+
# io = MemoryIO.new(stream)
|
62
|
+
# io.read(9)
|
63
|
+
# #=> "AAAAAAAAB"
|
64
|
+
# io.read(100)
|
65
|
+
# #=> "BBBBBBB"
|
66
|
+
#
|
67
|
+
# # read two unsigned 32-bit integers starts from posistion 4
|
68
|
+
# io.read(2, from: 4, as: :u32)
|
69
|
+
# #=> [1094795585, 1111638594] # [0x41414141, 0x42424242]
|
70
|
+
#
|
71
|
+
# io.read(1, as: :u16)
|
72
|
+
# #=> 16962 # 0x4242
|
73
|
+
# io.read(1, as: :u16, force_array: true)
|
74
|
+
# #=> [16962]
|
75
|
+
# @example
|
76
|
+
# stream = StringIO.new("\xef\xbe\xad\xde")
|
77
|
+
# io = MemoryIO.new(stream)
|
78
|
+
# io.read(1, as: :u32)
|
79
|
+
# #=> 3735928559
|
80
|
+
# io.rewind
|
81
|
+
# io.read(1, as: :s32)
|
82
|
+
# #=> -559038737
|
83
|
+
# @example
|
84
|
+
# stream = StringIO.new("123\x0045678\x00")
|
85
|
+
# io = MemoryIO.new(stream)
|
86
|
+
# io.read(2, as: :c_str)
|
87
|
+
# #=> ["123", "45678"]
|
88
|
+
# @example
|
89
|
+
# # pass lambda to `as`
|
90
|
+
# stream = StringIO.new("\x03123\x044567")
|
91
|
+
# io = MemoryIO.new(stream)
|
92
|
+
# io.read(2, as: lambda { |stream| stream.read(stream.read(1).ord) })
|
93
|
+
# #=> ["123", "4567"]
|
94
|
+
#
|
95
|
+
# @note
|
96
|
+
# This method's arguments and return value are different with +::IO#read+.
|
97
|
+
# Check documents and examples.
|
98
|
+
def read(num_elements, from: nil, as: nil, force_array: false)
|
99
|
+
stream.pos = from if from
|
100
|
+
return stream.read(num_elements) if as.nil?
|
101
|
+
conv = to_proc(as, :read)
|
102
|
+
# TODO: handle eof
|
103
|
+
ret = Array.new(num_elements) { conv.call(stream) }
|
104
|
+
ret = ret.first if num_elements == 1 && !force_array
|
105
|
+
ret
|
106
|
+
end
|
107
|
+
|
108
|
+
# Set +stream+ to the beginning.
|
109
|
+
# i.e. invoke +stream.pos = 0+.
|
110
|
+
#
|
111
|
+
# @return [0]
|
112
|
+
def rewind
|
113
|
+
stream.pos = 0
|
114
|
+
end
|
115
|
+
|
116
|
+
private
|
117
|
+
|
118
|
+
def to_proc(as, rw)
|
119
|
+
ret = as.respond_to?(:call) ? as : MemoryIO::Types.get_proc(as, rw)
|
120
|
+
raise ArgumentError, <<-EOERR.strip unless ret.respond_to?(:call)
|
121
|
+
Invalid argument `as`: #{as.inspect}. It should be either a Proc or a supported type of MemoryIO::Types.
|
122
|
+
EOERR
|
123
|
+
ret
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
module MemoryIO
|
2
|
+
# Records information of a process.
|
3
|
+
class Process
|
4
|
+
# @api private
|
5
|
+
# @return [#readable?, #writable?]
|
6
|
+
attr_reader :perm
|
7
|
+
|
8
|
+
# @api private
|
9
|
+
#
|
10
|
+
# Create process object from pid.
|
11
|
+
#
|
12
|
+
# @param [Integer] pid
|
13
|
+
# Process id.
|
14
|
+
#
|
15
|
+
# @note
|
16
|
+
# This class only supports procfs-based system. i.e. /proc is mounted and readable.
|
17
|
+
#
|
18
|
+
# @todo
|
19
|
+
# Support MacOS
|
20
|
+
# @todo
|
21
|
+
# Support Windows
|
22
|
+
def initialize(pid)
|
23
|
+
@pid = pid
|
24
|
+
@mem = "/proc/#{pid}/mem"
|
25
|
+
# check permission of '/proc/pid/mem'
|
26
|
+
@perm = MemoryIO::Util.file_permission(@mem)
|
27
|
+
# TODO: raise custom exception
|
28
|
+
raise Errno::ENOENT, @mem if perm.nil?
|
29
|
+
# FIXME: use logger
|
30
|
+
warn(<<-EOS.strip) unless perm.readable? || perm.writable?
|
31
|
+
You have no permission to read/write this process.
|
32
|
+
|
33
|
+
Check the setting of /proc/sys/kernel/yama/ptrace_scope, or try
|
34
|
+
again as the root user.
|
35
|
+
|
36
|
+
To enable attach another process, do:
|
37
|
+
|
38
|
+
$ echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
|
39
|
+
EOS
|
40
|
+
end
|
41
|
+
|
42
|
+
# Parse +/proc/[pid]/maps+ to get all bases.
|
43
|
+
#
|
44
|
+
# @return [{Symbol => Integer}]
|
45
|
+
# Hash of bases.
|
46
|
+
#
|
47
|
+
# @example
|
48
|
+
# process = Process.new(`pidof victim`.to_i)
|
49
|
+
# puts process.bases.map { |key, val| format('%s: 0x%016x', key, val) }
|
50
|
+
# # vsyscall: 0xffffffffff600000
|
51
|
+
# # vdso: 0x00007ffd5b565000
|
52
|
+
# # vvar: 0x00007ffd5b563000
|
53
|
+
# # stack: 0x00007ffd5ad21000
|
54
|
+
# # ld: 0x00007f339a69b000
|
55
|
+
# # libc: 0x00007f33996f1000
|
56
|
+
# # heap: 0x00005571994a1000
|
57
|
+
# # victim: 0x0000557198bcb000
|
58
|
+
# #=> nil
|
59
|
+
def bases
|
60
|
+
file = "/proc/#{@pid}/maps"
|
61
|
+
stat = MemoryIO::Util.file_permission(file)
|
62
|
+
return {} unless stat && stat.readable?
|
63
|
+
maps = ::IO.binread(file).split("\n").map do |line|
|
64
|
+
# 7f76515cf000-7f76515da000 r-xp 00000000 fd:01 29360257 /lib/x86_64-linux-gnu/libnss_files-2.24.so
|
65
|
+
addr, _perm, _offset, _dev, _inode, pathname = line.strip.split(' ', 6)
|
66
|
+
next nil if pathname.nil?
|
67
|
+
addr = addr.to_i(16)
|
68
|
+
pathname = pathname[1..-2] if pathname =~ /^\[.+\]$/
|
69
|
+
pathname = ::File.basename(pathname)
|
70
|
+
[MemoryIO::Util.trim_libname(pathname).to_sym, addr]
|
71
|
+
end
|
72
|
+
maps.compact.reverse.to_h
|
73
|
+
end
|
74
|
+
|
75
|
+
# Read from process's memory.
|
76
|
+
#
|
77
|
+
# This method has *almost* same arguements and return types as {IO#read}.
|
78
|
+
# The only difference is this method needs parameter +addr+ (which
|
79
|
+
# will be passed to paramter +from+ in {IO#reada}).
|
80
|
+
#
|
81
|
+
# @param [Integer, String] addr
|
82
|
+
# The address start to read.
|
83
|
+
# When String is given, it will be safe-evaluated.
|
84
|
+
# You can use variables such as +'heap'/'stack'/'libc'+ in this parameter.
|
85
|
+
# See examples.
|
86
|
+
# @param [Integer] num_elements
|
87
|
+
# Number of elements to read. See {IO#read}.
|
88
|
+
#
|
89
|
+
# @return [String, Object, Array<Object>]
|
90
|
+
# See {IO#read}.
|
91
|
+
#
|
92
|
+
# @example
|
93
|
+
# process = MemoryIO.attach(`pidof victim`.to_i)
|
94
|
+
# puts process.read('heap', 4, as: :u64).map { |c| '0x%016x' % c }
|
95
|
+
# # 0x0000000000000000
|
96
|
+
# # 0x0000000000000021
|
97
|
+
# # 0x00000000deadbeef
|
98
|
+
# # 0x0000000000000000
|
99
|
+
# #=> nil
|
100
|
+
# process.read('heap+0x10', 4, as: :u8).map { |c| '0x%x' % c }
|
101
|
+
# #=> ['0xef', '0xbe', '0xad', '0xde']
|
102
|
+
#
|
103
|
+
# process.read('libc', 4)
|
104
|
+
# #=> "\x7fELF"
|
105
|
+
# @see IO#read
|
106
|
+
def read(addr, num_elements, **options)
|
107
|
+
addr = MemoryIO::Util.safe_eval(addr, bases)
|
108
|
+
File.open(@mem, 'rb') do |f|
|
109
|
+
MemoryIO::IO.new(f).read(num_elements, from: addr, **options)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# encoding: ascii-8bit
|
2
|
+
|
3
|
+
require 'memory_io/types/type'
|
4
|
+
|
5
|
+
module MemoryIO
|
6
|
+
module Types
|
7
|
+
# Read a null-terminated string.
|
8
|
+
class CStr < Types::Type
|
9
|
+
# @api private
|
10
|
+
#
|
11
|
+
# @return [String]
|
12
|
+
# String without null byte.
|
13
|
+
def self.read(stream)
|
14
|
+
ret = ''
|
15
|
+
loop do
|
16
|
+
c = stream.read(1)
|
17
|
+
break if c.nil? || c == '' || c == "\x00"
|
18
|
+
ret << c
|
19
|
+
end
|
20
|
+
ret
|
21
|
+
end
|
22
|
+
|
23
|
+
# @api private
|
24
|
+
#
|
25
|
+
# @param [String] val
|
26
|
+
# A null byte would be appended if +val+ not ends with null byte.
|
27
|
+
def self.write(stream, val)
|
28
|
+
val = val.to_s
|
29
|
+
val << "\x00" unless val.end_with?("\x00")
|
30
|
+
stream.write(val)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'memory_io/types/type'
|
2
|
+
|
3
|
+
module MemoryIO
|
4
|
+
module Types
|
5
|
+
# @api private
|
6
|
+
# Register numbers to {Types}.
|
7
|
+
#
|
8
|
+
# All types registerd by this class are assumed as *little endian*.
|
9
|
+
class Number
|
10
|
+
# @param [Integer] bytes
|
11
|
+
# Bytes.
|
12
|
+
# @param [Boolean] signed
|
13
|
+
# Signed or unsigned.
|
14
|
+
# @param [String] pack_str
|
15
|
+
# The indicator to be passed to +Array#pack+ and +String#unpack+.
|
16
|
+
def initialize(bytes, signed, pack_str)
|
17
|
+
@bytes = bytes
|
18
|
+
@signed = signed
|
19
|
+
@pack_str = pack_str
|
20
|
+
end
|
21
|
+
|
22
|
+
# @return [Integer]
|
23
|
+
def read(stream)
|
24
|
+
unpack(stream.read(@bytes))
|
25
|
+
end
|
26
|
+
|
27
|
+
# @param [Integer] val
|
28
|
+
def write(stream, val)
|
29
|
+
stream.write(pack(val))
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def unpack(str)
|
35
|
+
val = str.unpack(@pack_str).first
|
36
|
+
val -= (2**@bytes) if @signed && val >= (2**(@bytes - 1))
|
37
|
+
val
|
38
|
+
end
|
39
|
+
|
40
|
+
def pack(val)
|
41
|
+
[val].pack(@pack_str)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Register (un)signed n-bites integers.
|
45
|
+
{
|
46
|
+
8 => 'C',
|
47
|
+
16 => 'S',
|
48
|
+
32 => 'I',
|
49
|
+
64 => 'Q'
|
50
|
+
}.each do |t, c|
|
51
|
+
Type.register("s#{t}".to_sym, Number.new(t, true, c))
|
52
|
+
Type.register("u#{t}".to_sym, Number.new(t, false, c))
|
53
|
+
end
|
54
|
+
|
55
|
+
# Register floating numbers.
|
56
|
+
Type.register(:float, Number.new(4, false, 'F'))
|
57
|
+
Type.register(:double, Number.new(8, false, 'D'))
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module MemoryIO
|
2
|
+
module Types
|
3
|
+
# The base class, all descendents of this class would be consider as a valid 'type'.
|
4
|
+
class Type
|
5
|
+
class << self
|
6
|
+
# Find the subclass of {Type} by symbol.
|
7
|
+
#
|
8
|
+
# @param [Symbol] symbol
|
9
|
+
# Symbol to be find.
|
10
|
+
#
|
11
|
+
# @return [#read, #write]
|
12
|
+
# The object that registered in {.register}.
|
13
|
+
def find(symbol)
|
14
|
+
@map[symbol]
|
15
|
+
end
|
16
|
+
|
17
|
+
# @api private
|
18
|
+
#
|
19
|
+
# @param [Symbol] symbol
|
20
|
+
# Symbol name that used for searching.
|
21
|
+
# @param [#read, #write] klass
|
22
|
+
# Normally, +klass+ is a descendent of {Type}.
|
23
|
+
#
|
24
|
+
# @return [void]
|
25
|
+
def register(symbol, klass)
|
26
|
+
@map ||= {}
|
27
|
+
@map[symbol] = klass
|
28
|
+
end
|
29
|
+
|
30
|
+
# TO record descendants.
|
31
|
+
def inherited(klass)
|
32
|
+
register(MemoryIO::Util.underscore(klass.name).to_sym, klass)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'memory_io/types/type'
|
2
|
+
require 'memory_io/util'
|
3
|
+
|
4
|
+
require 'memory_io/types/c_str'
|
5
|
+
require 'memory_io/types/number'
|
6
|
+
|
7
|
+
module MemoryIO
|
8
|
+
# Module that includes multiple types.
|
9
|
+
#
|
10
|
+
# Supported types are all descendants of {Types::Type}.
|
11
|
+
module Types
|
12
|
+
module_function
|
13
|
+
|
14
|
+
# @api private
|
15
|
+
#
|
16
|
+
# Returns the class whose name matches +name+.
|
17
|
+
#
|
18
|
+
# This method would search all descendants of {Types::Type}.
|
19
|
+
#
|
20
|
+
# @return [Symbol] name
|
21
|
+
# Class name to be searched.
|
22
|
+
#
|
23
|
+
# @return [Class?]
|
24
|
+
# The class.
|
25
|
+
#
|
26
|
+
# @example
|
27
|
+
# Types.find(:u64)
|
28
|
+
# #=> MemoryIO::Types::U64
|
29
|
+
#
|
30
|
+
# Types.find(:c_str)
|
31
|
+
# #=> MemoryIO::Types::CStr
|
32
|
+
def find(name)
|
33
|
+
Types::Type.find(Util.underscore(name.to_s).to_sym)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Returns a callable object according to +name+.
|
37
|
+
#
|
38
|
+
# @param [Symbol] name
|
39
|
+
# The name of type.
|
40
|
+
# @param [:read, :write] rw
|
41
|
+
# Read or write?
|
42
|
+
#
|
43
|
+
# @return [Proc?]
|
44
|
+
# The proc that accepts +stream+ as the first parameter.
|
45
|
+
#
|
46
|
+
# @example
|
47
|
+
# Types.get_proc(:c_str, :write)
|
48
|
+
# #=> #<Method: MemoryIO::Types::CStr.write>
|
49
|
+
# Types.get_proc(:s32, :read)
|
50
|
+
# #=> #<Method: MemoryIO::Types::Number#read>
|
51
|
+
def get_proc(name, rw)
|
52
|
+
klass = find(name)
|
53
|
+
klass && klass.method(rw)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
require 'dentaku'
|
3
|
+
|
4
|
+
module MemoryIO
|
5
|
+
# Defines utility methods.
|
6
|
+
module Util
|
7
|
+
module_function
|
8
|
+
|
9
|
+
# Convert input into snake-case.
|
10
|
+
#
|
11
|
+
# This method also removes strings before +'::'+ in +str+.
|
12
|
+
#
|
13
|
+
# @param [String] str
|
14
|
+
# String to be converted.
|
15
|
+
#
|
16
|
+
# @return [String]
|
17
|
+
# Converted string.
|
18
|
+
#
|
19
|
+
# @example
|
20
|
+
# Util.underscore('MemoryIO')
|
21
|
+
# #=> 'memory_io'
|
22
|
+
#
|
23
|
+
# Util.underscore('MyModule::MyClass')
|
24
|
+
# #=> 'my_class'
|
25
|
+
def underscore(str)
|
26
|
+
return '' if str.empty?
|
27
|
+
str = str.split('::').last
|
28
|
+
str.gsub!(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
29
|
+
str.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
|
30
|
+
str.downcase!
|
31
|
+
str
|
32
|
+
end
|
33
|
+
|
34
|
+
# @api private
|
35
|
+
#
|
36
|
+
# @param [String] file
|
37
|
+
# File name.
|
38
|
+
#
|
39
|
+
# @return [#readable?, #writable?, nil]
|
40
|
+
# Struct with two boolean method.
|
41
|
+
# +nil+ for file not exists or is inaccessible.
|
42
|
+
def file_permission(file)
|
43
|
+
return nil unless File.file?(file)
|
44
|
+
stat = File.stat(file)
|
45
|
+
# we do a trick here because /proc/[pid]/mem might be marked as readable but fails at sysopen.
|
46
|
+
os = OpenStruct.new(readable?: stat.readable_real?, writable?: stat.writable_real?)
|
47
|
+
begin
|
48
|
+
os.readable? && File.open(file, 'rb').close
|
49
|
+
rescue Errno::EACCES
|
50
|
+
os[:readable?] = false
|
51
|
+
end
|
52
|
+
begin
|
53
|
+
os.writable? && File.open(file, 'wb').close
|
54
|
+
rescue Errno::EACCES
|
55
|
+
os[:writable?] = false
|
56
|
+
end
|
57
|
+
os
|
58
|
+
end
|
59
|
+
|
60
|
+
# Evaluate string safely.
|
61
|
+
#
|
62
|
+
# @param [String] str
|
63
|
+
# String to be evaluated.
|
64
|
+
# @param [{Symbol => Integer}] vars
|
65
|
+
# Predefined variables
|
66
|
+
#
|
67
|
+
# @return [Integer]
|
68
|
+
# Result.
|
69
|
+
#
|
70
|
+
# @example
|
71
|
+
# Util.safe_eval('heap + 0x10 * pp', heap: 0xde00, pp: 8)
|
72
|
+
# #=> 56960 # 0xde80
|
73
|
+
def safe_eval(str, **vars)
|
74
|
+
return str if str.is_a?(Integer)
|
75
|
+
# dentaku 2 doesn't support hex
|
76
|
+
str = str.gsub(/0x[0-9a-zA-Z]+/) { |c| c.to_i(16) }
|
77
|
+
Dentaku::Calculator.new.store(vars).evaluate(str)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Remove extension name (.so) and version in library name.
|
81
|
+
#
|
82
|
+
# @param [String] name
|
83
|
+
# Original library filename.
|
84
|
+
#
|
85
|
+
# @return [String]
|
86
|
+
# Name without version and '.so'.
|
87
|
+
#
|
88
|
+
# @example
|
89
|
+
# Util.trim_libname('libc-2.24.so')
|
90
|
+
# #=> 'libc'
|
91
|
+
# Util.trim_libname('libcrypto.so.1.0.0')
|
92
|
+
# #=> 'libcrypto'
|
93
|
+
# Util.trim_libname('not_a_so')
|
94
|
+
# #=> 'not_a_so'
|
95
|
+
def trim_libname(name)
|
96
|
+
type1 = '(-[\d.]+)?\.so$'
|
97
|
+
type2 = '\.so.\d+[\d.]+$'
|
98
|
+
name.sub(/#{type1}|#{type2}/, '')
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
data/lib/memory_io.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# MemoryIO - Read/Write structures in memory.
|
2
|
+
#
|
3
|
+
# @author david942j
|
4
|
+
module MemoryIO
|
5
|
+
module_function
|
6
|
+
|
7
|
+
# Get a process by process id.
|
8
|
+
#
|
9
|
+
# @param [Integer] pid
|
10
|
+
# Process Id in linux.
|
11
|
+
#
|
12
|
+
# @return [MemoryIO::Process]
|
13
|
+
# A process object for further usage.
|
14
|
+
def attach(pid)
|
15
|
+
MemoryIO::Process.new(pid)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
require 'memory_io/io'
|
20
|
+
require 'memory_io/process'
|
21
|
+
require 'memory_io/version'
|
metadata
ADDED
@@ -0,0 +1,158 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: memory_io
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- david942j
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-01-02 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: dentaku
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 2.0.11
|
20
|
+
- - "<"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '3.0'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 2.0.11
|
30
|
+
- - "<"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '3.0'
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: codeclimate-test-reporter
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0.6'
|
40
|
+
type: :development
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - "~>"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0.6'
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: rake
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '12.0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - "~>"
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '12.0'
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: rspec
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - "~>"
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '3.5'
|
68
|
+
type: :development
|
69
|
+
prerelease: false
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - "~>"
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '3.5'
|
75
|
+
- !ruby/object:Gem::Dependency
|
76
|
+
name: rubocop
|
77
|
+
requirement: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - "~>"
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '0.52'
|
82
|
+
type: :development
|
83
|
+
prerelease: false
|
84
|
+
version_requirements: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - "~>"
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0.52'
|
89
|
+
- !ruby/object:Gem::Dependency
|
90
|
+
name: simplecov
|
91
|
+
requirement: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - "~>"
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: 0.13.0
|
96
|
+
type: :development
|
97
|
+
prerelease: false
|
98
|
+
version_requirements: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - "~>"
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: 0.13.0
|
103
|
+
- !ruby/object:Gem::Dependency
|
104
|
+
name: yard
|
105
|
+
requirement: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - "~>"
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0.9'
|
110
|
+
type: :development
|
111
|
+
prerelease: false
|
112
|
+
version_requirements: !ruby/object:Gem::Requirement
|
113
|
+
requirements:
|
114
|
+
- - "~>"
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '0.9'
|
117
|
+
description: ''
|
118
|
+
email:
|
119
|
+
- david942j@gmail.com
|
120
|
+
executables: []
|
121
|
+
extensions: []
|
122
|
+
extra_rdoc_files: []
|
123
|
+
files:
|
124
|
+
- README.md
|
125
|
+
- lib/memory_io.rb
|
126
|
+
- lib/memory_io/io.rb
|
127
|
+
- lib/memory_io/process.rb
|
128
|
+
- lib/memory_io/types/c_str.rb
|
129
|
+
- lib/memory_io/types/number.rb
|
130
|
+
- lib/memory_io/types/type.rb
|
131
|
+
- lib/memory_io/types/types.rb
|
132
|
+
- lib/memory_io/util.rb
|
133
|
+
- lib/memory_io/version.rb
|
134
|
+
homepage: https://github.com/david942j/memory_io
|
135
|
+
licenses:
|
136
|
+
- MIT
|
137
|
+
metadata: {}
|
138
|
+
post_install_message:
|
139
|
+
rdoc_options: []
|
140
|
+
require_paths:
|
141
|
+
- lib
|
142
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
143
|
+
requirements:
|
144
|
+
- - ">="
|
145
|
+
- !ruby/object:Gem::Version
|
146
|
+
version: 2.1.0
|
147
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
148
|
+
requirements:
|
149
|
+
- - ">="
|
150
|
+
- !ruby/object:Gem::Version
|
151
|
+
version: '0'
|
152
|
+
requirements: []
|
153
|
+
rubyforge_project:
|
154
|
+
rubygems_version: 2.6.14
|
155
|
+
signing_key:
|
156
|
+
specification_version: 4
|
157
|
+
summary: memory_io
|
158
|
+
test_files: []
|