tduehr-ragweed 0.1.5
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 +15 -0
- data/README.rdoc +35 -0
- data/README.txt +9 -0
- data/Rakefile +30 -0
- data/examples/hittracertux.rb +48 -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 +23 -0
- data/lib/ragweed/arena.rb +55 -0
- data/lib/ragweed/blocks.rb +128 -0
- data/lib/ragweed/debugger32.rb +338 -0
- data/lib/ragweed/debuggerosx.rb +419 -0
- data/lib/ragweed/debuggertux.rb +347 -0
- data/lib/ragweed/detour.rb +223 -0
- data/lib/ragweed/ptr.rb +48 -0
- data/lib/ragweed/rasm/isa.rb +1046 -0
- data/lib/ragweed/rasm/util.rb +26 -0
- data/lib/ragweed/rasm.rb +53 -0
- data/lib/ragweed/sbuf.rb +197 -0
- data/lib/ragweed/trampoline.rb +103 -0
- data/lib/ragweed/utils.rb +87 -0
- data/lib/ragweed/wrap32/debugging.rb +163 -0
- data/lib/ragweed/wrap32/device.rb +49 -0
- data/lib/ragweed/wrap32/event.rb +50 -0
- data/lib/ragweed/wrap32/hooks.rb +23 -0
- data/lib/ragweed/wrap32/overlapped.rb +46 -0
- data/lib/ragweed/wrap32/process.rb +506 -0
- data/lib/ragweed/wrap32/process_token.rb +59 -0
- data/lib/ragweed/wrap32/thread_context.rb +208 -0
- data/lib/ragweed/wrap32/winx.rb +16 -0
- data/lib/ragweed/wrap32/wrap32.rb +526 -0
- data/lib/ragweed/wrap32.rb +53 -0
- data/lib/ragweed/wraposx/constants.rb +101 -0
- data/lib/ragweed/wraposx/kernelerrorx.rb +147 -0
- data/lib/ragweed/wraposx/region_info.rb +244 -0
- data/lib/ragweed/wraposx/thread_context.rb +203 -0
- data/lib/ragweed/wraposx/thread_info.rb +213 -0
- data/lib/ragweed/wraposx/wraposx.rb +376 -0
- data/lib/ragweed/wraposx.rb +53 -0
- data/lib/ragweed/wraptux/constants.rb +68 -0
- data/lib/ragweed/wraptux/threads.rb +3 -0
- data/lib/ragweed/wraptux/wraptux.rb +76 -0
- data/lib/ragweed/wraptux.rb +53 -0
- data/lib/ragweed.rb +84 -0
- data/spec/ragweed_spec.rb +7 -0
- data/spec/spec_helper.rb +16 -0
- data/tasks/ann.rake +80 -0
- data/tasks/bones.rake +20 -0
- data/tasks/gem.rake +201 -0
- data/tasks/git.rake +40 -0
- data/tasks/notes.rake +27 -0
- data/tasks/post_load.rake +34 -0
- data/tasks/rdoc.rake +51 -0
- data/tasks/rubyforge.rake +55 -0
- data/tasks/setup.rb +292 -0
- data/tasks/spec.rake +54 -0
- data/tasks/svn.rake +47 -0
- data/tasks/test.rake +40 -0
- data/tasks/zentest.rake +36 -0
- data/test/test_ragweed.rb +0 -0
- metadata +127 -0
@@ -0,0 +1,347 @@
|
|
1
|
+
require 'ragweed/wraptux'
|
2
|
+
|
3
|
+
## Modeled after wraposx written by tduehr
|
4
|
+
|
5
|
+
module Ragweed; end
|
6
|
+
|
7
|
+
## Debugger class for Linux
|
8
|
+
## You can use this class in 2 ways:
|
9
|
+
##
|
10
|
+
## (1) You can create instances of Debuggertux and use them to set and handle
|
11
|
+
## breakpoints.
|
12
|
+
##
|
13
|
+
## (2) If you want to do more advanced event handling, you can subclass from
|
14
|
+
## debugger and define your own on_whatever events. If you handle an event
|
15
|
+
## that Debuggertux already handles, call "super", too.
|
16
|
+
class Ragweed::Debuggertux
|
17
|
+
include Ragweed
|
18
|
+
|
19
|
+
attr_reader :pid
|
20
|
+
attr_reader :status
|
21
|
+
attr_reader :exited
|
22
|
+
attr_accessor :breakpoints
|
23
|
+
|
24
|
+
## Class to handle installing/uninstalling breakpoints
|
25
|
+
class Breakpoint
|
26
|
+
|
27
|
+
INT3 = 0xCC ## obviously x86 specific debugger here
|
28
|
+
|
29
|
+
attr_accessor :orig
|
30
|
+
attr_reader :addr
|
31
|
+
attr_accessor :function
|
32
|
+
|
33
|
+
## bp: parent for method_missing calls
|
34
|
+
## ip: insertion point
|
35
|
+
## callable: lambda to be called when breakpoint is hit
|
36
|
+
## name: name of breakpoint
|
37
|
+
def initialize(bp, ip, callable, name = "")
|
38
|
+
@@bpid ||= 0
|
39
|
+
@bp = bp
|
40
|
+
@function = name
|
41
|
+
@addr = ip
|
42
|
+
@callable = callable
|
43
|
+
@installed = false
|
44
|
+
@orig = 0
|
45
|
+
@bpid = (@@bpid += 1)
|
46
|
+
end ## breakpoint initialize
|
47
|
+
|
48
|
+
## Install a breakpoint (replace instruction with int3)
|
49
|
+
def install
|
50
|
+
## Replace the original instruction with an int3
|
51
|
+
@orig = Ragweed::Wraptux::ptrace(Ragweed::Wraptux::Ptrace::PEEK_TEXT, $ppid, @addr, 0) ## Backup the old inst
|
52
|
+
if @orig != -1
|
53
|
+
new = (@orig & ~0xff) | INT3;
|
54
|
+
Ragweed::Wraptux::ptrace(Ragweed::Wraptux::Ptrace::POKE_TEXT, $ppid, @addr, new)
|
55
|
+
@installed = true
|
56
|
+
else
|
57
|
+
@installed = false
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
## Uninstall the breakpoint
|
62
|
+
def uninstall
|
63
|
+
## Put back the original instruction
|
64
|
+
if @orig != INT3
|
65
|
+
Ragweed::Wraptux::ptrace(Ragweed::Wraptux::Ptrace::POKE_TEXT, $ppid, @addr, @orig)
|
66
|
+
@installed = false
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def installed?; @installed; end
|
71
|
+
def call(*args); @callable.call(*args) if @callable != nil; end
|
72
|
+
def method_missing(meth, *args); @bp.send(meth, *args); end
|
73
|
+
end ## Breakpoint Class
|
74
|
+
|
75
|
+
## init object
|
76
|
+
## p: pid of process to be debugged
|
77
|
+
## opts: default options for automatically doing things (attach and install)
|
78
|
+
def initialize(pid,opts={}) ## Debuggertux Class
|
79
|
+
@pid = pid
|
80
|
+
|
81
|
+
## FIXME - globals are bad...
|
82
|
+
$ppid = @pid ## temporary work around
|
83
|
+
@opts = opts
|
84
|
+
|
85
|
+
default_opts(opts)
|
86
|
+
@installed = false
|
87
|
+
@attached = false
|
88
|
+
|
89
|
+
## Store all breakpoints in this hash
|
90
|
+
@breakpoints = Hash.new do |h, k|
|
91
|
+
bps = Array.new
|
92
|
+
def bps.call(*args); each {|bp| bp.call(*args)}; end
|
93
|
+
def bps.install; each {|bp| bp.install}; end
|
94
|
+
def bps.uninstall; each {|bp| bp.uninstall}; end
|
95
|
+
def bps.orig; each {|bp| dp.orig}; end
|
96
|
+
h[k] = bps
|
97
|
+
end
|
98
|
+
@opts.each {|k, v| try(k) if v}
|
99
|
+
end
|
100
|
+
|
101
|
+
## This is crude!
|
102
|
+
def self.find_by_regex(rx)
|
103
|
+
a = Dir.entries("/proc/")
|
104
|
+
a.delete_if do |x| x == '.' end
|
105
|
+
a.delete_if do |x| x == '..' end
|
106
|
+
a.delete_if do |x| x =~ /[a-z]/ end
|
107
|
+
a.each do |x|
|
108
|
+
f = File.read("/proc/#{x}/cmdline")
|
109
|
+
if f =~ rx
|
110
|
+
return x
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
## Return an array of thread PIDs
|
116
|
+
def self.get_thread_pids(pid)
|
117
|
+
a = Dir.entries("/proc/#{pid}/task/")
|
118
|
+
a.delete_if do |x| x == '.' end
|
119
|
+
a.delete_if do |x| x == '..' end
|
120
|
+
end
|
121
|
+
|
122
|
+
def install_bps
|
123
|
+
@breakpoints.each do |k,v|
|
124
|
+
v.install
|
125
|
+
end
|
126
|
+
@installed = true
|
127
|
+
end
|
128
|
+
|
129
|
+
def uninstall_bps
|
130
|
+
@breakpoints.each do |k,v|
|
131
|
+
v.uninstall
|
132
|
+
end
|
133
|
+
@installed = false
|
134
|
+
end
|
135
|
+
|
136
|
+
## Attach calls install_bps so dont forget to call breakpoint_set
|
137
|
+
## BEFORE attach or explicitly call install_bps
|
138
|
+
def attach(opts=@opts)
|
139
|
+
Wraptux::ptrace(Ragweed::Wraptux::Ptrace::ATTACH, @pid, 0, 0)
|
140
|
+
@attached = true
|
141
|
+
self.install_bps if (opts[:install] and not @installed)
|
142
|
+
end
|
143
|
+
|
144
|
+
def continue
|
145
|
+
on_continue
|
146
|
+
Wraptux::ptrace(Ragweed::Wraptux::Ptrace::CONTINUE, @pid, 0, 0)
|
147
|
+
end
|
148
|
+
|
149
|
+
def detach
|
150
|
+
on_detach
|
151
|
+
Wraptux::ptrace(Ragweed::Wraptux::Ptrace::DETACH, @pid, 0, 0)
|
152
|
+
end
|
153
|
+
|
154
|
+
def stepp
|
155
|
+
on_stepp
|
156
|
+
ret = Wraptux::ptrace(Ragweed::Wraptux::Ptrace::STEP, @pid, 1, 0)
|
157
|
+
end
|
158
|
+
|
159
|
+
## Adds a breakpoint to be installed
|
160
|
+
## ip: Insertion point
|
161
|
+
## name: name of breakpoint
|
162
|
+
## callable: object to .call at breakpoint
|
163
|
+
def breakpoint_set(ip, name="", callable=nil, &block)
|
164
|
+
if not callable and block_given?
|
165
|
+
callable = block
|
166
|
+
end
|
167
|
+
@breakpoints[ip] << Breakpoint.new(self, ip, callable, name)
|
168
|
+
end
|
169
|
+
|
170
|
+
## remove breakpoint with id bpid at insertion point or
|
171
|
+
## remove all breakpoints at insertion point if bpid not given
|
172
|
+
## ip: Insertion point
|
173
|
+
## bpid: id of breakpoint to be removed
|
174
|
+
def breakpoint_clear(ip, bpid=nil)
|
175
|
+
if not bpid
|
176
|
+
@breakpoints[ip].uninstall
|
177
|
+
@breakpoints[ip].delete ip
|
178
|
+
else
|
179
|
+
found = nil
|
180
|
+
@breakpoints[ip].each_with_index do |bp, i|
|
181
|
+
if bp.bpid == bpid
|
182
|
+
found = i
|
183
|
+
if bp.orig != Breakpoint::INT3
|
184
|
+
if @breakpoints[op][i+1]
|
185
|
+
@breakpoints[ip][i + 1].orig = bp.orig
|
186
|
+
else
|
187
|
+
bp.uninstall
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
raise "could not find bp ##{bpid} at ##{ip}" if not found
|
193
|
+
@breakpoints[ip].delete_at(found) if found
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
##loop for wait()
|
198
|
+
##times: the number of wait calls to make
|
199
|
+
## if nil loop will continue indefinitely
|
200
|
+
def loop(times=nil)
|
201
|
+
if times.kind_of? Numeric
|
202
|
+
times.times do
|
203
|
+
self.wait
|
204
|
+
end
|
205
|
+
elsif times.nil?
|
206
|
+
self.wait while not @exited
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
## This wait must be smart, it has to wait for a signal
|
211
|
+
## when SIGTRAP is received we need to see if one of our
|
212
|
+
## breakpoints has fired. If it has then execute the block
|
213
|
+
## originally stored with it. If its a different signal,
|
214
|
+
## then process it accordingly and move on
|
215
|
+
def wait(opts = 0)
|
216
|
+
r = Wraptux::waitpid(@pid,opts)
|
217
|
+
status = r[1]
|
218
|
+
wstatus = status & 0x7f
|
219
|
+
signal = status >> 8
|
220
|
+
found = false
|
221
|
+
if r[0] != 0 ## Check the ret
|
222
|
+
case ## FIXME - I need better logic (use Signal module)
|
223
|
+
when wstatus == 0 ##WIFEXITED
|
224
|
+
@exited = true
|
225
|
+
self.on_exit
|
226
|
+
when wstatus != 0x7f ##WIFSIGNALED
|
227
|
+
@exited = false
|
228
|
+
self.on_signal
|
229
|
+
when signal == Wraptux::Signal::SIGINT
|
230
|
+
self.continue
|
231
|
+
when signal == Wraptux::Signal::SIGSEGV
|
232
|
+
self.on_segv
|
233
|
+
when signal == Wraptux::Signal::SIGILL
|
234
|
+
self.on_illegalinst
|
235
|
+
when signal == Wraptux::Signal::SIGTRAP
|
236
|
+
## Check if EIP matches a breakpoint we have set
|
237
|
+
r = self.get_registers
|
238
|
+
eip = r[:eip]
|
239
|
+
eip -= 1
|
240
|
+
if @breakpoints.has_key?(eip)
|
241
|
+
found = true
|
242
|
+
self.on_breakpoint
|
243
|
+
else
|
244
|
+
puts "We got a SIGTRAP but not at our breakpoint... continuing"
|
245
|
+
end
|
246
|
+
self.continue
|
247
|
+
when signal == Wraptux::Signal::SIGCONT
|
248
|
+
self.continue
|
249
|
+
when signal == Wraptux::Signal::SIGSTOP
|
250
|
+
self.continue
|
251
|
+
else
|
252
|
+
raise "Add more signal handlers (##{signal})"
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
## Gets the registers for the given process
|
258
|
+
def get_registers
|
259
|
+
size = Wraptux::SIZEOFLONG * 17
|
260
|
+
regs = Array.new(size)
|
261
|
+
regs = regs.to_ptr
|
262
|
+
regs.struct!('LLLLLLLLLLLLLLLLL', :ebx,:ecx,:edx,:esi,:edi,:ebp,:eax,:xds,:xes,:xfs,:xgs,:orig_eax,:eip,:xcs,:eflags,:esp,:xss)
|
263
|
+
Wraptux::ptrace(Ragweed::Wraptux::Ptrace::GETREGS, @pid, 0, regs.to_i)
|
264
|
+
return regs
|
265
|
+
end
|
266
|
+
|
267
|
+
## Sets registers for the given process
|
268
|
+
def set_registers(r)
|
269
|
+
Wraptux::ptrace(Ragweed::Wraptux::Ptrace::SETREGS, @pid, 0, r.to_i)
|
270
|
+
end
|
271
|
+
|
272
|
+
## Here we need to do something about the bp
|
273
|
+
## we just hit. We have a block to execute
|
274
|
+
def on_breakpoint
|
275
|
+
r = self.get_registers
|
276
|
+
eip = r[:eip]
|
277
|
+
eip -= 1
|
278
|
+
|
279
|
+
## Call the block associated with the breakpoint
|
280
|
+
@breakpoints[eip].call(r, self)
|
281
|
+
|
282
|
+
if @breakpoints[eip].first.installed?
|
283
|
+
@breakpoints[eip].first.uninstall
|
284
|
+
r[:eip] = eip
|
285
|
+
set_registers(r)
|
286
|
+
stepp
|
287
|
+
## ptrace peektext returns -1 upon reinstallation of bp without calling
|
288
|
+
## waitpid() if that occurs the breakpoint cannot be reinstalled
|
289
|
+
Wraptux::waitpid(@pid, 0)
|
290
|
+
@breakpoints[eip].first.install
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
def print_regs
|
295
|
+
regs = self.get_registers
|
296
|
+
puts "eip %08x" % regs[:eip]
|
297
|
+
puts "edi %08x" % regs[:esi]
|
298
|
+
puts "edi %08x" % regs[:edi]
|
299
|
+
puts "esp %08x" % regs[:esp]
|
300
|
+
puts "eax %08x" % regs[:eax]
|
301
|
+
puts "ebx %08x" % regs[:ebx]
|
302
|
+
puts "ecx %08x" % regs[:ecx]
|
303
|
+
puts "edx %08x" % regs[:edx]
|
304
|
+
end
|
305
|
+
|
306
|
+
def on_exit
|
307
|
+
#puts "process exited"
|
308
|
+
end
|
309
|
+
|
310
|
+
def on_illegalinst
|
311
|
+
#puts "illegal instruction"
|
312
|
+
exit
|
313
|
+
end
|
314
|
+
|
315
|
+
def on_attach
|
316
|
+
#puts "attached to process"
|
317
|
+
end
|
318
|
+
|
319
|
+
def on_detach
|
320
|
+
#puts "process detached"
|
321
|
+
end
|
322
|
+
|
323
|
+
def on_continue
|
324
|
+
#puts "process continued"
|
325
|
+
end
|
326
|
+
|
327
|
+
def on_stopped
|
328
|
+
#puts "process stopped"
|
329
|
+
end
|
330
|
+
|
331
|
+
def on_signal
|
332
|
+
#puts "process received signal"
|
333
|
+
end
|
334
|
+
|
335
|
+
def on_stepp
|
336
|
+
#puts "single stepping"
|
337
|
+
end
|
338
|
+
|
339
|
+
def on_segv
|
340
|
+
print_regs
|
341
|
+
exit
|
342
|
+
end
|
343
|
+
|
344
|
+
def default_opts(opts)
|
345
|
+
@opts = @opts.merge(opts)
|
346
|
+
end
|
347
|
+
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 = 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 = Rasm::Subprogram.new
|
110
|
+
i.<< Rasm::Jmp(goto.to_i)
|
111
|
+
end
|
112
|
+
|
113
|
+
# Create the detours trampoline:
|
114
|
+
def trampoline(stack)
|
115
|
+
i = 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 = Rasm::Subprogram.new
|
128
|
+
i.<< Rasm::Push(eax)
|
129
|
+
i.<< Rasm::Mov(eax, addr+(sz-4))
|
130
|
+
i.<< Rasm::Mov([eax], esp)
|
131
|
+
i.<< Rasm::Pop(eax)
|
132
|
+
i.<< Rasm::Mov(esp, addr+(sz-4))
|
133
|
+
end
|
134
|
+
|
135
|
+
# Swap out the new stack.
|
136
|
+
def pop_stack
|
137
|
+
i = Rasm::Subprogram.new
|
138
|
+
i.<< Rasm::Pop(esp)
|
139
|
+
i.<< Rasm::Add(esp, 4)
|
140
|
+
end
|
141
|
+
|
142
|
+
# Just push all the registers in order
|
143
|
+
def save_all
|
144
|
+
i = Rasm::Subprogram.new
|
145
|
+
[eax,ecx,edx,ebx,ebp,esi,edi].each do |r|
|
146
|
+
i.<< Rasm::Push(r)
|
147
|
+
end
|
148
|
+
i
|
149
|
+
end
|
150
|
+
|
151
|
+
# Just pop all the registers
|
152
|
+
def restore_all
|
153
|
+
i = Rasm::Subprogram.new
|
154
|
+
[edi,esi,ebp,ebx,edx,ecx,eax].each do |r|
|
155
|
+
i.<< Rasm::Pop(r)
|
156
|
+
end
|
157
|
+
i
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
# A breakpoint implemented as a Detour. XXX 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 = 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
|
data/lib/ragweed/ptr.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
#XXX - TODO: make read/write work for other oses
|
2
|
+
|
3
|
+
class Ragweed::Ptr
|
4
|
+
# A dubious achievement. Wrap Integers in a pointer class, which,
|
5
|
+
# when you call to_s, returns the marshalled type, and which exports
|
6
|
+
# read/write methods.
|
7
|
+
attr_accessor :p
|
8
|
+
attr_reader :val
|
9
|
+
|
10
|
+
# ptr-to-zero?
|
11
|
+
def null?
|
12
|
+
@val == 0
|
13
|
+
end
|
14
|
+
|
15
|
+
# initialize with a number or another pointer (implements copy-ctor)
|
16
|
+
def initialize(i)
|
17
|
+
if i.kind_of? self.class
|
18
|
+
@val = i.val
|
19
|
+
@p = i.p
|
20
|
+
elsif not i
|
21
|
+
@val = 0
|
22
|
+
else
|
23
|
+
@val = i
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# return the raw pointer bits
|
28
|
+
def to_s; @val.to_l32; end
|
29
|
+
|
30
|
+
# return the underlying number
|
31
|
+
def to_i; @val; end
|
32
|
+
|
33
|
+
# only works if you attach a process
|
34
|
+
def write(arg); p.write(self, arg); end
|
35
|
+
def read(sz); p.read(self, sz); end
|
36
|
+
|
37
|
+
# everything else: work like an integer --- also, where these
|
38
|
+
# calls return numbers, turn them back into pointers, so pointer
|
39
|
+
# math doesn't shed the class wrapper
|
40
|
+
def method_missing(meth, *args)
|
41
|
+
ret = @val.send meth, *args
|
42
|
+
if ret.kind_of? Numeric
|
43
|
+
ret = Ptr.new(ret)
|
44
|
+
ret.p = self.p
|
45
|
+
end
|
46
|
+
ret
|
47
|
+
end
|
48
|
+
end
|