Class: HeapInfo::Process

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

Overview

Main class of heapinfo.

Constant Summary collapse

DEFAULT_LIB =

The default options of libraries, use for matching glibc segments in /proc/[pid]/maps.

{
  libc: /bc[^a-z]*\.so/
}.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(prog, options = {}) ⇒ Process

Instantiate a HeapInfo::Process object.

Parameters:

  • prog (String, Integer) —

    Process name or pid, see heapinfo for more information.

  • options (Hash{Symbol => Regexp, String}) (defaults to: {}) —

    Libraries' filename, see heapinfo for more information.



24
25
26
27
28
29
30
31
32
33
34
35
36
37
# File 'lib/heapinfo/process.rb', line 24

def initialize(prog, options = {})
  @prog = prog
  @options = DEFAULT_LIB.merge options
  @pid = nil
  # Transparent info's methods
  ProcessInfo::EXPORT.each do |m|
    define_singleton_method(m) do
      return Nil.instance if @pid.nil?

      @info.__send__(m)
    end
  end
  load!
end

Instance Attribute Details

#pid ⇒ Integer? (readonly)

Returns The pid of process, nil if no such process found.

Returns:

  • (Integer, nil) —

    The pid of process, nil if no such process found.



18
19
20
# File 'lib/heapinfo/process.rb', line 18

def pid
  @pid
end

Instance Method Details

#canary ⇒ Integer

Get the value of stack guard.

Examples:

h.canary
#=> 11342701118118205184 # 0x9d695e921adc9700

Returns:

  • (Integer)


323
324
325
326
327
328
# File 'lib/heapinfo/process.rb', line 323

def canary
  return Nil.instance unless load?

  addr = @info.auxv[:random]
  Helper.unpack(bits / 8, @dumper.dump(addr, bits / 8)) & 0xffffffffffffff00
end

#debug ⇒ Object

Use this method to wrapper all HeapInfo methods.

Since HeapInfo is a tool(debugger) for local usage, while exploiting remote service, all methods will not work properly. So I suggest to wrapper all methods inside #debug, which will ignore the block while the victim process is not found.

Examples:

h = heapinfo('./victim') # such process doesn't exist
libc_base = leak_libc_base_of_victim # normal exploit
h.debug {
  # for local to check if exploit correct
  fail('libc_base') unless libc_base == h.libc.base
}
# block of #debug will not execute if can't found process


66
67
68
69
70
# File 'lib/heapinfo/process.rb', line 66

def debug
  return unless load!

  yield if block_given?
end

#dump(*args) ⇒ String, HeapInfo::Nil

Dump the content of specific memory address.

Note: This method require you have permission of attaching another process. If not, a warning message will present.

Examples:

h = heapinfo('victim')
h.dump(:heap) # heap[0, 8]
h.dump(:heap, 64) # heap[0, 64]
h.dump('heap+256', 64)  # heap[256, 64]
h.dump('heap+0x100', 64) # heap[256, 64]
h.dump('heap+0x100 * 2 + 0x300', 64) # heap[1024, 64]
h.dump(<segment>, 8) # segment can be [heap, stack, (program|elf), libc, ld]
h.dump(addr, 64) # addr[0, 64]

# Invalid usage
dump(:meow) # no such segment

Parameters:

  • args (Mixed) —

    Will be parsed into [base, length], see Examples for more information.

Returns:

  • (String, HeapInfo::Nil) —

    The content needed. When the request address is not readable or the process not exists, instance of Nil is returned.



94
95
96
97
98
# File 'lib/heapinfo/process.rb', line 94

def dump(*args)
  return Nil.instance unless load?

  dumper.dump(*args)
end

#dump_chunks(*args) ⇒ HeapInfo::Chunks, HeapInfo::Nil

Return the dump result as chunks. see Dumper#dump_chunks for more information.

Parameters:

  • args (Mixed) —

    Same as arguments of #dump.

Returns:



105
106
107
108
109
# File 'lib/heapinfo/process.rb', line 105

def dump_chunks(*args)
  return Nil.instance unless load?

  dumper.dump_chunks(*args)
end

#find(pattern, from, length = :unlimited, rel: false) ⇒ Integer? Also known as: search

GDB-style command.

Search a specific value/string/regexp in memory.

