Class: Ragweed::Process

Inherits:
Object show all
Includes:
Ragweed
Defined in:
lib/ragweed/wrap32/process.rb,
lib/ragweed/wraptux/process.rb

Defined Under Namespace

Classes: AdlerChart

Constant Summary

Constants included from Ragweed

LIBPATH, PATH, VERSION

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Ragweed

libpath, path, require_all_libs_relative_to, require_os_libs_relative_to, version

Constructor Details

#initialize(pid) ⇒ Process

Just need a PID to get started.



154
155
156
157
158
# File 'lib/ragweed/wrap32/process.rb', line 154

def initialize(pid) 
  @pid = pid
  @h = Ragweed::Wrap32::open_process(pid)
  @a = arena()
end

Instance Attribute Details

#pidObject (readonly)

Returns the value of attribute pid.



3
4
5
# File 'lib/ragweed/wrap32/process.rb', line 3

def pid
  @pid
end

Class Method Details

.by_name(n) ⇒ Object

Look up a process by name or regex, returning an array of all matching processes, as objects.



142
143
144
145
146
147
148
149
150
151
# File 'lib/ragweed/wrap32/process.rb', line 142

def self.by_name(n)
  n = Regexp.new(n) if not n.kind_of? Regexp
  p = []
  all_processes do |px|
    if px.szExeFile =~ n
      p << self.new(px.th32ProcessID)
    end
  end
  p
end

.find_by_regex(name) ⇒ Object



6
7
8
9
10
11
12
# File 'lib/ragweed/wrap32/process.rb', line 6

def self.find_by_regex(name)
  Ragweed::Wrap32::all_processes do |p|
    if p.szExeFile =~ name
      return self.new(p.th32ProcessID)
    end
  end
end

Instance Method Details

#adler_chart(i) ⇒ Object

See WinProcess::AdlerChart. Get one.



524
525
526
# File 'lib/ragweed/wrap32/process.rb', line 524

def adler_chart(i)
  AdlerChart.new self, i
end

#adler_compare(i, orig, opts = {}) ⇒ Object

If you store the adler map, you’ve compressed the memory region down to a small series of fixnums, and you can use it with this function to re-check the memory region and see if anything’s changing.



472
473
474
475
476
477
478
479
480
481
482
483
484
485
# File 'lib/ragweed/wrap32/process.rb', line 472

def adler_compare(i, orig, opts={})
  refresh opts
  window = windowize(i, opts)
  ret = []
  c = -1
  scan(i, opts) do |block,soff|
    0.stepwith(block.size-1, window) do |off, len|
      if block[off,len].adler != orig[c += 1]
        ret << soff+off
      end
    end
  end
  ret 
end

#adler_map(i, opts = {}) ⇒ Object

Like entropy_map, scan a process and compute adler16 checksums for 1k (or :window) blocks.



455
456
457
458
459
460
461
462
463
464
465
466
467
# File 'lib/ragweed/wrap32/process.rb', line 455

def adler_map(i, opts={})
  refresh opts
  window = windowize(i, opts)
  ret = []
  scan(i, opts) do |block,soff|
    0.stepwith(block.size-1, window) do |off, len|
      if (b = block[off,len])
        ret << b.adler 
      end
    end
  end
  ret
end

#alloc(sz, syscall = false) ⇒ Object

Use arenas, when possible, to quickly allocate memory. The upside is this is very fast. The downside is you can’t free the memory without invalidating every allocation you’ve made prior.



237
238
239
240
241
242
243
# File 'lib/ragweed/wrap32/process.rb', line 237

def alloc(sz, syscall=false) 
  if syscall or sz > 4090
    ret = syscall_alloc(sz)
  else
    ptr(@a.alloc(sz))
  end
end

#arena(&block) ⇒ Object

Get another allocation arena for this process. Pretty cheap. Given a block, behaves like File#open, disposing of the arena when you’re done.



