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,427 @@
1
+ require ::File.join(::File.dirname(__FILE__),'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 an 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
+ # decrement our tasks suspend count
350
+ def resume_task
351
+ Ragweed::Wraposx::task_resume(@task)
352
+ end
353
+
354
+ # increment our tasks suspend count
355
+ def suspend_task
356
+ Ragweed::Wraposx::task_suspend(@task)
357
+ end
358
+
359
+ # returns a Ragweed::Wraposx::ThreadContext object containing the register states
360
+ # thread: thread to get the register state of
361
+ def get_registers(thread=nil)
362
+ thread = (thread or self.threads.first)
363
+ Ragweed::Wraposx::ThreadContext.get(thread)
364
+ end
365
+
366
+ # sets the register state of a thread
367
+ # thread: thread id to set registers for
368
+ # regs: Ragweed::Wraposx::ThreadContext object containing the new register state for the thread
369
+ def set_registers(thread, regs)
370
+ 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)
371
+ regs.set(thread)
372
+ end
373
+
374
+ # continue stopped child process.
375
+ # addr: address from which to continue child. defaults to current position.
376
+ # data: signal to be sent to child. defaults to no signal.
377
+ def continue(addr = 1, data = 0)
378
+ Ragweed::Wraposx::ptrace(Ragweed::Wraposx::Ptrace::CONTINUE,@pid,addr,data)
379
+ end
380
+
381
+ # Do not use this function unless you know what you're doing!
382
+ # It causes a kernel panic in some situations (fine if the trap flag is set in theory)
383
+ # same arguments as Debugerosx#continue
384
+ # single steps the child process
385
+ def stepp(addr = 1, data = 0)
386
+ Ragweed::Wraposx::ptrace(Ragweed::Wraposx::Ptrace::STEP,@pid,addr,data)
387
+ end
388
+
389
+ # sends a signal to a thread of the child's
390
+ # this option to ptrace is undocumented in OS X, usage pulled from gdb and googling
391
+ # thread: id of thread to which a signal is to be sent
392
+ # sig: signal to be sent to child's thread
393
+ def thread_update(thread = nil, sig = 0)
394
+ thread = thread or self.threads.first
395
+ Ragweed::Wraposx::ptrace(Ragweed::Wraposx::Ptrace::THUPDATE,@pid,thread,sig)
396
+ end
397
+
398
+ def hooked?; @hooked; end
399
+ def attached?; @attached; end
400
+ def installed?; @installed; end
401
+
402
+ def region_info(addr, flavor = :basic)
403
+ case flavor
404
+ when :basic
405
+ return Ragweed::Wraposx::RegionBasicInfo.get(@task, addr)
406
+
407
+ # Extended and Top info flavors are included in case Apple re implements them
408
+ when :extended
409
+ return Ragweed::Wraposx::RegionExtendedInfo.get(@task, addr)
410
+ when :top
411
+ return Ragweed::Wraposx::RegionTopInfo.get(@task, addr)
412
+ else
413
+ warn "Unknown flavor requested. Returning RegionBasicInfo."
414
+ return Ragweed::Wraposx::RegionBasicInfo.get(@task, addr)
415
+ end
416
+ end
417
+
418
+ private
419
+
420
+ # sets instance automagic options to sane(ish) defaults when not given
421
+ # FIXME - I should use Hash#merge!
422
+ def default_opts(opts)
423
+ @opts[:hook] = opts[:hook] != nil ? opts[:hook] : true
424
+ @opts[:attach] = opts[:attach] != nil ? opts[:attach] : false
425
+ @opts[:install] = opts[:install] != nil ? opts[:install] : false
426
+ end
427
+ end