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 ]
1920
21+ def assert_mem_equal (ql : "Qiling" ):
22+ map_info = ql .mem .map_info
23+ mem_regions = list (ql .uc .mem_regions ())
24+ assert len (map_info ) == len (mem_regions ), f'map_info={ len (map_info )} != mem_regions={ len (mem_regions )} '
25+ for i , mem_region in enumerate (mem_regions ):
26+ s , e , p , _ , _ , data = map_info [i ]
27+ assert (s , e - 1 , p ) == mem_region
28+ uc_mem = ql .mem .read (mem_region [0 ], mem_region [1 ] - mem_region [0 ] + 1 )
29+ assert len (data ) == len (uc_mem )
30+ if data == uc_mem : continue
31+ print (f"Memory region { i } { mem_region [0 ]:#x} - { mem_region [1 ]:#x} not equal to map_info[{ i } ]" )
32+ assert False
33+
2034class QlMemoryManager :
2135 """
2236 some ideas and code from:
@@ -48,6 +62,8 @@ def __init__(self, ql: Qiling):
4862 # make sure pagesize is a power of 2
4963 assert self .pagesize & (self .pagesize - 1 ) == 0 , 'pagesize has to be a power of 2'
5064
65+ self .cmap = {} # mapping from start addr to cdata ptr
66+
5167 def __read_string (self , addr : int ) -> str :
5268 ret = bytearray ()
5369 c = self .read (addr , 1 )
@@ -80,7 +96,7 @@ def string(self, addr: int, value=None, encoding='utf-8') -> Optional[str]:
8096
8197 self .__write_string (addr , value , encoding )
8298
83- def add_mapinfo (self , mem_s : int , mem_e : int , mem_p : int , mem_info : str , is_mmio : bool = False ):
99+ def add_mapinfo (self , mem_s : int , mem_e : int , mem_p : int , mem_info : str , is_mmio : bool = False , data : bytearray = None ):
84100 """Add a new memory range to map.
85101
86102 Args:
@@ -90,12 +106,11 @@ def add_mapinfo(self, mem_s: int, mem_e: int, mem_p: int, mem_info: str, is_mmio
90106 mem_info: map entry label
91107 is_mmio: memory range is mmio
92108 """
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 ])
109+ self .map_info .append ((mem_s , mem_e , mem_p , mem_info , is_mmio , data ))
110+ self .map_info .sort (key = lambda tp : tp [0 ])
96111
97112 def del_mapinfo (self , mem_s : int , mem_e : int ):
98- """Subtract a memory range from map.
113+ """Subtract a memory range from map, will destroy data and unmap uc mem in the range .
99114
100115 Args:
101116 mem_s: memory range start
@@ -104,30 +119,37 @@ def del_mapinfo(self, mem_s: int, mem_e: int):
104119
105120 tmp_map_info : MutableSequence [MapInfoEntry ] = []
106121
107- for s , e , p , info , mmio in self .map_info :
122+ for s , e , p , info , mmio , data in self .map_info :
108123 if e <= mem_s :
109- tmp_map_info .append ((s , e , p , info , mmio ))
124+ tmp_map_info .append ((s , e , p , info , mmio , data ))
110125 continue
111126
112127 if s >= mem_e :
113- tmp_map_info .append ((s , e , p , info , mmio ))
128+ tmp_map_info .append ((s , e , p , info , mmio , data ))
114129 continue
115130
131+ del self .cmap [s ] # remove cdata reference starting at s
116132 if s < mem_s :
117- tmp_map_info .append ((s , mem_s , p , info , mmio ))
133+ self .ql .uc .mem_unmap (s , mem_s - s )
134+ self .map_ptr (s , mem_s - s , p , data [:mem_s - s ])
135+ tmp_map_info .append ((s , mem_s , p , info , mmio , data [:mem_s - s ]))
118136
119137 if s == mem_s :
120138 pass
121139
122140 if e > mem_e :
123- tmp_map_info .append ((mem_e , e , p , info , mmio ))
141+ self .ql .uc .mem_unmap (mem_e , e - mem_e )
142+ self .map_ptr (mem_e , e - mem_e , p , data [mem_e - e :])
143+ tmp_map_info .append ((mem_e , e , p , info , mmio , data [mem_e - e :]))
124144
125145 if e == mem_e :
126146 pass
127147
148+ del data [mem_s - s :mem_e - s ]
149+
128150 self .map_info = tmp_map_info
129151
130- def change_mapinfo (self , mem_s : int , mem_e : int , mem_p : Optional [int ] = None , mem_info : Optional [str ] = None ):
152+ def change_mapinfo (self , mem_s : int , mem_e : int , mem_p : Optional [int ] = None , mem_info : Optional [str ] = None , data : Optional [ bytearray ] = None ):
131153 tmp_map_info : Optional [MapInfoEntry ] = None
132154 info_idx : int = None
133155
@@ -142,12 +164,16 @@ def change_mapinfo(self, mem_s: int, mem_e: int, mem_p: Optional[int] = None, me
142164 return
143165
144166 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 ])
167+ data = data or self .read (mem_s , mem_e - mem_s ).copy ()
168+ assert (len (data ) == mem_e - mem_s )
169+ self .unmap (mem_s , mem_e - mem_s )
170+ self .map_ptr (mem_s , mem_e - mem_s , mem_p , data )
171+ self .add_mapinfo (mem_s , mem_e , mem_p , mem_info or tmp_map_info [3 ], tmp_map_info [4 ], data )
172+ assert_mem_equal (self .ql )
147173 return
148174
149175 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 ])
176+ 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 ] )
151177
152178 def get_mapinfo (self ) -> Sequence [Tuple [int , int , str , str , str ]]:
153179 """Get memory map info.
@@ -166,7 +192,7 @@ def __perms_mapping(ps: int) -> str:
166192
167193 return '' .join (val if idx & ps else '-' for idx , val in perms_d .items ())
168194
169- def __process (lbound : int , ubound : int , perms : int , label : str , is_mmio : bool ) -> Tuple [int , int , str , str , str ]:
195+ def __process (lbound : int , ubound : int , perms : int , label : str , is_mmio : bool , _data : bytearray ) -> Tuple [int , int , str , str , str ]:
170196 perms_str = __perms_mapping (perms )
171197
172198 if hasattr (self .ql , 'loader' ):
@@ -211,7 +237,7 @@ def get_lib_base(self, filename: str) -> Optional[int]:
211237
212238 # some info labels may be prefixed by boxed label which breaks the search by basename.
213239 # 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 )
240+ stripped = ((lbound , p .sub ('' , info )) for lbound , _ , _ , info , _ , _ in self .map_info )
215241
216242 return next ((lbound for lbound , info in stripped if os .path .basename (info ) == filename ), None )
217243
@@ -268,11 +294,10 @@ def save(self):
268294 "mmio" : []
269295 }
270296
271- for lbound , ubound , perm , label , is_mmio in self .map_info :
297+ for lbound , ubound , perm , label , is_mmio , data in self .map_info :
272298 if is_mmio :
273299 mem_dict ['mmio' ].append ((lbound , ubound , perm , label , * self .mmio_cbs [(lbound , ubound )]))
274300 else :
275- data = self .read (lbound , ubound - lbound )
276301 mem_dict ['ram' ].append ((lbound , ubound , perm , label , bytes (data )))
277302
278303 return mem_dict
@@ -393,7 +418,7 @@ def search(self, needle: Union[bytes, Pattern[bytes]], begin: Optional[int] = No
393418 assert begin < end , 'search arguments do not make sense'
394419
395420 # 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 )]
421+ 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 )]
397422 results = []
398423
399424 # if needle is a bytes sequence use it verbatim, not as a pattern
@@ -439,10 +464,10 @@ def __mapped_regions(self) -> Iterator[Tuple[int, int]]:
439464
440465 iter_memmap = iter (self .map_info )
441466
442- p_lbound , p_ubound , _ , _ , _ = next (iter_memmap )
467+ p_lbound , p_ubound , _ , _ , _ , _ = next (iter_memmap )
443468
444469 # map_info is assumed to contain non-overlapping regions sorted by lbound
445- for lbound , ubound , _ , _ , _ in iter_memmap :
470+ for lbound , ubound , _ , _ , _ , _ in iter_memmap :
446471 if lbound == p_ubound :
447472 p_ubound = ubound
448473 else :
@@ -514,8 +539,8 @@ def find_free_space(self, size: int, minaddr: Optional[int] = None, maxaddr: Opt
514539 assert minaddr < maxaddr
515540
516541 # 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 )
542+ gaps_ubounds = tuple (lbound for lbound , _ , _ , _ , _ , _ in self .map_info ) + (mem_ubound ,)
543+ gaps_lbounds = (mem_lbound ,) + tuple (ubound for _ , ubound , _ , _ , _ , _ in self .map_info )
519544 gaps = zip (gaps_lbounds , gaps_ubounds )
520545
521546 for lbound , ubound in gaps :
@@ -563,7 +588,7 @@ def protect(self, addr: int, size: int, perms):
563588 self .change_mapinfo (aligned_address , aligned_address + aligned_size , mem_p = perms )
564589
565590
566- def map (self , addr : int , size : int , perms : int = UC_PROT_ALL , info : Optional [str ] = None ):
591+ def map (self , addr : int , size : int , perms : int = UC_PROT_ALL , info : Optional [str ] = None , ptr : Optional [ bytearray ] = None ):
567592 """Map a new memory range.
568593
569594 Args:
@@ -582,8 +607,27 @@ def map(self, addr: int, size: int, perms: int = UC_PROT_ALL, info: Optional[str
582607 if not self .is_available (addr , size ):
583608 raise QlMemoryMappedError ('Requested memory is unavailable' )
584609
585- self .ql .uc .mem_map (addr , size , perms )
586- self .add_mapinfo (addr , addr + size , perms , info or '[mapped]' , is_mmio = False )
610+ buf = self .map_ptr (addr , size , perms , ptr )
611+ self .add_mapinfo (addr , addr + size , perms , info or '[mapped]' , is_mmio = False , data = buf )
612+
613+ def map_ptr (self , addr : int , size : int , perms : int = UC_PROT_ALL , buf : Optional [bytearray ] = None ) -> bytearray :
614+ """Map a new memory range allocated as Python bytearray, will not affect map_info
615+
616+ Args:
617+ addr: memory range base address
618+ size: memory range size (in bytes)
619+ perms: requested permissions mask
620+ buf: bytearray already allocated (if any)
621+
622+ Returns:
623+ bytearray with size, should be added to map_info by caller
624+ """
625+ buf = buf or bytearray (size )
626+ buf_type = ctypes .c_byte * size
627+ cdata = buf_type .from_buffer (buf )
628+ self .cmap [addr ] = cdata
629+ self .ql .uc .mem_map_ptr (addr , size , perms , cdata )
630+ return buf
587631
588632 def map_mmio (self , addr : int , size : int , read_cb : Optional [MmioReadCallback ], write_cb : Optional [MmioWriteCallback ], info : str = '[mmio]' ):
589633 # TODO: mmio memory overlap with ram? Is that possible?
0 commit comments