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,49 @@
|
|
1
|
+
module Ragweed
|
2
|
+
class Device
|
3
|
+
def initialize(path, options={})
|
4
|
+
@path = path
|
5
|
+
@options = options
|
6
|
+
@h = Ragweed::Wrap32::create_file(@path, :flags => Ragweed::Wrap32::FileAttributes::OVERLAPPED|Ragweed::Wrap32::FileAttributes::NORMAL)
|
7
|
+
end
|
8
|
+
|
9
|
+
def ioctl(code, inbuf, outbuf)
|
10
|
+
overlap(lambda do |o|
|
11
|
+
Ragweed::Wrap32::device_io_control(@h, code, inbuf, outbuf, o)
|
12
|
+
end) do |ret, count|
|
13
|
+
outbuf[0..count]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def read(sz)
|
18
|
+
overlap(lambda do |o|
|
19
|
+
Ragweed::Wrap32::read_file(@h, sz, o)
|
20
|
+
end) do |ret, count|
|
21
|
+
ret[0..count]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def write(buf)
|
26
|
+
overlap(lambda do |o|
|
27
|
+
Ragweed::Wrap32::write_file(@h, buf, o)
|
28
|
+
end) do |ret, count|
|
29
|
+
count
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def release
|
34
|
+
Ragweed::Wrap32::close_handle(@h)
|
35
|
+
@h = nil
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def overlap(proc)
|
41
|
+
o = Ragweed::Wrap32::Overlapped.get
|
42
|
+
ret = proc.call(o)
|
43
|
+
count = o.wait(@h)
|
44
|
+
r = yield ret, count
|
45
|
+
o.release
|
46
|
+
ret = r if r
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -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
|
+
Ragweed::Wrap32::wait_for_single_object @h
|
31
|
+
end
|
32
|
+
|
33
|
+
# Signal the event; anyone waiting on it is now released.
|
34
|
+
def signal
|
35
|
+
Ragweed::Wrap32::set_event(@h)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Force the event back to unsignalled state.
|
39
|
+
def reset
|
40
|
+
Ragweed::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
|
+
# TODO - 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
|
+
Ragweed::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
|
+
Ragweed::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(Ragweed::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 = Ragweed::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 Ragweed::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 Ragweed::Wrap32::threads(@pid, &block) if block_given?
|
80
|
+
ret = []
|
81
|
+
Ragweed::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); Ragweed::Wrap32::open_thread(tid) {|x| Ragweed::Wrap32::suspend_thread(x)}; end
|
95
|
+
|
96
|
+
# Resume a thread by tid.
|
97
|
+
def resume(tid); Ragweed::Wrap32::open_thread(tid) {|x| Ragweed::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
|
+
Ragweed::Wrap32::list_modules(@pid, &block)
|
104
|
+
else
|
105
|
+
ret = []
|
106
|
+
Ragweed::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); Ragweed::Wrap32::read_process_memory(@h, off, sz); end
|
113
|
+
def write(off, data); Ragweed::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); Ragweed::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(Ragweed::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
|
+
Ragweed::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 = Ragweed::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
|
+
Ragweed::Wrap32::open_thread(tid) do |h|
|
254
|
+
Ragweed::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
|
+
Ragweed::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
|