Skip to content
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

Improvements and fixes for Windows and PE #1118

Merged
merged 80 commits into from
Apr 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
80 commits
Select commit Hold shift + click to select a range
7b56323
Introduce get_image_by_name to loader
elicn Mar 22, 2022
eb45064
Remove dlls dict in favor of get_image_by_name
elicn Mar 22, 2022
38d796d
Massive revamp of Windows OS and PE Loader
elicn Mar 22, 2022
82a4eec
Refresh handle module
elicn Mar 22, 2022
d0e9b90
Rewrite registry module
elicn Mar 22, 2022
e36f13d
Adjust registry module usages
elicn Mar 22, 2022
b7e7344
Refresh clipboard module
elicn Mar 22, 2022
6e183d9
Adjust clipboard module usages
elicn Mar 22, 2022
7256592
Refresh fiber module
elicn Mar 22, 2022
352fd77
Adjust fiber module usages
elicn Mar 22, 2022
3089a5c
Refresh thread module
elicn Mar 22, 2022
16706d1
Adjust components initialization
elicn Mar 22, 2022
6f702f0
Fix some Windows path usages
elicn Mar 22, 2022
3ee09d6
Call get_memory_mapped_image only once
elicn Mar 23, 2022
d4c5b68
Extract and extend QlOsStats
elicn Mar 23, 2022
0823dfb
Refresh gandcrab test
elicn Mar 23, 2022
726640a
Misc. small changes
elicn Mar 23, 2022
440dfd0
Fix path canonicalization on Windows hosts
elicn Mar 25, 2022
9f8522a
Add missing ucrtbase.dll to the dllscollector script
elicn Mar 26, 2022
3d40689
Rewrite dllscollector script
elicn Mar 27, 2022
153113e
Add missing exit command
elicn Mar 27, 2022
20c6ad8
Create missing drivers dir
elicn Mar 27, 2022
369f2fd
Add winsys property to Windows OS
elicn Mar 30, 2022
ea48d38
Simplify load_dll signature
elicn Mar 30, 2022
cbc3e0b
Let OS determine registry hive path
elicn Mar 30, 2022
4aa049b
Refreshed path module
elicn Mar 30, 2022
6393e12
Some changes to improve clarity
elicn Mar 30, 2022
ebb23f5
Some code quality changes
elicn Mar 30, 2022
74ea323
Refreshed sality test
elicn Mar 30, 2022
ad78b39
Minor additions to dllscolector
elicn Mar 30, 2022
deb079f
Allow searching image name case-insensitive
elicn Mar 31, 2022
420d4ca
Make Image class more linter-friendly
elicn Mar 31, 2022
e66e27e
Rewrite OS path module
elicn Apr 10, 2022
7831c99
Adjust path usages
elicn Apr 10, 2022
2bc4bbb
Adjust path tests
elicn Apr 10, 2022
074931f
Handle NT case-insensitive filenames on POSIX hosts
elicn Apr 10, 2022
d6fc766
Improve case insensitive filenames handling
elicn Apr 11, 2022
f8bc0d3
Make pack / unpack more linter-friendly
elicn Apr 12, 2022
d34a5be
Misc code quality fixes
elicn Apr 12, 2022
0f067e8
Turn cache entry into a named tuple
elicn Apr 12, 2022
736f426
Slightly better typing annotations on PE loader
elicn Apr 12, 2022
5279f60
Get rid of the problematic _typeshed import
elicn Apr 12, 2022
c98a171
Move EVM hooks handling to EVM arch
elicn Apr 12, 2022
64c9cb0
Tidy up EVM hooks
elicn Apr 12, 2022
3174c08
Remove ostype property from core and move to OS
elicn Apr 13, 2022
ab08c89
Adjust ostype usages
elicn Apr 13, 2022
d8890a1
Properly handle OS set_api
elicn Apr 14, 2022
1114426
Make interpreter predicate based on arch rather than OS
elicn Apr 14, 2022
47c6bd3
Misc insignificant changes
elicn Apr 14, 2022
2c6508b
Simplify loader_setup
elicn Apr 14, 2022
d2d3362
Extract logging logic from utils
elicn Apr 14, 2022
db0ab1e
Move QlFileDes definition to POSIX
elicn Apr 14, 2022
ac3ace4
Allow deleting log filter
elicn Apr 15, 2022
0951512
Simplify catch_KeyboardInterrupt
elicn Apr 15, 2022
e955cf7
Simplify hook_del
elicn Apr 15, 2022
de06207
Tidy up EVM hooks (round 2)
elicn Apr 15, 2022
0a5af91
Fix log colors bug
elicn Apr 17, 2022
683bc54
Skip tests properly
elicn Apr 17, 2022
e91a5f4
Improve and comment examples
elicn Apr 17, 2022
632d77a
Rearrange guess_emu_env
elicn Apr 17, 2022
e3e5c16
Rearrange enum convertion methods
elicn Apr 17, 2022
2a50840
Use lazy imports to reduce unnecessary deps
elicn Apr 17, 2022
5506cc9
Rearrange components setup methods
elicn Apr 17, 2022
e365e91
Misc insignificant fixes
elicn Apr 17, 2022
6f71b3f
Fix forgotten var rename
elicn Apr 18, 2022
6c547dc
Make argreg a class member
elicn Apr 19, 2022
707fb41
set getRawParam and setRawParam default argbits to 0
elicn Apr 19, 2022
e9de775
Remove unnecessary imports
elicn Apr 19, 2022
cd2c99b
Make dynamic imports relative to qiling package
elicn Apr 19, 2022
faf6719
Move get_syscall_mapper to POSIX
elicn Apr 19, 2022
45960e5
Allow dynamic import of an entire module
elicn Apr 19, 2022
2b1a00f
Have baremetal work around nonexistant OS
elicn Apr 19, 2022
791b5c2
Fix QDB init args enumeration
elicn Apr 20, 2022
6069920
Fix QDB test import
elicn Apr 20, 2022
a6e865c
Handle calls to get_terminal_size from non-tty
elicn Apr 20, 2022
86dbb47
Reduce imports clutter
elicn Apr 21, 2022
226a9ad
Have core uc property reference arch uc instance
elicn Apr 21, 2022
734810d
Minor robustness fixes to POSIX syscalls
elicn Apr 21, 2022
1713b68
Deduplicate Linux timespec struct
elicn Apr 21, 2022
7759e7f
Remove Python 3.6 workaround
elicn Apr 21, 2022
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
25 changes: 16 additions & 9 deletions examples/crackme_x86_linux.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,21 +89,26 @@ def replay(self, input: bytes) -> bool:

