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 +21 -0
- naft-1.0.1/PKG-INFO +17 -0
- naft-1.0.1/README.md +3 -0
- naft-1.0.1/naft/__init__.py +0 -0
- naft-1.0.1/naft/modules/__init__.py +0 -0
- naft-1.0.1/naft/modules/gfe.py +150 -0
- naft-1.0.1/naft/modules/icd.py +452 -0
- naft-1.0.1/naft/modules/ii.py +243 -0
- naft-1.0.1/naft/modules/iipf.py +495 -0
- naft-1.0.1/naft/modules/impf.py +774 -0
- naft-1.0.1/naft/modules/pfef.py +198 -0
- naft-1.0.1/naft/modules/uf.py +234 -0
- naft-1.0.1/naft/naft.py +149 -0
- naft-1.0.1/naft.egg-info/PKG-INFO +17 -0
- naft-1.0.1/naft.egg-info/SOURCES.txt +18 -0
- naft-1.0.1/naft.egg-info/dependency_links.txt +1 -0
- naft-1.0.1/naft.egg-info/entry_points.txt +2 -0
- naft-1.0.1/naft.egg-info/top_level.txt +2 -0
- naft-1.0.1/pyproject.toml +29 -0
- naft-1.0.1/setup.cfg +4 -0
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")
|