99import string
1010import sys
1111from functools import wraps
12- from typing import Any , Callable , Dict , List , Tuple
12+ from typing import Any , Callable , Dict , List , Tuple , TypeVar , Union
1313from volatility3 .cli import text_filter
1414
1515from volatility3 .framework import exceptions , interfaces , renderers
16+ from volatility3 .framework .interfaces .renderers import BaseAbsentValue
1617from volatility3 .framework .renderers import format_hints
1718
1819vollog = logging .getLogger (__name__ )
@@ -80,7 +81,10 @@ def multitypedata_as_text(value: format_hints.MultiTypeData) -> str:
8081 return hex_bytes_as_text (value )
8182
8283
83- def optional (func : Callable ) -> Callable :
84+ T = TypeVar ("T" )
85+
86+
87+ def optional (func : Callable [[Union [BaseAbsentValue , T ]], str ]) -> Callable [[T ], str ]:
8488 @wraps (func )
8589 def wrapped (x : Any ) -> str :
8690 if isinstance (x , interfaces .renderers .BaseAbsentValue ):
@@ -110,7 +114,7 @@ def wrapped(x: Any) -> str:
110114 return wrapped
111115
112116
113- def display_disassembly (disasm : interfaces . renderers .Disassembly ) -> str :
117+ def display_disassembly (disasm : renderers .Disassembly ) -> str :
114118 """Renders a disassembly renderer type into string format.
115119
116120 Args:
@@ -137,9 +141,104 @@ def display_disassembly(disasm: interfaces.renderers.Disassembly) -> str:
137141 return QuickTextRenderer ._type_renderers [bytes ](disasm .data )
138142
139143
144+ class CLITypeRenderer (interfaces .renderers .TypeRendererInterface ):
145+ def __init__ (self , func ):
146+ super ().__init__ (func = optional (func ))
147+
148+
149+ class LayerDataRenderer (CLITypeRenderer ):
150+ """Renders a LayerData object into data/bytes"""
151+
152+ def __init__ (self ):
153+ self .context_byte_len = 0
154+ self .width = 16
155+ self .display_offset = False
156+ self .display_hex = True
157+ self .display_ascii = True
158+
159+ def render (data : Union [renderers .LayerData , BaseAbsentValue ]):
160+ if isinstance (data , BaseAbsentValue ):
161+ # FIXME: Do something cleverer here
162+ return ""
163+
164+ context_byte_len = self .context_byte_len if not data .no_surrounding else 0
165+
166+ layer = data .context .layers [data .layer_name ]
167+ # Map of the holes
168+ error_bytes = set ()
169+ start_offset = data .offset - context_byte_len
170+ end_offset = data .offset + data .length + context_byte_len
171+ if isinstance (layer , interfaces .layers .TranslationLayerInterface ):
172+ error_bytes = set ()
173+ mapping = iter (layer .mapping (start_offset , end_offset , True ))
174+ current_map = next (mapping )
175+ for i in range (start_offset , end_offset ):
176+ # Run through the bytes, check if they're present
177+ offset , sublength , _ , _ , _ = current_map
178+ if i < offset :
179+ error_bytes .add (i - start_offset )
180+ if i > offset + sublength :
181+ try :
182+ current_map = next (mapping )
183+ except StopIteration :
184+ pass
185+ offset , sublength , _ , _ , _ = current_map
186+ if i > offset + sublength :
187+ error_bytes .add (i - start_offset )
188+
189+ # Padded data
190+ specific_data = data .context .layers [data .layer_name ].read (
191+ start_offset ,
192+ end_offset - start_offset ,
193+ True ,
194+ )
195+
196+ printables = ""
197+ output = "\n "
198+ for count , byte in enumerate (specific_data ):
199+ if count not in error_bytes :
200+ output += f"{ byte :02x} "
201+ char = chr (byte )
202+ printables += char if 0x20 <= byte <= 0x7E else "."
203+ else :
204+ output += "__ "
205+ printables += "."
206+ if count % self .width == self .width - 1 :
207+ output += printables
208+ if count < len (specific_data ) - 1 :
209+ output += "\n "
210+ printables = ""
211+
212+ # Handle leftovers when the length is not mutiple of width
213+ if printables :
214+ padding = self .width - len (printables )
215+ output += " " * padding
216+ output += printables
217+ output += " " * padding
218+
219+ return output
220+
221+ render_func = render
222+ return super ().__init__ (render_func )
223+
224+
140225class CLIRenderer (interfaces .renderers .Renderer ):
141226 """Class to add specific requirements for CLI renderers."""
142227
228+ _type_renderers = {
229+ format_hints .Bin : CLITypeRenderer (lambda x : f"0b{ x :b} " ),
230+ format_hints .Hex : CLITypeRenderer (lambda x : f"0x{ x :x} " ),
231+ format_hints .HexBytes : CLITypeRenderer (hex_bytes_as_text ),
232+ format_hints .MultiTypeData : CLITypeRenderer (multitypedata_as_text ),
233+ renderers .Disassembly : CLITypeRenderer (display_disassembly ),
234+ bytes : CLITypeRenderer (lambda x : " " .join (f"{ b :02x} " for b in x )),
235+ renderers .LayerData : LayerDataRenderer (),
236+ datetime .datetime : CLITypeRenderer (
237+ lambda x : x .strftime ("%Y-%m-%d %H:%M:%S.%f %Z" )
238+ ),
239+ "default" : CLITypeRenderer (lambda x : f"{ x } " ),
240+ }
241+
143242 name = "unnamed"
144243 structured_output = False
145244 filter : text_filter .CLIFilter = None
@@ -170,21 +269,11 @@ def ignored_columns(
170269
171270
172271class QuickTextRenderer (CLIRenderer ):
173- _type_renderers = {
174- format_hints .Bin : optional (lambda x : f"0b{ x :b} " ),
175- format_hints .Hex : optional (lambda x : f"0x{ x :x} " ),
176- format_hints .HexBytes : optional (hex_bytes_as_text ),
177- format_hints .MultiTypeData : quoted_optional (multitypedata_as_text ),
178- interfaces .renderers .Disassembly : optional (display_disassembly ),
179- bytes : optional (lambda x : " " .join (f"{ b :02x} " for b in x )),
180- datetime .datetime : optional (lambda x : x .strftime ("%Y-%m-%d %H:%M:%S.%f %Z" )),
181- "default" : optional (lambda x : f"{ x } " ),
182- }
183272
184273 name = "quick"
185274
186275 def get_render_options (self ):
187- pass
276+ return []
188277
189278 def render (self , grid : interfaces .renderers .TreeGrid ) -> None :
190279 """Renders each column immediately to stdout.
@@ -242,30 +331,20 @@ class NoneRenderer(CLIRenderer):
242331 name = "none"
243332
244333 def get_render_options (self ):
245- pass
334+ return []
246335
247336 def render (self , grid : interfaces .renderers .TreeGrid ) -> None :
248337 if not grid .populated :
249338 grid .populate (lambda x , y : True , True )
250339
251340
252341class CSVRenderer (CLIRenderer ):
253- _type_renderers = {
254- format_hints .Bin : optional (lambda x : f"0b{ x :b} " ),
255- format_hints .Hex : optional (lambda x : f"0x{ x :x} " ),
256- format_hints .HexBytes : optional (hex_bytes_as_text ),
257- format_hints .MultiTypeData : optional (multitypedata_as_text ),
258- interfaces .renderers .Disassembly : optional (display_disassembly ),
259- bytes : optional (lambda x : " " .join (f"{ b :02x} " for b in x )),
260- datetime .datetime : optional (lambda x : x .strftime ("%Y-%m-%d %H:%M:%S.%f %Z" )),
261- "default" : optional (lambda x : f"{ x } " ),
262- }
263342
264343 name = "csv"
265344 structured_output = True
266345
267346 def get_render_options (self ):
268- pass
347+ return []
269348
270349 def render (self , grid : interfaces .renderers .TreeGrid ) -> None :
271350 """Renders each row immediately to stdout.
@@ -316,12 +395,10 @@ def visitor(node: interfaces.renderers.TreeNode, accumulator):
316395
317396
318397class PrettyTextRenderer (CLIRenderer ):
319- _type_renderers = QuickTextRenderer ._type_renderers
320-
321398 name = "pretty"
322399
323400 def get_render_options (self ):
324- pass
401+ return []
325402
326403 def render (self , grid : interfaces .renderers .TreeGrid ) -> None :
327404 """Renders each column immediately to stdout.
@@ -380,7 +457,7 @@ def visitor(
380457 accumulator .append ((node .path_depth , line ))
381458 return accumulator
382459
383- final_output : List [Tuple [int , Dict [interfaces .renderers .Column , bytes ]]] = []
460+ final_output : List [Tuple [int , Dict [interfaces .renderers .Column , str ]]] = []
384461 if not grid .populated :
385462 grid .populate (visitor , final_output )
386463 else :
@@ -417,9 +494,7 @@ def visitor(
417494 if column in ignore_columns :
418495 del line [column ]
419496 else :
420- line [column ] = line [column ] + (
421- ["" ] * (nums_line - len (line [column ]))
422- )
497+ line [column ] = line [column ] + ("" * (nums_line - len (line [column ])))
423498 for index in range (nums_line ):
424499 if index == 0 :
425500 outfd .write (
@@ -448,7 +523,7 @@ def tab_stop(self, line: str) -> str:
448523class JsonRenderer (CLIRenderer ):
449524 _type_renderers = {
450525 format_hints .HexBytes : quoted_optional (hex_bytes_as_text ),
451- interfaces . renderers .Disassembly : quoted_optional (display_disassembly ),
526+ renderers .Disassembly : quoted_optional (display_disassembly ),
452527 format_hints .MultiTypeData : quoted_optional (multitypedata_as_text ),
453528 bytes : optional (lambda x : " " .join (f"{ b :02x} " for b in x )),
454529 datetime .datetime : lambda x : (
@@ -463,7 +538,7 @@ class JsonRenderer(CLIRenderer):
463538 structured_output = True
464539
465540 def get_render_options (self ) -> List [interfaces .renderers .RenderOption ]:
466- pass
541+ return []
467542
468543 def output_result (self , outfd , result ):
469544 """Outputs the JSON data to a file in a particular format"""
0 commit comments