iZsh-ragweed 0.1.8

Sign up to get free protection for your applications and to get access to all the features.
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