ragweed 0.1.7.3 → 0.2.0.pre1

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.
@@ -1,8 +1,5 @@
1
1
  require ::File.join(::File.dirname(__FILE__),'wrap32')
2
2
 
3
- # I am not particularly proud of this code, which I basically debugged
4
- # into existence, but it does work.
5
-
6
3
  # Debugger class for win32
7
4
  # You can use this class in 2 ways:
8
5
  #
@@ -15,47 +12,58 @@ require ::File.join(::File.dirname(__FILE__),'wrap32')
15
12
  class Ragweed::Debugger32
16
13
  include Ragweed
17
14
 
18
- # Breakpoint class. Handles the actual setting, removal and triggers for
19
- # breakpoints.
20
- # no user servicable parts.
15
+ ## Breakpoint class. Handles the actual setting,
16
+ ## removal and triggers for breakpoints.
17
+ ## no user servicable parts.
21
18
  class Breakpoint
19
+
22
20
  INT3 = 0xCC
23
- attr_accessor :orig
24
- attr_accessor :bpid
25
21
 
26
- def initialize(bp, ip, callable)
27
- @@bpid ||= 0
28
- @bp = bp
22
+ attr_accessor :orig, :deferred, :addr
23
+
24
+ def initialize(process, ip, def_status, callable)
25
+ @process = process
29
26
  @addr = ip
30
27
  @callable = callable
31
- @bpid = (@@bpid += 1)
28
+ @deferred = def_status
29
+ @orig = 0
32
30
  end
31
+
32
+ def addr; @addr; end
33
33
 
34
34
  def install
35
- @orig = process.read8(@addr)
36
- if(@orig != INT3)
37
- process.write8(@addr, INT3)
38
- Ragweed::Wrap32::flush_instruction_cache(@bp.process.handle)
39
- end
35
+ if @addr == 0 or @deferred == true
36
+ return
37
+ end
38
+
39
+ o = @process.read8(@addr)
40
+
41
+ if(orig != INT3)
42
+ @orig = o
43
+ @process.write8(@addr, INT3)
44
+ Ragweed::Wrap32::flush_instruction_cache(@process.handle)
45
+ end
46
+ end
47
+
48
+ def deferred_install(h, base)
49
+ @addr = @process.get_deferred_proc_remote(@addr, h, base)
50
+ self.install
51
+ return @addr
40
52
  end
41
53
 
42
54
  def uninstall
43
55
  if(@orig != INT3)
44
- process.write8(@addr, @orig)
45
- Ragweed::Wrap32::flush_instruction_cache(@bp.process.handle)
56
+ @process.write8(@addr, @orig)
57
+ Ragweed::Wrap32::flush_instruction_cache(@process.handle)
46
58
  end
47
59
  end
48
60
 
49
61
  def call(*args); @callable.call(*args); end
50
- def method_missing(meth, *args); @bp.send(meth, *args); end
51
- end
62
+ end ## End Breakpoint class
52
63
 
53
- # Get a handle to the process so you can mess with it.
64
+ ## Get a handle to the process so you can mess with it.
54
65
  def process; @p; end
55
66
 
56
- # This is how you normally create a debug instance: with a regex
57
- # on the image name of the process.
58
- # d = Debugger.find_by_regex /notepad/i
59
67
  def self.find_by_regex(rx)
60
68
  Ragweed::Wrap32::all_processes do |p|
61
69
  if p.szExeFile =~ rx
@@ -64,179 +72,201 @@ class Ragweed::Debugger32
64
72
  end
65
73
  nil
66
74
  end
67
-
68
- # If you want to get one by hand, and not by Debugger#find_by_regex,
69
- # pass this either a PID or a Process object.
75
+
70
76
  def initialize(p)
71
- # grab debug privilege at least once.
72
- @@token ||= Ragweed::Wrap32::ProcessToken.new.grant("seDebugPrivilege")
77
+ ## grab debug privilege at least once
78
+ @@token ||= Ragweed::Wrap32::ProcessToken.new.grant('seDebugPrivilege')
73
79
 
74
80
  p = Process.new(p) if p.kind_of? Numeric
75
81
  @p = p
76
82
  @steppers = []
77
83
  @handled = Ragweed::Wrap32::ContinueCodes::UNHANDLED
78
- @first = true
79
84
  @attached = false
80
85
 
