Class: Ragweed::Process

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

Overview

XXX - PORT ME!!

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.



59
60
61
62
63
# File 'lib/ragweed/wrap32/process.rb', line 59

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

Instance Attribute Details

#pidObject (readonly)

Returns the value of attribute pid.



5
6
7
# File 'lib/ragweed/wrap32/process.rb', line 5

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.



47
48
49
50
51
52
53
54
55
56
# File 'lib/ragweed/wrap32/process.rb', line 47

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



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

def self.find_by_regex(name)
  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.



429
430
431
# File 'lib/ragweed/wrap32/process.rb', line 429

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.



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

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.



360
361
362
363
364
365
366
367
368
369
370
371
372
# File 'lib/ragweed/wrap32/process.rb', line 360

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.



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

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.



174
175
176
177
178
179
180
181
182
183
184
185
# File 'lib/ragweed/wrap32/process.rb', line 174

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.



436
437
438
439
440
# File 'lib/ragweed/wrap32/process.rb', line 436

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

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



493
494
495
496
497
498
499
# File 'lib/ragweed/wrap32/process.rb', line 493

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.



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

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.



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

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.



486
487
488
489
490
# File 'lib/ragweed/wrap32/process.rb', line 486

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?)



25
26
27
# File 'lib/ragweed/wrap32/process.rb', line 25

def dup_handle(h)
  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.



262
263
264
265
266
267
268
269
270
271
272
273
# File 'lib/ragweed/wrap32/process.rb', line 262

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.



152
153
154
# File 'lib/ragweed/wrap32/process.rb', line 152

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

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

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



224
225
226
227
# File 'lib/ragweed/wrap32/process.rb', line 224

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.



32
33
34
35
# File 'lib/ragweed/wrap32/process.rb', line 32

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

#get_proc_remote(name) ⇒ Object



37
38
39
40
41
42
43
# File 'lib/ragweed/wrap32/process.rb', line 37

def get_proc_remote(name)
  mod, meth = name.split "!"
  modh = remote_call "kernel32!GetModuleHandleW", mod.to_utf16
  raise "no such module #{ mod }" if not modh
  ret = remote_call "kernel32!GetProcAddress", modh, meth
  ret
end

#handleObject



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

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.



321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
# File 'lib/ragweed/wrap32/process.rb', line 321

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.



66
67
68
69
70
71
72
73
74
# File 'lib/ragweed/wrap32/process.rb', line 66

def image
  buf = "\x00" * 256
  if 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.



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

def insert(buf); @a.copy(buf); 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.



199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
# File 'lib/ragweed/wrap32/process.rb', line 199

def list_memory(&block)
  ret = []
  i = 0
  while (mbi = 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.



101
102
103
104
105
106
107
108
109
# File 'lib/ragweed/wrap32/process.rb', line 101

def modules(&block)
  if block_given?
    Wrap32::list_modules(@pid, &block)
  else
    ret = []
    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.



279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
# File 'lib/ragweed/wrap32/process.rb', line 279

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.



18
19
20
21
22
# File 'lib/ragweed/wrap32/process.rb', line 18

def ptr(x)
  ret = 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.



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

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

#read16(off) ⇒ Object



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

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

#read32(off) ⇒ Object



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

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

#read8(off) ⇒ Object



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

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



443
444
445
446
# File 'lib/ragweed/wrap32/process.rb', line 443

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



123
124
125
126
127
128
129
130
# File 'lib/ragweed/wrap32/process.rb', line 123

def remote_call(meth, *args)
  loc = meth
  loc = get_proc(loc) if loc.kind_of? String
  loc = 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.



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

def resume(tid); Wrap32::open_thread(tid) {|x| 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.



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

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.



237
238
239
240
241
242
243
244
245
246
247
248
# File 'lib/ragweed/wrap32/process.rb', line 237

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.



301
302
303
304
305
306
307
308
309
310
311
312
313
# File 'lib/ragweed/wrap32/process.rb', line 301

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.



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

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

#suspend_allObject

Suspend all the threads in the process.



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

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.



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

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

#thread_context(tid) ⇒ Object

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



252
253
254
255
256
# File 'lib/ragweed/wrap32/process.rb', line 252

def thread_context(tid)
  Wrap32::open_thread(tid) do |h|
    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.



467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
# File 'lib/ragweed/wrap32/process.rb', line 467

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.



78
79
80
81
82
83
# File 'lib/ragweed/wrap32/process.rb', line 78

def threads(full=false, &block)
  return Wrap32::threads(@pid, &block) if block_given?
  ret = []
  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.



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

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)


449
450
451
452
453
454
455
# File 'lib/ragweed/wrap32/process.rb', line 449

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



458
459
460
461
462
# File 'lib/ragweed/wrap32/process.rb', line 458

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

#write(off, data) ⇒ Object



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

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

#write16(off, v) ⇒ Object



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

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

#write32(off, v) ⇒ Object



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

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

#write8(off, v) ⇒ Object



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

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

#writeable?(off) ⇒ Boolean

Can I write to this address in the process?

Returns:

  • (Boolean)


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

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