Class: Ragweed::Debugger32

Inherits:
Object show all
Includes:
Ragweed
Defined in:
lib/ragweed/debugger32.rb,
lib/ragweed/wrap32/hooks.rb

Overview

Debugger class for win32 You can use this class in 2 ways:

(1) You can create instances of Debugger and use them to set and handle

breakpoints.

(2) If you want to do more advanced event handling, you can subclass from

debugger and define your own on_whatever events. If you handle an event
that Debugger already handles, call "super", too.

Defined Under Namespace

Classes: Breakpoint

Constant Summary

Constants included from Ragweed

LIBPATH, PATH, VERSION

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Ragweed

libpath, path, require_all_libs_relative_to, require_os_libs_relative_to, version

Constructor Details

#initialize(p) ⇒ Debugger32

Returns a new instance of Debugger32.



81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/ragweed/debugger32.rb', line 81

def initialize(p)
  # grab debug privilege at least once
  @@token ||= Ragweed::Wrap32::ProcessToken.new.grant('seDebugPrivilege')

  p = Process.new(p) if p.kind_of? Numeric
  @p = p
  @steppers = []
  @handled = Ragweed::Wrap32::ContinueCodes::UNHANDLED
  @attached = false

  ## breakpoints is a hash with a key being the breakpoint
  ## addr and the value being a Breakpoint class
  @breakpoints = Hash.new

  ## We want to ignore ntdll!DbgBreakPoint 
  @ntdll_dbg_break_point = @p.get_proc_remote('ntdll!DbgBreakPoint')
end

Instance Attribute Details

#eventObject (readonly)

This will preserve the last event seen, but as read only useful if you want to pass around the ragweed object after an event has occured (post-mortem crash analysis)



18
19
20
# File 'lib/ragweed/debugger32.rb', line 18

def event
  @event
end

Class Method Details

.find_by_regex(rx) ⇒ Object



72
73
74
75
76
77
78
79
# File 'lib/ragweed/debugger32.rb', line 72

def self.find_by_regex(rx)
  Ragweed::Wrap32::all_processes do |p|
    if p.szExeFile =~ rx
      return self.new(p.th32ProcessID)
    end
  end
  nil
end

Instance Method Details

#attachObject

This is called implicitly by Debugger#wait. Attaches to the child process for debugging



400
401
402
403
404
405
406
407
408
# File 'lib/ragweed/debugger32.rb', line 400

def attach
  Ragweed::Wrap32::debug_active_process(@p.pid)
  Ragweed::Wrap32::debug_set_process_kill_on_exit
  @attached = true
  try(:on_attach)
  @breakpoints.each_pair do |k, bp|
    bp.install
  end
end

#breakpoint_clear(ip) ⇒ Object

Clear a breakpoint by ip



180
181
182
183
184
185
186
187
188
189
# File 'lib/ragweed/debugger32.rb', line 180

def breakpoint_clear(ip)
  bp = @breakpoints[ip]

  if bp.nil?
      return nil
  end

  bp.uninstall
  @breakpoints.delete(ip)
end

#breakpoint_set(ip, callable = nil, &block) ⇒ Object

set a breakpoint given an address, which can also be a string in the form “module!function”, as in, “user32!SendMessageW”. Be aware that the symbol lookup takes place in an injected thread; it’s safer to use literal addresses when possible.

to handle the breakpoint, pass a block to this method, which will be called when the breakpoint hits.

breakpoints are always re-set after firing. If you don’t want them to be re-set, unset them manually.



148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/ragweed/debugger32.rb', line 148

def breakpoint_set(ip, callable=nil, &block)
  if not callable and block_given?
    callable = block
  end

  def_status = false

  # This is usually 'Module!Function' or 'Module!0x1234'
  if @p.is_breakpoint_deferred(ip) == true
      def_status = true
  else
      def_status = false
      ip = @p.get_proc_remote(ip)
  end

  # If we cant immediately set the breakpoint
  # mark it as deferred and wait till later
  # Sometimes *_proc_remote() will return the
  # name indicating failure (just in case)
  if ip == 0 or ip == 0xFFFFFFFF or ip.kind_of? String
    def_status = true
  else
    def_status = false
  end

  # Dont want duplicate breakpoint objects
  @breakpoints.each_key { |k| if k == ip then return end }
  bp = Breakpoint.new(@p, ip, def_status, callable)
  @breakpoints[ip] = bp
end

#context(tid_or_event) ⇒ Object

convenience: either from a TID or a BreakpointEvent, get the thread context.



129
130
131
132
133
134
135
136
# File 'lib/ragweed/debugger32.rb', line 129