return False

def progress(msg: str) -> None:
print(msg, end='\r', file=sys.stderr, flush=True)

def main():
idx_list = (1, 4, 2, 0, 3)
flag = [0] * len(idx_list)
flag = bytearray(b'*****')
indices = (1, 4, 2, 0, 3)

solver = Solver(bytes(flag))
# all possible flag characters (may be reduced to uppercase and digits to save time)
charset = string.printable

for idx in idx_list:
progress('Initializing...')
solver = Solver(flag)

# bruteforce all possible flag characters
for ch in string.printable:
flag[idx] = ord(ch)
for i in indices:
for ch in charset:
flag[i] = ord(ch)

print(f'Guessing... [{"".join(chr(ch) if ch else "_" for ch in flag)}]', end='\r', file=sys.stderr, flush=True)
progress(f'Guessing... {flag.decode()}')

if solver.replay(bytes(flag)):
if solver.replay(flag):
break

else:
Expand All @@ -113,3 +118,5 @@ def main():

if __name__ == "__main__":
main()

# expected flag: L1NUX
146 changes: 105 additions & 41 deletions examples/crackme_x86_windows.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,47 +10,111 @@
from qiling.const import QL_VERBOSE
from qiling.extensions import pipe

def instruction_count(ql: Qiling, address: int, size: int, user_data):
user_data[0] += 1

def get_count(flag):
ql = Qiling(["rootfs/x86_windows/bin/crackme.exe"], "rootfs/x86_windows", verbose=QL_VERBOSE.OFF)

ql.os.stdin = pipe.SimpleStringBuffer()
ql.os.stdout = pipe.SimpleStringBuffer()

ql.os.stdin.write(bytes("".join(flag) + "\n", 'utf-8'))
count = [0]

ql.hook_code(instruction_count, count)
ql.run()

