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,3 +1,5 @@
1
+ require 'ffi'
2
+
1
3
  module Ragweed::Wrap32
2
4
  module DebugCodes
3
5
  CREATE_PROCESS = 3
@@ -35,64 +37,169 @@ module Ragweed::Wrap32
35
37
  end
36
38
  end
37
39
 
38
- class Ragweed::Wrap32::DebugEvent
39
- (FIELDS = [ :code,
40
- :pid,
41
- :tid,
42
- :file_handle,
43
- :process_handle,
44
- :thread_handle,
45
- :base,
46
- :offset,
47
- :info_size,
48
- :thread_base,
49
- :start_address,
50
- :image_name,
51
- :unicode,
52
- :exception_code,
53
- :exception_flags,
54
- :exception_record,
55
- :exception_address,
56
- :parameters,
57
- :exit_code,
58
- :dll_base,
59
- :rip_error,
60
- :rip_type]).each {|x| attr_accessor x}
61
-
62
- def initialize(str)
63
- @code, @pid, @tid = str.unpack("LLL")
64
- str.shift 12
65
- case @code
40
+ class Ragweed::Wrap32::RipInfo < FFI::Struct
41
+ include Ragweed::FFIStructInclude
42
+ layout :error, :ulong,
43
+ :type, :ulong
44
+ end
45
+
46
+ class Ragweed::Wrap32::OutputDebugStringInfo < FFI::Struct
47
+ include Ragweed::FFIStructInclude
48
+ layout :debug_string_data, :ulong, # pointer
49
+ :unicode, :uint16,
50
+ :debug_string_length, :uint16
51
+ end
52
+
53
+ class Ragweed::Wrap32::UnloadDLLDebugInfo < FFI::Struct
54
+ include Ragweed::FFIStructInclude
55
+ layout :base_of_dll, :ulong
56
+ end
57
+
58
+ class Ragweed::Wrap32::LoadDLLDebugInfo < FFI::Struct
59
+ include Ragweed::FFIStructInclude
60
+ layout :file_handle, :ulong,
61
+ :base_of_dll, :ulong,
62
+ :debug_info_file_offset, :ulong,
63
+ :debug_info_size, :ulong,
64
+ :image_name, :pointer,
65
+ :unicode, :uint16
66
+ end
67
+
68
+ class Ragweed::Wrap32::ExitProcessDebugInfo < FFI::Struct
69
+ include Ragweed::FFIStructInclude
70
+ layout :exit_code, :ulong
71
+ end
72
+
73
+ class Ragweed::Wrap32::ExitThreadDebugInfo < FFI::Struct
74
+ include Ragweed::FFIStructInclude
75
+ layout :exit_code, :ulong
76
+ end
77
+
78
+ class Ragweed::Wrap32::CreateProcessDebugInfo < FFI::Struct
79
+ include Ragweed::FFIStructInclude
80
+ layout :file_handle, :ulong,
81
+ :process_handle, :ulong,
82
+ :thread_handle, :ulong,
83
+ :base_of_image, :pointer,
84
+ :debug_info_file_offset, :ulong,
85
+ :debug_info_size, :ulong,
86
+ :thread_local_base, :pointer,
87
+ :start_address, :pointer,
88
+ :image_name, :pointer,
89
+ :unicode, :uint16
90
+ end
91
+
92
+ class Ragweed::Wrap32::CreateThreadDebugInfo < FFI::Struct
93
+ include Ragweed::FFIStructInclude
94
+ layout :thread_handle, :ulong,
95
+ :thread_local_base, :ulong,
96
+ :start_address, :pointer
97
+ end
98
+
99
+ class Ragweed::Wrap32::ExceptionRecord < FFI::Struct
100
+ include Ragweed::FFIStructInclude
101
+ layout :exception_code, :ulong,
102
+ :exception_flags, :ulong,
103
+ :exception_record, :pointer,
104
+ :exception_address, :ulong,
105
+ :number_of_parameters, :ulong,
106
+ :exception_information, [:uint8, 15] ## EXCEPTION_MAXIMUM_PARAMETERS
107
+ end
108
+
109
+ class Ragweed::Wrap32::ExceptionDebugInfo < FFI::Struct
110
+ include Ragweed::FFIStructInclude
111
+ layout :exception_record, Ragweed::Wrap32::ExceptionRecord,
112
+ :first_chance, :ulong
113
+ end
114
+
115
+ class Ragweed::Wrap32::DebugEventU < FFI::Union
116
+ include Ragweed::FFIStructInclude
117
+ layout :exception_debug_info, Ragweed::Wrap32::ExceptionDebugInfo,
118
+ :create_thread_debug_info, Ragweed::Wrap32::CreateThreadDebugInfo,
119
+ :create_process_debug_info, Ragweed::Wrap32::CreateProcessDebugInfo,
120
+ :exit_thread_debug_info, Ragweed::Wrap32::ExitThreadDebugInfo,
121
+ :exit_process_debug_info, Ragweed::Wrap32::ExitProcessDebugInfo,
122
+ :load_dll_debug_info, Ragweed::Wrap32::LoadDLLDebugInfo,
123
+ :unload_dll_debug_info, Ragweed::Wrap32::UnloadDLLDebugInfo,
124
+ :output_debug_string_info, Ragweed::Wrap32::OutputDebugStringInfo,
125
+ :rip_info, Ragweed::Wrap32::RipInfo
126
+ end
127
+
128
+ class Ragweed::Wrap32::DebugEvent < FFI::Struct
129
+ include Ragweed::FFIStructInclude
130
+
131
+ layout :code, :ulong,
132
+ :pid, :ulong,
133
+ :tid, :ulong,
134
+ :u, Ragweed::Wrap32::DebugEventU
135
+
136
+ ## We have rubified this FFI structure by creating a bunch of instance
137
+ ## variables that are normally only accessible via self.u.x.y which is
138
+ ## a lot to type. You can still use that method however these instance
139
+ ## variables should allow for considerably clearer code
140
+ attr_accessor :base_of_dll, :base_of_image, :debug_info_file_offset, :debug_info_size, :exception_address, :exception_code,
141
+ :exception_flags, :exception_record, :exit_code, :file_handle, :image_name, :number_of_parameters, :parameters,
142
+ :process_handle, :rip_error, :rip_type, :start_address, :thread_local_base, :thread_handle, :thread_local_base,
143
+ :unicode
144
+
145
+ def initialize(buf)
146
+ super buf
147
+
148
+ case self.code
66
149
  when Ragweed::Wrap32::DebugCodes::CREATE_PROCESS
