Class: MemoryIO::Process

Inherits:
Object
  • Object
show all
Defined in:
lib/memory_io/process.rb

Overview

Records information of a process.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(pid) ⇒ Process

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

TODO:

Support MacOS

TODO:

Support Windows

Note:

This class only supports procfs-based system. i.e. /proc is mounted and readable.

Create process object from pid.

Parameters:

  • pid (Integer)

    Process id.

Raises:

  • (Errno::ENOENT)


24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/memory_io/process.rb', line 24

def initialize(pid)
  @pid = pid
  @mem = "/proc/#{pid}/mem"
  # check permission of '/proc/pid/mem'
  @perm = MemoryIO::Util.file_permission(@mem)
  # TODO: raise custom exception
  raise Errno::ENOENT, @mem if perm.nil?

  # FIXME: use logger
  warn(<<-EOS.strip) unless perm.readable? || perm.writable?
You have no permission to read/write this process.

Check the setting of /proc/sys/kernel/yama/ptrace_scope, or try
again as the root user.

To enable attach another process, do:

$ echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
  EOS
end

Instance Attribute Details

#perm#readable?, #writable? (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns:

  • (#readable?, #writable?)


8
9
10
# File 'lib/memory_io/process.rb', line 8

def perm
  @perm
end

Instance Method Details

#bases{Symbol => Integer}

Parse /proc/[pid]/maps to get all bases.

Examples:

process = Process.new(`pidof victim`.to_i)
puts process.bases.map { |key, val| format('%s: 0x%016x', key, val) }
# vsyscall: 0xffffffffff600000
# vdso: 0x00007ffd5b565000
# vvar: 0x00007ffd5b563000
# stack: 0x00007ffd5ad21000
# ld: 0x00007f339a69b000
# libc: 0x00007f33996f1000
# heap: 0x00005571994a1000
# victim: 0x0000557198bcb000
#=> nil

Returns:

  • ({Symbol => Integer})

    Hash of bases.



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/memory_io/process.rb', line 62

def bases
  file = "/proc/#{@pid}/maps"
  stat = MemoryIO::Util.file_permission(file)
  return {} unless stat&.readable?

  maps = ::IO.binread(file).split("\n").map do |line|
    # 7f76515cf000-7f76515da000 r-xp 00000000 fd:01 29360257  /lib/x86_64-linux-gnu/libnss_files-2.24.so
    addr, _perm, _offset, _dev, _inode, pathname = line.strip.split(' ', 6)
    next nil if pathname.nil?

    addr = addr.to_i(16)
    pathname = pathname[1..-2] if pathname =~ /^\[.+\]$/
    pathname = ::File.basename(pathname)
    [MemoryIO::Util.trim_libname(pathname).to_sym, addr]
  end
  maps.compact.reverse.to_h
end

#read(addr, num_elements, **options) ⇒ String, ...

Read from process’s memory.

This method has almost same arguements and return types as IO#read. The only difference is this method needs parameter addr (which will be passed to paramter from in IO#read).

Examples:

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"

Parameters:

  • addr (Integer, String)

    The address start to read. When String is given, it will be safe-evaluated. You can use variables such as ‘heap’/‘stack’/‘libc’ in this parameter. See examples.

  • num_elements (Integer)

    Number of elements to read. See IO#read.

Returns:

  • (String, Object, Array<Object>)

    See IO#read.

See Also:



111
112
113
# File 'lib/memory_io/process.rb', line 111

def read(addr, num_elements, **options)
  mem_io(:read) { |io| io.read(num_elements, from: MemoryIO::Util.safe_eval(addr, **bases), **options) }
end

#write(addr, objects, **options) ⇒ void

This method returns an undefined value.

Write objects at addr.

This method has almost same arguments as IO#write.

Examples:

process = MemoryIO.attach('self')
s = 'A' * 16
process.write(s.object_id * 2 + 16, 'BBBBCCCC')
s
#=> 'BBBBCCCCAAAAAAAA'

Parameters:

  • addr (Integer, String)

    The address to start to write. See examples.

  • objects (Object, Array<Object>)

    Objects to write. If objects is an array, the write procedure will be invoked objects.size times.

See Also:



135
136
137
# File 'lib/memory_io/process.rb', line 135

def write(addr, objects, **options)
  mem_io(:write) { |io| io.write(objects, from: MemoryIO::Util.safe_eval(addr, **bases), **options) }
end