print(ql.os.stdout.read().decode('utf-8'), end='')
print(" ============ count: %d ============ " % count[0])

return count[0]

def solve():
# BJWXB_CTF{C5307D46-E70E-4038-B6F9-8C3F698B7C53}
prefix = list("BJWXB_CTF{C5307D46-E70E-4038-B6F9-8C3F698B7C")
flag = list("\x00" * 100)
base = get_count(prefix + flag)
i = 0

try:
for i in range(len(flag)):
for j in "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-{}":
flag[i] = j
data = get_count(prefix + flag)
if data > base:
base = data
print("\n\n\n>>> FLAG: " + "".join(prefix + flag) + "\n\n\n")
break
if flag[i] == "}":
ROOTFS = r"rootfs/x86_windows"

class Solver:
def __init__(self, invalid: bytes):
# create a silent qiling instance
self.ql = Qiling([rf"{ROOTFS}/bin/crackme.exe"], ROOTFS, verbose=QL_VERBOSE.OFF)

self.ql.os.stdin = pipe.SimpleInStream(sys.stdin.fileno()) # take over the input to the program using a fake stdin
self.ql.os.stdout = pipe.NullOutStream(sys.stdout.fileno()) # disregard program output

# execute program until it reaches the part that asks for input
self.ql.run(end=0x401030)

# record replay starting and ending points.
#
# since the emulation halted upon entering a function, its return address is there on
# the stack. we use it to limit the emulation till function returns
self.replay_starts = self.ql.arch.regs.arch_pc
self.replay_ends = self.ql.stack_read(0)

# instead of restarting the whole program every time a new flag character is guessed,
# we will restore its state to the latest point possible, fast-forwarding a good
# amount of start-up code that is not affected by the input.
#
# here we save the state just when the read input function is about to be called so we
# could use it to jumpstart the initialization part and get to the read input immediately
self.jumpstart = self.ql.save() or {}

# calibrate the replay instruction count by running the code with an invalid input
# first. the instruction count returned from the calibration process will be then
# used as a baseline for consequent replays
self.best_icount = self.__run(invalid)

def __run(self, input: bytes) -> int:
icount = [0]

def __count_instructions(ql: Qiling, address: int, size: int):
icount[0] += 1

# set a hook to fire up every time an instruction is about to execute
hobj = self.ql.hook_code(__count_instructions)

# feed stdin with input
self.ql.os.stdin.write(input + b'\n')

# resume emulation till function returns
self.ql.run(begin=self.replay_starts, end=self.replay_ends)

hobj.remove()

return icount[0]

def replay(self, input: bytes) -> bool:
"""Restore state and replay with a new input.

Returns an indication to execution progress: `True` if a progress
was made, `False` otherwise
"""

# restore program's state back to the starting point
self.ql.restore(self.jumpstart)

# resume emulation and count emulated instructions
curr_icount = self.__run(input)

# the larger part of the input is correct, the more instructions are expected to be executed. this is true
# for traditional loop-based validations like strcmp or memcmp which bails as soon as a mismatch is found:
# more correct characters mean more loop iterations - thus more executed instructions.
#
# if we got a higher instruction count, it means we made a progress in the right direction
if curr_icount > self.best_icount:
self.best_icount = curr_icount

return True

return False

def progress(msg: str) -> None:
print(msg, end='\r', file=sys.stderr, flush=True)

def main():
flag = bytearray(b'BJWXB_CTF{********-****-****-****-************}')
indices = (i for i, ch in enumerate(flag) if ch == ord('*'))

# uppercase hex digits
charset = '0123456789ABCDEF'

progress('Initializing...')
solver = Solver(flag)

for i in indices:
for ch in charset:
flag[i] = ord(ch)

progress(f'Guessing... {flag.decode()}')

if solver.replay(flag):
break
print("SOLVED!!!")
except KeyboardInterrupt:
print("STOP: KeyboardInterrupt")

else:
raise RuntimeError('no match found')

print(f'\nFlag found!')

if __name__ == "__main__":
solve()
main()

# expected flag: BJWXB_CTF{C5307D46-E70E-4038-B6F9-8C3F698B7C53}
41 changes: 24 additions & 17 deletions examples/hello_arm_linux_custom_syscall.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,30 +9,37 @@
from qiling import Qiling
from qiling.const import QL_VERBOSE

