1#!/usr/bin/env python3
2#
3# Author:
4#  Tamas Jos (@skelsec)
5#
6import io
7import enum
8from minidump.common_structs import *
9
10class AllocationProtect(enum.Enum):
11	NONE = 0
12	PAGE_EXECUTE = 0x10 #Enables execute access to the committed region of pages. An attempt to write to the committed region results in an access violation.
13						#This flag is not supported by the CreateFileMapping function.
14
15	PAGE_EXECUTE_READ = 0x20 #Enables execute or read-only access to the committed region of pages. An attempt to write to the committed region results in an access violation.
16							 #Windows Server 2003 and Windows XP:  This attribute is not supported by the CreateFileMapping function until Windows XP with SP2 and Windows Server 2003 with SP1.
17
18	PAGE_EXECUTE_READWRITE = 0x40 #Enables execute, read-only, or read/write access to the committed region of pages.#Windows Server 2003 and Windows XP:  This attribute is not supported by the CreateFileMapping function until Windows XP with SP2 and Windows Server 2003 with SP1.
19	PAGE_EXECUTE_WRITECOPY = 0x80 #Enables execute, read-only, or copy-on-write access to a mapped view of a file mapping object. An attempt to write to a committed copy-on-write page results in a private copy of the page being made for the process. The private page is marked as PAGE_EXECUTE_READWRITE, and the change is written to the new page.
20	#This flag is not supported by the VirtualAlloc or VirtualAllocEx functions.
21	#Windows Vista, Windows Server 2003 and Windows XP:  This attribute is not supported by the CreateFileMapping function until Windows Vista with SP1 and Windows Server 2008.
22
23	PAGE_NOACCESS = 0x01 #Disables all access to the committed region of pages. An attempt to read from, write to, or execute the committed region results in an access violation.
24	#This flag is not supported by the CreateFileMapping function.
25
26	PAGE_READONLY = 0x02 #Enables read-only access to the committed region of pages. An attempt to write to the committed region results in an access violation. If Data Execution Prevention is enabled, an attempt to execute code in the committed region results in an access violation.
27	PAGE_READWRITE = 0x04 #Enables read-only or read/write access to the committed region of pages. If Data Execution Prevention is enabled, attempting to execute code in the committed region results in an access violation.
28	PAGE_WRITECOPY = 0x08 #Enables read-only or copy-on-write access to a mapped view of a file mapping object. An attempt to write to a committed copy-on-write page results in a private copy of the page being made for the process. The private page is marked as PAGE_READWRITE, and the change is written to the new page. If Data Execution Prevention is enabled, attempting to execute code in the committed region results in an access violation.
29							#This flag is not supported by the VirtualAlloc or VirtualAllocEx functions.
30
31	PAGE_TARGETS_INVALID = 0x40000000
32	#Sets all locations in the pages as invalid targets for CFG. Used along with any execute page protection like PAGE_EXECUTE, PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE and PAGE_EXECUTE_WRITECOPY. Any indirect call to locations in those pages will fail CFG checks and the process will be terminated. The default behavior for executable pages allocated is to be marked valid call targets for CFG.
33	#This flag is not supported by the VirtualProtect or CreateFileMapping functions.
34
35	PAGE_TARGETS_NO_UPDATE = 0x40000000 #Pages in the region will not have their CFG information updated while the protection changes for VirtualProtect. For example, if the pages in the region was allocated using PAGE_TARGETS_INVALID, then the invalid information will be maintained while the page protection changes. This flag is only valid when the protection changes to an executable type like PAGE_EXECUTE, PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE and PAGE_EXECUTE_WRITECOPY. The default behavior for VirtualProtect protection change to executable is to mark all locations as valid call targets for CFG.
36	#The following are modifiers that can be used in addition to the options provided in the previous table, except as noted.
37	#Constant/value	Description
38
39	PAGE_GUARD = 0x100 #Pages in the region become guard pages. Any attempt to access a guard page causes the system to raise a STATUS_GUARD_PAGE_VIOLATION exception and turn off the guard page status. Guard pages thus act as a one-time access alarm. For more information, see Creating Guard Pages.
40	#When an access attempt leads the system to turn off guard page status, the underlying page protection takes over.
41	#If a guard page exception occurs during a system service, the service typically returns a failure status indicator.
42	#This value cannot be used with PAGE_NOACCESS.
43	#This flag is not supported by the CreateFileMapping function.
44
45	PAGE_NOCACHE = 0x200
46	#Sets all pages to be non-cachable. Applications should not use this attribute except when explicitly required for a device. Using the interlocked functions with memory that is mapped with SEC_NOCACHE can result in an EXCEPTION_ILLEGAL_INSTRUCTION exception.
47	#The PAGE_NOCACHE flag cannot be used with the PAGE_GUARD, PAGE_NOACCESS, or PAGE_WRITECOMBINE flags.
48	#The PAGE_NOCACHE flag can be used only when allocating private memory with the VirtualAlloc, VirtualAllocEx, or VirtualAllocExNuma functions. To enable non-cached memory access for shared memory, specify the SEC_NOCACHE flag when calling the CreateFileMapping function.
49	PAGE_WRITECOMBINE = 0x400 #Sets all pages to be write-combined.
50	#Applications should not use this attribute except when explicitly required for a device. Using the interlocked functions with memory that is mapped as write-combined can result in an EXCEPTION_ILLEGAL_INSTRUCTION exception.
51	#The PAGE_WRITECOMBINE flag cannot be specified with the PAGE_NOACCESS, PAGE_GUARD, and PAGE_NOCACHE flags.
52	#The PAGE_WRITECOMBINE flag can be used only when allocating private memory with the VirtualAlloc, VirtualAllocEx, or VirtualAllocExNuma functions. To enable write-combined memory access for shared memory, specify the SEC_WRITECOMBINE flag when calling the CreateFileMapping function.
53	#Windows Server 2003 and Windows XP:  This flag is not supported until Windows Server 2003 with SP1.
54
55class MemoryType(enum.Enum):
56	MEM_IMAGE = 0x1000000 #Indicates that the memory pages within the region are mapped into the view of an image section.
57	MEM_MAPPED = 0x40000 #Indicates that the memory pages within the region are mapped into the view of a section.
58	MEM_PRIVATE = 0x20000 #Indicates that the memory pages within the region are private (that is, not shared by other processes).
59class MemoryState(enum.Enum):
60	MEM_COMMIT = 0x1000 #Indicates committed pages for which physical storage has been allocated, either in memory or in the paging file on disk.
61	MEM_FREE = 0x10000 #Indicates free pages not accessible to the calling process and available to be allocated. For free pages, the information in the AllocationBase, AllocationProtect, Protect, and Type members is undefined.
62	MEM_RESERVE = 0x2000 #Indicates reserved pages where a range of the process's virtual address space is reserved without any physical storage being allocated. For reserved pages, the information in the Protect member is undefined.
63
64
65# https://msdn.microsoft.com/en-us/library/windows/desktop/ms680385(v=vs.85).aspx
66class MINIDUMP_MEMORY_INFO_LIST:
67	def __init__(self):
68		self.SizeOfHeader = 16
69		self.SizeOfEntry = 48
70		self.NumberOfEntries = None
71		self.entries = []
72
73	def get_size(self):
74		return self.SizeOfHeader + len(self.entries)*MINIDUMP_MEMORY_INFO().get_size()
75
76	def to_bytes(self):
77		t  = self.SizeOfHeader.to_bytes(4, byteorder = 'little', signed = False)
78		t += self.SizeOfEntry.to_bytes(4, byteorder = 'little', signed = False)
79		t += len(self.entries).to_bytes(8, byteorder = 'little', signed = False)
80		return t
81
82	@staticmethod
83	def parse(buff):
84		mhds = MINIDUMP_MEMORY_INFO_LIST()
85		mhds.SizeOfHeader = int.from_bytes(buff.read(4), byteorder = 'little', signed = False)
86		mhds.SizeOfEntry = int.from_bytes(buff.read(4), byteorder = 'little', signed = False)
87		mhds.NumberOfEntries = int.from_bytes(buff.read(8), byteorder = 'little', signed = False)
88
89		return mhds
90
91# https://msdn.microsoft.com/en-us/library/windows/desktop/ms680386(v=vs.85).aspx
92class MINIDUMP_MEMORY_INFO:
93	def __init__(self):
94		self.BaseAddress = None
95		self.AllocationBase = None
96		self.AllocationProtect = None
97		self.__alignment1 = 0
98		self.RegionSize = None
99		self.State = None
100		self.Protect = None
101		self.Type = None
102		self.__alignment2 = 0
103
104	def get_size(self):
105		return 8+8+4+4+8+4+4+4+4
106
107	def __str__(self):
108		t = ''
109		for k in self.__dict__:
110			t += '%s : %s\r\n' % (k, str(self.__dict__[k]))
111		return t
112
113	def to_bytes(self):
114		t = self.BaseAddress.to_bytes(8, byteorder = 'little', signed = False)
115		t += self.AllocationBase.to_bytes(8, byteorder = 'little', signed = False)
116		t += self.AllocationProtect.to_bytes(4, byteorder = 'little', signed = False)
117		t += self.__alignment1.to_bytes(4, byteorder = 'little', signed = False)
118		t += self.RegionSize.to_bytes(8, byteorder = 'little', signed = False)
119		t += self.State.value.to_bytes(4, byteorder = 'little', signed = False)
120		t += self.Protect.value.to_bytes(4, byteorder = 'little', signed = False)
121		t += self.Type.value.to_bytes(4, byteorder = 'little', signed = False)
122		t += self.__alignment2.to_bytes(4, byteorder = 'little', signed = False)
123		return t
124
125	@staticmethod
126	def parse(buff):
127		mmi = MINIDUMP_MEMORY_INFO()
128		mmi.BaseAddress = int.from_bytes(buff.read(8), byteorder = 'little', signed = False)
129		mmi.AllocationBase = int.from_bytes(buff.read(8), byteorder = 'little', signed = False)
130		mmi.AllocationProtect = int.from_bytes(buff.read(4), byteorder = 'little', signed = False)
131		mmi.__alignment1 = int.from_bytes(buff.read(4), byteorder = 'little', signed = False)
132		mmi.RegionSize = int.from_bytes(buff.read(8), byteorder = 'little', signed = False)
133		try:
134			mmi.State = MemoryState(int.from_bytes(buff.read(4), byteorder = 'little', signed = False))
135		except:
136			pass
137		try:
138			mmi.Protect = AllocationProtect(int.from_bytes(buff.read(4), byteorder = 'little', signed = False))
139		except:
140			pass
141		try:
142			mmi.Type = MemoryType(int.from_bytes(buff.read(4), byteorder = 'little', signed = False))
143		except:
144			pass
145		mmi.__alignment2 = int.from_bytes(buff.read(4), byteorder = 'little', signed = False)
146
147		return mmi
148
149class MinidumpMemoryInfo:
150	def __init__(self):
151		self.BaseAddress = None
152		self.AllocationBase = None
153		self.AllocationProtect = None
154		self.RegionSize = None
155		self.State = None
156		self.Protect = None
157		self.Type = None
158
159	@staticmethod
160	def parse(t, buff):
161		mmi = MinidumpMemoryInfo()
162		mmi.BaseAddress = t.BaseAddress
163		mmi.AllocationBase = t.AllocationBase
164		mmi.AllocationProtect = t.AllocationProtect
165		mmi.RegionSize = t.RegionSize
166		mmi.State = t.State
167		mmi.Protect = t.Protect
168		mmi.Type = t.Type
169		return mmi
170
171	@staticmethod
172	def get_header():
173		t = [
174			'BaseAddress',
175			'AllocationBase',
176			'AllocationProtect',
177			'RegionSize',
178			'State',
179			'Protect',
180			'Type',
181		]
182		return t
183
184	def to_row(self):
185		t = [
186			hex(self.BaseAddress),
187			hex(self.AllocationBase),
188			str(self.AllocationProtect),
189			hex(self.RegionSize),
190			self.State.name if self.State else 'N/A',
191			self.Protect.name if self.Protect else 'N/A',
192			self.Type.name if self.Type else 'N/A',
193		]
194		return t
195
196
197class MinidumpMemoryInfoList:
198	def __init__(self):
199		self.header = None
200		self.infos = []
201
202	@staticmethod
203	def parse(dir, buff):
204		t = MinidumpMemoryInfoList()
205		buff.seek(dir.Location.Rva)
206		data = buff.read(dir.Location.DataSize)
207		chunk = io.BytesIO(data)
208		t.header = MINIDUMP_MEMORY_INFO_LIST.parse(chunk)
209		for _ in range(t.header.NumberOfEntries):
210			mi = MINIDUMP_MEMORY_INFO.parse(chunk)
211			t.infos.append(MinidumpMemoryInfo.parse(mi, buff))
212
213		return t
214
215	@staticmethod
216	async def aparse(dir, buff):
217		t = MinidumpMemoryInfoList()
218		await buff.seek(dir.Location.Rva)
219		data = await buff.read(dir.Location.DataSize)
220		chunk = io.BytesIO(data)
221		t.header = MINIDUMP_MEMORY_INFO_LIST.parse(chunk)
222		for _ in range(t.header.NumberOfEntries):
223			mi = MINIDUMP_MEMORY_INFO.parse(chunk)
224			t.infos.append(MinidumpMemoryInfo.parse(mi, None))
225
226		return t
227
228	def to_table(self):
229		t = []
230		t.append(MinidumpMemoryInfo.get_header())
231		for info in self.infos:
232			t.append(info.to_row())
233		return t
234
235	def __str__(self):
236		return '== MinidumpMemoryInfoList ==\n' + construct_table(self.to_table())