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