iZsh-ragweed 0.1.8

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 +29 -0
  2. data/README.rdoc +35 -0
  3. data/README.txt +9 -0
  4. data/Rakefile +30 -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.rb +84 -0
  11. data/lib/ragweed/arena.rb +55 -0
  12. data/lib/ragweed/blocks.rb +128 -0
  13. data/lib/ragweed/debugger32.rb +338 -0
  14. data/lib/ragweed/debuggerosx.rb +419 -0
  15. data/lib/ragweed/debuggertux.rb +345 -0
  16. data/lib/ragweed/detour.rb +223 -0
  17. data/lib/ragweed/ptr.rb +48 -0
  18. data/lib/ragweed/rasm.rb +53 -0
  19. data/lib/ragweed/rasm/isa.rb +1046 -0
  20. data/lib/ragweed/rasm/util.rb +26 -0
  21. data/lib/ragweed/sbuf.rb +197 -0
  22. data/lib/ragweed/trampoline.rb +103 -0
  23. data/lib/ragweed/utils.rb +88 -0
  24. data/lib/ragweed/wrap32.rb +53 -0
  25. data/lib/ragweed/wrap32/debugging.rb +163 -0
  26. data/lib/ragweed/wrap32/device.rb +49 -0
  27. data/lib/ragweed/wrap32/event.rb +50 -0
  28. data/lib/ragweed/wrap32/hooks.rb +23 -0
  29. data/lib/ragweed/wrap32/overlapped.rb +46 -0
  30. data/lib/ragweed/wrap32/process.rb +506 -0
  31. data/lib/ragweed/wrap32/process_token.rb +59 -0
  32. data/lib/ragweed/wrap32/thread_context.rb +208 -0
  33. data/lib/ragweed/wrap32/winx.rb +16 -0
  34. data/lib/ragweed/wrap32/wrap32.rb +526 -0
  35. data/lib/ragweed/wraposx.rb +53 -0
  36. data/lib/ragweed/wraposx/constants.rb +112 -0
  37. data/lib/ragweed/wraposx/kernelerrorx.rb +147 -0
  38. data/lib/ragweed/wraposx/region_info.rb +250 -0
  39. data/lib/ragweed/wraposx/thread_context.rb +203 -0
  40. data/lib/ragweed/wraposx/thread_info.rb +225 -0
  41. data/lib/ragweed/wraposx/wraposx.rb +376 -0
  42. data/lib/ragweed/wraptux.rb +53 -0
  43. data/lib/ragweed/wraptux/constants.rb +68 -0
  44. data/lib/ragweed/wraptux/threads.rb +7 -0
  45. data/lib/ragweed/wraptux/wraptux.rb +76 -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
