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.



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

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.



150
151
152
153
154
155
156
157
158
159
# File 'lib/ragweed/wrap32/process.rb', line 150

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.



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

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.



480
481
482
483
484
485
486
487
488
489
490
491
492
493
# File 'lib/ragweed/wrap32/process.rb', line 480

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.



463
464
465
466
467
468
469
470
471
472
473
474
475
# File 'lib/ragweed/wrap32/process.rb', line 463

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.



245
246
247
248
249
250
251
# File 'lib/ragweed/wrap32/process.rb', line 245

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.



277
278
279
280
281
282
283
284
285
286
287
288
# File 'lib/ragweed/wrap32/process.rb', line 277

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.



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

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

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



596
597
598
599
600
601
602
# File 'lib/ragweed/wrap32/process.rb', line 596

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.



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

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.



321
322
323
324
# File 'lib/ragweed/wrap32/process.rb', line 321

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.



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

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.



365
366
367
368
369
370
371
372
373
374
375
376
# File 'lib/ragweed/wrap32/process.rb', line 365

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.



255
256
257
# File 'lib/ragweed/wrap32/process.rb', line 255

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
73
74
75
76
77
78
79
80
# File 'lib/ragweed/wrap32/process.rb', line 51

def get_deferred_proc_remote(name, handle, base_of_dll)
  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 = handle

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

      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.



327
328
329
330
# File 'lib/ragweed/wrap32/process.rb', line 327

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



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
115
116
117
118
119
120
121
122
# File 'lib/ragweed/wrap32/process.rb', line 84

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



424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
# File 'lib/ragweed/wrap32/process.rb', line 424

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.



169
170
171
172
173
174
175
176
177
# File 'lib/ragweed/wrap32/process.rb', line 169

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.



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

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



128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# File 'lib/ragweed/wrap32/process.rb', line 128

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.



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

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.



204
205
206
207
208
209
210
211
212
# File 'lib/ragweed/wrap32/process.rb', line 204

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.



382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
# File 'lib/ragweed/wrap32/process.rb', line 382

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.



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

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

#read16(off) ⇒ Object



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

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

#read32(off) ⇒ Object



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

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

#read8(off) ⇒ Object



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

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



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

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



226
227
228
229
230
231
232
233
# File 'lib/ragweed/wrap32/process.rb', line 226

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.



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

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.



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

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.



340
341
342
343
344
345
346
347
348
349
350
351
# File 'lib/ragweed/wrap32/process.rb', line 340

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.



404
405
406
407
408
409
410
411
412
413
414
415
416
# File 'lib/ragweed/wrap32/process.rb', line 404

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.



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

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

#suspend_allObject

Suspend all the threads in the process.



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

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.



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

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.



355
356
357
358
359
# File 'lib/ragweed/wrap32/process.rb', line 355

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.



570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
# File 'lib/ragweed/wrap32/process.rb', line 570

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.



181
182
183
184
185
186
# File 'lib/ragweed/wrap32/process.rb', line 181

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.



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

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)


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

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



561
562
563
564
565
# File 'lib/ragweed/wrap32/process.rb', line 561

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



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

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

#write32(off, v) ⇒ Object



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

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

#write8(off, v) ⇒ Object



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

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

#writeable?(off) ⇒ Boolean

Can I write to this address in the process?

Returns:

  • (Boolean)


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

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