269
270
271
272
273
274
275
276
277
278
279
280
# File 'lib/ragweed/wrap32/process.rb', line 269

def arena(&block)
  me = self
  a = Arena.new(lambda {me.syscall_alloc(4096)}, 
                lambda {|p| me.free(p)},
                lambda {|dst, src| me.write(dst, src)})
  if block_given?
    ret = yield a
    a.release
    return ret
  end
  a
end

#changes(i, sleeptime = 0.5) ⇒ Object

Given a memory region, use the adler routines to create a checksum map, wait a short period of time, and scan for changes, to find churning memory.



531
532
533
534
535
# File 'lib/ragweed/wrap32/process.rb', line 531

def changes(i, sleeptime=0.5)
  q = adler_map(i)
  sleep(sleeptime)
  adler_compare(i, q)
end

#detour(loc, o = {}) ⇒ Object



588
589
590
591
592
593
594
# File 'lib/ragweed/wrap32/process.rb', line 588

def detour(loc, o={})
  klass = o[:class] || Detour
  loc = get_proc(loc)
  r = klass.new(loc, o)
  r.call if not o[:chicken]
  return r
end

#dump_memory(i, opts = {}) ⇒ Object

Print a canonical hexdump of an entire memory region by region number.



325
# File 'lib/ragweed/wrap32/process.rb', line 325

def dump_memory(i, opts={}); get_memory(i, opts).hexdump; end

#dump_memory_listObject

Human-readable standard output of list_memory. Remember that the index number is important.



313
314
315
316
# File 'lib/ragweed/wrap32/process.rb', line 313

def dump_memory_list
  list_memory.each_with_index {|x,i| puts "#{ i }. #{ x[0].to_s(16) }(#{ x[1] })"}
  true
end

#dump_stack_trace(tid) ⇒ Object Also known as: bt

Human-readable version of thread_stack_trace, with module offsets.



581
582
583
584
585
# File 'lib/ragweed/wrap32/process.rb', line 581

def dump_stack_trace(tid)
  thread_stack_trace(tid).each do |frame, code|
    puts "#{ frame.to_x } @ #{ to_modoff(code) }"
  end
end

#dup_handle(h) ⇒ Object

clone a handle from the remote process to here (to here? tf?)



23
24
25
# File 'lib/ragweed/wrap32/process.rb', line 23

def dup_handle(h)
  Ragweed::Wrap32::duplicate_handle(@h, h)
end

#entropy_map(i, opts = {}) ⇒ Object

Take a region of memory and walk over it in 255-byte samples (less than 255 bytes and you lose accuracy, but you can increase it with the “:window” option), computing entropy for each sample, returning a list of [offset,entropy] tuples.



357
358
359
360
361
362
363
364
365
366
367
368
# File 'lib/ragweed/wrap32/process.rb', line 357

def entropy_map(i, opts={})
  ret = []
  startoff = opts[:starting_offset]
  startoff ||= 0
  window = opts[:window] || 255
  scan(i, opts) do |block, soff|
    startoff.stepwith(block.size, window) do |off, len|
      ret << [off, block[off,len].entropy]
    end
  end
  return ret
end

#free(off) ⇒ Object

Free the return value of syscall_alloc. Do NOT use for the return value of alloc.



247
248
249
# File 'lib/ragweed/wrap32/process.rb', line 247

def free(off)
  Ragweed::Wrap32::virtual_free_ex(@h, off)
end

#get_deferred_proc_remote(name, handle, base_of_dll) ⇒ Object

This only gets called for breakpoints in modules that have just been loaded and detected by a LOAD_DLL event. It is called from on_load_dll() -> deferred_install()



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/ragweed/wrap32/process.rb', line 51

def get_deferred_proc_remote(name, handle, base_of_dll)
  return name if !name.kind_of?String

  mod, meth = name.split "!"

  raise "can not set this breakpoint: #{name}" if mod.nil? or meth.nil?

  modh = handle

  # Location is an offset
  if is_hex(meth)
      baseaddr = 0
      modules.each { |m| break if m.szModule == mod }

      ret = base_of_dll + meth.hex
  else
      # Location is a symbolic name
      # Win32 should have successfully loaded the DLL
      ret = remote_call "kernel32!GetProcAddress", modh, meth
  end
  ret
