naft 1.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
naft-1.0.1/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022 Digital Sleuth
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
naft-1.0.1/PKG-INFO ADDED
@@ -0,0 +1,17 @@
1
+ Metadata-Version: 2.4
2
+ Name: naft
3
+ Version: 1.0.1
4
+ Summary: Network Appliance Forensic Toolkit
5
+ Author: @digitalsleuth and @G-K7
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/digitalsleuth/naft
8
+ Keywords: naft,network,appliance,forensic,toolkit
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Operating System :: OS Independent
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE.md
13
+ Dynamic: license-file
14
+
15
+ # NAFT - Network Appliance Forensic Toolkit
16
+
17
+ This project is a revival of the Didier Stevens original '[Network Appliance Forensic Toolkit](https://blog.didierstevens.com/programs/network-appliance-forensic-toolkit/)'. It is being updated to work with Python 3, with future plans for refactoring, streamlining, and a PyPi package.
naft-1.0.1/README.md ADDED
@@ -0,0 +1,3 @@
1
+ # NAFT - Network Appliance Forensic Toolkit
2
+
3
+ This project is a revival of the Didier Stevens original '[Network Appliance Forensic Toolkit](https://blog.didierstevens.com/programs/network-appliance-forensic-toolkit/)'. It is being updated to work with Python 3, with future plans for refactoring, streamlining, and a PyPi package.
File without changes
File without changes
@@ -0,0 +1,150 @@
1
+ #!/usr/bin/env python3
2
+
3
+ __description__ = "Network Appliance Forensic Toolkit - Generic Frame Extraction"
4
+ __version__ = "1.0.1"
5
+ __original_author__ = "Didier Stevens"
6
+ __current_authors__ = "@digitalsleuth and @G-K7"
7
+ __date__ = "2026/06/15"
8
+
9
+ import struct
10
+ import naft.modules.impf as impf
11
+ import naft.modules.uf as uf
12
+ import naft.modules.pfef as pfef
13
+
14
+
15
+ def ExtractIPPacketsFromFile(filenamesRawData, filenamePCAP, arguments):
16
+ uf.LogLine("Start")
17
+ if arguments["ouitxt"] is None:
18
+ oFrames = pfef.cFrames()
19
+ else:
20
+ oFrames = pfef.cFrames(arguments["ouitxt"])
21
+ countProcessedFiles = 0
22
+ for filenameRawData in filenamesRawData:
23
+ if arguments["buffer"]:
24
+ uf.LogLine(f"Buffering file {filenameRawData}")
25
+ oBufferFile = uf.cBufferFile(
26
+ filenameRawData,
27
+ arguments["buffersize"] * 1024 * 1024,
28
+ arguments["bufferoverlapsize"] * 1024 * 1024,
29
+ )
30
+ while oBufferFile.Read():
31
+ uf.LogLine(
32
+ f"Processing buffer 0x{oBufferFile.index:x} size {len(oBufferFile.buffer)/1024/1024:.2f} MB {oBufferFile.Progress():d}%"
33
+ )
34
+ uf.LogLine("Searching for IPv4 packets")
35
+ pfef.ExtractIPPackets(
36
+ oFrames,
37
+ oBufferFile.index,
38
+ oBufferFile.buffer,
39
+ arguments["options"],
40
+ arguments["duplicates"],
41
+ True,
42
+ filenameRawData,
43
+ )
44
+ uf.LogLine("Searching for ARP Ethernet frames")
45
+ pfef.ExtractARPFrames(
46
+ oFrames,
47
+ oBufferFile.index,
48
+ oBufferFile.buffer,
49
+ arguments["duplicates"],
50
+ True,
51
+ filenameRawData,
52
+ )
53
+ if oBufferFile.err == MemoryError:
54
+ uf.LogLine("Data is too large to fit in memory, use smaller buffer")
55
+ elif oBufferFile.err:
56
+ uf.LogLine("Error reading file")
57
+ countProcessedFiles += 1
58
+ else:
59
+ uf.LogLine(f"Reading file {filenameRawData}")
60
+ rawData = uf.File2Data(filenameRawData)
61
+ if rawData is None:
62
+ uf.LogLine("Error reading file")
63
+ if rawData == MemoryError:
64
+ uf.LogLine("File is too large to fit in memory")
65
+ else:
66
+ uf.LogLine("Searching for IPv4 packets")
67
+ pfef.ExtractIPPackets(
68
+ oFrames,
69
+ 0,
70
+ rawData,
71
+ arguments["options"],
72
+ arguments["duplicates"],
73
+ True,
74
+ filenameRawData,
75
+ )
76
+ uf.LogLine("Searching for ARP Ethernet frames")
77
+ pfef.ExtractARPFrames(
78
+ oFrames, 0, rawData, arguments["duplicates"], True, filenameRawData
79
+ )
80
+ countProcessedFiles += 1
81
+ if countProcessedFiles > 0:
82
+ uf.LogLine(f"Writing PCAP file {filenamePCAP}")
83
+ if not oFrames.WritePCAP(filenamePCAP):
84
+ uf.LogLine("Error writing PCAP file")
85
+ uf.LogLine(f"Number of identified frames: {oFrames.countFrames:5d}")
86
+ uf.LogLine(f"Number of identified packets: {oFrames.countPackets:5d}")
87
+ uf.LogLine(f"Number of frames in PCAP file: {len(oFrames.frames):5d}")
88
+ uf.LogLine("Done")
89
+
90
+
91
+ def IOSFrames(coredumpFilename, filenameIOMEM, filenamePCAP, arguments):
92
+ uf.LogLine("Start")
93
+ uf.LogLine(f"Reading file {coredumpFilename}")
94
+ oIOSCoreDump = impf.cIOSCoreDump(coredumpFilename)
95
+ if oIOSCoreDump.err is not None:
96
+ print(oIOSCoreDump.err)
97
+ return
98
+ uf.LogLine("Searching for heap region")
99
+ _, memoryHeap = oIOSCoreDump.RegionHEAP() # _ = addressHeap
100
+ if memoryHeap is None:
101
+ print("Heap region not found")
102
+ return
103
+ oIOSMemoryParserHeap = impf.cIOSMemoryParser(memoryHeap)
104
+ oIOSMemoryParserHeap.ResolveNames(oIOSCoreDump)
105
+ uf.LogLine(f"Reading file {filenameIOMEM}")
106
+ dataIOMEM = uf.File2Data(filenameIOMEM)
107
+ uf.LogLine(f"Searching for base address from {filenameIOMEM}")
108
+ oIOSMemoryParserIOMEM = impf.cIOSMemoryParser(dataIOMEM)
109
+ addressIOMEM = oIOSMemoryParserIOMEM.baseAddress
110
+ if addressIOMEM is None:
111
+ print("Error parsing IOMEM")
112
+ return
113
+ oFrames = pfef.cFrames()
114
+ if arguments["verbose"]:
115
+ print(impf.cIOSMemoryBlockHeader.ShowHeader)
116
+ for oIOSMemoryBlockHeader in oIOSMemoryParserHeap.Headers:
117
+ if oIOSMemoryBlockHeader.AllocNameResolved == "*Packet Header*":
118
+ frameAddress = struct.unpack(">I", oIOSMemoryBlockHeader.GetData()[40:44])[
119
+ 0
120
+ ]
121
+ frameSize = struct.unpack(">H", oIOSMemoryBlockHeader.GetData()[72:74])[0]
122
+ if frameSize <= 1:
123
+ frameSize = struct.unpack(">H", oIOSMemoryBlockHeader.GetData()[68:70])[
124
+ 0
125
+ ]
126
+ if frameAddress != 0 and frameSize != 0:
127
+ if arguments["verbose"]:
128
+ print(oIOSMemoryBlockHeader.ShowLine())
129
+ uf.DumpBytes(
130
+ dataIOMEM[
131
+ frameAddress
132
+ - addressIOMEM : frameAddress
133
+ - addressIOMEM
134
+ + frameSize
135
+ ],
136
+ frameAddress,
137
+ )
138
+ oFrames.AddFrame(
139
+ frameAddress - addressIOMEM,
140
+ dataIOMEM[
141
+ frameAddress
142
+ - addressIOMEM : frameAddress
143
+ - addressIOMEM
144
+ + frameSize
145
+ ],
146
+ True,
147
+ )
148
+ oFrames.WritePCAP(filenamePCAP)
149
+ uf.LogLine(f"{oFrames.countFrames:d} frames written to {filenamePCAP}")
150
+ uf.LogLine("Done")
@@ -0,0 +1,452 @@
1
+ #!/usr/bin/env python3
2
+
3
+ __description__ = "Network Appliance Forensic Toolkit - IOS Core Dumps"
4
+ __version__ = "1.0.1"
5
+ __original_author__ = "Didier Stevens"
6
+ __current_authors__ = "@digitalsleuth and @G-K7"
7
+ __date__ = "2026/06/15"
8
+
9
+ import struct
10
+ import re
11
+ import os
12
+ from datetime import datetime
13
+ import naft.modules.uf as uf
14
+ import naft.modules.impf as impf
15
+ import naft.modules.pfef as pfef
16
+ import naft.modules.iipf as iipf
17
+
18
+
19
+ def IOSRegions(coredumpFilename, arguments):
20
+ oIOSCoreDump = impf.cIOSCoreDump(coredumpFilename)
21
+ if oIOSCoreDump.err is not None:
22
+ print(oIOSCoreDump.err)
23
+ else:
24
+ print("Start End Size Name")
25
+ for region in oIOSCoreDump.regions:
26
+ if region[2] is not None:
27
+ print(
28
+ f"0x{region[1]:08X} 0x{(region[1] + region[2] - 1):08X} {region[2]:<10d} {region[0]}"
29
+ )
30
+ if arguments["output"]:
31
+ uf.Data2File(
32
+ oIOSCoreDump.Region(region[0])[1],
33
+ f"{os.path.basename(coredumpFilename)}-{region[0]}-0x{region[1]:08X}",
34
+ arguments["output"],
35
+ )
36
+ else:
37
+ print(f'0x{region[1]:08X} {(" " * 21)} {region[0]}')
38
+ addressBSS, dataBSS = oIOSCoreDump.RegionBSS()
39
+
40
+
41
+ def File2Strings(filename):
42
+ try:
43
+ with open(filename, "r", encoding="utf-8") as f:
44
+ return [line.rstrip("\n") for line in f]
45
+ except:
46
+ return None
47
+
48
+
49
+ def ProcessAt(argument):
50
+ if argument.startswith("@"):
51
+ strings = File2Strings(argument[1:])
52
+ if strings is None:
53
+ raise Exception(f"Error reading {argument}")
54
+ else:
55
+ return strings
56
+ return [argument]
57
+
58
+
59
+ def ProcessHeap(oIOSMemoryBlockHeader, arguments, coredumpFilename, output_path=None):
60
+ if not arguments["strings"]:
61
+ print(oIOSMemoryBlockHeader.ShowLine())
62
+ if arguments["strings"]:
63
+ dStrings = uf.SearchASCIIStrings(oIOSMemoryBlockHeader.GetData())
64
+ if arguments["grep"] != "":
65
+ printHeader = True
66
+ for key, value in dStrings.items():
67
+ if value.find(arguments["grep"].encode("utf-8")) >= 0:
68
+ if printHeader:
69
+ print(oIOSMemoryBlockHeader.ShowLine())
70
+ printHeader = False
71
+ print(
72
+ f' {(oIOSMemoryBlockHeader.address + oIOSMemoryBlockHeader.BlockSize + key):08X}: {value.decode("utf-8")}'
73
+ )
74
+ elif arguments["minimum"] == 0 or len(dStrings) >= arguments["minimum"]:
75
+ print(oIOSMemoryBlockHeader.ShowLine())
76
+ for key, value in dStrings.items():
77
+ print(
78
+ f' {(oIOSMemoryBlockHeader.address + oIOSMemoryBlockHeader.BlockSize + key):08X}: {value.decode("utf-8")}'
79
+ )
80
+ if arguments["dump"]:
81
+ uf.DumpBytes(
82
+ oIOSMemoryBlockHeader.GetData(),
83
+ oIOSMemoryBlockHeader.address + oIOSMemoryBlockHeader.headerSize,
84
+ )
85
+ if arguments["dumpraw"]:
86
+ uf.DumpBytes(oIOSMemoryBlockHeader.GetRawData(), oIOSMemoryBlockHeader.address)
87
+ if arguments["output"]:
88
+ uf.Data2File(
89
+ oIOSMemoryBlockHeader.GetData(),
90
+ f"{coredumpFilename}-heap-0x{oIOSMemoryBlockHeader.address:08X}.data",
91
+ output_path,
92
+ )
93
+ if arguments["verbose"]:
94
+ print(
95
+ f"\tFile: {output_path}{coredumpFilename}-heap-0x{oIOSMemoryBlockHeader.address:08X}.data created.\n"
96
+ )
97
+
98
+
99
+ def IOSHeap(coredumpFilename, arguments):
100
+ if arguments["output"] is not None:
101
+ output_path = os.path.join(arguments["output"], "heap_data")
102
+ os.mkdir(output_path)
103
+ else:
104
+ output_path = ""
105
+ oIOSCoreDump = impf.cIOSCoreDump(coredumpFilename)
106
+ if oIOSCoreDump.err is not None:
107
+ print(oIOSCoreDump.err)
108
+ return
109
+ addressHeap, memoryHeap = oIOSCoreDump.RegionHEAP()
110
+ if memoryHeap is None:
111
+ print("Heap region not found")
112
+ return
113
+ oIOSMemoryParser = impf.cIOSMemoryParser(memoryHeap)
114
+ if arguments["resolve"] or arguments["filter"] != "":
115
+ oIOSMemoryParser.ResolveNames(oIOSCoreDump)
116
+ if arguments["filter"] == "":
117
+ print(impf.cIOSMemoryBlockHeader.ShowHeader)
118
+ for oIOSMemoryBlockHeader in oIOSMemoryParser.Headers:
119
+ ProcessHeap(oIOSMemoryBlockHeader, arguments, coredumpFilename, output_path)
120
+ else:
121
+ print(impf.cIOSMemoryBlockHeader.ShowHeader)
122
+ for oIOSMemoryBlockHeader in oIOSMemoryParser.Headers:
123
+ if oIOSMemoryBlockHeader.AllocNameResolved == arguments["filter"]:
124
+ ProcessHeap(
125
+ oIOSMemoryBlockHeader, arguments, coredumpFilename, output_path
126
+ )
127
+
128
+
129
+ def IOSCWStringsSub(data):
130
+ oCWStrings = impf.cCiscoCWStrings(data)
131
+ if oCWStrings.err is not None:
132
+ print(oCWStrings.err)
133
+ return
134
+ keys = list(oCWStrings.dCWStrings.keys())
135
+ keys.sort()
136
+ for key in keys:
137
+ if key == b"CW_SYSDESCR":
138
+ print(f'{key.decode("utf-8")}:')
139
+ print(oCWStrings.dCWStrings[key].decode("utf-8"))
140
+ else:
141
+ print(
142
+ f'{key.decode("utf-8")}:{(' ' * (22 - len(key)))}{oCWStrings.dCWStrings[key].decode("utf-8")}'
143
+ )
144
+
145
+
146
+ def IOSCWStrings(coredumpFilename, arguments):
147
+ if arguments["raw"]:
148
+ coredump = uf.File2Data(coredumpFilename)
149
+ if coredump is None:
150
+ print(f"Error reading file {coredumpFilename}")
151
+ else:
152
+ IOSCWStringsSub(coredump)
153
+ else:
154
+ oIOSCoreDump = impf.cIOSCoreDump(coredumpFilename)
155
+ if oIOSCoreDump.err is not None:
156
+ print(oIOSCoreDump.err)
157
+ return
158
+ addressData, memoryData = oIOSCoreDump.RegionDATA()
159
+ if memoryData is None:
160
+ print("Data region not found")
161
+ return
162
+ IOSCWStringsSub(memoryData)
163
+
164
+
165
+ def PrintStatsAnalysis(dStats, oIOSCoreDump):
166
+ keys1 = list(dStats.keys())
167
+ keys1.sort()
168
+ for key1 in keys1:
169
+ countKeys = len(dStats[key1])
170
+ keys2 = list(dStats[key1].keys())
171
+ keys2.sort()
172
+ if 2 < countKeys <= 7:
173
+ bucket = "-> " + " ".join(
174
+ [f"{key2:X}:{dStats[key1][key2]:d}" for key2 in keys2]
175
+ )
176
+ else:
177
+ bucket = ""
178
+ filtered = [x for x in dStats[key1] if x != 0]
179
+ if filtered == []:
180
+ filteredMin = min(dStats[key1])
181
+ else:
182
+ filteredMin = min(filtered)
183
+ unfilteredMax = max(dStats[key1])
184
+ regionNames = []
185
+ for region in oIOSCoreDump.regions:
186
+ if region[2] is not None:
187
+ if (
188
+ region[1] <= filteredMin <= region[1] + region[2] - 1
189
+ or region[1] <= unfilteredMax <= region[1] + region[2] - 1
190
+ ):
191
+ if region[0] not in regionNames:
192
+ regionNames.append(region[0])
193
+ regionNames.sort()
194
+ regionName = " ".join(regionNames).strip()
195
+ print(
196
+ f"{key1:3d} {(key1*4):3X}: {countKeys:3d} {min(dStats[key1]):08X} {filteredMin:08X} {unfilteredMax:08X} {regionName} {bucket}"
197
+ )
198
+
199
+
200
+ def IOSProcesses(coredumpFilename, arguments):
201
+ oIOSCoreDumpAnalysis = impf.cIOSCoreDumpAnalysis(coredumpFilename)
202
+ if oIOSCoreDumpAnalysis.err is not None:
203
+ print(oIOSCoreDumpAnalysis.err)
204
+ return
205
+ print(
206
+ f"{'PID':>4} "
207
+ f"{'QTy':>3} "
208
+ f"{'PC':<8} "
209
+ f"{'Runtime (ms)':>12} "
210
+ f"{'Invoked':>10} "
211
+ f"{'uSecs':>8} "
212
+ f"{'Stacks':>12} "
213
+ f"{'TTY':>4} "
214
+ f"{'StackBlk':>8} "
215
+ f"Process"
216
+ )
217
+ for processID, addressProcess, oIOSProcess in oIOSCoreDumpAnalysis.processes:
218
+ if arguments["filter"] == "" or processID == int(arguments["filter"]):
219
+ if oIOSProcess is not None:
220
+ if oIOSProcess.err == "":
221
+ line = oIOSProcess.Line()
222
+ else:
223
+ line = f"{processID:4d} {oIOSProcess.err}"
224
+ print(line)
225
+ if arguments["dump"]:
226
+ uf.DumpBytes(oIOSProcess.data, addressProcess)
227
+ else:
228
+ print(
229
+ f" {processID:>3d} {addressProcess:08X} - addressProcess not found"
230
+ )
231
+ if oIOSCoreDumpAnalysis.RanHeuristics:
232
+ print("")
233
+ print("*** WARNING ***")
234
+ print("Unexpected process structure")
235
+ print("Please reports these results")
236
+ print("Fields determined with heuristics:")
237
+ print(f"Process structure size: {oIOSCoreDumpAnalysis.HeuristicsSize:d}")
238
+ keys = list(oIOSCoreDumpAnalysis.HeuristicsFields.keys())
239
+ keys.sort(key=str.lower)
240
+ for key in keys:
241
+ value = oIOSCoreDumpAnalysis.HeuristicsFields[key]
242
+ if value is not None:
243
+ print(f"{key:-22s}: 0x{value[1]:04X}")
244
+ if arguments["stats"]:
245
+ keys = list(oIOSCoreDumpAnalysis.dProcessStructureStats.keys())
246
+ keys.sort()
247
+ print(f"Number of different process structures: {len(keys):d}")
248
+ for index in keys:
249
+ print(f"Process structures length: {index:d}")
250
+ PrintStatsAnalysis(
251
+ oIOSCoreDumpAnalysis.dProcessStructureStats[index],
252
+ oIOSCoreDumpAnalysis.oIOSCoreDump,
253
+ )
254
+
255
+
256
+ def FilterInitBlocksForString(coredumpFilename, searchTerm):
257
+ oIOSCoreDump = impf.cIOSCoreDump(coredumpFilename)
258
+ if oIOSCoreDump.err is not None:
259
+ return []
260
+ addressHeap, memoryHeap = oIOSCoreDump.RegionHEAP()
261
+ if memoryHeap is None:
262
+ print("Heap region not found")
263
+ return []
264
+ oIOSMemoryParser = impf.cIOSMemoryParser(memoryHeap)
265
+ oIOSMemoryParser.ResolveNames(oIOSCoreDump)
266
+ found = []
267
+ for oIOSMemoryBlockHeader in oIOSMemoryParser.Headers:
268
+ if oIOSMemoryBlockHeader.AllocNameResolved == "Init":
269
+ dStrings = uf.SearchASCIIStrings(oIOSMemoryBlockHeader.GetData())
270
+ for value in dStrings.values():
271
+ if value.find(searchTerm) >= 0:
272
+ found.append(value)
273
+ return found
274
+
275
+
276
+ def IOSHistory(coredumpFilename, arguments=None):
277
+ history = []
278
+ hist_time_format = "%b %d %Y %H:%M:%S.%f %Z"
279
+ CMD_PATTERN = re.compile(
280
+ rb"CMD: '(.+?)' " rb"(\d{2}:\d{2}:\d{2} \S+ \S+ \S+ \d{1,2} \d{4})"
281
+ )
282
+ for command in FilterInitBlocksForString(coredumpFilename, b"CMD: "):
283
+ oMatch = CMD_PATTERN.search(command)
284
+ if oMatch:
285
+ timestamp = oMatch.group(2).decode("utf-8")
286
+ dt = uf.ParseDateTime(timestamp)
287
+ history.append((dt, oMatch.group(1).decode("utf-8")))
288
+ if not history:
289
+ print("No history found")
290
+ return
291
+ for command in sorted(history, key=lambda x: x[0]):
292
+ formatted_time = command[0].strftime(hist_time_format)
293
+ print(f"{formatted_time}: {command[1]}")
294
+
295
+
296
+ def IOSEvents(coredumpFilename, arguments=None):
297
+ events = []
298
+ evt_time_format = "%b %d %Y %H:%M:%S.%f"
299
+ EVT_PATTERN = re.compile(
300
+ rb"([\w\s\d:]+\.\d{3,6})(.*)"
301
+ )
302
+ for raw_event in FilterInitBlocksForString(coredumpFilename, b": %"):
303
+ decoded_event = raw_event.decode("utf-8")
304
+ clean_event, cmd_string, _ = decoded_event.partition("CMD:")
305
+ oMatch = EVT_PATTERN.search(clean_event.encode("utf-8"))
306
+ if oMatch:
307
+ timestamp_str = oMatch.group(1).decode("utf-8").strip()
308
+ event_data = oMatch.group(2).decode("utf-8").strip(" :").rstrip()
309
+ dt_object = uf.ParseDateTime(timestamp_str, time_format=None)
310
+ events.append((dt_object, event_data))
311
+ if not events:
312
+ print("No events found")
313
+ return
314
+ for event in sorted(events, key=lambda x: x[0]):
315
+ formatted_time = event[0].strftime(evt_time_format)
316
+ print(f"{formatted_time}: {event[1]}")
317
+
318
+
319
+ def IOSCheckText(coredumpFilename, imageFilename, arguments):
320
+ print("Comparing CW_SYSDESCR between core dump and IOS image")
321
+ oIOSCoreDump = impf.cIOSCoreDump(coredumpFilename)
322
+ if oIOSCoreDump.err is not None:
323
+ print(oIOSCoreDump.err)
324
+ return
325
+ textAddress, textCoredump = oIOSCoreDump.RegionTEXT()
326
+ if textCoredump is None:
327
+ print(f"Error extracting text region from coredump {coredumpFilename}")
328
+ return
329
+ sysdescrCoredump = ""
330
+ dataAddress, dataCoredump = oIOSCoreDump.RegionDATA()
331
+ if dataCoredump is not None:
332
+ oCWStrings = impf.cCiscoCWStrings(dataCoredump)
333
+ if oCWStrings.err is None and b"CW_SYSDESCR" in oCWStrings.dCWStrings:
334
+ sysdescrCoredump = oCWStrings.dCWStrings[b"CW_SYSDESCR"].decode("utf-8")
335
+ image = uf.File2Data(imageFilename)
336
+ if image is None:
337
+ print(f"Error reading image {imageFilename}")
338
+ return
339
+ oIOSImage = iipf.cIOSImage(image, imageFilename)
340
+ if oIOSImage.err != 0:
341
+ return
342
+ sysdescrImage = ""
343
+ if (
344
+ oIOSImage.oCWStrings is not None
345
+ and oIOSImage.oCWStrings.err is None
346
+ and b"CW_SYSDESCR" in oIOSImage.oCWStrings.dCWStrings
347
+ ):
348
+ sysdescrImage = oIOSImage.oCWStrings.dCWStrings[b"CW_SYSDESCR"].decode("utf-8")
349
+ if sysdescrCoredump != "" or sysdescrImage != "":
350
+ if sysdescrCoredump == sysdescrImage:
351
+ print("CW_SYSDESCR are identical:\n")
352
+ print(sysdescrCoredump)
353
+ elif sysdescrCoredump == sysdescrImage.replace("-MZ", "-M", 1):
354
+ print("CW_SYSDESCR are equivalent:\n")
355
+ print(sysdescrCoredump)
356
+ else:
357
+ print("CW_SYSDESCR are different:\n")
358
+ print(sysdescrCoredump)
359
+ print("")
360
+ print(sysdescrImage)
361
+ print("")
362
+ oELF = iipf.cELF(oIOSImage.imageUncompressed)
363
+ if oELF.err != 0:
364
+ print(f"ELF parsing error {oELF.err:d}.")
365
+ return
366
+ countSectionExecutableInstructions = 0
367
+ countSectionSRELOC = 0
368
+ for oSectionHeader in oELF.sections:
369
+ if oSectionHeader.flags & 4: # SHF_EXECINSTR executable instructions
370
+ textSectionData = oSectionHeader.sectionData
371
+ countSectionExecutableInstructions += 1
372
+ if oSectionHeader.nameIndexString == "sreloc":
373
+ countSectionSRELOC += 1
374
+ if countSectionExecutableInstructions != 1:
375
+ print(
376
+ f"Error executable sections in image: found {countSectionExecutableInstructions:d} sections, expected 1"
377
+ )
378
+ return
379
+ if countSectionSRELOC != 0:
380
+ print(
381
+ f"Error found {countSectionSRELOC:d} sreloc section in image: checktext command does not support relocation"
382
+ )
383
+ return
384
+ start = textAddress & 0xFF # to be further researched
385
+ textImage = textSectionData[start : start + len(textCoredump)]
386
+ if len(textCoredump) != len(textImage):
387
+ print("the text region is longer than the text section")
388
+ print(f"len(textCoredump) = {len(textCoredump):d}")
389
+ print(f"len(textImage) = {len(textImage):d}")
390
+ countBytesDifferent = 0
391
+ shortestLength = min(len(textCoredump), len(textImage))
392
+ for iIter in range(shortestLength):
393
+ if textCoredump[iIter] != textImage[iIter]:
394
+ if countBytesDifferent == 0:
395
+ print(
396
+ f"text region and section are different starting 0x{(textAddress + iIter):08X} in coredump (iter = 0x{iIter:08X})"
397
+ )
398
+ countBytesDifferent += 1
399
+ if countBytesDifferent == 0:
400
+ print("text region and section are identical")
401
+ else:
402
+ print(
403
+ f"number of different bytes: {countBytesDifferent:d} ({((countBytesDifferent * 100.0) / shortestLength):.2f}%)"
404
+ )
405
+
406
+
407
+ def IOSIntegrityText(coredumpFilename, arguments):
408
+ oIOSCoreDump = impf.cIOSCoreDump(coredumpFilename)
409
+ if oIOSCoreDump.err is not None:
410
+ print(oIOSCoreDump.err)
411
+ return
412
+ addressHeap, memoryHeap = oIOSCoreDump.RegionHEAP()
413
+ if memoryHeap is None:
414
+ print("Heap region not found")
415
+ return
416
+ oIOSMemoryParser = impf.cIOSMemoryParser(memoryHeap)
417
+ print("Check start magic:")
418
+ hit = False
419
+ for oIOSMemoryBlockHeader in oIOSMemoryParser.Headers:
420
+ if oIOSMemoryBlockHeader.GetRawData()[0:4] != impf.cCiscoMagic.STR_BLOCK_BEGIN:
421
+ print(oIOSMemoryBlockHeader.ShowLine())
422
+ hit = True
423
+ if not hit:
424
+ print("OK")
425
+ print("Check end magic:")
426
+ hit = False
427
+ for oIOSMemoryBlockHeader in oIOSMemoryParser.Headers:
428
+ if (
429
+ struct.unpack(">I", oIOSMemoryBlockHeader.GetRawData()[-4:])[0]
430
+ != impf.cCiscoMagic.INT_BLOCK_CANARY
431
+ and oIOSMemoryBlockHeader.RefCnt > 0
432
+ ):
433
+ print(oIOSMemoryBlockHeader.ShowLine())
434
+ hit = True
435
+ if not hit:
436
+ print("OK")
437
+ print("Check previous block:")
438
+ hit = False
439
+ for oIOSMemoryBlockHeader in oIOSMemoryParser.Headers[1:]:
440
+ if oIOSMemoryBlockHeader.PrevBlock == 0:
441
+ print(oIOSMemoryBlockHeader.ShowLine())
442
+ hit = True
443
+ if not hit:
444
+ print("OK")
445
+ print("Check next block:")
446
+ hit = False
447
+ for oIOSMemoryBlockHeader in oIOSMemoryParser.Headers[:-1]:
448
+ if oIOSMemoryBlockHeader.NextBlock == 0:
449
+ print(oIOSMemoryBlockHeader.ShowLine())
450
+ hit = True
451
+ if not hit:
452
+ print("OK")