def context(tid_or_event)
  if not tid_or_event.kind_of? Numeric
    tid = tid_or_event.tid
  else
    tid = tid_or_event
  end
  Ragweed::Wrap32::open_thread(tid) { |h| Ragweed::Wrap32::get_thread_context(h) }
end

#get_dll_name(ev) ⇒ Object

FIX: this method should be a bit more descriptive in its naming



231
232
233
234
235
236
237
238
# File 'lib/ragweed/debugger32.rb', line 231

def get_dll_name(ev)
  name = Ragweed::Wrap32::get_mapped_filename(@p.handle, ev.base_of_dll, 256)
  name.gsub!(/[\n]+/,'')
  name.gsub!(/[^\x21-\x7e]/,'')
  i = name.index('0')
  i ||= name.size
  return name[0, i]
end

#hook(ip, nargs, callable = nil, &block) ⇒ Object

Hook function calls nargs is the number of arguments taken by function at ip callable/block is called with ev, ctx, dir (:enter or :leave), and args Array (see examples/hook_notepad.rb) default handler prints arguments



6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/ragweed/wrap32/hooks.rb', line 6

def hook(ip, nargs, callable=nil, &block)

  callable ||= block || lambda do |ev,ctx,dir,args|
    # puts args.map{|a| "%08x" % a}.join(',')
  end

  breakpoint_set(ip) do |ev,ctx|
    esp = process.read32(ctx.esp)
    nargs = nargs.to_i

    if nargs >= 1
      args = (1..nargs).map {|i| process.read32(ctx.esp + 4*i)}
    end

    # set exit bpoint
    # We cant always set a leave bp due to
    # calling conventions but we can avoid
    # a crash by setting a breakpoint on
    # the wrong address. So we attempt to
    # get an idea of where the instruction
    # is mapped.
    eip = ctx.eip
    if esp != 0 #and esp > (eip & 0xf0000000)
      breakpoint_set(esp) do |ev,ctx|
        callable.call(ev, ctx, :leave, args)
        breakpoint_clear(esp)
      end.install
    end

    # Call the block sent to hook()
    callable.call(ev, ctx, :enter, args)
  end
end

#loopObject

Debug loop



392
393
394
395
396
# File 'lib/ragweed/debugger32.rb', line 392

def loop
  while true
    wait
  end
end

#on_alignment(ev) ⇒ Object

This method is abstract.


304
# File 'lib/ragweed/debugger32.rb', line 304

def on_alignment(ev)            end

#on_attachObject

This method is abstract.


326
# File 'lib/ragweed/debugger32.rb', line 326

def on_attach()                 end

#on_bounds(ev) ⇒ Object

This method is abstract.


306
# File 'lib/ragweed/debugger32.rb', line 306

def on_bounds(ev)               end

#on_breakpoint(ev) ⇒ Object

handle a breakpoint event: call handlers for the breakpoint, step past and reset it.



193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
# File 'lib/ragweed/debugger32.rb', line 193

def on_breakpoint(ev)
    ctx = context(ev)
    eip = ev.exception_address

    if eip == @ntdll_dbg_break_point
      return
    end

    @breakpoints[eip].uninstall

    # Call the block passed to breakpoint_set
    # which may have been passed through hook()
    @breakpoints[eip].call(ev, ctx)

    # single step past the instruction...
    step(ev.tid, (onestep = lambda do |ev, ctx|
      if ev.exception_address != eip
        # ... then re-install the breakpoint ...
        if not @breakpoints[eip].nil?
          @breakpoints[eip].install
        end
        # ... and stop single-stepping.
        unstep(ev.tid, onestep)
      end
    end))

    # Put execution back where it's supposed to be...
    Ragweed::Wrap32::open_thread(ev.tid) do |h|
      ctx = context(ev)
      ctx.eip = eip ## eip was ev.exception_address
      Ragweed::Wrap32::set_thread_context(h, ctx)
    end

  # Tell the target to stop handling this event
  @handled = Ragweed::Wrap32::ContinueCodes::CONTINUE
end

#on_buffer_overrun(ev) ⇒ Object

This method is abstract.


322
# File 'lib/ragweed/debugger32.rb', line 322

def on_buffer_overrun(ev)       end

#on_create_process(ev) ⇒ Object

This method is abstract.


290
# File 'lib/ragweed/debugger32.rb', line 290

def on_create_process(ev)       end

#on_create_thread(ev) ⇒ Object

This method is abstract.


292
# File 'lib/ragweed/debugger32.rb', line 292

def on_create_thread(ev)        end

#on_divide_by_zero(ev) ⇒ Object

