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,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