81
- # for setting breakpoints: inject a thread to do GetProcAddress,
82
- # cache the result.
83
- @resolve = Hash.new do |h, k|
84
- if k.kind_of? String
85
- ip = @p.get_proc_remote(k)
86
- else
87
- ip = k
88
- end
89
- raise "no such location" if ip == 0 or ip == 0xFFFFFFFF
90
- h[k] = ip
91
- end
86
+ ## breakpoints is a hash with a key being the breakpoint
87
+ ## addr and the value being a Breakpoint class
88
+ @breakpoints = Hash.new
92
89
 
93
- # the magic initializer here just makes sure the Hash always
94
- # contains arrays with convenience methods.
95
- @breakpoints = Hash.new do |h, k|
96
- bps = Array.new
97
- def bps.call(*args); each {|bp| bp.call(*args)}; end
98
- def bps.install; each {|bp| bp.install}; end
99
- def bps.uninstall; each {|bp| bp.uninstall}; end
100
- h[k] = bps
101
- end
90
+ ## We want to ignore ntdll!DbgBreakPoint
91
+ @ntdll_dbg_break_point = @p.get_proc_remote('ntdll!DbgBreakPoint')
102
92
  end
103
-
104
- # single-step the thread (by TID). "callable" is something that honors
105
- # .call, like a Proc. In a dubious design decision: the "handle" to the
106
- # single stepper is the Proc object itself. See Debugger#on_breakpoint
107
- # for an example of how to use this.
93
+
94
+ ## single-step the thread (by TID). "callable" is something that honors
95
+ ## .call, like a Proc. In a dubious design decision: the "handle" to the
96
+ ## single stepper is the Proc object itself. See Debugger#on_breakpoint
97
+ ## for an example of how to use this.
108
98
  def step(tid, callable)
109
99
  if @steppers.empty?
110
100
  Ragweed::Wrap32::open_thread(tid) do |h|
111
- ctx = Ragweed::Wrap32::ThreadContext.get(h)
101
+ ctx = Ragweed::Wrap32::get_thread_context(h)
112
102
  ctx.single_step(true)
113
- ctx.set(h)
103
+ Ragweed::Wrap32::set_thread_context(h, ctx)
114
104
  end
115
105
  end
116
106
  @steppers << callable
117
107
  end
118
108
 
119
- # turn off single-stepping for one callable (you can have more than one
120
- # at a time). In other words, when you pass a Proc to Debugger#step, save
121
- # it somewhere, and later pass it to "unstep" to turn it off.
109
+ ## turn off single-stepping for one callable (you can have more than one
110
+ ## at a time). In other words, when you pass a Proc to Debugger#step, save
111
+ ## it somewhere, and later pass it to "unstep" to turn it off.
122
112
  def unstep(tid, callable)
123
113
  @steppers = @steppers.reject {|x| x == callable}
124
114
  if @steppers.empty?
125
115
  Ragweed::Wrap32::open_thread(tid) do |h|
126
- ctx = Ragweed::Wrap32::ThreadContext.get(h)
116
+ ctx = Ragweed::Wrap32::get_thread_context(h)
127
117
  ctx.single_step(false)
128
- ctx.set(h)
118
+ Ragweed::Wrap32::set_thread_context(h, ctx)
129
119
  end
130
120
  end
131
121
  end
132
122
 
133
- # convenience: either from a TID or a BreakpointEvent, get the thread context.
123
+ ## convenience: either from a TID or a BreakpointEvent, get the thread context.
134
124
  def context(tid_or_event)
135
125
  if not tid_or_event.kind_of? Numeric
136
126
  tid = tid_or_event.tid
137
127
  else
138
128
  tid = tid_or_event
139
129
  end
140
- Ragweed::Wrap32::open_thread(tid) {|h| Ragweed::Wrap32::ThreadContext.get(h)}
130
+ Ragweed::Wrap32::open_thread(tid) { |h| Ragweed::Wrap32::get_thread_context(h) }
141
131
  end
142
132
 
143
- # set a breakpoint given an address, which can also be a string in the form
144
- # "module!function", as in, "user32!SendMessageW". Be aware that the symbol
145
- # lookup takes place in an injected thread; it's safer to use literal addresses.
133
+ ## set a breakpoint given an address, which can also be a string in the form
134
+ ## "module!function", as in, "user32!SendMessageW". Be aware that the symbol
135
+ ## lookup takes place in an injected thread; it's safer to use literal addresses
136
+ ## when possible.
146
137
  #