67
- @file_handle, @process_handle, @thread_handle,
68
- @base, @offset,
69
- @info_size, @thread_base, @start_address,
70
- @image_name, @unicode = str.unpack("LLLLLLLLLH")
150
+ @file_handle = self.u.create_process_debug_info.file_handle
151
+ @process_handle = self.u.create_process_debug_info.process_handle
152
+ @thread_handle = self.u.create_process_debug_info.thread_handle
153
+ @base_of_image = self.u.create_process_debug_info.base_of_image
154
+ @debug_info_file_offset = self.u.create_process_debug_info.debug_info_file_offset
155
+ @debug_info_size = self.u.create_process_debug_info.debug_info_size
156
+ @thread_local_base = self.u.create_process_debug_info.thread_local_base
157
+ @start_address = self.u.create_process_debug_info.start_address
158
+ @image_name = self.u.create_process_debug_info.image_name
159
+ @unicode = self.u.create_process_debug_info.unicode
160
+
71
161
  when Ragweed::Wrap32::DebugCodes::CREATE_THREAD
72
- @thread_handle, @thread_base, @start_address = str.unpack("LLL")
73
- when Ragweed::Wrap32::DebugCodes::EXCEPTION
74
- @exception_code, @exception_flags,
75
- @exception_record, @exception_address, @parameter_count = str.unpack("LLLLL")
76
- str = str[20..-1]
162
+ @thread_handle = self.u.create_thread_debug_info.thread_handle
163
+ @thread_local_base = self.u.create_thread_debug_info.thread_local_base
164
+ @start_address = self.u.create_thread_debug_info.start_address
165
+
166
+ when Ragweed::Wrap32::DebugCodes::EXCEPTION
167
+ @exception_code = self.u.exception_debug_info.exception_record.exception_code
168
+ @exception_flags = self.u.exception_debug_info.exception_record.exception_flags
169
+ @exception_record = self.u.exception_debug_info.exception_record.exception_record
170
+ @exception_address = self.u.exception_debug_info.exception_record.exception_address
171
+ @number_of_parameters = self.u.exception_debug_info.exception_record.number_of_parameters
172
+
77
173
  @parameters = []
