iZsh-ragweed 0.1.8

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.
Files changed (62) hide show
  1. data/History.txt +29 -0
  2. data/README.rdoc +35 -0
  3. data/README.txt +9 -0
  4. data/Rakefile +30 -0
  5. data/examples/hittracertux.rb +49 -0
  6. data/examples/hittracerx.rb +63 -0
  7. data/examples/hook_notepad.rb +9 -0
  8. data/examples/snicker.rb +183 -0
  9. data/examples/tux-example.rb +23 -0
  10. data/lib/ragweed.rb +84 -0
  11. data/lib/ragweed/arena.rb +55 -0
  12. data/lib/ragweed/blocks.rb +128 -0
  13. data/lib/ragweed/debugger32.rb +338 -0
  14. data/lib/ragweed/debuggerosx.rb +419 -0
  15. data/lib/ragweed/debuggertux.rb +345 -0
  16. data/lib/ragweed/detour.rb +223 -0
  17. data/lib/ragweed/ptr.rb +48 -0
  18. data/lib/ragweed/rasm.rb +53 -0
  19. data/lib/ragweed/rasm/isa.rb +1046 -0
  20. data/lib/ragweed/rasm/util.rb +26 -0
  21. data/lib/ragweed/sbuf.rb +197 -0
  22. data/lib/ragweed/trampoline.rb +103 -0
  23. data/lib/ragweed/utils.rb +88 -0
  24. data/lib/ragweed/wrap32.rb +53 -0
  25. data/lib/ragweed/wrap32/debugging.rb +163 -0
  26. data/lib/ragweed/wrap32/device.rb +49 -0
  27. data/lib/ragweed/wrap32/event.rb +50 -0
  28. data/lib/ragweed/wrap32/hooks.rb +23 -0
  29. data/lib/ragweed/wrap32/overlapped.rb +46 -0
  30. data/lib/ragweed/wrap32/process.rb +506 -0
  31. data/lib/ragweed/wrap32/process_token.rb +59 -0
  32. data/lib/ragweed/wrap32/thread_context.rb +208 -0
  33. data/lib/ragweed/wrap32/winx.rb +16 -0
  34. data/lib/ragweed/wrap32/wrap32.rb +526 -0
  35. data/lib/ragweed/wraposx.rb +53 -0
  36. data/lib/ragweed/wraposx/constants.rb +112 -0
  37. data/lib/ragweed/wraposx/kernelerrorx.rb +147 -0
  38. data/lib/ragweed/wraposx/region_info.rb +250 -0
  39. data/lib/ragweed/wraposx/thread_context.rb +203 -0
  40. data/lib/ragweed/wraposx/thread_info.rb +225 -0
  41. data/lib/ragweed/wraposx/wraposx.rb +376 -0
  42. data/lib/ragweed/wraptux.rb +53 -0
  43. data/lib/ragweed/wraptux/constants.rb +68 -0
  44. data/lib/ragweed/wraptux/threads.rb +7 -0
  45. data/lib/ragweed/wraptux/wraptux.rb +76 -0
  46. data/spec/ragweed_spec.rb +7 -0
  47. data/spec/spec_helper.rb +16 -0
  48. data/tasks/ann.rake +80 -0
  49. data/tasks/bones.rake +20 -0
  50. data/tasks/gem.rake +201 -0
  51. data/tasks/git.rake +40 -0
  52. data/tasks/notes.rake +27 -0
  53. data/tasks/post_load.rake +34 -0
  54. data/tasks/rdoc.rake +51 -0
  55. data/tasks/rubyforge.rake +55 -0
  56. data/tasks/setup.rb +292 -0
  57. data/tasks/spec.rake +54 -0
  58. data/tasks/svn.rake +47 -0
  59. data/tasks/test.rake +40 -0
  60. data/tasks/zentest.rake +36 -0
  61. data/test/test_ragweed.rb +0 -0
  62. metadata +127 -0
