ragweed 0.1.7.3 → 0.2.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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