ragweed 0.2.0-java

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. data/History.txt +32 -0
  2. data/README.rdoc +60 -0
  3. data/README.txt +9 -0
  4. data/Rakefile +86 -0
  5. data/VERSION +1 -0
  6. data/examples/hittracertux.rb +45 -0
  7. data/examples/hittracerx.rb +63 -0
  8. data/examples/hook_notepad.rb +9 -0
  9. data/examples/snicker.rb +183 -0
  10. data/examples/tux-example.rb +24 -0
  11. data/lib/ragweed/arena.rb +55 -0
  12. data/lib/ragweed/blocks.rb +128 -0
  13. data/lib/ragweed/debugger32.rb +400 -0
  14. data/lib/ragweed/debuggerosx.rb +456 -0
  15. data/lib/ragweed/debuggertux.rb +502 -0
  16. data/lib/ragweed/detour.rb +223 -0
  17. data/lib/ragweed/ptr.rb +48 -0
  18. data/lib/ragweed/rasm/bblock.rb +73 -0
  19. data/lib/ragweed/rasm/isa.rb +1115 -0
  20. data/lib/ragweed/rasm.rb +59 -0
  21. data/lib/ragweed/sbuf.rb +197 -0
  22. data/lib/ragweed/trampoline.rb +103 -0
  23. data/lib/ragweed/utils.rb +182 -0
  24. data/lib/ragweed/wrap32/debugging.rb +401 -0
  25. data/lib/ragweed/wrap32/device.rb +49 -0
  26. data/lib/ragweed/wrap32/event.rb +50 -0
  27. data/lib/ragweed/wrap32/hooks.rb +39 -0
  28. data/lib/ragweed/wrap32/overlapped.rb +46 -0
  29. data/lib/ragweed/wrap32/process.rb +613 -0
  30. data/lib/ragweed/wrap32/process_token.rb +75 -0
  31. data/lib/ragweed/wrap32/thread_context.rb +142 -0
  32. data/lib/ragweed/wrap32/winx.rb +16 -0
  33. data/lib/ragweed/wrap32/wrap32.rb +583 -0
  34. data/lib/ragweed/wrap32.rb +59 -0
  35. data/lib/ragweed/wraposx/constants.rb +114 -0
  36. data/lib/ragweed/wraposx/kernelerrorx.rb +147 -0
  37. data/lib/ragweed/wraposx/region_info.rb +275 -0
  38. data/lib/ragweed/wraposx/structs.rb +102 -0
  39. data/lib/ragweed/wraposx/thread_context.rb +902 -0
  40. data/lib/ragweed/wraposx/thread_info.rb +160 -0
  41. data/lib/ragweed/wraposx/thread_info.rb.old +121 -0
  42. data/lib/ragweed/wraposx/wraposx.rb +356 -0
  43. data/lib/ragweed/wraposx.rb +60 -0
  44. data/lib/ragweed/wraptux/constants.rb +101 -0
  45. data/lib/ragweed/wraptux/process.rb +35 -0
  46. data/lib/ragweed/wraptux/threads.rb +7 -0
  47. data/lib/ragweed/wraptux/wraptux.rb +72 -0
  48. data/lib/ragweed/wraptux.rb +57 -0
  49. data/lib/ragweed.rb +112 -0
  50. data/ragweed.gemspec +102 -0
  51. data/spec/ragweed_spec.rb +7 -0
  52. data/spec/spec_helper.rb +16 -0
  53. data/test/test_ragweed.rb +0 -0
  54. metadata +121 -0
