Module: Ragweed::Wrap32

Defined in:
lib/ragweed/wrap32.rb,
lib/ragweed/wrap32/winx.rb,
lib/ragweed/wrap32/wrap32.rb,
lib/ragweed/wrap32/debugging.rb,
lib/ragweed/wrap32/debugging.rb,
lib/ragweed/wrap32/process_token.rb,
lib/ragweed/wrap32/thread_context.rb,
lib/ragweed/wrap32/thread_context.rb

Defined Under Namespace

Modules: ContextFlags, ContinueCodes, DebugCodes, EFlags, ExceptionCodes, FileAccess, FileAttributes, FileDisposition, FileSharing, FormatArgs, PagePerms, PrivilegeAttribute, TokenAccess Classes: DebugEvent, Overlapped, ProcessToken, ThreadContext, WinX

Constant Summary collapse

VERSION =

:stopdoc:

'0.1.6'
LIBPATH =
::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
PATH =
::File.dirname(LIBPATH) + ::File::SEPARATOR
NULL =
0x0
CALLS =

Does 2 things:

  1. Parses a terse notation for Win32 functions: “module!function:args=return”, where “args” and “return” are in String#unpack notation.

  2. Memoizes the Win32API lookup.

Returns a callable object implementing the specified call.

Hash.new do |h, str|
  lib = proc = args = ret = nil
  lib, rest = str.split "!"
  proc, rest = rest.split ":"
  args, ret = rest.split("=") if rest
  ret ||= ""
  args ||= []
  raise "need proc" if not proc
  h[str] = Win32API.new(lib, proc, args, ret)
end

Class Method Summary collapse

Class Method Details

.adjust_token_privileges(t, disable, *args) ⇒ Object

Raises:



31
32
33
34
35
36
37
38
# File 'lib/ragweed/wrap32/process_token.rb', line 31

def adjust_token_privileges(t, disable, *args)
  buf = [args.size].pack("L") + (args.map {|tup| tup.pack("QL") }.join(""))

  r = CALLS["advapi32!AdjustTokenPrivileges:LLPLPP=L"].
    call(t, disable, buf, buf.size, NULL, NULL)

  raise WinX.new(:adjust_token_privileges) if r == 0
end

.all_processesObject

Use Toolhelp32 to enumerate all running processes on the box, returning a struct with PIDs and executable names.



267
268
269
270
271
272
273
274
275
276
277
278
279
280
# File 'lib/ragweed/wrap32/wrap32.rb', line 267

def all_processes
  h = CALLS["kernel32!CreateToolhelp32Snapshot:LL=L"].call(0x2, 0)
  if h != -1
    pi = [(9*4)+2048,0,0,0,0,0,0,0,0,"\x00"*2048].pack("LLLLLLLLLa2048")
    if CALLS["kernel32!Process32First:LP=L"].call(h, pi) != 0
      yield str2process_info(pi)
      while CALLS["kernel32!Process32Next:LP=L"].call(h, pi) != 0
        yield str2process_info(pi)
      end
    end
  else
    raise WinX.new(:create_toolhelp32_snapshot)
  end
end

.close_handle(h) ⇒ Object

Close any Win32 handle. Reminder: Win32 handles are just integers, like file descriptors in Posix.

Raises:



116
117
118
# File 'lib/ragweed/wrap32/wrap32.rb', line 116

def close_handle(h)
  raise WinX.new(:close_handle) if CALLS["kernel32!CloseHandle:L"].call(h) != 0
end

.continue_debug_event(pid, tid, code) ⇒ Object

Raises:



136
137
138
139
140
# File 'lib/ragweed/wrap32/debugging.rb', line 136

def continue_debug_event(pid, tid, code)
  r = CALLS["kernel32!ContinueDebugEvent:LLL=L"].call(pid, tid, code)
  raise WinX.new(:continue_debug_event) if r == 0
  return r
end

.create_event(name = nil, auto = false, signalled = false) ⇒ Object

create an event, which you can signal and wait on across processes

Raises:



435
436
437
438
439
440
441
442
443
# File 'lib/ragweed/wrap32/wrap32.rb', line 435

def create_event(name=nil, auto=false, signalled=false)
  auto = (1 if auto) || 0
  signalled = (1 if signalled) || 0
  name ||= 0

  r = CALLS["kernel32!CreateEvent:LLLP=L"].call(0, auto, signalled, name);
  raise WinX.new(:create_event) if r == 0
  return r
end

.create_file(name, opts = {}) ⇒ Object

Raises:



401
402
403
404
405
406
407
408
409
410
411
# File 'lib/ragweed/wrap32/wrap32.rb', line 401

def create_file(name, opts={})
  opts[:disposition] ||= FileDisposition::OPEN_ALWAYS
  opts[:sharing] ||= FileSharing::READ | FileSharing::WRITE
  opts[:access] ||= FileAccess::GENERIC_ALL
  opts[:flags] ||= 0

  r = CALLS["kernel32!CreateFile:PLLPLLP=L"].
    call(name, opts[:access], opts[:sharing], NULL, opts[:disposition], opts[:flags], NULL)
  raise WinX.new(:create_file) if r == -1
  return r
end

.create_remote_thread(h, start, arg) ⇒ Object

Create a remote thread in the process, starting at the location “start”, with the threadproc argument “arg”

Raises:



383
384
385
386
387
# File 'lib/ragweed/wrap32/wrap32.rb', line 383

def create_remote_thread(h, start, arg)
  r = CALLS["kernel32!CreateRemoteThread:LLLLLLL=L"].call(h, NULL, 0, start.to_i, arg.to_i, 0, 0)
  raise WinX.new(:create_remote_thread) if r == 0
  return r
end

.debug_active_process(pid) ⇒ Object

Raises:



142
143
144
145
146
# File 'lib/ragweed/wrap32/debugging.rb', line 142

def debug_active_process(pid)
  r = CALLS["kernel32!DebugActiveProcess:L=L"].call(pid)
  raise WinX.new(:debug_active_process) if r == 0
  return r
end

.debug_active_process_stop(pid) ⇒ Object



154
155
156
157
# File 'lib/ragweed/wrap32/debugging.rb', line 154

def debug_active_process_stop(pid)
  # don't care about failure

  CALLS["kernel32!DebugActiveProcessStop:L=L"].call(pid)
end

.debug_set_process_kill_on_exit(val = 0) ⇒ Object

Raises:



148
149
150
151
152
# File 'lib/ragweed/wrap32/debugging.rb', line 148

def debug_set_process_kill_on_exit(val=0)
  r = CALLS["kernel32!DebugSetProcessKillOnExit:L=L"].call(val)
  raise WinX.new(:debug_set_process_kill_on_exit) if r == 0
  return r
end

.device_io_control(h, code, inbuf, outbuf, overlapped = NULL) ⇒ Object

Raises:



475
476
477
478
479
480
481
482
# File 'lib/ragweed/wrap32/wrap32.rb', line 475

def device_io_control(h, code, inbuf, outbuf, overlapped=NULL)
  overlapped = overlapped.to_s if overlapped
  outw = "\x00" * 4
  r = CALLS["kernel32!DeviceIoControl:LLPLPLPP=L"].
    call(h, code, inbuf, inbuf.size, outbuf, outbuf.size, outw, overlapped)
  raise WinX.new(:device_io_control) if r == 0 and get_last_error != 997
  return outw.unpack("L").first
end

.duplicate_handle(ph, h) ⇒ Object

clone a handle out of another open process (or self, with -1)

Raises:



394
395
396
397
398
399
# File 'lib/ragweed/wrap32/wrap32.rb', line 394

def duplicate_handle(ph, h)
  ret = "\x00\x00\x00\x00"
  r = CALLS["kernel32!DuplicateHandle:LLLPLLL=L"].call(ph, h, -1, ret, 0, 0, 0x2)
  raise WinX.new(:duplicate_handle) if r == 0
  ret.to_l32
end