78
- @parameter_count.times do
79
- begin
80
- @parameters << (str.unpack("L").first)
81
- str = str[4..-1]
82
- rescue;end
174
+
175
+ self.number_of_parameters.times do |i|
176
+ @parameters << self.u.exception_debug_info.exception_record.exception_information[i]
83
177
  end
178
+
84
179
  when Ragweed::Wrap32::DebugCodes::EXIT_PROCESS
85
- @exit_code = str.unpack("L").first
180
+ @exit_code = self.u.exit_process_debug_info.exit_code
181
+
86
182
  when Ragweed::Wrap32::DebugCodes::EXIT_THREAD
87
- @exit_code = str.unpack("L").first
88
- when Ragweed::Wrap32::DebugCodes::LOAD_DLL
89
- @file_handle, @dll_base, @offset,
90
- @info_size, @image_name, @unicode = str.unpack("LLLLLH")
183
+ @exit_code = self.u.exit_thread_debug_info.exit_code
184
+
185
+ when Ragweed::Wrap32::DebugCodes::LOAD_DLL
186
+ @file_handle = self.u.load_dll_debug_info.file_handle
187
+ @base_of_dll = self.u.load_dll_debug_info.base_of_dll
188
+ @debug_info_file_offset = self.u.load_dll_debug_info.debug_info_file_offset
189
+ @debug_info_size = self.u.load_dll_debug_info.debug_info_size
190
+ @image_name = self.u.load_dll_debug_info.image_name
191
+ @unicode = self.u.load_dll_debug_info.unicode
192
+
91
193
  when Ragweed::Wrap32::DebugCodes::OUTPUT_DEBUG_STRING
194
+ ##
195
+
92
196
  when Ragweed::Wrap32::DebugCodes::RIP
93
- @rip_error, @rip_type = str.unpack("LL")
197
+ @rip_error = self.u.rip_info.rip_error
198
+ @rip_type = self.u.rip_info.rip_type
199
+
94
200
  when Ragweed::Wrap32::DebugCodes::UNLOAD_DLL
95
- @dll_base = str.unpack("L").first
201
+ @base_of_dll = self.u.unload_dll_debug_info.base_of_dll
202
+
96
203
  else
97
204
  raise WinX.new(:wait_for_debug_event)
98
205
  end
@@ -111,53 +218,66 @@ class Ragweed::Wrap32::DebugEvent
111
218
  end
112
219
 
113
220
  def inspect
114
- body = lambda do
115
- FIELDS.map do |f|
116
- if (v = send(f))
117
- f.to_s + "=" + (try("inspect_#{f.to_s}".intern, v) || v.to_i.to_s(16))
118
- end
119
- end.compact.join(" ")
120
- end
121
-
221
+ body = lambda do
222
+ self.members.each_with_index do |m,i|
223
+ "#{self.values[i].to_s.hexify} #{self.values[i].to_s.hexify}"
224
+ end.join(", ")
225
+ end
122
226
  "#<DebugEvent #{body.call}>"
123
227
  end
124
228
  end
125
229
 
230
+ ## Wrap the Win32 debugging specific APIs
126
231
  module Ragweed::Wrap32
232
+ module Win
233
+ extend FFI::Library
234
+
235
+ ffi_lib 'kernel32'
236
+ ffi_convention :stdcall
237
+
238
+ attach_function 'WaitForDebugEvent', [ :pointer, :ulong ], :ulong
239
+ attach_function 'ContinueDebugEvent', [ :ulong, :ulong, :ulong ], :ulong
240
+ attach_function 'DebugActiveProcess', [ :ulong ], :ulong
241
+ attach_function 'DebugSetProcessKillOnExit', [ :ulong ], :ulong
242
+ attach_function 'DebugActiveProcessStop', [ :ulong ], :ulong
243
+ attach_function 'FlushInstructionCache', [ :ulong, :ulong, :ulong ], :ulong
244
+ end
245
+
127
246
  class << self
