Skip to content

Commit 416b685

Browse files
authored
Merge pull request #1437 from elicn/dev-win
Windows bug fixes
2 parents 082b718 + e80fa26 commit 416b685

File tree

5 files changed

+229
-34
lines changed

5 files changed

+229
-34
lines changed

qiling/os/utils.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,10 @@ def __dup(iterator: Iterator[T], out: List[T]) -> Iterator[T]:
153153

154154
va_list = __dup(va_args, orig_args)
155155

156-
read_string = self.read_wstring if wstring else self.read_cstring
156+
read_str = {
157+
False: self.read_cstring,
158+
True: self.read_wstring
159+
}
157160

158161
def __repl(m: re.Match) -> str:
159162
"""Convert printf format string tokens into Python's.
@@ -187,17 +190,21 @@ def __repl(m: re.Match) -> str:
187190
typ = m['type']
188191
arg = next(va_list)
189192

190-
if typ in 'sS':
193+
if typ == 's':
191194
typ = 's'
192-
arg = read_string(arg)
195+
arg = read_str[wstring](arg)
196+
197+
elif typ == 'S':
198+
typ = 's'
199+
arg = read_str[not wstring](arg)
193200

194201
elif typ == 'Z':
195202
# note: ANSI_STRING and UNICODE_STRING have identical layout
196203
ucstr_struct = make_unicode_string(self.ql.arch.bits)
197204

198205
with ucstr_struct.ref(self.ql.mem, arg) as ucstr_obj:
199206
typ = 's'
200-
arg = read_string(ucstr_obj.Buffer)
207+
arg = read_str[wstring](ucstr_obj.Buffer)
201208

202209
elif typ == 'p':
203210
pound = '#'

qiling/os/windows/const.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -786,3 +786,10 @@
786786
TIME_ZONE_ID_UNKNOWN = 0
787787
TIME_ZONE_ID_STANDARD = 1
788788
TIME_ZONE_ID_DAYLIGHT = 2
789+
790+
# heap management flags
791+
HEAP_NO_SERIALIZE = 0x00000001
792+
HEAP_GENERATE_EXCEPTIONS = 0x00000004
793+
HEAP_ZERO_MEMORY = 0x00000008
794+
HEAP_REALLOC_IN_PLACE_ONLY = 0x00000010
795+
HEAP_CREATE_ENABLE_EXECUTE = 0x00040000

qiling/os/windows/dlls/kernel32/fileapi.py

Lines changed: 160 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,20 @@
33
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
44
#
55

6+
from contextlib import contextmanager
67
import ntpath
78
import os
89

910
from shutil import copyfile
1011
from datetime import datetime
12+
from typing import IO, Optional
1113

1214
from qiling import Qiling
1315
from qiling.exception import QlErrorNotImplemented
1416
from qiling.os.windows.api import *
1517
from qiling.os.windows.const import *
1618
from qiling.os.windows.fncc import *
17-
from qiling.os.windows.handle import Handle
19+
from qiling.os.windows.handle import Handle, HandleManager
1820
from 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+
607693
def _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
})
668780
def 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):
700832
def 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,

qiling/os/windows/dlls/kernel32/handleapi.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,18 @@
33
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
44
#
55

6-
from qiling import Qiling
6+
from __future__ import annotations
7+
8+
from typing import TYPE_CHECKING, IO
9+
710
from qiling.os.windows.api import *
8-
from qiling.os.windows.const import *
9-
from qiling.os.windows.fncc import *
11+
from qiling.os.windows.const import ERROR_INVALID_HANDLE, HANDLE_FLAG_PROTECT_FROM_CLOSE
12+
from qiling.os.windows.fncc import STDCALL, winsdkapi
13+
14+
15+
if TYPE_CHECKING:
16+
from qiling import Qiling
17+
1018

1119
# BOOL DuplicateHandle(
1220
# HANDLE hSourceProcessHandle,
@@ -53,6 +61,10 @@ def hook_CloseHandle(ql: Qiling, address: int, params):
5361
# FIXME: add error
5462
return 0
5563

64+
# if this a file handle, close it
65+
if isinstance(handle.obj, IO):
66+
handle.obj.close()
67+
5668
ql.os.handle_manager.delete(value)
5769

5870
return 1

qiling/os/windows/dlls/kernel32/heapapi.py

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,36 @@
33
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
44
#
55

6-
from qiling import Qiling
6+
from __future__ import annotations
7+
from typing import TYPE_CHECKING
8+
79
from qiling.os.windows.api import *
8-
from qiling.os.windows.fncc import *
10+
from qiling.os.windows.const import HEAP_ZERO_MEMORY
11+
from qiling.os.windows.fncc import STDCALL, winsdkapi
12+
13+
14+
if TYPE_CHECKING:
15+
from qiling import Qiling
16+
from qiling.os.memory import QlMemoryManager
17+
18+
19+
def __zero_mem(mem: QlMemoryManager, ptr: int, size: int) -> None:
20+
"""Zero a memory range, but avoid hogging to much on host resources.
21+
"""
22+
23+
# go by page granularity
24+
npages, remainder = divmod(size, mem.pagesize)
25+
26+
if npages:
27+
zeros = b'\x00' * mem.pagesize
28+
29+
for _ in range(npages):
30+
mem.write(ptr, zeros)
31+
ptr += len(zeros)
32+
33+
if remainder:
34+
mem.write(ptr, b'\x00' * remainder)
35+
936

1037
# HANDLE HeapCreate(
1138
# DWORD flOptions,
@@ -33,9 +60,15 @@ def hook_HeapCreate(ql: Qiling, address: int, params):
3360
'dwBytes' : SIZE_T
3461
})
3562
def hook_HeapAlloc(ql: Qiling, address: int, params):
63+
dwFlags = params["dwFlags"]
3664
dwBytes = params["dwBytes"]
3765

38-
return ql.os.heap.alloc(dwBytes)
66+
ptr = ql.os.heap.alloc(dwBytes)
67+
68+
if ptr and (dwFlags & HEAP_ZERO_MEMORY):
69+
__zero_mem(ql.mem, ptr, dwBytes)
70+
71+
return ptr
3972

4073
# SIZE_T HeapSize(
4174
# HANDLE hHeap,

0 commit comments

Comments
 (0)