147
- # to handle the breakpoint, pass a block to this method, which will be called
148
- # when the breakpoint hits.
138
+ ## to handle the breakpoint, pass a block to this method, which will be called
139
+ ## when the breakpoint hits.
149
140
  #
150
- # breakpoints are always re-set after firing. If you don't want them to be
151
- # re-set, unset them manually.
152
- #
153
- # returns a numeric id that can be used to clear the breakpoint.
141
+ ## breakpoints are always re-set after firing. If you don't want them to be
142
+ ## re-set, unset them manually.
154
143
  def breakpoint_set(ip, callable=nil, &block)
155
144
  if not callable and block_given?
156
145
  callable = block
157
146
  end
158
- ip = @resolve[ip]
159
- @breakpoints[ip] << Breakpoint.new(self, ip, callable)
160
- end
161
147
 
162
- # clear a breakpoint given an id, or clear all breakpoints associated with
163
- # an address.
164
- def breakpoint_clear(ip, bpid=nil)
165
- ip = @resolve[ip]
166
- if not bpid
167
- @breakpoints[ip].uninstall
168
- @breakpoints.delete ip
148
+ def_status = false
149
+
150
+ ## This is usually 'Module!Function' or 'Module!0x1234'
151
+ if @p.is_breakpoint_deferred(ip) == true
152
+ def_status = true
169
153
  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[ip][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 "couldn't find #{ ip }" if not found
184
- @breakpoints[ip].delete_at(found) if found
154
+ def_status = false
155
+ ip = @p.get_proc_remote(ip)
185
156
  end
157
+
158
+ ## If we cant immediately set the breakpoint
159
+ ## mark it as deferred and wait till later
160
+ ## Sometimes *_proc_remote() will return the
161
+ ## name indicating failure (just in case)
162
+ if ip == 0 or ip == 0xFFFFFFFF or ip.kind_of? String
163
+ def_status = true
164
+ else
165
+ def_status = false
166
+ end
167
+
168
+ ## Dont want duplicate breakpoint objects
169
+ @breakpoints.each_key { |k| if k == ip then return end }
170
+ bp = Breakpoint.new(@p, ip, def_status, callable)
171
+ @breakpoints[ip] = bp
172
+ end
173
+
174
+ ## Clear a breakpoint by ip
175
+ def breakpoint_clear(ip)
176
+ bp = @breakpoints[ip]
177
+
178
+ if bp.nil?
179
+ return nil
180
+ end
181
+
182
+ bp.uninstall
183
+ @breakpoints.delete(ip)
186
184
  end
187
185
 
188
- # handle a breakpoint event:
189
- # call handlers for the breakpoint, step past and reset it.
186
+ ## handle a breakpoint event:
187
+ ## call handlers for the breakpoint, step past and reset it.
190
188
  def on_breakpoint(ev)
191
- if @first
192
-
193
- # DbgUiRemoteInjectWhatever actually injects a thread into the
194
- # target process, which explicitly issues an INT3. We never care
195
- # about this breakpoint right now.
196
- @first = false
197
- else
198
189
  ctx = context(ev)
199
190
  eip = ev.exception_address
200
-
201
- # call handlers, then clear the breakpoint so we can execute the
202
- # real instruction.
203
- @breakpoints[eip].first.uninstall
191
+
192
+ if eip == @ntdll_dbg_break_point
193
+ return
194
+ end
195
+
196
+ @breakpoints[eip].uninstall
197
+
198
+ ## Call the block passed to breakpoint_set
199
+ ## which may have been passed through hook()
204
200
  @breakpoints[eip].call(ev, ctx)
205
201
 
206
- # single step past the instruction...
202
+ ## single step past the instruction...
207
203
  step(ev.tid, (onestep = lambda do |ev, ctx|
208
204
  if ev.exception_address != eip
209
-
210
- # ... then re-install the breakpoint ...
211
- if not @breakpoints[eip].empty?
212
- @breakpoints[eip].first.install
205
+ ## ... then re-install the breakpoint ...
206
+ if not @breakpoints[eip].nil?
207
+ @breakpoints[eip].install
213
208
  end
214
-
215
- # ... and stop single-stepping.
209
+ ## ... and stop single-stepping.
216
210
  unstep(ev.tid, onestep)
217
211
  end
218
- end))
212
+ end))
219
213
 
