33# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
44#
55
6+ from contextlib import contextmanager
67import ntpath
78import os
89
910from shutil import copyfile
1011from datetime import datetime
12+ from typing import IO , Optional
1113
1214from qiling import Qiling
1315from qiling .exception import QlErrorNotImplemented
1416from qiling .os .windows .api import *
1517from qiling .os .windows .const import *
1618from qiling .os .windows .fncc import *
17- from qiling .os .windows .handle import Handle
19+ from qiling .os .windows .handle import Handle , HandleManager
1820from qiling .os .windows .structs import FILETIME , make_win32_find_data
1921
2022# DWORD GetFileType(
@@ -604,14 +606,124 @@ def hook_GetFileSize(ql: Qiling, address: int, params):
604606 ql .os .last_error = ERROR_INVALID_HANDLE
605607 return - 1 # INVALID_FILE_SIZE
606608
609+
610+ class FileMapping :
611+ pass
612+
613+
614+ class FileMappingMem (FileMapping ):
615+ # mapping backed my page file, for which we simply use memory. no need to do anything really
616+ pass
617+
618+
619+ class FileMappingFile (FileMapping ):
620+ def __init__ (self , fobj : IO ) -> None :
621+ self ._fobj = fobj
622+
623+ self ._read_hook = None
624+ self ._write_hook = None
625+
626+ def map_view (self , ql : Qiling , fbase : int , lbound : int , ubound : int ) -> None :
627+ def __read_mapview (ql : Qiling , access : int , addr : int , size : int , _ ) -> None :
628+ """Fetch the corresponding file part into memory.
629+ """
630+
631+ data = self .read (fbase + (addr - lbound ), size )
632+
633+ # FIXME: that triggers the write hook, and may be problematic for read-only ranges
634+ ql .mem .write (addr , data )
635+
636+ def __write_mapview (ql : Qiling , access : int , addr : int , size : int , value : int ) -> None :
637+ """Write data back to the corresponding file part.
638+ """
639+
640+ pack = {
641+ 1 : ql .pack8 ,
642+ 2 : ql .pack16 ,
643+ 4 : ql .pack32 ,
644+ 8 : ql .pack64
645+ }[size ]
646+
647+ self .write (fbase + (addr - lbound ), pack (value ))
648+
649+ self ._read_hook = ql .hook_mem_read (__read_mapview , begin = lbound , end = ubound )
650+ self ._write_hook = ql .hook_mem_write (__write_mapview , begin = lbound , end = ubound )
651+
652+ def unmap_view (self ) -> None :
653+ if self ._read_hook :
654+ self ._read_hook .remove ()
655+
656+ if self ._write_hook :
657+ self ._write_hook .remove ()
658+
659+ @contextmanager
660+ def __seek_temporary (self , offset : Optional [int ] = None ):
661+ """A context manager construct for performing actions that would normaly affect the file
662+ position, but without actually affecting it.
663+ """
664+
665+ fpos = self ._fobj .tell ()
666+
667+ if offset is not None :
668+ self ._fobj .seek (offset )
669+
670+ try :
671+ yield self ._fobj
672+ finally :
673+ self ._fobj .seek (fpos )
674+
675+ def get_file_size (self ) -> int :
676+ with self .__seek_temporary () as fobj :
677+ return fobj .seek (0 , os .SEEK_END )
678+
679+ def inc_file_size (self , addendum : int ) -> None :
680+ with self .__seek_temporary () as fobj :
681+ fobj .seek (0 , os .SEEK_END )
682+ fobj .write (b'\x00 ' * addendum )
683+
684+ def read (self , offset : int , size : int ) -> bytes :
685+ with self .__seek_temporary (offset ) as fobj :
686+ return fobj .read (size )
687+
688+ def write (self , offset : int , data : bytes ) -> None :
689+ with self .__seek_temporary (offset ) as fobj :
690+ fobj .write (data )
691+
692+
607693def _CreateFileMapping (ql : Qiling , address : int , params ):
608694 hFile = params ['hFile' ]
695+ dwMaximumSizeHigh = params ['dwMaximumSizeHigh' ]
696+ dwMaximumSizeLow = params ['dwMaximumSizeLow' ]
609697 lpName = params ['lpName' ]
610698
611- new_handle = Handle (obj = hFile , name = lpName )
612- ql .os .handle_manager .append (new_handle )
699+ req_size = (dwMaximumSizeHigh << 32 ) | dwMaximumSizeLow
613700
614- return new_handle .id
701+ if hFile == ql .unpack (ql .packs (INVALID_HANDLE_VALUE )):
702+ fmobj = FileMappingMem ()
703+
704+ else :
705+ # look for an existing mapping handle with the same name
706+ if lpName :
707+ existing_handle = ql .os .handle_manager .search (lpName )
708+
709+ # if found, return it
710+ if existing_handle is not None :
711+ return existing_handle
712+
713+ fhandle = ql .os .handle_manager .get (hFile )
714+
715+ # wrap the opened file with an accessor class
716+ fmobj = FileMappingFile (fhandle .obj )
717+ fsize = fmobj .get_file_size ()
718+
719+ # if requeted mapping is size is larger than the file size, enlarge it
720+ if req_size > fsize :
721+ fmobj .inc_file_size (req_size - fsize )
722+
723+ fm_handle = Handle (obj = fmobj , name = lpName or None )
724+ ql .os .handle_manager .append (fm_handle )
725+
726+ return fm_handle .id
615727
616728# HANDLE CreateFileMappingA(
617729# HANDLE hFile,
@@ -667,29 +779,49 @@ def hook_CreateFileMappingW(ql: Qiling, address: int, params):
667779})
668780def hook_MapViewOfFile (ql : Qiling , address : int , params ):
669781 hFileMappingObject = params ['hFileMappingObject' ]
782+ dwFileOffsetHigh = params ['dwFileOffsetHigh' ]
670783 dwFileOffsetLow = params ['dwFileOffsetLow' ]
671784 dwNumberOfBytesToMap = params ['dwNumberOfBytesToMap' ]
672785
673- map_file_handle = ql .os .handle_manager .search_by_obj (hFileMappingObject )
786+ handles : HandleManager = ql .os .handle_manager
787+ fm_handle = handles .get (hFileMappingObject )
788+
789+ if fm_handle is None :
790+ return 0
791+
792+ fmobj = fm_handle .obj
793+
794+ # the respective file mapping hFile was set to INVALID_HANDLE_VALUE (that is, mapping is backed by page file)
795+ if isinstance (fmobj , FileMappingMem ):
796+ mapview = ql .os .heap .alloc (dwNumberOfBytesToMap )
797+
798+ if not mapview :
799+ return 0
674800
675- if map_file_handle is None :
676- ret = ql .os .heap .alloc (dwNumberOfBytesToMap )
677- new_handle = Handle (obj = hFileMappingObject , name = ret )
678- ql .os .handle_manager .append (new_handle )
679801 else :
680- ret = map_file_handle .name
802+ offset = (dwFileOffsetHigh << 32 ) | dwFileOffsetLow
803+ mapview_size = dwNumberOfBytesToMap or (fmobj .get_file_size () - offset )
804+
805+ if mapview_size < 1 :
806+ return 0
681807
682- hFile = ql .os .handle_manager . get ( hFileMappingObject ). obj
808+ mapview = ql .os .heap . alloc ( mapview_size )
683809
684- if ql . os . handle_manager . get ( hFile ) :
685- f = ql . os . handle_manager . get ( hFile ). obj
810+ if not mapview :
811+ return 0
686812
687- if type (f ) is file :
688- f .seek (dwFileOffsetLow , 0 )
689- data = f .read (dwNumberOfBytesToMap )
690- ql .mem .write (ret , data )
813+ # read content from file but retain original position.
814+ # not sure this is actually required since all accesses to this memory area are monitored
815+ # and relect file content rather than what is currently in memory
816+ data = fmobj .read (offset , mapview_size )
817+ ql .mem .write (mapview , data )
691818
692- return ret
819+ fmobj .map_view (ql , offset , mapview , mapview + mapview_size - 1 )
820+
821+ # although file views are not strictly handles, it would be easier to manage them as such
822+ handles .append (Handle (id = mapview , obj = fmobj ))
823+
824+ return mapview
693825
694826# BOOL UnmapViewOfFile(
695827# LPCVOID lpBaseAddress
@@ -700,15 +832,19 @@ def hook_MapViewOfFile(ql: Qiling, address: int, params):
700832def hook_UnmapViewOfFile (ql : Qiling , address : int , params ):
701833 lpBaseAddress = params ['lpBaseAddress' ]
702834
703- map_file_hande = ql .os .handle_manager .search (lpBaseAddress )
835+ handles : HandleManager = ql .os .handle_manager
836+ fv_handle = handles .get (lpBaseAddress )
704837
705- if not map_file_hande :
706- return 0
838+ if fv_handle :
839+ if isinstance (fv_handle .obj , FileMappingFile ):
840+ fv_handle .obj .unmap_view ()
707841
708- ql .os .heap .free (map_file_hande . name )
709- ql . os . handle_manager . delete (map_file_hande .id )
842+ ql .os .heap .free (lpBaseAddress )
843+ handles . delete (fv_handle .id )
710844
711- return 1
845+ return 1
846+
847+ return 0
712848
713849# BOOL CopyFileA(
714850# LPCSTR lpExistingFileName,
0 commit comments