@@ -0,0 +1,163 @@
1
+ module Ragweed::Wrap32
2
+ module DebugCodes
3
+ CREATE_PROCESS = 3
4
+ CREATE_THREAD = 2
5
+ EXCEPTION = 1
6
+ EXIT_PROCESS = 5
7
+ EXIT_THREAD = 4
8
+ LOAD_DLL = 6
9
+ OUTPUT_DEBUG_STRING = 8
10
+ RIP = 9
11
+ UNLOAD_DLL = 7
12
+ end
13
+
14
+ module ExceptionCodes
15
+ ACCESS_VIOLATION = 0xC0000005
16
+ BREAKPOINT = 0x80000003
17
+ ALIGNMENT = 0x80000002
18
+ SINGLE_STEP = 0x80000004
19
+ BOUNDS = 0xC0000008
20
+ DIVIDE_BY_ZERO = 0xC0000094
21
+ INT_OVERFLOW = 0xC0000095
22
+ INVALID_HANDLE = 0xC0000008
23
+ PRIV_INSTRUCTION = 0xC0000096
24
+ STACK_OVERFLOW = 0xC00000FD
25
+ INVALID_DISPOSITION = 0xC0000026
26
+ end
27
+
28
+ module ContinueCodes
29
+ CONTINUE = 0x10002
30
+ BREAK = 0x40010008
31
+ CONTROL_C = 0x40010005
32
+ UNHANDLED = 0x80010001
33
+ TERMINATE_THREAD = 0x40010003
34
+ TERMINATE_PROCESS = 0x40010004
35
+ end
36
+ end
37
+
38
+ class Ragweed::Wrap32::DebugEvent
39
+ (FIELDS = [ :code,
40
+ :pid,
41
+ :tid,
42
+ :file_handle,
43
+ :process_handle,
44
+ :thread_handle,
45
+ :base,
46
+ :offset,
47
+ :info_size,
48
+ :thread_base,
49
+ :start_address,
50
+ :image_name,
51
+ :unicode,
52
+ :exception_code,
53
+ :exception_flags,
54
+ :exception_record,
55
+ :exception_address,
56
+ :parameters,
57
+ :exit_code,
58
+ :dll_base,
59
+ :rip_error,
60
+ :rip_type]).each {|x| attr_accessor x}
61
+
62
+ def initialize(str)
63
+ @code, @pid, @tid = str.unpack("LLL")
64
+ str.shift 12
65
+ case @code
66
+ when Wrap32::DebugCodes::CREATE_PROCESS
67
+ @file_handle, @process_handle, @thread_handle,
68
+ @base, @offset,
69
+ @info_size, @thread_base, @start_address,
70
+ @image_name, @unicode = str.unpack("LLLLLLLLLH")
71
+ when Wrap32::DebugCodes::CREATE_THREAD
72
+ @thread_handle, @thread_base, @start_address = str.unpack("LLL")
73
+ when Wrap32::DebugCodes::EXCEPTION
74
+ @exception_code, @exception_flags,
75
+ @exception_record, @exception_address, @parameter_count = str.unpack("LLLLL")
76
+ str = str[20..-1]
77
+ @parameters = []
78
+ @parameter_count.times do
79
+ begin
80
+ @parameters << (str.unpack("L").first)
81
+ str = str[4..-1]
82
+ rescue;end
83
+ end
84
+ when Wrap32::DebugCodes::EXIT_PROCESS
85
+ @exit_code = str.unpack("L").first
86
+ when Wrap32::DebugCodes::EXIT_THREAD
87
+ @exit_code = str.unpack("L").first
88
+ when Wrap32::DebugCodes::LOAD_DLL
89
+ @file_handle, @dll_base, @offset,
90
+ @info_size, @image_name, @unicode = str.unpack("LLLLLH")
91
+ when Wrap32::DebugCodes::OUTPUT_DEBUG_STRING
92
+ when Wrap32::DebugCodes::RIP
93
+ @rip_error, @rip_type = str.unpack("LL")
94
+ when Wrap32::DebugCodes::UNLOAD_DLL
95
+ @dll_base = str.unpack("L").first
96
+ else
97
+ raise WinX.new(:wait_for_debug_event)
98
+ end
99
+ end
100
+
101
+ def inspect_code(c)
102
+ Wrap32::DebugCodes.to_key_hash[c].to_s || c.to_i
103
+ end
104
+
105
+ def inspect_exception_code(c)
106
+ Wrap32::ExceptionCodes.to_key_hash[c].to_s || c.to_i.to_s(16)
107
+ end
108
+
109
+ def inspect_parameters(p)
110
+ "[ " + p.map {|x| x.to_i.to_s}.join(", ") + " ]"
111
+ end
112
+
113
+ def inspect
114
+ body = lambda do
115
+ FIELDS.map do |f|
116
+ if (v = send(f))
117
+ f.to_s + "=" + (try("inspect_#{f.to_s}".intern, v) || v.to_i.to_s(16))
118
+ end
119
+ end.compact.join(" ")
120
+ end
121
+
122
+ "#<DebugEvent #{body.call}>"
123
+ end
124
+ end
125
+
126
+ module Ragweed::Wrap32
127
+ class << self
128
+ def wait_for_debug_event(ms=1000)
129
+ buf = "\x00" * 1024
130
+ r = CALLS["kernel32!WaitForDebugEvent:PL=L"].call(buf, ms)
131
+ raise WinX.new(:wait_for_debug_event) if r == 0 and get_last_error != 121
132
+ return Wrap32::DebugEvent.new(buf) if r != 0
133
+ return nil
134
+ end
135
+
136
+ def continue_debug_event(pid, tid, code)
137
+ r = CALLS["kernel32!ContinueDebugEvent:LLL=L"].call(pid, tid, code)
138
+ raise WinX.new(:continue_debug_event) if r == 0
139
+ return r
140
+ end
141
+
142
+ def debug_active_process(pid)
143
+ r = CALLS["kernel32!DebugActiveProcess:L=L"].call(pid)
144
+ raise WinX.new(:debug_active_process) if r == 0
145
+ return r
146
+ end
147
+
148
+ def debug_set_process_kill_on_exit(val=0)
149
+ r = CALLS["kernel32!DebugSetProcessKillOnExit:L=L"].call(val)
150
+ raise WinX.new(:debug_set_process_kill_on_exit) if r == 0
151
+ return r
152
+ end
153
+
154
+ def debug_active_process_stop(pid)
155
+ # don't care about failure
156
+ CALLS["kernel32!DebugActiveProcessStop:L=L"].call(pid)
157
+ end
158
+
159
+ def flush_instruction_cache(h, v1=0, v2=0)
160
+ CALLS["kernel32!FlushInstructionCache:LLL=L"].call(h, v1, v2)
161
+ end
162
+ end
163
+ end
@@ -0,0 +1,49 @@
1
+ module Ragweed
2
+ class Device
3
+ def initialize(path, options={})
4
+ @path = path
5
+ @options = options
6
+ @h = Wrap32::create_file(@path, :flags => Wrap32::FileAttributes::OVERLAPPED|Wrap32::FileAttributes::NORMAL)
7
+ end
8
+
9
+ def ioctl(code, inbuf, outbuf)
10
+ overlap(lambda do |o|
11
+ 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
+ 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
+ Wrap32::write_file(@h, buf, o)
28
+ end) do |ret, count|
29
+ count
30
+ end
31
+ end
32
+
33
+ def release
34
+ Wrap32::close_handle(@h)
35
+ @h = nil
36
+ end
37
+
38
+ private
39
+
40
+ def overlap(proc)
41
+ o = 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
+ 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