128
247
  def wait_for_debug_event(ms=1000)
129
- buf = "\x00" * 1024
130
- r = CALLS["kernel32!WaitForDebugEvent:PL=L"].call(buf, ms)
248
+ # buf = FFI::MemoryPointer.new(Ragweed::Wrap32::DebugEvent, 1)
249
+ buf = FFI::MemoryPointer.from_string("\x00" * 1024)
250
+ r = Win.WaitForDebugEvent(buf, ms)
131
251
  raise WinX.new(:wait_for_debug_event) if r == 0 and get_last_error != 121
132
252
  return Ragweed::Wrap32::DebugEvent.new(buf) if r != 0
133
253
  return nil
134
254
  end
135
255
 
136
256
  def continue_debug_event(pid, tid, code)
137
- r = CALLS["kernel32!ContinueDebugEvent:LLL=L"].call(pid, tid, code)
257
+ r = Win.ContinueDebugEvent(pid, tid, code)
138
258
  raise WinX.new(:continue_debug_event) if r == 0
139
259
  return r
140
260
  end
141
261
 
142
262
  def debug_active_process(pid)
143
- r = CALLS["kernel32!DebugActiveProcess:L=L"].call(pid)
263
+ r = Win.DebugActiveProcess(pid)
144
264
  raise WinX.new(:debug_active_process) if r == 0
145
265
  return r
146
266
  end
147
267
 
148
268
  def debug_set_process_kill_on_exit(val=0)
149
- r = CALLS["kernel32!DebugSetProcessKillOnExit:L=L"].call(val)
269
+ r = Win.DebugSetProcessKillOnExit(val)
150
270
  raise WinX.new(:debug_set_process_kill_on_exit) if r == 0
151
271
  return r
152
272
  end
153
273
 
154
274
  def debug_active_process_stop(pid)
155
275
  # don't care about failure
156
- CALLS["kernel32!DebugActiveProcessStop:L=L"].call(pid)
276
+ Win.DebugActiveProcessStop(pid)
157
277
  end
158
278
 
159
279
  def flush_instruction_cache(h, v1=0, v2=0)
160
- CALLS["kernel32!FlushInstructionCache:LLL=L"].call(h, v1, v2)
280
+ Win.FlushInstructionCache(h, v1, v2)
161
281
  end
162
282
  end
163
283
  end
@@ -4,20 +4,36 @@ class Ragweed::Debugger32
4
4
  # callable/block is called with ev, ctx, dir (:enter or :leave), and args Array (see examples/hook_notepad.rb)
5
5
  # default handler prints arguments
6
6
  def hook(ip, nargs, callable=nil, &block)
7
- callable ||= block || lambda do |ev, ctx,dir,args|
8
- puts "#{dir} #{ip.to_s(16) rescue ip.to_s}"
9
- puts args.map{|a| "%08x" % a}.join(',')
7
+
8
+ callable ||= block || lambda do |ev,ctx,dir,args|
9
+ #puts args.map{|a| "%08x" % a}.join(',')
10
10
  end
11
11
 
12
12
  breakpoint_set(ip) do |ev,ctx|
13
- args = (1..nargs).map {|i| process.read32(ctx.esp + 4*i)}
14
- retp = process.read32(ctx.esp)
15
- # set exit bpoint
16
- breakpoint_set(retp) do |ev,ctx|
17
- callable.call(ev, ctx, :leave, args)
18
- breakpoint_clear(retp)
19
- end.install
13
+ esp = process.read32(ctx.esp)
14
+ nargs = nargs.to_i
15
+
16
+ if nargs >= 1
17
+ args = (1..nargs).map {|i| process.read32(ctx.esp + 4*i)}
18
+ end
19
+
20
+ ## set exit bpoint
21
+ ## We cant always set a leave bp due to
22
+ ## calling conventions but we can avoid
23
+ ## a crash by setting a breakpoint on
24
+ ## the wrong address. So we attempt to
25
+ ## get an idea of where the instruction
26
+ ## is mapped.
27
+ eip = ctx.eip
28
+ if esp != 0 and esp > (eip & 0xf0000000)
29
+ breakpoint_set(esp) do |ev,ctx|
30
+ callable.call(ev, ctx, :leave, args)
31
+ breakpoint_clear(esp)
32
+ end.install
33
+ end
34
+
35
+ ## Call the block sent to hook()
20
36
  callable.call(ev, ctx, :enter, args)