@@ -0,0 +1,502 @@
1
+ require ::File.join(::File.dirname(__FILE__),'wraptux')
2
+
3
+ module Ragweed; end
4
+
5
+ ## Debugger class for Linux
6
+ ## You can use this class in 2 ways:
7
+ ##
8
+ ## (1) You can create instances of Debuggertux and use them to set and handle
9
+ ## breakpoints.
10
+ ##
11
+ ## (2) If you want to do more advanced event handling, you can subclass from
12
+ ## debugger and define your own on_whatever events. If you handle an event
13
+ ## that Debuggertux already handles, call "super", too.
14
+ class Ragweed::Debuggertux
15
+ attr_reader :pid, :status, :exited, :signal
16
+ attr_accessor :breakpoints, :mapped_regions, :process
17
+
18
+ ## Class to handle installing/uninstalling breakpoints
19
+ class Breakpoint
20
+
21
+ INT3 = 0xCC
22
+
23
+ attr_accessor :orig, :bppid, :function, :installed
24
+ attr_reader :addr
25
+
26
+ ## ip: insertion point
27
+ ## callable: lambda to be called when breakpoint is hit
28
+ ## p: process ID
29
+ ## name: name of breakpoint
30
+ def initialize(ip, callable, p, name = "")
31
+ @bppid = p
32
+ @function = name
33
+ @addr = ip
34
+ @callable = callable
35
+ @installed = false
36
+ @exited = false
37
+ @orig = 0
38
+ end
39
+
40
+ def install
41
+ @orig = Ragweed::Wraptux::ptrace(Ragweed::Wraptux::Ptrace::PEEK_TEXT, @bppid, @addr, 0)
42
+ if @orig != -1
43
+ n = (@orig & ~0xff) | INT3;
44
+ Ragweed::Wraptux::ptrace(Ragweed::Wraptux::Ptrace::POKE_TEXT, @bppid, @addr, n)
45
+ @installed = true
46
+ else
47
+ @installed = false
48
+ end
49
+ end
50
+
51
+ def uninstall
52
+ if @orig != INT3
53
+ a = Ragweed::Wraptux::ptrace(Ragweed::Wraptux::Ptrace::POKE_TEXT, @bppid, @addr, @orig)
54
+ @installed = false
55
+ end
56
+ end
57
+
58
+ def installed?; @installed; end
59
+ def call(*args); @callable.call(*args) if @callable != nil; end
60
+ end
61
+
62
+ ## init object
63
+ ## p: pid of process to be debugged
64
+ ## opts: default options for automatically doing things (attach and install)
65
+ def initialize(pid, opts) ## Debuggertux Class
66
+ if p.to_i.kind_of? Fixnum
67
+ @pid = pid.to_i
68
+ else
69
+ raise "Provide a PID"
70
+ end
71
+
72
+ @opts = opts
73
+
74
+ default_opts(opts)
75
+ @installed = false
76
+ @attached = false
77
+
78
+ @mapped_regions = Hash.new
79
+ @breakpoints = Hash.new
80
+ @opts.each { |k, v| try(k) if v }
81
+
82
+ @process = Ragweed::Process.new(@pid)
83
+ end
84
+
85
+ def self.find_by_regex(rx)
86
+ Dir.glob("/proc/*/cmdline").each do |x|
87
+ x.gsub(/^\/proc\/(\d+)\/cmdline$/) do |ln|
88
+ f = File.read(ln)
89
+ if f =~ rx and $1.to_i != ::Process.pid.to_i
90
+ return f
91
+ end
92
+ end
93
+ end
94
+ nil
95
+ end
96
+
97
+ def install_bps
98
+ @breakpoints.each do |k,v|
99
+ v.install
100
+ end
101
+ @installed = true
102
+ end
103
+
104
+ def uninstall_bps
105
+ @breakpoints.each do |k,v|
106
+ v.uninstall
107
+ end
108
+ @installed = false
109
+ end
110
+
111
+ ## This has not been fully tested yet
112
+ def set_options(option)
113
+ r = Ragweed::Wraptux::ptrace(Ragweed::Wraptux::Ptrace::SETOPTIONS, @pid, 0, option)
114
+ end
115
+
116
+ ## Attach calls install_bps so dont forget to call breakpoint_set
117
+ ## BEFORE attach or explicitly call install_bps
118
+ def attach(opts=@opts)
119
+ r = Ragweed::Wraptux::ptrace(Ragweed::Wraptux::Ptrace::ATTACH, @pid, 0, 0)
120
+ if r != -1
121
+ @attached = true
122
+ on_attach
123
+ self.install_bps if (opts[:install] and not @installed)
124
+ else
125
+ raise "Attach failed!"
126
+ end
127
+ end
128
+
129
+ ## This method returns a hash of mapped regions
130
+ ## The hash is also stored as @mapped_regions
131
+ ## key = Start address of region
132
+ ## value = Size of the region
133
+ def mapped
134
+ @mapped_regions.clear if @mapped_regions
135
+
136
+ File.open("/proc/#{pid}/maps") do |f|
137
+ f.each_line do |l|
138
+ e = l.split(' ',2).first
139
+ s,e = e.split('-').map{|x| x.to_i(16)}
140
+ sz = e - s
141
+ @mapped_regions.store(s, sz)
142
+ end
143
+ end
144
+ @mapped_regions
145
+ end
146
+
147
+ # Return a name for a range if possible. greedy match
148
+ # returns the first found
149
+ def get_mapping_name(val)
150
+ File.open("/proc/#{pid}/maps") do |f|
151
+ f.each_line do |l|
152
+ range, perms, offset, dev, inode, pathname = l.chomp.split(" ")
153
+ base, max = range.split('-').map{|x| x.to_i(16)}
154
+ if base <= val && val <= max
155
+ return pathname
156
+ end
157
+ end
158
+ end
159
+ nil
160
+ end
161
+ alias mapping_name get_mapping_name
162
+
163
+ ## Return a range via mapping name
164
+ def get_mapping_by_name(name, exact = true)
165
+ ret = []
166
+ File.open("/proc/#{pid}/maps") do |f|
167
+ f.each_line do |l|
168
+ range, perms, offset, dev, inode, pathname = l.chomp.split(" ",6)
169
+ base, max = range.split('-').map{|x| x.to_i(16)}
170
+ if pathname
171
+ if exact && pathname == name
172
+ ret << range.split('-').map{|x| x.to_i(16)}
173
+ elsif pathname.match(name)
174
+ ret << range.split('-').map{|x| x.to_i(16)}
175
+ end
176
+ end
177
+ end
178
+ end
179
+ ret
180
+ end
181
+ alias mapping_by_name get_mapping_by_name
182
+
183
+ ## Helper method for retrieving stack range
184
+ def get_stack_range
185
+ get_mapping_by_name('[stack]')
186
+ end
187
+ alias stack_range get_stack_range
188
+
189
+ ## Helper method for retrieving heap range
190
+ def get_heap_range
191
+ get_mapping_by_name('[heap]')
192
+ end
193
+ alias heap_range get_heap_range
194
+
195
+ ## Parse procfs and create a hash containing
196
+ ## a listing of each mapped shared object
197
+ def self.shared_libraries(p)
198
+ raise "pid is 0" if p.to_i == 0
199
+
200
+ if @shared_objects
201
+ @shared_objects.clear
202
+ else
203
+ @shared_objects = Hash.new
204
+ end
205
+
206
+ File.open("/proc/#{p}/maps") do |f|
207
+ f.each_line do |l|
208
+ if l =~ /[a-zA-Z0-9].so/ && l =~ /xp /
209
+ lib = l.split(' ', 6)
210
+ sa = l.split('-', 0)
211
+
212
+ if lib[5] =~ /vdso/
213
+ next
214
+ end
215
+
216
+ lib = lib[5].strip
217
+ lib.gsub!(/[\s\n]+/, "")
218
+ @shared_objects.store(sa[0], lib)
219
+ end
220
+ end
221
+ end
222
+ @shared_objects
223
+ end
224
+
225
+ # instance method for above
226
+ # returns a hash of the mapped shared libraries
227
+ def shared_libraries
228
+ self.class.shared_libraries(@pid)
229
+ end
230
+
231
+ ## Search a specific page for a value
232
+ ## Should be used by most of the search_* methods
233
+ def search_page(base, max, val)
234
+ loc = Array.new
235
+
236
+ while base.to_i < max.to_i
237
+ r = Ragweed::Wraptux::ptrace(Ragweed::Wraptux::Ptrace::PEEK_TEXT, @pid, base, 0)
238
+ if r == val
239
+ loc.push(base)
240
+ end
241
+ base += 1
242
+ end
243
+
244
+ loc
245
+ end
246
+
247
+ ## Search the heap for a value
248
+ def search_heap(val)
249
+ loc = Array.new
250
+ File.open("/proc/#{pid}/maps") do |f|
251
+ f.each_line do |l|
252
+ if l =~ /\[heap\]/
253
+ s,e = l.split('-')
254
+ e = e.split(' ').first
255
+ s = s.to_i(16)
256
+ e = e.to_i(16)
257
+ sz = e - s
258
+ max = s + sz
259
+ loc = search_page(s, max, val)
260
+ end
261
+ end
262
+ end
263
+ loc
264
+ end
265
+
266
+ ## Search all mapped regions for a value
267
+ def search_process(val)
268
+ loc = Array.new
269
+ self.mapped
270
+ @mapped_regions.each_pair do |k,v|
271
+ if k == 0 or v == 0
272
+ next
273
+ end
274
+ max = k+v
275
+ loc.concat(search_page(k, max, val))
276
+ end
277
+ loc
278
+ end
279
+
280
+ def continue
281
+ on_continue
282
+ Ragweed::Wraptux::ptrace(Ragweed::Wraptux::Ptrace::CONTINUE, @pid, 0, 0)
283
+ end
284
+
285
+ def detach
286
+ on_detach
287
+ Ragweed::Wraptux::ptrace(Ragweed::Wraptux::Ptrace::DETACH, @pid, 0, 0)
288
+ end
289
+
290
+ def single_step
291
+ on_single_step
292
+ ret = Ragweed::Wraptux::ptrace(Ragweed::Wraptux::Ptrace::STEP, @pid, 1, 0)
293
+ end
294
+
295
+ ## Adds a breakpoint to be installed
296
+ ## ip: Insertion point
297
+ ## name: name of breakpoint
298
+ ## callable: object to .call at breakpoint
299
+ def breakpoint_set(ip, name="", callable=nil, &block)
300
+ if not callable and block_given?
301
+ callable = block
302
+ end
303
+ @breakpoints.each_key { |k| if k == ip then return end }
304
+ bp = Breakpoint.new(ip, callable, @pid, name)
305
+ @breakpoints[ip] = bp
306
+ end
307
+
308
+ ## Remove a breakpoint by ip
309
+ def breakpoint_clear(ip)
310
+ bp = @breakpoints[ip]
311
+ return nil if bp.nil?
312
+ bp.uninstall
313
+ end
314
+
315
+ ## loop for wait()
316
+ ## times: the number of wait calls to make
317
+ def loop(times=nil)
318
+ if times.kind_of? Numeric
319
+ times.times do
320
+ self.wait
321
+ end
322
+ elsif times.nil?
323
+ self.wait while not @exited
324
+ end
325
+ end
326
+
327
+ def wexitstatus(status)
328
+ (((status) & 0xff00) >> 8)
329
+ end
330
+
331
+ def wtermsig(status)
332
+ ((status) & 0x7f)
333
+ end
334
+
335
+ ## This wait must be smart, it has to wait for a signal
336
+ ## when SIGTRAP is received we need to see if one of our
337
+ ## breakpoints has fired. If it has then execute the block
338
+ ## originally stored with it. If its a different signal,
339
+ ## then process it accordingly and move on
340
+ def wait(opts = 0)
341
+ r, status = Ragweed::Wraptux::waitpid(@pid, opts)
342
+ wstatus = wtermsig(status)
343
+ @signal = wexitstatus(status)
344
+ event_code = (status >> 16)
345
+ found = false
346
+
347
+ if r[0] != -1 ## Check the ret
348
+ case ## FIXME - I need better logic (use Signal module)
349
+ when wstatus == 0 ##WIFEXITED
350
+ @exited = true
351
+ try(:on_exit)
352
+ when wstatus != 0x7f ##WIFSIGNALED
353
+ @exited = false
354
+ try(:on_signal)
355
+ when @signal == Ragweed::Wraptux::Signal::SIGINT
356
+ try(:on_sigint)
357
+ self.continue
358
+ when @signal == Ragweed::Wraptux::Signal::SIGSEGV
359
+ try(:on_segv)
360
+ when @signal == Ragweed::Wraptux::Signal::SIGILL
361
+ try(:on_illegal_instruction)
362
+ when @signal == Ragweed::Wraptux::Signal::SIGIOT
363
+ try(:on_iot_trap)
364
+ self.continue
365
+ when @signal == Ragweed::Wraptux::Signal::SIGTRAP
366
+ try(:on_sigtrap)
367
+ r = self.get_registers
368
+ eip = r.eip
369
+ eip -= 1
370
+ case
371
+ when @breakpoints.has_key?(eip)
372
+ found = true
373
+ try(:on_breakpoint)
374
+ self.continue
375
+ when event_code == Ragweed::Wraptux::Ptrace::EventCodes::FORK
376
+ p = FFI::MemoryPointer.new(:int, 1)
377
+ Ragweed::Wraptux::ptrace(Ragweed::Wraptux::Ptrace::GETEVENTMSG, @pid, 0, p.to_i)
378
+ ## Fix up the PID in each breakpoint
379
+ if (1..65535) === p.get_int32(0) && @opts[:fork] == true
380
+ @breakpoints.each_pair do |k,v|
381
+ v.each do |b|
382
+ b.bppid = p[:pid]
383
+ end
384
+ end
385
+
386
+ @pid = p[:pid]
387
+ try(:on_fork_child, @pid)
388
+ end
389
+ when event_code == Ragweed::Wraptux::Ptrace::EventCodes::EXEC
390
+ when event_code == Ragweed::Wraptux::Ptrace::EventCodes::CLONE
391
+ when event_code == Ragweed::Wraptux::Ptrace::EventCodes::VFORK
392
+ when event_code == Ragweed::Wraptux::Ptrace::EventCodes::EXIT
393
+ ## Not done yet
394
+ else
395
+ self.continue
396
+ end
397
+ when @signal == Ragweed::Wraptux::Signal::SIGCHLD
398
+ try(:on_sigchild)
399
+ when @signal == Ragweed::Wraptux::Signal::SIGTERM
400
+ try(:on_sigterm)
401
+ when @signal == Ragweed::Wraptux::Signal::SIGCONT
402
+ try(:on_continue)
403
+ self.continue
404
+ when @signal == Ragweed::Wraptux::Signal::SIGSTOP
405
+ try(:on_sigstop)
406
+ Ragweed::Wraptux::kill(@pid, Ragweed::Wraptux::Signal::SIGCONT)
407
+ self.continue
408
+ when @signal == Ragweed::Wraptux::Signal::SIGWINCH
409
+ self.continue
410
+ else
411
+ raise "Add more signal handlers (##{@signal})"
412
+ end
413
+ end
414
+ end
415
+
416
+ def self.threads(pid)
417
+ a = []
418
+ begin
419
+ a = Dir.entries("/proc/#{pid}/task/")
420
+ a.delete_if {|x| x == '.' || x == '..'}
421
+ rescue
422
+ puts "No such PID: #{pid}"
423
+ end
424
+ a
425
+ end
426
+
427
+ def get_registers
428
+ regs = FFI::MemoryPointer.new(Ragweed::Wraptux::PTRegs, 1)
429
+ Ragweed::Wraptux::ptrace(Ragweed::Wraptux::Ptrace::GETREGS, @pid, 0, regs.to_i)
430
+ return Ragweed::Wraptux::PTRegs.new regs
431
+ end
432
+
433
+ def set_registers(regs)
434
+ Ragweed::Wraptux::ptrace(Ragweed::Wraptux::Ptrace::SETREGS, @pid, 0, regs.to_ptr.address)
435
+ end
436
+
437
+ ## Here we need to do something about the bp
438
+ ## we just hit. We have a block to execute.
439
+ ## Remember if you implement this on your own
440
+ ## make sure to call super, and also realize
441
+ ## EIP won't look correct until this runs
442
+ def on_breakpoint
443
+ r = get_registers
444
+ eip = r.eip
445
+ eip -= 1
446
+
447
+ ## Call the block associated with the breakpoint
448
+ @breakpoints[eip].call(r, self)
449
+
450
+ ## The block may have called breakpoint_clear
451
+ del = true if !@breakpoints[eip].installed?
452
+
453
+ ## Uninstall and single step the bp
454
+ @breakpoints[eip].uninstall
455
+ r.eip = eip
456
+ set_registers(r)
457
+ single_step
458
+
459
+ ## ptrace peektext returns -1 upon reinstallation of bp without calling
460
+ ## waitpid() if that occurs the breakpoint cannot be reinstalled
461
+ Ragweed::Wraptux::waitpid(@pid, 0)
462
+
463
+ if del == true
464
+ ## The breakpoint block may have called breakpoint_clear
465
+ @breakpoints.delete(eip)
466
+ else
467
+ @breakpoints[eip].install
468
+ end
469
+ end
470
+
471
+ def on_attach() end
472
+ def on_single_step() end
473
+ def on_continue() end
474
+ def on_exit() end
475
+ def on_signal() end
476
+ def on_sigint() end
477
+ def on_segv() end
478
+ def on_illegal_instruction() end
479
+ def on_sigtrap() end
480
+ def on_fork_child(pid) end
481
+ def on_sigchild() end
482
+ def on_sigterm() end
483
+ def on_sigstop() end
484
+ def on_iot_trap() end
485
+
486
+ def print_registers
487
+ regs = get_registers
488
+ puts "eip %08x" % regs.eip
489
+ puts "ebp %08x" % regs.ebp
490
+ puts "esi %08x" % regs.esi
491
+ puts "edi %08x" % regs.edi
492
+ puts "esp %08x" % regs.esp
493
+ puts "eax %08x" % regs.eax
494
+ puts "ebx %08x" % regs.ebx
495
+ puts "ecx %08x" % regs.ecx
496
+ puts "edx %08x" % regs.edx
497
+ end
498
+
499
+ def default_opts(opts)
500
+ @opts = @opts.merge(opts)
501
+ end
502
+ end
@@ -0,0 +1,223 @@
1
+ class Ragweed::Detour
2
+ # "Ghetto Detours", as Scott Stender might say. Patch subprograms
3
+ # in to running programs as a hooking mechanism.
4
+ class Detour
5
+ attr_reader :snarfed
6
+ attr_reader :dpoint
7
+ attr_reader :stack
8
+
9
+ # Easiest way to do this is just to ask WinProcess#detour. Wants
10
+ # "p" to be a pointer into the process, presumable returned from
11
+ # WinProcess#get_proc.
12
+ #
13
+ # In theory, "p" should be OK anywhere as long as there are 5
14
+ # bytes of instructions before the end of the basic block its
15
+ # in. In practice, this only really stands a chance of working
16
+ # if "p" points to a function prologue.
17
+ def initialize(p, opts={})
18
+ @p = p.p
19
+ @dpoint = p
20
+ @opts = opts
21
+ @a = @opts[:arena] || @p.arena
22
+ @stack = @a.alloc(2048)
23
+ @snarfed = snarf_prologue
24
+ end
25
+
26
+ # Release the detour and its associated memory, unpatch the
27
+ # target function.
28
+ def release
29
+ @dpoint.write(@snarfed)
30
+ @a.release if not @opts[:arena]
31
+ end
32
+
33
+ # Patch the target function. There is a 70% chance this will
34
+ # totally fuck your process.
35
+ #
36
+ # You would be wise to have the threads in the process suspended
37
+ # while you do this, but I'm not going to do it for you.
38
+ def call
39
+ # a Detours-style trampoline --- the location we patch the
40
+ # target function to jump to --- consists of:
41
+ #
42
+ # - A stack switch (to push/pop w/o fucking the program)
43
+ # - A context save
44
+ # - The Detour code
45
+ # - A context restore
46
+ # - A stack restore
47
+ # - The code we patched out of the target
48
+ # - A jump back to the target function (after the prologue)
49
+
50
+ # Do this now to make room for the (probably 5 byte) jump.
51
+ # We don't know what the address will be until we allocate.
52
+ jumpback = (Jmp 0xDEADBEEF) # patch back later
53
+
54
+ # Build the trampoline
55
+ tramp = trampoline(@stack).assemble
56
+
57
+ # Figure out how big the whole mess will be, allocate it
58
+ tsz = tramp.size + @snarfed.size + jumpback.to_s.size
59
+ tbuf = @a.alloc(tsz + 10)
60
+
61
+ # assume trampoline is ahead of the patched program text;
62
+ # jump to [dpoint+patch]
63
+ jumpback.dst = (@dpoint.to_i + @snarfed.size) - (tbuf + tsz)
64
+
65
+ # Write it into memory. It's not "live" yet because we haven't
66
+ # patched the target function.
67
+ @p.write(tbuf, tramp + @snarfed + jumpback.to_s)
68
+
69
+ # But now it is. =)
70
+ @p.write(@dpoint, injection(tbuf).assemRASble)
71
+ end
72
+
73
+ # Hook function. Override this in subclasses to provide different
74
+ # behavior.
75
+ def inner_block
76
+ i = Ragweed::Rasm::Subprogram.new
77
+ i.<< Int(3)
78
+ end
79
+
80
+ private
81
+
82
+ # No user-servicable parts below.
83
+
84
+ # Pull at least 5 bytes of instructions out of the prologue, using
85
+ # the disassembler, to make room for our patch jump. Save it, so
86
+ # we can unpatch later.
87
+ def snarf_prologue
88
+ i = 0
89
+ (buf = @dpoint.read(20)).distorm.each do |insn|
90
+ i += insn.size
91
+ break if i >= 5
92
+ end
93
+ buf[0...i]
94
+ end
95
+
96
+ # Create the Jmp instruction that implements the patch; you can't
97
+ # do this until you know where the trampoline was actually injected
98
+ # into the process.
99
+ def injection(tramp)
100
+ here = @dpoint
101
+ there = tramp
102
+
103
+ if there < here
104
+ goto = -((here - there) + 5)
105
+ else
106
+ goto = there - here
107
+ end
108
+
109
+ i = Ragweed::Rasm::Subprogram.new
110
+ i.<< Ragweed::Rasm::Jmp(goto.to_i)
111
+ end
112
+
113
+ # Create the detours trampoline:
114
+ def trampoline(stack)
115
+ i = Ragweed::Rasm::Subprogram.new
116
+ i.concat push_stack(stack) # 1. Give us a new stack
117
+ i.concat save_all # 2. Save all the GPRs just in case
118
+ i.concat inner_block # 3. The hook function
119
+ i.concat restore_all # 4. Restore all the GPRs.
120
+ i.concat pop_stack # 5. Restore the stack
121
+ return i
122
+ end
123
+
124
+ # Swap in a new stack, pushing the old stack address
125
+ # onto the top of it.
126
+ def push_stack(addr, sz=2048)
127
+ i = Ragweed::Rasm::Subprogram.new
128
+ i.<< Ragweed::Rasm::Push(eax)
129
+ i.<< Ragweed::Rasm::Mov(eax, addr+(sz-4))
130
+ i.<< Ragweed::Rasm::Mov([eax], esp)
131
+ i.<< Ragweed::Rasm::Pop(eax)
132
+ i.<< Ragweed::Rasm::Mov(esp, addr+(sz-4))
133
+ end
134
+
135
+ # Swap out the new stack.
136
+ def pop_stack
137
+ i = Ragweed::Rasm::Subprogram.new
138
+ i.<< Ragweed::Rasm::Pop(esp)
139
+ i.<< Ragweed::Rasm::Add(esp, 4)
140
+ end
141
+
142
+ # Just push all the registers in order
143
+ def save_all
144
+ i = Ragweed::Rasm::Subprogram.new
145
+ [eax,ecx,edx,ebx,ebp,esi,edi].each do |r|
146
+ i.<< Ragweed::Rasm::Push(r)
147
+ end
148
+ i
149
+ end
150
+
151
+ # Just pop all the registers
152
+ def restore_all
153
+ i = Ragweed::Rasm::Subprogram.new
154
+ [edi,esi,ebp,ebx,edx,ecx,eax].each do |r|
155
+ i.<< Ragweed::Rasm::Pop(r)
156
+ end
157
+ i
158
+ end
159
+ end
160
+
161
+ # A breakpoint implemented as a Detour. TODO not tested.
162
+ class Dbreak < Detour
163
+ attr_reader :ev1, :ev2
164
+
165
+ # accepts:
166
+ # :ev1: reuse events from somewhere else
167
+ # :ev2:
168
+ def initialize(*args)
169
+ super
170
+ @ev1 = @opts[:ev1] || WinEvent.new
171
+ @ev2 = @opts[:ev2] || WinEvent.new
172
+
173
+ # create the state block that the eventpair shim wants:
174
+ mem = @a.alloc(100)
175
+ @data = mem
176
+
177
+ # ghetto vtbl
178
+ swch = ["OpenProcess",
179
+ "DuplicateHandle",
180
+ "ResetEvent",
181
+ "SetEvent",
182
+ "WaitForSingleObject",
183
+ "GetCurrentThreadId"].
184
+ map {|x| @p.get_proc("kernel32!#{x}").to_i}.
185
+ pack("LLLLLL")
186
+
187
+ # ghetto instance vars
188
+ state = [@p.w.get_current_process_id, @ev1.handle, @ev2.handle].
189
+ pack("LLL")
190
+ @data.write(swch + state)
191
+ end
192
+
193
+ def inner_block
194
+ i = Ragweed::Rasm::Subprogram.new
195
+ i.<< Push(eax)
196
+ i.<< Xor(eax, eax)
197
+ i.<< Or(eax, @data)
198
+ i.<< Push(eax)
199
+ i.<< Call(1) # cheesy in the extreme: fake a call
200
+ # so I don't have to change my event shim
201
+ i.<< Nop.new
202
+ i.<< Nop.new
203
+ i.<< Nop.new
204
+ i.<< Nop.new
205
+ i.<< Nop.new
206
+ s = event_pair_stub
207
+ s[-1] = Add(esp, 4)
208
+ i.concat(s)
209
+ i.<< Pop(eax)
210
+ return i
211
+ end
212
+
213
+ # in theory, loop on this breakpoint
214
+ def on(&block)
215
+ puts "#{ @p.pid }: #{ @ev1.handle }" # in case we need to release
216
+ loop do
217
+ @ev1.wait
218
+ yield
219
+ @ev2.signal
220
+ end
221
+ end
222
+ end
223
+ end