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,456 @@
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
+ if $DEBUG
153
+ puts 'unable to self.continue'
154
+ puts self.get_registers
155
+ end
156
+ retry
157
+ end
158
+ when signal == 0x13 #WIFCONTINUED
159
+ try(:on_continue)
160
+ else
161
+ raise "Unknown signal '#{signal}' recieved: This should not happen - ever."
162
+ end
163
+ end
164
+ return r
165
+ end
166
+
167
+ # these event functions are stubs. Implementations should override these
168
+ def on_attach
169
+ end
170
+
171
+ def on_detach
172
+ end
173
+
174
+ def on_single_step
175
+ #puts Ragweed::Wraposx::ThreadInfo.get(thread).inspect
176
+ end
177
+
178
+ def on_exit(status)
179
+ @exited = true
180
+ end
181
+
182
+ def on_signal(signal)
183
+ @exited = true
184
+ end
185
+
186
+ def on_stop(signal)
187
+ end
188
+
189
+ def on_continue
190
+ end
191
+
192
+ # installs all breakpoints into child process
193
+ # add breakpoints to install via breakpoint_set
194
+ def install_bps
195
+ self.hook if not @hooked
196
+ @breakpoints.each do |k,v|
197
+ v.install
198
+ end
199
+ @installed = true
200
+ end
201
+
202
+ # removes all breakpoints from child process
203
+ def uninstall_bps
204
+ @breakpoints.each do |k,v|
205
+ v.uninstall
206
+ end
207
+ @installed = false
208
+ end
209
+
210
+ # attach to @pid for debugging
211
+ # opts is a hash for automatically firing other functions as an overide for @opts
212
+ # returns 0 on no error
213
+ def attach(opts=@opts)
214
+ r = Ragweed::Wraposx::ptrace(Ragweed::Wraposx::Ptrace::ATTACH,@pid,0,0)
215
+ # Ragweed::Wraposx::ptrace(Ragweed::Wraposx::Ptrace::CONTINUE,@pid,1,0)
216
+ @attached = true
217
+ on_attach
218
+ self.hook(opts) if (opts[:hook] and not @hooked)
219
+ self.install_bps if (opts[:install] and not @installed)
220
+ return r
221
+ end
222
+
223
+ # remove breakpoints and release child
224
+ # opts is a hash for automatically firing other functions as an overide for @opts
225
+ # returns 0 on no error
226
+ def detach(opts=@opts)
227
+ self.uninstall_bps if @installed
228
+ r = Ragweed::Wraposx::ptrace(Ragweed::Wraposx::Ptrace::DETACH,@pid,0,Ragweed::Wraposx::Wait::UNTRACED)
229
+ @attached = false
230
+ on_detach
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
+ # XXX watch this space for an object to hold this information
419
+ # Return a range via mapping name
420
+ def get_mapping_by_name name, exact = true
421
+ ret = []
422
+ IO.popen("vmmap -interleaved #{@pid}") do |pipe|
423
+ pipe.each_line do |line|
424
+ next if pipe.lineno < 5
425
+ break if line == "==== Legend\n"
426
+ rtype, saddr, eaddr, sz, perms, sm, purpose =
427
+ line.scan(/^([[:graph:]]+(?:\s[[:graph:]]+)?)\s+([[:xdigit:]]+)-([[:xdigit:]]+)\s+\[\s+([[:digit:]]+[A-Z])\s*\]\s+([-rwx\/]+)\s+SM=(COW|PRV|NUL|ALI|SHM|ZER|S\/A)\s+(.*)$/).
428
+ first
429
+ if exact && (rtype == name || purpose == name)
430
+ ret << [saddr, eaddr].map{|x| x.to_i(16)}
431
+ elsif rtype.match name || purpose.match name
432
+ ret << [saddr, eaddr].map{|x| x.to_i(16)}
433
+ end
434
+ end
435
+ end
436
+ ret
437
+ end
438
+
439
+ def get_stack_ranges
440
+ get_mapping_by_name "Stack", false
441
+ end
442
+
443
+ def get_heap_ranges
444
+ get_mapping_by_name "MALLOC", false
445
+ end
446
+
447
+ private
448
+
449
+ # sets instance automagic options to sane(ish) defaults when not given
450
+ # FIXME - I should use Hash#merge!
451
+ def default_opts(opts)
452
+ @opts[:hook] = opts[:hook] != nil ? opts[:hook] : true
453
+ @opts[:attach] = opts[:attach] != nil ? opts[:attach] : false
454
+ @opts[:install] = opts[:install] != nil ? opts[:install] : false
455
+ end
456
+ end