tduehr-ragweed 0.1.5

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 (62) hide show
  1. data/History.txt +15 -0
  2. data/README.rdoc +35 -0
  3. data/README.txt +9 -0
  4. data/Rakefile +30 -0
  5. data/examples/hittracertux.rb +48 -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 +419 -0
  14. data/lib/ragweed/debuggertux.rb +347 -0
  15. data/lib/ragweed/detour.rb +223 -0
  16. data/lib/ragweed/ptr.rb +48 -0
  17. data/lib/ragweed/rasm/isa.rb +1046 -0
  18. data/lib/ragweed/rasm/util.rb +26 -0
  19. data/lib/ragweed/rasm.rb +53 -0
  20. data/lib/ragweed/sbuf.rb +197 -0
  21. data/lib/ragweed/trampoline.rb +103 -0
  22. data/lib/ragweed/utils.rb +87 -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 +53 -0
  34. data/lib/ragweed/wraposx/constants.rb +101 -0
  35. data/lib/ragweed/wraposx/kernelerrorx.rb +147 -0
  36. data/lib/ragweed/wraposx/region_info.rb +244 -0
  37. data/lib/ragweed/wraposx/thread_context.rb +203 -0
  38. data/lib/ragweed/wraposx/thread_info.rb +213 -0
  39. data/lib/ragweed/wraposx/wraposx.rb +376 -0
  40. data/lib/ragweed/wraposx.rb +53 -0
  41. data/lib/ragweed/wraptux/constants.rb +68 -0
  42. data/lib/ragweed/wraptux/threads.rb +3 -0
  43. data/lib/ragweed/wraptux/wraptux.rb +76 -0
  44. data/lib/ragweed/wraptux.rb +53 -0
  45. data/lib/ragweed.rb +84 -0
  46. data/spec/ragweed_spec.rb +7 -0
  47. data/spec/spec_helper.rb +16 -0
  48. data/tasks/ann.rake +80 -0
  49. data/tasks/bones.rake +20 -0
  50. data/tasks/gem.rake +201 -0
  51. data/tasks/git.rake +40 -0
  52. data/tasks/notes.rake +27 -0
  53. data/tasks/post_load.rake +34 -0
  54. data/tasks/rdoc.rake +51 -0
  55. data/tasks/rubyforge.rake +55 -0
  56. data/tasks/setup.rb +292 -0
  57. data/tasks/spec.rake +54 -0
  58. data/tasks/svn.rake +47 -0
  59. data/tasks/test.rake +40 -0
  60. data/tasks/zentest.rake +36 -0
  61. data/test/test_ragweed.rb +0 -0
  62. metadata +127 -0
