ragweed 0.2.0-java
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +32 -0
- data/README.rdoc +60 -0
- data/README.txt +9 -0
- data/Rakefile +86 -0
- data/VERSION +1 -0
- data/examples/hittracertux.rb +45 -0
- data/examples/hittracerx.rb +63 -0
- data/examples/hook_notepad.rb +9 -0
- data/examples/snicker.rb +183 -0
- data/examples/tux-example.rb +24 -0
- data/lib/ragweed/arena.rb +55 -0
- data/lib/ragweed/blocks.rb +128 -0
- data/lib/ragweed/debugger32.rb +400 -0
- data/lib/ragweed/debuggerosx.rb +456 -0
- data/lib/ragweed/debuggertux.rb +502 -0
- data/lib/ragweed/detour.rb +223 -0
- data/lib/ragweed/ptr.rb +48 -0
- data/lib/ragweed/rasm/bblock.rb +73 -0
- data/lib/ragweed/rasm/isa.rb +1115 -0
- data/lib/ragweed/rasm.rb +59 -0
- data/lib/ragweed/sbuf.rb +197 -0
- data/lib/ragweed/trampoline.rb +103 -0
- data/lib/ragweed/utils.rb +182 -0
- data/lib/ragweed/wrap32/debugging.rb +401 -0
- data/lib/ragweed/wrap32/device.rb +49 -0
- data/lib/ragweed/wrap32/event.rb +50 -0
- data/lib/ragweed/wrap32/hooks.rb +39 -0
- data/lib/ragweed/wrap32/overlapped.rb +46 -0
- data/lib/ragweed/wrap32/process.rb +613 -0
- data/lib/ragweed/wrap32/process_token.rb +75 -0
- data/lib/ragweed/wrap32/thread_context.rb +142 -0
- data/lib/ragweed/wrap32/winx.rb +16 -0
- data/lib/ragweed/wrap32/wrap32.rb +583 -0
- data/lib/ragweed/wrap32.rb +59 -0
- data/lib/ragweed/wraposx/constants.rb +114 -0
- data/lib/ragweed/wraposx/kernelerrorx.rb +147 -0
- data/lib/ragweed/wraposx/region_info.rb +275 -0
- data/lib/ragweed/wraposx/structs.rb +102 -0
- data/lib/ragweed/wraposx/thread_context.rb +902 -0
- data/lib/ragweed/wraposx/thread_info.rb +160 -0
- data/lib/ragweed/wraposx/thread_info.rb.old +121 -0
- data/lib/ragweed/wraposx/wraposx.rb +356 -0
- data/lib/ragweed/wraposx.rb +60 -0
- data/lib/ragweed/wraptux/constants.rb +101 -0
- data/lib/ragweed/wraptux/process.rb +35 -0
- data/lib/ragweed/wraptux/threads.rb +7 -0
- data/lib/ragweed/wraptux/wraptux.rb +72 -0
- data/lib/ragweed/wraptux.rb +57 -0
- data/lib/ragweed.rb +112 -0
- data/ragweed.gemspec +102 -0
- data/spec/ragweed_spec.rb +7 -0
- data/spec/spec_helper.rb +16 -0
- data/test/test_ragweed.rb +0 -0
- metadata +121 -0
@@ -0,0 +1,613 @@
|
|
1
|
+
class Ragweed::Process
|
2
|
+
def handle; @h; end
|
3
|
+
attr_reader :pid
|
4
|
+
include Ragweed
|
5
|
+
|
6
|
+
def self.find_by_regex(name)
|
7
|
+
Ragweed::Wrap32::all_processes do |p|
|
8
|
+
if p.szExeFile =~ name
|
9
|
+
return self.new(p.th32ProcessID)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# Get a pointer into the remote process; pointers are just fixnums
|
15
|
+
# with a read/write method and a to_s.
|
16
|
+
def ptr(x)
|
17
|
+
ret = Ragweed::Ptr.new(x)
|
18
|
+
ret.p = self
|
19
|
+
return ret
|
20
|
+
end
|
21
|
+
|
22
|
+
# clone a handle from the remote process to here (to here? tf?)
|
23
|
+
def dup_handle(h)
|
24
|
+
Ragweed::Wrap32::duplicate_handle(@h, h)
|
25
|
+
end
|
26
|
+
|
27
|
+
# look up a process by its name --- but this is in the local process,
|
28
|
+
# which is broken --- a heuristic that sometimes works for w32 functions,
|
29
|
+
# but probably never otherwise.
|
30
|
+
def get_proc(name)
|
31
|
+
return Ragweed::Ptr.new(name) if name.kind_of? Numeric or name.kind_of? Ptr
|
32
|
+
ptr(Ragweed::Wrap32::get_proc_address(name))
|
33
|
+
end
|
34
|
+
|
35
|
+
def is_hex(s)
|
36
|
+
s = s.strip
|
37
|
+
|
38
|
+
## Strip leading 0s and 0x prefix
|
39
|
+
while s[0..1] == '0x' or s[0..1] == '00'
|
40
|
+
s = s[2..-1]
|
41
|
+
end
|
42
|
+
|
43
|
+
o = s
|
44
|
+
|
45
|
+
if s.hex.to_s(16) == o
|
46
|
+
return true
|
47
|
+
end
|
48
|
+
return false
|
49
|
+
end
|
50
|
+
|
51
|
+
## This only gets called for breakpoints in modules
|
52
|
+
## that have just been loaded and detected by a LOAD_DLL
|
53
|
+
## event. It is called from on_load_dll() -> deferred_install()
|
54
|
+
def get_deferred_proc_remote(name, handle, base_of_dll)
|
55
|
+
if !name.kind_of?String
|
56
|
+
return name
|
57
|
+
end
|
58
|
+
|
59
|
+
mod, meth = name.split "!"
|
60
|
+
|
61
|
+
if mod.nil? or meth.nil?
|
62
|
+
raise "can not set this breakpoint: #{name}"
|
63
|
+
end
|
64
|
+
|
65
|
+
modh = handle
|
66
|
+
|
67
|
+
## Location is an offset
|
68
|
+
if is_hex(meth)
|
69
|
+
baseaddr = 0
|
70
|
+
modules.each do |m|
|
71
|
+
if m.szModule == mod
|
72
|
+
break
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
ret = base_of_dll + meth.hex
|
77
|
+
else
|
78
|
+
## Location is a symbolic name
|
79
|
+
## Win32 should have successfully loaded the DLL
|
80
|
+
ret = remote_call "kernel32!GetProcAddress", modh, meth
|
81
|
+
end
|
82
|
+
ret
|
83
|
+
end
|
84
|
+
|
85
|
+
## This only gets called for breakpoints
|
86
|
+
## in modules that are already loaded
|
87
|
+
def get_proc_remote(name)
|
88
|
+
if !name.kind_of?String
|
89
|
+
return name
|
90
|
+
end
|
91
|
+
|
92
|
+
mod, meth = name.split "!"
|
93
|
+
|
94
|
+
if mod.nil? or meth.nil?
|
95
|
+
raise "can not set this breakpoint: #{name}"
|
96
|
+
end
|
97
|
+
|
98
|
+
# modh = remote_call "kernel32!GetModuleHandleW", mod.to_utf16
|
99
|
+
modh = remote_call "kernel32!GetModuleHandleA", mod
|
100
|
+
raise "no such module #{ mod }" if not modh
|
101
|
+
|
102
|
+
## Location is an offset
|
103
|
+
if is_hex(meth)
|
104
|
+
baseaddr = 0
|
105
|
+
modules.each do |m|
|
106
|
+
if m.szModule == mod
|
107
|
+
baseaddr = m.modBaseAddr
|
108
|
+
break
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
## Somehow the module does not appear to be
|
113
|
+
## loaded. This should have been caught by
|
114
|
+
## Process::is_breakpoint_deferred either way
|
115
|
+
## Process::initialize should catch this return
|
116
|
+
if baseaddr == 0 or baseaddr == -1
|
117
|
+
return name
|
118
|
+
end
|
119
|
+
|
120
|
+
ret = baseaddr + meth.hex
|
121
|
+
else
|
122
|
+
## Location is a symbolic name
|
123
|
+
ret = remote_call "kernel32!GetProcAddress", modh, meth
|
124
|
+
end
|
125
|
+
ret
|
126
|
+
end
|
127
|
+
|
128
|
+
## Check if breakpoint location is deferred
|
129
|
+
## This method expects a string 'module!function'
|
130
|
+
## true is the module is not yet loaded
|
131
|
+
## false is the module is loaded
|
132
|
+
def is_breakpoint_deferred(ip)
|
133
|
+
if !ip.kind_of? String
|
134
|
+
return false
|
135
|
+
end
|
136
|
+
|
137
|
+
m,f = ip.split('!')
|
138
|
+
|
139
|
+
if f.nil? or m.nil?
|
140
|
+
return true
|
141
|
+
end
|
142
|
+
|
143
|
+
modules.each do |d|
|
144
|
+
if d.szModule.to_s.match(/#{m}/)
|
145
|
+
return false
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
return true
|
150
|
+
end
|
151
|
+
|
152
|
+
## Look up a process by name or regex, returning an array of all
|
153
|
+
## matching processes, as objects.
|
154
|
+
def self.by_name(n)
|
155
|
+
n = Regexp.new(n) if not n.kind_of? Regexp
|
156
|
+
p = []
|
157
|
+
all_processes do |px|
|
158
|
+
if px.szExeFile =~ n
|
159
|
+
p << self.new(px.th32ProcessID)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
p
|
163
|
+
end
|
164
|
+
|
165
|
+
# Just need a PID to get started.
|
166
|
+
def initialize(pid)
|
167
|
+
@pid = pid
|
168
|
+
@h = Ragweed::Wrap32::open_process(pid)
|
169
|
+
@a = arena()
|
170
|
+
end
|
171
|
+
|
172
|
+
# Return the EXE name of the process.
|
173
|
+
def image
|
174
|
+
buf = "\x00" * 256
|
175
|
+
if Ragweed::Wrap32::nt_query_information_process(@h, 27, buf)
|
176
|
+
buf = buf.from_utf16
|
177
|
+
buf = buf[(buf.index("\\"))..-1]
|
178
|
+
return buf.asciiz
|
179
|
+
end
|
180
|
+
nil
|
181
|
+
end
|
182
|
+
|
183
|
+
# Return a list of all the threads in the process; relatively
|
184
|
+
# expensive, so cache the result.
|
185
|
+
def threads(full=false, &block)
|
186
|
+
return Ragweed::Wrap32::threads(@pid, &block) if block_given?
|
187
|
+
ret = []
|
188
|
+
Ragweed::Wrap32::threads(@pid) {|x| ((full) ? ret << x : ret << x.th32ThreadID) }
|
189
|
+
return ret
|
190
|
+
end
|
191
|
+
|
192
|
+
# Suspend all the threads in the process.
|
193
|
+
def suspend_all; threads.each {|x| suspend(x)}; end
|
194
|
+
|
195
|
+
# Resume all the threads in the process. XXX this will not
|
196
|
+
# resume threads with suspend counts greater than 1.
|
197
|
+
def resume_all; threads.each {|x| resume(x)}; end
|
198
|
+
|
199
|
+
# Suspend a thread by tid. Technically, this doesn't need to be
|
200
|
+
# a method; you can suspend a thread anywhere without a process handle.
|
201
|
+
def suspend(tid); Ragweed::Wrap32::open_thread(tid) {|x| Ragweed::Wrap32::suspend_thread(x)}; end
|
202
|
+
|
203
|
+
# Resume a thread by tid.
|
204
|
+
def resume(tid); Ragweed::Wrap32::open_thread(tid) {|x| Ragweed::Wrap32::resume_thread(x)}; end
|
205
|
+
|
206
|
+
# List the modules for the process, either yielding a struct for
|
207
|
+
# each to a block, or returning a list.
|
208
|
+
def modules(&block)
|
209
|
+
if block_given?
|
210
|
+
Ragweed::Wrap32::list_modules(@pid, &block)
|
211
|
+
else
|
212
|
+
ret = []
|
213
|
+
Ragweed::Wrap32::list_modules(@pid) {|x| ret << x}
|
214
|
+
return ret
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
# Read/write ranges of data or fixnums to/from the process by address.
|
219
|
+
def read(off, sz=4096); Ragweed::Wrap32::read_process_memory(@h, off, sz); end
|
220
|
+
def write(off, data); Ragweed::Wrap32::write_process_memory(@h, off, data); end
|
221
|
+
def read32(off); read(off, 4).unpack("L").first; end
|
222
|
+
def read16(off); read(off, 2).unpack("v").first; end
|
223
|
+
def read8(off); read(off, 1)[0]; end
|
224
|
+
def write32(off, v); write(off, [v].pack("L")); end
|
225
|
+
def write16(off, v); write(off, [v].pack("v")); end
|
226
|
+
def write8(off, v); write(off, v.chr); end
|
227
|
+
|
228
|
+
# call a function, by name or address, in the process, using
|
229
|
+
# CreateRemoteThread
|
230
|
+
def remote_call(meth, *args)
|
231
|
+
loc = meth
|
232
|
+
loc = get_proc(loc) if loc.kind_of? String
|
233
|
+
loc = Ragweed::Ptr.new loc
|
234
|
+
raise "bad proc name" if loc.null?
|
235
|
+
t = Trampoline.new(self, loc)
|
236
|
+
t.call *args
|
237
|
+
end
|
238
|
+
|
239
|
+
# Can I write to this address in the process?
|
240
|
+
def writeable?(off); Ragweed::Wrap32::writeable? @h, off; end
|
241
|
+
|
242
|
+
# Use VirtualAllocEx to grab a block of memory in the process. This
|
243
|
+
# is expensive, the equivalent of mmap()'ing for each allocation.
|
244
|
+
def syscall_alloc(sz); ptr(Ragweed::Wrap32::virtual_alloc_ex(@h, sz)); end
|
245
|
+
|
246
|
+
# Use arenas, when possible, to quickly allocate memory. The upside
|
247
|
+
# is this is very fast. The downside is you can't free the memory
|
248
|
+
# without invalidating every allocation you've made prior.
|
249
|
+
def alloc(sz, syscall=false)
|
250
|
+
if syscall or sz > 4090
|
251
|
+
ret = syscall_alloc(sz)
|
252
|
+
else
|
253
|
+
ptr(@a.alloc(sz))
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
# Free the return value of syscall_alloc. Do NOT use for the return
|
258
|
+
# value of alloc.
|
259
|
+
def free(off)
|
260
|
+
Ragweed::Wrap32::virtual_free_ex(@h, off)
|
261
|
+
end
|
262
|
+
|
263
|
+
# Convert an address to "module+10h" notation, when possible.
|
264
|
+
def to_modoff(off, force=false)
|
265
|
+
if not @modules or force
|
266
|
+
@modules = modules.sort {|x,y| x.modBaseAddr <=> y.modBaseAddr}
|
267
|
+
end
|
268
|
+
|
269
|
+
@modules.each do |m|
|
270
|
+
if off >= m.modBaseAddr and off < (m.modBaseAddr + m.modBaseSize)
|
271
|
+
return "#{ m.szModule }+#{ (off - m.modBaseAddr).to_s(16) }h"
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
return "#{ off.to_x }h"
|
276
|
+
end
|
277
|
+
|
278
|
+
# Get another allocation arena for this process. Pretty cheap. Given
|
279
|
+
# a block, behaves like File#open, disposing of the arena when you're
|
280
|
+
# done.
|
281
|
+
def arena(&block)
|
282
|
+
me = self
|
283
|
+
a = Arena.new(lambda {me.syscall_alloc(4096)},
|
284
|
+
lambda {|p| me.free(p)},
|
285
|
+
lambda {|dst, src| me.write(dst, src)})
|
286
|
+
if block_given?
|
287
|
+
ret = yield a
|
288
|
+
a.release
|
289
|
+
return ret
|
290
|
+
end
|
291
|
+
a
|
292
|
+
end
|
293
|
+
|
294
|
+
# Insert a string anywhere into the memory of the remote process,
|
295
|
+
# returning its address, using an arena.
|
296
|
+
def insert(buf); @a.copy(buf); end
|
297
|
+
|
298
|
+
# List all memory regions in the remote process by iterating over
|
299
|
+
# VirtualQueryEx. With a block, yields MEMORY_BASIC_INFORMATION
|
300
|
+
# structs. Without it, returns [baseaddr,size] tuples.
|
301
|
+
#
|
302
|
+
# We "index" this list, so that we can refer to memory locations
|
303
|
+
# by "region number", which is a Rubycorn-ism and not a Win32-ism.
|
304
|
+
# You'll see lots of functions asking for memory indices, and this
|
305
|
+
# is what they're referring to.
|
306
|
+
def list_memory(&block)
|
307
|
+
ret = []
|
308
|
+
i = 0
|
309
|
+
while (mbi = Ragweed::Wrap32::virtual_query_ex(@h, i))
|
310
|
+
break if (not ret.empty? and mbi.BaseAddress == 0)
|
311
|
+
if block_given?
|
312
|
+
yield mbi
|
313
|
+
else
|
314
|
+
base = mbi.BaseAddress || 0
|
315
|
+
size = mbi.RegionSize || 0
|
316
|
+
ret << [base,size] if mbi.State & 0x1000 # MEM_COMMIT
|
317
|
+
i = base + size
|
318
|
+
end
|
319
|
+
end
|
320
|
+
ret
|
321
|
+
end
|
322
|
+
|
323
|
+
# Human-readable standard output of list_memory. Remember that the
|
324
|
+
# index number is important.
|
325
|
+
def dump_memory_list
|
326
|
+
list_memory.each_with_index {|x,i| puts "#{ i }. #{ x[0].to_s(16) }(#{ x[1] })"}
|
327
|
+
true
|
328
|
+
end
|
329
|
+
|
330
|
+
# Read an entire memory region into a string by region number.
|
331
|
+
def get_memory(i, opts={})
|
332
|
+
refresh opts
|
333
|
+
read(@memlist[i][0], @memlist[i][1])
|
334
|
+
end
|
335
|
+
|
336
|
+
# Print a canonical hexdump of an entire memory region by region number.
|
337
|
+
def dump_memory(i, opts={}); get_memory(i, opts).hexdump; end
|
338
|
+
|
339
|
+
# In Python, and maybe Ruby, it was much faster to work on large
|
340
|
+
# memory regions a 4k page at a time, rather than reading the
|
341
|
+
# whole thing into one big string. Scan takes a memory region
|
342
|
+
# and yields 4k chunks of it to a block, along with the length
|
343
|
+
# of each chunk.
|
344
|
+
def scan(i, opts={})
|
345
|
+
refresh opts
|
346
|
+
memt = @memlist[i]
|
347
|
+
if memt[1] > 4096
|
348
|
+
0.step(memt[1], 4096) do |i|
|
349
|
+
block = (memt[1] - i).cap(4096)
|
350
|
+
yield read(memt[0] + i, block), memt[0]+i
|
351
|
+
end
|
352
|
+
else
|
353
|
+
yield read(memt[0], memt[1]), memt[0]
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
# Dump thread context, returning a struct that contains things like
|
358
|
+
# .Eip and .Eax.
|
359
|
+
def thread_context(tid)
|
360
|
+
Ragweed::Wrap32::open_thread(tid) do |h|
|
361
|
+
Ragweed::Wrap32::get_thread_context(h)
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
365
|
+
# Take a region of memory and walk over it in 255-byte samples (less
|
366
|
+
# than 255 bytes and you lose accuracy, but you can increase it with
|
367
|
+
# the ":window" option), computing entropy for each sample, returning
|
368
|
+
# a list of [offset,entropy] tuples.
|
369
|
+
def entropy_map(i, opts={})
|
370
|
+
ret = []
|
371
|
+
startoff = opts[:starting_offset]
|
372
|
+
startoff ||= 0
|
373
|
+
window = opts[:window] || 255
|
374
|
+
scan(i, opts) do |block, soff|
|
375
|
+
startoff.stepwith(block.size, window) do |off, len|
|
376
|
+
ret << [off, block[off,len].entropy]
|
377
|
+
end
|
378
|
+
end
|
379
|
+
return ret
|
380
|
+
end
|
381
|
+
|
382
|
+
# Given a source and destination memory region, scan through "source"
|
383
|
+
# looking for properly-aligned U32LE values that would be valid pointers
|
384
|
+
# into "destination". The ":range_start" and ":range_end" options
|
385
|
+
# constrain what a "valid pointer" into "destination" is.
|
386
|
+
def pointers_to(src, dst, opts={})
|
387
|
+
refresh opts
|
388
|
+
ret = {}
|
389
|
+
range = ((opts[:range_start] || @memlist[dst][0])..(opts[:range_stop] || @memlist[dst][0]+@memlist[dst][1]))
|
390
|
+
scan(src, opts) do |block, soff|
|
391
|
+
0.stepwith(block.size, 4) do |off, len|
|
392
|
+
if len == 4
|
393
|
+
if range.member? block[off,4].to_l32
|
394
|
+
ret[soff + off] = block[off,4].to_l32
|
395
|
+
end
|
396
|
+
end
|
397
|
+
end
|
398
|
+
end
|
399
|
+
return ret
|
400
|
+
end
|
401
|
+
|
402
|
+
# Given a memory region number, do a Unix strings(1) on it. Valid
|
403
|
+
# options:
|
404
|
+
# :unicode: you probably always want to set this to "true"
|
405
|
+
# :minimum: how small strings to accept.
|
406
|
+
#
|
407
|
+
# Fairly slow.
|
408
|
+
def strings_mem(i, opts={})
|
409
|
+
ret = []
|
410
|
+
opts[:offset] ||= 0
|
411
|
+
scan(i) do |block, soff|
|
412
|
+
while 1
|
413
|
+
off, size = block.nextstring(opts)
|
414
|
+
break if not off
|
415
|
+
opts[:offset] += (off + size)
|
416
|
+
ret << [soff+off, size, block[off,size]]
|
417
|
+
end
|
418
|
+
end
|
419
|
+
ret
|
420
|
+
end
|
421
|
+
|
422
|
+
# Given a string key, find it in memory. Very slow. Will read all
|
423
|
+
# memory regions, but you can provide ":index_range", which must be
|
424
|
+
# a Range object, to constrain which ranges to search through.
|
425
|
+
# Returns a list of structs containing absolute memory locations,
|
426
|
+
# the index of the region, and some surrounding context for the
|
427
|
+
# hit.
|
428
|
+
def hunt(key, opts={})
|
429
|
+
ret = []
|
430
|
+
refresh opts
|
431
|
+
range = opts[:index_range] || (0..@memlist.size)
|
432
|
+
@memlist.each_with_index do |t, i|
|
433
|
+
if range.member? i
|
434
|
+
if opts[:noisy]
|
435
|
+
puts "#{ i }. #{ t[0].to_s(16) } -> #{ (t[0]+t[1]).to_s(16) }"
|
436
|
+
end
|
437
|
+
scan(i, opts) do |block, soff|
|
438
|
+
if (needle = block.index(key))
|
439
|
+
r = OpenStruct.new
|
440
|
+
r.location = (t[0] + soff + needle)
|
441
|
+
r.index = i
|
442
|
+
r.context = block
|
443
|
+
ret << r
|
444
|
+
return ret if opts[:first]
|
445
|
+
end
|
446
|
+
end
|
447
|
+
end
|
448
|
+
end
|
449
|
+
ret
|
450
|
+
end
|
451
|
+
|
452
|
+
private
|
453
|
+
|
454
|
+
def windowize(i, opts={})
|
455
|
+
window = opts[:window] || 1024
|
456
|
+
if window == :auto
|
457
|
+
r = region_range(i)
|
458
|
+
window = (r.last - r.first) / 60
|
459
|
+
end
|
460
|
+
return window
|
461
|
+
end
|
462
|
+
|
463
|
+
public
|
464
|
+
|
465
|
+
# Like entropy_map, scan a process and compute adler16 checksums for
|
466
|
+
# 1k (or :window) blocks.
|
467
|
+
def adler_map(i, opts={})
|
468
|
+
refresh opts
|
469
|
+
window = windowize(i, opts)
|
470
|
+
ret = []
|
471
|
+
scan(i, opts) do |block,soff|
|
472
|
+
0.stepwith(block.size-1, window) do |off, len|
|
473
|
+
if (b = block[off,len])
|
474
|
+
ret << b.adler
|
475
|
+
end
|
476
|
+
end
|
477
|
+
end
|
478
|
+
ret
|
479
|
+
end
|
480
|
+
|
481
|
+
# If you store the adler map, you've compressed the memory region
|
482
|
+
# down to a small series of fixnums, and you can use it with this
|
483
|
+
# function to re-check the memory region and see if anything's changing.
|
484
|
+
def adler_compare(i, orig, opts={})
|
485
|
+
refresh opts
|
486
|
+
window = windowize(i, opts)
|
487
|
+
ret = []
|
488
|
+
c = -1
|
489
|
+
scan(i, opts) do |block,soff|
|
490
|
+
0.stepwith(block.size-1, window) do |off, len|
|
491
|
+
if block[off,len].adler != orig[c += 1]
|
492
|
+
ret << soff+off
|
493
|
+
end
|
494
|
+
end
|
495
|
+
end
|
496
|
+
ret
|
497
|
+
end
|
498
|
+
|
499
|
+
# Quick and dirty visualization of a memory region by checksum
|
500
|
+
# changes.
|
501
|
+
class AdlerChart
|
502
|
+
|
503
|
+
# Create with a WinProcess and region index
|
504
|
+
def initialize(p, i)
|
505
|
+
@p = p
|
506
|
+
@i = i
|
507
|
+
@initstate = map
|
508
|
+
end
|
509
|
+
|
510
|
+
private
|
511
|
+
def map; @p.adler_map(@i, :window => :auto); end
|
512
|
+
|
513
|
+
public
|
514
|
+
|
515
|
+
# Just puts the chart repeatedly, to get:
|
516
|
+
# ........................................................*.*..
|
517
|
+
# ..........................................................*..
|
518
|
+
# ..........................................................*..
|
519
|
+
# Where * represents a chunk of memory that has changed.
|
520
|
+
def to_s
|
521
|
+
s = StringIO.new
|
522
|
+
newstate = map
|
523
|
+
@initstate.each_with_index do |sum, i|
|
524
|
+
if sum != newstate[i]
|
525
|
+
s.write "*"
|
526
|
+
@initstate[i] = newstate[i]
|
527
|
+
else
|
528
|
+
s.write "."
|
529
|
+
end
|
530
|
+
end
|
531
|
+
s.rewind;s.read()
|
532
|
+
end
|
533
|
+
end
|
534
|
+
|
535
|
+
# See WinProcess::AdlerChart. Get one.
|
536
|
+
def adler_chart(i)
|
537
|
+
AdlerChart.new self, i
|
538
|
+
end
|
539
|
+
|
540
|
+
# Given a memory region, use the adler routines to create a checksum
|
541
|
+
# map, wait a short period of time, and scan for changes, to find
|
542
|
+
# churning memory.
|
543
|
+
def changes(i, sleeptime=0.5)
|
544
|
+
q = adler_map(i)
|
545
|
+
sleep(sleeptime)
|
546
|
+
adler_compare(i, q)
|
547
|
+
end
|
548
|
+
|
549
|
+
# Get the memory range, as a Ruby Range, for a region by index
|
550
|
+
def region_range(i, opts={})
|
551
|
+
refresh opts
|
552
|
+
(@memlist[i][0]..(@memlist[i][1]+@memlist[i][0]))
|
553
|
+
end
|
554
|
+
|
555
|
+
# Figure out what region (by region index) has an address
|
556
|
+
def which_region_has?(addr, opts={})
|
557
|
+
refresh opts
|
558
|
+
@memlist.each_with_index do |r, i|
|
559
|
+
return i if (r[0]..r[0]+r[1]).member? addr
|
560
|
+
end
|
561
|
+
return nil
|
562
|
+
end
|
563
|
+
|
564
|
+
# Do something with a thread while its suspended
|
565
|
+
def with_suspended_thread(tid)
|
566
|
+
ret = nil
|
567
|
+
Ragweed::Wrap32::with_suspended_thread(tid) {|x| ret = yield}
|
568
|
+
return ret
|
569
|
+
end
|
570
|
+
|
571
|
+
# For libraries compiled with frame pointers: walk EBP back
|
572
|
+
# until it stops giving intelligible addresses, and, at each
|
573
|
+
# step, grab the saved EIP from just before it.
|
574
|
+
def thread_stack_trace(tid)
|
575
|
+
with_suspended_thread(tid) do
|
576
|
+
ctx = thread_context(tid)
|
577
|
+
if((start = read32(ctx.Ebp)) != 0)
|
578
|
+
a = start
|
579
|
+
stack = [[start, read32(start+4)]]
|
580
|
+
while((a = read32(a)) and a != 0 and not stack.member?(a))
|
581
|
+
begin
|
582
|
+
stack << [a, read32(a+4)]
|
583
|
+
rescue; break; end
|
584
|
+
end
|
585
|
+
return stack
|
586
|
+
end
|
587
|
+
end
|
588
|
+
[]
|
589
|
+
end
|
590
|
+
|
591
|
+
# Human-readable version of thread_stack_trace, with module
|
592
|
+
# offsets.
|
593
|
+
def dump_stack_trace(tid)
|
594
|
+
thread_stack_trace(tid).each do |frame, code|
|
595
|
+
puts "#{ frame.to_x } @ #{ to_modoff(code) }"
|
596
|
+
end
|
597
|
+
end
|
598
|
+
alias_method :bt, :dump_stack_trace
|
599
|
+
|
600
|
+
def detour(loc, o={})
|
601
|
+
klass = o[:class] || Detour
|
602
|
+
loc = get_proc(loc)
|
603
|
+
r = klass.new(loc, o)
|
604
|
+
r.call if not o[:chicken]
|
605
|
+
return r
|
606
|
+
end
|
607
|
+
|
608
|
+
private
|
609
|
+
|
610
|
+
def refresh(opts={})
|
611
|
+
@memlist = list_memory if not @memlist or opts.delete(:refresh)
|
612
|
+
end
|
613
|
+
end
|
@@ -0,0 +1,75 @@
|
|
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
|
+
module Win
|
24
|
+
extend FFI::Library
|
25
|
+
|
26
|
+
ffi_lib 'kernel32','Advapi32'
|
27
|
+
ffi_convention :stdcall
|
28
|
+
attach_function 'OpenProcess', [ :long, :long, :long ], :long
|
29
|
+
attach_function 'OpenProcessToken', [:long, :long, :pointer ], :long
|
30
|
+
|
31
|
+
# ffi_lib 'advapi32'
|
32
|
+
# ffi_convention :stdcall
|
33
|
+
attach_function 'AdjustTokenPrivileges', [ :long, :long, :pointer, :long, :pointer, :pointer ], :long
|
34
|
+
attach_function 'LookupPrivilegeValueA', [ :pointer, :pointer, :pointer ] ,:long
|
35
|
+
end
|
36
|
+
|
37
|
+
class << self
|
38
|
+
|
39
|
+
def open_process_token(h, access=Ragweed::Wrap32::TokenAccess::ADJUST_PRIVILEGES)
|
40
|
+
outw = "\x00" * 4
|
41
|
+
r = Win.OpenProcessToken(h, access, outw)
|
42
|
+
raise WinX.new(:open_process_token) if r == 0
|
43
|
+
return outw.unpack("L").first
|
44
|
+
end
|
45
|
+
|
46
|
+
def adjust_token_privileges(t, disable, *args)
|
47
|
+
buf = FFI::MemoryPointer.from_string( [args.size].pack("L") + (args.map {|tup| tup.pack("QL") }.join("")) )
|
48
|
+
|
49
|
+
r = Win.AdjustTokenPrivileges(t, disable, buf, buf.size, nil, nil)
|
50
|
+
|
51
|
+
raise WinX.new(:adjust_token_privileges) if r == 0
|
52
|
+
end
|
53
|
+
|
54
|
+
def lookup_privilege_value(name)
|
55
|
+
namep = FFI::MemoryPointer.from_string(name)
|
56
|
+
outw = FFI::MemoryPointer.new(:int64, 1)
|
57
|
+
r = Win.LookupPrivilegeValueA(nil, namep, outw)
|
58
|
+
r = Win.LookupPrivilegeValueA(nil, name, outw)
|
59
|
+
raise WinX.new(:lookup_privilege_value) if r == 0
|
60
|
+
outw.read_long_long
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
class Ragweed::Wrap32::ProcessToken
|
66
|
+
def initialize(p=nil)
|
67
|
+
p ||= Ragweed::Wrap32::open_process(Ragweed::Wrap32::get_current_process_id)
|
68
|
+
@h = Ragweed::Wrap32::open_process_token(p)
|
69
|
+
end
|
70
|
+
|
71
|
+
def grant(name)
|
72
|
+
luid = Ragweed::Wrap32::lookup_privilege_value(name)
|
73
|
+
Ragweed::Wrap32::adjust_token_privileges(@h, 0, [luid, Ragweed::Wrap32::PrivilegeAttribute::ENABLED])
|
74
|
+
end
|
75
|
+
end
|