Skip to content
Merged
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
52 changes: 26 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,55 +88,55 @@ Please see [setup guide](https://docs.qiling.io/en/latest/install/) file for how

#### Examples

- Below example shows how to use Qiling framework to emulate a Windows EXE on a Linux machine
- The example below shows how to use Qiling framework in the most striaghtforward way to emulate a Windows executable.

```python
from qiling import *

# sandbox to emulate the EXE
def my_sandbox(path, rootfs):
# setup Qiling engine
ql = Qiling(path, rootfs)
# now emulate the EXE
ql.run()
from qiling import Qiling

if __name__ == "__main__":
# execute Windows EXE under our rootfs
my_sandbox(["examples/rootfs/x86_windows/bin/x86_hello.exe"], "examples/rootfs/x86_windows")
# initialize Qiling instance, specifying the executable to emulate and the emulated system root.
# note that the current working directory is assumed to be Qiling home
ql = Qiling([r'examples/rootfs/x86_windows/bin/x86_hello.exe'], r'examples/rootfs/x86_windows')

# start emulation
ql.run()
```

- Below example shows how to use Qiling framework to dynamically patch a Windows crackme, make it always display "Congratulation" dialog
- The following example shows how a Windows crackme may be patched dynamically to make it always display the "Congratulation" dialog.

```python
from qiling import *
from qiling import Qiling

def force_call_dialog_func(ql: Qiling):
# get DialogFunc address from current stack frame
lpDialogFunc = ql.stack_read(-8)

def force_call_dialog_func(ql):
# get DialogFunc address
lpDialogFunc = ql.unpack32(ql.mem.read(ql.reg.esp - 0x8, 4))
# setup stack memory for DialogFunc
ql.stack_push(0)
ql.stack_push(1001)
ql.stack_push(273)
ql.stack_push(1001) # IDS_APPNAME
ql.stack_push(0x111) # WM_COMMAND
ql.stack_push(0)

# push return address
ql.stack_push(0x0401018)
# force EIP to DialogFunc
ql.reg.eip = lpDialogFunc

# resume emulation from DialogFunc address
ql.arch.regs.eip = lpDialogFunc


def my_sandbox(path, rootfs):
ql = Qiling(path, rootfs)
if __name__ == "__main__":
# initialize Qiling instance
ql = Qiling([r'rootfs/x86_windows/bin/Easy_CrackMe.exe'], r'rootfs/x86_windows')

# NOP out some code
ql.patch(0x004010B5, b'\x90\x90')
ql.patch(0x004010CD, b'\x90\x90')
ql.patch(0x0040110B, b'\x90\x90')
ql.patch(0x00401112, b'\x90\x90')

# hook at an address with a callback
ql.hook_address(force_call_dialog_func, 0x00401016)
ql.run()


if __name__ == "__main__":
my_sandbox(["rootfs/x86_windows/bin/Easy_CrackMe.exe"], "rootfs/x86_windows")
```

The below Youtube video shows how the above example works.
Expand Down
15 changes: 12 additions & 3 deletions qiling/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ def __init__(
):
""" Create a Qiling instance.

For each argument or property, please refer to its docstring. e.g. Qiling.multithread.__doc__
For each argument or property, please refer to its help. e.g. help(Qiling.multithread)
"""

##################################
Expand Down Expand Up @@ -705,8 +705,17 @@ def stop(self):
self.uc.emu_stop()

# start emulation
def emu_start(self, begin, end, timeout=0, count=0):
def emu_start(self, begin: int, end: int, timeout: int = 0, count: int = 0):
"""Start emulation.

Args:
begin : emulation starting address
end : emulation ending address
timeout : max emulation time (in microseconds); unlimited by default
count : max emulation steps (instructions count); unlimited by default
"""

self.uc.emu_start(begin, end, timeout, count)

if self._internal_exception != None:
if self._internal_exception is not None:
raise self._internal_exception
11 changes: 8 additions & 3 deletions qiling/loader/elf.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,14 @@ def __init__(self, ql: Qiling):
def run(self):
if self.ql.code:
self.ql.mem.map(self.ql.os.entry_point, self.ql.os.code_ram_size, info="[shellcode_stack]")
self.ql.os.entry_point = (self.ql.os.entry_point + 0x200000 - 0x1000)
self.ql.mem.write(self.ql.os.entry_point, self.ql.code)
self.ql.arch.regs.arch_sp = self.ql.os.entry_point

shellcode_base = self.ql.os.entry_point + 0x200000 - 0x1000
self.ql.mem.write(shellcode_base, self.ql.code)

self.ql.arch.regs.arch_sp = shellcode_base
self.ql.os.entry_point = shellcode_base
self.load_address = shellcode_base

return

section = {
Expand Down
12 changes: 9 additions & 3 deletions qiling/os/freebsd/map_syscall.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,15 @@
from qiling.const import QL_ARCH
from qiling.os.posix.posix import SYSCALL_PREF

def map_syscall(ql, syscall_num):
if ql.arch.type == QL_ARCH.X8664:
return f'{SYSCALL_PREF}{x8664_syscall_table[syscall_num]}'
def get_syscall_mapper(archtype: QL_ARCH):
syscall_table = {
QL_ARCH.X8664 : x8664_syscall_table
}[archtype]

def __mapper(syscall_num: int) -> str:
return f'{SYSCALL_PREF}{syscall_table[syscall_num]}'

return __mapper

x8664_syscall_table = {
0: 'syscall',
Expand Down
23 changes: 13 additions & 10 deletions qiling/os/linux/map_syscall.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,21 @@
from qiling.const import QL_ARCH
from qiling.os.posix.posix import SYSCALL_PREF

def map_syscall(ql, syscall_num):
def get_syscall_mapper(archtype: QL_ARCH):
syscall_table = {
QL_ARCH.ARM64: arm64_syscall_table,
QL_ARCH.ARM: arm_syscall_table,
QL_ARCH.X8664: x8664_syscall_table,
QL_ARCH.X86: x86_syscall_table,
QL_ARCH.MIPS: mips_syscall_table,
QL_ARCH.RISCV: riscv32_syscall_table,
QL_ARCH.RISCV64: riscv64_syscall_table,
}[ql.arch.type]
QL_ARCH.ARM64 : arm64_syscall_table,
QL_ARCH.ARM : arm_syscall_table,
QL_ARCH.X8664 : x8664_syscall_table,
QL_ARCH.X86 : x86_syscall_table,
QL_ARCH.MIPS : mips_syscall_table,
QL_ARCH.RISCV : riscv32_syscall_table,
QL_ARCH.RISCV64 : riscv64_syscall_table
}[archtype]

return f'{SYSCALL_PREF}{syscall_table[syscall_num]}'
def __mapper(syscall_num: int) -> str:
return f'{SYSCALL_PREF}{syscall_table[syscall_num]}'

return __mapper

arm_syscall_table = {
0: "restart_syscall",
Expand Down
23 changes: 12 additions & 11 deletions qiling/os/macos/map_syscall.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,24 @@
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
#

# cols = ("arm64", "x8664")

from qiling.const import QL_ARCH
from qiling.os.posix.posix import SYSCALL_PREF

def map_syscall(ql, syscall_num):
if ql.arch.type == QL_ARCH.X8664:
if syscall_num >= 0x2000000 and syscall_num <= 0x3000000:
syscall_num = syscall_num - 0x2000000
def get_syscall_mapper(archtype: QL_ARCH):
syscall_table = {
QL_ARCH.X8664 : x8664_syscall_table,
QL_ARCH.ARM64 : arm64_syscall_table
}[archtype]

return f'{SYSCALL_PREF}{x8664_syscall_table[syscall_num]}'
syscall_fixup = {
QL_ARCH.X8664 : lambda n: (n - 0x2000000) if 0x2000000 <= n <= 0x3000000 else n,
QL_ARCH.ARM64 : lambda n: (n - 0xffffffffffffff00) if n >= 0xffffffffffffff00 else n
}[archtype]

elif ql.arch.type == QL_ARCH.ARM64:
if syscall_num >= 0xffffffffffffff00:
syscall_num = syscall_num - 0xffffffffffffff00
def __mapper(syscall_num: int) -> str:
return f'{SYSCALL_PREF}{syscall_table[syscall_fixup(syscall_num)]}'

return f'{SYSCALL_PREF}{arm64_syscall_table[syscall_num]}'
return __mapper


arm64_syscall_table = {
Expand Down
4 changes: 2 additions & 2 deletions qiling/os/os.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,10 @@ def __init__(self, ql: Qiling, resolvers: Mapping[Any, Resolver] = {}):
}.get(self.ql.arch.bits, None)

if self.ql.code:
self.code_ram_size = int(self.profile.get("CODE", "ram_size"), 16)
# this shellcode entrypoint does not work for windows
# windows shellcode entry point will comes from pe loader
self.entry_point = int(self.profile.get("CODE", "entry_point"), 16)
self.entry_point = self.profile.getint('CODE', 'entry_point')
self.code_ram_size = self.profile.getint('CODE', 'ram_size')

# default fcall paramters resolving methods
self.resolvers = {
Expand Down
58 changes: 28 additions & 30 deletions qiling/os/posix/posix.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,32 +80,36 @@ def __init__(self, ql: Qiling):
}

self.__syscall_id_reg = {
QL_ARCH.ARM64: UC_ARM64_REG_X8,
QL_ARCH.ARM : UC_ARM_REG_R7,
QL_ARCH.MIPS : UC_MIPS_REG_V0,
QL_ARCH.X86 : UC_X86_REG_EAX,
QL_ARCH.X8664: UC_X86_REG_RAX,
QL_ARCH.RISCV: UC_RISCV_REG_A7,
QL_ARCH.RISCV64: UC_RISCV_REG_A7
QL_ARCH.ARM64 : UC_ARM64_REG_X8,
QL_ARCH.ARM : UC_ARM_REG_R7,
QL_ARCH.MIPS : UC_MIPS_REG_V0,
QL_ARCH.X86 : UC_X86_REG_EAX,
QL_ARCH.X8664 : UC_X86_REG_RAX,
QL_ARCH.RISCV : UC_RISCV_REG_A7,
QL_ARCH.RISCV64 : UC_RISCV_REG_A7
}[self.ql.arch.type]

# handle a special case
# handle some special cases
if (self.ql.arch.type == QL_ARCH.ARM64) and (self.ql.ostype == QL_OS.MACOS):
self.__syscall_id_reg = UC_ARM64_REG_X16
if (self.ql.arch.type == QL_ARCH.ARM) and (self.ql.ostype == QL_OS.QNX):

elif (self.ql.arch.type == QL_ARCH.ARM) and (self.ql.ostype == QL_OS.QNX):
self.__syscall_id_reg = UC_ARM_REG_R12

# TODO: use abstract to access __syscall_cc and __syscall_id_reg by defining a system call class
self.__syscall_cc: QlCC = {
QL_ARCH.ARM64: aarch64,
QL_ARCH.ARM : aarch32,
QL_ARCH.MIPS : mipso32,
QL_ARCH.X86 : intel32,
QL_ARCH.X8664: intel64,
QL_ARCH.RISCV: riscv32,
QL_ARCH.RISCV64: riscv64,
QL_ARCH.ARM64 : aarch64,
QL_ARCH.ARM : aarch32,
QL_ARCH.MIPS : mipso32,
QL_ARCH.X86 : intel32,
QL_ARCH.X8664 : intel64,
QL_ARCH.RISCV : riscv32,
QL_ARCH.RISCV64 : riscv64
}[self.ql.arch.type](self.ql.arch)

# select syscall mapping function based on emulated OS and architecture
self.syscall_mapper = ql_syscall_mapping_function(self.ql.ostype, self.ql.arch.type)

self._fd = QlFileDes()

# the QlOs constructor cannot assign the standard streams using their designated properties since
Expand Down Expand Up @@ -141,10 +145,6 @@ def root(self, enabled: bool) -> None:
self.euid = 0 if enabled else self.uid
self.egid = 0 if enabled else self.gid

@property
def syscall(self):
return self.get_syscall()

def set_syscall(self, target: Union[int, str], handler: Callable, intercept: QL_INTERCEPT=QL_INTERCEPT.CALL):
"""Either hook or replace a system call with a custom one.

Expand Down Expand Up @@ -178,10 +178,8 @@ def getNameFromErrorCode(ret: int) -> str:
return f'{ret:#x}{f" ({errors[-ret]})" if -ret in errors else f""}'

def load_syscall(self):
# import syscall mapping function
map_syscall = ql_syscall_mapping_function(self.ql.ostype)
syscall_id = self.syscall
syscall_name = map_syscall(self.ql, syscall_id)
syscall_id = self.get_syscall()
syscall_name = self.syscall_mapper(syscall_id)

# get syscall on-enter hook (if any)
hooks_dict = self.posix_syscall_hooks[QL_INTERCEPT.ENTER]
Expand All @@ -196,14 +194,14 @@ def load_syscall(self):
syscall_hook = hooks_dict.get(syscall_name) or hooks_dict.get(syscall_id)

if not syscall_hook:
osname = ostype_convert_str(self.ql.ostype)
os_syscalls = ql_get_module_function(f"qiling.os.{osname.lower()}", "syscall")
posix_syscalls = ql_get_module_function(f"qiling.os.posix", "syscall")
def __get_os_module(osname: str):
return ql_get_module_function(f'qiling.os.{osname.lower()}', 'syscall')

os_syscalls = __get_os_module(ostype_convert_str(self.ql.ostype))
posix_syscalls = __get_os_module('posix')

# look in os-specific and posix syscall hooks
if syscall_name:
self.ql.log.debug("syscall hooked 0x%x: %s()" % (self.ql.arch.regs.arch_pc, syscall_name))
syscall_hook = getattr(os_syscalls, syscall_name, None) or getattr(posix_syscalls, syscall_name, None)
syscall_hook = getattr(os_syscalls, syscall_name, None) or getattr(posix_syscalls, syscall_name, None)

if syscall_hook:
syscall_name = syscall_hook.__name__
Expand Down
6 changes: 3 additions & 3 deletions qiling/os/posix/syscall/select.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def parse_fd_set(ql: Qiling, max_fd: int, struct_addr: int) -> Tuple[Sequence[in

for i in range(max_fd):
if i % 32 == 0:
tmp = ql.unpack32(ql.mem.read(struct_addr + i, 4))
tmp = ql.mem.read_ptr(struct_addr + i, 4)

if tmp & 0x1:
fileno = ql.os.fd[i].fileno()
Expand Down Expand Up @@ -52,8 +52,8 @@ def handle_ready_fds(ptr: int, ready_fds: Sequence, fds_map: Mapping):
n = ql.arch.pointersize

if timeout:
sec = ql.unpack(ql.mem.read(timeout + n * 0, n))
usec = ql.unpack(ql.mem.read(timeout + n * 1, n))
sec = ql.mem.read_ptr(timeout + n * 0)
usec = ql.mem.read_ptr(timeout + n * 1)

timeout_total = sec + float(usec) / 1000000
else:
Expand Down
8 changes: 3 additions & 5 deletions qiling/os/posix/syscall/signal.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,13 @@

def ql_syscall_rt_sigaction(ql: Qiling, signum: int, act: int, oldact: int):
if oldact:
if ql.os.sigaction_act[signum] == 0:
data = b'\x00' * 20
else:
data = b''.join(ql.pack32(key) for key in ql.os.sigaction_act[signum])
arr = ql.os.sigaction_act[signum] or [0] * 5
data = b''.join(ql.pack32(key) for key in arr)

ql.mem.write(oldact, data)

if act:
ql.os.sigaction_act[signum] = [ql.unpack32(ql.mem.read(act + 4 * key, 4)) for key in range(5)]
ql.os.sigaction_act[signum] = [ql.mem.read_ptr(act + 4 * i, 4) for i in range(5)]

return 0

Expand Down
12 changes: 9 additions & 3 deletions qiling/os/qnx/map_syscall.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,15 @@
from qiling.const import QL_ARCH
from qiling.os.posix.posix import SYSCALL_PREF

def map_syscall(ql, syscall_num):
if ql.arch.type == QL_ARCH.ARM:
return f'{SYSCALL_PREF}{arm_syscall_table[syscall_num]}'
def get_syscall_mapper(archtype: QL_ARCH):
syscall_table = {
QL_ARCH.ARM : arm_syscall_table
}[archtype]

def __mapper(syscall_num: int) -> str:
return f'{SYSCALL_PREF}{syscall_table[syscall_num]}'

return __mapper

# Source: https://github.com/vocho/openqnx
# trunk/services/system/public/sys/kercalls.h
Expand Down
3 changes: 3 additions & 0 deletions qiling/os/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
from qiling.const import QL_VERBOSE

class QlOsStats:
"""Record basic OS statistics, such as API calls and strings.
"""

def __init__(self):
self.syscalls: MutableMapping[str, List] = {}
self.syscalls_counter = 0
Expand Down
Loading