.flush_instruction_cache(h, v1 = 0, v2 = 0) ⇒ Object



159
160
161
# File 'lib/ragweed/wrap32/debugging.rb', line 159

def flush_instruction_cache(h, v1=0, v2=0)
  CALLS["kernel32!FlushInstructionCache:LLL=L"].call(h, v1, v2)
end

.format_message(code = nil) ⇒ Object

strerror(errno) (can’t fail)



126
127
128
129
130
131
132
# File 'lib/ragweed/wrap32/wrap32.rb', line 126

def format_message(code=nil)
  code ||= get_last_error
  buf = "\x00" * 4096
  CALLS["kernel32!FormatMessageA:LPLLPLP"].
    call(4096, NULL, code, 0x00000400, buf, 4096, NULL)
  return buf.split("\x00")[0]
end

.get_current_process_idObject

getpid



203
204
205
# File 'lib/ragweed/wrap32/wrap32.rb', line 203

def get_current_process_id
  CALLS["kernel32!GetCurrentProcessId:=L"].call # can't realistically fail

end

.get_current_thread_idObject

gettid



208
209
210
# File 'lib/ragweed/wrap32/wrap32.rb', line 208

def get_current_thread_id
  CALLS["kernel32!GetCurrentThreadId:=L"].call # can't realistically fail

end

.get_last_errorObject

Get the last error code (errno) (can’t fail)



121
122
123
# File 'lib/ragweed/wrap32/wrap32.rb', line 121

def get_last_error
  CALLS["kernel32!GetLastError:=L"].call
end

.get_module_handle(name) ⇒ Object

Given a DLL name, get a handle to the DLL.

Raises:



213
214
215
216
217
218
# File 'lib/ragweed/wrap32/wrap32.rb', line 213

def get_module_handle(name)
  name = name.to_utf16
  r = CALLS["kernel32!GetModuleHandleW:P=L"].call(name)
  raise WinX.new(:get_module_handle) if r == 0
  return r
end

.get_overlapped_result(h, overlapped) ⇒ Object

Raises:



484
485
486
487
488
489
490
# File 'lib/ragweed/wrap32/wrap32.rb', line 484

def get_overlapped_result(h, overlapped)
  overlapped = overlapped.to_s
  outw = "\x00" * 4
  r = CALLS["kernel32!GetOverlappedResult:LPPL=L"].call(h, overlapped, outw, 0)
  raise WinX.new(:get_overlapped_result) if r == 0
  return outw.unpack("L").first
end

.get_proc_address(x, y = nil) ⇒ Object

Using notation x = “foo!bar” or x = handle, y = meth, look up a function’s address in a module. Note that this is local, not remote.



230
231
232
233
234
235
236
237
238
239
240
241
# File 'lib/ragweed/wrap32/wrap32.rb', line 230

def get_proc_address(x, y=nil)
  if not y
    mod, meth = x.split "!"
    h = get_module_handle(mod)
  else
    h = x
    meth = y
  end

  r = CALLS["kernel32!GetProcAddress:LP=L"].call(h, meth)
  return r # pass error through

end

.get_thread_context(h) ⇒ Object

Retrieve the running context of a thread given its handle, returning a struct that mostly contains register values. Note that this will suspend and then resume the thread. Useful (among many other things) to sample EIP values to see what the code is doing.



196
197
198
199
200
201
202
203
204
205
206
# File 'lib/ragweed/wrap32/thread_context.rb', line 196

def get_thread_context(h)
  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")
  suspend_thread(h)
  ret = CALLS["kernel32!GetThreadContext:LP=L"].call(h, ctx)
  resume_thread(h)
  if ret != 0
    return str2context(ctx)
  else
    raise WinX.new(:get_thread_context)
  end
end

.get_thread_context_raw(h) ⇒ Object



137
138
139
140
141
142
143
144
145
# File 'lib/ragweed/wrap32/thread_context.rb', line 137

def get_thread_context_raw(h)
  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")
  ret = CALLS["kernel32!GetThreadContext:LP=L"].call(h, ctx)
  if ret != 0
    return ctx
  else
    raise WinX.new(:get_thread_context)
  end
