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,50 @@
|
|
1
|
+
class Ragweed::Event
|
2
|
+
# Quick wrapper around Win32 events. Events are simple thread sync
|
3
|
+
# objects that are cross-process. They are like semaphores that you
|
4
|
+
# can select() on.
|
5
|
+
|
6
|
+
# You can just do WinEvent.new to get a new anonymous handle, and
|
7
|
+
# then call .handle on it to find out what the handle was. Communicate
|
8
|
+
# your pid and the handle value, somehow, to a remote process. That
|
9
|
+
# process can get the same event by passing a WinProcess and the
|
10
|
+
# handle here.
|
11
|
+
#
|
12
|
+
# So, in Process1 (assume pid 668, and handle 300):
|
13
|
+
#
|
14
|
+
# e = WinEvent.new
|
15
|
+
# puts #{ get_current_process_id }: #{ e.handle }"
|
16
|
+
#
|
17
|
+
# And in Process2:
|
18
|
+
#
|
19
|
+
# e = WinEvent.new(WinProcess.new(668), 300)
|
20
|
+
#
|
21
|
+
# Now both processes share an event.
|
22
|
+
def initialize(p=nil, h=nil)
|
23
|
+
@p = p
|
24
|
+
@h = (@p.dup_handle(h) if h) || create_event
|
25
|
+
end
|
26
|
+
|
27
|
+
# Don't return until the event is signalled. Note that you
|
28
|
+
# can't break this with timeouts or CTR-C.
|
29
|
+
def wait
|
30
|
+
Wrap32::wait_for_single_object @h
|
31
|
+
end
|
32
|
+
|
33
|
+
# Signal the event; anyone waiting on it is now released.
|
34
|
+
def signal
|
35
|
+
Wrap32::set_event(@h)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Force the event back to unsignalled state.
|
39
|
+
def reset
|
40
|
+
Wrap32::reset_event(@h)
|
41
|
+
end
|
42
|
+
|
43
|
+
# A wait loop.
|
44
|
+
def on(&block)
|
45
|
+
while 1
|
46
|
+
wait
|
47
|
+
break if not yield
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class Ragweed::Debugger32
|
2
|
+
# Hook function calls
|
3
|
+
# nargs is the number of arguments taken by function at ip
|
4
|
+
# callable/block is called with ev, ctx, dir (:enter or :leave), and args Array (see examples/hook_notepad.rb)
|
5
|
+
# default handler prints arguments
|
6
|
+
def hook(ip, nargs, callable=nil, &block)
|
7
|
+
callable ||= block || lambda do |ev, ctx,dir,args|
|
8
|
+
puts "#{dir} #{ip.to_s(16) rescue ip.to_s}"
|
9
|
+
puts args.map{|a| "%08x" % a}.join(',')
|
10
|
+
end
|
11
|
+
|
12
|
+
breakpoint_set(ip) do |ev,ctx|
|
13
|
+
args = (1..nargs).map {|i| process.read32(ctx.esp + 4*i)}
|
14
|
+
retp = process.read32(ctx.esp)
|
15
|
+
# set exit bpoint
|
16
|
+
breakpoint_set(retp) do |ev,ctx|
|
17
|
+
callable.call(ev, ctx, :leave, args)
|
18
|
+
breakpoint_clear(retp)
|
19
|
+
end.install
|
20
|
+
callable.call(ev, ctx, :enter, args)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
class Ragweed::Wrap32::Overlapped
|
2
|
+
attr_accessor :internal
|
3
|
+
attr_accessor :internal_high
|
4
|
+
attr_accessor :offset
|
5
|
+
attr_accessor :offset_high
|
6
|
+
attr_accessor :event
|
7
|
+
attr_accessor :target
|
8
|
+
|
9
|
+
def self.get
|
10
|
+
h = Ragweed::Wrap32::create_event(nil, false, true)
|
11
|
+
r = self.new
|
12
|
+
r.event = h
|
13
|
+
return r
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(str=nil)
|
17
|
+
@buf = "\x00" * 20
|
18
|
+
@internal, @internal_high, @offset, @offset_high, @event = [0,0,0,0,0]
|
19
|
+
init(str) if str
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_s
|
23
|
+
buf = [@internal, @internal_high, @offset, @offset_high, @event].pack("LLLLL")
|
24
|
+
@buf.replace(buf)
|
25
|
+
end
|
26
|
+
|
27
|
+
def release
|
28
|
+
Ragweed::Wrap32::close_handle(@event)
|
29
|
+
end
|
30
|
+
|
31
|
+
def wait(h)
|
32
|
+
return if not @event
|
33
|
+
Ragweed::Wrap32::wait_for_single_object(@event)
|
34
|
+
Ragweed::Wrap32::get_overlapped_result(h, self)
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def init(str)
|
40
|
+
@internal,
|
41
|
+
@internal_high,
|
42
|
+
@offset,
|
43
|
+
@offset_high,
|
44
|
+
@event = str.unpack("LLLLL")
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,506 @@
|
|
1
|
+
# XXX - PORT ME!!
|
2
|
+
|
3
|
+
class Ragweed::Process
|
4
|
+
def handle; @h; end
|
5
|
+
attr_reader :pid
|
6
|
+
include Ragweed
|
7
|
+
|
8
|
+
def self.find_by_regex(name)
|
9
|
+
Wrap32::all_processes do |p|
|
10
|
+
if p.szExeFile =~ name
|
11
|
+
return self.new(p.th32ProcessID)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# Get a pointer into the remote process; pointers are just fixnums
|
17
|
+
# with a read/write method and a to_s.
|
18
|
+
def ptr(x)
|
19
|
+
ret = Ptr.new(x)
|
20
|
+
ret.p = self
|
21
|
+
return ret
|
22
|
+
end
|
23
|
+
|
24
|
+
# clone a handle from the remote process to here (to here? tf?)
|
25
|
+
def dup_handle(h)
|
26
|
+
Wrap32::duplicate_handle(@h, h)
|
27
|
+
end
|
28
|
+
|
29
|
+
# look up a process by its name --- but this is in the local process,
|
30
|
+
# which is broken --- a heuristic that sometimes works for w32 functions,
|
31
|
+
# but probably never otherwise.
|
32
|
+
def get_proc(name)
|
33
|
+
return Ptr.new(name) if name.kind_of? Numeric or name.kind_of? Ptr
|
34
|
+
ptr(Wrap32::get_proc_address(name))
|
35
|
+
end
|
36
|
+
|
37
|
+
def get_proc_remote(name)
|
38
|
+
mod, meth = name.split "!"
|
39
|
+
modh = remote_call "kernel32!GetModuleHandleW", mod.to_utf16
|
40
|
+
raise "no such module #{ mod }" if not modh
|
41
|
+
ret = remote_call "kernel32!GetProcAddress", modh, meth
|
42
|
+
ret
|
43
|
+
end
|
44
|
+
|
45
|
+
# Look up a process by name or regex, returning an array of all
|
46
|
+
# matching processes, as objects.
|
47
|
+
def self.by_name(n)
|
48
|
+
n = Regexp.new(n) if not n.kind_of? Regexp
|
49
|
+
p = []
|
50
|
+
all_processes do |px|
|
51
|
+
if px.szExeFile =~ n
|
52
|
+
p << self.new(px.th32ProcessID)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
p
|
56
|
+
end
|
57
|
+
|
58
|
+
# Just need a PID to get started.
|
59
|
+
def initialize(pid)
|
60
|
+
@pid = pid
|
61
|
+
@h = Wrap32::open_process(pid)
|
62
|
+
@a = arena()
|
63
|
+
end
|
64
|
+
|
65
|
+
# Return the EXE name of the process.
|
66
|
+
def image
|
67
|
+
buf = "\x00" * 256
|
68
|
+
if Wrap32::nt_query_information_process(@h, 27, buf)
|
69
|
+
buf = buf.from_utf16
|
70
|
+
buf = buf[(buf.index("\\"))..-1]
|
71
|
+
return buf.asciiz
|
72
|
+
end
|
73
|
+
nil
|
74
|
+
end
|
75
|
+
|
76
|
+
# Return a list of all the threads in the process; relatively
|
77
|
+
# expensive, so cache the result.
|
78
|
+
def threads(full=false, &block)
|
79
|
+
return Wrap32::threads(@pid, &block) if block_given?
|
80
|
+
ret = []
|
81
|
+
Wrap32::threads(@pid) {|x| ((full) ? ret << x : ret << x.th32ThreadID) }
|
82
|
+
return ret
|
83
|
+
end
|
84
|
+
|
85
|
+
# Suspend all the threads in the process.
|
86
|
+
def suspend_all; threads.each {|x| suspend(x)}; end
|
87
|
+
|
88
|
+
# Resume all the threads in the process. XXX this will not
|
89
|
+
# resume threads with suspend counts greater than 1.
|
90
|
+
def resume_all; threads.each {|x| resume(x)}; end
|
91
|
+
|
92
|
+
# Suspend a thread by tid. Technically, this doesn't need to be
|
93
|
+
# a method; you can suspend a thread anywhere without a process handle.
|
94
|
+
def suspend(tid); Wrap32::open_thread(tid) {|x| Wrap32::suspend_thread(x)}; end
|
95
|
+
|
96
|
+
# Resume a thread by tid.
|
97
|
+
def resume(tid); Wrap32::open_thread(tid) {|x| Wrap32::resume_thread(x)}; end
|
98
|
+
|
99
|
+
# List the modules for the process, either yielding a struct for
|
100
|
+
# each to a block, or returning a list.
|
101
|
+
def modules(&block)
|
102
|
+
if block_given?
|
103
|
+
Wrap32::list_modules(@pid, &block)
|
104
|
+
else
|
105
|
+
ret = []
|
106
|
+
Wrap32::list_modules(@pid) {|x| ret << x}
|
107
|
+
return ret
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# Read/write ranges of data or fixnums to/from the process by address.
|
112
|
+
def read(off, sz=4096); Wrap32::read_process_memory(@h, off, sz); end
|
113
|
+
def write(off, data); Wrap32::write_process_memory(@h, off, data); end
|
114
|
+
def read32(off); read(off, 4).unpack("L").first; end
|
115
|
+
def read16(off); read(off, 2).unpack("v").first; end
|
116
|
+
def read8(off); read(off, 1)[0]; end
|
117
|
+
def write32(off, v); write(off, [v].pack("L")); end
|
118
|
+
def write16(off, v); write(off, [v].pack("v")); end
|
119
|
+
def write8(off, v); write(off, v.chr); end
|
120
|
+
|
121
|
+
# call a function, by name or address, in the process, using
|
122
|
+
# CreateRemoteThread
|
123
|
+
def remote_call(meth, *args)
|
124
|
+
loc = meth
|
125
|
+
loc = get_proc(loc) if loc.kind_of? String
|
126
|
+
loc = Ptr.new loc
|
127
|
+
raise "bad proc name" if loc.null?
|
128
|
+
t = Trampoline.new(self, loc)
|
129
|
+
t.call *args
|
130
|
+
end
|
131
|
+
|
132
|
+
# Can I write to this address in the process?
|
133
|
+
def writeable?(off); Wrap32::writeable? @h, off; end
|
134
|
+
|
135
|
+
# Use VirtualAllocEx to grab a block of memory in the process. This
|
136
|
+
# is expensive, the equivalent of mmap()'ing for each allocation.
|
137
|
+
def syscall_alloc(sz); ptr(Wrap32::virtual_alloc_ex(@h, sz)); end
|
138
|
+
|
139
|
+
# Use arenas, when possible, to quickly allocate memory. The upside
|
140
|
+
# is this is very fast. The downside is you can't free the memory
|
141
|
+
# without invalidating every allocation you've made prior.
|
142
|
+
def alloc(sz, syscall=false)
|
143
|
+
if syscall or sz > 4090
|
144
|
+
ret = syscall_alloc(sz)
|
145
|
+
else
|
146
|
+
ptr(@a.alloc(sz))
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
# Free the return value of syscall_alloc. Do NOT use for the return
|
151
|
+
# value of alloc.
|
152
|
+
def free(off)
|
153
|
+
Wrap32::virtual_free_ex(@h, off)
|
154
|
+
end
|
155
|
+
|
156
|
+
# Convert an address to "module+10h" notation, when possible.
|
157
|
+
def to_modoff(off, force=false)
|
158
|
+
if not @modules or force
|
159
|
+
@modules = modules.sort {|x,y| x.modBaseAddr <=> y.modBaseAddr}
|
160
|
+
end
|
161
|
+
|
162
|
+
@modules.each do |m|
|
163
|
+
if off >= m.modBaseAddr and off < (m.modBaseAddr + m.modBaseSize)
|
164
|
+
return "#{ m.szModule }+#{ (off - m.modBaseAddr).to_s(16) }h"
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
return "#{ off.to_x }h"
|
169
|
+
end
|
170
|
+
|
171
|
+
# Get another allocation arena for this process. Pretty cheap. Given
|
172
|
+
# a block, behaves like File#open, disposing of the arena when you're
|
173
|
+
# done.
|
174
|
+
def arena(&block)
|
175
|
+
me = self
|
176
|
+
a = Arena.new(lambda {me.syscall_alloc(4096)},
|
177
|
+
lambda {|p| me.free(p)},
|
178
|
+
lambda {|dst, src| me.write(dst, src)})
|
179
|
+
if block_given?
|
180
|
+
ret = yield a
|
181
|
+
a.release
|
182
|
+
return ret
|
183
|
+
end
|
184
|
+
a
|
185
|
+
end
|
186
|
+
|
187
|
+
# Insert a string anywhere into the memory of the remote process,
|
188
|
+
# returning its address, using an arena.
|
189
|
+
def insert(buf); @a.copy(buf); end
|
190
|
+
|
191
|
+
# List all memory regions in the remote process by iterating over
|
192
|
+
# VirtualQueryEx. With a block, yields MEMORY_BASIC_INFORMATION
|
193
|
+
# structs. Without it, returns [baseaddr,size] tuples.
|
194
|
+
#
|
195
|
+
# We "index" this list, so that we can refer to memory locations
|
196
|
+
# by "region number", which is a Rubycorn-ism and not a Win32-ism.
|
197
|
+
# You'll see lots of functions asking for memory indices, and this
|
198
|
+
# is what they're referring to.
|
199
|
+
def list_memory(&block)
|
200
|
+
ret = []
|
201
|
+
i = 0
|
202
|
+
while (mbi = Wrap32::virtual_query_ex(@h, i))
|
203
|
+
break if (not ret.empty? and mbi.BaseAddress == 0)
|
204
|
+
if block_given?
|
205
|
+
yield mbi
|
206
|
+
else
|
207
|
+
base = mbi.BaseAddress || 0
|
208
|
+
size = mbi.RegionSize || 0
|
209
|
+
ret << [base,size] if mbi.State & 0x1000 # MEM_COMMIT
|
210
|
+
i = base + size
|
211
|
+
end
|
212
|
+
end
|
213
|
+
ret
|
214
|
+
end
|
215
|
+
|
216
|
+
# Human-readable standard output of list_memory. Remember that the
|
217
|
+
# index number is important.
|
218
|
+
def dump_memory_list
|
219
|
+
list_memory.each_with_index {|x,i| puts "#{ i }. #{ x[0].to_s(16) }(#{ x[1] })"}
|
220
|
+
true
|
221
|
+
end
|
222
|
+
|
223
|
+
# Read an entire memory region into a string by region number.
|
224
|
+
def get_memory(i, opts={})
|
225
|
+
refresh opts
|
226
|
+
read(@memlist[i][0], @memlist[i][1])
|
227
|
+
end
|
228
|
+
|
229
|
+
# Print a canonical hexdump of an entire memory region by region number.
|
230
|
+
def dump_memory(i, opts={}); get_memory(i, opts).hexdump; end
|
231
|
+
|
232
|
+
# In Python, and maybe Ruby, it was much faster to work on large
|
233
|
+
# memory regions a 4k page at a time, rather than reading the
|
234
|
+
# whole thing into one big string. Scan takes a memory region
|
235
|
+
# and yields 4k chunks of it to a block, along with the length
|
236
|
+
# of each chunk.
|
237
|
+
def scan(i, opts={})
|
238
|
+
refresh opts
|
239
|
+
memt = @memlist[i]
|
240
|
+
if memt[1] > 4096
|
241
|
+
0.step(memt[1], 4096) do |i|
|
242
|
+
block = (memt[1] - i).cap(4096)
|
243
|
+
yield read(memt[0] + i, block), memt[0]+i
|
244
|
+
end
|
245
|
+
else
|
246
|
+
yield read(memt[0], memt[1]), memt[0]
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
# Dump thread context, returning a struct that contains things like
|
251
|
+
# .Eip and .Eax.
|
252
|
+
def thread_context(tid)
|
253
|
+
Wrap32::open_thread(tid) do |h|
|
254
|
+
Wrap32::get_thread_context(h)
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
# Take a region of memory and walk over it in 255-byte samples (less
|
259
|
+
# than 255 bytes and you lose accuracy, but you can increase it with
|
260
|
+
# the ":window" option), computing entropy for each sample, returning
|
261
|
+
# a list of [offset,entropy] tuples.
|
262
|
+
def entropy_map(i, opts={})
|
263
|
+
ret = []
|
264
|
+
startoff = opts[:starting_offset]
|
265
|
+
startoff ||= 0
|
266
|
+
window = opts[:window] || 255
|
267
|
+
scan(i, opts) do |block, soff|
|
268
|
+
startoff.stepwith(block.size, window) do |off, len|
|
269
|
+
ret << [off, block[off,len].entropy]
|
270
|
+
end
|
271
|
+
end
|
272
|
+
return ret
|
273
|
+
end
|
274
|
+
|
275
|
+
# Given a source and destination memory region, scan through "source"
|
276
|
+
# looking for properly-aligned U32LE values that would be valid pointers
|
277
|
+
# into "destination". The ":range_start" and ":range_end" options
|
278
|
+
# constrain what a "valid pointer" into "destination" is.
|
279
|
+
def pointers_to(src, dst, opts={})
|
280
|
+
refresh opts
|
281
|
+
ret = {}
|
282
|
+
range = ((opts[:range_start] || @memlist[dst][0])..(opts[:range_stop] || @memlist[dst][0]+@memlist[dst][1]))
|
283
|
+
scan(src, opts) do |block, soff|
|
284
|
+
0.stepwith(block.size, 4) do |off, len|
|
285
|
+
if len == 4
|
286
|
+
if range.member? block[off,4].to_l32
|
287
|
+
ret[soff + off] = block[off,4].to_l32
|
288
|
+
end
|
289
|
+
end
|
290
|
+
end
|
291
|
+
end
|
292
|
+
return ret
|
293
|
+
end
|
294
|
+
|
295
|
+
# Given a memory region number, do a Unix strings(1) on it. Valid
|
296
|
+
# options:
|
297
|
+
# :unicode: you probably always want to set this to "true"
|
298
|
+
# :minimum: how small strings to accept.
|
299
|
+
#
|
300
|
+
# Fairly slow.
|
301
|
+
def strings_mem(i, opts={})
|
302
|
+
ret = []
|
303
|
+
opts[:offset] ||= 0
|
304
|
+
scan(i) do |block, soff|
|
305
|
+
while 1
|
306
|
+
off, size = block.nextstring(opts)
|
307
|
+
break if not off
|
308
|
+
opts[:offset] += (off + size)
|
309
|
+
ret << [soff+off, size, block[off,size]]
|
310
|
+
end
|
311
|
+
end
|
312
|
+
ret
|
313
|
+
end
|
314
|
+
|
315
|
+
# Given a string key, find it in memory. Very slow. Will read all
|
316
|
+
# memory regions, but you can provide ":index_range", which must be
|
317
|
+
# a Range object, to constrain which ranges to search through.
|
318
|
+
# Returns a list of structs containing absolute memory locations,
|
319
|
+
# the index of the region, and some surrounding context for the
|
320
|
+
# hit.
|
321
|
+
def hunt(key, opts={})
|
322
|
+
ret = []
|
323
|
+
refresh opts
|
324
|
+
range = opts[:index_range] || (0..@memlist.size)
|
325
|
+
@memlist.each_with_index do |t, i|
|
326
|
+
if range.member? i
|
327
|
+
if opts[:noisy]
|
328
|
+
puts "#{ i }. #{ t[0].to_s(16) } -> #{ (t[0]+t[1]).to_s(16) }"
|
329
|
+
end
|
330
|
+
scan(i, opts) do |block, soff|
|
331
|
+
if (needle = block.index(key))
|
332
|
+
r = OpenStruct.new
|
333
|
+
r.location = (t[0] + soff + needle)
|
334
|
+
r.index = i
|
335
|
+
r.context = block
|
336
|
+
ret << r
|
337
|
+
return ret if opts[:first]
|
338
|
+
end
|
339
|
+
end
|
340
|
+
end
|
341
|
+
end
|
342
|
+
ret
|
343
|
+
end
|
344
|
+
|
345
|
+
private
|
346
|
+
|
347
|
+
def windowize(i, opts={})
|
348
|
+
window = opts[:window] || 1024
|
349
|
+
if window == :auto
|
350
|
+
r = region_range(i)
|
351
|
+
window = (r.last - r.first) / 60
|
352
|
+
end
|
353
|
+
return window
|
354
|
+
end
|
355
|
+
|
356
|
+
public
|
357
|
+
|
358
|
+
# Like entropy_map, scan a process and compute adler16 checksums for
|
359
|
+
# 1k (or :window) blocks.
|
360
|
+
def adler_map(i, opts={})
|
361
|
+
refresh opts
|
362
|
+
window = windowize(i, opts)
|
363
|
+
ret = []
|
364
|
+
scan(i, opts) do |block,soff|
|
365
|
+
0.stepwith(block.size-1, window) do |off, len|
|
366
|
+
if (b = block[off,len])
|
367
|
+
ret << b.adler
|
368
|
+
end
|
369
|
+
end
|
370
|
+
end
|
371
|
+
ret
|
372
|
+
end
|
373
|
+
|
374
|
+
# If you store the adler map, you've compressed the memory region
|
375
|
+
# down to a small series of fixnums, and you can use it with this
|
376
|
+
# function to re-check the memory region and see if anything's changing.
|
377
|
+
def adler_compare(i, orig, opts={})
|
378
|
+
refresh opts
|
379
|
+
window = windowize(i, opts)
|
380
|
+
ret = []
|
381
|
+
c = -1
|
382
|
+
scan(i, opts) do |block,soff|
|
383
|
+
0.stepwith(block.size-1, window) do |off, len|
|
384
|
+
if block[off,len].adler != orig[c += 1]
|
385
|
+
ret << soff+off
|
386
|
+
end
|
387
|
+
end
|
388
|
+
end
|
389
|
+
ret
|
390
|
+
end
|
391
|
+
|
392
|
+
# Quick and dirty visualization of a memory region by checksum
|
393
|
+
# changes.
|
394
|
+
class AdlerChart
|
395
|
+
|
396
|
+
# Create with a WinProcess and region index
|
397
|
+
def initialize(p, i)
|
398
|
+
@p = p
|
399
|
+
@i = i
|
400
|
+
@initstate = map
|
401
|
+
end
|
402
|
+
|
403
|
+
private
|
404
|
+
def map; @p.adler_map(@i, :window => :auto); end
|
405
|
+
|
406
|
+
public
|
407
|
+
|
408
|
+
# Just puts the chart repeatedly, to get:
|
409
|
+
# ........................................................*.*..
|
410
|
+
# ..........................................................*..
|
411
|
+
# ..........................................................*..
|
412
|
+
# Where * represents a chunk of memory that has changed.
|
413
|
+
def to_s
|
414
|
+
s = StringIO.new
|
415
|
+
newstate = map
|
416
|
+
@initstate.each_with_index do |sum, i|
|
417
|
+
if sum != newstate[i]
|
418
|
+
s.write "*"
|
419
|
+
@initstate[i] = newstate[i]
|
420
|
+
else
|
421
|
+
s.write "."
|
422
|
+
end
|
423
|
+
end
|
424
|
+
s.rewind;s.read()
|
425
|
+
end
|
426
|
+
end
|
427
|
+
|
428
|
+
# See WinProcess::AdlerChart. Get one.
|
429
|
+
def adler_chart(i)
|
430
|
+
AdlerChart.new self, i
|
431
|
+
end
|
432
|
+
|
433
|
+
# Given a memory region, use the adler routines to create a checksum
|
434
|
+
# map, wait a short period of time, and scan for changes, to find
|
435
|
+
# churning memory.
|
436
|
+
def changes(i, sleeptime=0.5)
|
437
|
+
q = adler_map(i)
|
438
|
+
sleep(sleeptime)
|
439
|
+
adler_compare(i, q)
|
440
|
+
end
|
441
|
+
|
442
|
+
# Get the memory range, as a Ruby Range, for a region by index
|
443
|
+
def region_range(i, opts={})
|
444
|
+
refresh opts
|
445
|
+
(@memlist[i][0]..(@memlist[i][1]+@memlist[i][0]))
|
446
|
+
end
|
447
|
+
|
448
|
+
# Figure out what region (by region index) has an address
|
449
|
+
def which_region_has?(addr, opts={})
|
450
|
+
refresh opts
|
451
|
+
@memlist.each_with_index do |r, i|
|
452
|
+
return i if (r[0]..r[0]+r[1]).member? addr
|
453
|
+
end
|
454
|
+
return nil
|
455
|
+
end
|
456
|
+
|
457
|
+
# Do something with a thread while its suspended
|
458
|
+
def with_suspended_thread(tid)
|
459
|
+
ret = nil
|
460
|
+
Wrap32::with_suspended_thread(tid) {|x| ret = yield}
|
461
|
+
return ret
|
462
|
+
end
|
463
|
+
|
464
|
+
# For libraries compiled with frame pointers: walk EBP back
|
465
|
+
# until it stops giving intelligible addresses, and, at each
|
466
|
+
# step, grab the saved EIP from just before it.
|
467
|
+
def thread_stack_trace(tid)
|
468
|
+
with_suspended_thread(tid) do
|
469
|
+
ctx = thread_context(tid)
|
470
|
+
if((start = read32(ctx.Ebp)) != 0)
|
471
|
+
a = start
|
472
|
+
stack = [[start, read32(start+4)]]
|
473
|
+
while((a = read32(a)) and a != 0 and not stack.member?(a))
|
474
|
+
begin
|
475
|
+
stack << [a, read32(a+4)]
|
476
|
+
rescue; break; end
|
477
|
+
end
|
478
|
+
return stack
|
479
|
+
end
|
480
|
+
end
|
481
|
+
[]
|
482
|
+
end
|
483
|
+
|
484
|
+
# Human-readable version of thread_stack_trace, with module
|
485
|
+
# offsets.
|
486
|
+
def dump_stack_trace(tid)
|
487
|
+
thread_stack_trace(tid).each do |frame, code|
|
488
|
+
puts "#{ frame.to_x } @ #{ to_modoff(code) }"
|
489
|
+
end
|
490
|
+
end
|
491
|
+
alias_method :bt, :dump_stack_trace
|
492
|
+
|
493
|
+
def detour(loc, o={})
|
494
|
+
klass = o[:class] || Detour
|
495
|
+
loc = get_proc(loc)
|
496
|
+
r = klass.new(loc, o)
|
497
|
+
r.call if not o[:chicken]
|
498
|
+
return r
|
499
|
+
end
|
500
|
+
|
501
|
+
private
|
502
|
+
|
503
|
+
def refresh(opts={})
|
504
|
+
@memlist = list_memory if not @memlist or opts.delete(:refresh)
|
505
|
+
end
|
506
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Ragweed::Wrap32
|
2
|
+
module TokenAccess
|
3
|
+
ADJUST_DEFAULT = 128
|
4
|
+
ADJUST_GROUPS = 64
|
5
|
+
ADJUST_PRIVILEGES = 32
|
6
|
+
ALL_ACCESS = 0xf00ff
|
7
|
+
ASSIGN_PRIMARY = 1
|
8
|
+
DUPLICATE = 2
|
9
|
+
EXECUTE = 0x20000
|
10
|
+
IMPERSONATE = 4
|
11
|
+
QUERY = 8
|
12
|
+
QUERY_SOURCE = 16
|
13
|
+
READ = 0x20008
|
14
|
+
WRITE = 0x200e0
|
15
|
+
end
|
16
|
+
|
17
|
+
module PrivilegeAttribute
|
18
|
+
ENABLED = 0x2
|
19
|
+
ENABLED_BY_DEFAULT = 0x1
|
20
|
+
USED_FOR_ACCESS = 0x80000000
|
21
|
+
end
|
22
|
+
|
23
|
+
class << self
|
24
|
+
def open_process_token(h, access=Wrap32::TokenAccess::ADJUST_PRIVILEGES)
|
25
|
+
outw = "\x00" * 4
|
26
|
+
r = CALLS["advapi32!OpenProcessToken:LLP=L"].call(h, access, outw)
|
27
|
+
raise WinX.new(:open_process_token) if r == 0
|
28
|
+
return outw.unpack("L").first
|
29
|
+
end
|
30
|
+
|
31
|
+
def adjust_token_privileges(t, disable, *args)
|
32
|
+
buf = [args.size].pack("L") + (args.map {|tup| tup.pack("QL") }.join(""))
|
33
|
+
|
34
|
+
r = CALLS["advapi32!AdjustTokenPrivileges:LLPLPP=L"].
|
35
|
+
call(t, disable, buf, buf.size, NULL, NULL)
|
36
|
+
|
37
|
+
raise WinX.new(:adjust_token_privileges) if r == 0
|
38
|
+
end
|
39
|
+
|
40
|
+
def lookup_privilege_value(name)
|
41
|
+
outw = "\x00" * 8
|
42
|
+
r = CALLS["advapi32!LookupPrivilegeValueA:PPP=L"].call(NULL, name, outw)
|
43
|
+
raise WinX.new(:lookup_privilege_value) if r == 0
|
44
|
+
return outw.unpack("Q").first
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class Ragweed::Wrap32::ProcessToken
|
50
|
+
def initialize(p=nil)
|
51
|
+
p ||= Wrap32::open_process(Wrap32::get_current_process_id)
|
52
|
+
@h = Wrap32::open_process_token(p)
|
53
|
+
end
|
54
|
+
|
55
|
+
def grant(name)
|
56
|
+
luid = Wrap32::lookup_privilege_value(name)
|
57
|
+
Wrap32::adjust_token_privileges(@h, 0, [luid, Wrap32::PrivilegeAttribute::ENABLED])
|
58
|
+
end
|
59
|
+
end
|