21
37
  end
22
38
  end
23
- end
39
+ end
@@ -1,5 +1,3 @@
1
- # TODO - PORT ME!!
2
-
3
1
  class Ragweed::Process
4
2
  def handle; @h; end
5
3
  attr_reader :pid
@@ -34,16 +32,125 @@ class Ragweed::Process
34
32
  ptr(Ragweed::Wrap32::get_proc_address(name))
35
33
  end
36
34
 
35
+ def is_hex(s)
36
+ s = s.strip
37
+
38
+ ## Strip leading 0s and 0x prefix
39
+ while s[0..1] == '0x' or s[0..1] == '00'
40
+ s = s[2..-1]
41
+ end
42
+
43
+ o = s
44
+
45
+ if s.hex.to_s(16) == o
46
+ return true
47
+ end
48
+ return false
49
+ end
50
+
51
+ ## This only gets called for breakpoints in modules
52
+ ## that have just been loaded and detected by a LOAD_DLL
53
+ ## event. It is called from on_load_dll() -> deferred_install()
54
+ def get_deferred_proc_remote(name, handle, base_of_dll)
55
+ if !name.kind_of?String
56
+ return name
57
+ end
58
+
59
+ mod, meth = name.split "!"
60
+
61
+ if mod.nil? or meth.nil?
62
+ raise "can not set this breakpoint: #{name}"
63
+ end
64
+
65
+ modh = handle
66
+
67
+ ## Location is an offset
68
+ if is_hex(meth)
69
+ baseaddr = 0
70
+ modules.each do |m|
71
+ if m.szModule == mod
72
+ break
73
+ end
74
+ end
75
+
76
+ ret = base_of_dll + meth.hex
77
+ else
78
+ ## Location is a symbolic name
79
+ ## Win32 should have successfully loaded the DLL
80
+ ret = remote_call "kernel32!GetProcAddress", modh, meth
81
+ end
82
+ ret
83
+ end
84
+
85
+ ## This only gets called for breakpoints
86
+ ## in modules that are already loaded
37
87
  def get_proc_remote(name)
88
+ if !name.kind_of?String
89
+ return name
90
+ end
91
+
38
92
  mod, meth = name.split "!"
39
- modh = remote_call "kernel32!GetModuleHandleW", mod.to_utf16
93
+
94
+ if mod.nil? or meth.nil?
95
+ raise "can not set this breakpoint: #{name}"
96
+ end
97
+
98
+ # modh = remote_call "kernel32!GetModuleHandleW", mod.to_utf16
99
+ modh = remote_call "kernel32!GetModuleHandleA", mod
40
100
  raise "no such module #{ mod }" if not modh
41
- ret = remote_call "kernel32!GetProcAddress", modh, meth
101
+
102
+ ## Location is an offset
103
+ if is_hex(meth)
104
+ baseaddr = 0
105
+ modules.each do |m|
106
+ if m.szModule == mod
107
+ baseaddr = m.modBaseAddr
108
+ break
109
+ end
110
+ end
111
+
112
+ ## Somehow the module does not appear to be
113
+ ## loaded. This should have been caught by
114
+ ## Process::is_breakpoint_deferred either way
115
+ ## Process::initialize should catch this return
116
+ if baseaddr == 0 or baseaddr == -1
117
+ return name
118
+ end
119
+
120
+ ret = baseaddr + meth.hex
121
+ else
122
+ ## Location is a symbolic name
123
+ ret = remote_call "kernel32!GetProcAddress", modh, meth
124
+ end
42
125
  ret
43
126
  end
44
127
 
