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.
- data/History.txt +29 -0
- data/README.rdoc +35 -0
- data/README.txt +9 -0
- data/Rakefile +30 -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.rb +84 -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 +419 -0
- data/lib/ragweed/debuggertux.rb +345 -0
- data/lib/ragweed/detour.rb +223 -0
- data/lib/ragweed/ptr.rb +48 -0
- data/lib/ragweed/rasm.rb +53 -0
- data/lib/ragweed/rasm/isa.rb +1046 -0
- data/lib/ragweed/rasm/util.rb +26 -0
- data/lib/ragweed/sbuf.rb +197 -0
- data/lib/ragweed/trampoline.rb +103 -0
- data/lib/ragweed/utils.rb +88 -0
- data/lib/ragweed/wrap32.rb +53 -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/wraposx.rb +53 -0
- data/lib/ragweed/wraposx/constants.rb +112 -0
- data/lib/ragweed/wraposx/kernelerrorx.rb +147 -0
- data/lib/ragweed/wraposx/region_info.rb +250 -0
- data/lib/ragweed/wraposx/thread_context.rb +203 -0
- data/lib/ragweed/wraposx/thread_info.rb +225 -0
- data/lib/ragweed/wraposx/wraposx.rb +376 -0
- data/lib/ragweed/wraptux.rb +53 -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/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 +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
|