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,59 @@
1
+ module Ragweed::Wrap32
2
+ module TokenAccess
3
+ ADJUST_DEFAULT = 128
4
+ ADJUST_GROUPS = 64
5
+ ADJUST_PRIVILEGES = 32
6
+ ALL_ACCESS = 0xf00ff
7
+ ASSIGN_PRIMARY = 1
8
+ DUPLICATE = 2
9
+ EXECUTE = 0x20000
10
+ IMPERSONATE = 4
11
+ QUERY = 8
12
+ QUERY_SOURCE = 16
13
+ READ = 0x20008
14
+ WRITE = 0x200e0
15
+ end
16
+
17
+ module PrivilegeAttribute
18
+ ENABLED = 0x2
19
+ ENABLED_BY_DEFAULT = 0x1
20
+ USED_FOR_ACCESS = 0x80000000
21
+ end
22
+
23
+ class << self
24
+ def open_process_token(h, access=Wrap32::TokenAccess::ADJUST_PRIVILEGES)
25
+ outw = "\x00" * 4
26
+ r = CALLS["advapi32!OpenProcessToken:LLP=L"].call(h, access, outw)
27
+ raise WinX.new(:open_process_token) if r == 0
28
+ return outw.unpack("L").first
29
+ end
30
+
31
+ def adjust_token_privileges(t, disable, *args)
32
+ buf = [args.size].pack("L") + (args.map {|tup| tup.pack("QL") }.join(""))
33
+
34
+ r = CALLS["advapi32!AdjustTokenPrivileges:LLPLPP=L"].
35
+ call(t, disable, buf, buf.size, NULL, NULL)
36
+
37
+ raise WinX.new(:adjust_token_privileges) if r == 0
38
+ end
39
+
40
+ def lookup_privilege_value(name)
41
+ outw = "\x00" * 8
42
+ r = CALLS["advapi32!LookupPrivilegeValueA:PPP=L"].call(NULL, name, outw)
43
+ raise WinX.new(:lookup_privilege_value) if r == 0
44
+ return outw.unpack("Q").first
45
+ end
46
+ end
47
+ end
48
+
49
+ class Ragweed::Wrap32::ProcessToken
50
+ def initialize(p=nil)
51
+ p ||= Wrap32::open_process(Wrap32::get_current_process_id)
52
+ @h = Wrap32::open_process_token(p)
53
+ end
54
+
55
+ def grant(name)
56
+ luid = Wrap32::lookup_privilege_value(name)
57
+ Wrap32::adjust_token_privileges(@h, 0, [luid, Wrap32::PrivilegeAttribute::ENABLED])
58
+ end
59
+ end
@@ -0,0 +1,208 @@
1
+ module Ragweed::Wrap32
2
+ module EFlags
3
+ CARRY = (1<< 0)
4
+ X0 = (1<< 1)
5
+ PARITY = (1<< 2)
6
+ X1 = (1<< 3)
7
+ ADJUST = (1<< 4)
8
+ X2 = (1<< 5)
9
+ ZERO = (1<< 6)
10
+ SIGN = (1<< 7)
11
+ TRAP = (1<< 8)
12
+ INTERRUPT = (1<< 9)
13
+ DIRECTION = (1<< 10)
14
+ OVERFLOW = (1<< 11)
15
+ IOPL1 = (1<< 12)
16
+ IOPL2 = (1<< 13)
17
+ NESTEDTASK = (1<< 14)
18
+ X3 = (1<< 15)
19
+ RESUME = (1<< 16)
20
+ V86MODE = (1<< 17)
21
+ ALIGNCHECK = (1<< 18)
22
+ VINT = (1<< 19)
23
+ VINTPENDING = (1<< 20)
24
+ CPUID = (1<< 21)
25
+ end
26
+
27
+ module ContextFlags
28
+ I386 = 0x10000
29
+ CONTROL = 1
30
+ INTEGER = 2
31
+ SEGMENTS = 4
32
+ FLOATING_POINT = 8
33
+ DEBUG_REGISTERS = 0x10
34
+
35
+ FULL = (I386|CONTROL|INTEGER|SEGMENTS)
36
+ DEBUG = (FULL|DEBUG_REGISTERS)
37
+ end
38
+ end
39
+
40
+ class Ragweed::Wrap32::ThreadContext
41
+ (FIELDS = [ [:context_flags, "L"],
42
+ [:dr0, "L"],
43
+ [:dr1, "L"],
44
+ [:dr2, "L"],
45
+ [:dr3, "L"],
46
+ [:dr6, "L"],
47
+ [:dr7, "L"],
48
+ [:floating_save, "a112"],
49
+ [:seg_gs, "L"],
50
+ [:seg_gs, "L"],
51
+ [:seg_es, "L"],
52
+ [:seg_ds, "L"],
53
+ [:edi, "L"],
54
+ [:esi, "L"],
55
+ [:ebx, "L"],
56
+ [:edx, "L"],
57
+ [:ecx, "L"],
58
+ [:eax, "L"],
59
+ [:ebp, "L"],
60
+ [:eip, "L"],
61
+ [:seg_cs, "L"],
62
+ [:eflags, "L"],
63
+ [:esp, "L"],
64
+ [:seg_ss, "L"],
65
+ [:spill, "a1024"]]).each {|x| attr_accessor x[0]}
66
+
67
+ def initialize(str=nil)
68
+ refresh(str) if str
69
+ end
70
+
71
+ def refresh(str)
72
+ if str
73
+ str.unpack(FIELDS.map {|x| x[1]}.join("")).each_with_index do |val, i|
74
+ instance_variable_set "@#{ FIELDS[i][0] }".intern, val
75
+ end
76
+ end
77
+ end
78
+
79
+ def to_s
80
+ FIELDS.map {|f| send(f[0])}.pack(FIELDS.map {|x| x[1]}.join(""))
81
+ end
82
+
83
+ def self.get(h)
84
+ self.new(Wrap32::get_thread_context_raw(h))
85
+ end
86
+
87
+ def get(h)
88
+ refresh(Wrap32::get_thread_context_raw(h))
89
+ end
90
+
91
+ def set(h)
92
+ Wrap32::set_thread_context_raw(h, self.to_s)
93
+ end
94
+
95
+ def inspect
96
+ body = lambda do
97
+ FIELDS.map do |f|
98
+ val = send(f[0])
99
+ "#{f[0]}=#{val.to_s(16) rescue val.to_s}"
100
+ end.join(", ")
101
+ end
102
+ "#<ThreadContext #{body.call}>"
103
+ end
104
+
105
+ def dump(&block)
106
+ maybe_hex = lambda {|a| begin; "\n" + (" " * 9) + block.call(a, 16).hexdump(true)[10..-2]; rescue; ""; end }
107
+ maybe_dis = lambda {|a| begin; "\n" + block.call(a, 16).distorm.map {|i| " " + i.mnem}.join("\n"); rescue; ""; end }
108
+
109
+ string =<<EOM
110
+ -----------------------------------------------------------------------
111
+ CONTEXT:
112
+ EIP: #{self.eip.to_s(16).rjust(8, "0")} #{maybe_dis.call(self.eip)}
113
+
114
+ EAX: #{self.eax.to_s(16).rjust(8, "0")} #{maybe_hex.call(self.eax)}
115
+ EBX: #{self.ebx.to_s(16).rjust(8, "0")} #{maybe_hex.call(self.ebx)}
116
+ ECX: #{self.ecx.to_s(16).rjust(8, "0")} #{maybe_hex.call(self.ecx)}
117
+ EDX: #{self.edx.to_s(16).rjust(8, "0")} #{maybe_hex.call(self.edx)}
118
+ EDI: #{self.edi.to_s(16).rjust(8, "0")} #{maybe_hex.call(self.edi)}
119
+ ESI: #{self.esi.to_s(16).rjust(8, "0")} #{maybe_hex.call(self.esi)}
120
+ EBP: #{self.ebp.to_s(16).rjust(8, "0")} #{maybe_hex.call(self.ebp)}
121
+ ESP: #{self.esp.to_s(16).rjust(8, "0")} #{maybe_hex.call(self.esp)}
122
+ EFL: #{self.eflags.to_s(2).rjust(32, "0")} #{Wrap32::EFlags.flag_dump(self.eflags)}
123
+ EOM
124
+ end
125
+
126
+ def single_step(v=true)
127
+ if v
128
+ @eflags |= Wrap32::EFlags::TRAP
129
+ else
130
+ @eflags &= ~(Wrap32::EFlags::TRAP)
131
+ end
132
+ end
133
+ end
134
+
135
+ module Ragweed::Wrap32
136
+ class << self
137
+ def get_thread_context_raw(h)
138
+ ctx = [Wrap32::ContextFlags::DEBUG,0,0,0,0,0,0,"\x00"*112,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"\x00"*1024].pack("LLLLLLLa112LLLLLLLLLLLLLLLLa1024")
139
+ ret = CALLS["kernel32!GetThreadContext:LP=L"].call(h, ctx)
140
+ if ret != 0
141
+ return ctx
142
+ else
143
+ raise WinX.new(:get_thread_context)
144
+ end
145
+ end
146
+
147
+ def set_thread_context_raw(h, c)
148
+ buf = c.to_s
149
+ ret = CALLS["kernel32!SetThreadContext:LP=L"].call(h, buf)
150
+ raise WinX.new(:set_thread_context) if ret == 0
151
+ return ret
152
+ end
153
+
154
+ def str2context(str)
155
+ ret = OpenStruct.new
156
+ ret.ContextFlags,
157
+ ret.Dr0,
158
+ ret.Dr1,
159
+ ret.Dr2,
160
+ ret.Dr3,
161
+ ret.Dr6,
162
+ ret.Dr7,
163
+ ret.FloatControlWord,
164
+ ret.FloatStatusWord,
165
+ ret.FloatTagWord,
166
+ ret.FloatErrorOffset,
167
+ ret.FloatErrorSelector,
168
+ ret.FloatDataOffset,
169
+ ret.FloatDataSelector,
170
+ ret.FloatRegisterArea,
171
+ ret.FloatCr0NpxState,
172
+ ret.SegGs,
173
+ ret.SegFs,
174
+ ret.SegEs,
175
+ ret.SegDs,
176
+ ret.Edi,
177
+ ret.Esi,
178
+ ret.Ebx,
179
+ ret.Edx,
180
+ ret.Ecx,
181
+ ret.Eax,
182
+ ret.Ebp,
183
+ ret.Eip,
184
+ ret.SegCs,
185
+ ret.EFlags,
186
+ ret.Esp,
187
+ ret.SegSs,
188
+ ret.Spill = str.unpack("LLLLLLLLLLLLLLA80LLLLLLLLLLLLLLLLLA1024")
189
+ return ret
190
+ end
191
+
192
+ # Retrieve the running context of a thread given its handle, returning a
193
+ # struct that mostly contains register values. Note that this will suspend
194
+ # and then resume the thread. Useful (among many other things) to sample
195
+ # EIP values to see what the code is doing.
196
+ def get_thread_context(h)
197
+ ctx = [Wrap32::ContextFlags::DEBUG,0,0,0,0,0,0,"\x00"*112,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"\x00"*1024].pack("LLLLLLLa112LLLLLLLLLLLLLLLLa1024")
198
+ suspend_thread(h)
199
+ ret = CALLS["kernel32!GetThreadContext:LP=L"].call(h, ctx)
200
+ resume_thread(h)
201
+ if ret != 0
202
+ return str2context(ctx)
203
+ else
204
+ raise WinX.new(:get_thread_context)
205
+ end
206
+ end
207
+ end
208
+ end
@@ -0,0 +1,16 @@
1
+ %w[ostruct Win32API pp].each {|x| require x}
2
+
3
+ module Ragweed;end
4
+ module Ragweed::Wrap32
5
+ class WinX < StandardError
6
+ attr_reader :code
7
+ attr_reader :msg
8
+ attr_reader :call
9
+ def initialize(sym=nil)
10
+ @call = sym
11
+ @code = Ragweed::Wrap32::get_last_error()
12
+ @msg = "#{(@call ? @call.to_s + ": " : "")}(#{@code}) #{ Ragweed::Wrap32::format_message(@code) }"
13
+ super @msg
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,526 @@
1
+ %w[ostruct Win32API pp].each {|x| require x}
2
+
3
+ module Ragweed;end
4
+ module Ragweed::Wrap32
5
+ NULL = 0x0
6
+
7
+ module PagePerms
8
+ EXECUTE = 0x10
9
+ EXECUTE_READ = 0x20
10
+ EXECUTE_READWRITE = 0x40
11
+ EXECUTE_WRITECOPY = 0x80
12
+ NOACCESS = 0x1
13
+ READONLY = 0x2
14
+ READWRITE = 0x4
15
+ WRITECOPY = 0x8
16
+
17
+ WRITEABLE = [EXECUTE_READWRITE,
18
+ EXECUTE_WRITECOPY,
19
+ READWRITE,
20
+ WRITECOPY]
21
+ end
22
+
23
+ module FileSharing
24
+ NONE = 0
25
+ DELETE = 4
26
+ READ = 1
27
+ WRITE = 2
28
+ end
29
+
30
+ module FileDisposition
31
+ CREATE_ALWAYS = 2
32
+ CREATE_NEW = 1
33
+ OPEN_ALWAYS = 4
34
+ OPEN_EXISTING = 3
35
+ TRUNCATE_EXISTING = 5
36
+ end
37
+
38
+ module FileAttributes
39
+ ARCHIVE = 0x20
40
+ ENCRYPTED = 0x4000
41
+ HIDDEN = 0x2
42
+ NORMAL = 0x80
43
+ OFFLINE = 0x1000
44
+ READONLY = 1
45
+ SYSTEM = 4
46
+ TEMPORARY = 0x100
47
+ BACKUP = 0x02000000
48
+ DELETE_ON_CLOSE = 0x04000000
49
+ NO_BUFFERING = 0x20000000
50
+ NO_RECALL = 0x00100000
51
+ REPARSE_POINT = 0x00200000
52
+ OVERLAPPED = 0x40000000
53
+ POSIX = 0x0100000
54
+ RANDOM_ACCESS = 0x10000000
55
+ SEQUENTIAL = 0x08000000
56
+ WRITE_THROUGH = 0x80000000
57
+ end
58
+
59
+ module FileAccess
60
+ GENERIC_READ = 0x80000000
61
+ GENERIC_WRITE = 0x40000000
62
+ GENERIC_EXECUTE = 0x20000000
63
+ GENERIC_ALL = 0x10000000
64
+ end
65
+
66
+ module FormatArgs
67
+ FROM_SYSTEM = 4096
68
+ ALLOCATE_BUFFER = 256
69
+ end
70
+
71
+ # Does 2 things:
72
+ # 1. Parses a terse notation for Win32 functions: "module!function:args=return",
73
+ # where "args" and "return" are in String#unpack notation.
74
+ #
75
+ # 2. Memoizes the Win32API lookup.
76
+ #
77
+ # Returns a callable object implementing the specified call.
78
+
79
+ CALLS = Hash.new do |h, str|
80
+ lib = proc = args = ret = nil
81
+ lib, rest = str.split "!"
82
+ proc, rest = rest.split ":"
83
+ args, ret = rest.split("=") if rest
84
+ ret ||= ""
85
+ args ||= []
86
+ raise "need proc" if not proc
87
+ h[str] = Win32API.new(lib, proc, args, ret)
88
+ end
89
+
90
+ # --------------------------------------------------------------------------------------
91
+
92
+ class << self
93
+
94
+ # Get a process handle given a pid
95
+ def open_process(pid)
96
+ r = CALLS["kernel32!OpenProcess:LLL=L"].call(0x1F0FFF, 0, pid)
97
+ raise WinX.new(:open_process) if r == 0
98
+ return r
99
+ end
100
+
101
+ # Get a thread handle given a tid; if a block is provided, the semantics are
102
+ # as File#open with a block.
103
+ def open_thread(tid, &block)
104
+ h = CALLS["kernel32!OpenThread:LLL=L"].call(0x1F03FF, 0, tid)
105
+ raise WinX.new(:open_thread) if h == 0
106
+ if block_given?
107
+ ret = yield h
108
+ close_handle(h)
109
+ return ret
110
+ end
111
+ h
112
+ end
113
+
114
+ # Close any Win32 handle. Reminder: Win32 handles are just integers, like file
115
+ # descriptors in Posix.
116
+ def close_handle(h)
117
+ raise WinX.new(:close_handle) if CALLS["kernel32!CloseHandle:L"].call(h) != 0
118
+ end
119
+
120
+ # Get the last error code (errno) (can't fail)
121
+ def get_last_error
122
+ CALLS["kernel32!GetLastError:=L"].call
123
+ end
124
+
125
+ # strerror(errno) (can't fail)
126
+ def format_message(code=nil)
127
+ code ||= get_last_error
128
+ buf = "\x00" * 4096
129
+ CALLS["kernel32!FormatMessageA:LPLLPLP"].
130
+ call(4096, NULL, code, 0x00000400, buf, 4096, NULL)
131
+ return buf.split("\x00")[0]
132
+ end
133
+
134
+ # Allocate memory in a remote process (or yourself, with handle -1)
135
+ def virtual_alloc_ex(h, sz, addr=NULL, prot=0x40)
136
+ r = CALLS["kernel32!VirtualAllocEx:LLLLL=L"].
137
+ call(h, addr, sz, 0x1000, prot)
138
+ raise WinX.new(:virtual_alloc_ex) if r == 0
139
+ return r
140
+ end
141
+
142
+ # Free memory in a remote process given the pointer returned from virtual_alloc_ex
143
+ def virtual_free_ex(h, ptr, type=0x8000)
144
+ r = CALLS["kernel32!VirtualFreeEx:LLLL=L"].call(h, ptr.to_i, 0, type)
145
+ raise WinX.new(:virtual_free_ex) if r == 0
146
+ return r
147
+ end
148
+
149
+ # Write a string into the memory of a remote process given its handle and an address
150
+ def write_process_memory(h, dst, val)
151
+ val = val.to_s if not val.kind_of? String
152
+ r = CALLS["kernel32!WriteProcessMemory:LLPLL=L"].call(h, dst.to_i, val, val.size, NULL)
153
+ raise WinX.new(:write_process_memory) if r == 0
154
+ return r
155
+ end
156
+
157
+ # Read from a remote process given an address and length, returning a string.
158
+ def read_process_memory(h, ptr, len)
159
+ val = "\x00" * len
160
+ r = CALLS["kernel32!ReadProcessMemory:LLPLL=L"].call(h, ptr.to_i, val, len, NULL)
161
+ raise WinX.new(:read_process_memory) if r == 0
162
+ return val ## don't handle short reads XXX
163
+ end
164
+
165
+ def str2memory_basic_info(mbi)
166
+ s = OpenStruct.new
167
+ s.BaseAddress,
168
+ s.AllocationBase,
169
+ s.AllocationProtect,
170
+ s.RegionSize,
171
+ s.State,
172
+ s.Protect,
173
+ s.Type = mbi.unpack("LLLLLLL")
174
+ return s
175
+ end
176
+
177
+ # Return a struct with the MEMORY_BASIC_INFORMATION for a given address in the
178
+ # memory of a remote process. Gives you addressable memory ranges and protection
179
+ # flags.
180
+ def virtual_query_ex(h, ptr)
181
+ mbi = [0,0,0,0,0,0,0].pack("LLLLLLL")
182
+ if CALLS["kernel32!VirtualQueryEx:LLPL=L"].call(h, ptr, mbi, mbi.size)
183
+ str2memory_basic_info(mbi)
184
+ else
185
+ nil
186
+ end
187
+ end
188
+
189
+ # Change the protection of specific memory regions in a remote process.
190
+ def virtual_protect_ex(h, addr, prot, size=0)
191
+ old = [0].pack("L")
192
+ base = virtual_query_ex(h, addr).BaseAddress if size == 0
193
+ base ||= addr
194
+
195
+ if CALLS["kernel32!VirtualProtectEx:LLLLP=L"].call(h, base, size, prot, old)
196
+ old.unpack("L").first
197
+ else
198
+ raise WinX.new(:virtual_protect_ex)
199
+ end
200
+ end
201
+
202
+ # getpid
203
+ def get_current_process_id
204
+ CALLS["kernel32!GetCurrentProcessId:=L"].call # can't realistically fail
205
+ end
206
+
207
+ # gettid
208
+ def get_current_thread_id
209
+ CALLS["kernel32!GetCurrentThreadId:=L"].call # can't realistically fail
210
+ end
211
+
212
+ # Given a DLL name, get a handle to the DLL.
213
+ def get_module_handle(name)
214
+ name = name.to_utf16
215
+ r = CALLS["kernel32!GetModuleHandleW:P=L"].call(name)
216
+ raise WinX.new(:get_module_handle) if r == 0
217
+ return r
218
+ end
219
+
220
+ # load a library explicitly from a dll
221
+ def load_library(name)
222
+ name = name.to_utf16
223
+ r = CALLS["kernel32!LoadLibraryW:P=L"].call(name)
224
+ raise WinX.new(:load_library) if r == 0
225
+ return r
226
+ end
227
+
228
+ # Using notation x = "foo!bar" or x = handle, y = meth, look up a function's
229
+ # address in a module. Note that this is local, not remote.
230
+ def get_proc_address(x, y=nil)
231
+ if not y
232
+ mod, meth = x.split "!"
233
+ h = get_module_handle(mod)
234
+ else
235
+ h = x
236
+ meth = y
237
+ end
238
+
239
+ r = CALLS["kernel32!GetProcAddress:LP=L"].call(h, meth)
240
+ return r # pass error through
241
+ end
242
+
243
+ # Select(2), for a single object handle.
244
+ def wait_for_single_object(h)
245
+ r = CALLS["kernel32!WaitForSingleObject:LL=L"].call(h, -1)
246
+ raise WinX.new(:wait_for_single_object) if r == -1
247
+ end
248
+
249
+ def str2process_info(str)
250
+ ret = OpenStruct.new
251
+ ret.dwSize,
252
+ ret.cntUsage,
253
+ ret.th32ProcessID,
254
+ ret.th32DefaultHeapID,
255
+ ret.th32ModuleID,
256
+ ret.cntThreads,
257
+ ret.th32ParentProcessID,
258
+ ret.pcPriClassBase,
259
+ ret.dwFlags,
260
+ ret.szExeFile = str.unpack("LLLLLLLLLA2048")
261
+ ret.szExeFile = ret.szExeFile.asciiz
262
+ return ret
263
+ end
264
+
265
+ # Use Toolhelp32 to enumerate all running processes on the box, returning
266
+ # a struct with PIDs and executable names.
267
+ def all_processes
268
+ h = CALLS["kernel32!CreateToolhelp32Snapshot:LL=L"].call(0x2, 0)
269
+ if h != -1
270
+ pi = [(9*4)+2048,0,0,0,0,0,0,0,0,"\x00"*2048].pack("LLLLLLLLLa2048")
271
+ if CALLS["kernel32!Process32First:LP=L"].call(h, pi) != 0
272
+ yield str2process_info(pi)
273
+ while CALLS["kernel32!Process32Next:LP=L"].call(h, pi) != 0
274
+ yield str2process_info(pi)
275
+ end
276
+ end
277
+ else
278
+ raise WinX.new(:create_toolhelp32_snapshot)
279
+ end
280
+ end
281
+
282
+ def str2module_info(str)
283
+ ret = OpenStruct.new
284
+ ret.dwSize,
285
+ ret.th32ModuleID,
286
+ ret.th32ProcessID,
287
+ ret.GlblcntUsage,
288
+ ret.ProccntUsage,
289
+ ret.modBaseAddr,
290
+ ret.modBaseSize,
291
+ ret.hModule,
292
+ ret.szModule,
293
+ ret.szExePath = str.unpack("LLLLLLLLA256A260")
294
+ ret.szModule = ret.szModule.asciiz
295
+ ret.szExePath = ret.szExePath.asciiz
296
+ return ret
297
+ end
298
+
299
+ # Given a pid, enumerate the modules loaded into the process, returning base
300
+ # addresses, memory ranges, and the module name.
301
+ def list_modules(pid=0)
302
+ h = CALLS["kernel32!CreateToolhelp32Snapshot:LL=L"].call(0x8, pid)
303
+ if h != -1
304
+ mi = [260+256+(8*4),0,0,0,0,0,0,0,"\x00"*256,"\x00"*260].pack("LLLLLLLLa256a260")
305
+ if w32("kernel32!Module32First:LP=L").call(h, mi) != 0
306
+ yield str2module_info(mi)
307
+ while w32("kernel32!Module32Next:LP=L").call(h, mi) != 0
308
+ yield str2module_info(mi)
309
+ end
310
+ end
311
+ else
312
+ raise WinX.new(:create_toolhelp32_snapshot)
313
+ end
314
+ end
315
+
316
+ # Use virtual_query_ex to tell whether an address is writable.
317
+ def writeable?(h, off)
318
+ if (x = virtual_query_ex(h, off))
319
+ return PagePerms::WRITEABLE.member?(x.Protect & 0xFF)
320
+ else
321
+ return false
322
+ end
323
+ end
324
+
325
+ # NQIP does a lot of things, the most useful of which are getting the
326
+ # image name of a running process, and telling whether a debugger is loaded. This
327
+ # interface is Ioctl-style; provide an ordinal and a buffer to pass results through.
328
+ def nt_query_information_process(h, ord, buf)
329
+ lenp = [0].pack("L")
330
+ if CALLS["ntdll!NtQueryInformationProcess:LLPLP=L"].call(h, ord, buf, buf.size, lenp) == 0
331
+ len = lenp.unpack("L").first
332
+ return buf[0..(len-1)]
333
+ end
334
+ nil
335
+ end
336
+
337
+ def str2thread_info(str)
338
+ ret = OpenStruct.new
339
+ ret.dwSize,
340
+ ret.cntUsage,
341
+ ret.th32ThreadID,
342
+ ret.th32OwnerProcessID,
343
+ ret.tpBasePri,
344
+ ret.tpDeltaPri,
345
+ ret.thFlags = str.unpack("LLLLLLL")
346
+ return ret
347
+ end
348
+
349
+ # List all the threads in a process given its pid, returning a struct containing
350
+ # tids and run state. This is relatively expensive, because it uses Toolhelp32.
351
+ def threads(pid)
352
+ h = CALLS["kernel32!CreateToolhelp32Snapshot:LL=L"].call(0x4, pid)
353
+ if h != -1
354
+ mi = [(7*4),0,0,0,0,0,0].pack("LLLLLLL")
355
+ if w32("kernel32!Thread32First:LP=L").call(h, mi) != 0
356
+ ti = str2thread_info(mi)
357
+ yield str2thread_info(mi) if ti.th32OwnerProcessID == pid
358
+ while w32("kernel32!Thread32Next:LP=L").call(h, mi) != 0
359
+ ti = str2thread_info(mi)
360
+ yield str2thread_info(mi) if ti.th32OwnerProcessID == pid
361
+ end
362
+ end
363
+ else
364
+ raise WinX.new(:create_toolhelp32_snapshot)
365
+ end
366
+ end
367
+
368
+ # Suspend a thread given its handle.
369
+ def suspend_thread(h)
370
+ r = CALLS["kernel32!SuspendThread:L=L"].call(h)
371
+ raise WinX.new(:suspend_thread) if r == 0
372
+ return r
373
+ end
374
+
375
+ # Resume a suspended thread, returning nonzero if the thread was suspended,
376
+ # and 0 if it was running.
377
+ def resume_thread(h)
378
+ CALLS["kernel32!ResumeThread:L=L"].call(h)
379
+ end
380
+
381
+ # Create a remote thread in the process, starting at the location
382
+ # "start", with the threadproc argument "arg"
383
+ def create_remote_thread(h, start, arg)
384
+ r = CALLS["kernel32!CreateRemoteThread:LLLLLLL=L"].call(h, NULL, 0, start.to_i, arg.to_i, 0, 0)
385
+ raise WinX.new(:create_remote_thread) if r == 0
386
+ return r
387
+ end
388
+
389
+ def sleep(ms=0)
390
+ CALLS["kernel32!Sleep:L=L"].call(ms)
391
+ end
392
+
393
+ # clone a handle out of another open process (or self, with -1)
394
+ def duplicate_handle(ph, h)
395
+ ret = "\x00\x00\x00\x00"
396
+ r = CALLS["kernel32!DuplicateHandle:LLLPLLL=L"].call(ph, h, -1, ret, 0, 0, 0x2)
397
+ raise WinX.new(:duplicate_handle) if r == 0
398
+ ret.to_l32
399
+ end
400
+
401
+ def create_file(name, opts={})
402
+ opts[:disposition] ||= FileDisposition::OPEN_ALWAYS
403
+ opts[:sharing] ||= FileSharing::READ | FileSharing::WRITE
404
+ opts[:access] ||= FileAccess::GENERIC_ALL
405
+ opts[:flags] ||= 0
406
+
407
+ r = CALLS["kernel32!CreateFile:PLLPLLP=L"].
408
+ call(name, opts[:access], opts[:sharing], NULL, opts[:disposition], opts[:flags], NULL)
409
+ raise WinX.new(:create_file) if r == -1
410
+ return r
411
+ end
412
+
413
+ # i haven't made this work, but named handles are kind of silly anyways
414
+ def open_event(name)
415
+ r = CALLS["kernel32!OpenEvent:LLP=L"].call(0, 0, name)
416
+ raise WinX.new(:open_event) if r == 0
417
+ return r
418
+ end
419
+
420
+ # signal an event
421
+ def set_event(h)
422
+ r = CALLS["kernel32!SetEvent:L=L"].call(h)
423
+ raise WinX.new(:set_event) if r == 0
424
+ return r
425
+ end
426
+
427
+ # force-unsignal event (waiting on the event handle also does this)
428
+ def reset_event(h)
429
+ r = CALLS["kernel32!ResetEvent:L=L"].call(h)
430
+ raise WinX.new(:reset_event) if r == 0
431
+ return r
432
+ end
433
+
434
+ # create an event, which you can signal and wait on across processes
435
+ def create_event(name=nil, auto=false, signalled=false)
436
+ auto = (1 if auto) || 0
437
+ signalled = (1 if signalled) || 0
438
+ name ||= 0
439
+
440
+ r = CALLS["kernel32!CreateEvent:LLLP=L"].call(0, auto, signalled, name);
441
+ raise WinX.new(:create_event) if r == 0
442
+ return r
443
+ end
444
+
445
+ def write_file(h, buf, overlapped=nil)
446
+ if overlapped
447
+ opp = overlapped.to_s
448
+ else
449
+ opp = NULL
450
+ end
451
+
452
+ outw = "\x00" * 4
453
+ r = CALLS["kernel32!WriteFile:LPLPP=L"].call(h, buf, buf.size, outw, opp)
454
+ raise WinX.new(:write_file) if r == 0 and get_last_error != 997
455
+ return buf, outw.unpack("L").first
456
+ end
457
+
458
+ def read_file(h, count, overlapped=nil)
459
+ if overlapped
460
+ opp = overlapped.to_s
461
+ else
462
+ opp = NULL
463
+ end
464
+ outw = "\x00" * 4
465
+ if not (buf = overlapped.try(:target)) or buf.size < count
466
+ buf = "\x00" * count
467
+ overlapped.target = buf if overlapped
468
+ end
469
+
470
+ r = CALLS["kernel32!ReadFile:LPLPP=L"].call(h, buf, count, outw, opp)
471
+ raise WinX.new(:read_file) if r == 0 and get_last_error != 997
472
+ return buf, outw.unpack("L").first
473
+ end
474
+
475
+ def device_io_control(h, code, inbuf, outbuf, overlapped=NULL)
476
+ overlapped = overlapped.to_s if overlapped
477
+ outw = "\x00" * 4
478
+ r = CALLS["kernel32!DeviceIoControl:LLPLPLPP=L"].
479
+ call(h, code, inbuf, inbuf.size, outbuf, outbuf.size, outw, overlapped)
480
+ raise WinX.new(:device_io_control) if r == 0 and get_last_error != 997
481
+ return outw.unpack("L").first
482
+ end
483
+
484
+ def get_overlapped_result(h, overlapped)
485
+ overlapped = overlapped.to_s
486
+ outw = "\x00" * 4
487
+ r = CALLS["kernel32!GetOverlappedResult:LPPL=L"].call(h, overlapped, outw, 0)
488
+ raise WinX.new(:get_overlapped_result) if r == 0
489
+ return outw.unpack("L").first
490
+ end
491
+
492
+ # just grab some local memory
493
+ def malloc(sz)
494
+ r = CALLS["msvcrt!malloc:L=L"].call(sz)
495
+ raise WinX.new(:malloc) if r == 0
496
+ return r
497
+ end
498
+
499
+ def memcpy(dst, src, size)
500
+ CALLS["msvcrt!memcpy:PPL=L"].call(dst, src, size)
501
+ end
502
+
503
+ # Block wrapper for thread suspension
504
+ def with_suspended_thread(tid)
505
+ open_thread(tid) do |h|
506
+ begin
507
+ suspend_thread(h)
508
+ ret = yield h
509
+ ensure
510
+ resume_thread(h)
511
+ end
512
+ end
513
+ end
514
+
515
+ def wfmo(handles, ms=100)
516
+ hp = handles.to_ptr
517
+ r = CALLS["kernel32!WaitForMultipleObjects:LPLL=L"].call(handles.size, hp, 0, ms)
518
+ raise WinX(:wait_for_multiple_objects) if r == 0xFFFFFFFF
519
+ if r < handles.size
520
+ return handles[r]
521
+ else
522
+ return nil
523
+ end
524
+ end
525
+ end
526
+ end