Examples:

h.find(0xdeadbeef, 'heap+0x10', 0x1000)
#=> 6299664 # 0x602010
h.find(/E.F/, 0x400000, 4)
#=> 4194305 # 0x400001
h.find(/E.F/, 0x400000, 3)
#=> nil
sh_offset = h.find('/bin/sh', :libc) - h.libc
#=> 1559771 # 0x17ccdb
h.find('/bin/sh', :libc, rel: true) == h.find('/bin/sh', :libc) - h.libc
#=> true

Parameters:

  • pattern (Integer, String, Regexp) —

    The desired search pattern, can be value(+Integer+), string, or regular expression.

  • from (Integer, String, Symbol) —

    Start address for searching, can be segment(+Symbol+) or segments with offset. See examples for more information.

  • length (Integer) (defaults to: :unlimited) —

    The search length limit, default is unlimited, which will search until pattern found or reach unreadable memory.

  • rel (Boolean) (defaults to: false) —

    To show relative offset of from or absolute address.

Returns:

  • (Integer, nil) —

    The first matched address, nil is returned when no such pattern found.



209
210
211
212
213
# File 'lib/heapinfo/process.rb', line 209

def find(pattern, from, length = :unlimited, rel: false)
  return Nil.instance unless load?

  dumper.find(pattern, from, length, rel)
end

#find_all(pattern, from = 0, to = 1 << 64) ⇒ void Also known as: findall

This method returns an undefined value.

Find pattern in all segments with pretty output.

The search result will be output to $stdout.

Examples:

h = heapinfo('victim')
h.find_all(0xdeadbeef)
# Searching 0xdeadbeef:
# In [heap](0x563055f3c000-0x56305b82c000), permission=rw-
#   0x563058076510
#   0x563058253d50
#=> nil
h = heapinfo('victim')
h.find_all(h.canary)
# Searching 0xc83db42feb3c0f00:
# In (0x7ffff7fd3000-0x7ffff7ff7000), permission=rw-
#   0x7ffff7ff5728
# In [stack](0x7ffffffdd000-0x7ffffffff000), permission=rw-
#   0x7fffffffda28
#=> nil
h = heapinfo('victim')
h.find_all(h.canary, :ld, :stack)
# Searching 0xc83db42feb3c0f00:
# In (0x7ffff7fd3000-0x7ffff7ff7000), permission=rw-
#   0x7ffff7ff5728
#=> nil

Parameters:

  • pattern (Integer, String, Regexp) —

    The desired search pattern, can be value(+Integer+), string, or regular expression.

  • from (String, Symbol, Integer) (defaults to: 0) —

    Instead of searching all mapped segments, find the pattern from this address. from can be an address, symbol name of segments (+:ld/:libc/:heap+, etc.) or string with address calculation, see #dump or Dumper#base_of for examples of string annotation.

  • to (String, Symbol, Integer) (defaults to: 1 << 64) —

    End address for searching. In the same format as from.



255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
# File 'lib/heapinfo/process.rb', line 255

def find_all(pattern, from = 0, to = 1 << 64)
  return Nil.instance unless load?

  from = dumper.base_of(from)
  to = dumper.base_of(to)
  result = []
  HeapInfo::Helper.parsed_maps(pid).each do |st, ed, perm, name|
    next if st >= to || ed < from || !perm.include?('r')

    start = [st, from].max
    len = [ed, to].min - start
    matches = dumper.scan(pattern, start, len).map { |v| v + start }
    result << [st, ed, perm, name, matches] if matches.any?
  end

  target = pattern.is_a?(Integer) ? Helper.hex(pattern) : pattern.inspect
  str = ["Searching #{Helper.color(target)}:"]
  str.concat(format_findall_result(result))
  $stdout.puts(str)
end

#inspect ⇒ String

Make pry not so verbose.

Returns:

  • (String)


333
334
335
# File 'lib/heapinfo/process.rb', line 333

def inspect
  format('#<HeapInfo::Process:0x%016x>', __id__)
end

#layouts(*args) ⇒ void

This method returns an undefined value.

Pretty dump of bins' layouts.

The request layouts will output to stdout.

Examples:

h.layouts(:fast, :unsorted, :small)
# ...
h.layouts(:tcache)
# ...
h.layouts(:all) # show all bin(s), includes tcache

