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.



23
24
25
26
27
# File 'lib/heapinfo/process.rb', line 23

def initialize(prog, options = {})
  @prog = prog
  @options = DEFAULT_LIB.merge options
  load!
end

Instance Attribute Details

#pidInteger? (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.



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

def pid
  @pid
end

Instance Method Details

#canaryInteger

Get the value of stack guard.

Examples:

h.canary
#=> 11342701118118205184 # 0x9d695e921adc9700

Returns:

  • (Integer)


263
264
265
266
267
# File 'lib/heapinfo/process.rb', line 263

def canary
  return Nil.new unless load?
  addr = @info.auxv[:random]
  Helper.unpack(bits / 8, @dumper.dump(addr, bits / 8)) & 0xffffffffffffff00
end

#debugObject

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


56
57
58
59
# File 'lib/heapinfo/process.rb', line 56

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) # semgent 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.



83
84
85
86
# File 'lib/heapinfo/process.rb', line 83

def dump(*args)
  return Nil.new 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:



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

def dump_chunks(*args)
  return Nil.new unless load?
  dumper.dump_chunks(*args)
end

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

Gdb-like 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.



192
193
194
195
# File 'lib/heapinfo/process.rb', line 192

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

#find_all(pattern, segment = :all) ⇒ void Also known as: findall

This method returns an undefined value.

Find pattern in all segments with pretty output.

Parameters:

  • pattern (Integer, String, Regexp)

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

  • segment (Symbol, Array<Symbol>) (defaults to: :all)

    Only find pattern in these symbols.



206
207
208
209
210
211
212
213
214
215
216
217
# File 'lib/heapinfo/process.rb', line 206

def find_all(pattern, segment = :all)
  return Nil.new unless load?
  segments = segment == :all ? %i[elf heap libc ld stack] : Array(segment)
  result = findall_raw(pattern, segments).reject { |(_, _, ary)| ary.empty? }
  target = pattern.is_a?(Integer) ? Helper.hex(pattern) : pattern.inspect
  str = ["Searching #{Helper.color(target)}:\n"]
  str.concat(result.map do |(sym, base, ary)|
    "In #{Helper.color(sym, sev: :bin)} (#{Helper.color(Helper.hex(base))}):\n" +
    ary.map { |v| "  #{Helper.color(sym, sev: :bin)}+#{Helper.color(Helper.hex(v))}\n" }.join
  end)
  $stdout.puts str
end

#inspectString

Make pry not so verbose.

Returns:

  • (String)


272
273
274
# File 'lib/heapinfo/process.rb', line 272

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.



231
232
233
234
235
236
237
# File 'lib/heapinfo/process.rb', line 231

def layouts(*args)
  return unless load?
  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 segement symbol to be calculated offset. If this parameter not given, will loop segments and find the most close one. See examples for more details.



116
117
118
119
120
121
122
123
124
125
126
# File 'lib/heapinfo/process.rb', line 116

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.hex(addr)}" if segment.nil?
  $stdout.puts Helper.color(Helper.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:



34
35
36
37
38
# File 'lib/heapinfo/process.rb', line 34

def reload!
  @pid = nil
  load!
  self
end

#s(address) ⇒ String

Gdb-like 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.



162
163
164
165
# File 'lib/heapinfo/process.rb', line 162

def s(address)
  return Nil.new unless load?
  dumper.cstring(address)
end

#to_sString

Show simple information of target process.

Contains program names, pid, and segments’ info.

Examples:

puts h

Returns:

  • (String)


246
247
248
249
250
251
252
253
254
255
# File 'lib/heapinfo/process.rb', line 246

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-like 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.



149
150
151
152
# File 'lib/heapinfo/process.rb', line 149

def x(count, address)
  return unless load?
  dumper.x(count, address)
end