end

.libpath(*args) ⇒ Object

Returns the library path for the module. If any arguments are given, they will be joined to the end of the libray path using File.join.



23
24
25
# File 'lib/ragweed/wrap32.rb', line 23

def self.libpath( *args )
  args.empty? ? LIBPATH : ::File.join(LIBPATH, args.flatten)
end

.list_modules(pid = 0) ⇒ Object

Given a pid, enumerate the modules loaded into the process, returning base addresses, memory ranges, and the module name.



301
302
303
304
305
306
307
308
309
310
311
312
313
314
# File 'lib/ragweed/wrap32/wrap32.rb', line 301

def list_modules(pid=0)
  h = CALLS["kernel32!CreateToolhelp32Snapshot:LL=L"].call(0x8, pid)
  if h != -1
    mi = [260+256+(8*4),0,0,0,0,0,0,0,"\x00"*256,"\x00"*260].pack("LLLLLLLLa256a260")
    if w32("kernel32!Module32First:LP=L").call(h, mi) != 0
      yield str2module_info(mi)
      while w32("kernel32!Module32Next:LP=L").call(h, mi) != 0
        yield str2module_info(mi)
      end
    end
  else
    raise WinX.new(:create_toolhelp32_snapshot)
  end
end

.load_library(name) ⇒ Object

load a library explicitly from a dll

Raises:



221
222
223
224
225
226
# File 'lib/ragweed/wrap32/wrap32.rb', line 221

def load_library(name)
  name = name.to_utf16
  r = CALLS["kernel32!LoadLibraryW:P=L"].call(name)
  raise WinX.new(:load_library) if r == 0
  return r
end

.lookup_privilege_value(name) ⇒ Object

Raises:



40
41
42
43
44
45
# File 'lib/ragweed/wrap32/process_token.rb', line 40

def lookup_privilege_value(name)
  outw = "\x00" * 8
  r = CALLS["advapi32!LookupPrivilegeValueA:PPP=L"].call(NULL, name, outw)
  raise WinX.new(:lookup_privilege_value) if r == 0
  return outw.unpack("Q").first
end

.malloc(sz) ⇒ Object

just grab some local memory

Raises:



493
494
495
496
497
# File 'lib/ragweed/wrap32/wrap32.rb', line 493

def malloc(sz)
  r = CALLS["msvcrt!malloc:L=L"].call(sz)
  raise WinX.new(:malloc) if r == 0
  return r
end

.memcpy(dst, src, size) ⇒ Object



499
500
501
# File 'lib/ragweed/wrap32/wrap32.rb', line 499

def memcpy(dst, src, size)
  CALLS["msvcrt!memcpy:PPL=L"].call(dst, src, size)
end

.nt_query_information_process(h, ord, buf) ⇒ Object

NQIP does a lot of things, the most useful of which are getting the image name of a running process, and telling whether a debugger is loaded. This interface is Ioctl-style; provide an ordinal and a buffer to pass results through.



328
329
330
331
332
333
334
335
# File 'lib/ragweed/wrap32/wrap32.rb', line 328

def nt_query_information_process(h, ord, buf)
  lenp = [0].pack("L")
  if CALLS["ntdll!NtQueryInformationProcess:LLPLP=L"].call(h, ord, buf, buf.size, lenp) == 0
    len = lenp.unpack("L").first
    return buf[0..(len-1)]
  end
  nil
end

.open_event(name) ⇒ Object

i haven’t made this work, but named handles are kind of silly anyways

Raises:



414
415
416
417
418
# File 'lib/ragweed/wrap32/wrap32.rb', line 414

def open_event(name)
  r = CALLS["kernel32!OpenEvent:LLP=L"].call(0, 0, name)
  raise WinX.new(:open_event) if r == 0
  return r
end

.open_process(pid) ⇒ Object

Get a process handle given a pid

Raises:



95
96
97
98
99
# File 'lib/ragweed/wrap32/wrap32.rb', line 95