220
- # put execution back where it's supposed to be...
214
+ ## Put execution back where it's supposed to be...
221
215
  Ragweed::Wrap32::open_thread(ev.tid) do |h|
222
216
  ctx = context(ev)
223
- ctx.eip = eip # eip was ev.exception_address
224
- ctx.set(h)
217
+ ctx.eip = eip ## eip was ev.exception_address
218
+ Ragweed::Wrap32::set_thread_context(h, ctx)
225
219
  end
226
- end
227
220
 
228
- # tell the target to stop handling this event
221
+ ## Tell the target to stop handling this event
229
222
  @handled = Ragweed::Wrap32::ContinueCodes::CONTINUE
230
223
  end
231
224
 
232
- # handle a single-step event
225
+ ## FIX: this method should be a bit more descriptive in its naming
226
+ def get_dll_name(ev)
227
+ name = Ragweed::Wrap32::get_mapped_filename(@p.handle, ev.base_of_dll, 256)
228
+ name.gsub!(/[\n]+/,'')
229
+ name.gsub!(/[^\x21-\x7e]/,'')
230
+ i = name.index('0')
231
+ i ||= name.size
232
+ return name[0, i]
233
+ end
234
+
235
+ def on_load_dll(ev)
236
+ dll_name = get_dll_name(ev)
237
+
238
+ @breakpoints.each_pair do |k,bp|
239
+ if !bp.addr.kind_of?String
240
+ next
241
+ end
242
+
243
+ m,f = bp.addr.split('!')
244
+
245
+ if dll_name =~ /#{m}/i
246
+ deferred = bp.deferred
247
+
248
+ if deferred == true
249
+ bp.deferred = false
250
+ end
251
+
252
+ new_addr = bp.deferred_install(ev.file_handle, ev.base_of_dll)
253
+
254
+ if !new_addr.nil?
255
+ @breakpoints[new_addr] = bp.dup
256
+ @breakpoints.delete(k)
257
+ end
258
+ end
259
+ end
260
+ end
261
+
262
+ ## handle a single-step event
233
263
  def on_single_step(ev)
234
264
  ctx = context(ev)
235
265
  Ragweed::Wrap32::open_thread(ev.tid) do |h|
236
- # re-enable the trap flag before our handler, which may
237
- # choose to disable it.
266
+ ## re-enable the trap flag before our handler,
267
+ ## which may choose to disable it.
238
268
  ctx.single_step(true)
239
- ctx.set(h)
269
+ Ragweed::Wrap32.set_thread_context(h, ctx)
240
270
  end
241
271
 
242
272
  @steppers.each {|s| s.call(ev, ctx)}
@@ -244,18 +274,31 @@ class Ragweed::Debugger32
244
274
  @handled = Ragweed::Wrap32::ContinueCodes::CONTINUE
245
275
  end
246
276
 
247
- # this is sort of insane but most of my programs are just
248
- # debug loops, so if you don't do this, they just hang when
249
- # the target closes.
277
+ ## This is sort of insane but most of my programs are just
278
+ ## debug loops, so if you don't do this, they just hang when
279
+ ## the target closes.
250
280
  def on_exit_process(ev)
251
281
  exit(1)
252
282
  end
253
283
 
254
- # Read through me to see all the random events you can hook in
255
- # a subclass.
256
- #
257
- # call me directly if you want to handle multiple debugger instances,
258
- # i guess.
284
+ ## TODO: Implement each of these
285
+ def on_create_process(ev) end
286
+ def on_create_thread(ev) end
287
+ def on_exit_thread(ev) end
288
+ def on_output_debug_string(ev) end
289
+ def on_rip(ev) end
290
+ def on_unload_dll(ev) end
291
+ def on_alignment(ev) end
292
+ def on_bounds(ev) end
293
+ def on_divide_by_zero(ev) end
294
+ def on_int_overflow(ev) end
295
+ def on_invalid_handle(ev) end
296
+ def on_priv_instruction(ev) end
297
+ def on_stack_overflow(ev) end
298
+ def on_invalid_disposition(ev) end
299
+
300
+ ## Read through me to see all the random events
301
+ ## you can hook in a subclass.
259
302
  def wait
260
303
  self.attach() if not @attached