45
- # Look up a process by name or regex, returning an array of all
46
- # matching processes, as objects.
128
+ ## Check if breakpoint location is deferred
129
+ ## This method expects a string 'module!function'
130
+ ## true is the module is not yet loaded
131
+ ## false is the module is loaded
132
+ def is_breakpoint_deferred(ip)
133
+ if !ip.kind_of? String
134
+ return false
135
+ end
136
+
137
+ m,f = ip.split('!')
138
+
139
+ if f.nil? or m.nil?
140
+ return true
141
+ end
142
+
143
+ modules.each do |d|
144
+ if d.szModule.to_s.match(/#{m}/)
145
+ return false
146
+ end
147
+ end
148
+
149
+ return true
150
+ end
151
+
152
+ ## Look up a process by name or regex, returning an array of all
153
+ ## matching processes, as objects.
47
154
  def self.by_name(n)
48
155
  n = Regexp.new(n) if not n.kind_of? Regexp
49
156
  p = []
@@ -126,7 +233,7 @@ class Ragweed::Process
126
233
  loc = Ragweed::Ptr.new loc
127
234
  raise "bad proc name" if loc.null?
128
235
  t = Trampoline.new(self, loc)
129
- t.call *args
236
+ t.call *args
130
237
  end
131
238
 
132
239
  # Can I write to this address in the process?
@@ -20,28 +20,44 @@ module Ragweed::Wrap32
20
20
  USED_FOR_ACCESS = 0x80000000
21
21
  end
22
22
 
23
+ module Win
24
+ extend FFI::Library
25
+
26
+ ffi_lib 'kernel32','Advapi32'
27
+ ffi_convention :stdcall
28
+ attach_function 'OpenProcess', [ :long, :long, :long ], :long
29
+ attach_function 'OpenProcessToken', [:long, :long, :pointer ], :long
30
+
31
+ # ffi_lib 'advapi32'
32
+ # ffi_convention :stdcall
33
+ attach_function 'AdjustTokenPrivileges', [ :long, :long, :pointer, :long, :pointer, :pointer ], :long
34
+ attach_function 'LookupPrivilegeValueA', [ :pointer, :pointer, :pointer ] ,:long
35
+ end
36
+
23
37
  class << self
38
+
24
39
  def open_process_token(h, access=Ragweed::Wrap32::TokenAccess::ADJUST_PRIVILEGES)
25
40
  outw = "\x00" * 4
26
- r = CALLS["advapi32!OpenProcessToken:LLP=L"].call(h, access, outw)
41
+ r = Win.OpenProcessToken(h, access, outw)
27
42
  raise WinX.new(:open_process_token) if r == 0
28
43
  return outw.unpack("L").first
29
44
  end
30
45
 
31
46
  def adjust_token_privileges(t, disable, *args)
32
- buf = [args.size].pack("L") + (args.map {|tup| tup.pack("QL") }.join(""))
47
+ buf = FFI::MemoryPointer.from_string( [args.size].pack("L") + (args.map {|tup| tup.pack("QL") }.join("")) )
33
48
 
34
- r = CALLS["advapi32!AdjustTokenPrivileges:LLPLPP=L"].
35
- call(t, disable, buf, buf.size, NULL, NULL)
49
+ r = Win.AdjustTokenPrivileges(t, disable, buf, buf.size, nil, nil)
36
50
 
37
51
  raise WinX.new(:adjust_token_privileges) if r == 0
38
52
  end
39
53
 
40
54
  def lookup_privilege_value(name)
41
- outw = "\x00" * 8
42
- r = CALLS["advapi32!LookupPrivilegeValueA:PPP=L"].call(NULL, name, outw)
55
+ namep = FFI::MemoryPointer.from_string(name)
56
+ outw = FFI::MemoryPointer.new(:int64, 1)
57
+ r = Win.LookupPrivilegeValueA(nil, namep, outw)
58
+ r = Win.LookupPrivilegeValueA(nil, name, outw)
43
59
  raise WinX.new(:lookup_privilege_value) if r == 0
44
- return outw.unpack("Q").first
60
+ outw.read_long_long
45
61
  end
46
62
  end
47
63
  end