-
Notifications
You must be signed in to change notification settings - Fork 771
Fix elf stack, pe stack and pe sections memory protection #1601
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,6 +15,7 @@ | |
|
|
||
| from unicorn import UcError | ||
| from unicorn.x86_const import UC_X86_REG_CR4, UC_X86_REG_CR8 | ||
| from unicorn.unicorn_const import UC_PROT_READ, UC_PROT_WRITE, UC_PROT_EXEC, UC_PROT_NONE | ||
|
|
||
| from qiling.arch.x86_const import FS_SEGMENT_ADDR, GS_SEGMENT_ADDR | ||
| from qiling.const import QL_ARCH, QL_STATE | ||
|
|
@@ -381,7 +382,7 @@ def load_dll(self, name: str, is_driver: bool = False) -> int: | |
| dll_len = image_size | ||
|
|
||
| self.dll_size += dll_len | ||
| self.ql.mem.map(dll_base, dll_len, info=dll_name) | ||
| _map_pe_(self.ql, dll, dll_base, dll_len, dll_name) | ||
| self.ql.mem.write(dll_base, bytes(data)) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That is not going to work... Mapping a PE file section be section does not necessarily form a continuous block in memory, so we cannot just As far I can see, there should be two methods: one that maps and writes the PE file section by section, and one that maps and writes the PE file as a whole. Just like it is on the UEFI PE loader. If this is too much to handle, it is OK. We can stay with the stack permission adjustment and leave the PE mapping by sections to another time. |
||
|
|
||
| if dll_base == self.dll_last_address: | ||
|
|
@@ -814,6 +815,39 @@ def init_security_cookie(self, pe: pefile.PE, image_base: int): | |
|
|
||
| self.ql.mem.write_ptr(cookie_rva + image_base, cookie) | ||
|
|
||
| def _map_pe_(ql : Qiling, pe : pefile.PE, image_base: int, image_size : int, image_name : str): | ||
| """Load file sections to memory, each in its own memory region protected by | ||
| its defined permissions. That allows separation of code and data, which makes | ||
| it easier to detect abnomal behavior or memory corruptions. | ||
| """ | ||
|
|
||
| # if sections are aligned to page, we can map them separately | ||
| sec_alignment = pe.OPTIONAL_HEADER.SectionAlignment | ||
| if (sec_alignment % ql.mem.pagesize) != 0: | ||
| ql.mem.map(image_base, image_size, info=f'{image_name}') | ||
| return | ||
|
|
||
| # load the header | ||
| hdr_base = image_base | ||
| hdr_perm = UC_PROT_READ | ||
|
|
||
| ql.mem.map(hdr_base, image_size, hdr_perm, image_name) | ||
|
|
||
| # load sections | ||
| for section in pe.sections: | ||
| sec_name = section.Name.rstrip(b'\x00').decode() | ||
| sec_base = image_base + section.VirtualAddress | ||
| sec_size = ql.mem.align_up(section.Misc_VirtualSize, sec_alignment) | ||
|
|
||
| sec_perm = sum(( | ||
| section.IMAGE_SCN_MEM_READ * UC_PROT_READ, | ||
| section.IMAGE_SCN_MEM_WRITE * UC_PROT_WRITE, | ||
| section.IMAGE_SCN_MEM_EXECUTE * UC_PROT_EXEC | ||
| )) | ||
|
|
||
| ql.mem.protect(sec_base, sec_size, sec_perm) | ||
| ql.mem.change_mapinfo(sec_base, sec_base+sec_size, new_perms=sec_perm, new_info=f'{image_name} {sec_name}') | ||
|
|
||
| class QlLoaderPE(QlLoader, Process): | ||
| def __init__(self, ql: Qiling, libcache: bool): | ||
| super().__init__(ql) | ||
|
|
@@ -890,6 +924,9 @@ def load(self, pe: Optional[pefile.PE]): | |
| image_base = pe.OPTIONAL_HEADER.ImageBase | ||
| image_size = self.ql.mem.align_up(pe.OPTIONAL_HEADER.SizeOfImage) | ||
|
|
||
| if pe.OPTIONAL_HEADER.DllCharacteristics & pefile.DLL_CHARACTERISTICS['IMAGE_DLLCHARACTERISTICS_NX_COMPAT']: | ||
| self.ql.mem.protect(self.stack_address, self.stack_size, UC_PROT_WRITE | UC_PROT_READ) | ||
|
|
||
| # if default base address is taken, use the one specified in profile | ||
| if not self.ql.mem.is_available(image_base, image_size): | ||
| image_base = self.image_address | ||
|
|
@@ -902,7 +939,8 @@ def load(self, pe: Optional[pefile.PE]): | |
| self.ql.log.info(f'Loading {self.path} to {image_base:#x}') | ||
| self.ql.log.info(f'PE entry point at {self.entry_point:#x}') | ||
|
|
||
| self.ql.mem.map(image_base, image_size, info=f'{image_name}') | ||
| _map_pe_(self.ql, pe, image_base, image_size, image_name) | ||
|
|
||
| self.images.append(Image(image_base, image_base + pe.NT_HEADERS.OPTIONAL_HEADER.SizeOfImage, os.path.abspath(self.path))) | ||
|
|
||
| if self.is_driver: | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if there is no such segment in the file? The code should be prepared to handle this situation (that is, default to RWX).