33# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
44#
55
6+ import ctypes
67import os , re
78from typing import Any , Callable , Iterator , List , Mapping , MutableSequence , Optional , Pattern , Sequence , Tuple , Union
89
1112from qiling import Qiling
1213from qiling .exception import *
1314
14- # tuple: range start, range end, permissions mask, range label, is mmio?
15- MapInfoEntry = Tuple [int , int , int , str , bool ]
15+ # tuple: range start, range end, permissions mask, range label, is mmio?, bytearray
16+ MapInfoEntry = Tuple [int , int , int , str , bool , bytearray ]
1617
1718MmioReadCallback = Callable [[Qiling , int , int ], int ]
1819MmioWriteCallback = Callable [[Qiling , int , int , int ], None ]
@@ -48,6 +49,8 @@ def __init__(self, ql: Qiling):
4849 # make sure pagesize is a power of 2
4950 assert self .pagesize & (self .pagesize - 1 ) == 0 , 'pagesize has to be a power of 2'
5051
52+ self .cmap = {} # mapping from start addr to cdata ptr
53+
5154 def __read_string (self , addr : int ) -> str :
5255 ret = bytearray ()
5356 c = self .read (addr , 1 )
@@ -80,7 +83,7 @@ def string(self, addr: int, value=None, encoding='utf-8') -> Optional[str]:
8083
8184 self .__write_string (addr , value , encoding )
8285
83- def add_mapinfo (self , mem_s : int , mem_e : int , mem_p : int , mem_info : str , is_mmio : bool = False ):
86+ def add_mapinfo (self , mem_s : int , mem_e : int , mem_p : int , mem_info : str , is_mmio : bool = False , data : bytearray = None ):
8487 """Add a new memory range to map.
8588
8689 Args:
@@ -90,12 +93,11 @@ def add_mapinfo(self, mem_s: int, mem_e: int, mem_p: int, mem_info: str, is_mmio
9093 mem_info: map entry label
9194 is_mmio: memory range is mmio
9295 """
93-
94- self .map_info .append ((mem_s , mem_e , mem_p , mem_info , is_mmio ))
95- self .map_info = sorted (self .map_info , key = lambda tp : tp [0 ])
96+ self .map_info .append ((mem_s , mem_e , mem_p , mem_info , is_mmio , data ))
97+ self .map_info .sort (key = lambda tp : tp [0 ])
9698
9799 def del_mapinfo (self , mem_s : int , mem_e : int ):
98- """Subtract a memory range from map.
100+ """Subtract a memory range from map, will destroy data and unmap uc mem in the range .
99101
100102 Args:
101103 mem_s: memory range start
@@ -104,30 +106,37 @@ def del_mapinfo(self, mem_s: int, mem_e: int):
104106
105107 tmp_map_info : MutableSequence [MapInfoEntry ] = []
106108
107- for s , e , p , info , mmio in self .map_info :
109+ for s , e , p , info , mmio , data in self .map_info :
108110 if e <= mem_s :
109- tmp_map_info .append ((s , e , p , info , mmio ))
111+ tmp_map_info .append ((s , e , p , info , mmio , data ))
110112 continue
111113
112114 if s >= mem_e :
113- tmp_map_info .append ((s , e , p , info , mmio ))
115+ tmp_map_info .append ((s , e , p , info , mmio , data ))
114116 continue
115117
118+ del self .cmap [s ] # remove cdata reference starting at s
116119 if s < mem_s :
117- tmp_map_info .append ((s , mem_s , p , info , mmio ))
120+ self .ql .uc .mem_unmap (s , mem_s - s )
121+ self .map_ptr (s , mem_s - s , p , data [:mem_s - s ])
122+ tmp_map_info .append ((s , mem_s , p , info , mmio , data [:mem_s - s ]))
118123
119124 if s == mem_s :
120125 pass
121126
122127 if e > mem_e :
123- tmp_map_info .append ((mem_e , e , p , info , mmio ))
128+ self .ql .uc .mem_unmap (mem_e , e - mem_e )
129+ self .map_ptr (mem_e , e - mem_e , p , data [mem_e - e :])
130+ tmp_map_info .append ((mem_e , e , p , info , mmio , data [mem_e - e :]))
124131
125132 if e == mem_e :
126133 pass
127134
135+ del data [mem_s - s :mem_e - s ]
136+
128137 self .map_info = tmp_map_info
129138
130- def change_mapinfo (self , mem_s : int , mem_e : int , mem_p : Optional [int ] = None , mem_info : Optional [str ] = None ):
139+ def change_mapinfo (self , mem_s : int , mem_e : int , mem_p : Optional [int ] = None , mem_info : Optional [str ] = None , data : Optional [ bytearray ] = None ):
131140 tmp_map_info : Optional [MapInfoEntry ] = None
132141 info_idx : int = None
133142
@@ -142,12 +151,15 @@ def change_mapinfo(self, mem_s: int, mem_e: int, mem_p: Optional[int] = None, me
142151 return
143152
144153 if mem_p is not None :
145- self .del_mapinfo (mem_s , mem_e )
146- self .add_mapinfo (mem_s , mem_e , mem_p , mem_info if mem_info else tmp_map_info [3 ])
154+ data = data or self .read (mem_s , mem_e - mem_s ).copy ()
155+ assert (len (data ) == mem_e - mem_s )
156+ self .unmap (mem_s , mem_e - mem_s )
157+ self .map_ptr (mem_s , mem_e - mem_s , mem_p , data )
158+ self .add_mapinfo (mem_s , mem_e , mem_p , mem_info or tmp_map_info [3 ], tmp_map_info [4 ], data )
147159 return
148160
149161 if mem_info is not None :
150- self .map_info [info_idx ] = (tmp_map_info [0 ], tmp_map_info [1 ], tmp_map_info [2 ], mem_info , tmp_map_info [4 ])
162+ self .map_info [info_idx ] = (tmp_map_info [0 ], tmp_map_info [1 ], tmp_map_info [2 ], mem_info , tmp_map_info [4 ], tmp_map_info [ 5 ] )
151163
152164 def get_mapinfo (self ) -> Sequence [Tuple [int , int , str , str , str ]]:
153165 """Get memory map info.
@@ -166,7 +178,7 @@ def __perms_mapping(ps: int) -> str:
166178
167179 return '' .join (val if idx & ps else '-' for idx , val in perms_d .items ())
168180
169- def __process (lbound : int , ubound : int , perms : int , label : str , is_mmio : bool ) -> Tuple [int , int , str , str , str ]:
181+ def __process (lbound : int , ubound : int , perms : int , label : str , is_mmio : bool , _data : bytearray ) -> Tuple [int , int , str , str , str ]:
170182 perms_str = __perms_mapping (perms )
171183
172184 if hasattr (self .ql , 'loader' ):
@@ -211,7 +223,7 @@ def get_lib_base(self, filename: str) -> Optional[int]:
211223
212224 # some info labels may be prefixed by boxed label which breaks the search by basename.
213225 # iterate through all info labels and remove all boxed prefixes, if any
214- stripped = ((lbound , p .sub ('' , info )) for lbound , _ , _ , info , _ in self .map_info )
226+ stripped = ((lbound , p .sub ('' , info )) for lbound , _ , _ , info , _ , _ in self .map_info )
215227
216228 return next ((lbound for lbound , info in stripped if os .path .basename (info ) == filename ), None )
217229
@@ -268,12 +280,11 @@ def save(self):
268280 "mmio" : []
269281 }
270282
271- for lbound , ubound , perm , label , is_mmio in self .map_info :
283+ for lbound , ubound , perm , label , is_mmio , data in self .map_info :
272284 if is_mmio :
273285 mem_dict ['mmio' ].append ((lbound , ubound , perm , label , * self .mmio_cbs [(lbound , ubound )]))
274286 else :
275- data = self .read (lbound , ubound - lbound )
276- mem_dict ['ram' ].append ((lbound , ubound , perm , label , bytes (data )))
287+ mem_dict ['ram' ].append ((lbound , ubound , perm , label , data ))
277288
278289 return mem_dict
279290
@@ -287,10 +298,10 @@ def restore(self, mem_dict):
287298 size = ubound - lbound
288299 if self .is_available (lbound , size ):
289300 self .ql .log .debug (f'mapping { lbound :#08x} { ubound :#08x} , mapsize = { size :#x} ' )
290- self .map (lbound , size , perms , label )
301+ self .map (lbound , size , perms , label , data )
291302
292303 self .ql .log .debug (f'writing { len (data ):#x} bytes at { lbound :#08x} ' )
293- self .write (lbound , data )
304+ self .write (lbound , bytes ( data ) )
294305
295306 for lbound , ubound , perms , label , read_cb , write_cb in mem_dict ['mmio' ]:
296307 self .ql .log .debug (f"restoring mmio range: { lbound :#08x} { ubound :#08x} { label } " )
@@ -393,7 +404,7 @@ def search(self, needle: Union[bytes, Pattern[bytes]], begin: Optional[int] = No
393404 assert begin < end , 'search arguments do not make sense'
394405
395406 # narrow the search down to relevant ranges; mmio ranges are excluded due to potential read size effects
396- ranges = [(max (begin , lbound ), min (ubound , end )) for lbound , ubound , _ , _ , is_mmio in self .map_info if not (end < lbound or ubound < begin or is_mmio )]
407+ ranges = [(max (begin , lbound ), min (ubound , end )) for lbound , ubound , _ , _ , is_mmio , _data in self .map_info if not (end < lbound or ubound < begin or is_mmio )]
397408 results = []
398409
399410 # if needle is a bytes sequence use it verbatim, not as a pattern
@@ -439,10 +450,10 @@ def __mapped_regions(self) -> Iterator[Tuple[int, int]]:
439450
440451 iter_memmap = iter (self .map_info )
441452
442- p_lbound , p_ubound , _ , _ , _ = next (iter_memmap )
453+ p_lbound , p_ubound , _ , _ , _ , _ = next (iter_memmap )
443454
444455 # map_info is assumed to contain non-overlapping regions sorted by lbound
445- for lbound , ubound , _ , _ , _ in iter_memmap :
456+ for lbound , ubound , _ , _ , _ , _ in iter_memmap :
446457 if lbound == p_ubound :
447458 p_ubound = ubound
448459 else :
@@ -514,8 +525,8 @@ def find_free_space(self, size: int, minaddr: Optional[int] = None, maxaddr: Opt
514525 assert minaddr < maxaddr
515526
516527 # get gap ranges between mapped ones and memory bounds
517- gaps_ubounds = tuple (lbound for lbound , _ , _ , _ , _ in self .map_info ) + (mem_ubound ,)
518- gaps_lbounds = (mem_lbound ,) + tuple (ubound for _ , ubound , _ , _ , _ in self .map_info )
528+ gaps_ubounds = tuple (lbound for lbound , _ , _ , _ , _ , _ in self .map_info ) + (mem_ubound ,)
529+ gaps_lbounds = (mem_lbound ,) + tuple (ubound for _ , ubound , _ , _ , _ , _ in self .map_info )
519530 gaps = zip (gaps_lbounds , gaps_ubounds )
520531
521532 for lbound , ubound in gaps :
@@ -563,7 +574,7 @@ def protect(self, addr: int, size: int, perms):
563574 self .change_mapinfo (aligned_address , aligned_address + aligned_size , mem_p = perms )
564575
565576
566- def map (self , addr : int , size : int , perms : int = UC_PROT_ALL , info : Optional [str ] = None ):
577+ def map (self , addr : int , size : int , perms : int = UC_PROT_ALL , info : Optional [str ] = None , ptr : Optional [ bytearray ] = None ):
567578 """Map a new memory range.
568579
569580 Args:
@@ -580,10 +591,31 @@ def map(self, addr: int, size: int, perms: int = UC_PROT_ALL, info: Optional[str
580591 assert perms & ~ UC_PROT_ALL == 0 , f'unexpected permissions mask { perms } '
581592
582593 if not self .is_available (addr , size ):
583- raise QlMemoryMappedError ('Requested memory is unavailable' )
594+ for line in self .get_formatted_mapinfo ():
595+ print (line )
596+ raise QlMemoryMappedError (f'Requested memory { addr :#x} + { size :#x} is unavailable' )
584597
585- self .ql .uc .mem_map (addr , size , perms )
586- self .add_mapinfo (addr , addr + size , perms , info or '[mapped]' , is_mmio = False )
598+ buf = self .map_ptr (addr , size , perms , ptr )
599+ self .add_mapinfo (addr , addr + size , perms , info or '[mapped]' , is_mmio = False , data = buf )
600+
601+ def map_ptr (self , addr : int , size : int , perms : int = UC_PROT_ALL , buf : Optional [bytearray ] = None ) -> bytearray :
602+ """Map a new memory range allocated as Python bytearray, will not affect map_info
603+
604+ Args:
605+ addr: memory range base address
606+ size: memory range size (in bytes)
607+ perms: requested permissions mask
608+ buf: bytearray already allocated (if any)
609+
610+ Returns:
611+ bytearray with size, should be added to map_info by caller
612+ """
613+ buf = buf or bytearray (size )
614+ buf_type = ctypes .c_byte * size
615+ cdata = buf_type .from_buffer (buf )
616+ self .cmap [addr ] = cdata
617+ self .ql .uc .mem_map_ptr (addr , size , perms , cdata )
618+ return buf
587619
588620 def map_mmio (self , addr : int , size : int , read_cb : Optional [MmioReadCallback ], write_cb : Optional [MmioWriteCallback ], info : str = '[mmio]' ):
589621 # TODO: mmio memory overlap with ram? Is that possible?
0 commit comments