Skip to content

Commit 3387ecc

Browse files
authored
Merge pull request #1102 from elicn/qiling-next
Late additions to qiling-next
2 parents 8e7fdf8 + 5e5df87 commit 3387ecc

File tree

20 files changed

+284
-217
lines changed

20 files changed

+284
-217
lines changed

README.md

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -88,55 +88,55 @@ Please see [setup guide](https://docs.qiling.io/en/latest/install/) file for how
8888

8989
#### Examples
9090

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

9393
```python
94-
from qiling import *
95-
96-
# sandbox to emulate the EXE
97-
def my_sandbox(path, rootfs):
98-
# setup Qiling engine
99-
ql = Qiling(path, rootfs)
100-
# now emulate the EXE
101-
ql.run()
94+
from qiling import Qiling
10295

10396
if __name__ == "__main__":
104-
# execute Windows EXE under our rootfs
105-
my_sandbox(["examples/rootfs/x86_windows/bin/x86_hello.exe"], "examples/rootfs/x86_windows")
97+
# initialize Qiling instance, specifying the executable to emulate and the emulated system root.
98+
# note that the current working directory is assumed to be Qiling home
99+
ql = Qiling([r'examples/rootfs/x86_windows/bin/x86_hello.exe'], r'examples/rootfs/x86_windows')
100+
101+
# start emulation
102+
ql.run()
106103
```
107104

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

110107
```python
111-
from qiling import *
108+
from qiling import Qiling
109+
110+
def force_call_dialog_func(ql: Qiling):
111+
# get DialogFunc address from current stack frame
112+
lpDialogFunc = ql.stack_read(-8)
112113

113-
def force_call_dialog_func(ql):
114-
# get DialogFunc address
115-
lpDialogFunc = ql.unpack32(ql.mem.read(ql.reg.esp - 0x8, 4))
116114
# setup stack memory for DialogFunc
117115
ql.stack_push(0)
118-
ql.stack_push(1001)
119-
ql.stack_push(273)
116+
ql.stack_push(1001) # IDS_APPNAME
117+
ql.stack_push(0x111) # WM_COMMAND
120118
ql.stack_push(0)
119+
120+
# push return address
121121
ql.stack_push(0x0401018)
122-
# force EIP to DialogFunc
123-
ql.reg.eip = lpDialogFunc
122+
123+
# resume emulation from DialogFunc address
124+
ql.arch.regs.eip = lpDialogFunc
124125

125126

126-
def my_sandbox(path, rootfs):
127-
ql = Qiling(path, rootfs)
127+
if __name__ == "__main__":
128+
# initialize Qiling instance
129+
ql = Qiling([r'rootfs/x86_windows/bin/Easy_CrackMe.exe'], r'rootfs/x86_windows')
130+
128131
# NOP out some code
129132
ql.patch(0x004010B5, b'\x90\x90')
130133
ql.patch(0x004010CD, b'\x90\x90')
131134
ql.patch(0x0040110B, b'\x90\x90')
132135
ql.patch(0x00401112, b'\x90\x90')
136+
133137
# hook at an address with a callback
134138
ql.hook_address(force_call_dialog_func, 0x00401016)
135139
ql.run()
136-
137-
138-
if __name__ == "__main__":
139-
my_sandbox(["rootfs/x86_windows/bin/Easy_CrackMe.exe"], "rootfs/x86_windows")
140140
```
141141

142142
The below Youtube video shows how the above example works.

qiling/core.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def __init__(
5353
):
5454
""" Create a Qiling instance.
5555
56-
For each argument or property, please refer to its docstring. e.g. Qiling.multithread.__doc__
56+
For each argument or property, please refer to its help. e.g. help(Qiling.multithread)
5757
"""
5858

5959
##################################
@@ -705,8 +705,17 @@ def stop(self):
705705
self.uc.emu_stop()
706706

707707
# start emulation
708-
def emu_start(self, begin, end, timeout=0, count=0):
708+
def emu_start(self, begin: int, end: int, timeout: int = 0, count: int = 0):
709+
"""Start emulation.
710+
711+
Args:
712+
begin : emulation starting address
713+
end : emulation ending address
714+
timeout : max emulation time (in microseconds); unlimited by default
715+
count : max emulation steps (instructions count); unlimited by default
716+
"""
717+
709718
self.uc.emu_start(begin, end, timeout, count)
710719

711-
if self._internal_exception != None:
720+
if self._internal_exception is not None:
712721
raise self._internal_exception

qiling/loader/elf.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,14 @@ def __init__(self, ql: Qiling):
6868
def run(self):
6969
if self.ql.code:
7070
self.ql.mem.map(self.ql.os.entry_point, self.ql.os.code_ram_size, info="[shellcode_stack]")
71-
self.ql.os.entry_point = (self.ql.os.entry_point + 0x200000 - 0x1000)
72-
self.ql.mem.write(self.ql.os.entry_point, self.ql.code)
73-
self.ql.arch.regs.arch_sp = self.ql.os.entry_point
71+
72+
shellcode_base = self.ql.os.entry_point + 0x200000 - 0x1000
73+
self.ql.mem.write(shellcode_base, self.ql.code)
74+
75+
self.ql.arch.regs.arch_sp = shellcode_base
76+
self.ql.os.entry_point = shellcode_base
77+
self.load_address = shellcode_base
78+
7479
return
7580

7681
section = {

qiling/os/freebsd/map_syscall.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,15 @@
66
from qiling.const import QL_ARCH
77
from qiling.os.posix.posix import SYSCALL_PREF
88

9-
def map_syscall(ql, syscall_num):
10-
if ql.arch.type == QL_ARCH.X8664:
11-
return f'{SYSCALL_PREF}{x8664_syscall_table[syscall_num]}'
9+
def get_syscall_mapper(archtype: QL_ARCH):
10+
syscall_table = {
11+
QL_ARCH.X8664 : x8664_syscall_table
12+
}[archtype]
13+
14+
def __mapper(syscall_num: int) -> str:
15+
return f'{SYSCALL_PREF}{syscall_table[syscall_num]}'
16+
17+
return __mapper
1218

1319
x8664_syscall_table = {
1420
0: 'syscall',

qiling/os/linux/map_syscall.py

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,21 @@
1111
from qiling.const import QL_ARCH
1212
from qiling.os.posix.posix import SYSCALL_PREF
1313

14-
def map_syscall(ql, syscall_num):
14+
def get_syscall_mapper(archtype: QL_ARCH):
1515
syscall_table = {
16-
QL_ARCH.ARM64: arm64_syscall_table,
17-
QL_ARCH.ARM: arm_syscall_table,
18-
QL_ARCH.X8664: x8664_syscall_table,
19-
QL_ARCH.X86: x86_syscall_table,
20-
QL_ARCH.MIPS: mips_syscall_table,
21-
QL_ARCH.RISCV: riscv32_syscall_table,
22-
QL_ARCH.RISCV64: riscv64_syscall_table,
23-
}[ql.arch.type]
16+
QL_ARCH.ARM64 : arm64_syscall_table,
17+
QL_ARCH.ARM : arm_syscall_table,
18+
QL_ARCH.X8664 : x8664_syscall_table,
19+
QL_ARCH.X86 : x86_syscall_table,
20+
QL_ARCH.MIPS : mips_syscall_table,
21+
QL_ARCH.RISCV : riscv32_syscall_table,
22+
QL_ARCH.RISCV64 : riscv64_syscall_table
23+
}[archtype]
2424

25-
return f'{SYSCALL_PREF}{syscall_table[syscall_num]}'
25+
def __mapper(syscall_num: int) -> str:
26+
return f'{SYSCALL_PREF}{syscall_table[syscall_num]}'
27+
28+
return __mapper
2629

2730
arm_syscall_table = {
2831
0: "restart_syscall",

qiling/os/macos/map_syscall.py

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

6-
# cols = ("arm64", "x8664")
7-
86
from qiling.const import QL_ARCH
97
from qiling.os.posix.posix import SYSCALL_PREF
108

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

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

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

22-
return f'{SYSCALL_PREF}{arm64_syscall_table[syscall_num]}'
23+
return __mapper
2324

2425

2526
arm64_syscall_table = {

qiling/os/os.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,10 +69,10 @@ def __init__(self, ql: Qiling, resolvers: Mapping[Any, Resolver] = {}):
6969
}.get(self.ql.arch.bits, None)
7070

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

7777
# default fcall paramters resolving methods
7878
self.resolvers = {

qiling/os/posix/posix.py

Lines changed: 28 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -80,32 +80,36 @@ def __init__(self, ql: Qiling):
8080
}
8181

8282
self.__syscall_id_reg = {
83-
QL_ARCH.ARM64: UC_ARM64_REG_X8,
84-
QL_ARCH.ARM : UC_ARM_REG_R7,
85-
QL_ARCH.MIPS : UC_MIPS_REG_V0,
86-
QL_ARCH.X86 : UC_X86_REG_EAX,
87-
QL_ARCH.X8664: UC_X86_REG_RAX,
88-
QL_ARCH.RISCV: UC_RISCV_REG_A7,
89-
QL_ARCH.RISCV64: UC_RISCV_REG_A7
83+
QL_ARCH.ARM64 : UC_ARM64_REG_X8,
84+
QL_ARCH.ARM : UC_ARM_REG_R7,
85+
QL_ARCH.MIPS : UC_MIPS_REG_V0,
86+
QL_ARCH.X86 : UC_X86_REG_EAX,
87+
QL_ARCH.X8664 : UC_X86_REG_RAX,
88+
QL_ARCH.RISCV : UC_RISCV_REG_A7,
89+
QL_ARCH.RISCV64 : UC_RISCV_REG_A7
9090
}[self.ql.arch.type]
9191

92-
# handle a special case
92+
# handle some special cases
9393
if (self.ql.arch.type == QL_ARCH.ARM64) and (self.ql.ostype == QL_OS.MACOS):
9494
self.__syscall_id_reg = UC_ARM64_REG_X16
95-
if (self.ql.arch.type == QL_ARCH.ARM) and (self.ql.ostype == QL_OS.QNX):
95+
96+
elif (self.ql.arch.type == QL_ARCH.ARM) and (self.ql.ostype == QL_OS.QNX):
9697
self.__syscall_id_reg = UC_ARM_REG_R12
9798

9899
# TODO: use abstract to access __syscall_cc and __syscall_id_reg by defining a system call class
99100
self.__syscall_cc: QlCC = {
100-
QL_ARCH.ARM64: aarch64,
101-
QL_ARCH.ARM : aarch32,
102-
QL_ARCH.MIPS : mipso32,
103-
QL_ARCH.X86 : intel32,
104-
QL_ARCH.X8664: intel64,
105-
QL_ARCH.RISCV: riscv32,
106-
QL_ARCH.RISCV64: riscv64,
101+
QL_ARCH.ARM64 : aarch64,
102+
QL_ARCH.ARM : aarch32,
103+
QL_ARCH.MIPS : mipso32,
104+
QL_ARCH.X86 : intel32,
105+
QL_ARCH.X8664 : intel64,
106+
QL_ARCH.RISCV : riscv32,
107+
QL_ARCH.RISCV64 : riscv64
107108
}[self.ql.arch.type](self.ql.arch)
108109

110+
# select syscall mapping function based on emulated OS and architecture
111+
self.syscall_mapper = ql_syscall_mapping_function(self.ql.ostype, self.ql.arch.type)
112+
109113
self._fd = QlFileDes()
110114

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

144-
@property
145-
def syscall(self):
146-
return self.get_syscall()
147-
148148
def set_syscall(self, target: Union[int, str], handler: Callable, intercept: QL_INTERCEPT=QL_INTERCEPT.CALL):
149149
"""Either hook or replace a system call with a custom one.
150150
@@ -178,10 +178,8 @@ def getNameFromErrorCode(ret: int) -> str:
178178
return f'{ret:#x}{f" ({errors[-ret]})" if -ret in errors else f""}'
179179

180180
def load_syscall(self):
181-
# import syscall mapping function
182-
map_syscall = ql_syscall_mapping_function(self.ql.ostype)
183-
syscall_id = self.syscall
184-
syscall_name = map_syscall(self.ql, syscall_id)
181+
syscall_id = self.get_syscall()
182+
syscall_name = self.syscall_mapper(syscall_id)
185183

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

198196
if not syscall_hook:
199-
osname = ostype_convert_str(self.ql.ostype)
200-
os_syscalls = ql_get_module_function(f"qiling.os.{osname.lower()}", "syscall")
201-
posix_syscalls = ql_get_module_function(f"qiling.os.posix", "syscall")
197+
def __get_os_module(osname: str):
198+
return ql_get_module_function(f'qiling.os.{osname.lower()}', 'syscall')
199+
200+
os_syscalls = __get_os_module(ostype_convert_str(self.ql.ostype))
201+
posix_syscalls = __get_os_module('posix')
202202

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

208206
if syscall_hook:
209207
syscall_name = syscall_hook.__name__

qiling/os/posix/syscall/select.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ def parse_fd_set(ql: Qiling, max_fd: int, struct_addr: int) -> Tuple[Sequence[in
2121

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

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

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

5858
timeout_total = sec + float(usec) / 1000000
5959
else:

qiling/os/posix/syscall/signal.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,13 @@
77

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

1513
ql.mem.write(oldact, data)
1614

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

2018
return 0
2119

0 commit comments

Comments
 (0)