tduehr-ragweed 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. data/History.txt +15 -0
  2. data/README.rdoc +35 -0
  3. data/README.txt +9 -0
  4. data/Rakefile +30 -0
  5. data/examples/hittracertux.rb +48 -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/arena.rb +55 -0
  11. data/lib/ragweed/blocks.rb +128 -0
  12. data/lib/ragweed/debugger32.rb +338 -0
  13. data/lib/ragweed/debuggerosx.rb +419 -0
  14. data/lib/ragweed/debuggertux.rb +347 -0
  15. data/lib/ragweed/detour.rb +223 -0
  16. data/lib/ragweed/ptr.rb +48 -0
  17. data/lib/ragweed/rasm/isa.rb +1046 -0
  18. data/lib/ragweed/rasm/util.rb +26 -0
  19. data/lib/ragweed/rasm.rb +53 -0
  20. data/lib/ragweed/sbuf.rb +197 -0
  21. data/lib/ragweed/trampoline.rb +103 -0
  22. data/lib/ragweed/utils.rb +87 -0
  23. data/lib/ragweed/wrap32/debugging.rb +163 -0
  24. data/lib/ragweed/wrap32/device.rb +49 -0
  25. data/lib/ragweed/wrap32/event.rb +50 -0
  26. data/lib/ragweed/wrap32/hooks.rb +23 -0
  27. data/lib/ragweed/wrap32/overlapped.rb +46 -0
  28. data/lib/ragweed/wrap32/process.rb +506 -0
  29. data/lib/ragweed/wrap32/process_token.rb +59 -0
  30. data/lib/ragweed/wrap32/thread_context.rb +208 -0
  31. data/lib/ragweed/wrap32/winx.rb +16 -0
  32. data/lib/ragweed/wrap32/wrap32.rb +526 -0
  33. data/lib/ragweed/wrap32.rb +53 -0
  34. data/lib/ragweed/wraposx/constants.rb +101 -0
  35. data/lib/ragweed/wraposx/kernelerrorx.rb +147 -0
  36. data/lib/ragweed/wraposx/region_info.rb +244 -0
  37. data/lib/ragweed/wraposx/thread_context.rb +203 -0
  38. data/lib/ragweed/wraposx/thread_info.rb +213 -0
  39. data/lib/ragweed/wraposx/wraposx.rb +376 -0
  40. data/lib/ragweed/wraposx.rb +53 -0
  41. data/lib/ragweed/wraptux/constants.rb +68 -0
  42. data/lib/ragweed/wraptux/threads.rb +3 -0
  43. data/lib/ragweed/wraptux/wraptux.rb +76 -0
  44. data/lib/ragweed/wraptux.rb +53 -0
  45. data/lib/ragweed.rb +84 -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,50 @@