def open_process(pid)
  r = CALLS["kernel32!OpenProcess:LLL=L"].call(0x1F0FFF, 0, pid)
  raise WinX.new(:open_process) if r == 0
  return r
end

.open_process_token(h, access = Wrap32::TokenAccess::ADJUST_PRIVILEGES) ⇒ Object

Raises:



24
25
26
27
28
29
# File 'lib/ragweed/wrap32/process_token.rb', line 24

def open_process_token(h, access=Wrap32::TokenAccess::ADJUST_PRIVILEGES)
  outw = "\x00" * 4
  r = CALLS["advapi32!OpenProcessToken:LLP=L"].call(h, access, outw)
  raise WinX.new(:open_process_token) if r == 0
  return outw.unpack("L").first
end

.open_thread(tid, &block) ⇒ Object

Get a thread handle given a tid; if a block is provided, the semantics are as File#open with a block.

Raises:



103
104
105
106
107
108
109
110
111
112
# File 'lib/ragweed/wrap32/wrap32.rb', line 103

def open_thread(tid, &block)
  h = CALLS["kernel32!OpenThread:LLL=L"].call(0x1F03FF, 0, tid)
  raise WinX.new(:open_thread) if h == 0
  if block_given?
    ret = yield h
    close_handle(h)
    return ret
  end
  h
end

.path(*args) ⇒ Object

Returns the lpath for the module. If any arguments are given, they will be joined to the end of the path using File.join.



31
32
33
# File 'lib/ragweed/wrap32.rb', line 31

def self.path( *args )
  args.empty? ? PATH : ::File.join(PATH, args.flatten)
end

.read_file(h, count, overlapped = nil) ⇒ Object

Raises:



458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
# File 'lib/ragweed/wrap32/wrap32.rb', line 458

def read_file(h, count, overlapped=nil)
  if overlapped
    opp = overlapped.to_s
  else
    opp = NULL
  end
  outw = "\x00" * 4
  if not (buf = overlapped.try(:target)) or buf.size < count
    buf = "\x00" * count
    overlapped.target = buf if overlapped
  end

  r = CALLS["kernel32!ReadFile:LPLPP=L"].call(h, buf, count, outw, opp)
  raise WinX.new(:read_file) if r == 0 and get_last_error != 997
  return buf, outw.unpack("L").first
end

.read_process_memory(h, ptr, len) ⇒ Object

Read from a remote process given an address and length, returning a string.

Raises:



158
159
160
161
162
163
# File 'lib/ragweed/wrap32/wrap32.rb', line 158

def read_process_memory(h, ptr, len)
  val = "\x00" * len
  r = CALLS["kernel32!ReadProcessMemory:LLPLL=L"].call(h, ptr.to_i, val, len, NULL)
  raise WinX.new(:read_process_memory) if r == 0
  return val ## don't handle short reads XXX

end

.require_all_libs_relative_to(fname, dir = nil) ⇒ Object

Utility method used to require all files ending in .rb that lie in the directory below this file that has the same name as the filename passed in. Optionally, a specific directory name can be passed in such that the filename does not have to be equivalent to the directory.



40
41
42
43
44
45
46
47
48
# File 'lib/ragweed/wrap32.rb', line 40

def self.require_all_libs_relative_to( fname, dir = nil )
  dir ||= ::File.basename(fname, '.*')
  search_me = ::File.expand_path(
      ::File.join(::File.dirname(fname), dir, '**', '*.rb'))
  
  Dir.glob(search_me).sort.each {|rb| require rb}
  # require File.dirname(File.basename(__FILE__)) + "/#{x}"


end

.reset_event(h) ⇒ Object

force-unsignal event (waiting on the event handle also does this)

Raises:



428
429
430
431
432
# File 'lib/ragweed/wrap32/wrap32.rb', line 428

def reset_event(h)
  r = CALLS["kernel32!ResetEvent:L=L"].call(h)
  raise WinX.new(:reset_event) if r == 0
  return r
end

