ragweed 0.2.0-java

Sign up to get free protection for your applications and to get access to all the features.
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