1
+ class Ragweed::Event
2
+ # Quick wrapper around Win32 events. Events are simple thread sync
3
+ # objects that are cross-process. They are like semaphores that you
4
+ # can select() on.
5
+
6
+ # You can just do WinEvent.new to get a new anonymous handle, and
7
+ # then call .handle on it to find out what the handle was. Communicate
8
+ # your pid and the handle value, somehow, to a remote process. That
9
+ # process can get the same event by passing a WinProcess and the
10
+ # handle here.
11
+ #
12
+ # So, in Process1 (assume pid 668, and handle 300):
13
+ #
14
+ # e = WinEvent.new
15
+ # puts #{ get_current_process_id }: #{ e.handle }"
16
+ #
17
+ # And in Process2:
18
+ #
19
+ # e = WinEvent.new(WinProcess.new(668), 300)
20
+ #
21
+ # Now both processes share an event.
22
+ def initialize(p=nil, h=nil)
23
+ @p = p
24
+ @h = (@p.dup_handle(h) if h) || create_event
25
+ end
26
+
27
+ # Don't return until the event is signalled. Note that you
28
+ # can't break this with timeouts or CTR-C.
29
+ def wait
30
+ Wrap32::wait_for_single_object @h
31
+ end
32
+
33
+ # Signal the event; anyone waiting on it is now released.
34
+ def signal
35
+ Wrap32::set_event(@h)
36
+ end
37
+
38
+ # Force the event back to unsignalled state.
39
+ def reset
40
+ Wrap32::reset_event(@h)
41
+ end
42
+
43
+ # A wait loop.
44
+ def on(&block)
45
+ while 1
46
+ wait
47
+ break if not yield
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,23 @@
1
+ class Ragweed::Debugger32
2
+ # Hook function calls
3
+ # nargs is the number of arguments taken by function at ip
4
+ # callable/block is called with ev, ctx, dir (:enter or :leave), and args Array (see examples/hook_notepad.rb)
5
+ # default handler prints arguments
6
+ def hook(ip, nargs, callable=nil, &block)
7
+ callable ||= block || lambda do |ev, ctx,dir,args|
8
+ puts "#{dir} #{ip.to_s(16) rescue ip.to_s}"
9
+ puts args.map{|a| "%08x" % a}.join(',')
10
+ end
11
+
12
+ breakpoint_set(ip) do |ev,ctx|
13
+ args = (1..nargs).map {|i| process.read32(ctx.esp + 4*i)}
14
+ retp = process.read32(ctx.esp)
15
+ # set exit bpoint
16
+ breakpoint_set(retp) do |ev,ctx|
17
+ callable.call(ev, ctx, :leave, args)
18
+ breakpoint_clear(retp)
19
+ end.install
20
+ callable.call(ev, ctx, :enter, args)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,46 @@
1
+ class Ragweed::Wrap32::Overlapped
2
+ attr_accessor :internal
3
+ attr_accessor :internal_high
4
+ attr_accessor :offset
5
+ attr_accessor :offset_high
6
+ attr_accessor :event
7
+ attr_accessor :target
8
+
9
+ def self.get
10
+ h = Ragweed::Wrap32::create_event(nil, false, true)
11
+ r = self.new
12
+ r.event = h
13
+ return r
14
+ end
15
+
16
+ def initialize(str=nil)
17
+ @buf = "\x00" * 20
18
+ @internal, @internal_high, @offset, @offset_high, @event = [0,0,0,0,0]
19
+ init(str) if str
20
+ end
21
+
22
+ def to_s
23
+ buf = [@internal, @internal_high, @offset, @offset_high, @event].pack("LLLLL")
24
+ @buf.replace(buf)
25
+ end
26
+
27
+ def release
28
+ Ragweed::Wrap32::close_handle(@event)
29
+ end
30
+
31
+ def wait(h)
32
+ return if not @event
33
+ Ragweed::Wrap32::wait_for_single_object(@event)
34
+ Ragweed::Wrap32::get_overlapped_result(h, self)
35
+ end
36
+
37
+ private
38
+
39
+ def init(str)
40
+ @internal,
41
+ @internal_high,
42
+ @offset,
43
+ @offset_high,
44
+ @event = str.unpack("LLLLL")
45
+ end
46
+ end
@@ -0,0 +1,506 @@
1
+ # XXX - PORT ME!!
2
+
3
+ class Ragweed::Process
4
+ def handle; @h; end
5
+ attr_reader :pid
6
+ include Ragweed
7
+
8
+ def self.find_by_regex(name)
9
+ Wrap32::all_processes do |p|
10
+ if p.szExeFile =~ name
11
+ return self.new(p.th32ProcessID)
12
+ end
13
+ end
14
+ end
15
+
16
+ # Get a pointer into the remote process; pointers are just fixnums
17
+ # with a read/write method and a to_s.
18
+ def ptr(x)
19
+ ret = Ptr.new(x)
20
+ ret.p = self
21
+ return ret
22
+ end
23
+
24
+ # clone a handle from the remote process to here (to here? tf?)
25
+ def dup_handle(h)
26
+ Wrap32::duplicate_handle(@h, h)
27
+ end
28
+
29
+ # look up a process by its name --- but this is in the local process,
30
+ # which is broken --- a heuristic that sometimes works for w32 functions,
31
+ # but probably never otherwise.
32
+ def get_proc(name)
33
+ return Ptr.new(name) if name.kind_of? Numeric or name.kind_of? Ptr
34
+ ptr(Wrap32::get_proc_address(name))
35
+ end
36
+
37
+ def get_proc_remote(name)
38
+ mod, meth = name.split "!"
39
+ modh = remote_call "kernel32!GetModuleHandleW", mod.to_utf16
40
+ raise "no such module #{ mod }" if not modh
41
+ ret = remote_call "kernel32!GetProcAddress", modh, meth
42
+ ret
43
+ end
44
+
45
+ # Look up a process by name or regex, returning an array of all
46
+ # matching processes, as objects.
47
+ def self.by_name(n)
48
+ n = Regexp.new(n) if not n.kind_of? Regexp
49
+ p = []
50
+ all_processes do |px|
51
+ if px.szExeFile =~ n
52
+ p << self.new(px.th32ProcessID)
53
+ end
54
+ end
55
+ p
56
+ end
57
+
58
+ # Just need a PID to get started.
59
+ def initialize(pid)
60
+ @pid = pid
61
+ @h = Wrap32::open_process(pid)
62
+ @a = arena()
63
+ end
64
+
65
+ # Return the EXE name of the process.
66
+ def image
67
+ buf = "\x00" * 256
68
+ if Wrap32::nt_query_information_process(@h, 27, buf)
69
+ buf = buf.from_utf16
70
+ buf = buf[(buf.index("\\"))..-1]
71
+ return buf.asciiz
72
+ end
73
+ nil
74
+ end
75
+
76
+ # Return a list of all the threads in the process; relatively
77
+ # expensive, so cache the result.
78
+ def threads(full=false, &block)
79
+ return Wrap32::threads(@pid, &block) if block_given?
80
+ ret = []
81
+ Wrap32::threads(@pid) {|x| ((full) ? ret << x : ret << x.th32ThreadID) }
82
+ return ret
83
+ end
84
+
85
+ # Suspend all the threads in the process.
86
+ def suspend_all; threads.each {|x| suspend(x)}; end
87
+
88
+ # Resume all the threads in the process. XXX this will not
89
+ # resume threads with suspend counts greater than 1.
90
+ def resume_all; threads.each {|x| resume(x)}; end
91
+
92
+ # Suspend a thread by tid. Technically, this doesn't need to be
93
+ # a method; you can suspend a thread anywhere without a process handle.
94
+ def suspend(tid); Wrap32::open_thread(tid) {|x| Wrap32::suspend_thread(x)}; end
95
+
96
+ # Resume a thread by tid.
97
+ def resume(tid); Wrap32::open_thread(tid) {|x| Wrap32::resume_thread(x)}; end
98
+
99
+ # List the modules for the process, either yielding a struct for
100
+ # each to a block, or returning a list.
101
+ def modules(&block)
102
+ if block_given?
103
+ Wrap32::list_modules(@pid, &block)
104
+ else
105
+ ret = []
106
+ Wrap32::list_modules(@pid) {|x| ret << x}
107
+ return ret
108
+ end
109
+ end
110
+
111
+ # Read/write ranges of data or fixnums to/from the process by address.
112
+ def read(off, sz=4096); Wrap32::read_process_memory(@h, off, sz); end
113
+ def write(off, data); Wrap32::write_process_memory(@h, off, data); end
114
+ def read32(off); read(off, 4).unpack("L").first; end
115
+ def read16(off); read(off, 2).unpack("v").first; end
116
+ def read8(off); read(off, 1)[0]; end
117
+ def write32(off, v); write(off, [v].pack("L")); end
118
+ def write16(off, v); write(off, [v].pack("v")); end
119
+ def write8(off, v); write(off, v.chr); end
120
+
121
+ # call a function, by name or address, in the process, using
122
+ # CreateRemoteThread
123
+ def remote_call(meth, *args)
124
+ loc = meth
125
+ loc = get_proc(loc) if loc.kind_of? String
126
+ loc = Ptr.new loc
127
+ raise "bad proc name" if loc.null?
128
+ t = Trampoline.new(self, loc)
129
+ t.call *args
130
+ end
131
+
132
+ # Can I write to this address in the process?
133
+ def writeable?(off); Wrap32::writeable? @h, off; end
134
+
135
+ # Use VirtualAllocEx to grab a block of memory in the process. This
136
+ # is expensive, the equivalent of mmap()'ing for each allocation.
137
+ def syscall_alloc(sz); ptr(Wrap32::virtual_alloc_ex(@h, sz)); end
138
+
139
+ # Use arenas, when possible, to quickly allocate memory. The upside
140
+ # is this is very fast. The downside is you can't free the memory
141
+ # without invalidating every allocation you've made prior.
142
+ def alloc(sz, syscall=false)
143
+ if syscall or sz > 4090
144
+ ret = syscall_alloc(sz)
145
+ else
146
+ ptr(@a.alloc(sz))
147
+ end
148
+ end
149
+
150
+ # Free the return value of syscall_alloc. Do NOT use for the return
151
+ # value of alloc.
152
+ def free(off)
153
+ Wrap32::virtual_free_ex(@h, off)
154
+ end
155
+
156
+ # Convert an address to "module+10h" notation, when possible.
157
+ def to_modoff(off, force=false)
158
+ if not @modules or force
159
+ @modules = modules.sort {|x,y| x.modBaseAddr <=> y.modBaseAddr}
160
+ end
161
+
162
+ @modules.each do |m|
163
+ if off >= m.modBaseAddr and off < (m.modBaseAddr + m.modBaseSize)
164
+ return "#{ m.szModule }+#{ (off - m.modBaseAddr).to_s(16) }h"
165
+ end
166
+ end
167
+
168
+ return "#{ off.to_x }h"
169
+ end
170
+
171
+ # Get another allocation arena for this process. Pretty cheap. Given
172
+ # a block, behaves like File#open, disposing of the arena when you're
173
+ # done.
174
+ def arena(&block)
175
+ me = self
176
+ a = Arena.new(lambda {me.syscall_alloc(4096)},
177
+ lambda {|p| me.free(p)},
178
+ lambda {|dst, src| me.write(dst, src)})
179
+ if block_given?
180
+ ret = yield a
181
+ a.release
182
+ return ret
183
+ end
184
+ a
185
+ end
186
+
187
+ # Insert a string anywhere into the memory of the remote process,
188
+ # returning its address, using an arena.
189
+ def insert(buf); @a.copy(buf); end
190
+
191
+ # List all memory regions in the remote process by iterating over
192
+ # VirtualQueryEx. With a block, yields MEMORY_BASIC_INFORMATION
193
+ # structs. Without it, returns [baseaddr,size] tuples.
194
+ #
195
+ # We "index" this list, so that we can refer to memory locations
196
+ # by "region number", which is a Rubycorn-ism and not a Win32-ism.
197
+ # You'll see lots of functions asking for memory indices, and this
198
+ # is what they're referring to.
199
+ def list_memory(&block)
200
+ ret = []
201
+ i = 0
202
+ while (mbi = Wrap32::virtual_query_ex(@h, i))
203
+ break if (not ret.empty? and mbi.BaseAddress == 0)
204
+ if block_given?
205
+ yield mbi
206
+ else
207
+ base = mbi.BaseAddress || 0
208
+ size = mbi.RegionSize || 0
209
+ ret << [base,size] if mbi.State & 0x1000 # MEM_COMMIT
210
+ i = base + size
211
+ end
212
+ end
213
+ ret
214
+ end
215
+
216
+ # Human-readable standard output of list_memory. Remember that the
217
+ # index number is important.
218
+ def dump_memory_list
219
+ list_memory.each_with_index {|x,i| puts "#{ i }. #{ x[0].to_s(16) }(#{ x[1] })"}
220
+ true
221
+ end
222
+
223
+ # Read an entire memory region into a string by region number.
224
+ def get_memory(i, opts={})
225
+ refresh opts
226
+ read(@memlist[i][0], @memlist[i][1])
227
+ end
228
+
229
+ # Print a canonical hexdump of an entire memory region by region number.
230
+ def dump_memory(i, opts={}); get_memory(i, opts).hexdump; end
231
+
232
+ # In Python, and maybe Ruby, it was much faster to work on large
233
+ # memory regions a 4k page at a time, rather than reading the
234
+ # whole thing into one big string. Scan takes a memory region
235
+ # and yields 4k chunks of it to a block, along with the length
236
+ # of each chunk.
237
+ def scan(i, opts={})
238
+ refresh opts
239
+ memt = @memlist[i]
240
+ if memt[1] > 4096
241
+ 0.step(memt[1], 4096) do |i|
242
+ block = (memt[1] - i).cap(4096)
243
+ yield read(memt[0] + i, block), memt[0]+i
244
+ end
245
+ else
246
+ yield read(memt[0], memt[1]), memt[0]
247
+ end
248
+ end
249
+
250
+ # Dump thread context, returning a struct that contains things like
251
+ # .Eip and .Eax.
252
+ def thread_context(tid)
253
+ Wrap32::open_thread(tid) do |h|
254
+ Wrap32::get_thread_context(h)
255
+ end
256
+ end
257
+
258
+ # Take a region of memory and walk over it in 255-byte samples (less
259
+ # than 255 bytes and you lose accuracy, but you can increase it with
260
+ # the ":window" option), computing entropy for each sample, returning
261
+ # a list of [offset,entropy] tuples.
262
+ def entropy_map(i, opts={})
263
+ ret = []
264
+ startoff = opts[:starting_offset]
265
+ startoff ||= 0
266
+ window = opts[:window] || 255
267
+ scan(i, opts) do |block, soff|
268
+ startoff.stepwith(block.size, window) do |off, len|
269
+ ret << [off, block[off,len].entropy]
270
+ end
271
+ end
272
+ return ret
273
+ end
274
+
275
+ # Given a source and destination memory region, scan through "source"
276
+ # looking for properly-aligned U32LE values that would be valid pointers
277
+ # into "destination". The ":range_start" and ":range_end" options
278
+ # constrain what a "valid pointer" into "destination" is.
279
+ def pointers_to(src, dst, opts={})
280
+ refresh opts
281
+ ret = {}
282
+ range = ((opts[:range_start] || @memlist[dst][0])..(opts[:range_stop] || @memlist[dst][0]+@memlist[dst][1]))
283
+ scan(src, opts) do |block, soff|
284
+ 0.stepwith(block.size, 4) do |off, len|
285
+ if len == 4
286
+ if range.member? block[off,4].to_l32
287
+ ret[soff + off] = block[off,4].to_l32
288
+ end
289
+ end
290
+ end
291
+ end
292
+ return ret
293
+ end
294
+
295
+ # Given a memory region number, do a Unix strings(1) on it. Valid
296
+ # options:
297
+ # :unicode: you probably always want to set this to "true"
298
+ # :minimum: how small strings to accept.
299
+ #
300
+ # Fairly slow.
301
+ def strings_mem(i, opts={})
302
+ ret = []
303
+ opts[:offset] ||= 0
304
+ scan(i) do |block, soff|
305
+ while 1
306
+ off, size = block.nextstring(opts)
307
+ break if not off
308
+ opts[:offset] += (off + size)
309
+ ret << [soff+off, size, block[off,size]]
310
+ end
311
+ end
312
+ ret
313
+ end
314
+
315
+ # Given a string key, find it in memory. Very slow. Will read all
316
+ # memory regions, but you can provide ":index_range", which must be
317
+ # a Range object, to constrain which ranges to search through.
318
+ # Returns a list of structs containing absolute memory locations,
319
+ # the index of the region, and some surrounding context for the
320
+ # hit.
321
+ def hunt(key, opts={})
322
+ ret = []
323
+ refresh opts
324
+ range = opts[:index_range] || (0..@memlist.size)
325
+ @memlist.each_with_index do |t, i|
326
+ if range.member? i
327
+ if opts[:noisy]
328
+ puts "#{ i }. #{ t[0].to_s(16) } -> #{ (t[0]+t[1]).to_s(16) }"
329
+ end
330
+ scan(i, opts) do |block, soff|
331
+ if (needle = block.index(key))
332
+ r = OpenStruct.new
333
+ r.location = (t[0] + soff + needle)
334
+ r.index = i
335
+ r.context = block
336
+ ret << r
337
+ return ret if opts[:first]
338
+ end
339
+ end
340
+ end
341
+ end
342
+ ret
343
+ end
344
+
345
+ private
346
+
347
+ def windowize(i, opts={})
348
+ window = opts[:window] || 1024
349
+ if window == :auto
350
+ r = region_range(i)
351
+ window = (r.last - r.first) / 60
352
+ end
353
+ return window
354
+ end
355
+
356
+ public
357
+
358
+ # Like entropy_map, scan a process and compute adler16 checksums for
359
+ # 1k (or :window) blocks.
360
+ def adler_map(i, opts={})
361
+ refresh opts
362
+ window = windowize(i, opts)
363
+ ret = []
364
+ scan(i, opts) do |block,soff|
365
+ 0.stepwith(block.size-1, window) do |off, len|
366
+ if (b = block[off,len])
367
+ ret << b.adler
368
+ end
369
+ end
370
+ end
371
+ ret
372
+ end
373
+
374
+ # If you store the adler map, you've compressed the memory region
375
+ # down to a small series of fixnums, and you can use it with this
376
+ # function to re-check the memory region and see if anything's changing.
377
+ def adler_compare(i, orig, opts={})
378
+ refresh opts
379
+ window = windowize(i, opts)
380
+ ret = []
381
+ c = -1
382
+ scan(i, opts) do |block,soff|
383
+ 0.stepwith(block.size-1, window) do |off, len|
384
+ if block[off,len].adler != orig[c += 1]
385
+ ret << soff+off
386
+ end
387
+ end
388
+ end
389
+ ret
390
+ end
391
+
392
+ # Quick and dirty visualization of a memory region by checksum
393
+ # changes.
394
+ class AdlerChart
395
+
396
+ # Create with a WinProcess and region index
397
+ def initialize(p, i)
398
+ @p = p
399
+ @i = i
400
+ @initstate = map
401
+ end
402
+
403
+ private
404
+ def map; @p.adler_map(@i, :window => :auto); end
405
+
406
+ public
407
+
408
+ # Just puts the chart repeatedly, to get:
409
+ # ........................................................*.*..
410
+ # ..........................................................*..
411
+ # ..........................................................*..
412
+ # Where * represents a chunk of memory that has changed.
413
+ def to_s
414
+ s = StringIO.new
415
+ newstate = map
416
+ @initstate.each_with_index do |sum, i|
417
+ if sum != newstate[i]
418
+ s.write "*"
419
+ @initstate[i] = newstate[i]
420
+ else
421
+ s.write "."
422
+ end
423
+ end
424
+ s.rewind;s.read()
425
+ end
426
+ end
427
+
428
+ # See WinProcess::AdlerChart. Get one.
429
+ def adler_chart(i)
430
+ AdlerChart.new self, i
431
+ end
432
+
433
+ # Given a memory region, use the adler routines to create a checksum
434
+ # map, wait a short period of time, and scan for changes, to find
435
+ # churning memory.
436
+ def changes(i, sleeptime=0.5)
437
+ q = adler_map(i)
438
+ sleep(sleeptime)
439
+ adler_compare(i, q)
440
+ end
441
+
442
+ # Get the memory range, as a Ruby Range, for a region by index
443
+ def region_range(i, opts={})
444
+ refresh opts
445
+ (@memlist[i][0]..(@memlist[i][1]+@memlist[i][0]))
446
+ end
447
+
448
+ # Figure out what region (by region index) has an address
449
+ def which_region_has?(addr, opts={})
450
+ refresh opts
451
+ @memlist.each_with_index do |r, i|
452
+ return i if (r[0]..r[0]+r[1]).member? addr
453
+ end
454
+ return nil
455
+ end
456
+
457
+ # Do something with a thread while its suspended
458
+ def with_suspended_thread(tid)
459
+ ret = nil
460
+ Wrap32::with_suspended_thread(tid) {|x| ret = yield}
461
+ return ret
462
+ end
463
+
464
+ # For libraries compiled with frame pointers: walk EBP back
465
+ # until it stops giving intelligible addresses, and, at each
466
+ # step, grab the saved EIP from just before it.
467
+ def thread_stack_trace(tid)
468
+ with_suspended_thread(tid) do
469
+ ctx = thread_context(tid)
470
+ if((start = read32(ctx.Ebp)) != 0)
471
+ a = start
472
+ stack = [[start, read32(start+4)]]
473
+ while((a = read32(a)) and a != 0 and not stack.member?(a))
474
+ begin
475
+ stack << [a, read32(a+4)]
476
+ rescue; break; end
477
+ end
478
+ return stack
479
+ end
480
+ end
481
+ []
482
+ end
483
+
484
+ # Human-readable version of thread_stack_trace, with module
485
+ # offsets.
486
+ def dump_stack_trace(tid)
487
+ thread_stack_trace(tid).each do |frame, code|
488
+ puts "#{ frame.to_x } @ #{ to_modoff(code) }"
489
+ end
490
+ end
491
+ alias_method :bt, :dump_stack_trace
492
+
493
+ def detour(loc, o={})
494
+ klass = o[:class] || Detour
495
+ loc = get_proc(loc)
496
+ r = klass.new(loc, o)
497
+ r.call if not o[:chicken]
498
+ return r
499
+ end
500
+
501
+ private
502
+
503
+ def refresh(opts={})
504
+ @memlist = list_memory if not @memlist or opts.delete(:refresh)
505
+ end
506
+ end
@@ -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