.resume_thread(h) ⇒ Object

Resume a suspended thread, returning nonzero if the thread was suspended, and 0 if it was running.



377
378
379
# File 'lib/ragweed/wrap32/wrap32.rb', line 377

def resume_thread(h)
  CALLS["kernel32!ResumeThread:L=L"].call(h)
end

.set_event(h) ⇒ Object

signal an event

Raises:



421
422
423
424
425
# File 'lib/ragweed/wrap32/wrap32.rb', line 421

def set_event(h)
  r = CALLS["kernel32!SetEvent:L=L"].call(h)
  raise WinX.new(:set_event) if r == 0
  return r
end

.set_thread_context_raw(h, c) ⇒ Object

Raises:



147
148
149
150
151
152
# File 'lib/ragweed/wrap32/thread_context.rb', line 147

def set_thread_context_raw(h, c)
  buf = c.to_s
  ret = CALLS["kernel32!SetThreadContext:LP=L"].call(h, buf)
  raise WinX.new(:set_thread_context) if ret == 0
  return ret
end

.sleep(ms = 0) ⇒ Object



389
390
391
# File 'lib/ragweed/wrap32/wrap32.rb', line 389

def sleep(ms=0)
  CALLS["kernel32!Sleep:L=L"].call(ms)
end

.str2context(str) ⇒ Object



154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
# File 'lib/ragweed/wrap32/thread_context.rb', line 154

def str2context(str)
  ret = OpenStruct.new
  ret.ContextFlags,
  ret.Dr0, 
  ret.Dr1,
  ret.Dr2,
  ret.Dr3,
  ret.Dr6,
  ret.Dr7,
  ret.FloatControlWord,
  ret.FloatStatusWord,
  ret.FloatTagWord,
  ret.FloatErrorOffset,
  ret.FloatErrorSelector,
  ret.FloatDataOffset,
  ret.FloatDataSelector,
  ret.FloatRegisterArea,
  ret.FloatCr0NpxState,
  ret.SegGs,
  ret.SegFs,
  ret.SegEs,
  ret.SegDs,
  ret.Edi,
  ret.Esi,
  ret.Ebx,
  ret.Edx,
  ret.Ecx,
  ret.Eax,
  ret.Ebp,
  ret.Eip,
  ret.SegCs,
  ret.EFlags,
  ret.Esp,
  ret.SegSs,
  ret.Spill = str.unpack("LLLLLLLLLLLLLLA80LLLLLLLLLLLLLLLLLA1024")
  return ret
end

.str2memory_basic_info(mbi) ⇒ Object



165
166
167
168
169
170
171
172
173
174
175
# File 'lib/ragweed/wrap32/wrap32.rb', line 165

def str2memory_basic_info(mbi)
  s = OpenStruct.new
  s.BaseAddress,
  s.AllocationBase,
  s.AllocationProtect,
  s.RegionSize,
  s.State,
  s.Protect,
  s.Type = mbi.unpack("LLLLLLL")
  return s
end

.str2module_info(str) ⇒ Object



282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
# File 'lib/ragweed/wrap32/wrap32.rb', line 282

def str2module_info(str)
  ret = OpenStruct.new
  ret.dwSize,
  ret.th32ModuleID,
  ret.th32ProcessID,
  ret.GlblcntUsage,
  ret.ProccntUsage,
  ret.modBaseAddr,
  ret.modBaseSize,
  ret.hModule,
  ret.szModule,
  ret.szExePath = str.unpack("LLLLLLLLA256A260")
  ret.szModule = ret.szModule.asciiz
  ret.szExePath = ret.szExePath.asciiz
  return ret
end

.str2process_info(str) ⇒ Object



249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
# File 'lib/ragweed/wrap32/wrap32.rb', line 249

def str2process_info(str)
  ret = OpenStruct.new
  ret.dwSize,
  ret.cntUsage,
  ret.th32ProcessID,
  ret.th32DefaultHeapID,
  ret.th32ModuleID,
  ret.cntThreads,
  ret.th32ParentProcessID,
  ret.pcPriClassBase,
  ret.dwFlags,
  ret.szExeFile = str.unpack("LLLLLLLLLA2048")
  ret.szExeFile = ret.szExeFile.asciiz
  return ret