end

#get_memory(i, opts = {}) ⇒ Object

Read an entire memory region into a string by region number.



319
320
321
322
# File 'lib/ragweed/wrap32/process.rb', line 319

def get_memory(i, opts={})
  refresh opts
  read(@memlist[i][0], @memlist[i][1])
end

#get_proc(name) ⇒ Object

look up a process by its name — but this is in the local process, which is broken — a heuristic that sometimes works for w32 functions, but probably never otherwise.



30
31
32
33
# File 'lib/ragweed/wrap32/process.rb', line 30

def get_proc(name)
  return Ragweed::Ptr.new(name) if name.kind_of? Numeric or name.kind_of? Ptr
  ptr(Ragweed::Wrap32::get_proc_address(name))
end

#get_proc_remote(name) ⇒ Object

This only gets called for breakpoints in modules that are already loaded



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/ragweed/wrap32/process.rb', line 76

def get_proc_remote(name)
  if !name.kind_of?String
      return name
  end

  mod, meth = name.split "!"

  if mod.nil? or meth.nil?
      raise "can not set this breakpoint: #{name}"
  end

  modh = remote_call "kernel32!GetModuleHandleA", mod
  raise "no such module #{ mod }" if not modh

  # Location is an offset
  if is_hex(meth)
      baseaddr = 0
      modules.each do |m|
          if m.szModule.to_s.match(/#{mod}/)
              baseaddr = m.modBaseAddr
              break
          end
      end

      # Somehow the module does not appear to be
      # loaded. This should have been caught by
      # Process::is_breakpoint_deferred either way
      # Process::initialize should catch this return
      if baseaddr == 0 or baseaddr == -1
          return name
      end

      ret = baseaddr + meth.hex
  else
      # Location is a symbolic name
      ret = remote_call "kernel32!GetProcAddress", modh, meth
  end
  ret
end

#handleObject



2
# File 'lib/ragweed/wrap32/process.rb', line 2

def handle; @h; end

#hunt(key, opts = {}) ⇒ Object

Given a string key, find it in memory. Very slow. Will read all memory regions, but you can provide “:index_range”, which must be a Range object, to constrain which ranges to search through. Returns a list of structs containing absolute memory locations, the index of the region, and some surrounding context for the hit.



416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
# File 'lib/ragweed/wrap32/process.rb', line 416

def hunt(key, opts={})
  ret = []
  refresh opts
  range = opts[:index_range] || (0..@memlist.size)
  @memlist.each_with_index do |t, i|
    if range.member? i
      if opts[:noisy]
        puts "#{ i }. #{ t[0].to_s(16) } -> #{ (t[0]+t[1]).to_s(16) }"
      end
      scan(i, opts) do |block, soff|
        if (needle = block.index(key))
          r = OpenStruct.new
          r.location = (t[0] + soff + needle)
          r.index = i
          r.context = block
          ret << r
          return ret if opts[:first]
        end
      end
    end
  end
  ret
end

#imageObject

Return the EXE name of the process.



161
162
163
164
165
166
167
168
169
# File 'lib/ragweed/wrap32/process.rb', line 161

def image
  buf = "\x00" * 256
  if Ragweed::Wrap32::nt_query_information_process(@h, 27, buf)
    buf = buf.from_utf16
    buf = buf[(buf.index("\\"))..-1]
    return buf.asciiz
  end
  nil
end

#insert(buf) ⇒ Object

Insert a string anywhere into the memory of the remote process, returning its address, using an arena.



284
# File 'lib/ragweed/wrap32/process.rb', line 284

def insert(buf); @a.copy(buf); end

#is_breakpoint_deferred(ip) ⇒ Object

Check if breakpoint location is deferred This method expects a string ‘module!function’ true is the module is not yet loaded false is the module is loaded



120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/ragweed/wrap32/process.rb', line 120

def is_breakpoint_deferred(ip)
  if !ip.kind_of? String
      return false
  end

  m,f = ip.split('!')

  if f.nil? or m.nil?
      return true
  end

  modules.each do |d|
      if d.szModule.to_s.match(/#{m}/)
          return false
      end
    end

  return true
end

#is_hex(s) ⇒ Object



35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/ragweed/wrap32/process.rb', line 35

def is_hex(s)
  s = s.strip

  # Strip leading 0s and 0x prefix
  while s[0..1] == '0x' or s[0..1] == '00'
    s = s[2..-1]
  end

  o = s

  s.hex.to_s(16) == o
end

#list_memory(&block) ⇒ Object

List all memory regions in the remote process by iterating over VirtualQueryEx. With a block, yields MEMORY_BASIC_INFORMATION structs. Without it, returns [baseaddr,size] tuples.

We “index” this list, so that we can refer to memory locations by “region number”, which is a Rubycorn-ism and not a Win32-ism. You’ll see lots of functions asking for memory indices, and this is what they’re referring to.



294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
# File 'lib/ragweed/wrap32/process.rb', line 294

def list_memory(&block)
  ret = []
  i = 0
  while (mbi = Ragweed::Wrap32::virtual_query_ex(@h, i))
    break if (not ret.empty? and mbi.BaseAddress == 0)
    if block_given?
      yield mbi
    else
      base = mbi.BaseAddress || 0
      size = mbi.RegionSize || 0
      ret << [base,size] if mbi.State & 0x1000 # MEM_COMMIT
      i = base + size
    end
  end
  ret
end

#modules(&block) ⇒ Object

List the modules for the process, either yielding a struct for each to a block, or returning a list.



196
197
198
199
200
201
202
203
204
# File 'lib/ragweed/wrap32/process.rb', line 196

def modules(&block)
  if block_given?
    Ragweed::Wrap32::list_modules(@pid, &block)
  else
    ret = []
    Ragweed::Wrap32::list_modules(@pid) {|x| ret << x}
    return ret
  end
end

#pointers_to(src, dst, opts = {}) ⇒ Object

Given a source and destination memory region, scan through “source” looking for properly-aligned U32LE values that would be valid pointers into “destination”. The “:range_start” and “:range_end” options constrain what a “valid pointer” into “destination” is.



374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
# File 'lib/ragweed/wrap32/process.rb', line 374

def pointers_to(src, dst, opts={})
  refresh opts
  ret = {}
  range = ((opts[:range_start] || @memlist[dst][0])..(opts[:range_stop] || @memlist[dst][0]+@memlist[dst][1]))
  scan(src, opts) do |block, soff|
    0.stepwith(block.size, 4) do |off, len|
      if len == 4
        if range.member? block[off,4].to_l32
          ret[soff + off] = block[off,4].to_l32
        end
      end
    end
  end
  return ret
end

#ptr(x) ⇒ Object

Get a pointer into the remote process; pointers are just fixnums with a read/write method and a to_s.



16
17
18
19
20
# File 'lib/ragweed/wrap32/process.rb', line 16

def ptr(x)
  ret = Ragweed::Ptr.new(x)
  ret.p = self
  return ret
end

#read(off, sz = 4096) ⇒ Object

Read/write ranges of data or fixnums to/from the process by address.



207
# File 'lib/ragweed/wrap32/process.rb', line 207

def read(off, sz=4096); Ragweed::Wrap32::read_process_memory(@h, off, sz); end

#read16(off) ⇒ Object



210
# File 'lib/ragweed/wrap32/process.rb', line 210

def read16(off); read(off, 2).unpack("v").first; end

#read32(off) ⇒ Object



209
# File 'lib/ragweed/wrap32/process.rb', line 209

def read32(off); read(off, 4).unpack("L").first; end

#read8(off) ⇒ Object



211
# File 'lib/ragweed/wrap32/process.rb', line 211

def read8(off); read(off, 1)[0]; end

#region_range(i, opts = {}) ⇒ Object

Get the memory range, as a Ruby Range, for a region by index



538
539
540
541
# File 'lib/ragweed/wrap32/process.rb', line 538

def region_range(i, opts={})
  refresh opts
  (@memlist[i][0]..(@memlist[i][1]+@memlist[i][0]))
end

#remote_call(meth, *args) ⇒ Object

call a function, by name or address, in the process, using CreateRemoteThread



218
219
220
221
222
223
224
225
# File 'lib/ragweed/wrap32/process.rb', line 218

def remote_call(meth, *args)
  loc = meth
  loc = get_proc(loc) if loc.kind_of? String
  loc = Ragweed::Ptr.new loc
  raise "bad proc name" if loc.null?
  t = Trampoline.new(self, loc)
  t.call *args
end

#resume(tid) ⇒ Object

Resume a thread by tid.



192
# File 'lib/ragweed/wrap32/process.rb', line 192

def resume(tid); Ragweed::Wrap32::open_thread(tid) {|x| Ragweed::Wrap32::resume_thread(x)}; end

#resume_allObject

Resume all the threads in the process. XXX this will not resume threads with suspend counts greater than 1.



185
# File 'lib/ragweed/wrap32/process.rb', line 185

def resume_all; threads.each {|x| resume(x)}; end

#scan(i, opts = {}) ⇒ Object

In Python, and maybe Ruby, it was much faster to work on large memory regions a 4k page at a time, rather than reading the whole thing into one big string. Scan takes a memory region and yields 4k chunks of it to a block, along with the length of each chunk.



332
333
334
335
336
337
338
339
340
341
342
343
# File 'lib/ragweed/wrap32/process.rb', line 332

def scan(i, opts={})
  refresh opts
  memt = @memlist[i]
  if memt[1] > 4096
    0.step(memt[1], 4096) do |i|
      block = (memt[1] - i).cap(4096)
      yield read(memt[0] + i, block), memt[0]+i
    end
  else
    yield read(memt[0], memt[1]), memt[0]
  end
end

#strings_mem(i, opts = {}) ⇒ Object

Given a memory region number, do a Unix strings(1) on it. Valid options: :unicode: you probably always want to set this to “true” :minimum: how small strings to accept.

Fairly slow.



396
397
398
399
400
401
402
403
404
405
406
407
408
# File 'lib/ragweed/wrap32/process.rb', line 396

def strings_mem(i, opts={})
  ret = []
  opts[:offset] ||= 0
  scan(i) do |block, soff|
    while 1
      off, size = block.nextstring(opts)
      break if not off
      opts[:offset] += (off + size)
      ret << [soff+off, size, block[off,size]]
    end
  end
  ret
end

#suspend(tid) ⇒ Object

Suspend a thread by tid. Technically, this doesn’t need to be a method; you can suspend a thread anywhere without a process handle.



189
# File 'lib/ragweed/wrap32/process.rb', line 189

def suspend(tid); Ragweed::Wrap32::open_thread(tid) {|x| Ragweed::Wrap32::suspend_thread(x)}; end

#suspend_allObject

Suspend all the threads in the process.



181
# File 'lib/ragweed/wrap32/process.rb', line 181

def suspend_all; threads.each {|x| suspend(x)}; end

#syscall_alloc(sz) ⇒ Object

Use VirtualAllocEx to grab a block of memory in the process. This is expensive, the equivalent of mmap()‘ing for each allocation.



232
# File 'lib/ragweed/wrap32/process.rb', line 232

def syscall_alloc(sz); ptr(Ragweed::Wrap32::virtual_alloc_ex(@h, sz)); end

#thread_context(tid) ⇒ Object

Dump thread context, returning a struct that contains things like .Eip and .Eax.



347
348
349
350
351
# File 'lib/ragweed/wrap32/process.rb', line 347

def thread_context(tid)
  Ragweed::Wrap32::open_thread(tid) do |h|
    Ragweed::Wrap32::get_thread_context(h)
  end
end

#thread_stack_trace(tid) ⇒ Object

For libraries compiled with frame pointers: walk EBP back until it stops giving intelligible addresses, and, at each step, grab the saved EIP from just before it.



562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
# File 'lib/ragweed/wrap32/process.rb', line 562

def thread_stack_trace(tid)
  with_suspended_thread(tid) do
    ctx = thread_context(tid)
    if((start = read32(ctx.Ebp)) != 0) 
      a = start
      stack = [[start, read32(start+4)]]
      while((a = read32(a)) and a != 0 and not stack.member?(a))
        begin
          stack << [a, read32(a+4)]
        rescue; break; end
      end
      return stack
    end
  end
  []
end

#threads(full = false, &block) ⇒ Object

Return a list of all the threads in the process; relatively expensive, so cache the result.



173
174
175
176
177
178
# File 'lib/ragweed/wrap32/process.rb', line 173

def threads(full=false, &block)
  return Ragweed::Wrap32::threads(@pid, &block) if block_given?
  ret = []
  Ragweed::Wrap32::threads(@pid) {|x| ((full) ? ret << x : ret << x.th32ThreadID) }
  return ret
end

#to_modoff(off, force = false) ⇒ Object

Convert an address to “module+10h” notation, when possible.



252
253
254
255
256
257
258
259
260
261
262
263
264
# File 'lib/ragweed/wrap32/process.rb', line 252

def to_modoff(off, force=false)
  if not @modules or force
    @modules = modules.sort {|x,y| x.modBaseAddr <=> y.modBaseAddr}
  end

  @modules.each do |m|
    if off >= m.modBaseAddr and off < (m.modBaseAddr + m.modBaseSize)
      return "#{ m.szModule }+#{ (off - m.modBaseAddr).to_s(16) }h"
    end
  end

  return "#{ off.to_x }h"
end

#which_region_has?(addr, opts = {}) ⇒ Boolean

Figure out what region (by region index) has an address

Returns:

  • (Boolean)


544
545
546
547
548
549
550
# File 'lib/ragweed/wrap32/process.rb', line 544

def which_region_has?(addr, opts={})
  refresh opts
  @memlist.each_with_index do |r, i|
    return i if (r[0]..r[0]+r[1]).member? addr
  end
  return nil
end

#with_suspended_thread(tid) ⇒ Object

Do something with a thread while its suspended



553
554
555
556
557
# File 'lib/ragweed/wrap32/process.rb', line 553

def with_suspended_thread(tid)
  ret = nil
  Ragweed::Wrap32::with_suspended_thread(tid) {|x| ret = yield}
  return ret
end

#write(off, data) ⇒ Object

ptrace sucks, writing 8 or 16 bytes will probably result in failure unless you PTRACE_POKE first and get the rest of the original value at the address



23
# File 'lib/ragweed/wraptux/process.rb', line 23

def write(off, data); Ragweed::Wrap32::write_process_memory(@h, off, data); end

#write16(off, v) ⇒ Object



213
# File 'lib/ragweed/wrap32/process.rb', line 213

def write16(off, v); write(off, [v].pack("v")); end

#write32(off, v) ⇒ Object



212
# File 'lib/ragweed/wrap32/process.rb', line 212

def write32(off, v); write(off, [v].pack("L")); end

#write8(off, v) ⇒ Object



214
# File 'lib/ragweed/wrap32/process.rb', line 214

def write8(off, v); write(off, v.chr); end

#writeable?(off) ⇒ Boolean

Can I write to this address in the process?

Returns:

  • (Boolean)


228
# File 'lib/ragweed/wrap32/process.rb', line 228

def writeable?(off); Ragweed::Wrap32::writeable? @h, off; end