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.
- data/History.txt +15 -0
- data/README.rdoc +35 -0
- data/README.txt +9 -0
- data/Rakefile +30 -0
- data/examples/hittracertux.rb +48 -0
- data/examples/hittracerx.rb +63 -0
- data/examples/hook_notepad.rb +9 -0
- data/examples/snicker.rb +183 -0
- data/examples/tux-example.rb +23 -0
- data/lib/ragweed/arena.rb +55 -0
- data/lib/ragweed/blocks.rb +128 -0
- data/lib/ragweed/debugger32.rb +338 -0
- data/lib/ragweed/debuggerosx.rb +419 -0
- data/lib/ragweed/debuggertux.rb +347 -0
- data/lib/ragweed/detour.rb +223 -0
- data/lib/ragweed/ptr.rb +48 -0
- data/lib/ragweed/rasm/isa.rb +1046 -0
- data/lib/ragweed/rasm/util.rb +26 -0
- data/lib/ragweed/rasm.rb +53 -0
- data/lib/ragweed/sbuf.rb +197 -0
- data/lib/ragweed/trampoline.rb +103 -0
- data/lib/ragweed/utils.rb +87 -0
- data/lib/ragweed/wrap32/debugging.rb +163 -0
- data/lib/ragweed/wrap32/device.rb +49 -0
- data/lib/ragweed/wrap32/event.rb +50 -0
- data/lib/ragweed/wrap32/hooks.rb +23 -0
- data/lib/ragweed/wrap32/overlapped.rb +46 -0
- data/lib/ragweed/wrap32/process.rb +506 -0
- data/lib/ragweed/wrap32/process_token.rb +59 -0
- data/lib/ragweed/wrap32/thread_context.rb +208 -0
- data/lib/ragweed/wrap32/winx.rb +16 -0
- data/lib/ragweed/wrap32/wrap32.rb +526 -0
- data/lib/ragweed/wrap32.rb +53 -0
- data/lib/ragweed/wraposx/constants.rb +101 -0
- data/lib/ragweed/wraposx/kernelerrorx.rb +147 -0
- data/lib/ragweed/wraposx/region_info.rb +244 -0
- data/lib/ragweed/wraposx/thread_context.rb +203 -0
- data/lib/ragweed/wraposx/thread_info.rb +213 -0
- data/lib/ragweed/wraposx/wraposx.rb +376 -0
- data/lib/ragweed/wraposx.rb +53 -0
- data/lib/ragweed/wraptux/constants.rb +68 -0
- data/lib/ragweed/wraptux/threads.rb +3 -0
- data/lib/ragweed/wraptux/wraptux.rb +76 -0
- data/lib/ragweed/wraptux.rb +53 -0
- data/lib/ragweed.rb +84 -0
- data/spec/ragweed_spec.rb +7 -0
- data/spec/spec_helper.rb +16 -0
- data/tasks/ann.rake +80 -0
- data/tasks/bones.rake +20 -0
- data/tasks/gem.rake +201 -0
- data/tasks/git.rake +40 -0
- data/tasks/notes.rake +27 -0
- data/tasks/post_load.rake +34 -0
- data/tasks/rdoc.rake +51 -0
- data/tasks/rubyforge.rake +55 -0
- data/tasks/setup.rb +292 -0
- data/tasks/spec.rake +54 -0
- data/tasks/svn.rake +47 -0
- data/tasks/test.rake +40 -0
- data/tasks/zentest.rake +36 -0
- data/test/test_ragweed.rb +0 -0
- 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
|