end

.str2thread_info(str) ⇒ Object



337
338
339
340
341
342
343
344
345
346
347
# File 'lib/ragweed/wrap32/wrap32.rb', line 337

def str2thread_info(str)
  ret = OpenStruct.new
  ret.dwSize,
  ret.cntUsage,
  ret.th32ThreadID,
  ret.th32OwnerProcessID,
  ret.tpBasePri,
  ret.tpDeltaPri,
  ret.thFlags = str.unpack("LLLLLLL")
  return ret
end

.suspend_thread(h) ⇒ Object

Suspend a thread given its handle.

Raises:



369
370
371
372
373
# File 'lib/ragweed/wrap32/wrap32.rb', line 369

def suspend_thread(h)
  r = CALLS["kernel32!SuspendThread:L=L"].call(h)
  raise WinX.new(:suspend_thread) if r == 0
  return r
end

.threads(pid) ⇒ Object

List all the threads in a process given its pid, returning a struct containing tids and run state. This is relatively expensive, because it uses Toolhelp32.



351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
# File 'lib/ragweed/wrap32/wrap32.rb', line 351

def threads(pid)
  h = CALLS["kernel32!CreateToolhelp32Snapshot:LL=L"].call(0x4, pid)
  if h != -1
    mi = [(7*4),0,0,0,0,0,0].pack("LLLLLLL")
    if w32("kernel32!Thread32First:LP=L").call(h, mi) != 0
      ti = str2thread_info(mi)
      yield str2thread_info(mi) if ti.th32OwnerProcessID == pid
      while w32("kernel32!Thread32Next:LP=L").call(h, mi) != 0
        ti = str2thread_info(mi)
        yield str2thread_info(mi) if ti.th32OwnerProcessID == pid
      end
    end
  else
    raise WinX.new(:create_toolhelp32_snapshot)
  end
end

.versionObject

Returns the version string for the library.



15
16
17
# File 'lib/ragweed/wrap32.rb', line 15

def self.version
  VERSION
end

.virtual_alloc_ex(h, sz, addr = NULL, prot = 0x40) ⇒ Object

Allocate memory in a remote process (or yourself, with handle -1)

Raises:



135
136
137
138
139
140
# File 'lib/ragweed/wrap32/wrap32.rb', line 135

def virtual_alloc_ex(h, sz, addr=NULL, prot=0x40)
  r = CALLS["kernel32!VirtualAllocEx:LLLLL=L"].
    call(h, addr, sz, 0x1000, prot)
  raise WinX.new(:virtual_alloc_ex) if r == 0
  return r
end

.virtual_free_ex(h, ptr, type = 0x8000) ⇒ Object

Free memory in a remote process given the pointer returned from virtual_alloc_ex

Raises:



143
144
145
146
147
# File 'lib/ragweed/wrap32/wrap32.rb', line 143

def virtual_free_ex(h, ptr, type=0x8000)
  r = CALLS["kernel32!VirtualFreeEx:LLLL=L"].call(h, ptr.to_i, 0, type)
  raise WinX.new(:virtual_free_ex) if r == 0
  return r
end

.virtual_protect_ex(h, addr, prot, size = 0) ⇒ Object

Change the protection of specific memory regions in a remote process.



190
191
192
193
194
195
196
197
198
199
200
# File 'lib/ragweed/wrap32/wrap32.rb', line 190

def virtual_protect_ex(h, addr, prot, size=0)
  old = [0].pack("L")
  base = virtual_query_ex(h, addr).BaseAddress if size == 0
  base ||= addr

  if CALLS["kernel32!VirtualProtectEx:LLLLP=L"].call(h, base, size, prot, old)
    old.unpack("L").first
  else
    raise WinX.new(:virtual_protect_ex)
  end
end

.virtual_query_ex(h, ptr) ⇒ Object

