ragweed 0.2.0-java
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/History.txt +32 -0
- data/README.rdoc +60 -0
- data/README.txt +9 -0
- data/Rakefile +86 -0
- data/VERSION +1 -0
- data/examples/hittracertux.rb +45 -0
- data/examples/hittracerx.rb +63 -0
- data/examples/hook_notepad.rb +9 -0
- data/examples/snicker.rb +183 -0
- data/examples/tux-example.rb +24 -0
- data/lib/ragweed/arena.rb +55 -0
- data/lib/ragweed/blocks.rb +128 -0
- data/lib/ragweed/debugger32.rb +400 -0
- data/lib/ragweed/debuggerosx.rb +456 -0
- data/lib/ragweed/debuggertux.rb +502 -0
- data/lib/ragweed/detour.rb +223 -0
- data/lib/ragweed/ptr.rb +48 -0
- data/lib/ragweed/rasm/bblock.rb +73 -0
- data/lib/ragweed/rasm/isa.rb +1115 -0
- data/lib/ragweed/rasm.rb +59 -0
- data/lib/ragweed/sbuf.rb +197 -0
- data/lib/ragweed/trampoline.rb +103 -0
- data/lib/ragweed/utils.rb +182 -0
- data/lib/ragweed/wrap32/debugging.rb +401 -0
- data/lib/ragweed/wrap32/device.rb +49 -0
- data/lib/ragweed/wrap32/event.rb +50 -0
- data/lib/ragweed/wrap32/hooks.rb +39 -0
- data/lib/ragweed/wrap32/overlapped.rb +46 -0
- data/lib/ragweed/wrap32/process.rb +613 -0
- data/lib/ragweed/wrap32/process_token.rb +75 -0
- data/lib/ragweed/wrap32/thread_context.rb +142 -0
- data/lib/ragweed/wrap32/winx.rb +16 -0
- data/lib/ragweed/wrap32/wrap32.rb +583 -0
- data/lib/ragweed/wrap32.rb +59 -0
- data/lib/ragweed/wraposx/constants.rb +114 -0
- data/lib/ragweed/wraposx/kernelerrorx.rb +147 -0
- data/lib/ragweed/wraposx/region_info.rb +275 -0
- data/lib/ragweed/wraposx/structs.rb +102 -0
- data/lib/ragweed/wraposx/thread_context.rb +902 -0
- data/lib/ragweed/wraposx/thread_info.rb +160 -0
- data/lib/ragweed/wraposx/thread_info.rb.old +121 -0
- data/lib/ragweed/wraposx/wraposx.rb +356 -0
- data/lib/ragweed/wraposx.rb +60 -0
- data/lib/ragweed/wraptux/constants.rb +101 -0
- data/lib/ragweed/wraptux/process.rb +35 -0
- data/lib/ragweed/wraptux/threads.rb +7 -0
- data/lib/ragweed/wraptux/wraptux.rb +72 -0
- data/lib/ragweed/wraptux.rb +57 -0
- data/lib/ragweed.rb +112 -0
- data/ragweed.gemspec +102 -0
- data/spec/ragweed_spec.rb +7 -0
- data/spec/spec_helper.rb +16 -0
- data/test/test_ragweed.rb +0 -0
- metadata +121 -0
@@ -0,0 +1,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
|