Parameters:

  • args (Array<Symbol>) —

    Bin type(s) you want to see.



288
289
290
291
292
293
294
295
296
# File 'lib/heapinfo/process.rb', line 288

def layouts(*args)
  return unless load?

  args << :all if args.empty?
  str = +''
  str << libc.tcache.layouts if libc.tcache? && (%w[all tcache] & args.map(&:to_s)).any?
  str << libc.main_arena.layouts(*args)
  $stdout.puts(str)
end

#offset(addr, sym = nil) ⇒ void Also known as: off

This method returns an undefined value.

Show the offset in pretty way between the segment. Very useful in pwn when leak some address, see examples for more details.

Examples:

h.offset(0x7f11f6ae1670, :libc)
#=> 0xf6670 after libc
h.offset(0x5559edc057a0, :heap)
#=> 0x9637a0 after heap
h.offset(0x7f11f6ae1670)
#=> 0xf6670 after :libc
h.offset(0x5559edc057a0)
#=> 0x9637a0 after :heap

Parameters:

  • addr (Integer) —

    The leaked address.

  • sym (Symbol) (defaults to: nil) —

    The segment symbol to be calculated offset. If this parameter not given, will loop segments and find the most close one. See examples for more details.



129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/heapinfo/process.rb', line 129

def offset(addr, sym = nil)
  return unless load?

  segment = @info.to_segment(sym)
  if segment.nil?
    sym, segment = @info.segments
                        .select { |_, seg| seg.base <= addr }
                        .min_by { |_, seg| addr - seg }
  end
  return $stdout.puts("Invalid address #{Helper.color_hex(addr)}") if segment.nil?

  $stdout.puts(Helper.color_hex(addr - segment) + ' after ' + Helper.color(sym, sev: :sym))
end

#reload! ⇒ HeapInfo::Process Also known as: reload

Reload a new process with same program name.

Examples:

puts h.reload!

Returns:



44
45
46
47
48
# File 'lib/heapinfo/process.rb', line 44

def reload!
  @pid = nil
  load!
  self
end

#s(address) ⇒ String

GDB-style command

Dump a string until reach the null-byte.

Parameters:

  • address (String, Symbol, Integer) —

    The base address to be dumped. See #dump.

Returns:

  • (String) —

    The string without null-byte.



178
179
180
181
182
# File 'lib/heapinfo/process.rb', line 178

def s(address)
  return Nil.instance unless load?

  dumper.cstring(address)
end

#to_s ⇒ String

Show simple information of target process.

Contains program names, pid, and segments' info.

Examples:

puts h

Returns:

  • (String)


305
306
307
308
309
310
311
312
313
314
315
# File 'lib/heapinfo/process.rb', line 305

def to_s
  return 'Process not found' unless load?

  "Program: #{Helper.color(program.name)} PID: #{Helper.color(pid)}\n" +
    program.to_s +
    heap.to_s +
    stack.to_s +
    libc.to_s +
    ld.to_s +
    format("%-28s\tvalue: #{Helper.color(format('%#x', canary), sev: :sym)}", Helper.color('canary', sev: :sym))
end

#x(count, address) ⇒ void

This method returns an undefined value.

GDB-style command

Show dump results like gdb's command x. While will auto detect the current elf class to decide using gx or wx.

The dump results wrapper with color codes and nice typesetting will output to stdout.

Examples:

h.x 8, :heap
# 0x1f0d000:      0x0000000000000000      0x0000000000002011
# 0x1f0d010:      0x00007f892a9f87b8      0x00007f892a9f87b8
# 0x1f0d020:      0x0000000000000000      0x0000000000000000
# 0x1f0d030:      0x0000000000000000      0x0000000000000000
h.x 3, 0x400000
# 0x400000:       0x00010102464c457f      0x0000000000000000
# 0x400010:       0x00000001003e0002

Parameters:

  • count (Integer) —

    The number of result need to dump, see examples for more information.

  • address (String, Symbol, Integer) —

    The base address to be dumped. Same format as #dump, see #dump for more information.



164
165
166
167
168
# File 'lib/heapinfo/process.rb', line 164

def x(count, address)
  return unless load?

  dumper.x(count, address)
end