diff --git a/qiling/const.py b/qiling/const.py index fca6dde0..fd06eefa 100644 --- a/qiling/const.py +++ b/qiling/const.py @@ -91,10 +91,11 @@ def __casefold_enum(e: Type[T]) -> Mapping[str, T]: return dict((k.casefold(), v) for k, v in e.__members__.items()) -debugger_map = __casefold_enum(QL_DEBUGGER) +endian_map = __casefold_enum(QL_ENDIAN) arch_map = __casefold_enum(QL_ARCH) os_map = __casefold_enum(QL_OS) verbose_map = __casefold_enum(QL_VERBOSE) +debugger_map = __casefold_enum(QL_DEBUGGER) arch_os_map = { QL_ARCH.EVM : QL_OS.EVM, diff --git a/qltool b/qltool index 392ca5ac..18fb1801 100755 --- a/qltool +++ b/qltool @@ -10,7 +10,7 @@ import ast import pickle from pprint import pprint -from typing import TYPE_CHECKING, Mapping, Type +from typing import TYPE_CHECKING, Any, AnyStr, Dict, Literal, Mapping, Type, overload from unicorn import __version__ as uc_ver from qiling import __version__ as ql_ver @@ -18,7 +18,7 @@ from qiling import __version__ as ql_ver from qiling import Qiling from qiling.arch import utils as arch_utils from qiling.debugger.qdb import QlQdb -from qiling.const import QL_VERBOSE, QL_ENDIAN, os_map, arch_map, verbose_map +from qiling.const import QL_ENDIAN, QL_VERBOSE, endian_map, os_map, arch_map, verbose_map from qiling.extensions.coverage import utils as cov_utils from qiling.extensions import report @@ -27,9 +27,19 @@ if TYPE_CHECKING: from enum import Enum -# read code from file -def read_file(fname: str): - with open(fname, "rb") as f: +@overload +def read_file(fname: AnyStr) -> str: ... + + +@overload +def read_file(fname: AnyStr, mode: Literal['b']) -> bytes: ... + + +def read_file(fname: AnyStr, mode: Literal['b', ''] = ''): + """Read file contents. + """ + + with open(fname, f'r{mode}') as f: content = f.read() return content @@ -59,66 +69,64 @@ def __make_enum_arg(enum_rmap: Mapping[str, 'Enum'], aliases: Mapping[str, str] return __enum_arg +__arg_endian = __make_enum_arg(endian_map) __arg_archtype = __make_enum_arg(arch_map, {'x86_64': 'x8664', 'riscv32': 'riscv'}) __arg_ostype = __make_enum_arg(os_map, {'darwin': 'macos'}) __arg_verbose = __make_enum_arg(verbose_map) -def handle_code(options: argparse.Namespace): - archendian = { - 'little': QL_ENDIAN.EL, - 'big' : QL_ENDIAN.EB - }[options.endian] - +def handle_code(options: argparse.Namespace) -> Dict[str, Any]: if options.format == 'hex': if options.input is not None: - print("Load HEX from ARGV") - code = str(options.input).strip("\\\\x").split("x") - code = "".join(code).strip() - code = bytes.fromhex(code) + print("Loading HEX from ARGV") + payload = str(options.input) + elif options.filename is not None: - print("Load HEX from FILE") - code = str(read_file(options.filename)).strip('b\'').strip('\\n') - code = code.strip('x').split("\\\\x") - code = "".join(code).strip() - code = bytes.fromhex(code) + print("Loading HEX from FILE") + payload = read_file(options.filename) + else: - print("ERROR: File not found") + print("Error: please specify an input source for hex") exit(1) - elif options.format == 'asm': - print("Load ASM from FILE") - assembly = read_file(options.filename) + code = bytes.fromhex(payload.replace(r'\x', '')) - assembler = arch_utils.assembler(options.arch, archendian, options.thumb) - code, _ = assembler.asm(assembly) + elif options.format == 'asm': + print("Loading ASM from FILE") + + if options.filename is None: + print("Error: please specify an input source for asm") + exit(1) + + payload = read_file(options.filename) + + # assemble the payload and turn it into opcodes bytes + assembler = arch_utils.assembler(options.arch, options.endian, options.thumb) + code, _ = assembler.asm(payload) code = bytes(code) elif options.format == 'bin': - print("Load BIN from FILE") - if options.filename is not None: - code = read_file(options.filename) - else: - print("ERROR: File not found") + print("Loading BIN from FILE") + + if options.filename is None: + print("Error: please specify an input source for bin") exit(1) - ql = Qiling( - rootfs=options.rootfs, - env=options.env, - code=code, - ostype=options.os, - archtype=options.arch, - verbose=options.verbose, - profile=options.profile, - filter=options.filter, - endian=archendian, - thumb=options.thumb, - ) + code = read_file(options.filename, 'b') - return ql + ql_args = { + 'rootfs': options.rootfs, + 'code': code, + 'ostype': options.os, + 'archtype': options.arch, + 'endian': options.endian, + 'thumb': options.thumb + } + + return ql_args -def handle_run(options: argparse.Namespace): +def handle_run(options: argparse.Namespace) -> Dict[str, Any]: effective_argv = [] # with argv @@ -132,26 +140,12 @@ def handle_run(options: argparse.Namespace): else: print("ERROR: Command error!") - ql = Qiling( - argv=effective_argv, - rootfs=options.rootfs, - env=options.env, - verbose=options.verbose, - profile=options.profile, - console=options.console, - log_file=options.log_file, - log_plain=options.log_plain, - multithread=options.multithread, - filter=options.filter, - libcache=options.libcache - ) + ql_args = { + 'argv': effective_argv, + 'rootfs': options.rootfs + } - # attach Qdb at entry point - if options.qdb is True: - QlQdb(ql, rr=options.rr).run() - exit() - - return ql + return ql_args def handle_examples(parser: argparse.ArgumentParser): @@ -211,7 +205,7 @@ if __name__ == '__main__': code_parser.add_argument('-i', '--input', metavar="INPUT", dest="input", help='input hex value') code_parser.add_argument('--arch', required=True, choices=arch_map, action=__arg_archtype) code_parser.add_argument('--thumb', action='store_true', default=False, help='specify thumb mode for ARM') - code_parser.add_argument('--endian', choices=('little', 'big'), default='little', help='specify endianess for bi-endian archs') + code_parser.add_argument('--endian', choices=endian_map, default=QL_ENDIAN.EL, action=__arg_endian, help='specify endianess for bi-endian archs') code_parser.add_argument('--os', required=True, choices=os_map, action=__arg_ostype) code_parser.add_argument('--rootfs', default='.', help='emulated root filesystem, that is where all libraries reside') code_parser.add_argument('--format', choices=('asm', 'hex', 'bin'), default='bin', help='input file format') @@ -222,7 +216,6 @@ if __name__ == '__main__': # set "qltui" subcommand qltui_parser = commands.add_parser('qltui', help='show qiling Terminal User Interface', add_help=False) - qltui_enabled = False comm_parser = run_parser if len(sys.argv) > 1 and sys.argv[1] == 'code': @@ -249,21 +242,44 @@ if __name__ == '__main__': comm_parser.add_argument('--libcache', action='store_true', help='enable dll caching for windows') options = parser.parse_args() - if options.subcommand == 'examples': - handle_examples(parser) + qltui_enabled = False + ql_args = {} if options.subcommand == 'qltui': import qltui options = qltui.get_data() qltui_enabled = True + if options.subcommand == 'examples': + handle_examples(parser) + # ql file setup - if options.subcommand == 'run': - ql = handle_run(options) + elif options.subcommand == 'run': + ql_args = handle_run(options) # ql code setup - if options.subcommand == 'code': - ql = handle_code(options) + elif options.subcommand == 'code': + ql_args = handle_code(options) + + ql_args.update({ + 'env': options.env, + 'verbose': options.verbose, + 'profile': options.profile, + 'console': options.console, + 'filter': options.filter, + 'log_file': options.log_file, + 'log_plain': options.log_plain, + 'multithread': options.multithread, + 'libcache': options.libcache + }) + + ql = Qiling(**ql_args) + + # attach Qdb at entry point + if options.qdb: + QlQdb(ql, rr=bool(options.rr)).run() + + parser.exit(0) # ql execute additional options if options.gdb: @@ -281,20 +297,25 @@ if __name__ == '__main__': ql.debug_stop = True if options.root: - ql.root = True + ql.os.root = True + + if qltui_enabled: + hook_dictionary = qltui.hook(ql) # ql run - with cov_utils.collect_coverage(ql, options.coverage_format, options.coverage_file): - if qltui_enabled: - hook_dictionary = qltui.hook(ql) + if options.coverage_file: + with cov_utils.collect_coverage(ql, options.coverage_format, options.coverage_file): + ql.run(timeout=options.timeout) + else: ql.run(timeout=options.timeout) if options.json: - report = report.generate_report(ql) + rep = report.generate_report(ql) + if qltui_enabled: - report["syscalls"] = qltui.transform_syscalls(ql.os.stats.syscalls) - qltui.show_report(ql, report, hook_dictionary) + rep["syscalls"] = qltui.transform_syscalls(ql.os.stats.syscalls) + qltui.show_report(ql, rep, hook_dictionary) else: - pprint(report) + pprint(rep) exit(ql.os.exit_code)