From b4d54fe0d44f9c9890c7b2d37781936cf470e583 Mon Sep 17 00:00:00 2001 From: user Date: Mon, 6 Feb 2023 14:41:03 -0500 Subject: [PATCH 1/3] partially fixed kernel32.dll$_CreateFile function --- qiling/os/mapper.py | 21 ++++++ qiling/os/windows/dlls/kernel32/fileapi.py | 86 +++++++++++++++++++++- 2 files changed, 103 insertions(+), 4 deletions(-) diff --git a/qiling/os/mapper.py b/qiling/os/mapper.py index d6e8b170a..cc0d840c5 100644 --- a/qiling/os/mapper.py +++ b/qiling/os/mapper.py @@ -107,7 +107,28 @@ def open_ql_file(self, path: str, openflags: int, openmode: int): raise PermissionError(f'unsafe path: {host_path}') return ql_file.open(host_path, openflags, openmode) + def file_exists(self, path:str) -> bool: + # check if file exists + if self.has_mapping(path): + return True + host_path = self.path.virtual_to_host_path(path) + if not self.path.is_safe_host_path(host_path): + raise PermissionError(f'unsafe path: {host_path}') + return os.path.isfile(host_path) + + def create_empty_file(self, path:str)->bool: + if not self.file_exists(path): + try: + f = self.open(path, "w+") + f.close() + return True + + except Exception as e: + # for some reason, we could not create an empty file. + return False + return True + def open(self, path: str, openmode: str): if self.has_mapping(path): return self._open_mapping(path, openmode) diff --git a/qiling/os/windows/dlls/kernel32/fileapi.py b/qiling/os/windows/dlls/kernel32/fileapi.py index 482471086..ac0e0601c 100644 --- a/qiling/os/windows/dlls/kernel32/fileapi.py +++ b/qiling/os/windows/dlls/kernel32/fileapi.py @@ -222,19 +222,97 @@ def _CreateFile(ql: Qiling, address: int, params): dwDesiredAccess = params["dwDesiredAccess"] # dwShareMode = params["dwShareMode"] # lpSecurityAttributes = params["lpSecurityAttributes"] - # dwCreationDisposition = params["dwCreationDisposition"] + + # Handle Creation Disposition. I.e. how to respond + # when a file either exists or doesn't + # See https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea + dwCreationDisposition = params["dwCreationDisposition"] + # dwFlagsAndAttributes = params["dwFlagsAndAttributes"] # hTemplateFile = params["hTemplateFile"] # access mask DesiredAccess - if dwDesiredAccess & GENERIC_WRITE: - mode = "wb" - else: + perm_write = dwDesiredAccess & GENERIC_WRITE + perm_read = dwDesiredAccess & GENERIC_READ + + # TODO: unused + perm_exec = dwDesiredAccess & GENERIC_EXECUTE + + # only open file if it exists. error otherwise + open_existing = ( + (dwCreationDisposition == OPEN_EXISTING) or + (dwCreationDisposition == TRUNCATE_EXISTING ) + ) + + # check if the file exists + # TODO: race condition if file is deleted/reated + file_exists = ql.os.fs_mapper.file_exists(s_lpFileName) + + if (open_existing and (not file_exists)): + # the CreationDisposition wants a file to exist + # it does not + ql.os.last_error = ERROR_FILE_NOT_FOUND + return INVALID_HANDLE_VALUE + + if ((dwCreationDisposition == CREATE_NEW ) and file_exists): + # only create a file if it does not exist. + # if it does, error + ql.os.last_error = ERROR_FILE_EXISTS + + truncate = (dwCreationDisposition == CREATE_ALWAYS) or (dwCreationDisposition == TRUNCATE_EXISTING) + + # TODO: this function does not handle general access masks. + # see https://learn.microsoft.com/en-us/windows/win32/secauthz/access-mask + # it is only able to handle Generic R/W + + # read only + if (perm_read) and ( not (perm_write)): mode = "rb" + # Write only + elif ( perm_write and (not perm_read)): + # TODO: fopen modes do not allow for write only access + # Likely need to use os.open instead. + ql.log.warn("_CreateFile has been called with Write only access. This is not currently supported and the handle is still allows for read access!") + + # read/write, do not create. do not truncatd + mode = "rb+" + + elif perm_read and perm_write: + # Note that this ignores exec access mask + mode = "rb+" + + elif perm_exec: + # TODO: handle exec access mask + # it is only executable or has a non standard access mask + ql.log.warn("_CreateFile has been called with executable only access or with a non standard access mask. This is not currently supported and the handle is set to Read/Write") + mode = "rb+" + else: + # This is probably an invalid access mask + ql.log.warn(f"Invalid access mask provided: {dwDesiredAccess}") + + try: + # we should have exited by now if the file doesn't exist + if not file_exists: + status = ql.os.fs_mapper.create_empty_file(s_lpFileName) + if not status: + # could not create a new file + # bail out. + # TODO: set last_error + ql.log.warn(f"_CreateFile could not create new file {s_lpFileName}") + return INVALID_HANDLE_VALUE f = ql.os.fs_mapper.open(s_lpFileName, mode) + if truncate: + f.truncate(0) + if dwCreationDisposition == CREATE_ALWAYS: + # we overwrote the file. + ql.os.last_error = ERROR_ALREADY_EXISTS + if dwCreationDisposition == OPEN_ALWAYS: + ql.os.last_error = ERROR_ALREADY_EXISTS + except FileNotFoundError: + # Creation disposition determines what happens when the file doesn't exist ql.os.last_error = ERROR_FILE_NOT_FOUND return INVALID_HANDLE_VALUE From b60eaba0aaee934607add84709a7c9dcdcc4b705 Mon Sep 17 00:00:00 2001 From: user Date: Mon, 6 Feb 2023 15:03:56 -0500 Subject: [PATCH 2/3] fixed issue with Write only, Truncate + Create new --- qiling/os/windows/dlls/kernel32/fileapi.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/qiling/os/windows/dlls/kernel32/fileapi.py b/qiling/os/windows/dlls/kernel32/fileapi.py index ac0e0601c..5885265f7 100644 --- a/qiling/os/windows/dlls/kernel32/fileapi.py +++ b/qiling/os/windows/dlls/kernel32/fileapi.py @@ -273,10 +273,14 @@ def _CreateFile(ql: Qiling, address: int, params): elif ( perm_write and (not perm_read)): # TODO: fopen modes do not allow for write only access # Likely need to use os.open instead. - ql.log.warn("_CreateFile has been called with Write only access. This is not currently supported and the handle is still allows for read access!") - # read/write, do not create. do not truncatd - mode = "rb+" + if (truncate and (not open_existing)) or (truncate and open_existing and file_exists): + # create a new file or truncate an existing one + mode = "wb" + else: + ql.log.warn("_CreateFile has been called with Write only access. This is not currently supported and the handle is still allows for read access!") + # read/write, do not create. do not truncatd + mode = "rb+" elif perm_read and perm_write: # Note that this ignores exec access mask @@ -294,7 +298,7 @@ def _CreateFile(ql: Qiling, address: int, params): try: # we should have exited by now if the file doesn't exist - if not file_exists: + if (not file_exists) and (mode != "wb"): status = ql.os.fs_mapper.create_empty_file(s_lpFileName) if not status: # could not create a new file @@ -302,12 +306,16 @@ def _CreateFile(ql: Qiling, address: int, params): # TODO: set last_error ql.log.warn(f"_CreateFile could not create new file {s_lpFileName}") return INVALID_HANDLE_VALUE + f = ql.os.fs_mapper.open(s_lpFileName, mode) - if truncate: + if truncate and mode != "wb": + # redundant if mode is wb f.truncate(0) + if dwCreationDisposition == CREATE_ALWAYS: # we overwrote the file. ql.os.last_error = ERROR_ALREADY_EXISTS + if dwCreationDisposition == OPEN_ALWAYS: ql.os.last_error = ERROR_ALREADY_EXISTS From f2b3209fdcfbd48d5edd91fc98b1aa47e0f75e0e Mon Sep 17 00:00:00 2001 From: user Date: Mon, 6 Feb 2023 15:14:22 -0500 Subject: [PATCH 3/3] fix error if access mask is invalid --- qiling/os/windows/dlls/kernel32/fileapi.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qiling/os/windows/dlls/kernel32/fileapi.py b/qiling/os/windows/dlls/kernel32/fileapi.py index 5885265f7..1176462f6 100644 --- a/qiling/os/windows/dlls/kernel32/fileapi.py +++ b/qiling/os/windows/dlls/kernel32/fileapi.py @@ -294,7 +294,8 @@ def _CreateFile(ql: Qiling, address: int, params): else: # This is probably an invalid access mask ql.log.warn(f"Invalid access mask provided: {dwDesiredAccess}") - + # TODO: add error code + return INVALID_HANDLE_VALUE try: # we should have exited by now if the file doesn't exist