ragweed 0.2.0-java
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.
- 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
|