@@ -0,0 +1,419 @@
1
+ require 'ragweed/wraposx'
2
+
3
+ module Ragweed; end
4
+
5
+ # Debugger class for Mac OS X
6
+ # You can use this class in 2 ways:
7
+ #
8
+ # (1) You can create instances of Debuggerosx 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 Debuggerosx already handles, call "super", too.
14
+ class Ragweed::Debuggerosx
15
+ include Ragweed
16
+
17
+ attr_reader :pid
18
+ attr_reader :status
19
+ attr_reader :task
20
+ attr_reader :exited
21
+ attr_accessor :breakpoints
22
+
23
+ class Breakpoint
24
+ # include Ragweed::Wraposx
25
+ INT3 = 0xCC
26
+ attr_accessor :orig
27
+ attr_accessor :bpid
28
+ attr_reader :addr
29
+ attr_accessor :function
30
+
31
+ # bp: parent for method_missing calls
32
+ # ip: insertion point
33
+ # callable: lambda to be called when breakpoint is hit
34
+ # name: name of breakpoint
35
+ def initialize(bp, ip, callable, name = "")
36
+ @@bpid ||= 0
37
+ @bp = bp
38
+ @function = name
39
+ @addr = ip
40
+ @callable = callable
41
+ @bpid = (@@bpid += 1)
42
+ @installed = false
43
+ end
44
+
45
+ # Install this breakpoint.
46
+ def install
47
+ Ragweed::Wraposx::task_suspend(@bp.task)
48
+ @bp.hook if not @bp.hooked?
49
+ Ragweed::Wraposx::vm_protect(@bp.task,@addr,1,false,Ragweed::Wraposx::Vm::Prot::ALL)
50
+ @orig = Ragweed::Wraposx::vm_read(@bp.task,@addr,1)
51
+ if(@orig != INT3)
52
+ Ragweed::Wraposx::vm_write(@bp.task,@addr, [INT3].pack('C'))
53
+ end
54
+ @installed = true
55
+ Ragweed::Wraposx::task_resume(@bp.task)
56
+ end
57
+
58
+ # Uninstall this breakpoint.
59
+ def uninstall
60
+ Ragweed::Wraposx::task_suspend(@bp.task)
61
+ if(@orig != INT3)
62
+ Ragweed::Wraposx::vm_write(@bp.task, @addr, @orig)
63
+ end
64
+ @installed = false
65
+ Ragweed::Wraposx::task_resume(@bp.task)
66
+ end
67
+
68
+ def installed?; @installed; end
69
+ def call(*args); @callable.call(*args) if @callable != nil; end
70
+ def method_missing(meth, *args); @bp.send(meth, *args); end
71
+ end
72
+
73
+ #init object
74
+ #p: pid of process to be debugged
75
+ #opts: default options for automatically doing things (attach, install, and hook)
76
+ def initialize(p,opts={})
77
+ if p.kind_of? Numeric
78
+ @pid = p
79
+ else
80
+ #coming soon: find process by name
81
+ raise "Provide a PID"
82
+ end
83
+ @opts = opts
84
+ default_opts(opts)
85
+
86
+ @installed = false
87
+ @attached = false
88
+ @hooked = false
89
+ @breakpoints = Hash.new do |h, k|
90
+ bps = Array.new
91
+ def bps.call(*args); each {|bp| bp.call(*args)}; end
92
+ def bps.install; each {|bp| bp.install}; end
93
+ def bps.uninstall; each {|bp| bp.uninstall}; end
94
+ def bps.orig; each {|bp| dp.orig}; end
95
+ h[k] = bps
96
+ end
97
+ @opts.each {|k, v| try(k) if v}
98
+ end
99
+
100
+ #loop calls to wait
101
+ #times: number of times to loop
102
+ # if nil this will loop until @exited is set
103
+ def loop(times=nil)
104
+ if times.kind_of? Numeric
105
+ times.times do
106
+ self.wait
107
+ end
108
+ elsif times.nil?
109
+ self.wait while not @exited
110
+ end
111
+ end
112
+
113
+ # wait for process and run callback on return then continue child
114
+ # FIXME - need to do signal handling better (loop through threads only for breakpoints and stepping)
115
+ # opts: option flags to waitpid(2)
116
+ #
117
+ # returns and array containing the pid of the stopped or terminated child and the status of that child
118
+ # r[0]: pid of stopped/terminated child or 0 if Ragweed::Wraposx::Wait:NOHANG was passed and there was nothing to report
119
+ # r[1]: staus of child or 0 if Ragweed::Wraposx::Wait:NOHANG was passed and there was nothing to report
120
+ def wait(opts = 0)
121
+ r = Ragweed::Wraposx::waitpid(@pid,opts)
122
+ status = r[1]
123
+ wstatus = status & 0x7f
124
+ signal = status >> 8
125
+ found = false
126
+ if r[0] != 0 #r[0] == 0 iff wait had nothing to report and NOHANG option was passed
127
+ case
128
+ when wstatus == 0 #WIFEXITED
129
+ @exited = true
130
+ try(:on_exit, signal)
131
+ when wstatus != 0x7f #WIFSIGNALED
132
+ @exited = false
133
+ try(:on_signaled, wstatus)
134
+ when signal != 0x13 #WIFSTOPPED
135
+ self.threads.each do |t|
136
+ if @breakpoints.has_key?(self.get_registers(t).eip-1)
137
+ found = true
138
+ try(:on_breakpoint, t)
139
+ end
140
+ end
141
+ if not found # no breakpoint so iterate through Signal constants to find the current SIG
142
+ Signal.list.each do |sig, val|
143
+ try("on_sig#{ sig.downcase }".intern) if signal == val
144
+ end
145
+ end
146
+ try(:on_stop, signal)
147
+ begin
148
+ self.continue
149
+ rescue Errno::EBUSY
150
+ # Yes this happens and it's wierd
151
+ # Not sure it should happen
152
+ pp 'unable to self.continue'
153
+ pp self.get_registers
154
+ retry
155
+ end
156
+ when signal == 0x13 #WIFCONTINUED
157
+ try(:on_continue)
158
+ else #holy broken stuff batman
159
+ raise "Unknown signal '#{signal}' recieved: This should not happen - ever."
160
+ end
161
+ end
162
+ return r
163
+ end
164
+
165
+ # these event functions are stubs. Implementations should override these
166
+ def on_single_step
167
+ pp "Single stepping #{ thread } (on_single_step)"
168
+ pp Ragweed::Wraposx::ThreadInfo.get(thread)
169
+ end
170
+
171
+ def on_sigsegv
172
+ pp Ragweed::Wraposx::ThreadContext.get(thread)
173
+ pp Ragweed::Wraposx::ThreadInfo.get(thread)
174
+ end
175
+
176
+ def on_exit(status)
177
+ pp "Exited! (on_exit)"
178
+ @exited = true
179
+ end
180
+
181
+ def on_signal(signal)
182
+ pp "Exited with signal #{ signal } (on_signal)"
183
+ @exited = true
184
+ end
185
+
186
+ def on_stop(signal)
187
+ pp "#Stopped with signal #{ signal } (on_stop)"
188
+ end
189
+
190
+ def on_continue
191
+ pp "Continued! (on_continue)"
192
+ end
193
+
194
+ # installs all breakpoints into child process
195
+ # add breakpoints to install via breakpoint_set
196
+ def install_bps
197
+ self.hook if not @hooked
198
+ @breakpoints.each do |k,v|
199
+ v.install
200
+ end
201
+ @installed = true
202
+ end
203
+
204
+ # removes all breakpoints from child process
205
+ def uninstall_bps
206
+ @breakpoints.each do |k,v|
207
+ v.uninstall
208
+ end
209
+ @installed = false
210
+ end
211
+
212
+ # attach to @pid for debugging
213
+ # opts is a hash for automatically firing other functions as an overide for @opts
214
+ # returns 0 on no error
215
+ def attach(opts=@opts)
216
+ r = Ragweed::Wraposx::ptrace(Ragweed::Wraposx::Ptrace::ATTACH,@pid,0,0)
217
+ # Ragweed::Wraposx::ptrace(Ragweed::Wraposx::Ptrace::CONTINUE,@pid,1,0)
218
+ @attached = true
219
+ self.hook(opts) if (opts[:hook] and not @hooked)
220
+ self.install_bps if (opts[:install] and not @installed)
221
+ return r
222
+ end
223
+
224
+ # remove breakpoints and release child
225
+ # opts is a hash for automatically firing other functions as an overide for @opts
226
+ # returns 0 on no error
227
+ def detach(opts=@opts)
228
+ self.uninstall_bps if @installed
229
+ r = Ragweed::Wraposx::ptrace(Ragweed::Wraposx::Ptrace::DETACH,@pid,0,Ragweed::Wraposx::Wait::UNTRACED)
230
+ @attached = false
231
+ self.unhook(opts) if opts[:hook] and @hooked
232
+ return r
233
+ end
234
+
235
+ # get task port for @pid and store in @task so mach calls can be made
236
+ # opts is a hash for automatically firing other functions as an overide for @opts
237
+ # returns the task port for @pid
238
+ def hook(opts=@opts)
239
+ @task = Ragweed::Wraposx::task_for_pid(@pid)
240
+ @hooked = true
241
+ self.attach(opts) if opts[:attach] and not @attached
242
+ return @task
243
+ end
244
+
245
+ # theoretically to close the task port but,
246
+ # no way to close the port has yet been found.
247
+ # This function currently does little/nothing.
248
+ def unhook(opts=@opts)
249
+ self.detach(opts) if opts[:attach] and @attached
250
+ self.unintsall_bps if opts[:install] and @installed
251
+ end
252
+
253
+ # resumes thread that has been suspended via thread_suspend
254
+ # thread: thread id of thread to be resumed
255
+ def resume(thread = nil)
256
+ thread = (thread or self.threads.first)
257
+ Ragweed::Wraposx::thread_resume(thread)
258
+ end
259
+
260
+ # suspends thread
261
+ # thread: thread id of thread to be suspended
262
+ def suspend(thread = nil)
263
+ thread = (thread or self.threads.first)
264
+ Ragweed::Wraposx::thread_suspend(thread)
265
+ end
266
+
267
+ # sends a signal to process with id @pid
268
+ # sig: signal to be sent to process @pid
269
+ def kill(sig = 0)
270
+ Ragweed::Wraposx::kill(@pid,sig)
271
+ end
272
+
273
+ # adds a breakpoint and callable block to be installed into child process
274
+ # ip: address of insertion point
275
+ # callable: object to receive call() when this breakpoint is hit
276
+ def breakpoint_set(ip, name="", callable=nil, &block)
277
+ if not callable and block_given?
278
+ callable = block
279
+ end
280
+ @breakpoints[ip] << Breakpoint.new(self, ip, callable, name)
281
+ end
282
+
283
+ # removes breakpoint from child process
284
+ # ip: insertion point of breakpoints to be removed
285
+ # bpid: id of breakpoint to be removed
286
+ def breakpoint_clear(ip, bpid=nil)
287
+ if not bpid
288
+ @breakpoints[ip].uninstall
289
+ @breakpoints.delete ip
290
+ else
291
+ found = nil
292
+ @breakpoints[ip].each_with_index do |bp, i|
293
+ if bp.bpid == bpid
294
+ found = i
295
+ if bp.orig != Breakpoint::INT3
296
+ if @breakpoints[ip][i+1]
297
+ @breakpoints[ip][i + 1].orig = bp.orig
298
+ else
299
+ bp.uninstall
300
+ end
301
+ end
302
+ end
303
+ end
304
+ raise "couldn't find #{ ip }" if not found
305
+ @breakpoints[ip].delete_at(found) if found
306
+ end
307
+ end
308
+
309
+ # default method for breakpoint handling
310
+ # thread: id of the thread stopped at a breakpoint
311
+ def on_breakpoint(thread)
312
+ r = self.get_registers(thread)
313
+ #rewind eip to correct position
314
+ r.eip -= 1
315
+ #don't use r.eip since it may be changed by breakpoint callback
316
+ eip = r.eip
317
+ #clear stuff set by INT3
318
+ #r.esp -=4
319
+ #r.ebp = r.esp
320
+ #fire callback
321
+ @breakpoints[eip].call(thread, r, self)
322
+ if @breakpoints[eip].first.installed?
323
+ #uninstall breakpoint to continue past it
324
+ @breakpoints[eip].first.uninstall
325
+ #set trap flag so we don't go too far before reinserting breakpoint
326
+ r.eflags |= Ragweed::Wraposx::EFlags::TRAP
327
+ #set registers to commit eip and eflags changes
328
+ self.set_registers(thread, r)
329
+
330
+ #step once
331
+ self.stepp
332
+
333
+ # now we wait() to prevent a race condition that'll SIGBUS us
334
+ # Yup, a race condition where the child may not complete a single
335
+ # instruction before the parent completes many
336
+ Ragweed::Wraposx::waitpid(@pid,0)
337
+
338
+ #reset breakpoint
339
+ @breakpoints[eip].first.install
340
+ end
341
+ end
342
+
343
+ # returns an array of the thread ids of the child process
344
+ def threads
345
+ self.hook if not @hooked
346
+ Ragweed::Wraposx::task_threads(@task)
347
+ end
348
+
349
+ # returns a Ragweed::Wraposx::ThreadContext object containing the register states
350
+ # thread: thread to get the register state of
351
+ def get_registers(thread=nil)
352
+ thread = (thread or self.threads.first)
353
+ Ragweed::Wraposx::ThreadContext.get(thread)
354
+ end
355
+
356
+ # sets the register state of a thread
357
+ # thread: thread id to set registers for
358
+ # regs: Ragweed::Wraposx::ThreadContext object containing the new register state for the thread
359
+ def set_registers(thread, regs)
360
+ raise "Must supply registers and thread to set" if (not (thread and regs) or not thread.kind_of? Numeric or not regs.kind_of? Ragweed::Wraposx::ThreadContext)
361
+ regs.set(thread)
362
+ end
363
+
364
+ # continue stopped child process.
365
+ # addr: address from which to continue child. defaults to current position.
366
+ # data: signal to be sent to child. defaults to no signal.
367
+ def continue(addr = 1, data = 0)
368
+ Ragweed::Wraposx::ptrace(Ragweed::Wraposx::Ptrace::CONTINUE,@pid,addr,data)
369
+ end
370
+
371
+ # Do not use this function unless you know what you're doing!
372
+ # It causes a kernel panic in some situations (fine if the trap flag is set in theory)
373
+ # same arguments as Debugerosx#continue
374
+ # single steps the child process
375
+ def stepp(addr = 1, data = 0)
376
+ Ragweed::Wraposx::ptrace(Ragweed::Wraposx::Ptrace::STEP,@pid,addr,data)
377
+ end
378
+
379
+ # sends a signal to a thread of the child's
380
+ # this option to ptrace is undocumented in OS X, usage pulled from gdb and googling
381
+ # thread: id of thread to which a signal is to be sent
382
+ # sig: signal to be sent to child's thread
383
+ def thread_update(thread = nil, sig = 0)
384
+ thread = thread or self.threads.first
385
+ Ragweed::Wraposx::ptrace(Ragweed::Wraposx::Ptrace::THUPDATE,@pid,thread,sig)
386
+ end
387
+
388
+ def hooked?; @hooked; end
389
+ def attached?; @attached; end
390
+ def installed?; @installed; end
391
+
392
+ def region_info(addr, flavor = :basic)
393
+ case flavor
394
+ when :basic
395
+ return Ragweed::Wraposx::RegionBasicInfo.get(@task, addr)
396
+
397
+ # Extended and Top info flavors are included in case Apple re implements them
398
+ when :extended
399
+ warn "VM Region Extended Info not implemented by Apple. Returning RegionBasicInfo"
400
+ return Ragweed::Wraposx::RegionBasicInfo.get(@task, addr)
401
+ when :top
402
+ warn "VM Region Top Info not implemented be Apple. Returning RegionBasicInfo"
403
+ return Ragweed::Wraposx::RegionBasicInfo.get(@task, addr)
404
+ else
405
+ warn "Unknown flavor requested. Returning RegionBasicInfo."
406
+ return Ragweed::Wraposx::RegionBasicInfo.get(@task, addr)
407
+ end
408
+ end
409
+
410
+ private
411
+
412
+ # sets instance automagic options to sane(ish) defaults when not given
413
+ # FIXME - I should use Hash#merge!
414
+ def default_opts(opts)
415
+ @opts[:hook] = opts[:hook] != nil ? opts[:hook] : true
416
+ @opts[:attach] = opts[:attach] != nil ? opts[:attach] : false
417
+ @opts[:install] = opts[:install] != nil ? opts[:install] : false
418
+ end
419
+ end