@@ -0,0 +1,345 @@
1
+ ## Modeled after wraposx written by tduehr
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
+ include Ragweed
16
+
17
+ attr_reader :pid
18
+ attr_reader :status
19
+ attr_reader :exited
20
+ attr_accessor :breakpoints
21
+
22
+ ## Class to handle installing/uninstalling breakpoints
23
+ class Breakpoint
24
+
25
+ INT3 = 0xCC ## obviously x86 specific debugger here
26
+
27
+ attr_accessor :orig
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
+ @installed = false
42
+ @orig = 0
43
+ @bpid = (@@bpid += 1)
44
+ end ## breakpoint initialize
45
+
46
+ ## Install a breakpoint (replace instruction with int3)
47
+ def install
48
+ ## Replace the original instruction with an int3
49
+ @orig = Ragweed::Wraptux::ptrace(Ragweed::Wraptux::Ptrace::PEEK_TEXT, $ppid, @addr, 0) ## Backup the old inst
50
+ if @orig != -1
51
+ new = (@orig & ~0xff) | INT3;
52
+ Ragweed::Wraptux::ptrace(Ragweed::Wraptux::Ptrace::POKE_TEXT, $ppid, @addr, new)
53
+ @installed = true
54
+ else
55
+ @installed = false
56
+ end
57
+ end
58
+
59
+ ## Uninstall the breakpoint
60
+ def uninstall
61
+ ## Put back the original instruction
62
+ if @orig != INT3
63
+ Ragweed::Wraptux::ptrace(Ragweed::Wraptux::Ptrace::POKE_TEXT, $ppid, @addr, @orig)
64
+ @installed = false
65
+ end
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 ## Breakpoint Class
72
+
73
+ ## init object
74
+ ## p: pid of process to be debugged
75
+ ## opts: default options for automatically doing things (attach and install)
76
+ def initialize(pid,opts={}) ## Debuggertux Class
77
+ @pid = pid
78
+
79
+ ## FIXME - globals are bad...
80
+ $ppid = @pid ## temporary work around
81
+ @opts = opts
82
+
83
+ default_opts(opts)
84
+ @installed = false
85
+ @attached = false
86
+
87
+ ## Store all breakpoints in this hash
88
+ @breakpoints = Hash.new do |h, k|
89
+ bps = Array.new
90
+ def bps.call(*args); each {|bp| bp.call(*args)}; end
91
+ def bps.install; each {|bp| bp.install}; end
92
+ def bps.uninstall; each {|bp| bp.uninstall}; end
93
+ def bps.orig; each {|bp| dp.orig}; end
94
+ h[k] = bps
95
+ end
96
+ @opts.each {|k, v| try(k) if v}
97
+ end
98
+
99
+ ## This is crude!
100
+ def self.find_by_regex(rx)
101
+ a = Dir.entries("/proc/")
102
+ a.delete_if do |x| x == '.' end
103
+ a.delete_if do |x| x == '..' end
104
+ a.delete_if do |x| x =~ /[a-z]/ end
105
+ a.each do |x|
106
+ f = File.read("/proc/#{x}/cmdline")
107
+ if f =~ rx
108
+ return x
109
+ end
110
+ end
111
+ end
112
+
113
+ def install_bps
114
+ @breakpoints.each do |k,v|
115
+ v.install
116
+ end
117
+ @installed = true
118
+ end
119
+
120
+ def uninstall_bps
121
+ @breakpoints.each do |k,v|
122
+ v.uninstall
123
+ end
124
+ @installed = false
125
+ end
126
+
127
+ ## Attach calls install_bps so dont forget to call breakpoint_set
128
+ ## BEFORE attach or explicitly call install_bps
129
+ def attach(opts=@opts)
130
+ Wraptux::ptrace(Ragweed::Wraptux::Ptrace::ATTACH, @pid, 0, 0)
131
+ @attached = true
132
+ self.install_bps if (opts[:install] and not @installed)
133
+ end
134
+
135
+ def continue
136
+ on_continue
137
+ Wraptux::ptrace(Ragweed::Wraptux::Ptrace::CONTINUE, @pid, 0, 0)
138
+ end
139
+
140
+ def detach
141
+ on_detach
142
+ Wraptux::ptrace(Ragweed::Wraptux::Ptrace::DETACH, @pid, 0, 0)
143
+ end
144
+
145
+ def stepp
146
+ on_stepp
147
+ ret = Wraptux::ptrace(Ragweed::Wraptux::Ptrace::STEP, @pid, 1, 0)
148
+ end
149
+
150
+ ## Adds a breakpoint to be installed
151
+ ## ip: Insertion point
152
+ ## name: name of breakpoint
153
+ ## callable: object to .call at breakpoint
154
+ def breakpoint_set(ip, name="", callable=nil, &block)
155
+ if not callable and block_given?
156
+ callable = block
157
+ end
158
+ @breakpoints[ip] << Breakpoint.new(self, ip, callable, name)
159
+ end
160
+
161
+ ## remove breakpoint with id bpid at insertion point or
162
+ ## remove all breakpoints at insertion point if bpid not given
163
+ ## ip: Insertion point
164
+ ## bpid: id of breakpoint to be removed
165
+ def breakpoint_clear(ip, bpid=nil)
166
+ if not bpid
167
+ @breakpoints[ip].uninstall
168
+ @breakpoints[ip].delete ip
169
+ else
170
+ found = nil
171
+ @breakpoints[ip].each_with_index do |bp, i|
172
+ if bp.bpid == bpid
173
+ found = i
174
+ if bp.orig != Breakpoint::INT3
175
+ if @breakpoints[op][i+1]
176
+ @breakpoints[ip][i + 1].orig = bp.orig
177
+ else
178
+ bp.uninstall
179
+ end
180
+ end
181
+ end
182
+ end
183
+ raise "could not find bp ##{bpid} at ##{ip}" if not found
184
+ @breakpoints[ip].delete_at(found) if found
185
+ end
186
+ end
187
+
188
+ ##loop for wait()
189
+ ##times: the number of wait calls to make
190
+ ## if nil loop will continue indefinitely
191
+ def loop(times=nil)
192
+ if times.kind_of? Numeric
193
+ times.times do
194
+ self.wait
195
+ end
196
+ elsif times.nil?
197
+ self.wait while not @exited
198
+ end
199
+ end
200
+
201
+ ## This wait must be smart, it has to wait for a signal
202
+ ## when SIGTRAP is received we need to see if one of our
203
+ ## breakpoints has fired. If it has then execute the block
204
+ ## originally stored with it. If its a different signal,
205
+ ## then process it accordingly and move on
206
+ def wait(opts = 0)
207
+ r = Wraptux::waitpid(@pid,opts)
208
+ status = r[1]
209
+ wstatus = status & 0x7f
210
+ signal = status >> 8
211
+ found = false
212
+ if r[0] != 0 ## Check the ret
213
+ case ## FIXME - I need better logic (use Signal module)
214
+ when wstatus == 0 ##WIFEXITED
215
+ @exited = true
216
+ self.on_exit
217
+ when wstatus != 0x7f ##WIFSIGNALED
218
+ @exited = false
219
+ self.on_signal
220
+ when signal == Wraptux::Signal::SIGINT
221
+ self.continue
222
+ when signal == Wraptux::Signal::SIGSEGV
223
+ self.on_segv
224
+ when signal == Wraptux::Signal::SIGILL
225
+ self.on_illegalinst
226
+ when signal == Wraptux::Signal::SIGTRAP
227
+ ## Check if EIP matches a breakpoint we have set
228
+ r = self.get_registers
229
+ eip = r[:eip]
230
+ eip -= 1
231
+ if @breakpoints.has_key?(eip)
232
+ found = true
233
+ self.on_breakpoint
234
+ else
235
+ puts "We got a SIGTRAP but not at our breakpoint... continuing"
236
+ end
237
+ self.continue
238
+ when signal == Wraptux::Signal::SIGCONT
239
+ self.continue
240
+ when signal == Wraptux::Signal::SIGSTOP
241
+ self.continue
242
+ else
243
+ raise "Add more signal handlers (##{signal})"
244
+ end
245
+ end
246
+ end
247
+
248
+ ## Return an array of thread PIDs
249
+ def self.threads(pid)
250
+ a = Dir.entries("/proc/#{pid}/task/")
251
+ a.delete_if do |x| x == '.' end
252
+ a.delete_if do |x| x == '..' end
253
+ end
254
+
255
+ ## Gets the registers for the given process
256
+ def get_registers
257
+ size = Wraptux::SIZEOFLONG * 17
258
+ regs = Array.new(size)
259
+ regs = regs.to_ptr
260
+ regs.struct!('LLLLLLLLLLLLLLLLL', :ebx,:ecx,:edx,:esi,:edi,:ebp,:eax,:xds,:xes,:xfs,:xgs,:orig_eax,:eip,:xcs,:eflags,:esp,:xss)
261
+ Wraptux::ptrace(Ragweed::Wraptux::Ptrace::GETREGS, @pid, 0, regs.to_i)
262
+ return regs
263
+ end
264
+
265
+ ## Sets registers for the given process
266
+ def set_registers(r)
267
+ Wraptux::ptrace(Ragweed::Wraptux::Ptrace::SETREGS, @pid, 0, r.to_i)
268
+ end
269
+
270
+ ## Here we need to do something about the bp
271
+ ## we just hit. We have a block to execute
272
+ def on_breakpoint
273
+ r = self.get_registers
274
+ eip = r[:eip]
275
+ eip -= 1
276
+
277
+ ## Call the block associated with the breakpoint
278
+ @breakpoints[eip].call(r, self)
279
+
280
+ if @breakpoints[eip].first.installed?
281
+ @breakpoints[eip].first.uninstall
282
+ r[:eip] = eip
283
+ set_registers(r)
284
+ stepp
285
+ ## ptrace peektext returns -1 upon reinstallation of bp without calling
286
+ ## waitpid() if that occurs the breakpoint cannot be reinstalled
287
+ Wraptux::waitpid(@pid, 0)
288
+ @breakpoints[eip].first.install
289
+ end
290
+ end
291
+
292
+ def print_regs
293
+ regs = self.get_registers
294
+ puts "eip %08x" % regs[:eip]
295
+ puts "edi %08x" % regs[:esi]
296
+ puts "edi %08x" % regs[:edi]
297
+ puts "esp %08x" % regs[:esp]
298
+ puts "eax %08x" % regs[:eax]
299
+ puts "ebx %08x" % regs[:ebx]
300
+ puts "ecx %08x" % regs[:ecx]
301
+ puts "edx %08x" % regs[:edx]
302
+ end
303
+
304
+ def on_exit
305
+ #puts "process exited"
306
+ end
307
+
308
+ def on_illegalinst
309
+ #puts "illegal instruction"
310
+ exit
311
+ end
312
+
313
+ def on_attach
314
+ #puts "attached to process"
315
+ end
316
+
317
+ def on_detach
318
+ #puts "process detached"
319
+ end
320
+
321
+ def on_continue
322
+ #puts "process continued"
323
+ end
324
+
325
+ def on_stopped
326
+ #puts "process stopped"
327
+ end
328
+
329
+ def on_signal
330
+ #puts "process received signal"
331
+ end
332
+
333
+ def on_stepp
334
+ #puts "single stepping"
335
+ end
336
+
337
+ def on_segv
338
+ print_regs
339
+ exit
340
+ end
341
+
342
+ def default_opts(opts)
343
+ @opts = @opts.merge(opts)
344
+ end
345
+ end