ragweed 0.1.7.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. data/History.txt +32 -0
  2. data/README.rdoc +35 -0
  3. data/README.txt +9 -0
  4. data/Rakefile +34 -0
  5. data/examples/hittracertux.rb +49 -0
  6. data/examples/hittracerx.rb +63 -0
  7. data/examples/hook_notepad.rb +9 -0
  8. data/examples/snicker.rb +183 -0
  9. data/examples/tux-example.rb +23 -0
  10. data/lib/ragweed/arena.rb +55 -0
  11. data/lib/ragweed/blocks.rb +128 -0
  12. data/lib/ragweed/debugger32.rb +338 -0
  13. data/lib/ragweed/debuggerosx.rb +427 -0
  14. data/lib/ragweed/debuggertux.rb +346 -0
  15. data/lib/ragweed/detour.rb +223 -0
  16. data/lib/ragweed/ptr.rb +48 -0
  17. data/lib/ragweed/rasm/bblock.rb +73 -0
  18. data/lib/ragweed/rasm/isa.rb +1115 -0
  19. data/lib/ragweed/rasm.rb +59 -0
  20. data/lib/ragweed/sbuf.rb +197 -0
  21. data/lib/ragweed/trampoline.rb +103 -0
  22. data/lib/ragweed/utils.rb +156 -0
  23. data/lib/ragweed/wrap32/debugging.rb +163 -0
  24. data/lib/ragweed/wrap32/device.rb +49 -0
  25. data/lib/ragweed/wrap32/event.rb +50 -0
  26. data/lib/ragweed/wrap32/hooks.rb +23 -0
  27. data/lib/ragweed/wrap32/overlapped.rb +46 -0
  28. data/lib/ragweed/wrap32/process.rb +506 -0
  29. data/lib/ragweed/wrap32/process_token.rb +59 -0
  30. data/lib/ragweed/wrap32/thread_context.rb +208 -0
  31. data/lib/ragweed/wrap32/winx.rb +16 -0
  32. data/lib/ragweed/wrap32/wrap32.rb +526 -0
  33. data/lib/ragweed/wrap32.rb +59 -0
  34. data/lib/ragweed/wraposx/constants.rb +122 -0
  35. data/lib/ragweed/wraposx/kernelerrorx.rb +147 -0
  36. data/lib/ragweed/wraposx/region_info.rb +254 -0
  37. data/lib/ragweed/wraposx/thread_context.rb +203 -0
  38. data/lib/ragweed/wraposx/thread_info.rb +227 -0
  39. data/lib/ragweed/wraposx/wraposx.rb +433 -0
  40. data/lib/ragweed/wraposx.rb +59 -0
  41. data/lib/ragweed/wraptux/constants.rb +68 -0
  42. data/lib/ragweed/wraptux/threads.rb +7 -0
  43. data/lib/ragweed/wraptux/wraptux.rb +76 -0
  44. data/lib/ragweed/wraptux.rb +59 -0
  45. data/lib/ragweed.rb +84 -0
  46. data/ragweed.gemspec +34 -0
  47. data/spec/ragweed_spec.rb +7 -0
  48. data/spec/spec_helper.rb +16 -0
  49. data/tasks/ann.rake +80 -0
  50. data/tasks/bones.rake +20 -0
  51. data/tasks/gem.rake +201 -0
  52. data/tasks/git.rake +40 -0
  53. data/tasks/notes.rake +27 -0
  54. data/tasks/post_load.rake +34 -0
  55. data/tasks/rdoc.rake +51 -0
  56. data/tasks/rubyforge.rake +55 -0
  57. data/tasks/setup.rb +292 -0
  58. data/tasks/spec.rake +54 -0
  59. data/tasks/svn.rake +47 -0
  60. data/tasks/test.rake +40 -0
  61. data/tasks/zentest.rake +36 -0
  62. data/test/test_ragweed.rb +0 -0
  63. metadata +132 -0
