Import upstream version 0.0.17, md5 ece5142a4fc2f7722b0d6d2ec6966f9a
Kali Janitor
3 years ago
0 | MIT License | |
1 | ||
2 | Copyright (c) 2018 Tamas Jos | |
3 | ||
4 | Permission is hereby granted, free of charge, to any person obtaining a copy | |
5 | of this software and associated documentation files (the "Software"), to deal | |
6 | in the Software without restriction, including without limitation the rights | |
7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
8 | copies of the Software, and to permit persons to whom the Software is | |
9 | furnished to do so, subject to the following conditions: | |
10 | ||
11 | The above copyright notice and this permission notice shall be included in all | |
12 | copies or substantial portions of the Software. | |
13 | ||
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
20 | SOFTWARE. |
0 | include LICENSE README.md |
0 | 0 | Metadata-Version: 1.2 |
1 | 1 | Name: minidump |
2 | Version: 0.0.12 | |
2 | Version: 0.0.17 | |
3 | 3 | Summary: Python library to parse Windows minidump file format |
4 | 4 | Home-page: https://github.com/skelsec/minidump |
5 | 5 | Author: Tamas Jos |
4 | 4 | Python >= 3.6 |
5 | 5 | |
6 | 6 | # Basic Usage |
7 | This module is primarily intended to be used as a library, however for the sake of demonstarting its capabilities there is a command line tool implemented called `minidump`. This tool has the following modes of operation. | |
8 | ||
9 | #### Console | |
10 | One-shot parsing and information retrieval. | |
11 | ||
7 | 12 | ```minidump.py --all <mindidump file> ``` |
8 | 13 | See help for possible options. |
14 | #### Shell | |
15 | There is and interactive command shell to get all info (modules, threads, excetpions etc) and browse the virtual memory of the process dumped (read/read int/read uint/move/peek/tell) | |
16 | ||
17 | ```minidump.py -i <mindidump file> ``` | |
18 | Once in the shell, all commands are documented. Use the `?` command to see all options. | |
9 | 19 | |
10 | 20 | # Advanced usage |
11 | 21 | After parsing the minidump file, you can use the MinidumpFileReader and MinidumpBufferedReader objects to perform various searches/reads in the dumped process' address space. |
0 | #!/usr/bin/env python3 | |
1 | # | |
2 | # Author: | |
3 | # Tamas Jos (@skelsec) | |
4 | # | |
5 | ||
6 | import logging | |
7 | import asyncio | |
8 | from minidump.aminidumpfile import AMinidumpFile | |
9 | from minidump.common_structs import hexdump | |
10 | from minidump._version import __banner__ | |
11 | ||
12 | async def run(): | |
13 | import argparse | |
14 | ||
15 | parser = argparse.ArgumentParser(description='A parser for minidumnp files') | |
16 | parser.add_argument('minidumpfile', help='path to the minidump file of lsass.exe') | |
17 | parser.add_argument('-v', '--verbose', action='count', default=0) | |
18 | parser.add_argument('--header', action='store_true', help='File header info') | |
19 | parser.add_argument('--modules', action='store_true', help='List modules') | |
20 | parser.add_argument('--threads', action='store_true', help='List threads') | |
21 | parser.add_argument('--memory', action='store_true', help='List memory') | |
22 | parser.add_argument('--sysinfo', action='store_true', help='Show sysinfo') | |
23 | parser.add_argument('--comments', action='store_true', help='Show comments') | |
24 | parser.add_argument('--exception', action='store_true', help='Show exception records') | |
25 | parser.add_argument('--handles', action='store_true', help='List handles') | |
26 | parser.add_argument('--misc', action='store_true', help='Show misc info') | |
27 | parser.add_argument('--all', action='store_true', help='Show all info') | |
28 | parser.add_argument('-r', '--read-addr', type=lambda x: int(x,0), help='Dump a memory region from the process\'s addres space') | |
29 | parser.add_argument('-s', '--read-size', type=lambda x: int(x,0), default = 0x20, help='Dump a memory region from the process\'s addres space') | |
30 | ||
31 | args = parser.parse_args() | |
32 | if args.verbose == 0: | |
33 | logging.basicConfig(level=logging.INFO) | |
34 | elif args.verbose == 1: | |
35 | logging.basicConfig(level=logging.DEBUG) | |
36 | else: | |
37 | logging.basicConfig(level=1) | |
38 | ||
39 | print(__banner__) | |
40 | ||
41 | ||
42 | mf = await AMinidumpFile.parse(args.minidumpfile) | |
43 | reader = mf.get_reader() | |
44 | ||
45 | if args.all or args.threads: | |
46 | if mf.threads is not None: | |
47 | print(str(mf.threads)) | |
48 | if mf.threads_ex is not None: | |
49 | print(str(mf.threads_ex)) | |
50 | if mf.thread_info is not None: | |
51 | print(str(mf.thread_info)) | |
52 | if args.all or args.modules: | |
53 | if mf.modules is not None: | |
54 | print(str(mf.modules)) | |
55 | if mf.unloaded_modules is not None: | |
56 | print(str(mf.unloaded_modules)) | |
57 | if args.all or args.memory: | |
58 | if mf.memory_segments is not None: | |
59 | print(str(mf.memory_segments)) | |
60 | if mf.memory_segments_64 is not None: | |
61 | print(str(mf.memory_segments_64)) | |
62 | if mf.memory_info is not None: | |
63 | print(str(mf.memory_info)) | |
64 | if args.all or args.sysinfo: | |
65 | if mf.sysinfo is not None: | |
66 | print(str(mf.sysinfo)) | |
67 | if args.all or args.exception: | |
68 | if mf.exception is not None: | |
69 | print(str(mf.exception)) | |
70 | if args.all or args.comments: | |
71 | if mf.comment_a is not None: | |
72 | print(str(mf.comment_a)) | |
73 | if mf.comment_w is not None: | |
74 | print(str(mf.comment_w)) | |
75 | if args.all or args.handles: | |
76 | if mf.handles is not None: | |
77 | print(str(mf.handles)) | |
78 | if args.all or args.misc: | |
79 | if mf.misc_info is not None: | |
80 | print(str(mf.misc_info)) | |
81 | if args.all or args.header: | |
82 | print(str(mf.header)) | |
83 | ||
84 | if args.read_addr: | |
85 | buff_reader = reader.get_buffered_reader() | |
86 | await buff_reader.move(args.read_addr) | |
87 | data = await buff_reader.peek(args.read_size) | |
88 | print(hexdump(data, start = args.read_addr)) | |
89 | ||
90 | def main(): | |
91 | asyncio.run(run()) | |
92 | ||
93 | if __name__ == '__main__': | |
94 | main()⏎ |
0 | #!/usr/bin/env python3 | |
1 | # | |
2 | # Author: | |
3 | # Tamas Jos (@skelsec) | |
4 | # | |
5 | ||
6 | import io | |
7 | import sys | |
8 | import enum | |
9 | import struct | |
10 | import logging | |
11 | ||
12 | from minidump.header import MinidumpHeader | |
13 | from minidump.aminidumpreader import AMinidumpFileReader | |
14 | from minidump.streams import * | |
15 | from minidump.common_structs import * | |
16 | from minidump.constants import MINIDUMP_STREAM_TYPE | |
17 | from minidump.directory import MINIDUMP_DIRECTORY | |
18 | ||
19 | ||
20 | class AsyncFile: | |
21 | def __init__(self, filename): | |
22 | self.filename = filename | |
23 | self.fhandle = open(filename, 'rb') | |
24 | ||
25 | async def read(self, n = -1): | |
26 | return self.fhandle.read(n) | |
27 | ||
28 | async def seek(self, n, beg = 0): | |
29 | return self.fhandle.seek(n, beg) | |
30 | ||
31 | def tell(self): | |
32 | return self.fhandle.tell() | |
33 | ||
34 | class AMinidumpFile: | |
35 | def __init__(self): | |
36 | self.filename = None | |
37 | self.file_handle = None | |
38 | self.header = None | |
39 | self.directories = [] | |
40 | ||
41 | self.threads_ex = None | |
42 | self.threads = None | |
43 | self.modules = None | |
44 | self.memory_segments = None | |
45 | self.memory_segments_64 = None | |
46 | self.sysinfo = None | |
47 | self.comment_a = None | |
48 | self.comment_w = None | |
49 | self.exception = None | |
50 | self.handles = None | |
51 | self.unloaded_modules = None | |
52 | self.misc_info = None | |
53 | self.memory_info = None | |
54 | self.thread_info = None | |
55 | ||
56 | @staticmethod | |
57 | async def parse(filename): | |
58 | mf = AMinidumpFile() | |
59 | mf.filename = filename | |
60 | mf.file_handle = AsyncFile(filename) | |
61 | await mf._parse() | |
62 | return mf | |
63 | ||
64 | @staticmethod | |
65 | async def parse_external(file_handle, filename = ''): | |
66 | """ | |
67 | External file handle must be an object that exposes basic file IO functionality | |
68 | that you'd get by python's file buffer (read, seek, tell etc.) | |
69 | """ | |
70 | mf = AMinidumpFile() | |
71 | mf.filename = filename | |
72 | mf.file_handle = file_handle | |
73 | await mf._parse() | |
74 | return mf | |
75 | ||
76 | @staticmethod | |
77 | async def parse_bytes(data): | |
78 | return await AMinidumpFile.parse_buff(io.BytesIO(data)) | |
79 | ||
80 | @staticmethod | |
81 | def parse_buff(buffer): | |
82 | mf = AMinidumpFile() | |
83 | mf.file_handle = buffer | |
84 | mf._parse() | |
85 | return mf | |
86 | ||
87 | def get_reader(self): | |
88 | return AMinidumpFileReader(self) | |
89 | ||
90 | async def _parse(self): | |
91 | await self.__parse_header() | |
92 | await self.__parse_directories() | |
93 | ||
94 | async def __parse_header(self): | |
95 | self.header = await MinidumpHeader.aparse(self.file_handle) | |
96 | for i in range(0, self.header.NumberOfStreams): | |
97 | await self.file_handle.seek(self.header.StreamDirectoryRva + i * 12, 0 ) | |
98 | minidump_dir = await MINIDUMP_DIRECTORY.aparse(self.file_handle) | |
99 | ||
100 | if minidump_dir: | |
101 | self.directories.append(minidump_dir) | |
102 | else: | |
103 | await self.file_handle.seek(self.header.StreamDirectoryRva + i * 12, 0 ) | |
104 | t = await self.file_handle.read(4) | |
105 | user_stream_type_value = int.from_bytes(t, byteorder = 'little', signed = False) | |
106 | logging.debug('Found Unknown UserStream directory Type: %x' % (user_stream_type_value)) | |
107 | ||
108 | async def __parse_directories(self): | |
109 | ||
110 | for dir in self.directories: | |
111 | if dir.StreamType == MINIDUMP_STREAM_TYPE.UnusedStream: | |
112 | logging.debug('Found UnusedStream @%x Size: %d' % (dir.Location.Rva, dir.Location.DataSize)) | |
113 | continue # Reserved. Do not use this enumeration value. | |
114 | elif dir.StreamType == MINIDUMP_STREAM_TYPE.ReservedStream0: | |
115 | logging.debug('Found ReservedStream0 @%x Size: %d' % (dir.Location.Rva, dir.Location.DataSize)) | |
116 | continue # Reserved. Do not use this enumeration value. | |
117 | elif dir.StreamType == MINIDUMP_STREAM_TYPE.ReservedStream1: | |
118 | logging.debug('Found ReservedStream1 @%x Size: %d' % (dir.Location.Rva, dir.Location.DataSize)) | |
119 | continue # Reserved. Do not use this enumeration value. | |
120 | elif dir.StreamType == MINIDUMP_STREAM_TYPE.ThreadListStream: | |
121 | logging.debug('Found ThreadListStream @%x Size: %d' % (dir.Location.Rva, dir.Location.DataSize)) | |
122 | self.threads = await MinidumpThreadList.aparse(dir, self.file_handle) | |
123 | continue | |
124 | elif dir.StreamType == MINIDUMP_STREAM_TYPE.ModuleListStream: | |
125 | logging.debug('Found ModuleListStream @%x Size: %d' % (dir.Location.Rva, dir.Location.DataSize)) | |
126 | self.modules = await MinidumpModuleList.aparse(dir, self.file_handle) | |
127 | #logging.debug(str(modules_list)) | |
128 | continue | |
129 | elif dir.StreamType == MINIDUMP_STREAM_TYPE.MemoryListStream: | |
130 | logging.debug('Found MemoryListStream @%x Size: %d' % (dir.Location.Rva, dir.Location.DataSize)) | |
131 | self.memory_segments = await MinidumpMemoryList.aparse(dir, self.file_handle) | |
132 | #logging.debug(str(self.memory_segments)) | |
133 | continue | |
134 | elif dir.StreamType == MINIDUMP_STREAM_TYPE.SystemInfoStream: | |
135 | logging.debug('Found SystemInfoStream @%x Size: %d' % (dir.Location.Rva, dir.Location.DataSize)) | |
136 | self.sysinfo = await MinidumpSystemInfo.aparse(dir, self.file_handle) | |
137 | #logging.debug(str(self.sysinfo)) | |
138 | continue | |
139 | elif dir.StreamType == MINIDUMP_STREAM_TYPE.ThreadExListStream: | |
140 | logging.debug('Found ThreadExListStream @%x Size: %d' % (dir.Location.Rva, dir.Location.DataSize)) | |
141 | self.threads_ex = await MinidumpThreadExList.aparse(dir, self.file_handle) | |
142 | #logging.debug(str(self.threads_ex)) | |
143 | continue | |
144 | elif dir.StreamType == MINIDUMP_STREAM_TYPE.Memory64ListStream: | |
145 | logging.debug('Found Memory64ListStream @%x Size: %d' % (dir.Location.Rva, dir.Location.DataSize)) | |
146 | self.memory_segments_64 = await MinidumpMemory64List.aparse(dir, self.file_handle) | |
147 | #logging.debug(str(self.memory_segments_64)) | |
148 | continue | |
149 | elif dir.StreamType == MINIDUMP_STREAM_TYPE.CommentStreamA: | |
150 | logging.debug('Found CommentStreamA @%x Size: %d' % (dir.Location.Rva, dir.Location.DataSize)) | |
151 | self.comment_a = await CommentStreamA.aparse(dir, self.file_handle) | |
152 | #logging.debug(str(self.comment_a)) | |
153 | continue | |
154 | elif dir.StreamType == MINIDUMP_STREAM_TYPE.CommentStreamW: | |
155 | logging.debug('Found CommentStreamW @%x Size: %d' % (dir.Location.Rva, dir.Location.DataSize)) | |
156 | self.comment_w = await CommentStreamW.aparse(dir, self.file_handle) | |
157 | #logging.debug(str(self.comment_w)) | |
158 | continue | |
159 | elif dir.StreamType == MINIDUMP_STREAM_TYPE.ExceptionStream: | |
160 | logging.debug('Found ExceptionStream @%x Size: %d' % (dir.Location.Rva, dir.Location.DataSize)) | |
161 | self.exception = await ExceptionList.aparse(dir, self.file_handle) | |
162 | #logging.debug(str(self.comment_w)) | |
163 | continue | |
164 | elif dir.StreamType == MINIDUMP_STREAM_TYPE.HandleDataStream: | |
165 | logging.debug('Found HandleDataStream @%x Size: %d' % (dir.Location.Rva, dir.Location.DataSize)) | |
166 | self.handles = await MinidumpHandleDataStream.aparse(dir, self.file_handle) | |
167 | #logging.debug(str(self.handles)) | |
168 | continue | |
169 | ||
170 | elif dir.StreamType == MINIDUMP_STREAM_TYPE.FunctionTableStream: | |
171 | logging.debug('Found FunctionTableStream @%x Size: %d' % (dir.Location.Rva, dir.Location.DataSize)) | |
172 | logging.debug('Parsing of this stream type is not yet implemented!') | |
173 | continue | |
174 | ||
175 | elif dir.StreamType == MINIDUMP_STREAM_TYPE.UnloadedModuleListStream: | |
176 | logging.debug('Found UnloadedModuleListStream @%x Size: %d' % (dir.Location.Rva, dir.Location.DataSize)) | |
177 | self.unloaded_modules = await MinidumpUnloadedModuleList.aparse(dir, self.file_handle) | |
178 | #logging.debug(str(self.unloaded_modules)) | |
179 | continue | |
180 | elif dir.StreamType == MINIDUMP_STREAM_TYPE.MiscInfoStream: | |
181 | logging.debug('Found MiscInfoStream @%x Size: %d' % (dir.Location.Rva, dir.Location.DataSize)) | |
182 | self.misc_info = await MinidumpMiscInfo.aparse(dir, self.file_handle) | |
183 | #logging.debug(str(self.misc_info)) | |
184 | continue | |
185 | elif dir.StreamType == MINIDUMP_STREAM_TYPE.MemoryInfoListStream: | |
186 | logging.debug('Found MemoryInfoListStream @%x Size: %d' % (dir.Location.Rva, dir.Location.DataSize)) | |
187 | self.memory_info = await MinidumpMemoryInfoList.aparse(dir, self.file_handle) | |
188 | #logging.debug(str(self.memory_info)) | |
189 | continue | |
190 | elif dir.StreamType == MINIDUMP_STREAM_TYPE.ThreadInfoListStream: | |
191 | logging.debug('Found ThreadInfoListStream @%x Size: %d' % (dir.Location.Rva, dir.Location.DataSize)) | |
192 | self.thread_info = await MinidumpThreadInfoList.aparse(dir, self.file_handle) | |
193 | logging.debug(str(self.thread_info)) | |
194 | continue | |
195 | elif dir.StreamType == MINIDUMP_STREAM_TYPE.SystemMemoryInfoStream: | |
196 | logging.debug('Found SystemMemoryInfoStream @%x Size: %d' % (dir.Location.Rva, dir.Location.DataSize)) | |
197 | logging.debug('SystemMemoryInfoStream parsing is not implemented (Missing documentation)') | |
198 | continue | |
199 | ||
200 | elif dir.StreamType == MINIDUMP_STREAM_TYPE.JavaScriptDataStream: | |
201 | logging.debug('Found JavaScriptDataStream @%x Size: %d' % (dir.Location.Rva, dir.Location.DataSize)) | |
202 | logging.debug('JavaScriptDataStream parsing is not implemented (Missing documentation)') | |
203 | ||
204 | elif dir.StreamType == MINIDUMP_STREAM_TYPE.ProcessVmCountersStream: | |
205 | logging.debug('Found ProcessVmCountersStream @%x Size: %d' % (dir.Location.Rva, dir.Location.DataSize)) | |
206 | logging.debug('ProcessVmCountersStream parsing is not implemented (Missing documentation)') | |
207 | ||
208 | elif dir.StreamType == MINIDUMP_STREAM_TYPE.TokenStream: | |
209 | logging.debug('Found TokenStream @%x Size: %d' % (dir.Location.Rva, dir.Location.DataSize)) | |
210 | logging.debug('TokenStream parsing is not implemented (Missing documentation)') | |
211 | ||
212 | else: | |
213 | logging.debug('Found Unknown Stream! Type: %s @%x Size: %d' % (dir.StreamType.name, dir.Location.Rva, dir.Location.DataSize)) | |
214 | ||
215 | """ | |
216 | elif dir.StreamType == MINIDUMP_STREAM_TYPE.HandleOperationListStream: | |
217 | elif dir.StreamType == MINIDUMP_STREAM_TYPE.LastReservedStream: | |
218 | ||
219 | """ | |
220 | ||
221 | def __str__(self): | |
222 | t = '== Minidump File ==\n' | |
223 | t += str(self.header) | |
224 | t += str(self.sysinfo) | |
225 | for dir in self.directories: | |
226 | t += str(dir) + '\n' | |
227 | for mod in self.modules: | |
228 | t += str(mod) + '\n' | |
229 | if self.memory_segments is not None: | |
230 | for segment in self.memory_segments: | |
231 | t+= str(segment) + '\n' | |
232 | ||
233 | if self.memory_segments_64 is not None: | |
234 | for segment in self.memory_segments_64: | |
235 | t+= str(segment) + '\n' | |
236 | ||
237 | return t |
0 | #!/usr/bin/env python3 | |
1 | # | |
2 | # Author: | |
3 | # Tamas Jos (@skelsec) | |
4 | # | |
5 | import struct | |
6 | import ntpath | |
7 | from .common_structs import * | |
8 | from .streams.SystemInfoStream import PROCESSOR_ARCHITECTURE | |
9 | ||
10 | ||
11 | class VirtualSegment: | |
12 | def __init__(self, start, end, start_file_address): | |
13 | self.start = start | |
14 | self.end = end | |
15 | self.start_file_address = start_file_address | |
16 | ||
17 | self.data = None | |
18 | ||
19 | def inrange(self, start, end): | |
20 | return self.start <= start and end<= self.end | |
21 | ||
22 | class AMinidumpBufferedMemorySegment: | |
23 | def __init__(self, memory_segment, chunksize = 10*1024): | |
24 | self.start_address = memory_segment.start_virtual_address | |
25 | self.end_address = memory_segment.end_virtual_address | |
26 | self.total_size = memory_segment.end_virtual_address - memory_segment.start_virtual_address | |
27 | self.start_file_address = memory_segment.start_file_address | |
28 | self.chunksize = chunksize | |
29 | self.chunks = [] | |
30 | ||
31 | def inrange(self, position): | |
32 | return self.start_address <= position <= self.end_address | |
33 | ||
34 | def remaining_len(self, position): | |
35 | if not self.inrange(position): | |
36 | return None | |
37 | return self.end_address - position | |
38 | ||
39 | async def find(self, file_handle, pattern, startpos): | |
40 | data = await self.read(file_handle, 0, -1) | |
41 | return data.find(pattern, startpos) | |
42 | ||
43 | async def read(self, file_handle, start, end): | |
44 | if end is None: | |
45 | await file_handle.seek(self.start_file_address + start) | |
46 | return await file_handle.read(self.end_address - (self.start_file_address + start)) | |
47 | ||
48 | for chunk in self.chunks: | |
49 | if chunk.inrange(start, end): | |
50 | return chunk.data[start - chunk.start: end - chunk.start] | |
51 | ||
52 | if self.total_size <= 2*self.chunksize: | |
53 | chunksize = self.total_size | |
54 | vs = VirtualSegment(0, chunksize, self.start_file_address) | |
55 | await file_handle.seek(self.start_file_address) | |
56 | vs.data = await file_handle.read(chunksize) | |
57 | self.chunks.append(vs) | |
58 | return vs.data[start - vs.start: end - vs.start] | |
59 | ||
60 | chunksize = max((end-start), self.chunksize) | |
61 | if start + chunksize > self.end_address: | |
62 | chunksize = self.end_address - start | |
63 | ||
64 | vs = VirtualSegment(start, start+chunksize, self.start_file_address + start) | |
65 | await file_handle.seek(vs.start_file_address) | |
66 | vs.data = await file_handle.read(chunksize) | |
67 | self.chunks.append(vs) | |
68 | ||
69 | return vs.data[start - vs.start: end - vs.start] | |
70 | ||
71 | class AMinidumpBufferedReader: | |
72 | def __init__(self, reader, segment_chunk_size = 10*1024): | |
73 | self.reader = reader | |
74 | self.memory_segments = [] | |
75 | self.segment_chunk_size = segment_chunk_size | |
76 | ||
77 | self.current_segment = None | |
78 | self.current_position = None | |
79 | ||
80 | async def _select_segment(self, requested_position): | |
81 | """ | |
82 | ||
83 | """ | |
84 | # check if we have semgnet for requested address in cache | |
85 | for memory_segment in self.memory_segments: | |
86 | if memory_segment.inrange(requested_position): | |
87 | self.current_segment = memory_segment | |
88 | self.current_position = requested_position | |
89 | return | |
90 | ||
91 | # not in cache, check if it's present in memory space. if yes then create a new buffered memeory object, and copy data | |
92 | for memory_segment in self.reader.memory_segments: | |
93 | if memory_segment.inrange(requested_position): | |
94 | newsegment = AMinidumpBufferedMemorySegment(memory_segment, chunksize=self.segment_chunk_size) | |
95 | self.memory_segments.append(newsegment) | |
96 | self.current_segment = newsegment | |
97 | self.current_position = requested_position | |
98 | return | |
99 | ||
100 | raise Exception('Memory address 0x%08x is not in process memory space' % requested_position) | |
101 | ||
102 | async def seek(self, offset, whence = 0): | |
103 | """ | |
104 | Changes the current address to an offset of offset. The whence parameter controls from which position should we count the offsets. | |
105 | 0: beginning of the current memory segment | |
106 | 1: from current position | |
107 | 2: from the end of the current memory segment | |
108 | If you wish to move out from the segment, use the 'move' function | |
109 | """ | |
110 | if whence == 0: | |
111 | t = self.current_segment.start_address + offset | |
112 | elif whence == 1: | |
113 | t = self.current_position + offset | |
114 | elif whence == 2: | |
115 | t = self.current_segment.end_address - offset | |
116 | else: | |
117 | raise Exception('Seek function whence value must be between 0-2') | |
118 | ||
119 | if not self.current_segment.inrange(t): | |
120 | raise Exception('Seek would cross memory segment boundaries (use move)') | |
121 | ||
122 | self.current_position = t | |
123 | return | |
124 | ||
125 | async def move(self, address): | |
126 | """ | |
127 | Moves the buffer to a virtual address specified by address | |
128 | """ | |
129 | await self._select_segment(address) | |
130 | return | |
131 | ||
132 | async def align(self, alignment = None): | |
133 | """ | |
134 | Repositions the current reader to match architecture alignment | |
135 | """ | |
136 | if alignment is None: | |
137 | if self.reader.sysinfo.ProcessorArchitecture == PROCESSOR_ARCHITECTURE.AMD64: | |
138 | alignment = 8 | |
139 | else: | |
140 | alignment = 4 | |
141 | offset = self.current_position % alignment | |
142 | if offset == 0: | |
143 | return | |
144 | offset_to_aligned = (alignment - offset) % alignment | |
145 | await self.seek(offset_to_aligned, 1) | |
146 | return | |
147 | ||
148 | def tell(self): | |
149 | """ | |
150 | Returns the current virtual address | |
151 | """ | |
152 | return self.current_position | |
153 | ||
154 | async def peek(self, length): | |
155 | """ | |
156 | Returns up to length bytes from the current memory segment | |
157 | """ | |
158 | t = self.current_position + length | |
159 | if not self.current_segment.inrange(t): | |
160 | raise Exception('Would read over segment boundaries!') | |
161 | return await self.current_segment.read(self.reader.file_handle, self.current_position - self.current_segment.start_address , t - self.current_segment.start_address) | |
162 | ||
163 | async def read(self, size = -1): | |
164 | """ | |
165 | Returns data bytes of size size from the current segment. If size is -1 it returns all the remaining data bytes from memory segment | |
166 | """ | |
167 | if size < -1: | |
168 | raise Exception('You shouldnt be doing this') | |
169 | if size == -1: | |
170 | t = self.current_segment.remaining_len(self.current_position) | |
171 | if not t: | |
172 | return None | |
173 | ||
174 | old_new_pos = self.current_position | |
175 | self.current_position = self.current_segment.end_address | |
176 | return await self.current_segment.read(self.reader.file_handle, old_new_pos - self.current_segment.start_address, None) | |
177 | ||
178 | t = self.current_position + size | |
179 | if not self.current_segment.inrange(t): | |
180 | raise Exception('Would read over segment boundaries!') | |
181 | ||
182 | old_new_pos = self.current_position | |
183 | self.current_position = t | |
184 | return await self.current_segment.read(self.reader.file_handle, old_new_pos - self.current_segment.start_address, t - self.current_segment.start_address) | |
185 | ||
186 | async def read_int(self): | |
187 | """ | |
188 | Reads an integer. The size depends on the architecture. | |
189 | Reads a 4 byte small-endian singed int on 32 bit arch | |
190 | Reads an 8 byte small-endian singed int on 64 bit arch | |
191 | """ | |
192 | if self.reader.sysinfo.ProcessorArchitecture == PROCESSOR_ARCHITECTURE.AMD64: | |
193 | t = await self.read(8) | |
194 | return int.from_bytes(t, byteorder = 'little', signed = True) | |
195 | else: | |
196 | t = t = await self.read(4) | |
197 | return int.from_bytes(t, byteorder = 'little', signed = True) | |
198 | ||
199 | async def read_uint(self): | |
200 | """ | |
201 | Reads an integer. The size depends on the architecture. | |
202 | Reads a 4 byte small-endian unsinged int on 32 bit arch | |
203 | Reads an 8 byte small-endian unsinged int on 64 bit arch | |
204 | """ | |
205 | if self.reader.sysinfo.ProcessorArchitecture == PROCESSOR_ARCHITECTURE.AMD64: | |
206 | t = await self.read(8) | |
207 | return int.from_bytes(t, byteorder = 'little', signed = False) | |
208 | else: | |
209 | t = await self.read(4) | |
210 | return int.from_bytes(t, byteorder = 'little', signed = False) | |
211 | ||
212 | async def find(self, pattern): | |
213 | """ | |
214 | Searches for a pattern in the current memory segment | |
215 | """ | |
216 | pos = await self.current_segment.find(self.reader.file_handle, pattern) | |
217 | if pos == -1: | |
218 | return -1 | |
219 | return pos + self.current_position | |
220 | ||
221 | async def find_all(self, pattern): | |
222 | """ | |
223 | Searches for all occurrences of a pattern in the current memory segment, returns all occurrences as a list | |
224 | """ | |
225 | pos = [] | |
226 | last_found = -1 | |
227 | while True: | |
228 | last_found = await self.current_segment.find(self.reader.file_handle, pattern, last_found + 1) | |
229 | if last_found == -1: | |
230 | break | |
231 | pos.append(last_found + self.current_segment.start_address) | |
232 | ||
233 | return pos | |
234 | ||
235 | async def find_global(self, pattern): | |
236 | """ | |
237 | Searches for the pattern in the whole process memory space and returns the first occurrence. | |
238 | This is exhaustive! | |
239 | """ | |
240 | pos_s = await self.reader.search(pattern) | |
241 | if len(pos_s) == 0: | |
242 | return -1 | |
243 | ||
244 | return pos_s[0] | |
245 | ||
246 | async def find_all_global(self, pattern): | |
247 | """ | |
248 | Searches for the pattern in the whole process memory space and returns a list of addresses where the pattern begins. | |
249 | This is exhaustive! | |
250 | """ | |
251 | return await self.reader.search(pattern) | |
252 | ||
253 | async def get_ptr(self, pos): | |
254 | await self.move(pos) | |
255 | return await self.read_uint() | |
256 | #raw_data = self.read(pos, self.sizeof_ptr) | |
257 | #return struct.unpack(self.unpack_ptr, raw_data)[0] | |
258 | ||
259 | async def get_ptr_with_offset(self, pos): | |
260 | if self.reader.sysinfo.ProcessorArchitecture == PROCESSOR_ARCHITECTURE.AMD64: | |
261 | await self.move(pos) | |
262 | t = await self.read(4) | |
263 | ptr = int.from_bytes(t, byteorder = 'little', signed = True) | |
264 | return pos + 4 + ptr | |
265 | else: | |
266 | await self.move(pos) | |
267 | return await self.read_uint() | |
268 | ||
269 | async def find_in_module(self, module_name, pattern, find_first = False, reverse_order = False): | |
270 | t = await self.reader.search_module(module_name, pattern, find_first = find_first, reverse_order = reverse_order,chunksize = self.segment_chunk_size) | |
271 | return t | |
272 | ||
273 | ||
274 | ||
275 | ||
276 | class AMinidumpFileReader: | |
277 | def __init__(self, minidumpfile): | |
278 | self.modules = minidumpfile.modules.modules | |
279 | self.sysinfo = minidumpfile.sysinfo | |
280 | ||
281 | if minidumpfile.memory_segments_64: | |
282 | self.memory_segments = minidumpfile.memory_segments_64.memory_segments | |
283 | self.is_fulldump = True | |
284 | ||
285 | else: | |
286 | self.memory_segments = minidumpfile.memory_segments.memory_segments | |
287 | self.is_fulldump = False | |
288 | ||
289 | self.filename = minidumpfile.filename | |
290 | self.file_handle = minidumpfile.file_handle | |
291 | ||
292 | #reader params | |
293 | self.sizeof_long = 4 | |
294 | self.unpack_long = '<L' | |
295 | if minidumpfile.sysinfo.ProcessorArchitecture in [PROCESSOR_ARCHITECTURE.AMD64, PROCESSOR_ARCHITECTURE.AARCH64]: | |
296 | self.sizeof_ptr = 8 | |
297 | self.unpack_ptr = '<Q' | |
298 | elif self.sysinfo.ProcessorArchitecture in [PROCESSOR_ARCHITECTURE.INTEL, | |
299 | PROCESSOR_ARCHITECTURE.ARM]: | |
300 | self.sizeof_ptr = 4 | |
301 | self.unpack_ptr = '<L' | |
302 | else: | |
303 | raise Exception('Unknown processor architecture %s! Please fix and submit PR!' % self.sysinfo.ProcessorArchitecture) | |
304 | ||
305 | def get_buffered_reader(self, segment_chunk_size = 10*1024): | |
306 | return AMinidumpBufferedReader(self, segment_chunk_size = segment_chunk_size) | |
307 | ||
308 | def get_module_by_name(self, module_name): | |
309 | for mod in self.modules: | |
310 | if ntpath.basename(mod.name).find(module_name) != -1: | |
311 | return mod | |
312 | return None | |
313 | ||
314 | async def search_module(self, module_name, pattern, find_first = False, reverse_order = False, chunksize = 10*1024): | |
315 | mod = self.get_module_by_name(module_name) | |
316 | if mod is None: | |
317 | raise Exception('Could not find module! %s' % module_name) | |
318 | needles = [] | |
319 | for ms in self.memory_segments: | |
320 | if mod.baseaddress <= ms.start_virtual_address < mod.endaddress: | |
321 | needles += await ms.asearch(pattern, self.file_handle, find_first = find_first, chunksize = chunksize) | |
322 | if len(needles) > 0 and find_first is True: | |
323 | return needles | |
324 | ||
325 | return needles | |
326 | ||
327 | async def search(self, pattern, find_first = False, chunksize = 10*1024): | |
328 | t = [] | |
329 | for ms in self.memory_segments: | |
330 | t += await ms.asearch(pattern, self.file_handle, find_first = find_first, chunksize = chunksize) | |
331 | ||
332 | return t | |
333 | ||
334 | async def read(self, virt_addr, size): | |
335 | for segment in self.memory_segments: | |
336 | if segment.inrange(virt_addr): | |
337 | return await segment.aread(virt_addr, size, self.file_handle) | |
338 | raise Exception('Address not in memory range! %s' % hex(virt_addr)) | |
339 |
18 | 18 | mld.DataSize = int.from_bytes(buff.read(4), byteorder = 'little', signed = False) |
19 | 19 | mld.Rva = int.from_bytes(buff.read(4), byteorder = 'little', signed = False) |
20 | 20 | return mld |
21 | ||
22 | @staticmethod | |
23 | async def aparse(buff): | |
24 | mld = MINIDUMP_LOCATION_DESCRIPTOR() | |
25 | t = await buff.read(4) | |
26 | mld.DataSize = int.from_bytes(t, byteorder = 'little', signed = False) | |
27 | t = await buff.read(4) | |
28 | mld.Rva = int.from_bytes(t, byteorder = 'little', signed = False) | |
29 | return mld | |
21 | 30 | |
22 | 31 | def __str__(self): |
23 | 32 | t = 'Size: %s File offset: %s' % (self.DataSize, self.Rva) |
58 | 67 | ms.Length = int.from_bytes(buff.read(4), byteorder = 'little', signed = False) |
59 | 68 | ms.Buffer = buff.read(ms.Length) |
60 | 69 | return ms |
70 | ||
71 | @staticmethod | |
72 | async def aparse(buff): | |
73 | ms = MINIDUMP_STRING() | |
74 | t = await buff.read(4) | |
75 | ms.Length = int.from_bytes(t, byteorder = 'little', signed = False) | |
76 | ms.Buffer = await buff.read(ms.Length) | |
77 | return ms | |
61 | 78 | |
62 | 79 | @staticmethod |
63 | 80 | def get_from_rva(rva, buff): |
65 | 82 | buff.seek(rva, 0) |
66 | 83 | ms = MINIDUMP_STRING.parse(buff) |
67 | 84 | buff.seek(pos, 0) |
85 | return ms.Buffer.decode('utf-16-le') | |
86 | ||
87 | @staticmethod | |
88 | async def aget_from_rva(rva, buff): | |
89 | pos = buff.tell() | |
90 | await buff.seek(rva, 0) | |
91 | ms = await MINIDUMP_STRING.aparse(buff) | |
92 | await buff.seek(pos, 0) | |
68 | 93 | return ms.Buffer.decode('utf-16-le') |
69 | 94 | |
70 | 95 | class MinidumpMemorySegment: |
88 | 113 | return mms |
89 | 114 | |
90 | 115 | @staticmethod |
91 | def parse_full(memory_decriptor, buff, rva): | |
116 | def parse_full(memory_decriptor, rva): | |
92 | 117 | mms = MinidumpMemorySegment() |
93 | 118 | mms.start_virtual_address = memory_decriptor.StartOfMemoryRange |
94 | 119 | mms.size = memory_decriptor.DataSize |
95 | 120 | mms.start_file_address = rva |
96 | 121 | mms.end_virtual_address = mms.start_virtual_address + mms.size |
97 | return mms | |
98 | ||
122 | return mms | |
99 | 123 | |
100 | 124 | def inrange(self, virt_addr): |
101 | 125 | if virt_addr >= self.start_virtual_address and virt_addr < self.end_virtual_address: |
102 | 126 | return True |
103 | 127 | return False |
128 | ||
104 | 129 | def read(self, virtual_address, size, file_handler): |
105 | 130 | if virtual_address > self.end_virtual_address or virtual_address < self.start_virtual_address: |
106 | 131 | raise Exception('Reading from wrong segment!') |
114 | 139 | data = file_handler.read(size) |
115 | 140 | file_handler.seek(pos, 0) |
116 | 141 | return data |
117 | ||
118 | def search(self, pattern, file_handler): | |
142 | ||
143 | async def aread(self, virtual_address, size, file_handler): | |
144 | if virtual_address > self.end_virtual_address or virtual_address < self.start_virtual_address: | |
145 | raise Exception('Reading from wrong segment!') | |
146 | ||
147 | if virtual_address+size > self.end_virtual_address: | |
148 | raise Exception('Read would cross boundaries!') | |
149 | ||
150 | pos = file_handler.tell() | |
151 | offset = virtual_address - self.start_virtual_address | |
152 | await file_handler.seek(self.start_file_address + offset, 0) | |
153 | data = await file_handler.read(size) | |
154 | await file_handler.seek(pos, 0) | |
155 | return data | |
156 | ||
157 | def search(self, pattern, file_handler, find_first = False, chunksize = 50*1024): | |
119 | 158 | if len(pattern) > self.size: |
120 | 159 | return [] |
121 | 160 | pos = file_handler.tell() |
122 | 161 | file_handler.seek(self.start_file_address, 0) |
123 | data = file_handler.read(self.size) | |
162 | fl = [] | |
163 | if find_first is True: | |
164 | chunksize = min(chunksize, self.size) | |
165 | data = b'' | |
166 | i = 0 | |
167 | while len(data) < self.size: | |
168 | i += 1 | |
169 | if chunksize > (self.size - len(data)): | |
170 | chunksize = (self.size - len(data)) | |
171 | data += file_handler.read(chunksize) | |
172 | marker = data.find(pattern) | |
173 | if marker != -1: | |
174 | #print('FOUND! size: %s i: %s read: %s perc: %s' % (self.size, i, i*chunksize, 100*((i*chunksize)/self.size))) | |
175 | file_handler.seek(pos, 0) | |
176 | return [self.start_virtual_address + marker] | |
177 | ||
178 | ||
179 | #print('NOTFOUND! size: %s i: %s read: %s perc %s' % (self.size, i, len(data), 100*(len(data)/self.size) )) | |
180 | ||
181 | else: | |
182 | data = file_handler.read(self.size) | |
183 | file_handler.seek(pos, 0) | |
184 | ||
185 | offset = 0 | |
186 | while len(data) > len(pattern): | |
187 | marker = data.find(pattern) | |
188 | if marker == -1: | |
189 | return fl | |
190 | fl.append(marker + offset + self.start_virtual_address) | |
191 | data = data[marker+1:] | |
192 | offset = marker + 1 | |
193 | if find_first is True: | |
194 | return fl | |
195 | ||
124 | 196 | file_handler.seek(pos, 0) |
197 | return fl | |
198 | ||
199 | async def asearch(self, pattern, file_handler, find_first = False, chunksize = 50*1024): | |
200 | if len(pattern) > self.size: | |
201 | return [] | |
202 | pos = file_handler.tell() | |
203 | await file_handler.seek(self.start_file_address, 0) | |
125 | 204 | fl = [] |
126 | offset = 0 | |
127 | while len(data) > len(pattern): | |
128 | marker = data.find(pattern) | |
129 | if marker == -1: | |
130 | return fl | |
131 | fl.append(marker + offset + self.start_virtual_address) | |
132 | data = data[marker+1:] | |
133 | offset = marker + 1 | |
134 | ||
205 | ||
206 | if find_first is True: | |
207 | chunksize = min(chunksize, self.size) | |
208 | data = b'' | |
209 | i = 0 | |
210 | while len(data) < self.size: | |
211 | i += 1 | |
212 | if chunksize > (self.size - len(data)): | |
213 | chunksize = (self.size - len(data)) | |
214 | data += await file_handler.read(chunksize) | |
215 | marker = data.find(pattern) | |
216 | if marker != -1: | |
217 | #print('FOUND! size: %s i: %s read: %s perc: %s' % (self.size, i, i*chunksize, 100*((i*chunksize)/self.size))) | |
218 | await file_handler.seek(pos, 0) | |
219 | return [self.start_virtual_address + marker] | |
220 | ||
221 | ||
222 | #print('NOTFOUND! size: %s i: %s read: %s perc %s' % (self.size, i, len(data), 100*(len(data)/self.size) )) | |
223 | ||
224 | else: | |
225 | offset = 0 | |
226 | data = await file_handler.read(self.size) | |
227 | await file_handler.seek(pos, 0) | |
228 | while len(data) > len(pattern): | |
229 | marker = data.find(pattern) | |
230 | if marker == -1: | |
231 | return fl | |
232 | fl.append(marker + offset + self.start_virtual_address) | |
233 | data = data[marker+1:] | |
234 | offset = marker + 1 | |
235 | if find_first is True: | |
236 | return fl | |
237 | ||
238 | await file_handler.seek(pos, 0) | |
135 | 239 | return fl |
240 | ||
136 | 241 | |
137 | 242 | @staticmethod |
138 | 243 | def get_header(): |
33 | 33 | md.Location = MINIDUMP_LOCATION_DESCRIPTOR.parse(buff) |
34 | 34 | return md |
35 | 35 | |
36 | @staticmethod | |
37 | async def aparse(buff): | |
38 | ||
39 | t = await buff.read(4) | |
40 | raw_stream_type_value = int.from_bytes(t, byteorder = 'little', signed = False) | |
41 | ||
42 | # StreamType value that are over 0xffff are considered MINIDUMP_USER_STREAM streams | |
43 | # and their format depends on the client used to create the minidump. | |
44 | # As per the documentation, this stream should be ignored : https://docs.microsoft.com/en-us/windows/win32/api/minidumpapiset/ne-minidumpapiset-minidumminidump_dirp_stream_type#remarks | |
45 | is_user_stream = raw_stream_type_value > MINIDUMP_STREAM_TYPE.LastReservedStream.value | |
46 | is_stream_supported = raw_stream_type_value in MINIDUMP_STREAM_TYPE._value2member_map_ | |
47 | if is_user_stream and not is_stream_supported: | |
48 | return None | |
49 | ||
50 | md = MINIDUMP_DIRECTORY() | |
51 | md.StreamType = MINIDUMP_STREAM_TYPE(raw_stream_type_value) | |
52 | md.Location = await MINIDUMP_LOCATION_DESCRIPTOR.aparse(buff) | |
53 | return md | |
54 | ||
36 | 55 | def __str__(self): |
37 | 56 | t = 'StreamType: %s %s' % (self.StreamType, self.Location) |
38 | 57 | return t⏎ |
0 | 0 | from minidump.constants import MINIDUMP_TYPE |
1 | 1 | from minidump.exceptions import MinidumpHeaderFlagsException, MinidumpHeaderSignatureMismatchException |
2 | import io | |
2 | 3 | |
3 | 4 | # https://msdn.microsoft.com/en-us/library/windows/desktop/ms680378(v=vs.85).aspx |
4 | 5 | class MinidumpHeader: |
46 | 47 | |
47 | 48 | return mh |
48 | 49 | |
50 | @staticmethod | |
51 | async def aparse(abuff): | |
52 | adata = await abuff.read(32) | |
53 | buff = io.BytesIO(adata) | |
54 | return MinidumpHeader.parse(buff) | |
55 | ||
49 | 56 | def __str__(self): |
50 | 57 | t = '== MinidumpHeader ==\n' |
51 | 58 | t+= 'Signature: %s\n' % self.Signature |
4 | 4 | # |
5 | 5 | import struct |
6 | 6 | import ntpath |
7 | from .common_structs import * | |
7 | from .common_structs import * | |
8 | 8 | from .streams.SystemInfoStream import PROCESSOR_ARCHITECTURE |
9 | 9 | |
10 | class VirtualSegment: | |
11 | def __init__(self, start, end, start_file_address): | |
12 | self.start = start | |
13 | self.end = end | |
14 | self.start_file_address = start_file_address | |
15 | ||
16 | ||
17 | ||
18 | self.data = None | |
19 | ||
20 | def inrange(self, start, end): | |
21 | return self.start <= start and end<= self.end | |
22 | ||
10 | 23 | class MinidumpBufferedMemorySegment: |
11 | def __init__(self, memory_segment, file_handle): | |
24 | def __init__(self, memory_segment, file_handle, chunksize = 10*1024): | |
12 | 25 | self.start_address = memory_segment.start_virtual_address |
13 | 26 | self.end_address = memory_segment.end_virtual_address |
14 | ||
15 | file_handle.seek(memory_segment.start_file_address) | |
16 | self.data = file_handle.read(memory_segment.size) | |
17 | ||
27 | self.total_size = memory_segment.end_virtual_address - memory_segment.start_virtual_address | |
28 | self.start_file_address = memory_segment.start_file_address | |
29 | self.chunksize = chunksize | |
30 | self.chunks = [] | |
31 | ||
18 | 32 | def inrange(self, position): |
19 | 33 | return self.start_address <= position <= self.end_address |
20 | ||
34 | ||
21 | 35 | def remaining_len(self, position): |
22 | 36 | if not self.inrange(position): |
23 | 37 | return None |
24 | 38 | return self.end_address - position |
25 | ||
39 | ||
40 | def find(self, file_handle, pattern, startpos): | |
41 | data = self.read(file_handle, 0, -1) | |
42 | return data.find(pattern, startpos) | |
43 | ||
44 | def read(self, file_handle, start, end): | |
45 | if end is None: | |
46 | file_handle.seek(self.start_file_address + start) | |
47 | return file_handle.read(self.end_address - (self.start_file_address + start)) | |
48 | ||
49 | for chunk in self.chunks: | |
50 | if chunk.inrange(start, end): | |
51 | return chunk.data[start - chunk.start: end - chunk.start] | |
52 | ||
53 | if self.total_size <= 2*self.chunksize: | |
54 | chunksize = self.total_size | |
55 | vs = VirtualSegment(0, chunksize, self.start_file_address) | |
56 | file_handle.seek(self.start_file_address) | |
57 | vs.data = file_handle.read(chunksize) | |
58 | self.chunks.append(vs) | |
59 | return vs.data[start - vs.start: end - vs.start] | |
60 | ||
61 | chunksize = max((end-start), self.chunksize) | |
62 | if start + chunksize > self.end_address: | |
63 | chunksize = self.end_address - start | |
64 | ||
65 | vs = VirtualSegment(start, start+chunksize, self.start_file_address + start) | |
66 | file_handle.seek(vs.start_file_address) | |
67 | vs.data = file_handle.read(chunksize) | |
68 | self.chunks.append(vs) | |
69 | ||
70 | return vs.data[start - vs.start: end - vs.start] | |
71 | ||
72 | ||
73 | ||
26 | 74 | class MinidumpBufferedReader: |
27 | def __init__(self, reader): | |
75 | def __init__(self, reader, segment_chunk_size = 10*1024): | |
28 | 76 | self.reader = reader |
77 | self.segment_chunk_size = segment_chunk_size | |
29 | 78 | self.memory_segments = [] |
30 | ||
79 | ||
31 | 80 | self.current_segment = None |
32 | 81 | self.current_position = None |
33 | ||
82 | ||
34 | 83 | def _select_segment(self, requested_position): |
35 | 84 | """ |
36 | ||
85 | ||
37 | 86 | """ |
38 | 87 | # check if we have semgnet for requested address in cache |
39 | 88 | for memory_segment in self.memory_segments: |
41 | 90 | self.current_segment = memory_segment |
42 | 91 | self.current_position = requested_position |
43 | 92 | return |
44 | ||
93 | ||
45 | 94 | # not in cache, check if it's present in memory space. if yes then create a new buffered memeory object, and copy data |
46 | 95 | for memory_segment in self.reader.memory_segments: |
47 | 96 | if memory_segment.inrange(requested_position): |
48 | newsegment = MinidumpBufferedMemorySegment(memory_segment, self.reader.file_handle) | |
97 | newsegment = MinidumpBufferedMemorySegment(memory_segment, self.reader.file_handle, chunksize=self.segment_chunk_size) | |
49 | 98 | self.memory_segments.append(newsegment) |
50 | 99 | self.current_segment = newsegment |
51 | 100 | self.current_position = requested_position |
52 | 101 | return |
53 | ||
102 | ||
54 | 103 | raise Exception('Memory address 0x%08x is not in process memory space' % requested_position) |
55 | ||
104 | ||
56 | 105 | def seek(self, offset, whence = 0): |
57 | 106 | """ |
58 | 107 | Changes the current address to an offset of offset. The whence parameter controls from which position should we count the offsets. |
69 | 118 | t = self.current_segment.end_address - offset |
70 | 119 | else: |
71 | 120 | raise Exception('Seek function whence value must be between 0-2') |
72 | ||
121 | ||
73 | 122 | if not self.current_segment.inrange(t): |
74 | 123 | raise Exception('Seek would cross memory segment boundaries (use move)') |
75 | ||
124 | ||
76 | 125 | self.current_position = t |
77 | 126 | return |
78 | ||
127 | ||
79 | 128 | def move(self, address): |
80 | 129 | """ |
81 | 130 | Moves the buffer to a virtual address specified by address |
82 | 131 | """ |
83 | 132 | self._select_segment(address) |
84 | 133 | return |
85 | ||
134 | ||
86 | 135 | def align(self, alignment = None): |
87 | 136 | """ |
88 | 137 | Repositions the current reader to match architecture alignment |
98 | 147 | offset_to_aligned = (alignment - offset) % alignment |
99 | 148 | self.seek(offset_to_aligned, 1) |
100 | 149 | return |
101 | ||
150 | ||
102 | 151 | def tell(self): |
103 | 152 | """ |
104 | 153 | Returns the current virtual address |
105 | 154 | """ |
106 | 155 | return self.current_position |
107 | ||
156 | ||
108 | 157 | def peek(self, length): |
109 | 158 | """ |
110 | 159 | Returns up to length bytes from the current memory segment |
112 | 161 | t = self.current_position + length |
113 | 162 | if not self.current_segment.inrange(t): |
114 | 163 | raise Exception('Would read over segment boundaries!') |
115 | return self.current_segment.data[self.current_position - self.current_segment.start_address :t - self.current_segment.start_address] | |
116 | ||
164 | return self.current_segment.read(self.reader.file_handle, self.current_position - self.current_segment.start_address , t - self.current_segment.start_address) | |
165 | ||
117 | 166 | def read(self, size = -1): |
118 | 167 | """ |
119 | 168 | Returns data bytes of size size from the current segment. If size is -1 it returns all the remaining data bytes from memory segment |
124 | 173 | t = self.current_segment.remaining_len(self.current_position) |
125 | 174 | if not t: |
126 | 175 | return None |
127 | ||
176 | ||
128 | 177 | old_new_pos = self.current_position |
129 | 178 | self.current_position = self.current_segment.end_address |
130 | return self.current_segment.data[old_new_pos - self.current_segment.start_address:] | |
131 | ||
179 | return self.current_segment.read(self.reader.file_handle, old_new_pos - self.current_segment.start_address, None) | |
180 | ||
132 | 181 | t = self.current_position + size |
133 | 182 | if not self.current_segment.inrange(t): |
134 | 183 | raise Exception('Would read over segment boundaries!') |
135 | ||
184 | ||
136 | 185 | old_new_pos = self.current_position |
137 | self.current_position = t | |
138 | return self.current_segment.data[old_new_pos - self.current_segment.start_address :t - self.current_segment.start_address] | |
139 | ||
186 | self.current_position = t | |
187 | return self.current_segment.read(self.reader.file_handle, old_new_pos - self.current_segment.start_address, t - self.current_segment.start_address) | |
188 | ||
140 | 189 | def read_int(self): |
141 | 190 | """ |
142 | Reads an integer. The size depends on the architecture. | |
191 | Reads an integer. The size depends on the architecture. | |
143 | 192 | Reads a 4 byte small-endian singed int on 32 bit arch |
144 | 193 | Reads an 8 byte small-endian singed int on 64 bit arch |
145 | 194 | """ |
147 | 196 | return int.from_bytes(self.read(8), byteorder = 'little', signed = True) |
148 | 197 | else: |
149 | 198 | return int.from_bytes(self.read(4), byteorder = 'little', signed = True) |
150 | ||
199 | ||
151 | 200 | def read_uint(self): |
152 | 201 | """ |
153 | Reads an integer. The size depends on the architecture. | |
202 | Reads an integer. The size depends on the architecture. | |
154 | 203 | Reads a 4 byte small-endian unsinged int on 32 bit arch |
155 | 204 | Reads an 8 byte small-endian unsinged int on 64 bit arch |
156 | 205 | """ |
158 | 207 | return int.from_bytes(self.read(8), byteorder = 'little', signed = False) |
159 | 208 | else: |
160 | 209 | return int.from_bytes(self.read(4), byteorder = 'little', signed = False) |
161 | ||
210 | ||
162 | 211 | def find(self, pattern): |
163 | 212 | """ |
164 | 213 | Searches for a pattern in the current memory segment |
165 | 214 | """ |
166 | pos = self.current_segment.data.find(pattern) | |
215 | pos = self.current_segment.find(self.reader.file_handle, pattern) | |
167 | 216 | if pos == -1: |
168 | 217 | return -1 |
169 | 218 | return pos + self.current_position |
170 | ||
219 | ||
171 | 220 | def find_all(self, pattern): |
172 | 221 | """ |
173 | 222 | Searches for all occurrences of a pattern in the current memory segment, returns all occurrences as a list |
175 | 224 | pos = [] |
176 | 225 | last_found = -1 |
177 | 226 | while True: |
178 | last_found = self.current_segment.data.find(pattern, last_found + 1) | |
227 | last_found = self.current_segment.find(self.reader.file_handle, pattern, last_found + 1) | |
179 | 228 | if last_found == -1: |
180 | 229 | break |
181 | 230 | pos.append(last_found + self.current_segment.start_address) |
182 | ||
231 | ||
183 | 232 | return pos |
184 | ||
233 | ||
185 | 234 | def find_global(self, pattern): |
186 | 235 | """ |
187 | 236 | Searches for the pattern in the whole process memory space and returns the first occurrence. |
190 | 239 | pos_s = self.reader.search(pattern) |
191 | 240 | if len(pos_s) == 0: |
192 | 241 | return -1 |
193 | ||
242 | ||
194 | 243 | return pos_s[0] |
195 | ||
244 | ||
196 | 245 | def find_all_global(self, pattern): |
197 | 246 | """ |
198 | 247 | Searches for the pattern in the whole process memory space and returns a list of addresses where the pattern begins. |
199 | 248 | This is exhaustive! |
200 | 249 | """ |
201 | 250 | return self.reader.search(pattern) |
202 | ||
251 | ||
203 | 252 | def get_ptr(self, pos): |
204 | 253 | self.move(pos) |
205 | 254 | return self.read_uint() |
206 | 255 | #raw_data = self.read(pos, self.sizeof_ptr) |
207 | 256 | #return struct.unpack(self.unpack_ptr, raw_data)[0] |
208 | ||
257 | ||
209 | 258 | def get_ptr_with_offset(self, pos): |
210 | 259 | if self.reader.sysinfo.ProcessorArchitecture == PROCESSOR_ARCHITECTURE.AMD64: |
211 | 260 | self.move(pos) |
214 | 263 | else: |
215 | 264 | self.move(pos) |
216 | 265 | return self.read_uint() |
217 | ||
218 | def find_in_module(self, module_name, pattern): | |
219 | t = self.reader.search_module(module_name, pattern) | |
266 | ||
267 | def find_in_module(self, module_name, pattern, find_first = False, reverse_order = False): | |
268 | t = self.reader.search_module(module_name, pattern, find_first = find_first, reverse_order = reverse_order, chunksize = self.segment_chunk_size) | |
220 | 269 | return t |
221 | ||
222 | ||
223 | ||
224 | ||
270 | ||
271 | ||
272 | ||
273 | ||
225 | 274 | class MinidumpFileReader: |
226 | 275 | def __init__(self, minidumpfile): |
227 | 276 | self.modules = minidumpfile.modules.modules |
277 | self.unloaded_modules = [] | |
278 | if minidumpfile.unloaded_modules is not None: | |
279 | self.unloaded_modules = minidumpfile.unloaded_modules.modules | |
280 | ||
228 | 281 | self.sysinfo = minidumpfile.sysinfo |
229 | ||
282 | ||
230 | 283 | if minidumpfile.memory_segments_64: |
231 | 284 | self.memory_segments = minidumpfile.memory_segments_64.memory_segments |
232 | 285 | self.is_fulldump = True |
233 | ||
286 | ||
234 | 287 | else: |
235 | 288 | self.memory_segments = minidumpfile.memory_segments.memory_segments |
236 | 289 | self.is_fulldump = False |
237 | ||
290 | ||
238 | 291 | self.filename = minidumpfile.filename |
239 | 292 | self.file_handle = minidumpfile.file_handle |
240 | ||
293 | ||
241 | 294 | #reader params |
242 | 295 | self.sizeof_long = 4 |
243 | 296 | self.unpack_long = '<L' |
244 | if minidumpfile.sysinfo.ProcessorArchitecture == PROCESSOR_ARCHITECTURE.AMD64: | |
297 | if minidumpfile.sysinfo.ProcessorArchitecture in [PROCESSOR_ARCHITECTURE.AMD64, PROCESSOR_ARCHITECTURE.AARCH64]: | |
245 | 298 | self.sizeof_ptr = 8 |
246 | 299 | self.unpack_ptr = '<Q' |
247 | elif self.sysinfo.ProcessorArchitecture == PROCESSOR_ARCHITECTURE.INTEL: | |
300 | elif self.sysinfo.ProcessorArchitecture in [PROCESSOR_ARCHITECTURE.INTEL, | |
301 | PROCESSOR_ARCHITECTURE.ARM]: | |
248 | 302 | self.sizeof_ptr = 4 |
249 | 303 | self.unpack_ptr = '<L' |
250 | 304 | else: |
251 | 305 | raise Exception('Unknown processor architecture %s! Please fix and submit PR!' % self.sysinfo.ProcessorArchitecture) |
252 | ||
253 | def get_buffered_reader(self): | |
254 | return MinidumpBufferedReader(self) | |
255 | ||
306 | ||
307 | def get_buffered_reader(self, segment_chunk_size = 10*1024): | |
308 | return MinidumpBufferedReader(self, segment_chunk_size = segment_chunk_size) | |
309 | ||
256 | 310 | def get_module_by_name(self, module_name): |
257 | 311 | for mod in self.modules: |
258 | 312 | if ntpath.basename(mod.name).find(module_name) != -1: |
259 | 313 | return mod |
260 | 314 | return None |
261 | ||
262 | def search_module(self, module_name, pattern): | |
315 | ||
316 | def get_unloaded_by_name(self, module_name): | |
317 | for mod in self.unloaded_modules: | |
318 | if ntpath.basename(mod.name).find(module_name) != -1: | |
319 | return mod | |
320 | return None | |
321 | ||
322 | def search_module(self, module_name, pattern, find_first = False, reverse_order = False, chunksize = 10*1024): | |
263 | 323 | mod = self.get_module_by_name(module_name) |
264 | 324 | if mod is None: |
265 | raise Exception('Could not find module! %s' % module_name) | |
325 | mod = self.get_unloaded_by_name(module_name) | |
326 | if mod is None: | |
327 | raise Exception('Could not find module! %s' % module_name) | |
328 | ||
329 | needles = [] | |
330 | for ms in self.memory_segments: | |
331 | if mod.baseaddress <= ms.start_virtual_address < mod.endaddress: | |
332 | needles+= ms.search(pattern, self.file_handle, find_first = find_first, chunksize = chunksize) | |
333 | if len(needles) > 0 and find_first is True: | |
334 | return needles | |
335 | ||
336 | ||
337 | return needles | |
338 | ||
339 | def search(self, pattern, find_first = False, chunksize = 10*1024): | |
266 | 340 | t = [] |
267 | 341 | for ms in self.memory_segments: |
268 | if mod.baseaddress <= ms.start_virtual_address < mod.endaddress: | |
269 | t+= ms.search(pattern, self.file_handle) | |
270 | ||
342 | t+= ms.search(pattern, self.file_handle, find_first = find_first, chunksize = chunksize) | |
343 | ||
271 | 344 | return t |
272 | ||
273 | def search(self, pattern): | |
274 | t = [] | |
275 | for ms in self.memory_segments: | |
276 | t+= ms.search(pattern, self.file_handle) | |
277 | ||
278 | return t | |
279 | ||
345 | ||
280 | 346 | def read(self, virt_addr, size): |
281 | 347 | for segment in self.memory_segments: |
282 | 348 | if segment.inrange(virt_addr): |
15 | 15 | buff.seek(dir.Location.Rva) |
16 | 16 | csa.data = buff.read(dir.Location.DataSize).decode() |
17 | 17 | return csa |
18 | ||
19 | @staticmethod | |
20 | async def aparse(dir, buff): | |
21 | csa = CommentStreamA() | |
22 | await buff.seek(dir.Location.Rva) | |
23 | csdata = await buff.read(dir.Location.DataSize) | |
24 | csa.data = csdata.decode() | |
25 | return csa | |
18 | 26 | |
19 | 27 | def __str__(self): |
20 | 28 | return 'CommentA: %s' % self.data⏎ |
15 | 15 | buff.seek(dir.Location.Rva) |
16 | 16 | csa.data = buff.read(dir.Location.DataSize).decode('utf-16-le') |
17 | 17 | return csa |
18 | ||
19 | @staticmethod | |
20 | async def aparse(dir, buff): | |
21 | csa = CommentStreamW() | |
22 | await buff.seek(dir.Location.Rva) | |
23 | csdata = await buff.read(dir.Location.DataSize) | |
24 | csa.data = csdata.decode('utf-16-le') | |
25 | return csa | |
18 | 26 | |
19 | 27 | def __str__(self): |
20 | 28 | return 'CommentW: %s' % self.data⏎ |
50 | 50 | |
51 | 51 | class ExceptionCode(enum.Enum): |
52 | 52 | # Not a real exception code, it's just a placeholder to prevent the parser from raising an error |
53 | EXCEPTION_UNKNOWN = 'EXCEPTION_UNKNOWN_CHECK_RAW' | |
53 | 54 | EXCEPTION_NONE = 0x00 |
54 | 55 | |
55 | 56 | # Linux SIG values (for crashpad generated dumps) |
116 | 117 | self.NumberParameters = None |
117 | 118 | self.__unusedAlignment = None |
118 | 119 | self.ExceptionInformation = [] |
120 | self.ExceptionCode_raw = None | |
119 | 121 | |
120 | 122 | @staticmethod |
121 | 123 | def parse(buff): |
122 | 124 | me = MINIDUMP_EXCEPTION() |
123 | me.ExceptionCode = ExceptionCode(int.from_bytes(buff.read(4), byteorder = 'little', signed = False)) | |
125 | me.ExceptionCode_raw = int.from_bytes(buff.read(4), byteorder = 'little', signed = False) | |
126 | try: | |
127 | me.ExceptionCode = ExceptionCode(me.ExceptionCode_raw) | |
128 | except: | |
129 | me.ExceptionCode = ExceptionCode.EXCEPTION_UNKNOWN | |
130 | ||
124 | 131 | me.ExceptionFlags = int.from_bytes(buff.read(4), byteorder = 'little', signed = False) |
125 | 132 | me.ExceptionRecord = int.from_bytes(buff.read(8), byteorder = 'little', signed = False) |
126 | 133 | me.ExceptionAddress = int.from_bytes(buff.read(8), byteorder = 'little', signed = False) |
127 | 134 | me.NumberParameters = int.from_bytes(buff.read(4), byteorder = 'little', signed = False) |
128 | 135 | me.__unusedAlignment = int.from_bytes(buff.read(4), byteorder = 'little', signed = False) |
129 | for i in range(me.NumberParameters): | |
136 | for _ in range(me.NumberParameters): | |
130 | 137 | me.ExceptionInformation.append(int.from_bytes(buff.read(8), byteorder = 'little', signed = False)) |
131 | 138 | |
132 | 139 | return me |
191 | 198 | t.exception_records.append(mes) |
192 | 199 | |
193 | 200 | return t |
201 | ||
202 | @staticmethod | |
203 | async def aparse(dir, buff): | |
204 | t = ExceptionList() | |
205 | ||
206 | await buff.seek(dir.Location.Rva) | |
207 | chunk_data = await buff.read(dir.Location.DataSize) | |
208 | chunk = io.BytesIO(chunk_data) | |
209 | ||
210 | # Unfortunately, we don't have a certain way to figure out how many exception records | |
211 | # there is in the stream, so we have to fallback on heuristics (EOF or bad data read) | |
212 | # | |
213 | # NB : some tool only read one exception record : https://github.com/GregTheDev/MinidumpExplorer/blob/a6dd974757c16142eefcfff7d99be10b14f87eaf/MinidumpExplorer/MinidumpExplorer/MainForm.cs#L257 | |
214 | # but it's incorrect since we can have an exception chain (double fault, exception catched and re-raised, etc.) | |
215 | while chunk.tell() < dir.Location.DataSize: | |
216 | mes = MINIDUMP_EXCEPTION_STREAM.parse(chunk) | |
217 | ||
218 | # a minidump exception stream is usally padded with zeroes | |
219 | # so whenever we parse an exception record with the code EXCEPTION_NONE | |
220 | # we can stop. | |
221 | if mes.ExceptionRecord.ExceptionCode == ExceptionCode.EXCEPTION_NONE: | |
222 | break | |
223 | ||
224 | t.exception_records.append(mes) | |
225 | ||
226 | return t | |
194 | 227 | |
195 | 228 | def to_table(self): |
196 | 229 | t = [] |
103 | 103 | mhoi.SizeOfInfo = int.from_bytes(buff.read(4), byteorder = 'little', signed = False) |
104 | 104 | mhoi.info_bytes = buff.read(mhoi.SizeOfInfo) |
105 | 105 | return mhoi |
106 | ||
107 | @staticmethod | |
108 | async def aparse(buff): | |
109 | mhoi = MINIDUMP_HANDLE_OBJECT_INFORMATION() | |
110 | t = await buff.read(4) | |
111 | mhoi.NextInfoRva = int.from_bytes(t, byteorder = 'little', signed = False) | |
112 | t = await buff.read(4) | |
113 | mhoi.InfoType = int.from_bytes(t, byteorder = 'little', signed = False) | |
114 | t = await buff.read(4) | |
115 | mhoi.SizeOfInfo = int.from_bytes(t, byteorder = 'little', signed = False) | |
116 | mhoi.info_bytes = await buff.read(mhoi.SizeOfInfo) | |
117 | return mhoi | |
106 | 118 | |
107 | 119 | class MinidumpHandleObjectInformation: |
108 | 120 | def __init__(self): |
112 | 124 | self.info_bytes = None |
113 | 125 | |
114 | 126 | @staticmethod |
115 | def parse(mhoi, buff): | |
127 | def parse(mhoi): | |
116 | 128 | t = MinidumpHandleObjectInformation() |
117 | 129 | t.InfoType = mhoi.InfoType |
118 | 130 | t.SizeOfInfo = mhoi.SizeOfInfo |
151 | 163 | if t.ObjectInfoRva is not None and t.ObjectInfoRva != 0: |
152 | 164 | MinidumpHandleDescriptor.walk_objectinfo(mhd, t.ObjectInfoRva, buff) |
153 | 165 | return mhd |
166 | ||
167 | @staticmethod | |
168 | async def aparse(t, buff): | |
169 | mhd = MinidumpHandleDescriptor() | |
170 | mhd.Handle = t.Handle | |
171 | if t.TypeNameRva != 0: | |
172 | mhd.TypeName = await MINIDUMP_STRING.aget_from_rva(t.TypeNameRva, buff) | |
173 | if t.ObjectNameRva != 0: | |
174 | mhd.ObjectName = await MINIDUMP_STRING.aget_from_rva(t.ObjectNameRva, buff) | |
175 | mhd.Attributes = t.Attributes | |
176 | mhd.GrantedAccess = t.GrantedAccess | |
177 | mhd.HandleCount = t.HandleCount | |
178 | mhd.PointerCount = t.PointerCount | |
179 | if isinstance(t, MINIDUMP_HANDLE_DESCRIPTOR_2): | |
180 | if t.ObjectInfoRva is not None and t.ObjectInfoRva != 0: | |
181 | await MinidumpHandleDescriptor.awalk_objectinfo(mhd, t.ObjectInfoRva, buff) | |
182 | return mhd | |
154 | 183 | |
155 | 184 | @staticmethod |
156 | 185 | def walk_objectinfo(mhd, start, buff): |
157 | 186 | while start is not None and start != 0: |
158 | 187 | buff.seek(start) |
159 | 188 | mhoi = MINIDUMP_HANDLE_OBJECT_INFORMATION.parse(buff) |
160 | t = MinidumpHandleObjectInformation.parse(mhoi, buff) | |
189 | t = MinidumpHandleObjectInformation.parse(mhoi) | |
190 | mhd.ObjectInfos.append(t) | |
191 | start = t.NextInfo | |
192 | ||
193 | @staticmethod | |
194 | async def awalk_objectinfo(mhd, start, buff): | |
195 | while start is not None and start != 0: | |
196 | await buff.seek(start) | |
197 | mhoi = await MINIDUMP_HANDLE_OBJECT_INFORMATION.aparse(buff) | |
198 | t = MinidumpHandleObjectInformation.parse(mhoi) | |
161 | 199 | mhd.ObjectInfos.append(t) |
162 | 200 | start = t.NextInfo |
163 | 201 | |
194 | 232 | mhd = MINIDUMP_HANDLE_DESCRIPTOR_2.parse(chunk) |
195 | 233 | t.handles.append(MinidumpHandleDescriptor.parse(mhd, buff)) |
196 | 234 | return t |
235 | ||
236 | @staticmethod | |
237 | async def aparse(dir, buff): | |
238 | t = MinidumpHandleDataStream() | |
239 | await buff.seek(dir.Location.Rva) | |
240 | chunk_data = await buff.read(dir.Location.DataSize) | |
241 | chunk = io.BytesIO(chunk_data) | |
242 | t.header = MINIDUMP_HANDLE_DATA_STREAM.parse(chunk) | |
243 | for _ in range(t.header.NumberOfDescriptors): | |
244 | if t.header.SizeOfDescriptor == MINIDUMP_HANDLE_DESCRIPTOR.size: | |
245 | mhd = MINIDUMP_HANDLE_DESCRIPTOR.parse(chunk) | |
246 | r = await MinidumpHandleDescriptor.aparse(mhd, buff) | |
247 | t.handles.append(r) | |
248 | else: | |
249 | mhd = MINIDUMP_HANDLE_DESCRIPTOR_2.parse(chunk) | |
250 | r = await MinidumpHandleDescriptor.aparse(mhd, buff) | |
251 | t.handles.append(r) | |
252 | return t | |
197 | 253 | |
198 | 254 | def __str__(self): |
199 | 255 | t = '== MinidumpHandleDataStream ==\n' |
79 | 79 | mtl = MINIDUMP_MEMORY64_LIST.parse(chunk) |
80 | 80 | rva = mtl.BaseRva |
81 | 81 | for mod in mtl.MemoryRanges: |
82 | t.memory_segments.append(MinidumpMemorySegment.parse_full(mod, buff, rva)) | |
82 | t.memory_segments.append(MinidumpMemorySegment.parse_full(mod, rva)) | |
83 | 83 | rva += mod.DataSize |
84 | 84 | return t |
85 | ||
86 | @staticmethod | |
87 | async def aparse(dir, buff): | |
88 | mml = MinidumpMemory64List() | |
89 | await buff.seek(dir.Location.Rva) | |
90 | chunk_data = await buff.read(dir.Location.DataSize) | |
91 | chunk = io.BytesIO(chunk_data) | |
92 | mtl = MINIDUMP_MEMORY64_LIST.parse(chunk) | |
93 | rva = mtl.BaseRva | |
94 | for mod in mtl.MemoryRanges: | |
95 | ms = MinidumpMemorySegment.parse_full(mod, rva) | |
96 | mml.memory_segments.append(ms) | |
97 | rva += mod.DataSize | |
98 | return mml | |
85 | 99 | |
86 | 100 | def to_table(self): |
87 | 101 | t = [] |
210 | 210 | t.infos.append(MinidumpMemoryInfo.parse(mi, buff)) |
211 | 211 | |
212 | 212 | return t |
213 | ||
214 | @staticmethod | |
215 | async def aparse(dir, buff): | |
216 | t = MinidumpMemoryInfoList() | |
217 | await buff.seek(dir.Location.Rva) | |
218 | data = await buff.read(dir.Location.DataSize) | |
219 | chunk = io.BytesIO(data) | |
220 | t.header = MINIDUMP_MEMORY_INFO_LIST.parse(chunk) | |
221 | for _ in range(t.header.NumberOfEntries): | |
222 | mi = MINIDUMP_MEMORY_INFO.parse(chunk) | |
223 | t.infos.append(MinidumpMemoryInfo.parse(mi, None)) | |
224 | ||
225 | return t | |
213 | 226 | |
214 | 227 | def to_table(self): |
215 | 228 | t = [] |
85 | 85 | for mod in mtl.MemoryRanges: |
86 | 86 | t.memory_segments.append(MinidumpMemorySegment.parse_mini(mod, buff)) |
87 | 87 | return t |
88 | ||
89 | @staticmethod | |
90 | async def aparse(dir, buff): | |
91 | t = MinidumpMemoryList() | |
92 | await buff.seek(dir.Location.Rva) | |
93 | chunk_data = await buff.read(dir.Location.DataSize) | |
94 | chunk = io.BytesIO(chunk_data) | |
95 | mtl = MINIDUMP_MEMORY_LIST.parse(chunk) | |
96 | for mod in mtl.MemoryRanges: | |
97 | t.memory_segments.append(MinidumpMemorySegment.parse_mini(mod, buff)) | |
98 | return t | |
88 | 99 | |
89 | 100 | def __str__(self): |
90 | 101 | t = '== MinidumpMemoryList ==\n' |
122 | 122 | t.ProcessorMaxIdleState = misc.ProcessorMaxIdleState |
123 | 123 | t.ProcessorCurrentIdleState = misc.ProcessorCurrentIdleState |
124 | 124 | return t |
125 | ||
126 | @staticmethod | |
127 | async def aparse(dir, buff): | |
128 | t = MinidumpMiscInfo() | |
129 | await buff.seek(dir.Location.Rva) | |
130 | chunk_data = await buff.read(dir.Location.DataSize) | |
131 | chunk = io.BytesIO(chunk_data) | |
132 | if dir.Location.DataSize == MINIDUMP_MISC_INFO.size: | |
133 | misc = MINIDUMP_MISC_INFO.parse(chunk) | |
134 | t.ProcessId = misc.ProcessId | |
135 | t.ProcessCreateTime = misc.ProcessCreateTime | |
136 | t.ProcessUserTime = misc.ProcessUserTime | |
137 | t.ProcessKernelTime = misc.ProcessKernelTime | |
138 | else: | |
139 | misc = MINIDUMP_MISC_INFO_2.parse(chunk) | |
140 | t.ProcessId = misc.ProcessId | |
141 | t.ProcessCreateTime = misc.ProcessCreateTime | |
142 | t.ProcessUserTime = misc.ProcessUserTime | |
143 | t.ProcessKernelTime = misc.ProcessKernelTime | |
144 | t.ProcessorMaxMhz = misc.ProcessorMaxMhz | |
145 | t.ProcessorCurrentMhz = misc.ProcessorCurrentMhz | |
146 | t.ProcessorMhzLimit = misc.ProcessorMhzLimit | |
147 | t.ProcessorMaxIdleState = misc.ProcessorMaxIdleState | |
148 | t.ProcessorCurrentIdleState = misc.ProcessorCurrentIdleState | |
149 | return t | |
125 | 150 | |
126 | 151 | def __str__(self): |
127 | 152 | t = '== MinidumpMiscInfo ==\n' |
28 | 28 | mm.checksum = mod.CheckSum |
29 | 29 | mm.timestamp = mod.TimeDateStamp |
30 | 30 | mm.name = MINIDUMP_STRING.get_from_rva(mod.ModuleNameRva, buff) |
31 | mm.versioninfo = mod.VersionInfo | |
32 | mm.endaddress = mm.baseaddress + mm.size | |
33 | return mm | |
34 | ||
35 | @staticmethod | |
36 | async def aparse(mod, buff): | |
37 | """ | |
38 | mod: MINIDUMP_MODULE | |
39 | buff: file handle | |
40 | """ | |
41 | mm = MinidumpModule() | |
42 | mm.baseaddress = mod.BaseOfImage | |
43 | mm.size = mod.SizeOfImage | |
44 | mm.checksum = mod.CheckSum | |
45 | mm.timestamp = mod.TimeDateStamp | |
46 | mm.name = await MINIDUMP_STRING.aget_from_rva(mod.ModuleNameRva, buff) | |
31 | 47 | mm.versioninfo = mod.VersionInfo |
32 | 48 | mm.endaddress = mm.baseaddress + mm.size |
33 | 49 | return mm |
211 | 227 | for mod in mtl.Modules: |
212 | 228 | t.modules.append(MinidumpModule.parse(mod, buff)) |
213 | 229 | return t |
230 | ||
231 | @staticmethod | |
232 | async def aparse(dir, buff): | |
233 | t = MinidumpModuleList() | |
234 | await buff.seek(dir.Location.Rva) | |
235 | chunk_data = await buff.read(dir.Location.DataSize) | |
236 | chunk = io.BytesIO(chunk_data) | |
237 | mtl = MINIDUMP_MODULE_LIST.parse(chunk) | |
238 | for mod in mtl.Modules: | |
239 | x = await MinidumpModule.aparse(mod, buff) | |
240 | t.modules.append(x) | |
241 | return t | |
214 | 242 | |
215 | 243 | def to_table(self): |
216 | 244 | t = [] |
6 | 6 | import io |
7 | 7 | import enum |
8 | 8 | import logging |
9 | from minidump.common_structs import * | |
9 | from minidump.common_structs import * | |
10 | 10 | |
11 | 11 | # https://msdn.microsoft.com/en-us/library/windows/desktop/ms680396(v=vs.85).aspx |
12 | 12 | class PROCESSOR_ARCHITECTURE(enum.Enum): |
14 | 14 | ARM = 5 #ARM |
15 | 15 | IA64 = 6 #Intel Itanium |
16 | 16 | INTEL = 0 #x86 |
17 | AARCH64 = 0x8003 #ARM64 | |
17 | 18 | UNKNOWN = 0xffff #Unknown processor |
18 | 19 | # https://msdn.microsoft.com/en-us/library/windows/desktop/ms680396(v=vs.85).aspx |
19 | 20 | class PROCESSOR_LEVEL(enum.Enum): |
21 | 22 | INTEL_80486 = 4 |
22 | 23 | INTEL_PENTIUM = 5 |
23 | 24 | INTEL_PENTIUM_PRO = 6 #or Pentium II |
24 | # https://msdn.microsoft.com/en-us/library/windows/desktop/ms680396(v=vs.85).aspx | |
25 | # https://msdn.microsoft.com/en-us/library/windows/desktop/ms680396(v=vs.85).aspx | |
25 | 26 | class PRODUCT_TYPE(enum.Enum): |
26 | 27 | VER_UNIDENTIFIED_PRODUCT = 0x0000000 # Crashpad des not set ProductType value on non-Windows systems |
27 | 28 | VER_NT_WORKSTATION = 0x0000001 # The system is running Windows XP, Windows Vista, Windows 7, or Windows 8. |
28 | 29 | VER_NT_DOMAIN_CONTROLLER = 0x0000002 # The system is a domain controller. |
29 | 30 | VER_NT_SERVER = 0x0000003 # The system is a server. |
30 | ||
31 | ||
31 | 32 | # https://msdn.microsoft.com/en-us/library/windows/desktop/ms680396(v=vs.85).aspx |
32 | 33 | class PLATFORM_ID(enum.Enum): |
33 | 34 | VER_PLATFORM_WIN32s = 0 #Not supported |
34 | 35 | VER_PLATFORM_WIN32_WINDOWS = 1 #Not supported. |
35 | 36 | VER_PLATFORM_WIN32_NT = 2 #The operating system platform is Windows. |
36 | ||
37 | ||
37 | 38 | # source : https://github.com/chromium/crashpad/blob/4b05be4265c0ffacfce26d7db7644ffbf9037696/minidump/minidump_extensions.h#L239 |
38 | 39 | VER_PLATFORM_CRASHPAD_MAC = 0x8101 |
39 | 40 | VER_PLATFORM_CRASHPAD_IOS = 0x8102 |
124 | 125 | else: |
125 | 126 | for pf in self.ProcessorFeatures: |
126 | 127 | t += pf.to_bytes(8, byteorder = 'little', signed = False) |
127 | ||
128 | ||
128 | 129 | if data_buffer is None: |
129 | 130 | return t |
130 | 131 | else: |
131 | 132 | data_buffer.write(t) |
132 | ||
133 | ||
133 | 134 | @staticmethod |
134 | 135 | def parse(buff): |
135 | 136 | msi = MINIDUMP_SYSTEM_INFO() |
157 | 158 | else: |
158 | 159 | for _ in range(2): |
159 | 160 | msi.ProcessorFeatures.append(int.from_bytes(buff.read(8), byteorder = 'little', signed = False)) |
160 | ||
161 | ||
161 | 162 | return msi |
162 | 163 | |
163 | 164 | def __str__(self): |
165 | 166 | for k in self.__dict__: |
166 | 167 | t += '%s : %s\r\n' % (k, str(self.__dict__[k])) |
167 | 168 | return t |
168 | ||
169 | ||
169 | 170 | class MinidumpSystemInfo: |
170 | 171 | def __init__(self): |
171 | 172 | self.ProcessorArchitecture = None |
184 | 185 | self.FeatureInformation = None |
185 | 186 | self.AMDExtendedCpuFeatures = None |
186 | 187 | self.ProcessorFeatures = None |
187 | ||
188 | ||
188 | 189 | #extra |
189 | 190 | self.OperatingSystem = None |
190 | ||
191 | ||
191 | 192 | def guess_os(self): |
192 | 193 | if self.MajorVersion == 10 and self.MinorVersion == 0 and self.ProductType == PRODUCT_TYPE.VER_NT_WORKSTATION: |
193 | 194 | self.OperatingSystem = "Windows 10" |
218 | 219 | self.OperatingSystem = "Windows XP" |
219 | 220 | elif self.MajorVersion == 5 and self.MinorVersion == 0: |
220 | 221 | self.OperatingSystem = "Windows 2000" |
221 | ||
222 | ||
222 | 223 | @staticmethod |
223 | 224 | def parse(dir, buff): |
224 | 225 | t = MinidumpSystemInfo() |
247 | 248 | logging.log(1, 'Failed to guess OS! MajorVersion: %s MinorVersion %s BuildNumber %s ProductType: %s' % (t.MajorVersion, t.MinorVersion, t.BuildNumber, t.ProductType )) |
248 | 249 | t.OperatingSystem = None |
249 | 250 | return t |
250 | ||
251 | ||
251 | ||
252 | @staticmethod | |
253 | async def aparse(dir, buff): | |
254 | t = MinidumpSystemInfo() | |
255 | await buff.seek(dir.Location.Rva) | |
256 | chunk_data = await buff.read(dir.Location.DataSize) | |
257 | chunk = io.BytesIO(chunk_data) | |
258 | si = MINIDUMP_SYSTEM_INFO.parse(chunk) | |
259 | t.ProcessorArchitecture = si.ProcessorArchitecture | |
260 | t.ProcessorLevel = si.ProcessorLevel | |
261 | t.ProcessorRevision = si.ProcessorRevision | |
262 | t.NumberOfProcessors = si.NumberOfProcessors | |
263 | t.ProductType = si.ProductType | |
264 | t.MajorVersion = si.MajorVersion | |
265 | t.MinorVersion = si.MinorVersion | |
266 | t.BuildNumber = si.BuildNumber | |
267 | t.PlatformId = si.PlatformId | |
268 | t.CSDVersion = await MINIDUMP_STRING.aget_from_rva(si.CSDVersionRva, buff) | |
269 | t.SuiteMask = si.SuiteMask | |
270 | t.VendorId = si.VendorId | |
271 | t.VersionInformation = si.VersionInformation | |
272 | t.FeatureInformation = si.FeatureInformation | |
273 | t.AMDExtendedCpuFeatures = si.AMDExtendedCpuFeatures | |
274 | t.ProcessorFeatures = si.ProcessorFeatures | |
275 | try: | |
276 | t.guess_os() | |
277 | except Exception as e: | |
278 | logging.log(1, 'Failed to guess OS! MajorVersion: %s MinorVersion %s BuildNumber %s ProductType: %s' % (t.MajorVersion, t.MinorVersion, t.BuildNumber, t.ProductType )) | |
279 | t.OperatingSystem = None | |
280 | return t | |
281 | ||
282 | ||
252 | 283 | def __str__(self): |
253 | 284 | t = '== System Info ==\n' |
254 | 285 | t += 'ProcessorArchitecture %s\n' % self.ProcessorArchitecture |
268 | 299 | t += 'FeatureInformation %s\n' % self.FeatureInformation |
269 | 300 | t += 'AMDExtendedCpuFeatures %s\n' % self.AMDExtendedCpuFeatures |
270 | 301 | t += 'ProcessorFeatures %s\n' % ' '.join( [hex(x) for x in self.ProcessorFeatures] ) |
271 | ||
272 | return t⏎ | |
302 | ||
303 | return t |
83 | 83 | mtl = MINIDUMP_THREAD_EX_LIST.parse(chunk) |
84 | 84 | t.threads = mtl.Threads |
85 | 85 | return t |
86 | ||
87 | @staticmethod | |
88 | async def aparse(dir, buff): | |
89 | t = MinidumpThreadExList() | |
90 | await buff.seek(dir.Location.Rva) | |
91 | chunk_data = await buff.read(dir.Location.DataSize) | |
92 | chunk = io.BytesIO(chunk_data) | |
93 | mtl = MINIDUMP_THREAD_EX_LIST.parse(chunk) | |
94 | t.threads = mtl.Threads | |
95 | return t | |
86 | 96 | |
87 | 97 | def to_table(self): |
88 | 98 | t = [] |
159 | 159 | data = buff.read(dir.Location.DataSize) |
160 | 160 | chunk = io.BytesIO(data) |
161 | 161 | t.header = MINIDUMP_THREAD_INFO_LIST.parse(chunk) |
162 | for i in range(t.header.NumberOfEntries): | |
162 | for _ in range(t.header.NumberOfEntries): | |
163 | 163 | mi = MINIDUMP_THREAD_INFO.parse(chunk) |
164 | 164 | t.infos.append(MinidumpThreadInfo.parse(mi, buff)) |
165 | ||
166 | return t | |
167 | ||
168 | @staticmethod | |
169 | async def aparse(dir, buff): | |
170 | t = MinidumpThreadInfoList() | |
171 | await buff.seek(dir.Location.Rva) | |
172 | data = await buff.read(dir.Location.DataSize) | |
173 | chunk = io.BytesIO(data) | |
174 | t.header = MINIDUMP_THREAD_INFO_LIST.parse(chunk) | |
175 | for _ in range(t.header.NumberOfEntries): | |
176 | mi = MINIDUMP_THREAD_INFO.parse(chunk) | |
177 | t.infos.append(MinidumpThreadInfo.parse(mi, None)) | |
165 | 178 | |
166 | 179 | return t |
167 | 180 |
95 | 95 | mtl = MINIDUMP_THREAD_LIST.parse(chunk) |
96 | 96 | t.threads = mtl.Threads |
97 | 97 | return t |
98 | ||
99 | @staticmethod | |
100 | async def aparse(dir, buff): | |
101 | t = MinidumpThreadList() | |
102 | await buff.seek(dir.Location.Rva) | |
103 | chunk_data = await buff.read(dir.Location.DataSize) | |
104 | chunk = io.BytesIO(chunk_data) | |
105 | mtl = MINIDUMP_THREAD_LIST.parse(chunk) | |
106 | t.threads = mtl.Threads | |
107 | return t | |
98 | 108 | |
99 | 109 | def to_table(self): |
100 | 110 | t = [] |
79 | 79 | mm.name = MINIDUMP_STRING.get_from_rva(mod.ModuleNameRva, buff) |
80 | 80 | mm.endaddress = mm.baseaddress + mm.size |
81 | 81 | return mm |
82 | ||
83 | @staticmethod | |
84 | async def aparse(mod, buff): | |
85 | """ | |
86 | mod: MINIDUMP_MODULE | |
87 | buff: file handle | |
88 | """ | |
89 | mm = MinidumpUnloadedModule() | |
90 | mm.baseaddress = mod.BaseOfImage | |
91 | mm.size = mod.SizeOfImage | |
92 | mm.checksum = mod.CheckSum | |
93 | mm.timestamp = mod.TimeDateStamp | |
94 | mm.name = await MINIDUMP_STRING.aget_from_rva(mod.ModuleNameRva, buff) | |
95 | mm.endaddress = mm.baseaddress + mm.size | |
96 | return mm | |
82 | 97 | |
83 | 98 | def assign_memory_regions(self, segments): |
84 | 99 | for segment in segments: |
121 | 136 | t.modules.append(MinidumpUnloadedModule.parse(mod, buff)) |
122 | 137 | |
123 | 138 | return t |
139 | ||
140 | @staticmethod | |
141 | async def aparse(dir, buff): | |
142 | t = MinidumpUnloadedModuleList() | |
143 | await buff.seek(dir.Location.Rva) | |
144 | chunk_data = await buff.read(dir.Location.DataSize) | |
145 | chunk = io.BytesIO(chunk_data) | |
146 | muml = MINIDUMP_UNLOADED_MODULE_LIST.parse(chunk) | |
147 | for _ in range(muml.NumberOfEntries): | |
148 | mod = MINIDUMP_UNLOADED_MODULE.parse(chunk) | |
149 | dr = await MinidumpUnloadedModule.aparse(mod, buff) | |
150 | t.modules.append(dr) | |
151 | ||
152 | return t | |
124 | 153 | |
125 | 154 | def to_table(self): |
126 | 155 | t = [] |
0 | 0 | Metadata-Version: 1.2 |
1 | 1 | Name: minidump |
2 | Version: 0.0.12 | |
2 | Version: 0.0.17 | |
3 | 3 | Summary: Python library to parse Windows minidump file format |
4 | 4 | Home-page: https://github.com/skelsec/minidump |
5 | 5 | Author: Tamas Jos |
0 | LICENSE | |
1 | MANIFEST.in | |
0 | 2 | README.md |
1 | 3 | setup.py |
4 | minidump/__amain__.py | |
2 | 5 | minidump/__init__.py |
3 | 6 | minidump/__main__.py |
4 | 7 | minidump/_version.py |
8 | minidump/aminidumpfile.py | |
9 | minidump/aminidumpreader.py | |
5 | 10 | minidump/common_structs.py |
6 | 11 | minidump/constants.py |
7 | 12 | minidump/directory.py |
16 | 21 | minidump.egg-info/SOURCES.txt |
17 | 22 | minidump.egg-info/dependency_links.txt |
18 | 23 | minidump.egg-info/entry_points.txt |
24 | minidump.egg-info/not-zip-safe | |
19 | 25 | minidump.egg-info/top_level.txt |
20 | minidump.egg-info/zip-safe | |
21 | 26 | minidump/streams/CommentStreamA.py |
22 | 27 | minidump/streams/CommentStreamW.py |
23 | 28 | minidump/streams/ExceptionStream.py |