This method is abstract.


308
# File 'lib/ragweed/debugger32.rb', line 308

def on_divide_by_zero(ev)       end

#on_exit_process(ev) ⇒ Object

This is sort of insane but most of my programs are just debug loops, so if you don’t do this, they just hang when the target closes.



285
286
287
# File 'lib/ragweed/debugger32.rb', line 285

def on_exit_process(ev)
  exit(1)
end

#on_exit_thread(ev) ⇒ Object

This method is abstract.


294
# File 'lib/ragweed/debugger32.rb', line 294

def on_exit_thread(ev)          end

#on_guard_page(ev) ⇒ Object

This method is abstract.


302
# File 'lib/ragweed/debugger32.rb', line 302

def on_guard_page(ev)           end

#on_heap_corruption(ev) ⇒ Object

This method is abstract.


320
# File 'lib/ragweed/debugger32.rb', line 320

def on_heap_corruption(ev)      end

#on_illegal_instruction(ev) ⇒ Object

This method is abstract.


314
# File 'lib/ragweed/debugger32.rb', line 314

def on_illegal_instruction(ev)  end

#on_int_overflow(ev) ⇒ Object

This method is abstract.


310
# File 'lib/ragweed/debugger32.rb', line 310

def on_int_overflow(ev)         end

#on_invalid_disposition(ev) ⇒ Object

This method is abstract.


324
# File 'lib/ragweed/debugger32.rb', line 324

def on_invalid_disposition(ev)  end

#on_invalid_handle(ev) ⇒ Object

This method is abstract.


312
# File 'lib/ragweed/debugger32.rb', line 312

def on_invalid_handle(ev)       end

#on_load_dll(ev) ⇒ Object



240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
# File 'lib/ragweed/debugger32.rb', line 240

def on_load_dll(ev)
  dll_name = get_dll_name(ev)

  @breakpoints.each_pair do |k,bp|
      if !bp.addr.kind_of?String
          next
      end

      m,f = bp.addr.split('!')

      if dll_name =~ /#{m}/i
          deferred = bp.deferred

          if deferred == true
              bp.deferred = false
          end

          new_addr = bp.deferred_install(ev.file_handle, ev.base_of_dll)

          if !new_addr.nil?
              @breakpoints[new_addr] = bp.dup
              @breakpoints.delete(k)
          end
      end
  end
end

#on_output_debug_string(ev) ⇒ Object

This method is abstract.


296
# File 'lib/ragweed/debugger32.rb', line 296

def on_output_debug_string(ev)  end

#on_priv_instruction(ev) ⇒ Object

This method is abstract.


316
# File 'lib/ragweed/debugger32.rb', line 316

def on_priv_instruction(ev)     end

#on_rip(ev) ⇒ Object

This method is abstract.


298
# File 'lib/ragweed/debugger32.rb', line 298

def on_rip(ev)                  end

#on_single_step(ev) ⇒ Object

handle a single-step event



268
269
270
271
272
273
274
275
276
277
278
279
280
# File 'lib/ragweed/debugger32.rb', line 268

def on_single_step(ev)
  ctx = context(ev)
  Ragweed::Wrap32::open_thread(ev.tid) do |h|
    ## re-enable the trap flag before our handler,
    ## which may choose to disable it.
    ctx.single_step(true)
    Ragweed::Wrap32.set_thread_context(h, ctx)
  end
  
  @steppers.each {|s| s.call(ev, ctx)}
      
  @handled = Ragweed::Wrap32::ContinueCodes::CONTINUE
end

#on_stack_overflow(ev) ⇒ Object

This method is abstract.


318
# File 'lib/ragweed/debugger32.rb', line 318

def on_stack_overflow(ev)       end

#on_unload_dll(ev) ⇒ Object

This method is abstract.


300
# File 'lib/ragweed/debugger32.rb', line 300

def on_unload_dll(ev)           end

#processObject

Get a handle to the process so you can mess with it.



70
# File 'lib/ragweed/debugger32.rb', line 70

def process; @p; end

#releaseObject

Let go of the target.



411
412
413
414
415
416
417
# File 'lib/ragweed/debugger32.rb', line 411

def release
  Ragweed::Wrap32::debug_active_process_stop(@p.pid)
  @attached = false
  @breakpoints.each_pair do |k, bp|
    bp.uninstall
  end
end

#step(tid, callable) ⇒ Object

single-step the thread (by TID). “callable” is something that honors .call, like a Proc. In a dubious design decision: the “handle” to the single stepper is the Proc object itself. See Debugger#on_breakpoint for an example of how to use this.