@@ -0,0 +1,49 @@
1
+ module Ragweed
2
+ class Device
3
+ def initialize(path, options={})
4
+ @path = path
5
+ @options = options
6
+ @h = Ragweed::Wrap32::create_file(@path, :flags => Ragweed::Wrap32::FileAttributes::OVERLAPPED|Ragweed::Wrap32::FileAttributes::NORMAL)
7
+ end
8
+
9
+ def ioctl(code, inbuf, outbuf)
10
+ overlap(lambda do |o|
11
+ Ragweed::Wrap32::device_io_control(@h, code, inbuf, outbuf, o)
12
+ end) do |ret, count|
13
+ outbuf[0..count]
14
+ end
15
+ end
16
+
17
+ def read(sz)
18
+ overlap(lambda do |o|
19
+ Ragweed::Wrap32::read_file(@h, sz, o)
20
+ end) do |ret, count|
21
+ ret[0..count]
22
+ end
23
+ end
24
+
25
+ def write(buf)
26
+ overlap(lambda do |o|
27
+ Ragweed::Wrap32::write_file(@h, buf, o)
28
+ end) do |ret, count|
29
+ count
30
+ end
31
+ end
32
+
33
+ def release
34
+ Ragweed::Wrap32::close_handle(@h)
35
+ @h = nil
36
+ end
37
+
38
+ private
39
+
40
+ def overlap(proc)
41
+ o = Ragweed::Wrap32::Overlapped.get
42
+ ret = proc.call(o)
43
+ count = o.wait(@h)
44
+ r = yield ret, count
45
+ o.release
46
+ ret = r if r
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,50 @@
1
+ class Ragweed::Event
2
+ # Quick wrapper around Win32 events. Events are simple thread sync
3
+ # objects that are cross-process. They are like semaphores that you
4
+ # can select() on.
5
+
6
+ # You can just do WinEvent.new to get a new anonymous handle, and
7
+ # then call .handle on it to find out what the handle was. Communicate
8
+ # your pid and the handle value, somehow, to a remote process. That
9
+ # process can get the same event by passing a WinProcess and the
10
+ # handle here.
11
+ #
12
+ # So, in Process1 (assume pid 668, and handle 300):
13
+ #
14
+ # e = WinEvent.new
15
+ # puts #{ get_current_process_id }: #{ e.handle }"
16
+ #
17
+ # And in Process2:
18
+ #
19
+ # e = WinEvent.new(WinProcess.new(668), 300)
20
+ #
21
+ # Now both processes share an event.
22
+ def initialize(p=nil, h=nil)
23
+ @p = p
24
+ @h = (@p.dup_handle(h) if h) || create_event
25
+ end
26
+
27
+ # Don't return until the event is signalled. Note that you
28
+ # can't break this with timeouts or CTR-C.
29
+ def wait
30
+ Ragweed::Wrap32::wait_for_single_object @h
31
+ end
32
+
33
+ # Signal the event; anyone waiting on it is now released.
34
+ def signal
35
+ Ragweed::Wrap32::set_event(@h)
36
+ end
37
+
38
+ # Force the event back to unsignalled state.
39
+ def reset
40
+ Ragweed::Wrap32::reset_event(@h)
41
+ end
42
+
43
+ # A wait loop.
44
+ def on(&block)
45
+ while 1
46
+ wait
47
+ break if not yield
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,23 @@
1
+ class Ragweed::Debugger32
2
+ # Hook function calls
3
+ # nargs is the number of arguments taken by function at ip
4
+ # callable/block is called with ev, ctx, dir (:enter or :leave), and args Array (see examples/hook_notepad.rb)
5
+ # default handler prints arguments
6
+ def hook(ip, nargs, callable=nil, &block)
7
+ callable ||= block || lambda do |ev, ctx,dir,args|
8
+ puts "#{dir} #{ip.to_s(16) rescue ip.to_s}"
9
+ puts args.map{|a| "%08x" % a}.join(',')
10
+ end
11
+
12
+ breakpoint_set(ip) do |ev,ctx|
13
+ args = (1..nargs).map {|i| process.read32(ctx.esp + 4*i)}
14
+ retp = process.read32(ctx.esp)
15
+ # set exit bpoint
16
+ breakpoint_set(retp) do |ev,ctx|
17
+ callable.call(ev, ctx, :leave, args)
18
+ breakpoint_clear(retp)
19
+ end.install
20
+ callable.call(ev, ctx, :enter, args)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,46 @@
1
+ class Ragweed::Wrap32::Overlapped
2
+ attr_accessor :internal
3
+ attr_accessor :internal_high
4
+ attr_accessor :offset
5
+ attr_accessor :offset_high
6
+ attr_accessor :event
7
+ attr_accessor :target
8
+
9
+ def self.get
10
+ h = Ragweed::Wrap32::create_event(nil, false, true)
11
+ r = self.new
12
+ r.event = h
13
+ return r
14
+ end
15
+
16
+ def initialize(str=nil)
17
+ @buf = "\x00" * 20
18
+ @internal, @internal_high, @offset, @offset_high, @event = [0,0,0,0,0]
19
+ init(str) if str
20
+ end
21
+
22
+ def to_s
23
+ buf = [@internal, @internal_high, @offset, @offset_high, @event].pack("LLLLL")
24
+ @buf.replace(buf)
25
+ end
26
+
27
+ def release
28
+ Ragweed::Wrap32::close_handle(@event)
29
+ end
30
+
31
+ def wait(h)
32
+ return if not @event
33
+ Ragweed::Wrap32::wait_for_single_object(@event)
34
+ Ragweed::Wrap32::get_overlapped_result(h, self)
35
+ end
36
+
37
+ private
38
+
39
+ def init(str)
40
+ @internal,
41
+ @internal_high,
42
+ @offset,
43
+ @offset_high,
44
+ @event = str.unpack("LLLLL")
45
+ end
46
+ end
@@ -0,0 +1,506 @@
1
+ # TODO - PORT ME!!
2
+
3
+ class Ragweed::Process
4
+ def handle; @h; end
5
+ attr_reader :pid
6
+ include Ragweed
7
+
8
+ def self.find_by_regex(name)
9
+ Ragweed::Wrap32::all_processes do |p|
10
+ if p.szExeFile =~ name
11
+ return self.new(p.th32ProcessID)
12
+ end
13
+ end
14
+ end
15
+
16
+ # Get a pointer into the remote process; pointers are just fixnums
17
+ # with a read/write method and a to_s.
18
+ def ptr(x)
19
+ ret = Ptr.new(x)
20
+ ret.p = self
21
+ return ret
22
+ end
23
+
24
+ # clone a handle from the remote process to here (to here? tf?)
25
+ def dup_handle(h)
26
+ Ragweed::Wrap32::duplicate_handle(@h, h)
27
+ end
28
+
29
+ # look up a process by its name --- but this is in the local process,
30
+ # which is broken --- a heuristic that sometimes works for w32 functions,
31
+ # but probably never otherwise.
32
+ def get_proc(name)
33
+ return Ptr.new(name) if name.kind_of? Numeric or name.kind_of? Ptr
34
+ ptr(Ragweed::Wrap32::get_proc_address(name))
35
+ end
36
+
37
+ def get_proc_remote(name)
38
+ mod, meth = name.split "!"
39
+ modh = remote_call "kernel32!GetModuleHandleW", mod.to_utf16
40
+ raise "no such module #{ mod }" if not modh
41
+ ret = remote_call "kernel32!GetProcAddress", modh, meth
42
+ ret
43
+ end
44
+
45
+ # Look up a process by name or regex, returning an array of all
46
+ # matching processes, as objects.
47
+ def self.by_name(n)
48
+ n = Regexp.new(n) if not n.kind_of? Regexp
49
+ p = []
50
+ all_processes do |px|
51
+ if px.szExeFile =~ n
52
+ p << self.new(px.th32ProcessID)
53
+ end
54
+ end
55
+ p
56
+ end
57
+
58
+ # Just need a PID to get started.
59
+ def initialize(pid)
60
+ @pid = pid
61
+ @h = Ragweed::Wrap32::open_process(pid)
62
+ @a = arena()
63
+ end
64
+
65
+ # Return the EXE name of the process.
66
+ def image
67
+ buf = "\x00" * 256
68
+ if Ragweed::Wrap32::nt_query_information_process(@h, 27, buf)
69
+ buf = buf.from_utf16
70
+ buf = buf[(buf.index("\\"))..-1]
71
+ return buf.asciiz
72
+ end
73
+ nil
74
+ end
75
+
76
+ # Return a list of all the threads in the process; relatively
77
+ # expensive, so cache the result.
78
+ def threads(full=false, &block)
79
+ return Ragweed::Wrap32::threads(@pid, &block) if block_given?
80
+ ret = []
81
+ Ragweed::Wrap32::threads(@pid) {|x| ((full) ? ret << x : ret << x.th32ThreadID) }
82
+ return ret
83
+ end
84
+
85
+ # Suspend all the threads in the process.
86
+ def suspend_all; threads.each {|x| suspend(x)}; end
87
+
88
+ # Resume all the threads in the process. XXX this will not
89
+ # resume threads with suspend counts greater than 1.
90
+ def resume_all; threads.each {|x| resume(x)}; end
91
+
92
+ # Suspend a thread by tid. Technically, this doesn't need to be
93
+ # a method; you can suspend a thread anywhere without a process handle.
94
+ def suspend(tid); Ragweed::Wrap32::open_thread(tid) {|x| Ragweed::Wrap32::suspend_thread(x)}; end
95
+
96
+ # Resume a thread by tid.
97
+ def resume(tid); Ragweed::Wrap32::open_thread(tid) {|x| Ragweed::Wrap32::resume_thread(x)}; end
98
+
99
+ # List the modules for the process, either yielding a struct for
100
+ # each to a block, or returning a list.
101
+ def modules(&block)
102
+ if block_given?
103
+ Ragweed::Wrap32::list_modules(@pid, &block)
104
+ else
105
+ ret = []
106
+ Ragweed::Wrap32::list_modules(@pid) {|x| ret << x}
107
+ return ret
108
+ end
109
+ end
110
+
111
+ # Read/write ranges of data or fixnums to/from the process by address.
112
+ def read(off, sz=4096); Ragweed::Wrap32::read_process_memory(@h, off, sz); end
113
+ def write(off, data); Ragweed::Wrap32::write_process_memory(@h, off, data); end
114
+ def read32(off); read(off, 4).unpack("L").first; end
115
+ def read16(off); read(off, 2).unpack("v").first; end
116
+ def read8(off); read(off, 1)[0]; end
117
+ def write32(off, v); write(off, [v].pack("L")); end
118
+ def write16(off, v); write(off, [v].pack("v")); end
119
+ def write8(off, v); write(off, v.chr); end
120
+
121
+ # call a function, by name or address, in the process, using
122
+ # CreateRemoteThread
123
+ def remote_call(meth, *args)
124
+ loc = meth
125
+ loc = get_proc(loc) if loc.kind_of? String
126
+ loc = Ptr.new loc
127
+ raise "bad proc name" if loc.null?
128
+ t = Trampoline.new(self, loc)
129
+ t.call *args
130
+ end
131
+
132
+ # Can I write to this address in the process?
133
+ def writeable?(off); Ragweed::Wrap32::writeable? @h, off; end
134
+
135
+ # Use VirtualAllocEx to grab a block of memory in the process. This
136
+ # is expensive, the equivalent of mmap()'ing for each allocation.
137
+ def syscall_alloc(sz); ptr(Ragweed::Wrap32::virtual_alloc_ex(@h, sz)); end
138
+
139
+ # Use arenas, when possible, to quickly allocate memory. The upside
140
+ # is this is very fast. The downside is you can't free the memory
141
+ # without invalidating every allocation you've made prior.
142
+ def alloc(sz, syscall=false)
143
+ if syscall or sz > 4090
144
+ ret = syscall_alloc(sz)
145
+ else
146
+ ptr(@a.alloc(sz))
147
+ end
148
+ end
149
+
150
+ # Free the return value of syscall_alloc. Do NOT use for the return
151
+ # value of alloc.
152
+ def free(off)
153
+ Ragweed::Wrap32::virtual_free_ex(@h, off)
154
+ end
155
+
156
+ # Convert an address to "module+10h" notation, when possible.
157
+ def to_modoff(off, force=false)
158
+ if not @modules or force
159
+ @modules = modules.sort {|x,y| x.modBaseAddr <=> y.modBaseAddr}
160
+ end
161
+
162
+ @modules.each do |m|
163
+ if off >= m.modBaseAddr and off < (m.modBaseAddr + m.modBaseSize)
164
+ return "#{ m.szModule }+#{ (off - m.modBaseAddr).to_s(16) }h"
165
+ end
166
+ end
167
+
168
+ return "#{ off.to_x }h"
169
+ end
170
+
171
+ # Get another allocation arena for this process. Pretty cheap. Given
172
+ # a block, behaves like File#open, disposing of the arena when you're
173
+ # done.
174
+ def arena(&block)
175
+ me = self
176
+ a = Arena.new(lambda {me.syscall_alloc(4096)},
177
+ lambda {|p| me.free(p)},
178
+ lambda {|dst, src| me.write(dst, src)})
179
+ if block_given?
180
+ ret = yield a
181
+ a.release
182
+ return ret
183
+ end
184
+ a
185
+ end
186
+
187
+ # Insert a string anywhere into the memory of the remote process,
188
+ # returning its address, using an arena.
189
+ def insert(buf); @a.copy(buf); end
190
+
191
+ # List all memory regions in the remote process by iterating over
192
+ # VirtualQueryEx. With a block, yields MEMORY_BASIC_INFORMATION
193
+ # structs. Without it, returns [baseaddr,size] tuples.
194
+ #
195
+ # We "index" this list, so that we can refer to memory locations
196
+ # by "region number", which is a Rubycorn-ism and not a Win32-ism.
197
+ # You'll see lots of functions asking for memory indices, and this
198
+ # is what they're referring to.
199
+ def list_memory(&block)
200
+ ret = []
201
+ i = 0
202
+ while (mbi = Ragweed::Wrap32::virtual_query_ex(@h, i))
203
+ break if (not ret.empty? and mbi.BaseAddress == 0)
204
+ if block_given?
205
+ yield mbi
206
+ else
207
+ base = mbi.BaseAddress || 0
208
+ size = mbi.RegionSize || 0
209
+ ret << [base,size] if mbi.State & 0x1000 # MEM_COMMIT
210
+ i = base + size
211
+ end
212
+ end
213
+ ret
214
+ end
215
+
216
+ # Human-readable standard output of list_memory. Remember that the
217
+ # index number is important.
218
+ def dump_memory_list
219
+ list_memory.each_with_index {|x,i| puts "#{ i }. #{ x[0].to_s(16) }(#{ x[1] })"}
220
+ true
221
+ end
222
+
223
+ # Read an entire memory region into a string by region number.
224
+ def get_memory(i, opts={})
225
+ refresh opts
226
+ read(@memlist[i][0], @memlist[i][1])
227
+ end
228
+
229
+ # Print a canonical hexdump of an entire memory region by region number.
230
+ def dump_memory(i, opts={}); get_memory(i, opts).hexdump; end
231
+
232
+ # In Python, and maybe Ruby, it was much faster to work on large
233
+ # memory regions a 4k page at a time, rather than reading the
234
+ # whole thing into one big string. Scan takes a memory region
235
+ # and yields 4k chunks of it to a block, along with the length
236
+ # of each chunk.
237
+ def scan(i, opts={})
238
+ refresh opts
239
+ memt = @memlist[i]
240
+ if memt[1] > 4096
241
+ 0.step(memt[1], 4096) do |i|
242
+ block = (memt[1] - i).cap(4096)
243
+ yield read(memt[0] + i, block), memt[0]+i
244
+ end
245
+ else
246
+ yield read(memt[0], memt[1]), memt[0]
247
+ end
248
+ end
249
+
250
+ # Dump thread context, returning a struct that contains things like
251
+ # .Eip and .Eax.
252
+ def thread_context(tid)
253
+ Ragweed::Wrap32::open_thread(tid) do |h|
254
+ Ragweed::Wrap32::get_thread_context(h)
255
+ end
256
+ end
257
+
258
+ # Take a region of memory and walk over it in 255-byte samples (less
259
+ # than 255 bytes and you lose accuracy, but you can increase it with
260
+ # the ":window" option), computing entropy for each sample, returning
261
+ # a list of [offset,entropy] tuples.
262
+ def entropy_map(i, opts={})
263
+ ret = []
264
+ startoff = opts[:starting_offset]
265
+ startoff ||= 0
266
+ window = opts[:window] || 255
267
+ scan(i, opts) do |block, soff|
268
+ startoff.stepwith(block.size, window) do |off, len|
269
+ ret << [off, block[off,len].entropy]
270
+ end
271
+ end
272
+ return ret
273
+ end
274
+
275
+ # Given a source and destination memory region, scan through "source"
276
+ # looking for properly-aligned U32LE values that would be valid pointers
277
+ # into "destination". The ":range_start" and ":range_end" options
278
+ # constrain what a "valid pointer" into "destination" is.
279
+ def pointers_to(src, dst, opts={})
280
+ refresh opts
281
+ ret = {}
282
+ range = ((opts[:range_start] || @memlist[dst][0])..(opts[:range_stop] || @memlist[dst][0]+@memlist[dst][1]))
283
+ scan(src, opts) do |block, soff|
284
+ 0.stepwith(block.size, 4) do |off, len|
285
+ if len == 4
286
+ if range.member? block[off,4].to_l32
287
+ ret[soff + off] = block[off,4].to_l32
288
+ end
289
+ end
290
+ end
291
+ end
292
+ return ret
293
+ end
294
+
295
+ # Given a memory region number, do a Unix strings(1) on it. Valid
296
+ # options:
297
+ # :unicode: you probably always want to set this to "true"
298
+ # :minimum: how small strings to accept.
299
+ #
300
+ # Fairly slow.
301
+ def strings_mem(i, opts={})
302
+ ret = []
303
+ opts[:offset] ||= 0
304
+ scan(i) do |block, soff|
305
+ while 1
306
+ off, size = block.nextstring(opts)
307
+ break if not off
308
+ opts[:offset] += (off + size)
309
+ ret << [soff+off, size, block[off,size]]
310
+ end
311
+ end
312
+ ret
313
+ end
314
+
315
+ # Given a string key, find it in memory. Very slow. Will read all
316
+ # memory regions, but you can provide ":index_range", which must be
317
+ # a Range object, to constrain which ranges to search through.
318
+ # Returns a list of structs containing absolute memory locations,
319
+ # the index of the region, and some surrounding context for the
320
+ # hit.
321
+ def hunt(key, opts={})
322
+ ret = []
323
+ refresh opts
324
+ range = opts[:index_range] || (0..@memlist.size)
325
+ @memlist.each_with_index do |t, i|
326
+ if range.member? i
327
+ if opts[:noisy]
328
+ puts "#{ i }. #{ t[0].to_s(16) } -> #{ (t[0]+t[1]).to_s(16) }"
329
+ end
330
+ scan(i, opts) do |block, soff|
331
+ if (needle = block.index(key))
332
+ r = OpenStruct.new
333
+ r.location = (t[0] + soff + needle)
334
+ r.index = i
335
+ r.context = block
336
+ ret << r
337
+ return ret if opts[:first]
338
+ end
339
+ end
340
+ end
341
+ end
342
+ ret
343
+ end
344
+
345
+ private
346
+
347
+ def windowize(i, opts={})
348
+ window = opts[:window] || 1024
349
+ if window == :auto
350
+ r = region_range(i)
351
+ window = (r.last - r.first) / 60
352
+ end
353
+ return window
354
+ end
355
+
356
+ public
357
+
358
+ # Like entropy_map, scan a process and compute adler16 checksums for
359
+ # 1k (or :window) blocks.
360
+ def adler_map(i, opts={})
361
+ refresh opts
362
+ window = windowize(i, opts)
363
+ ret = []
364
+ scan(i, opts) do |block,soff|
365
+ 0.stepwith(block.size-1, window) do |off, len|
366
+ if (b = block[off,len])
367
+ ret << b.adler
368
+ end
369
+ end
370
+ end
371
+ ret
372
+ end
373
+
374
+ # If you store the adler map, you've compressed the memory region
375
+ # down to a small series of fixnums, and you can use it with this
376
+ # function to re-check the memory region and see if anything's changing.
377
+ def adler_compare(i, orig, opts={})
378
+ refresh opts
379
+ window = windowize(i, opts)
380
+ ret = []
381
+ c = -1
382
+ scan(i, opts) do |block,soff|
383
+ 0.stepwith(block.size-1, window) do |off, len|
384
+ if block[off,len].adler != orig[c += 1]
385
+ ret << soff+off
386
+ end
387
+ end
388
+ end
389
+ ret
390
+ end
391
+
392
+ # Quick and dirty visualization of a memory region by checksum
393
+ # changes.
394
+ class AdlerChart
395
+
396
+ # Create with a WinProcess and region index
397
+ def initialize(p, i)
398
+ @p = p
399
+ @i = i
400
+ @initstate = map
401
+ end
402
+
403
+ private
404
+ def map; @p.adler_map(@i, :window => :auto); end
405
+
406
+ public
407
+
408
+ # Just puts the chart repeatedly, to get:
409
+ # ........................................................*.*..
410
+ # ..........................................................*..
411
+ # ..........................................................*..
412
+ # Where * represents a chunk of memory that has changed.
413
+ def to_s
414
+ s = StringIO.new
415
+ newstate = map
416
+ @initstate.each_with_index do |sum, i|
417
+ if sum != newstate[i]
418
+ s.write "*"
419
+ @initstate[i] = newstate[i]
420
+ else
421
+ s.write "."
422
+ end
423
+ end
424
+ s.rewind;s.read()
425
+ end
426
+ end
427
+
428
+ # See WinProcess::AdlerChart. Get one.
429
+ def adler_chart(i)
430
+ AdlerChart.new self, i
431
+ end
432
+
433
+ # Given a memory region, use the adler routines to create a checksum
434
+ # map, wait a short period of time, and scan for changes, to find
435
+ # churning memory.
436
+ def changes(i, sleeptime=0.5)
437
+ q = adler_map(i)
438
+ sleep(sleeptime)
439
+ adler_compare(i, q)
440
+ end
441
+
442
+ # Get the memory range, as a Ruby Range, for a region by index
443
+ def region_range(i, opts={})
444
+ refresh opts
445
+ (@memlist[i][0]..(@memlist[i][1]+@memlist[i][0]))
446
+ end
447
+
448
+ # Figure out what region (by region index) has an address
449
+ def which_region_has?(addr, opts={})
450
+ refresh opts
451
+ @memlist.each_with_index do |r, i|
452
+ return i if (r[0]..r[0]+r[1]).member? addr
453
+ end
454
+ return nil
455
+ end
456
+
457
+ # Do something with a thread while its suspended
458
+ def with_suspended_thread(tid)
459
+ ret = nil
460
+ Ragweed::Wrap32::with_suspended_thread(tid) {|x| ret = yield}
461
+ return ret
462
+ end
463
+
464
+ # For libraries compiled with frame pointers: walk EBP back
465
+ # until it stops giving intelligible addresses, and, at each
466
+ # step, grab the saved EIP from just before it.
467
+ def thread_stack_trace(tid)
468
+ with_suspended_thread(tid) do
469
+ ctx = thread_context(tid)
470
+ if((start = read32(ctx.Ebp)) != 0)
471
+ a = start
472
+ stack = [[start, read32(start+4)]]
473
+ while((a = read32(a)) and a != 0 and not stack.member?(a))
474
+ begin
475
+ stack << [a, read32(a+4)]
476
+ rescue; break; end
477
+ end
478
+ return stack
479
+ end
480
+ end
481
+ []
482
+ end
483
+
484
+ # Human-readable version of thread_stack_trace, with module
485
+ # offsets.
486
+ def dump_stack_trace(tid)
487
+ thread_stack_trace(tid).each do |frame, code|
488
+ puts "#{ frame.to_x } @ #{ to_modoff(code) }"
489
+ end
490
+ end
491
+ alias_method :bt, :dump_stack_trace
492
+
493
+ def detour(loc, o={})
494
+ klass = o[:class] || Detour
495
+ loc = get_proc(loc)
496
+ r = klass.new(loc, o)
497
+ r.call if not o[:chicken]
498
+ return r
499
+ end
500
+
501
+ private
502
+
503
+ def refresh(opts={})
504
+ @memlist = list_memory if not @memlist or opts.delete(:refresh)
505
+ end
506
+ end