def my_syscall_write(ql: Qiling, write_fd, write_buf, write_count, *args, **kw):
regreturn = 0

# customized system calls always use the same arguments list as the original ones,
# but with a Qiling instance as first argument
def my_syscall_write(ql: Qiling, fd: int, buf: int, count: int):
try:
buf = ql.mem.read(write_buf, write_count)
ql.log.info("\n+++++++++\nmy write(%d,%x,%i) = %d\n+++++++++" % (write_fd, write_buf, write_count, regreturn))
ql.os.fd[write_fd].write(buf)
regreturn = write_count
# read data from emulated memory
data = ql.mem.read(buf, count)

# select the emulated file object that corresponds to the requested
# file descriptor
fobj = ql.os.fd[fd]

# write the data into the file object, if it supports write operations
if hasattr(fobj, 'write'):
fobj.write(data)
except:
regreturn = -1
ql.log.info("\n+++++++++\nmy write(%d,%x,%i) = %d\n+++++++++" % (write_fd, write_buf, write_count, regreturn))
ret = -1
else:
ret = count

return regreturn
ql.log.info(f'my_syscall_write({fd}, {buf:#x}, {count}) = {ret}')

return ret

if __name__ == "__main__":
ql = Qiling(["rootfs/arm_linux/bin/arm_hello"], "rootfs/arm_linux", verbose=QL_VERBOSE.DEBUG)
# Custom syscall handler by syscall name or syscall number.
# Known issue: If the syscall func is not be implemented in qiling, qiling does
# not know which func should be replaced.
# In that case, you must specify syscall by its number.
ql = Qiling([r'rootfs/arm_linux/bin/arm_hello'], r'rootfs/arm_linux', verbose=QL_VERBOSE.DEBUG)

# replacing a system call with a custom implementation.
# note that system calls may be referred to either by their name or number.
ql.os.set_syscall(0x04, my_syscall_write)

# set syscall by syscall name
#ql.os.set_syscall("write", my_syscall_write)
# an possible alternative: refer to a syscall by its name
#ql.os.set_syscall('write', my_syscall_write)

ql.run()
11 changes: 6 additions & 5 deletions examples/hello_arm_linux_debug.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@
from qiling import Qiling
from qiling.const import QL_VERBOSE

def run_sandbox(path, rootfs, verbose):
ql = Qiling(path, rootfs, verbose = verbose)
if __name__ == "__main__":
ql = Qiling([r'rootfs/arm_linux/bin/arm_hello'], r'rootfs/arm_linux', verbose=QL_VERBOSE.DEBUG)

ql.debugger = "qdb" # enable qdb without options

# other possible alternatives:
# ql.debugger = "qdb::rr" # switch on record and replay with rr
# ql.debugger = "qdb:0x1030c" # enable qdb and setup breakpoin at 0x1030c
ql.run()

if __name__ == "__main__":
run_sandbox(["rootfs/arm_linux/bin/arm_hello"], "rootfs/arm_linux", QL_VERBOSE.DEBUG)
ql.run()
6 changes: 5 additions & 1 deletion examples/sality.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,8 +151,12 @@ def hook_StartServiceA(ql: Qiling, address: int, params):
if service_handle.name in ql.os.services:
service_path = ql.os.services[service_handle.name]
service_path = ql.os.path.transform_to_real_path(service_path)

ql.amsint32_driver = Qiling([service_path], ql.rootfs, verbose=QL_VERBOSE.DEBUG)
init_unseen_symbols(ql.amsint32_driver, ql.amsint32_driver.loader.dlls["ntoskrnl.exe"]+0xb7695, b"NtTerminateProcess", 0, "ntoskrnl.exe")
ntoskrnl = ql.amsint32_driver.loader.get_image_by_name("ntoskrnl.exe")
assert ntoskrnl, 'ntoskernl.exe was not loaded'

init_unseen_symbols(ql.amsint32_driver, ntoskrnl.base+0xb7695, b"NtTerminateProcess", 0, "ntoskrnl.exe")
#ql.amsint32_driver.debugger= ":9999"
try:
ql.amsint32_driver.load()
Expand Down
Loading