Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions qiling/loader/elf.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,12 +84,6 @@ def run(self):

self.profile = self.ql.os.profile[f'OS{self.ql.arch.bits}']

# setup program stack
stack_address = self.profile.getint('stack_address')
stack_size = self.profile.getint('stack_size')
top_of_stack = stack_address + stack_size
self.ql.mem.map(stack_address, stack_size, info='[stack]')

self.path = self.ql.path

with open(self.path, 'rb') as infile:
Expand All @@ -98,6 +92,14 @@ def run(self):
elffile = ELFFile(fstream)
elftype = elffile['e_type']

# setup program stack
stack_seg = elffile.iter_segments('PT_GNU_STACK')
stack_perm = QlLoaderELF.seg_perm_to_uc_prot(next(stack_seg)['p_flags'])
Copy link
Member

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).

stack_address = self.profile.getint('stack_address')
stack_size = self.profile.getint('stack_size')
top_of_stack = stack_address + stack_size
self.ql.mem.map(stack_address, stack_size, stack_perm, info='[stack]')

# is it a driver?
if elftype == 'ET_REL':
self.load_driver(elffile, top_of_stack, loadbase=0x8000000)
Expand Down
42 changes: 40 additions & 2 deletions qiling/loader/pe.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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))
Copy link
Member

Choose a reason for hiding this comment

The 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 mem.write the file as a whole.

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:
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -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:
Expand Down