261
304
 
@@ -309,30 +352,30 @@ class Ragweed::Debugger32
309
352
  @handled = Ragweed::Wrap32::ContinueCodes::UNHANDLED
310
353
  end
311
354
 
312
- # debug loop.
355
+ ## Debug loop
313
356
  def loop
314
357
  while true
315
358
  wait
316
359
  end
317
360
  end
318
361
 
319
- # this is called implicitly by Debugger#wait.
320
- # Attaches to the child process for debugging
362
+ ## This is called implicitly by Debugger#wait.
363
+ ## Attaches to the child process for debugging
321
364
  def attach
322
365
  Ragweed::Wrap32::debug_active_process(@p.pid)
323
366
  Ragweed::Wrap32::debug_set_process_kill_on_exit
324
367
  @attached = true
325
- @breakpoints.each do |k, v|
326
- v.install
368
+ @breakpoints.each_pair do |k, bp|
369
+ bp.install
327
370
  end
328
371
  end
329
372
 
330
- # let go of the target.
373
+ ## Let go of the target.
331
374
  def release
332
375
  Ragweed::Wrap32::debug_active_process_stop(@p.pid)
333
376
  @attached = false
334
- @breakpoints.each do |k, v|
335
- v.uninstall
377
+ @breakpoints.each_pair do |k, bp|
378
+ bp.uninstall
336
379
  end
337
380
  end
338
381
  end
@@ -149,13 +149,15 @@ class Ragweed::Debuggerosx
149
149
  rescue Errno::EBUSY
150
150
  # Yes this happens and it's wierd
151
151
  # Not sure it should happen
152
- pp 'unable to self.continue'
153
- pp self.get_registers
152
+ if $DEBUG
153
+ puts 'unable to self.continue'
154
+ puts self.get_registers
155
+ end
154
156
  retry
155
157
  end
156
158
  when signal == 0x13 #WIFCONTINUED
157
159
  try(:on_continue)
158
- else #holy broken stuff batman
160
+ else
159
161
  raise "Unknown signal '#{signal}' recieved: This should not happen - ever."
160
162
  end
161
163
  end
@@ -163,32 +165,28 @@ class Ragweed::Debuggerosx
163
165
  end
164
166
 
165
167
  # 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)
168
+ def on_attach
169
169
  end
170
170
 
171
- def on_sigsegv
172
- pp Ragweed::Wraposx::ThreadContext.get(thread)
173
- pp Ragweed::Wraposx::ThreadInfo.get(thread)
171
+ def on_detach
172
+ end
173
+
174
+ def on_single_step
175
+ #puts Ragweed::Wraposx::ThreadInfo.get(thread).inspect
174
176
  end
175
177
 
176
178
  def on_exit(status)
177
- pp "Exited! (on_exit)"
178
179
  @exited = true
179
180
  end
180
181
 
181
182
  def on_signal(signal)
182
- pp "Exited with signal #{ signal } (on_signal)"
183
183
  @exited = true
184
184
  end
185
185
 
186
186
  def on_stop(signal)
187
- pp "#Stopped with signal #{ signal } (on_stop)"
188
187
  end
189
188
 
190
189
  def on_continue
191
- pp "Continued! (on_continue)"
192
190
  end
193
191
 
194
192
  # installs all breakpoints into child process
@@ -216,6 +214,7 @@ class Ragweed::Debuggerosx
216
214
  r = Ragweed::Wraposx::ptrace(Ragweed::Wraposx::Ptrace::ATTACH,@pid,0,0)
217
215
  # Ragweed::Wraposx::ptrace(Ragweed::Wraposx::Ptrace::CONTINUE,@pid,1,0)
218
216
  @attached = true
217
+ on_attach
219
218
  self.hook(opts) if (opts[:hook] and not @hooked)
220
219
  self.install_bps if (opts[:install] and not @installed)
221
220
  return r
@@ -228,6 +227,7 @@ class Ragweed::Debuggerosx
228
227
  self.uninstall_bps if @installed
229
228
  r = Ragweed::Wraposx::ptrace(Ragweed::Wraposx::Ptrace::DETACH,@pid,0,Ragweed::Wraposx::Wait::UNTRACED)
230
229
  @attached = false
230
+ on_detach
231
231
  self.unhook(opts) if opts[:hook] and @hooked
232
232
  return r
233
233
  end