Class: Ragweed::Process
- Includes:
- Ragweed
- Defined in:
- lib/ragweed/wrap32/process.rb
Overview
XXX - PORT ME!!
Defined Under Namespace
Classes: AdlerChart
Constant Summary
Constants included from Ragweed
Instance Attribute Summary collapse
-
#pid ⇒ Object
readonly
Returns the value of attribute pid.
Class Method Summary collapse
-
.by_name(n) ⇒ Object
Look up a process by name or regex, returning an array of all matching processes, as objects.
- .find_by_regex(name) ⇒ Object
Instance Method Summary collapse
-
#adler_chart(i) ⇒ Object
See WinProcess::AdlerChart.
-
#adler_compare(i, orig, opts = {}) ⇒ Object
If you store the adler map, you’ve compressed the memory region down to a small series of fixnums, and you can use it with this function to re-check the memory region and see if anything’s changing.
-
#adler_map(i, opts = {}) ⇒ Object
Like entropy_map, scan a process and compute adler16 checksums for 1k (or :window) blocks.
-
#alloc(sz, syscall = false) ⇒ Object
Use arenas, when possible, to quickly allocate memory.
-
#arena(&block) ⇒ Object
Get another allocation arena for this process.
-
#changes(i, sleeptime = 0.5) ⇒ Object
Given a memory region, use the adler routines to create a checksum map, wait a short period of time, and scan for changes, to find churning memory.
- #detour(loc, o = {}) ⇒ Object
-
#dump_memory(i, opts = {}) ⇒ Object
Print a canonical hexdump of an entire memory region by region number.
-
#dump_memory_list ⇒ Object
Human-readable standard output of list_memory.
-
#dump_stack_trace(tid) ⇒ Object
(also: #bt)
Human-readable version of thread_stack_trace, with module offsets.
-
#dup_handle(h) ⇒ Object
clone a handle from the remote process to here (to here? tf?).
-
#entropy_map(i, opts = {}) ⇒ Object
Take a region of memory and walk over it in 255-byte samples (less than 255 bytes and you lose accuracy, but you can increase it with the “:window” option), computing entropy for each sample, returning a list of [offset,entropy] tuples.
-
#free(off) ⇒ Object
Free the return value of syscall_alloc.
-
#get_memory(i, opts = {}) ⇒ Object
Read an entire memory region into a string by region number.
-
#get_proc(name) ⇒ Object
look up a process by its name — but this is in the local process, which is broken — a heuristic that sometimes works for w32 functions, but probably never otherwise.
- #get_proc_remote(name) ⇒ Object
- #handle ⇒ Object
-
#hunt(key, opts = {}) ⇒ Object
Given a string key, find it in memory.
-
#image ⇒ Object
Return the EXE name of the process.
-
#initialize(pid) ⇒ Process
constructor
Just need a PID to get started.
-
#insert(buf) ⇒ Object
Insert a string anywhere into the memory of the remote process, returning its address, using an arena.
-
#list_memory(&block) ⇒ Object
List all memory regions in the remote process by iterating over VirtualQueryEx.
-
#modules(&block) ⇒ Object
List the modules for the process, either yielding a struct for each to a block, or returning a list.
-
#pointers_to(src, dst, opts = {}) ⇒ Object
Given a source and destination memory region, scan through “source” looking for properly-aligned U32LE values that would be valid pointers into “destination”.
-
#ptr(x) ⇒ Object
Get a pointer into the remote process; pointers are just fixnums with a read/write method and a to_s.
-
#read(off, sz = 4096) ⇒ Object
Read/write ranges of data or fixnums to/from the process by address.
- #read16(off) ⇒ Object
- #read32(off) ⇒ Object
- #read8(off) ⇒ Object
-
#region_range(i, opts = {}) ⇒ Object
Get the memory range, as a Ruby Range, for a region by index.
-
#remote_call(meth, *args) ⇒ Object
call a function, by name or address, in the process, using CreateRemoteThread.
-
#resume(tid) ⇒ Object
Resume a thread by tid.
-
#resume_all ⇒ Object
Resume all the threads in the process.
-
#scan(i, opts = {}) ⇒ Object
In Python, and maybe Ruby, it was much faster to work on large memory regions a 4k page at a time, rather than reading the whole thing into one big string.
-
#strings_mem(i, opts = {}) ⇒ Object
Given a memory region number, do a Unix strings(1) on it.
-
#suspend(tid) ⇒ Object
Suspend a thread by tid.
-
#suspend_all ⇒ Object
Suspend all the threads in the process.
-
#syscall_alloc(sz) ⇒ Object
Use VirtualAllocEx to grab a block of memory in the process.
-
#thread_context(tid) ⇒ Object
Dump thread context, returning a struct that contains things like .Eip and .Eax.
-
#thread_stack_trace(tid) ⇒ Object
For libraries compiled with frame pointers: walk EBP back until it stops giving intelligible addresses, and, at each step, grab the saved EIP from just before it.
-
#threads(full = false, &block) ⇒ Object
Return a list of all the threads in the process; relatively expensive, so cache the result.
-
#to_modoff(off, force = false) ⇒ Object
Convert an address to “module+10h” notation, when possible.
-
#which_region_has?(addr, opts = {}) ⇒ Boolean
Figure out what region (by region index) has an address.
-
#with_suspended_thread(tid) ⇒ Object
Do something with a thread while its suspended.
- #write(off, data) ⇒ Object
- #write16(off, v) ⇒ Object
- #write32(off, v) ⇒ Object
- #write8(off, v) ⇒ Object
-
#writeable?(off) ⇒ Boolean
Can I write to this address in the process?.
Methods included from Ragweed
libpath, path, require_all_libs_relative_to, require_os_libs_relative_to, version
Constructor Details
#initialize(pid) ⇒ Process
Just need a PID to get started.
59 60 61 62 63 |
# File 'lib/ragweed/wrap32/process.rb', line 59 def initialize(pid) @pid = pid @h = Wrap32::open_process(pid) @a = arena() end |
Instance Attribute Details
#pid ⇒ Object (readonly)
Returns the value of attribute pid.
5 6 7 |
# File 'lib/ragweed/wrap32/process.rb', line 5 def pid @pid end |
Class Method Details
.by_name(n) ⇒ Object
Look up a process by name or regex, returning an array of all matching processes, as objects.
47 48 49 50 51 52 53 54 55 56 |
# File 'lib/ragweed/wrap32/process.rb', line 47 def self.by_name(n) n = Regexp.new(n) if not n.kind_of? Regexp p = [] all_processes do |px| if px.szExeFile =~ n p << self.new(px.th32ProcessID) end end p end |
.find_by_regex(name) ⇒ Object
8 9 10 11 12 13 14 |
# File 'lib/ragweed/wrap32/process.rb', line 8 def self.find_by_regex(name) Wrap32::all_processes do |p| if p.szExeFile =~ name return self.new(p.th32ProcessID) end end end |
Instance Method Details
#adler_chart(i) ⇒ Object
See WinProcess::AdlerChart. Get one.
429 430 431 |
# File 'lib/ragweed/wrap32/process.rb', line 429 def adler_chart(i) AdlerChart.new self, i end |
#adler_compare(i, orig, opts = {}) ⇒ Object
If you store the adler map, you’ve compressed the memory region down to a small series of fixnums, and you can use it with this function to re-check the memory region and see if anything’s changing.
377 378 379 380 381 382 383 384 385 386 387 388 389 390 |
# File 'lib/ragweed/wrap32/process.rb', line 377 def adler_compare(i, orig, opts={}) refresh opts window = windowize(i, opts) ret = [] c = -1 scan(i, opts) do |block,soff| 0.stepwith(block.size-1, window) do |off, len| if block[off,len].adler != orig[c += 1] ret << soff+off end end end ret end |
#adler_map(i, opts = {}) ⇒ Object
Like entropy_map, scan a process and compute adler16 checksums for 1k (or :window) blocks.
360 361 362 363 364 365 366 367 368 369 370 371 372 |
# File 'lib/ragweed/wrap32/process.rb', line 360 def adler_map(i, opts={}) refresh opts window = windowize(i, opts) ret = [] scan(i, opts) do |block,soff| 0.stepwith(block.size-1, window) do |off, len| if (b = block[off,len]) ret << b.adler end end end ret end |
#alloc(sz, syscall = false) ⇒ Object
Use arenas, when possible, to quickly allocate memory. The upside is this is very fast. The downside is you can’t free the memory without invalidating every allocation you’ve made prior.
142 143 144 145 146 147 148 |
# File 'lib/ragweed/wrap32/process.rb', line 142 def alloc(sz, syscall=false) if syscall or sz > 4090 ret = syscall_alloc(sz) else ptr(@a.alloc(sz)) end end |
#arena(&block) ⇒ Object
Get another allocation arena for this process. Pretty cheap. Given a block, behaves like File#open, disposing of the arena when you’re done.
174 175 176 177 178 179 180 181 182 183 184 185 |
# File 'lib/ragweed/wrap32/process.rb', line 174 def arena(&block) me = self a = Arena.new(lambda {me.syscall_alloc(4096)}, lambda {|p| me.free(p)}, lambda {|dst, src| me.write(dst, src)}) if block_given? ret = yield a a.release return ret end a end |
#changes(i, sleeptime = 0.5) ⇒ Object
Given a memory region, use the adler routines to create a checksum map, wait a short period of time, and scan for changes, to find churning memory.
436 437 438 439 440 |
# File 'lib/ragweed/wrap32/process.rb', line 436 def changes(i, sleeptime=0.5) q = adler_map(i) sleep(sleeptime) adler_compare(i, q) end |
#detour(loc, o = {}) ⇒ Object
493 494 495 496 497 498 499 |
# File 'lib/ragweed/wrap32/process.rb', line 493 def detour(loc, o={}) klass = o[:class] || Detour loc = get_proc(loc) r = klass.new(loc, o) r.call if not o[:chicken] return r end |
#dump_memory(i, opts = {}) ⇒ Object
Print a canonical hexdump of an entire memory region by region number.
230 |
# File 'lib/ragweed/wrap32/process.rb', line 230 def dump_memory(i, opts={}); get_memory(i, opts).hexdump; end |
#dump_memory_list ⇒ Object
Human-readable standard output of list_memory. Remember that the index number is important.
218 219 220 221 |
# File 'lib/ragweed/wrap32/process.rb', line 218 def dump_memory_list list_memory.each_with_index {|x,i| puts "#{ i }. #{ x[0].to_s(16) }(#{ x[1] })"} true end |
#dump_stack_trace(tid) ⇒ Object Also known as: bt
Human-readable version of thread_stack_trace, with module offsets.
486 487 488 489 490 |
# File 'lib/ragweed/wrap32/process.rb', line 486 def dump_stack_trace(tid) thread_stack_trace(tid).each do |frame, code| puts "#{ frame.to_x } @ #{ to_modoff(code) }" end end |
#dup_handle(h) ⇒ Object
clone a handle from the remote process to here (to here? tf?)
25 26 27 |
# File 'lib/ragweed/wrap32/process.rb', line 25 def dup_handle(h) Wrap32::duplicate_handle(@h, h) end |
#entropy_map(i, opts = {}) ⇒ Object
Take a region of memory and walk over it in 255-byte samples (less than 255 bytes and you lose accuracy, but you can increase it with the “:window” option), computing entropy for each sample, returning a list of [offset,entropy] tuples.
262 263 264 265 266 267 268 269 270 271 272 273 |
# File 'lib/ragweed/wrap32/process.rb', line 262 def entropy_map(i, opts={}) ret = [] startoff = opts[:starting_offset] startoff ||= 0 window = opts[:window] || 255 scan(i, opts) do |block, soff| startoff.stepwith(block.size, window) do |off, len| ret << [off, block[off,len].entropy] end end return ret end |
#free(off) ⇒ Object
Free the return value of syscall_alloc. Do NOT use for the return value of alloc.
152 153 154 |
# File 'lib/ragweed/wrap32/process.rb', line 152 def free(off) Wrap32::virtual_free_ex(@h, off) end |
#get_memory(i, opts = {}) ⇒ Object
Read an entire memory region into a string by region number.
224 225 226 227 |
# File 'lib/ragweed/wrap32/process.rb', line 224 def get_memory(i, opts={}) refresh opts read(@memlist[i][0], @memlist[i][1]) end |
#get_proc(name) ⇒ Object
look up a process by its name — but this is in the local process, which is broken — a heuristic that sometimes works for w32 functions, but probably never otherwise.
32 33 34 35 |
# File 'lib/ragweed/wrap32/process.rb', line 32 def get_proc(name) return Ptr.new(name) if name.kind_of? Numeric or name.kind_of? Ptr ptr(Wrap32::get_proc_address(name)) end |
#get_proc_remote(name) ⇒ Object
37 38 39 40 41 42 43 |
# File 'lib/ragweed/wrap32/process.rb', line 37 def get_proc_remote(name) mod, meth = name.split "!" modh = remote_call "kernel32!GetModuleHandleW", mod.to_utf16 raise "no such module #{ mod }" if not modh ret = remote_call "kernel32!GetProcAddress", modh, meth ret end |
#handle ⇒ Object
4 |
# File 'lib/ragweed/wrap32/process.rb', line 4 def handle; @h; end |
#hunt(key, opts = {}) ⇒ Object
Given a string key, find it in memory. Very slow. Will read all memory regions, but you can provide “:index_range”, which must be a Range object, to constrain which ranges to search through. Returns a list of structs containing absolute memory locations, the index of the region, and some surrounding context for the hit.
321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 |
# File 'lib/ragweed/wrap32/process.rb', line 321 def hunt(key, opts={}) ret = [] refresh opts range = opts[:index_range] || (0..@memlist.size) @memlist.each_with_index do |t, i| if range.member? i if opts[:noisy] puts "#{ i }. #{ t[0].to_s(16) } -> #{ (t[0]+t[1]).to_s(16) }" end scan(i, opts) do |block, soff| if (needle = block.index(key)) r = OpenStruct.new r.location = (t[0] + soff + needle) r.index = i r.context = block ret << r return ret if opts[:first] end end end end ret end |
#image ⇒ Object
Return the EXE name of the process.
66 67 68 69 70 71 72 73 74 |
# File 'lib/ragweed/wrap32/process.rb', line 66 def image buf = "\x00" * 256 if Wrap32::nt_query_information_process(@h, 27, buf) buf = buf.from_utf16 buf = buf[(buf.index("\\"))..-1] return buf.asciiz end nil end |
#insert(buf) ⇒ Object
Insert a string anywhere into the memory of the remote process, returning its address, using an arena.
189 |
# File 'lib/ragweed/wrap32/process.rb', line 189 def insert(buf); @a.copy(buf); end |
#list_memory(&block) ⇒ Object
List all memory regions in the remote process by iterating over VirtualQueryEx. With a block, yields MEMORY_BASIC_INFORMATION structs. Without it, returns [baseaddr,size] tuples.
We “index” this list, so that we can refer to memory locations by “region number”, which is a Rubycorn-ism and not a Win32-ism. You’ll see lots of functions asking for memory indices, and this is what they’re referring to.
199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 |
# File 'lib/ragweed/wrap32/process.rb', line 199 def list_memory(&block) ret = [] i = 0 while (mbi = Wrap32::virtual_query_ex(@h, i)) break if (not ret.empty? and mbi.BaseAddress == 0) if block_given? yield mbi else base = mbi.BaseAddress || 0 size = mbi.RegionSize || 0 ret << [base,size] if mbi.State & 0x1000 # MEM_COMMIT i = base + size end end ret end |
#modules(&block) ⇒ Object
List the modules for the process, either yielding a struct for each to a block, or returning a list.
101 102 103 104 105 106 107 108 109 |
# File 'lib/ragweed/wrap32/process.rb', line 101 def modules(&block) if block_given? Wrap32::list_modules(@pid, &block) else ret = [] Wrap32::list_modules(@pid) {|x| ret << x} return ret end end |
#pointers_to(src, dst, opts = {}) ⇒ Object
Given a source and destination memory region, scan through “source” looking for properly-aligned U32LE values that would be valid pointers into “destination”. The “:range_start” and “:range_end” options constrain what a “valid pointer” into “destination” is.
279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 |
# File 'lib/ragweed/wrap32/process.rb', line 279 def pointers_to(src, dst, opts={}) refresh opts ret = {} range = ((opts[:range_start] || @memlist[dst][0])..(opts[:range_stop] || @memlist[dst][0]+@memlist[dst][1])) scan(src, opts) do |block, soff| 0.stepwith(block.size, 4) do |off, len| if len == 4 if range.member? block[off,4].to_l32 ret[soff + off] = block[off,4].to_l32 end end end end return ret end |
#ptr(x) ⇒ Object
Get a pointer into the remote process; pointers are just fixnums with a read/write method and a to_s.
18 19 20 21 22 |
# File 'lib/ragweed/wrap32/process.rb', line 18 def ptr(x) ret = Ptr.new(x) ret.p = self return ret end |
#read(off, sz = 4096) ⇒ Object
Read/write ranges of data or fixnums to/from the process by address.
112 |
# File 'lib/ragweed/wrap32/process.rb', line 112 def read(off, sz=4096); Wrap32::read_process_memory(@h, off, sz); end |
#read16(off) ⇒ Object
115 |
# File 'lib/ragweed/wrap32/process.rb', line 115 def read16(off); read(off, 2).unpack("v").first; end |
#read32(off) ⇒ Object
114 |
# File 'lib/ragweed/wrap32/process.rb', line 114 def read32(off); read(off, 4).unpack("L").first; end |
#read8(off) ⇒ Object
116 |
# File 'lib/ragweed/wrap32/process.rb', line 116 def read8(off); read(off, 1)[0]; end |
#region_range(i, opts = {}) ⇒ Object
Get the memory range, as a Ruby Range, for a region by index
443 444 445 446 |
# File 'lib/ragweed/wrap32/process.rb', line 443 def region_range(i, opts={}) refresh opts (@memlist[i][0]..(@memlist[i][1]+@memlist[i][0])) end |
#remote_call(meth, *args) ⇒ Object
call a function, by name or address, in the process, using CreateRemoteThread
123 124 125 126 127 128 129 130 |
# File 'lib/ragweed/wrap32/process.rb', line 123 def remote_call(meth, *args) loc = meth loc = get_proc(loc) if loc.kind_of? String loc = Ptr.new loc raise "bad proc name" if loc.null? t = Trampoline.new(self, loc) t.call *args end |
#resume(tid) ⇒ Object
Resume a thread by tid.
97 |
# File 'lib/ragweed/wrap32/process.rb', line 97 def resume(tid); Wrap32::open_thread(tid) {|x| Wrap32::resume_thread(x)}; end |
#resume_all ⇒ Object
Resume all the threads in the process. XXX this will not resume threads with suspend counts greater than 1.
90 |
# File 'lib/ragweed/wrap32/process.rb', line 90 def resume_all; threads.each {|x| resume(x)}; end |
#scan(i, opts = {}) ⇒ Object
In Python, and maybe Ruby, it was much faster to work on large memory regions a 4k page at a time, rather than reading the whole thing into one big string. Scan takes a memory region and yields 4k chunks of it to a block, along with the length of each chunk.
237 238 239 240 241 242 243 244 245 246 247 248 |
# File 'lib/ragweed/wrap32/process.rb', line 237 def scan(i, opts={}) refresh opts memt = @memlist[i] if memt[1] > 4096 0.step(memt[1], 4096) do |i| block = (memt[1] - i).cap(4096) yield read(memt[0] + i, block), memt[0]+i end else yield read(memt[0], memt[1]), memt[0] end end |
#strings_mem(i, opts = {}) ⇒ Object
Given a memory region number, do a Unix strings(1) on it. Valid options: :unicode: you probably always want to set this to “true” :minimum: how small strings to accept.
Fairly slow.
301 302 303 304 305 306 307 308 309 310 311 312 313 |
# File 'lib/ragweed/wrap32/process.rb', line 301 def strings_mem(i, opts={}) ret = [] opts[:offset] ||= 0 scan(i) do |block, soff| while 1 off, size = block.nextstring(opts) break if not off opts[:offset] += (off + size) ret << [soff+off, size, block[off,size]] end end ret end |
#suspend(tid) ⇒ Object
Suspend a thread by tid. Technically, this doesn’t need to be a method; you can suspend a thread anywhere without a process handle.
94 |
# File 'lib/ragweed/wrap32/process.rb', line 94 def suspend(tid); Wrap32::open_thread(tid) {|x| Wrap32::suspend_thread(x)}; end |
#suspend_all ⇒ Object
Suspend all the threads in the process.
86 |
# File 'lib/ragweed/wrap32/process.rb', line 86 def suspend_all; threads.each {|x| suspend(x)}; end |
#syscall_alloc(sz) ⇒ Object
Use VirtualAllocEx to grab a block of memory in the process. This is expensive, the equivalent of mmap()‘ing for each allocation.
137 |
# File 'lib/ragweed/wrap32/process.rb', line 137 def syscall_alloc(sz); ptr(Wrap32::virtual_alloc_ex(@h, sz)); end |
#thread_context(tid) ⇒ Object
Dump thread context, returning a struct that contains things like .Eip and .Eax.
252 253 254 255 256 |
# File 'lib/ragweed/wrap32/process.rb', line 252 def thread_context(tid) Wrap32::open_thread(tid) do |h| Wrap32::get_thread_context(h) end end |
#thread_stack_trace(tid) ⇒ Object
For libraries compiled with frame pointers: walk EBP back until it stops giving intelligible addresses, and, at each step, grab the saved EIP from just before it.
467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 |
# File 'lib/ragweed/wrap32/process.rb', line 467 def thread_stack_trace(tid) with_suspended_thread(tid) do ctx = thread_context(tid) if((start = read32(ctx.Ebp)) != 0) a = start stack = [[start, read32(start+4)]] while((a = read32(a)) and a != 0 and not stack.member?(a)) begin stack << [a, read32(a+4)] rescue; break; end end return stack end end [] end |
#threads(full = false, &block) ⇒ Object
Return a list of all the threads in the process; relatively expensive, so cache the result.
78 79 80 81 82 83 |
# File 'lib/ragweed/wrap32/process.rb', line 78 def threads(full=false, &block) return Wrap32::threads(@pid, &block) if block_given? ret = [] Wrap32::threads(@pid) {|x| ((full) ? ret << x : ret << x.th32ThreadID) } return ret end |
#to_modoff(off, force = false) ⇒ Object
Convert an address to “module+10h” notation, when possible.
157 158 159 160 161 162 163 164 165 166 167 168 169 |
# File 'lib/ragweed/wrap32/process.rb', line 157 def to_modoff(off, force=false) if not @modules or force @modules = modules.sort {|x,y| x.modBaseAddr <=> y.modBaseAddr} end @modules.each do |m| if off >= m.modBaseAddr and off < (m.modBaseAddr + m.modBaseSize) return "#{ m.szModule }+#{ (off - m.modBaseAddr).to_s(16) }h" end end return "#{ off.to_x }h" end |
#which_region_has?(addr, opts = {}) ⇒ Boolean
Figure out what region (by region index) has an address
449 450 451 452 453 454 455 |
# File 'lib/ragweed/wrap32/process.rb', line 449 def which_region_has?(addr, opts={}) refresh opts @memlist.each_with_index do |r, i| return i if (r[0]..r[0]+r[1]).member? addr end return nil end |
#with_suspended_thread(tid) ⇒ Object
Do something with a thread while its suspended
458 459 460 461 462 |
# File 'lib/ragweed/wrap32/process.rb', line 458 def with_suspended_thread(tid) ret = nil Wrap32::with_suspended_thread(tid) {|x| ret = yield} return ret end |
#write(off, data) ⇒ Object
113 |
# File 'lib/ragweed/wrap32/process.rb', line 113 def write(off, data); Wrap32::write_process_memory(@h, off, data); end |
#write16(off, v) ⇒ Object
118 |
# File 'lib/ragweed/wrap32/process.rb', line 118 def write16(off, v); write(off, [v].pack("v")); end |
#write32(off, v) ⇒ Object
117 |
# File 'lib/ragweed/wrap32/process.rb', line 117 def write32(off, v); write(off, [v].pack("L")); end |
#write8(off, v) ⇒ Object
119 |
# File 'lib/ragweed/wrap32/process.rb', line 119 def write8(off, v); write(off, v.chr); end |
#writeable?(off) ⇒ Boolean
Can I write to this address in the process?
133 |
# File 'lib/ragweed/wrap32/process.rb', line 133 def writeable?(off); Wrap32::writeable? @h, off; end |