ragweed 0.2.0-java
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +32 -0
- data/README.rdoc +60 -0
- data/README.txt +9 -0
- data/Rakefile +86 -0
- data/VERSION +1 -0
- data/examples/hittracertux.rb +45 -0
- data/examples/hittracerx.rb +63 -0
- data/examples/hook_notepad.rb +9 -0
- data/examples/snicker.rb +183 -0
- data/examples/tux-example.rb +24 -0
- data/lib/ragweed/arena.rb +55 -0
- data/lib/ragweed/blocks.rb +128 -0
- data/lib/ragweed/debugger32.rb +400 -0
- data/lib/ragweed/debuggerosx.rb +456 -0
- data/lib/ragweed/debuggertux.rb +502 -0
- data/lib/ragweed/detour.rb +223 -0
- data/lib/ragweed/ptr.rb +48 -0
- data/lib/ragweed/rasm/bblock.rb +73 -0
- data/lib/ragweed/rasm/isa.rb +1115 -0
- data/lib/ragweed/rasm.rb +59 -0
- data/lib/ragweed/sbuf.rb +197 -0
- data/lib/ragweed/trampoline.rb +103 -0
- data/lib/ragweed/utils.rb +182 -0
- data/lib/ragweed/wrap32/debugging.rb +401 -0
- data/lib/ragweed/wrap32/device.rb +49 -0
- data/lib/ragweed/wrap32/event.rb +50 -0
- data/lib/ragweed/wrap32/hooks.rb +39 -0
- data/lib/ragweed/wrap32/overlapped.rb +46 -0
- data/lib/ragweed/wrap32/process.rb +613 -0
- data/lib/ragweed/wrap32/process_token.rb +75 -0
- data/lib/ragweed/wrap32/thread_context.rb +142 -0
- data/lib/ragweed/wrap32/winx.rb +16 -0
- data/lib/ragweed/wrap32/wrap32.rb +583 -0
- data/lib/ragweed/wrap32.rb +59 -0
- data/lib/ragweed/wraposx/constants.rb +114 -0
- data/lib/ragweed/wraposx/kernelerrorx.rb +147 -0
- data/lib/ragweed/wraposx/region_info.rb +275 -0
- data/lib/ragweed/wraposx/structs.rb +102 -0
- data/lib/ragweed/wraposx/thread_context.rb +902 -0
- data/lib/ragweed/wraposx/thread_info.rb +160 -0
- data/lib/ragweed/wraposx/thread_info.rb.old +121 -0
- data/lib/ragweed/wraposx/wraposx.rb +356 -0
- data/lib/ragweed/wraposx.rb +60 -0
- data/lib/ragweed/wraptux/constants.rb +101 -0
- data/lib/ragweed/wraptux/process.rb +35 -0
- data/lib/ragweed/wraptux/threads.rb +7 -0
- data/lib/ragweed/wraptux/wraptux.rb +72 -0
- data/lib/ragweed/wraptux.rb +57 -0
- data/lib/ragweed.rb +112 -0
- data/ragweed.gemspec +102 -0
- data/spec/ragweed_spec.rb +7 -0
- data/spec/spec_helper.rb +16 -0
- data/test/test_ragweed.rb +0 -0
- metadata +121 -0
@@ -0,0 +1,502 @@
|
|
1
|
+
require ::File.join(::File.dirname(__FILE__),'wraptux')
|
2
|
+
|
3
|
+
module Ragweed; end
|
4
|
+
|
5
|
+
## Debugger class for Linux
|
6
|
+
## You can use this class in 2 ways:
|
7
|
+
##
|
8
|
+
## (1) You can create instances of Debuggertux and use them to set and handle
|
9
|
+
## breakpoints.
|
10
|
+
##
|
11
|
+
## (2) If you want to do more advanced event handling, you can subclass from
|
12
|
+
## debugger and define your own on_whatever events. If you handle an event
|
13
|
+
## that Debuggertux already handles, call "super", too.
|
14
|
+
class Ragweed::Debuggertux
|
15
|
+
attr_reader :pid, :status, :exited, :signal
|
16
|
+
attr_accessor :breakpoints, :mapped_regions, :process
|
17
|
+
|
18
|
+
## Class to handle installing/uninstalling breakpoints
|
19
|
+
class Breakpoint
|
20
|
+
|
21
|
+
INT3 = 0xCC
|
22
|
+
|
23
|
+
attr_accessor :orig, :bppid, :function, :installed
|
24
|
+
attr_reader :addr
|
25
|
+
|
26
|
+
## ip: insertion point
|
27
|
+
## callable: lambda to be called when breakpoint is hit
|
28
|
+
## p: process ID
|
29
|
+
## name: name of breakpoint
|
30
|
+
def initialize(ip, callable, p, name = "")
|
31
|
+
@bppid = p
|
32
|
+
@function = name
|
33
|
+
@addr = ip
|
34
|
+
@callable = callable
|
35
|
+
@installed = false
|
36
|
+
@exited = false
|
37
|
+
@orig = 0
|
38
|
+
end
|
39
|
+
|
40
|
+
def install
|
41
|
+
@orig = Ragweed::Wraptux::ptrace(Ragweed::Wraptux::Ptrace::PEEK_TEXT, @bppid, @addr, 0)
|
42
|
+
if @orig != -1
|
43
|
+
n = (@orig & ~0xff) | INT3;
|
44
|
+
Ragweed::Wraptux::ptrace(Ragweed::Wraptux::Ptrace::POKE_TEXT, @bppid, @addr, n)
|
45
|
+
@installed = true
|
46
|
+
else
|
47
|
+
@installed = false
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def uninstall
|
52
|
+
if @orig != INT3
|
53
|
+
a = Ragweed::Wraptux::ptrace(Ragweed::Wraptux::Ptrace::POKE_TEXT, @bppid, @addr, @orig)
|
54
|
+
@installed = false
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def installed?; @installed; end
|
59
|
+
def call(*args); @callable.call(*args) if @callable != nil; end
|
60
|
+
end
|
61
|
+
|
62
|
+
## init object
|
63
|
+
## p: pid of process to be debugged
|
64
|
+
## opts: default options for automatically doing things (attach and install)
|
65
|
+
def initialize(pid, opts) ## Debuggertux Class
|
66
|
+
if p.to_i.kind_of? Fixnum
|
67
|
+
@pid = pid.to_i
|
68
|
+
else
|
69
|
+
raise "Provide a PID"
|
70
|
+
end
|
71
|
+
|
72
|
+
@opts = opts
|
73
|
+
|
74
|
+
default_opts(opts)
|
75
|
+
@installed = false
|
76
|
+
@attached = false
|
77
|
+
|
78
|
+
@mapped_regions = Hash.new
|
79
|
+
@breakpoints = Hash.new
|
80
|
+
@opts.each { |k, v| try(k) if v }
|
81
|
+
|
82
|
+
@process = Ragweed::Process.new(@pid)
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.find_by_regex(rx)
|
86
|
+
Dir.glob("/proc/*/cmdline").each do |x|
|
87
|
+
x.gsub(/^\/proc\/(\d+)\/cmdline$/) do |ln|
|
88
|
+
f = File.read(ln)
|
89
|
+
if f =~ rx and $1.to_i != ::Process.pid.to_i
|
90
|
+
return f
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
nil
|
95
|
+
end
|
96
|
+
|
97
|
+
def install_bps
|
98
|
+
@breakpoints.each do |k,v|
|
99
|
+
v.install
|
100
|
+
end
|
101
|
+
@installed = true
|
102
|
+
end
|
103
|
+
|
104
|
+
def uninstall_bps
|
105
|
+
@breakpoints.each do |k,v|
|
106
|
+
v.uninstall
|
107
|
+
end
|
108
|
+
@installed = false
|
109
|
+
end
|
110
|
+
|
111
|
+
## This has not been fully tested yet
|
112
|
+
def set_options(option)
|
113
|
+
r = Ragweed::Wraptux::ptrace(Ragweed::Wraptux::Ptrace::SETOPTIONS, @pid, 0, option)
|
114
|
+
end
|
115
|
+
|
116
|
+
## Attach calls install_bps so dont forget to call breakpoint_set
|
117
|
+
## BEFORE attach or explicitly call install_bps
|
118
|
+
def attach(opts=@opts)
|
119
|
+
r = Ragweed::Wraptux::ptrace(Ragweed::Wraptux::Ptrace::ATTACH, @pid, 0, 0)
|
120
|
+
if r != -1
|
121
|
+
@attached = true
|
122
|
+
on_attach
|
123
|
+
self.install_bps if (opts[:install] and not @installed)
|
124
|
+
else
|
125
|
+
raise "Attach failed!"
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
## This method returns a hash of mapped regions
|
130
|
+
## The hash is also stored as @mapped_regions
|
131
|
+
## key = Start address of region
|
132
|
+
## value = Size of the region
|
133
|
+
def mapped
|
134
|
+
@mapped_regions.clear if @mapped_regions
|
135
|
+
|
136
|
+
File.open("/proc/#{pid}/maps") do |f|
|
137
|
+
f.each_line do |l|
|
138
|
+
e = l.split(' ',2).first
|
139
|
+
s,e = e.split('-').map{|x| x.to_i(16)}
|
140
|
+
sz = e - s
|
141
|
+
@mapped_regions.store(s, sz)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
@mapped_regions
|
145
|
+
end
|
146
|
+
|
147
|
+
# Return a name for a range if possible. greedy match
|
148
|
+
# returns the first found
|
149
|
+
def get_mapping_name(val)
|
150
|
+
File.open("/proc/#{pid}/maps") do |f|
|
151
|
+
f.each_line do |l|
|
152
|
+
range, perms, offset, dev, inode, pathname = l.chomp.split(" ")
|
153
|
+
base, max = range.split('-').map{|x| x.to_i(16)}
|
154
|
+
if base <= val && val <= max
|
155
|
+
return pathname
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
nil
|
160
|
+
end
|
161
|
+
alias mapping_name get_mapping_name
|
162
|
+
|
163
|
+
## Return a range via mapping name
|
164
|
+
def get_mapping_by_name(name, exact = true)
|
165
|
+
ret = []
|
166
|
+
File.open("/proc/#{pid}/maps") do |f|
|
167
|
+
f.each_line do |l|
|
168
|
+
range, perms, offset, dev, inode, pathname = l.chomp.split(" ",6)
|
169
|
+
base, max = range.split('-').map{|x| x.to_i(16)}
|
170
|
+
if pathname
|
171
|
+
if exact && pathname == name
|
172
|
+
ret << range.split('-').map{|x| x.to_i(16)}
|
173
|
+
elsif pathname.match(name)
|
174
|
+
ret << range.split('-').map{|x| x.to_i(16)}
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
ret
|
180
|
+
end
|
181
|
+
alias mapping_by_name get_mapping_by_name
|
182
|
+
|
183
|
+
## Helper method for retrieving stack range
|
184
|
+
def get_stack_range
|
185
|
+
get_mapping_by_name('[stack]')
|
186
|
+
end
|
187
|
+
alias stack_range get_stack_range
|
188
|
+
|
189
|
+
## Helper method for retrieving heap range
|
190
|
+
def get_heap_range
|
191
|
+
get_mapping_by_name('[heap]')
|
192
|
+
end
|
193
|
+
alias heap_range get_heap_range
|
194
|
+
|
195
|
+
## Parse procfs and create a hash containing
|
196
|
+
## a listing of each mapped shared object
|
197
|
+
def self.shared_libraries(p)
|
198
|
+
raise "pid is 0" if p.to_i == 0
|
199
|
+
|
200
|
+
if @shared_objects
|
201
|
+
@shared_objects.clear
|
202
|
+
else
|
203
|
+
@shared_objects = Hash.new
|
204
|
+
end
|
205
|
+
|
206
|
+
File.open("/proc/#{p}/maps") do |f|
|
207
|
+
f.each_line do |l|
|
208
|
+
if l =~ /[a-zA-Z0-9].so/ && l =~ /xp /
|
209
|
+
lib = l.split(' ', 6)
|
210
|
+
sa = l.split('-', 0)
|
211
|
+
|
212
|
+
if lib[5] =~ /vdso/
|
213
|
+
next
|
214
|
+
end
|
215
|
+
|
216
|
+
lib = lib[5].strip
|
217
|
+
lib.gsub!(/[\s\n]+/, "")
|
218
|
+
@shared_objects.store(sa[0], lib)
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
@shared_objects
|
223
|
+
end
|
224
|
+
|
225
|
+
# instance method for above
|
226
|
+
# returns a hash of the mapped shared libraries
|
227
|
+
def shared_libraries
|
228
|
+
self.class.shared_libraries(@pid)
|
229
|
+
end
|
230
|
+
|
231
|
+
## Search a specific page for a value
|
232
|
+
## Should be used by most of the search_* methods
|
233
|
+
def search_page(base, max, val)
|
234
|
+
loc = Array.new
|
235
|
+
|
236
|
+
while base.to_i < max.to_i
|
237
|
+
r = Ragweed::Wraptux::ptrace(Ragweed::Wraptux::Ptrace::PEEK_TEXT, @pid, base, 0)
|
238
|
+
if r == val
|
239
|
+
loc.push(base)
|
240
|
+
end
|
241
|
+
base += 1
|
242
|
+
end
|
243
|
+
|
244
|
+
loc
|
245
|
+
end
|
246
|
+
|
247
|
+
## Search the heap for a value
|
248
|
+
def search_heap(val)
|
249
|
+
loc = Array.new
|
250
|
+
File.open("/proc/#{pid}/maps") do |f|
|
251
|
+
f.each_line do |l|
|
252
|
+
if l =~ /\[heap\]/
|
253
|
+
s,e = l.split('-')
|
254
|
+
e = e.split(' ').first
|
255
|
+
s = s.to_i(16)
|
256
|
+
e = e.to_i(16)
|
257
|
+
sz = e - s
|
258
|
+
max = s + sz
|
259
|
+
loc = search_page(s, max, val)
|
260
|
+
end
|
261
|
+
end
|
262
|
+
end
|
263
|
+
loc
|
264
|
+
end
|
265
|
+
|
266
|
+
## Search all mapped regions for a value
|
267
|
+
def search_process(val)
|
268
|
+
loc = Array.new
|
269
|
+
self.mapped
|
270
|
+
@mapped_regions.each_pair do |k,v|
|
271
|
+
if k == 0 or v == 0
|
272
|
+
next
|
273
|
+
end
|
274
|
+
max = k+v
|
275
|
+
loc.concat(search_page(k, max, val))
|
276
|
+
end
|
277
|
+
loc
|
278
|
+
end
|
279
|
+
|
280
|
+
def continue
|
281
|
+
on_continue
|
282
|
+
Ragweed::Wraptux::ptrace(Ragweed::Wraptux::Ptrace::CONTINUE, @pid, 0, 0)
|
283
|
+
end
|
284
|
+
|
285
|
+
def detach
|
286
|
+
on_detach
|
287
|
+
Ragweed::Wraptux::ptrace(Ragweed::Wraptux::Ptrace::DETACH, @pid, 0, 0)
|
288
|
+
end
|
289
|
+
|
290
|
+
def single_step
|
291
|
+
on_single_step
|
292
|
+
ret = Ragweed::Wraptux::ptrace(Ragweed::Wraptux::Ptrace::STEP, @pid, 1, 0)
|
293
|
+
end
|
294
|
+
|
295
|
+
## Adds a breakpoint to be installed
|
296
|
+
## ip: Insertion point
|
297
|
+
## name: name of breakpoint
|
298
|
+
## callable: object to .call at breakpoint
|
299
|
+
def breakpoint_set(ip, name="", callable=nil, &block)
|
300
|
+
if not callable and block_given?
|
301
|
+
callable = block
|
302
|
+
end
|
303
|
+
@breakpoints.each_key { |k| if k == ip then return end }
|
304
|
+
bp = Breakpoint.new(ip, callable, @pid, name)
|
305
|
+
@breakpoints[ip] = bp
|
306
|
+
end
|
307
|
+
|
308
|
+
## Remove a breakpoint by ip
|
309
|
+
def breakpoint_clear(ip)
|
310
|
+
bp = @breakpoints[ip]
|
311
|
+
return nil if bp.nil?
|
312
|
+
bp.uninstall
|
313
|
+
end
|
314
|
+
|
315
|
+
## loop for wait()
|
316
|
+
## times: the number of wait calls to make
|
317
|
+
def loop(times=nil)
|
318
|
+
if times.kind_of? Numeric
|
319
|
+
times.times do
|
320
|
+
self.wait
|
321
|
+
end
|
322
|
+
elsif times.nil?
|
323
|
+
self.wait while not @exited
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
def wexitstatus(status)
|
328
|
+
(((status) & 0xff00) >> 8)
|
329
|
+
end
|
330
|
+
|
331
|
+
def wtermsig(status)
|
332
|
+
((status) & 0x7f)
|
333
|
+
end
|
334
|
+
|
335
|
+
## This wait must be smart, it has to wait for a signal
|
336
|
+
## when SIGTRAP is received we need to see if one of our
|
337
|
+
## breakpoints has fired. If it has then execute the block
|
338
|
+
## originally stored with it. If its a different signal,
|
339
|
+
## then process it accordingly and move on
|
340
|
+
def wait(opts = 0)
|
341
|
+
r, status = Ragweed::Wraptux::waitpid(@pid, opts)
|
342
|
+
wstatus = wtermsig(status)
|
343
|
+
@signal = wexitstatus(status)
|
344
|
+
event_code = (status >> 16)
|
345
|
+
found = false
|
346
|
+
|
347
|
+
if r[0] != -1 ## Check the ret
|
348
|
+
case ## FIXME - I need better logic (use Signal module)
|
349
|
+
when wstatus == 0 ##WIFEXITED
|
350
|
+
@exited = true
|
351
|
+
try(:on_exit)
|
352
|
+
when wstatus != 0x7f ##WIFSIGNALED
|
353
|
+
@exited = false
|
354
|
+
try(:on_signal)
|
355
|
+
when @signal == Ragweed::Wraptux::Signal::SIGINT
|
356
|
+
try(:on_sigint)
|
357
|
+
self.continue
|
358
|
+
when @signal == Ragweed::Wraptux::Signal::SIGSEGV
|
359
|
+
try(:on_segv)
|
360
|
+
when @signal == Ragweed::Wraptux::Signal::SIGILL
|
361
|
+
try(:on_illegal_instruction)
|
362
|
+
when @signal == Ragweed::Wraptux::Signal::SIGIOT
|
363
|
+
try(:on_iot_trap)
|
364
|
+
self.continue
|
365
|
+
when @signal == Ragweed::Wraptux::Signal::SIGTRAP
|
366
|
+
try(:on_sigtrap)
|
367
|
+
r = self.get_registers
|
368
|
+
eip = r.eip
|
369
|
+
eip -= 1
|
370
|
+
case
|
371
|
+
when @breakpoints.has_key?(eip)
|
372
|
+
found = true
|
373
|
+
try(:on_breakpoint)
|
374
|
+
self.continue
|
375
|
+
when event_code == Ragweed::Wraptux::Ptrace::EventCodes::FORK
|
376
|
+
p = FFI::MemoryPointer.new(:int, 1)
|
377
|
+
Ragweed::Wraptux::ptrace(Ragweed::Wraptux::Ptrace::GETEVENTMSG, @pid, 0, p.to_i)
|
378
|
+
## Fix up the PID in each breakpoint
|
379
|
+
if (1..65535) === p.get_int32(0) && @opts[:fork] == true
|
380
|
+
@breakpoints.each_pair do |k,v|
|
381
|
+
v.each do |b|
|
382
|
+
b.bppid = p[:pid]
|
383
|
+
end
|
384
|
+
end
|
385
|
+
|
386
|
+
@pid = p[:pid]
|
387
|
+
try(:on_fork_child, @pid)
|
388
|
+
end
|
389
|
+
when event_code == Ragweed::Wraptux::Ptrace::EventCodes::EXEC
|
390
|
+
when event_code == Ragweed::Wraptux::Ptrace::EventCodes::CLONE
|
391
|
+
when event_code == Ragweed::Wraptux::Ptrace::EventCodes::VFORK
|
392
|
+
when event_code == Ragweed::Wraptux::Ptrace::EventCodes::EXIT
|
393
|
+
## Not done yet
|
394
|
+
else
|
395
|
+
self.continue
|
396
|
+
end
|
397
|
+
when @signal == Ragweed::Wraptux::Signal::SIGCHLD
|
398
|
+
try(:on_sigchild)
|
399
|
+
when @signal == Ragweed::Wraptux::Signal::SIGTERM
|
400
|
+
try(:on_sigterm)
|
401
|
+
when @signal == Ragweed::Wraptux::Signal::SIGCONT
|
402
|
+
try(:on_continue)
|
403
|
+
self.continue
|
404
|
+
when @signal == Ragweed::Wraptux::Signal::SIGSTOP
|
405
|
+
try(:on_sigstop)
|
406
|
+
Ragweed::Wraptux::kill(@pid, Ragweed::Wraptux::Signal::SIGCONT)
|
407
|
+
self.continue
|
408
|
+
when @signal == Ragweed::Wraptux::Signal::SIGWINCH
|
409
|
+
self.continue
|
410
|
+
else
|
411
|
+
raise "Add more signal handlers (##{@signal})"
|
412
|
+
end
|
413
|
+
end
|
414
|
+
end
|
415
|
+
|
416
|
+
def self.threads(pid)
|
417
|
+
a = []
|
418
|
+
begin
|
419
|
+
a = Dir.entries("/proc/#{pid}/task/")
|
420
|
+
a.delete_if {|x| x == '.' || x == '..'}
|
421
|
+
rescue
|
422
|
+
puts "No such PID: #{pid}"
|
423
|
+
end
|
424
|
+
a
|
425
|
+
end
|
426
|
+
|
427
|
+
def get_registers
|
428
|
+
regs = FFI::MemoryPointer.new(Ragweed::Wraptux::PTRegs, 1)
|
429
|
+
Ragweed::Wraptux::ptrace(Ragweed::Wraptux::Ptrace::GETREGS, @pid, 0, regs.to_i)
|
430
|
+
return Ragweed::Wraptux::PTRegs.new regs
|
431
|
+
end
|
432
|
+
|
433
|
+
def set_registers(regs)
|
434
|
+
Ragweed::Wraptux::ptrace(Ragweed::Wraptux::Ptrace::SETREGS, @pid, 0, regs.to_ptr.address)
|
435
|
+
end
|
436
|
+
|
437
|
+
## Here we need to do something about the bp
|
438
|
+
## we just hit. We have a block to execute.
|
439
|
+
## Remember if you implement this on your own
|
440
|
+
## make sure to call super, and also realize
|
441
|
+
## EIP won't look correct until this runs
|
442
|
+
def on_breakpoint
|
443
|
+
r = get_registers
|
444
|
+
eip = r.eip
|
445
|
+
eip -= 1
|
446
|
+
|
447
|
+
## Call the block associated with the breakpoint
|
448
|
+
@breakpoints[eip].call(r, self)
|
449
|
+
|
450
|
+
## The block may have called breakpoint_clear
|
451
|
+
del = true if !@breakpoints[eip].installed?
|
452
|
+
|
453
|
+
## Uninstall and single step the bp
|
454
|
+
@breakpoints[eip].uninstall
|
455
|
+
r.eip = eip
|
456
|
+
set_registers(r)
|
457
|
+
single_step
|
458
|
+
|
459
|
+
## ptrace peektext returns -1 upon reinstallation of bp without calling
|
460
|
+
## waitpid() if that occurs the breakpoint cannot be reinstalled
|
461
|
+
Ragweed::Wraptux::waitpid(@pid, 0)
|
462
|
+
|
463
|
+
if del == true
|
464
|
+
## The breakpoint block may have called breakpoint_clear
|
465
|
+
@breakpoints.delete(eip)
|
466
|
+
else
|
467
|
+
@breakpoints[eip].install
|
468
|
+
end
|
469
|
+
end
|
470
|
+
|
471
|
+
def on_attach() end
|
472
|
+
def on_single_step() end
|
473
|
+
def on_continue() end
|
474
|
+
def on_exit() end
|
475
|
+
def on_signal() end
|
476
|
+
def on_sigint() end
|
477
|
+
def on_segv() end
|
478
|
+
def on_illegal_instruction() end
|
479
|
+
def on_sigtrap() end
|
480
|
+
def on_fork_child(pid) end
|
481
|
+
def on_sigchild() end
|
482
|
+
def on_sigterm() end
|
483
|
+
def on_sigstop() end
|
484
|
+
def on_iot_trap() end
|
485
|
+
|
486
|
+
def print_registers
|
487
|
+
regs = get_registers
|
488
|
+
puts "eip %08x" % regs.eip
|
489
|
+
puts "ebp %08x" % regs.ebp
|
490
|
+
puts "esi %08x" % regs.esi
|
491
|
+
puts "edi %08x" % regs.edi
|
492
|
+
puts "esp %08x" % regs.esp
|
493
|
+
puts "eax %08x" % regs.eax
|
494
|
+
puts "ebx %08x" % regs.ebx
|
495
|
+
puts "ecx %08x" % regs.ecx
|
496
|
+
puts "edx %08x" % regs.edx
|
497
|
+
end
|
498
|
+
|
499
|
+
def default_opts(opts)
|
500
|
+
@opts = @opts.merge(opts)
|
501
|
+
end
|
502
|
+
end
|
@@ -0,0 +1,223 @@
|
|
1
|
+
class Ragweed::Detour
|
2
|
+
# "Ghetto Detours", as Scott Stender might say. Patch subprograms
|
3
|
+
# in to running programs as a hooking mechanism.
|
4
|
+
class Detour
|
5
|
+
attr_reader :snarfed
|
6
|
+
attr_reader :dpoint
|
7
|
+
attr_reader :stack
|
8
|
+
|
9
|
+
# Easiest way to do this is just to ask WinProcess#detour. Wants
|
10
|
+
# "p" to be a pointer into the process, presumable returned from
|
11
|
+
# WinProcess#get_proc.
|
12
|
+
#
|
13
|
+
# In theory, "p" should be OK anywhere as long as there are 5
|
14
|
+
# bytes of instructions before the end of the basic block its
|
15
|
+
# in. In practice, this only really stands a chance of working
|
16
|
+
# if "p" points to a function prologue.
|
17
|
+
def initialize(p, opts={})
|
18
|
+
@p = p.p
|
19
|
+
@dpoint = p
|
20
|
+
@opts = opts
|
21
|
+
@a = @opts[:arena] || @p.arena
|
22
|
+
@stack = @a.alloc(2048)
|
23
|
+
@snarfed = snarf_prologue
|
24
|
+
end
|
25
|
+
|
26
|
+
# Release the detour and its associated memory, unpatch the
|
27
|
+
# target function.
|
28
|
+
def release
|
29
|
+
@dpoint.write(@snarfed)
|
30
|
+
@a.release if not @opts[:arena]
|
31
|
+
end
|
32
|
+
|
33
|
+
# Patch the target function. There is a 70% chance this will
|
34
|
+
# totally fuck your process.
|
35
|
+
#
|
36
|
+
# You would be wise to have the threads in the process suspended
|
37
|
+
# while you do this, but I'm not going to do it for you.
|
38
|
+
def call
|
39
|
+
# a Detours-style trampoline --- the location we patch the
|
40
|
+
# target function to jump to --- consists of:
|
41
|
+
#
|
42
|
+
# - A stack switch (to push/pop w/o fucking the program)
|
43
|
+
# - A context save
|
44
|
+
# - The Detour code
|
45
|
+
# - A context restore
|
46
|
+
# - A stack restore
|
47
|
+
# - The code we patched out of the target
|
48
|
+
# - A jump back to the target function (after the prologue)
|
49
|
+
|
50
|
+
# Do this now to make room for the (probably 5 byte) jump.
|
51
|
+
# We don't know what the address will be until we allocate.
|
52
|
+
jumpback = (Jmp 0xDEADBEEF) # patch back later
|
53
|
+
|
54
|
+
# Build the trampoline
|
55
|
+
tramp = trampoline(@stack).assemble
|
56
|
+
|
57
|
+
# Figure out how big the whole mess will be, allocate it
|
58
|
+
tsz = tramp.size + @snarfed.size + jumpback.to_s.size
|
59
|
+
tbuf = @a.alloc(tsz + 10)
|
60
|
+
|
61
|
+
# assume trampoline is ahead of the patched program text;
|
62
|
+
# jump to [dpoint+patch]
|
63
|
+
jumpback.dst = (@dpoint.to_i + @snarfed.size) - (tbuf + tsz)
|
64
|
+
|
65
|
+
# Write it into memory. It's not "live" yet because we haven't
|
66
|
+
# patched the target function.
|
67
|
+
@p.write(tbuf, tramp + @snarfed + jumpback.to_s)
|
68
|
+
|
69
|
+
# But now it is. =)
|
70
|
+
@p.write(@dpoint, injection(tbuf).assemRASble)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Hook function. Override this in subclasses to provide different
|
74
|
+
# behavior.
|
75
|
+
def inner_block
|
76
|
+
i = Ragweed::Rasm::Subprogram.new
|
77
|
+
i.<< Int(3)
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
# No user-servicable parts below.
|
83
|
+
|
84
|
+
# Pull at least 5 bytes of instructions out of the prologue, using
|
85
|
+
# the disassembler, to make room for our patch jump. Save it, so
|
86
|
+
# we can unpatch later.
|
87
|
+
def snarf_prologue
|
88
|
+
i = 0
|
89
|
+
(buf = @dpoint.read(20)).distorm.each do |insn|
|
90
|
+
i += insn.size
|
91
|
+
break if i >= 5
|
92
|
+
end
|
93
|
+
buf[0...i]
|
94
|
+
end
|
95
|
+
|
96
|
+
# Create the Jmp instruction that implements the patch; you can't
|
97
|
+
# do this until you know where the trampoline was actually injected
|
98
|
+
# into the process.
|
99
|
+
def injection(tramp)
|
100
|
+
here = @dpoint
|
101
|
+
there = tramp
|
102
|
+
|
103
|
+
if there < here
|
104
|
+
goto = -((here - there) + 5)
|
105
|
+
else
|
106
|
+
goto = there - here
|
107
|
+
end
|
108
|
+
|
109
|
+
i = Ragweed::Rasm::Subprogram.new
|
110
|
+
i.<< Ragweed::Rasm::Jmp(goto.to_i)
|
111
|
+
end
|
112
|
+
|
113
|
+
# Create the detours trampoline:
|
114
|
+
def trampoline(stack)
|
115
|
+
i = Ragweed::Rasm::Subprogram.new
|
116
|
+
i.concat push_stack(stack) # 1. Give us a new stack
|
117
|
+
i.concat save_all # 2. Save all the GPRs just in case
|
118
|
+
i.concat inner_block # 3. The hook function
|
119
|
+
i.concat restore_all # 4. Restore all the GPRs.
|
120
|
+
i.concat pop_stack # 5. Restore the stack
|
121
|
+
return i
|
122
|
+
end
|
123
|
+
|
124
|
+
# Swap in a new stack, pushing the old stack address
|
125
|
+
# onto the top of it.
|
126
|
+
def push_stack(addr, sz=2048)
|
127
|
+
i = Ragweed::Rasm::Subprogram.new
|
128
|
+
i.<< Ragweed::Rasm::Push(eax)
|
129
|
+
i.<< Ragweed::Rasm::Mov(eax, addr+(sz-4))
|
130
|
+
i.<< Ragweed::Rasm::Mov([eax], esp)
|
131
|
+
i.<< Ragweed::Rasm::Pop(eax)
|
132
|
+
i.<< Ragweed::Rasm::Mov(esp, addr+(sz-4))
|
133
|
+
end
|
134
|
+
|
135
|
+
# Swap out the new stack.
|
136
|
+
def pop_stack
|
137
|
+
i = Ragweed::Rasm::Subprogram.new
|
138
|
+
i.<< Ragweed::Rasm::Pop(esp)
|
139
|
+
i.<< Ragweed::Rasm::Add(esp, 4)
|
140
|
+
end
|
141
|
+
|
142
|
+
# Just push all the registers in order
|
143
|
+
def save_all
|
144
|
+
i = Ragweed::Rasm::Subprogram.new
|
145
|
+
[eax,ecx,edx,ebx,ebp,esi,edi].each do |r|
|
146
|
+
i.<< Ragweed::Rasm::Push(r)
|
147
|
+
end
|
148
|
+
i
|
149
|
+
end
|
150
|
+
|
151
|
+
# Just pop all the registers
|
152
|
+
def restore_all
|
153
|
+
i = Ragweed::Rasm::Subprogram.new
|
154
|
+
[edi,esi,ebp,ebx,edx,ecx,eax].each do |r|
|
155
|
+
i.<< Ragweed::Rasm::Pop(r)
|
156
|
+
end
|
157
|
+
i
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
# A breakpoint implemented as a Detour. TODO not tested.
|
162
|
+
class Dbreak < Detour
|
163
|
+
attr_reader :ev1, :ev2
|
164
|
+
|
165
|
+
# accepts:
|
166
|
+
# :ev1: reuse events from somewhere else
|
167
|
+
# :ev2:
|
168
|
+
def initialize(*args)
|
169
|
+
super
|
170
|
+
@ev1 = @opts[:ev1] || WinEvent.new
|
171
|
+
@ev2 = @opts[:ev2] || WinEvent.new
|
172
|
+
|
173
|
+
# create the state block that the eventpair shim wants:
|
174
|
+
mem = @a.alloc(100)
|
175
|
+
@data = mem
|
176
|
+
|
177
|
+
# ghetto vtbl
|
178
|
+
swch = ["OpenProcess",
|
179
|
+
"DuplicateHandle",
|
180
|
+
"ResetEvent",
|
181
|
+
"SetEvent",
|
182
|
+
"WaitForSingleObject",
|
183
|
+
"GetCurrentThreadId"].
|
184
|
+
map {|x| @p.get_proc("kernel32!#{x}").to_i}.
|
185
|
+
pack("LLLLLL")
|
186
|
+
|
187
|
+
# ghetto instance vars
|
188
|
+
state = [@p.w.get_current_process_id, @ev1.handle, @ev2.handle].
|
189
|
+
pack("LLL")
|
190
|
+
@data.write(swch + state)
|
191
|
+
end
|
192
|
+
|
193
|
+
def inner_block
|
194
|
+
i = Ragweed::Rasm::Subprogram.new
|
195
|
+
i.<< Push(eax)
|
196
|
+
i.<< Xor(eax, eax)
|
197
|
+
i.<< Or(eax, @data)
|
198
|
+
i.<< Push(eax)
|
199
|
+
i.<< Call(1) # cheesy in the extreme: fake a call
|
200
|
+
# so I don't have to change my event shim
|
201
|
+
i.<< Nop.new
|
202
|
+
i.<< Nop.new
|
203
|
+
i.<< Nop.new
|
204
|
+
i.<< Nop.new
|
205
|
+
i.<< Nop.new
|
206
|
+
s = event_pair_stub
|
207
|
+
s[-1] = Add(esp, 4)
|
208
|
+
i.concat(s)
|
209
|
+
i.<< Pop(eax)
|
210
|
+
return i
|
211
|
+
end
|
212
|
+
|
213
|
+
# in theory, loop on this breakpoint
|
214
|
+
def on(&block)
|
215
|
+
puts "#{ @p.pid }: #{ @ev1.handle }" # in case we need to release
|
216
|
+
loop do
|
217
|
+
@ev1.wait
|
218
|
+
yield
|
219
|
+
@ev2.signal
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|