@@ -173,3 +173,140 @@ def run(self):
173173 ],
174174 self ._generator (),
175175 )
176+
177+
178+ class ADS (interfaces .plugins .PluginInterface ):
179+
180+ """Scans for Alternate Data Stream"""
181+
182+ _required_framework_version = (2 , 0 , 0 )
183+
184+ @classmethod
185+ def get_requirements (cls ):
186+ return [
187+ requirements .TranslationLayerRequirement (
188+ name = "primary" ,
189+ description = "Memory layer for the kernel" ,
190+ architectures = ["Intel32" , "Intel64" ],
191+ ),
192+ requirements .VersionRequirement (
193+ name = "yarascanner" , component = yarascan .YaraScanner , version = (2 , 0 , 0 )
194+ ),
195+ ]
196+
197+ def _generator (self ):
198+ layer = self .context .layers [self .config ["primary" ]]
199+
200+ # Yara Rule to scan for MFT Header Signatures
201+ rules = yarascan .YaraScan .process_yara_options (
202+ {"yara_rules" : "/FILE0|FILE\*|BAAD/" }
203+ )
204+
205+ # Read in the Symbol File
206+ symbol_table = intermed .IntermediateSymbolTable .create (
207+ context = self .context ,
208+ config_path = self .config_path ,
209+ sub_path = "windows" ,
210+ filename = "mft" ,
211+ class_types = {
212+ "MFT_ENTRY" : mft .MFTEntry ,
213+ "FILE_NAME_ENTRY" : mft .MFTFileName ,
214+ "ATTRIBUTE" : mft .MFTAttribute ,
215+ },
216+ )
217+
218+ # get each of the individual Field Sets
219+ mft_object = symbol_table + constants .BANG + "MFT_ENTRY"
220+ attribute_object = symbol_table + constants .BANG + "ATTRIBUTE"
221+ fn_object = symbol_table + constants .BANG + "FILE_NAME_ENTRY"
222+
223+ # Scan the layer for Raw MFT records and parse the fields
224+ for offset , _rule_name , _name , _value in layer .scan (
225+ context = self .context , scanner = yarascan .YaraScanner (rules = rules )
226+ ):
227+ with contextlib .suppress (exceptions .PagedInvalidAddressException ):
228+ mft_record = self .context .object (
229+ mft_object , offset = offset , layer_name = layer .name
230+ )
231+ # We will update this on each pass in the next loop and use it as the new offset.
232+ attr_base_offset = mft_record .FirstAttrOffset
233+
234+ attr = self .context .object (
235+ attribute_object ,
236+ offset = offset + attr_base_offset ,
237+ layer_name = layer .name ,
238+ )
239+
240+ # There is no field that has a count of Attributes
241+ # Keep Attempting to read attributes until we get an invalid attr.AttrType
242+ is_ads = False
243+ file_name = renderers .NotAvailableValue
244+ # The First $DATA Attr is the 'principal' file itself not the ADS
245+ while attr .Attr_Header .AttrType .is_valid_choice :
246+ if attr .Attr_Header .AttrType .lookup () == "FILE_NAME" :
247+ attr_data = attr .Attr_Data .cast (fn_object )
248+ file_name = attr_data .get_full_name ()
249+ if attr .Attr_Header .AttrType .lookup () == "DATA" :
250+ if is_ads :
251+ if not attr .Attr_Header .NonResidentFlag :
252+ # Resident files are the most interesting.
253+ if attr .Attr_Header .NameLength > 0 :
254+ ads_name = attr .get_resident_filename ()
255+ if not ads_name :
256+ ads_name = renderers .NotAvailableValue
257+
258+ content = attr .get_resident_filecontent ()
259+ if content :
260+ # Preparing for Disassembly
261+ disasm = interfaces .renderers .BaseAbsentValue
262+ architecture = layer .metadata .get (
263+ "architecture" , None
264+ )
265+ if architecture :
266+ disasm = interfaces .renderers .Disassembly (
267+ content , 0 , architecture .lower ()
268+ )
269+ else :
270+ content = renderers .NotAvailableValue
271+ disasm = interfaces .renderers .BaseAbsentValue
272+
273+ yield 0 , (
274+ format_hints .Hex (attr_data .vol .offset ),
275+ mft_record .get_signature (),
276+ mft_record .RecordNumber ,
277+ attr .Attr_Header .AttrType .lookup (),
278+ file_name ,
279+ ads_name ,
280+ format_hints .HexBytes (content ),
281+ disasm ,
282+ )
283+ else :
284+ is_ads = True
285+
286+ # If there's no advancement the loop will never end, so break it now
287+ if attr .Attr_Header .Length == 0 :
288+ break
289+
290+ # Update the base offset to point to the next attribute
291+ attr_base_offset += attr .Attr_Header .Length
292+ # Get the next attribute
293+ attr = self .context .object (
294+ attribute_object ,
295+ offset = offset + attr_base_offset ,
296+ layer_name = layer .name ,
297+ )
298+
299+ def run (self ):
300+ return renderers .TreeGrid (
301+ [
302+ ("Offset" , format_hints .Hex ),
303+ ("Record Type" , str ),
304+ ("Record Number" , int ),
305+ ("MFT Type" , str ),
306+ ("Filename" , str ),
307+ ("ADS Filename" , str ),
308+ ("Hexdump" , format_hints .HexBytes ),
309+ ("Disasm" , interfaces .renderers .Disassembly ),
310+ ],
311+ self ._generator (),
312+ )
0 commit comments