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 ]
@@ -80,7 +81,7 @@ def string(self, addr: int, value=None, encoding='utf-8') -> Optional[str]:
8081
8182 self .__write_string (addr , value , encoding )
8283
83- def add_mapinfo (self , mem_s : int , mem_e : int , mem_p : int , mem_info : str , is_mmio : bool = False ):
84+ def add_mapinfo (self , mem_s : int , mem_e : int , mem_p : int , mem_info : str , is_mmio : bool = False , data : bytearray = None ):
8485 """Add a new memory range to map.
8586
8687 Args:
@@ -90,12 +91,11 @@ def add_mapinfo(self, mem_s: int, mem_e: int, mem_p: int, mem_info: str, is_mmio
9091 mem_info: map entry label
9192 is_mmio: memory range is mmio
9293 """
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 ])
94+ self .map_info .append ((mem_s , mem_e , mem_p , mem_info , is_mmio , data ))
95+ self .map_info .sort (key = lambda tp : tp [0 ])
9696
9797 def del_mapinfo (self , mem_s : int , mem_e : int ):
98- """Subtract a memory range from map.
98+ """Subtract a memory range from map, will destroy data and unmap uc mem in the range .
9999
100100 Args:
101101 mem_s: memory range start
@@ -104,30 +104,37 @@ def del_mapinfo(self, mem_s: int, mem_e: int):
104104
105105 tmp_map_info : MutableSequence [MapInfoEntry ] = []
106106
107- for s , e , p , info , mmio in self .map_info :
107+ for s , e , p , info , mmio , data in self .map_info :
108108 if e <= mem_s :
109- tmp_map_info .append ((s , e , p , info , mmio ))
109+ tmp_map_info .append ((s , e , p , info , mmio , data ))
110110 continue
111111
112112 if s >= mem_e :
113- tmp_map_info .append ((s , e , p , info , mmio ))
113+ tmp_map_info .append ((s , e , p , info , mmio , data ))
114114 continue
115115
116116 if s < mem_s :
117- tmp_map_info .append ((s , mem_s , p , info , mmio ))
117+ self .ql .uc .mem_unmap (s , mem_s - s )
118+ self .map_ptr (s , mem_s - s , p , data [:mem_s - s ])
119+ tmp_map_info .append ((s , mem_s , p , info , mmio , data [:mem_s - s ]))
118120
119121 if s == mem_s :
120122 pass
121123
122124 if e > mem_e :
123- tmp_map_info .append ((mem_e , e , p , info , mmio ))
125+ self .ql .uc .mem_unmap (mem_e , e - mem_e )
126+ self .map_ptr (mem_e , e - mem_e , p , data [mem_e - e :])
127+ tmp_map_info .append ((mem_e , e , p , info , mmio , data [mem_e - e :]))
124128
125129 if e == mem_e :
126130 pass
127131
132+ del data [mem_s - s :mem_e - s ]
133+ self .ql .uc .mem_unmap (mem_s , mem_e - mem_s )
134+
128135 self .map_info = tmp_map_info
129136
130- def change_mapinfo (self , mem_s : int , mem_e : int , mem_p : Optional [int ] = None , mem_info : Optional [str ] = None ):
137+ def change_mapinfo (self , mem_s : int , mem_e : int , mem_p : Optional [int ] = None , mem_info : Optional [str ] = None , data : Optional [ bytearray ] = None ):
131138 tmp_map_info : Optional [MapInfoEntry ] = None
132139 info_idx : int = None
133140
@@ -142,12 +149,15 @@ def change_mapinfo(self, mem_s: int, mem_e: int, mem_p: Optional[int] = None, me
142149 return
143150
144151 if mem_p is not None :
152+ data = data or self .read (mem_s , mem_e - mem_s )
153+ assert (len (data ) == mem_e - mem_s )
145154 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 ])
155+ self .map_ptr (mem_s , mem_e - mem_s , mem_p , data )
156+ self .add_mapinfo (mem_s , mem_e , mem_p , mem_info or tmp_map_info [3 ], tmp_map_info [4 ], data )
147157 return
148158
149159 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 ])
160+ 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 ] )
151161
152162 def get_mapinfo (self ) -> Sequence [Tuple [int , int , str , str , str ]]:
153163 """Get memory map info.
@@ -166,7 +176,7 @@ def __perms_mapping(ps: int) -> str:
166176
167177 return '' .join (val if idx & ps else '-' for idx , val in perms_d .items ())
168178
169- def __process (lbound : int , ubound : int , perms : int , label : str , is_mmio : bool ) -> Tuple [int , int , str , str , str ]:
179+ def __process (lbound : int , ubound : int , perms : int , label : str , is_mmio : bool , _data : bytearray ) -> Tuple [int , int , str , str , str ]:
170180 perms_str = __perms_mapping (perms )
171181
172182 if hasattr (self .ql , 'loader' ):
@@ -211,7 +221,7 @@ def get_lib_base(self, filename: str) -> Optional[int]:
211221
212222 # some info labels may be prefixed by boxed label which breaks the search by basename.
213223 # 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 )
224+ stripped = ((lbound , p .sub ('' , info )) for lbound , _ , _ , info , _ , _ in self .map_info )
215225
216226 return next ((lbound for lbound , info in stripped if os .path .basename (info ) == filename ), None )
217227
@@ -268,11 +278,10 @@ def save(self):
268278 "mmio" : []
269279 }
270280
271- for lbound , ubound , perm , label , is_mmio in self .map_info :
281+ for lbound , ubound , perm , label , is_mmio , data in self .map_info :
272282 if is_mmio :
273283 mem_dict ['mmio' ].append ((lbound , ubound , perm , label , * self .mmio_cbs [(lbound , ubound )]))
274284 else :
275- data = self .read (lbound , ubound - lbound )
276285 mem_dict ['ram' ].append ((lbound , ubound , perm , label , bytes (data )))
277286
278287 return mem_dict
@@ -393,7 +402,7 @@ def search(self, needle: Union[bytes, Pattern[bytes]], begin: Optional[int] = No
393402 assert begin < end , 'search arguments do not make sense'
394403
395404 # 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 )]
405+ 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 )]
397406 results = []
398407
399408 # if needle is a bytes sequence use it verbatim, not as a pattern
@@ -439,10 +448,10 @@ def __mapped_regions(self) -> Iterator[Tuple[int, int]]:
439448
440449 iter_memmap = iter (self .map_info )
441450
442- p_lbound , p_ubound , _ , _ , _ = next (iter_memmap )
451+ p_lbound , p_ubound , _ , _ , _ , _ = next (iter_memmap )
443452
444453 # map_info is assumed to contain non-overlapping regions sorted by lbound
445- for lbound , ubound , _ , _ , _ in iter_memmap :
454+ for lbound , ubound , _ , _ , _ , _ in iter_memmap :
446455 if lbound == p_ubound :
447456 p_ubound = ubound
448457 else :
@@ -514,8 +523,8 @@ def find_free_space(self, size: int, minaddr: Optional[int] = None, maxaddr: Opt
514523 assert minaddr < maxaddr
515524
516525 # 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 )
526+ gaps_ubounds = tuple (lbound for lbound , _ , _ , _ , _ , _ in self .map_info ) + (mem_ubound ,)
527+ gaps_lbounds = (mem_lbound ,) + tuple (ubound for _ , ubound , _ , _ , _ , _ in self .map_info )
519528 gaps = zip (gaps_lbounds , gaps_ubounds )
520529
521530 for lbound , ubound in gaps :
@@ -582,8 +591,25 @@ def map(self, addr: int, size: int, perms: int = UC_PROT_ALL, info: Optional[str
582591 if not self .is_available (addr , size ):
583592 raise QlMemoryMappedError ('Requested memory is unavailable' )
584593
585- self .ql .uc .mem_map (addr , size , perms )
586- self .add_mapinfo (addr , addr + size , perms , info or '[mapped]' , is_mmio = False )
594+ buf = self .map_ptr (addr , size , perms )
595+ self .add_mapinfo (addr , addr + size , perms , info or '[mapped]' , is_mmio = False , data = buf )
596+
597+ def map_ptr (self , addr : int , size : int , perms : int = UC_PROT_ALL , buf : Optional [bytearray ] = None ) -> bytearray :
598+ """Map a new memory range allocated as Python bytearray, will not affect map_info
599+
600+ Args:
601+ addr: memory range base address
602+ size: memory range size (in bytes)
603+ perms: requested permissions mask
604+ buf: bytearray already allocated (if any)
605+
606+ Returns:
607+ bytearray with size, should be added to map_info by caller
608+ """
609+ buf = buf or bytearray (size )
610+ buf_type = ctypes .c_byte * size
611+ self .ql .uc .mem_map_ptr (addr , size , perms , buf_type .from_buffer (buf ))
612+ return buf
587613
588614 def map_mmio (self , addr : int , size : int , read_cb : Optional [MmioReadCallback ], write_cb : Optional [MmioWriteCallback ], info : str = '[mmio]' ):
589615 # TODO: mmio memory overlap with ram? Is that possible?
0 commit comments