Return a struct with the MEMORY_BASIC_INFORMATION for a given address in the memory of a remote process. Gives you addressable memory ranges and protection flags.



180
181
182
183
184
185
186
187
# File 'lib/ragweed/wrap32/wrap32.rb', line 180

def virtual_query_ex(h, ptr)
  mbi = [0,0,0,0,0,0,0].pack("LLLLLLL")
  if CALLS["kernel32!VirtualQueryEx:LLPL=L"].call(h, ptr, mbi, mbi.size)
    str2memory_basic_info(mbi)
  else
    nil
  end
end

.wait_for_debug_event(ms = 1000) ⇒ Object

Raises:



128
129
130
131
132
133
134
# File 'lib/ragweed/wrap32/debugging.rb', line 128

def wait_for_debug_event(ms=1000)
  buf = "\x00" * 1024
  r = CALLS["kernel32!WaitForDebugEvent:PL=L"].call(buf, ms)
  raise WinX.new(:wait_for_debug_event) if r == 0 and get_last_error != 121
  return Wrap32::DebugEvent.new(buf) if r != 0
  return nil
end

.wait_for_single_object(h) ⇒ Object

Select(2), for a single object handle.

Raises:



244
245
246
247
# File 'lib/ragweed/wrap32/wrap32.rb', line 244

def wait_for_single_object(h)
  r = CALLS["kernel32!WaitForSingleObject:LL=L"].call(h, -1)
  raise WinX.new(:wait_for_single_object) if r == -1
end

.wfmo(handles, ms = 100) ⇒ Object



515
516
517
518
519
520
521
522
523
524
# File 'lib/ragweed/wrap32/wrap32.rb', line 515

def wfmo(handles, ms=100)
  hp = handles.to_ptr
  r = CALLS["kernel32!WaitForMultipleObjects:LPLL=L"].call(handles.size, hp, 0, ms)
  raise WinX(:wait_for_multiple_objects) if r == 0xFFFFFFFF
  if r < handles.size
    return handles[r]
  else
    return nil
  end
end

.with_suspended_thread(tid) ⇒ Object

Block wrapper for thread suspension



504
505
506
507
508
509
510
511
512
513
# File 'lib/ragweed/wrap32/wrap32.rb', line 504

def with_suspended_thread(tid)
  open_thread(tid) do |h|
    begin
      suspend_thread(h)
      ret = yield h
    ensure
      resume_thread(h)
    end
  end
end

.write_file(h, buf, overlapped = nil) ⇒ Object

Raises:



445
446
447
448
449
450
451
452
453
454
455
456
# File 'lib/ragweed/wrap32/wrap32.rb', line 445

def write_file(h, buf, overlapped=nil)
  if overlapped
    opp = overlapped.to_s
  else
    opp = NULL
  end

  outw = "\x00" * 4
  r = CALLS["kernel32!WriteFile:LPLPP=L"].call(h, buf, buf.size, outw, opp)
  raise WinX.new(:write_file) if r == 0 and get_last_error != 997
  return buf, outw.unpack("L").first
end

.write_process_memory(h, dst, val) ⇒ Object

Write a string into the memory of a remote process given its handle and an address

Raises:



150
151
152
153
154
155
# File 'lib/ragweed/wrap32/wrap32.rb', line 150

def write_process_memory(h, dst, val)
  val = val.to_s if not val.kind_of? String
  r = CALLS["kernel32!WriteProcessMemory:LLPLL=L"].call(h, dst.to_i, val, val.size, NULL)
  raise WinX.new(:write_process_memory) if r == 0
  return r
end

.writeable?(h, off) ⇒ Boolean

Use virtual_query_ex to tell whether an address is writable.

Returns:

  • (Boolean)


317
318
319
320
321
322
323
# File 'lib/ragweed/wrap32/wrap32.rb', line 317

def writeable?(h, off)
  if (x = virtual_query_ex(h, off))
    return PagePerms::WRITEABLE.member?(x.Protect & 0xFF)
  else
    return false
  end
end