103
104
105
106
107
108
109
110
111
112
# File 'lib/ragweed/debugger32.rb', line 103

def step(tid, callable)
  if @steppers.empty?
    Ragweed::Wrap32::open_thread(tid) do |h|
      ctx = Ragweed::Wrap32::get_thread_context(h)
      ctx.single_step(true)
      Ragweed::Wrap32::set_thread_context(h, ctx)
    end
  end
  @steppers << callable    
end

#unstep(tid, callable) ⇒ Object

turn off single-stepping for one callable (you can have more than one at a time). In other words, when you pass a Proc to Debugger#step, save it somewhere, and later pass it to “unstep” to turn it off.



117
118
119
120
121
122
123
124
125
126
# File 'lib/ragweed/debugger32.rb', line 117

def unstep(tid, callable)
  @steppers = @steppers.reject {|x| x == callable}
  if @steppers.empty?
    Ragweed::Wrap32::open_thread(tid) do |h|
      ctx = Ragweed::Wrap32::get_thread_context(h)
      ctx.single_step(false)
      Ragweed::Wrap32::set_thread_context(h, ctx)
    end
  end
end

#waitObject

Read through me to see all the random events you can hook in a subclass.



330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
# File 'lib/ragweed/debugger32.rb', line 330

def wait
  self.attach() if not @attached

  @event = ev = Ragweed::Wrap32::wait_for_debug_event
  return if not ev
  case ev.code
  when Ragweed::Wrap32::DebugCodes::CREATE_PROCESS
    try(:on_create_process, ev)
  when Ragweed::Wrap32::DebugCodes::CREATE_THREAD
    try(:on_create_thread, ev)
  when Ragweed::Wrap32::DebugCodes::EXIT_PROCESS
    try(:on_exit_process, ev)
  when Ragweed::Wrap32::DebugCodes::EXIT_THREAD
    try(:on_exit_thread, ev)
  when Ragweed::Wrap32::DebugCodes::LOAD_DLL
    try(:on_load_dll, ev)
  when Ragweed::Wrap32::DebugCodes::OUTPUT_DEBUG_STRING
    try(:on_output_debug_string, ev)
  when Ragweed::Wrap32::DebugCodes::RIP
    try(:on_rip, ev)
  when Ragweed::Wrap32::DebugCodes::UNLOAD_DLL
    try(:on_unload_dll, ev)
  when Ragweed::Wrap32::DebugCodes::EXCEPTION
    case ev.exception_code
    when Ragweed::Wrap32::ExceptionCodes::ACCESS_VIOLATION
      try(:on_access_violation, ev)
    when Ragweed::Wrap32::ExceptionCodes::GUARD_PAGE
      try(:on_guard_page, ev)
    when Ragweed::Wrap32::ExceptionCodes::BREAKPOINT 
      try(:on_breakpoint, ev)
    when Ragweed::Wrap32::ExceptionCodes::ALIGNMENT  
      try(:on_alignment, ev)
    when Ragweed::Wrap32::ExceptionCodes::SINGLE_STEP 
      try(:on_single_step, ev)
    when Ragweed::Wrap32::ExceptionCodes::BOUNDS 
      try(:on_bounds, ev)
    when Ragweed::Wrap32::ExceptionCodes::DIVIDE_BY_ZERO 
      try(:on_divide_by_zero, ev)
    when Ragweed::Wrap32::ExceptionCodes::INT_OVERFLOW 
      try(:on_int_overflow, ev)
    when Ragweed::Wrap32::ExceptionCodes::INVALID_HANDLE 
      try(:on_invalid_handle, ev)
    when Ragweed::Wrap32::ExceptionCodes::ILLEGAL_INSTRUCTION
      try(:on_illegal_instruction, ev)
    when Ragweed::Wrap32::ExceptionCodes::PRIV_INSTRUCTION
      try(:on_priv_instruction, ev)
    when Ragweed::Wrap32::ExceptionCodes::STACK_OVERFLOW 
      try(:on_stack_overflow, ev)
    when Ragweed::Wrap32::ExceptionCodes::HEAP_CORRUPTION
      try(:on_heap_corruption, ev)
    when Ragweed::Wrap32::ExceptionCodes::BUFFER_OVERRUN
      try(:on_buffer_overrun, ev)
    when Ragweed::Wrap32::ExceptionCodes::INVALID_DISPOSITION 
      try(:on_invalid_disposition, ev)
    end
  end

  Ragweed::Wrap32::continue_debug_event(ev.pid, ev.tid, @handled)
  @handled = Ragweed::Wrap32::ContinueCodes::UNHANDLED
end