1- from binascii import hexlify
21from collections import defaultdict , namedtuple
32from enum import Enum
4- from typing import Optional
3+ from typing import Optional , Iterator
54
65from asn1crypto .cms import ContentInfo
76from asn1crypto .core import Sequence
@@ -41,7 +40,7 @@ class CatrootFileType(str, Enum):
4140CatrootFileInfo = namedtuple ("CatrootFileInfo" , ["file_type" , "pattern" , "base_path" ])
4241
4342
44- def findall (buf , needle ) :
43+ def findall (buf : bytes , needle : bytes ) -> Iterator [ int ] :
4544 offset = 0
4645 while True :
4746 offset = buf .find (needle , offset )
@@ -101,7 +100,7 @@ def check_compatible(self) -> None:
101100 raise UnsupportedPluginError ("No catroot files or catroot ESE files found" )
102101
103102 @export (record = CatrootRecord )
104- def files (self ):
103+ def files (self ) -> Iterator [ CatrootRecord ] :
105104 """Return the content of the catalog files in the CatRoot folder.
106105
107106 A catalog file contains a collection of cryptographic hashes, or thumbprints. These files are generally used to
@@ -114,8 +113,8 @@ def files(self):
114113 Yields CatrootRecords with the following fields:
115114 hostname (string): The target hostname.
116115 domain (string): The target domain.
117- digest (digest): The parsed digest .
118- hint (string): File hint , if present.
116+ digests (digest[] ): The parsed digests .
117+ hints (string[] ): File hints , if present.
119118 filename (string): catroot filename.
120119 source (path): Source catroot file.
121120 """
@@ -165,31 +164,29 @@ def files(self):
165164 # the format is not yet known and therefore not supported.
166165 hints = []
167166 try :
168- # As far as known, the hint data is only present in the encap_content_info after the last digest.
169- hint_buf = encap_contents [offset + len (needle ) + len (raw_digest ) + 2 :]
167+ if offset :
168+ # As far as known, the PackageName data is only present in the encap_content_info after the last digest.
169+ hint_buf = encap_contents [offset + len (needle ) + len (raw_digest ) + 2 :]
170170
171- # First try to find to find the "PackageName" value, if it's present.
172- hint = find_package_name (hint_buf )
173- if hint :
174- hints .append (hint )
171+ # First try to find to find the "PackageName" value, if it's present.
172+ hint = find_package_name (hint_buf )
173+ if hint :
174+ hints .append (hint )
175175
176- # If the package_name needle is not found or it's present in the first 7 bytes of the hint_buf
177- # We are dealing with a catroot file including a file hint.
176+ # If the package_name needle is not found or it's not present in the first 7 bytes of the hint_buf
177+ # We are probably dealing with a catroot file that contains " hint" needle .
178178 if not hints :
179179 for hint_offset in findall (encap_contents , NEEDLES ["hint" ]):
180180 # Either 3 or 4 bytes before the needle, a sequence starts
181- if encap_contents [hint_offset - 3 ] == 48 :
182- name_sequence = Sequence .load (encap_contents [hint_offset - 3 :])
183- else :
184- name_sequence = Sequence .load (encap_contents [hint_offset - 4 :])
181+ bytes_before_needle = 3 if encap_contents [hint_offset - 3 ] == 48 else 4
182+ name_sequence = Sequence .load (encap_contents [hint_offset - bytes_before_needle :])
185183
186184 hint = name_sequence [2 ].native .decode ("utf-16-le" ).strip ("\x00 " )
187185 hints .append (hint )
188- self .target .log .error (f"{ hint } - { file } " )
189186
190187 except Exception as error :
191188 self .target .log .warning (
192- f"An error occurred while parsing the hint for catroot file ' { file } ': { error } "
189+ f"An error occurred while parsing the hint for catroot file %s: %s" , file , error
193190 )
194191
195192 yield CatrootRecord (
@@ -201,13 +198,13 @@ def files(self):
201198 )
202199
203200 except Exception as error :
204- self .target .log .error (f"An error occurred while parsing the catroot file ' { file } ': { error } " )
201+ self .target .log .error (f"An error occurred while parsing the catroot file %s: %s" , file , error )
205202
206203 @export (record = CatrootRecord )
207- def catdb (self ):
204+ def catdb (self ) -> Iterator [ CatrootRecord ] :
208205 """Return the hash values present in the catdb files in the catroot2 folder.
209206
210- The catdb file is a ESE database file that contains the digests of the catalog files present on the system.
207+ The catdb file is an ESE database file that contains the digests of the catalog files present on the system.
211208 This database is used to speed up the process of validating a Portable Executable (PE) file.
212209
213210 References:
@@ -217,8 +214,8 @@ def catdb(self):
217214 Yields CatrootRecords with the following fields:
218215 hostname (string): The target hostname.
219216 domain (string): The target domain.
220- digest (digest): The parsed digest .
221- hint (string): File hint , if present.
217+ digests (digest[] ): The parsed digests .
218+ hints (string[] ): File hints , if present.
222219 filename (string): catroot filename.
223220 source (path): Source catroot file.
224221 """
@@ -235,7 +232,7 @@ def catdb(self):
235232
236233 for record in ese_db .table (table_name ).records ():
237234 file_digest = digest ()
238- setattr (file_digest , hash_type , hexlify ( record .get ("HashCatNameTable_HashCol" )). decode ( "utf-8" ))
235+ setattr (file_digest , hash_type , record .get ("HashCatNameTable_HashCol" ). hex ( ))
239236 raw_hint = record .get ("HashCatNameTable_CatNameCol" ).decode ("utf-8" ).rstrip ("|" )
240237 # Group by raw_hint to get one record per raw_hint containing all digests (SHA256, SHA1)
241238 catroot_records [raw_hint ].append (file_digest )
0 commit comments