mirror of
https://github.com/qilingframework/qiling.git
synced 2026-10-12 02:21:40 +00:00
@@ -0,0 +1,12 @@
|
||||
# EditorConfig is awesome: https://editorconfig.org
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
[*.py]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_size = 4
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
+10
-5
@@ -3,7 +3,8 @@
|
||||
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
|
||||
#
|
||||
|
||||
from abc import abstractmethod
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import ClassVar, Optional
|
||||
|
||||
from unicorn import Uc
|
||||
from unicorn.unicorn import UcContext
|
||||
@@ -12,16 +13,20 @@ from keystone import Ks
|
||||
|
||||
from qiling import Qiling
|
||||
from qiling.const import QL_ARCH, QL_ENDIAN
|
||||
|
||||
from .models import QL_CPU
|
||||
from .register import QlRegisterManager
|
||||
from .utils import QlArchUtils
|
||||
|
||||
|
||||
class QlArch:
|
||||
type: QL_ARCH
|
||||
bits: int
|
||||
class QlArch(ABC):
|
||||
type: ClassVar[QL_ARCH]
|
||||
bits: ClassVar[int]
|
||||
|
||||
def __init__(self, ql: Qiling):
|
||||
def __init__(self, ql: Qiling, *, cputype: Optional[QL_CPU] = None):
|
||||
self.ql = ql
|
||||
|
||||
self.cpu = cputype
|
||||
self.utils = QlArchUtils(ql)
|
||||
|
||||
@property
|
||||
|
||||
+13
-6
@@ -4,6 +4,7 @@
|
||||
#
|
||||
|
||||
from functools import cached_property, lru_cache
|
||||
from typing import Optional
|
||||
|
||||
from unicorn import Uc, UC_ARCH_ARM, UC_MODE_ARM, UC_MODE_THUMB, UC_MODE_BIG_ENDIAN
|
||||
from capstone import Cs, CS_ARCH_ARM, CS_MODE_ARM, CS_MODE_THUMB, CS_MODE_BIG_ENDIAN
|
||||
@@ -12,6 +13,7 @@ from keystone import Ks, KS_ARCH_ARM, KS_MODE_ARM, KS_MODE_THUMB, KS_MODE_BIG_EN
|
||||
from qiling import Qiling
|
||||
from qiling.arch.arch import QlArch
|
||||
from qiling.arch import arm_const
|
||||
from qiling.arch.models import ARM_CPU_MODEL
|
||||
from qiling.arch.register import QlRegisterManager
|
||||
from qiling.const import QL_ARCH, QL_ENDIAN
|
||||
|
||||
@@ -20,8 +22,8 @@ class QlArchARM(QlArch):
|
||||
type = QL_ARCH.ARM
|
||||
bits = 32
|
||||
|
||||
def __init__(self, ql: Qiling, endian: QL_ENDIAN, thumb: bool):
|
||||
super().__init__(ql)
|
||||
def __init__(self, ql: Qiling, *, cputype: Optional[ARM_CPU_MODEL], endian: QL_ENDIAN, thumb: bool):
|
||||
super().__init__(ql, cputype=cputype)
|
||||
|
||||
self._init_endian = endian
|
||||
self._init_thumb = thumb
|
||||
@@ -32,13 +34,18 @@ class QlArchARM(QlArch):
|
||||
def uc(self) -> Uc:
|
||||
mode = UC_MODE_ARM
|
||||
|
||||
if self._init_endian == QL_ENDIAN.EB:
|
||||
if self._init_endian is QL_ENDIAN.EB:
|
||||
mode += UC_MODE_BIG_ENDIAN
|
||||
|
||||
if self._init_thumb:
|
||||
mode += UC_MODE_THUMB
|
||||
|
||||
return Uc(UC_ARCH_ARM, mode)
|
||||
obj = Uc(UC_ARCH_ARM, mode)
|
||||
|
||||
if self.cpu is not None:
|
||||
obj.ctl_set_cpu_model(self.cpu.value)
|
||||
|
||||
return obj
|
||||
|
||||
@cached_property
|
||||
def regs(self) -> QlRegisterManager:
|
||||
@@ -82,7 +89,7 @@ class QlArchARM(QlArch):
|
||||
|
||||
mode = CS_MODE_ARM
|
||||
|
||||
if self.endian == QL_ENDIAN.EB:
|
||||
if self.endian is QL_ENDIAN.EB:
|
||||
mode += CS_MODE_BIG_ENDIAN
|
||||
|
||||
if self.is_thumb:
|
||||
@@ -102,7 +109,7 @@ class QlArchARM(QlArch):
|
||||
|
||||
mode = KS_MODE_ARM
|
||||
|
||||
if self.endian == QL_ENDIAN.EB:
|
||||
if self.endian is QL_ENDIAN.EB:
|
||||
mode += KS_MODE_BIG_ENDIAN
|
||||
|
||||
if self.is_thumb:
|
||||
|
||||
+12
-1
@@ -4,13 +4,16 @@
|
||||
#
|
||||
|
||||
from functools import cached_property
|
||||
from typing import Optional
|
||||
|
||||
from unicorn import Uc, UC_ARCH_ARM64, UC_MODE_ARM
|
||||
from capstone import Cs, CS_ARCH_ARM64, CS_MODE_ARM
|
||||
from keystone import Ks, KS_ARCH_ARM64, KS_MODE_ARM
|
||||
|
||||
from qiling import Qiling
|
||||
from qiling.arch.arch import QlArch
|
||||
from qiling.arch import arm64_const
|
||||
from qiling.arch.models import ARM64_CPU_MODEL
|
||||
from qiling.arch.register import QlRegisterManager
|
||||
from qiling.const import QL_ARCH, QL_ENDIAN
|
||||
|
||||
@@ -19,9 +22,17 @@ class QlArchARM64(QlArch):
|
||||
type = QL_ARCH.ARM64
|
||||
bits = 64
|
||||
|
||||
def __init__(self, ql: Qiling, *, cputype: Optional[ARM64_CPU_MODEL] = None):
|
||||
super().__init__(ql, cputype=cputype)
|
||||
|
||||
@cached_property
|
||||
def uc(self) -> Uc:
|
||||
return Uc(UC_ARCH_ARM64, UC_MODE_ARM)
|
||||
obj = Uc(UC_ARCH_ARM64, UC_MODE_ARM)
|
||||
|
||||
if self.cpu is not None:
|
||||
obj.ctl_set_cpu_model(self.cpu.value)
|
||||
|
||||
return obj
|
||||
|
||||
@cached_property
|
||||
def regs(self) -> QlRegisterManager:
|
||||
|
||||
+13
-8
@@ -5,6 +5,7 @@
|
||||
|
||||
from functools import cached_property
|
||||
from contextlib import ContextDecorator
|
||||
from typing import Optional
|
||||
|
||||
from unicorn import UC_ARCH_ARM, UC_MODE_ARM, UC_MODE_MCLASS, UC_MODE_THUMB
|
||||
from capstone import Cs, CS_ARCH_ARM, CS_MODE_ARM, CS_MODE_MCLASS, CS_MODE_THUMB
|
||||
@@ -13,6 +14,7 @@ from keystone import Ks, KS_ARCH_ARM, KS_MODE_ARM, KS_MODE_THUMB
|
||||
from qiling import Qiling
|
||||
from qiling.arch.arm import QlArchARM
|
||||
from qiling.arch import cortex_m_const
|
||||
from qiling.arch.models import ARM_CPU_MODEL
|
||||
from qiling.arch.register import QlRegisterManager
|
||||
from qiling.arch.cortex_m_const import IRQ, EXC_RETURN, CONTROL, EXCP
|
||||
from qiling.const import QL_ARCH, QL_ENDIAN, QL_VERBOSE
|
||||
@@ -66,12 +68,14 @@ class QlArchCORTEX_M(QlArchARM):
|
||||
type = QL_ARCH.CORTEX_M
|
||||
bits = 32
|
||||
|
||||
def __init__(self, ql: Qiling):
|
||||
super().__init__(ql, endian=QL_ENDIAN.EL, thumb=True)
|
||||
def __init__(self, ql: Qiling, *, cputype: Optional[ARM_CPU_MODEL] = None):
|
||||
super().__init__(ql, cputype=cputype, endian=QL_ENDIAN.EL, thumb=True)
|
||||
|
||||
@cached_property
|
||||
def uc(self):
|
||||
return MultiTaskUnicorn(UC_ARCH_ARM, UC_MODE_ARM + UC_MODE_MCLASS + UC_MODE_THUMB, 10)
|
||||
cpu = self.cpu and self.cpu.value
|
||||
|
||||
return MultiTaskUnicorn(UC_ARCH_ARM, UC_MODE_ARM + UC_MODE_MCLASS + UC_MODE_THUMB, cpu, 10)
|
||||
|
||||
@cached_property
|
||||
def regs(self) -> QlRegisterManager:
|
||||
@@ -97,18 +101,18 @@ class QlArchCORTEX_M(QlArchARM):
|
||||
def endian(self) -> QL_ENDIAN:
|
||||
return QL_ENDIAN.EL
|
||||
|
||||
def is_handler_mode(self):
|
||||
def is_handler_mode(self) -> bool:
|
||||
return self.regs.ipsr > 1
|
||||
|
||||
def using_psp(self):
|
||||
def using_psp(self) -> bool:
|
||||
return not self.is_handler_mode() and (self.regs.control & CONTROL.SPSEL) > 0
|
||||
|
||||
def init_context(self):
|
||||
def init_context(self) -> None:
|
||||
self.regs.lr = 0xffffffff
|
||||
self.regs.msp = self.ql.mem.read_ptr(0x0)
|
||||
self.regs.pc = self.ql.mem.read_ptr(0x4)
|
||||
|
||||
def unicorn_exception_handler(self, ql, intno):
|
||||
def unicorn_exception_handler(self, ql: Qiling, intno: int):
|
||||
forward_mapper = {
|
||||
EXCP.UDEF : IRQ.HARD_FAULT, # undefined instruction
|
||||
EXCP.SWI : IRQ.SVCALL, # software interrupt
|
||||
@@ -139,8 +143,9 @@ class QlArchCORTEX_M(QlArchARM):
|
||||
except IndexError:
|
||||
raise QlErrorNotImplemented(f'Unhandled interrupt number ({intno})')
|
||||
|
||||
def interrupt_handler(self, ql, intno):
|
||||
def interrupt_handler(self, ql: Qiling, intno: int):
|
||||
basepri = self.regs.basepri & 0xf0
|
||||
|
||||
if basepri and basepri <= ql.hw.nvic.get_priority(intno):
|
||||
return
|
||||
|
||||
|
||||
+16
-2
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
#
|
||||
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ from qiling.arch.evm.vm.evm import QlArchEVMEmulator
|
||||
from qiling.arch.evm.vm.message import Message
|
||||
from qiling.const import *
|
||||
|
||||
|
||||
class QlArchEVM(QlArch):
|
||||
type = QL_ARCH.EVM
|
||||
bits = 1
|
||||
@@ -39,7 +40,19 @@ class QlArchEVM(QlArch):
|
||||
|
||||
@property
|
||||
def uc(self):
|
||||
return None
|
||||
raise AttributeError
|
||||
|
||||
@property
|
||||
def regs(self):
|
||||
raise AttributeError
|
||||
|
||||
@property
|
||||
def disassembler(self):
|
||||
raise AttributeError
|
||||
|
||||
@property
|
||||
def assembler(self):
|
||||
raise AttributeError
|
||||
|
||||
@property
|
||||
def endian(self) -> QL_ENDIAN:
|
||||
@@ -49,6 +62,7 @@ class QlArchEVM(QlArch):
|
||||
def __evm_run(self, code: Message):
|
||||
return self.arch.run(code)
|
||||
|
||||
|
||||
def monkeypatch_core_methods(ql):
|
||||
"""Monkeypatch core methods for evm
|
||||
"""
|
||||
|
||||
+4
-2
@@ -4,6 +4,7 @@
|
||||
#
|
||||
|
||||
from functools import cached_property
|
||||
from typing import Optional
|
||||
|
||||
from unicorn import Uc, UC_ARCH_MIPS, UC_MODE_MIPS32, UC_MODE_BIG_ENDIAN, UC_MODE_LITTLE_ENDIAN
|
||||
from capstone import Cs, CS_ARCH_MIPS, CS_MODE_MIPS32, CS_MODE_BIG_ENDIAN, CS_MODE_LITTLE_ENDIAN
|
||||
@@ -12,6 +13,7 @@ from keystone import Ks, KS_ARCH_MIPS, KS_MODE_MIPS32, KS_MODE_BIG_ENDIAN, KS_MO
|
||||
from qiling import Qiling
|
||||
from qiling.arch.arch import QlArch
|
||||
from qiling.arch import mips_const
|
||||
from qiling.arch.models import MIPS_CPU_MODEL
|
||||
from qiling.arch.register import QlRegisterManager
|
||||
from qiling.const import QL_ARCH, QL_ENDIAN
|
||||
|
||||
@@ -20,8 +22,8 @@ class QlArchMIPS(QlArch):
|
||||
type = QL_ARCH.MIPS
|
||||
bits = 32
|
||||
|
||||
def __init__(self, ql: Qiling, endian: QL_ENDIAN):
|
||||
super().__init__(ql)
|
||||
def __init__(self, ql: Qiling, *, cputype: Optional[MIPS_CPU_MODEL], endian: QL_ENDIAN):
|
||||
super().__init__(ql, cputype=cputype)
|
||||
|
||||
self._init_endian = endian
|
||||
|
||||
|
||||
@@ -0,0 +1,447 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
|
||||
#
|
||||
|
||||
from enum import Enum
|
||||
from typing import Union
|
||||
|
||||
|
||||
class X86_CPU_MODEL(Enum):
|
||||
|
||||
# generic
|
||||
GENERIC_QEMU64 = 0
|
||||
GENERIC_KVM64 = 3
|
||||
GENERIC_QEMU32 = 4
|
||||
GENERIC_KVM32 = 5
|
||||
|
||||
# amd
|
||||
AMD_PHENOM = 1
|
||||
AMD_ATHLON = 11
|
||||
AMD_OPTERON_G1 = 30
|
||||
AMD_OPTERON_G2 = 31
|
||||
AMD_OPTERON_G3 = 32
|
||||
AMD_OPTERON_G4 = 33
|
||||
AMD_OPTERON_G5 = 34
|
||||
AMD_EPYC = 35
|
||||
AMD_DHYANA = 36
|
||||
AMD_EPYC_ROME = 37
|
||||
|
||||
# intel
|
||||
INTEL_CORE2DUO = 2
|
||||
INTEL_COREDUO = 6
|
||||
INTEL_486 = 7
|
||||
INTEL_PENTIUM = 8
|
||||
INTEL_PENTIUM2 = 9
|
||||
INTEL_PENTIUM3 = 10
|
||||
INTEL_N270 = 12
|
||||
INTEL_CONROE = 13
|
||||
INTEL_PENRYN = 14
|
||||
INTEL_NEHALEM = 15
|
||||
INTEL_WESTMERE = 16
|
||||
INTEL_SANDYBRIDGE = 17
|
||||
INTEL_IVYBRIDGE = 18
|
||||
INTEL_HASWELL = 19
|
||||
INTEL_BROADWELL = 20
|
||||
INTEL_SKYLAKE_CLIENT = 21
|
||||
INTEL_SKYLAKE_SERVER = 22
|
||||
INTEL_CASCADELAKE_SERVER = 23
|
||||
INTEL_COOPERLAKE = 24
|
||||
INTEL_ICELAKE_CLIENT = 25
|
||||
INTEL_ICELAKE_SERVER = 26
|
||||
INTEL_DENVERTON = 27
|
||||
INTEL_SNOWRIDGE = 28
|
||||
INTEL_KNIGHTSMILL = 29
|
||||
|
||||
|
||||
class ARM_CPU_MODEL(Enum):
|
||||
ARM_926 = 0
|
||||
ARM_946 = 1
|
||||
ARM_1026 = 2
|
||||
ARM_1136_R2 = 3
|
||||
ARM_1136 = 4
|
||||
ARM_1176 = 5
|
||||
ARM_11MPCORE = 6
|
||||
ARM_CORTEX_M0 = 7
|
||||
ARM_CORTEX_M3 = 8
|
||||
ARM_CORTEX_M4 = 9
|
||||
ARM_CORTEX_M7 = 10
|
||||
ARM_CORTEX_M33 = 11
|
||||
ARM_CORTEX_R5 = 12
|
||||
ARM_CORTEX_R5F = 13
|
||||
ARM_CORTEX_A7 = 14
|
||||
ARM_CORTEX_A8 = 15
|
||||
ARM_CORTEX_A9 = 16
|
||||
ARM_CORTEX_A15 = 17
|
||||
ARM_TI925T = 18
|
||||
ARM_SA1100 = 19
|
||||
ARM_SA1110 = 20
|
||||
ARM_PXA250 = 21
|
||||
ARM_PXA255 = 22
|
||||
ARM_PXA260 = 23
|
||||
ARM_PXA261 = 24
|
||||
ARM_PXA262 = 25
|
||||
ARM_PXA270 = 26
|
||||
ARM_PXA270A0 = 27
|
||||
ARM_PXA270A1 = 28
|
||||
ARM_PXA270B0 = 29
|
||||
ARM_PXA270B1 = 30
|
||||
ARM_PXA270C0 = 31
|
||||
ARM_PXA270C5 = 32
|
||||
ARM_MAX = 33
|
||||
|
||||
|
||||
class ARM64_CPU_MODEL(Enum):
|
||||
ARM64_A57 = 0
|
||||
ARM64_A53 = 1
|
||||
ARM64_A72 = 2
|
||||
ARM64_MAX = 3
|
||||
|
||||
|
||||
class MIPS_CPU_MODEL(Enum):
|
||||
MIPS32_4KC = 0
|
||||
MIPS32_4KM = 1
|
||||
MIPS32_4KECR1 = 2
|
||||
MIPS32_4KEMR1 = 3
|
||||
MIPS32_4KEC = 4
|
||||
MIPS32_4KEM = 5
|
||||
MIPS32_24KC = 6
|
||||
MIPS32_24KEC = 7
|
||||
MIPS32_24KF = 8
|
||||
MIPS32_34KF = 9
|
||||
MIPS32_74KF = 10
|
||||
MIPS32_M14K = 11
|
||||
MIPS32_M14KC = 12
|
||||
MIPS32_P5600 = 13
|
||||
MIPS32_MIPS32R6_GENERIC = 14
|
||||
MIPS32_I7200 = 15
|
||||
|
||||
|
||||
class PPC_CPU_MODEL(Enum):
|
||||
PPC32_401 = 0
|
||||
PPC32_401A1 = 1
|
||||
PPC32_401B2 = 2
|
||||
PPC32_401C2 = 3
|
||||
PPC32_401D2 = 4
|
||||
PPC32_401E2 = 5
|
||||
PPC32_401F2 = 6
|
||||
PPC32_401G2 = 7
|
||||
PPC32_IOP480 = 8
|
||||
PPC32_COBRA = 9
|
||||
PPC32_403GA = 10
|
||||
PPC32_403GB = 11
|
||||
PPC32_403GC = 12
|
||||
PPC32_403GCX = 13
|
||||
PPC32_405D2 = 14
|
||||
PPC32_405D4 = 15
|
||||
PPC32_405CRA = 16
|
||||
PPC32_405CRB = 17
|
||||
PPC32_405CRC = 18
|
||||
PPC32_405EP = 19
|
||||
PPC32_405EZ = 20
|
||||
PPC32_405GPA = 21
|
||||
PPC32_405GPB = 22
|
||||
PPC32_405GPC = 23
|
||||
PPC32_405GPD = 24
|
||||
PPC32_405GPR = 25
|
||||
PPC32_405LP = 26
|
||||
PPC32_NPE405H = 27
|
||||
PPC32_NPE405H2 = 28
|
||||
PPC32_NPE405L = 29
|
||||
PPC32_NPE4GS3 = 30
|
||||
PPC32_STB03 = 31
|
||||
PPC32_STB04 = 32
|
||||
PPC32_STB25 = 33
|
||||
PPC32_X2VP4 = 34
|
||||
PPC32_X2VP20 = 35
|
||||
PPC32_440_XILINX = 36
|
||||
PPC32_440_XILINX_W_DFPU = 37
|
||||
PPC32_440EPA = 38
|
||||
PPC32_440EPB = 39
|
||||
PPC32_440EPX = 40
|
||||
PPC32_460EXB = 41
|
||||
PPC32_G2 = 42
|
||||
PPC32_G2H4 = 43
|
||||
PPC32_G2GP = 44
|
||||
PPC32_G2LS = 45
|
||||
PPC32_G2HIP3 = 46
|
||||
PPC32_G2HIP4 = 47
|
||||
PPC32_MPC603 = 48
|
||||
PPC32_G2LE = 49
|
||||
PPC32_G2LEGP = 50
|
||||
PPC32_G2LELS = 51
|
||||
PPC32_G2LEGP1 = 52
|
||||
PPC32_G2LEGP3 = 53
|
||||
PPC32_MPC5200_V10 = 54
|
||||
PPC32_MPC5200_V11 = 55
|
||||
PPC32_MPC5200_V12 = 56
|
||||
PPC32_MPC5200B_V20 = 57
|
||||
PPC32_MPC5200B_V21 = 58
|
||||
PPC32_E200Z5 = 59
|
||||
PPC32_E200Z6 = 60
|
||||
PPC32_E300C1 = 61
|
||||
PPC32_E300C2 = 62
|
||||
PPC32_E300C3 = 63
|
||||
PPC32_E300C4 = 64
|
||||
PPC32_MPC8343 = 65
|
||||
PPC32_MPC8343A = 66
|
||||
PPC32_MPC8343E = 67
|
||||
PPC32_MPC8343EA = 68
|
||||
PPC32_MPC8347T = 69
|
||||
PPC32_MPC8347P = 70
|
||||
PPC32_MPC8347AT = 71
|
||||
PPC32_MPC8347AP = 72
|
||||
PPC32_MPC8347ET = 73
|
||||
PPC32_MPC8347EP = 74
|
||||
PPC32_MPC8347EAT = 75
|
||||
PPC32_MPC8347EAP = 76
|
||||
PPC32_MPC8349 = 77
|
||||
PPC32_MPC8349A = 78
|
||||
PPC32_MPC8349E = 79
|
||||
PPC32_MPC8349EA = 80
|
||||
PPC32_MPC8377 = 81
|
||||
PPC32_MPC8377E = 82
|
||||
PPC32_MPC8378 = 83
|
||||
PPC32_MPC8378E = 84
|
||||
PPC32_MPC8379 = 85
|
||||
PPC32_MPC8379E = 86
|
||||
PPC32_E500_V10 = 87
|
||||
PPC32_E500_V20 = 88
|
||||
PPC32_E500V2_V10 = 89
|
||||
PPC32_E500V2_V20 = 90
|
||||
PPC32_E500V2_V21 = 91
|
||||
PPC32_E500V2_V22 = 92
|
||||
PPC32_E500V2_V30 = 93
|
||||
PPC32_E500MC = 94
|
||||
PPC32_MPC8533_V10 = 95
|
||||
PPC32_MPC8533_V11 = 96
|
||||
PPC32_MPC8533E_V10 = 97
|
||||
PPC32_MPC8533E_V11 = 98
|
||||
PPC32_MPC8540_V10 = 99
|
||||
PPC32_MPC8540_V20 = 100
|
||||
PPC32_MPC8540_V21 = 101
|
||||
PPC32_MPC8541_V10 = 102
|
||||
PPC32_MPC8541_V11 = 103
|
||||
PPC32_MPC8541E_V10 = 104
|
||||
PPC32_MPC8541E_V11 = 105
|
||||
PPC32_MPC8543_V10 = 106
|
||||
PPC32_MPC8543_V11 = 107
|
||||
PPC32_MPC8543_V20 = 108
|
||||
PPC32_MPC8543_V21 = 109
|
||||
PPC32_MPC8543E_V10 = 110
|
||||
PPC32_MPC8543E_V11 = 111
|
||||
PPC32_MPC8543E_V20 = 112
|
||||
PPC32_MPC8543E_V21 = 113
|
||||
PPC32_MPC8544_V10 = 114
|
||||
PPC32_MPC8544_V11 = 115
|
||||
PPC32_MPC8544E_V10 = 116
|
||||
PPC32_MPC8544E_V11 = 117
|
||||
PPC32_MPC8545_V20 = 118
|
||||
PPC32_MPC8545_V21 = 119
|
||||
PPC32_MPC8545E_V20 = 120
|
||||
PPC32_MPC8545E_V21 = 121
|
||||
PPC32_MPC8547E_V20 = 122
|
||||
PPC32_MPC8547E_V21 = 123
|
||||
PPC32_MPC8548_V10 = 124
|
||||
PPC32_MPC8548_V11 = 125
|
||||
PPC32_MPC8548_V20 = 126
|
||||
PPC32_MPC8548_V21 = 127
|
||||
PPC32_MPC8548E_V10 = 128
|
||||
PPC32_MPC8548E_V11 = 129
|
||||
PPC32_MPC8548E_V20 = 130
|
||||
PPC32_MPC8548E_V21 = 131
|
||||
PPC32_MPC8555_V10 = 132
|
||||
PPC32_MPC8555_V11 = 133
|
||||
PPC32_MPC8555E_V10 = 134
|
||||
PPC32_MPC8555E_V11 = 135
|
||||
PPC32_MPC8560_V10 = 136
|
||||
PPC32_MPC8560_V20 = 137
|
||||
PPC32_MPC8560_V21 = 138
|
||||
PPC32_MPC8567 = 139
|
||||
PPC32_MPC8567E = 140
|
||||
PPC32_MPC8568 = 141
|
||||
PPC32_MPC8568E = 142
|
||||
PPC32_MPC8572 = 143
|
||||
PPC32_MPC8572E = 144
|
||||
PPC32_E600 = 145
|
||||
PPC32_MPC8610 = 146
|
||||
PPC32_MPC8641 = 147
|
||||
PPC32_MPC8641D = 148
|
||||
PPC32_601_V0 = 149
|
||||
PPC32_601_V1 = 150
|
||||
PPC32_601_V2 = 151
|
||||
PPC32_602 = 152
|
||||
PPC32_603 = 153
|
||||
PPC32_603E_V1_1 = 154
|
||||
PPC32_603E_V1_2 = 155
|
||||
PPC32_603E_V1_3 = 156
|
||||
PPC32_603E_V1_4 = 157
|
||||
PPC32_603E_V2_2 = 158
|
||||
PPC32_603E_V3 = 159
|
||||
PPC32_603E_V4 = 160
|
||||
PPC32_603E_V4_1 = 161
|
||||
PPC32_603E7 = 162
|
||||
PPC32_603E7T = 163
|
||||
PPC32_603E7V = 164
|
||||
PPC32_603E7V1 = 165
|
||||
PPC32_603E7V2 = 166
|
||||
PPC32_603P = 167
|
||||
PPC32_604 = 168
|
||||
PPC32_604E_V1_0 = 169
|
||||
PPC32_604E_V2_2 = 170
|
||||
PPC32_604E_V2_4 = 171
|
||||
PPC32_604R = 172
|
||||
PPC32_740_V1_0 = 173
|
||||
PPC32_750_V1_0 = 174
|
||||
PPC32_740_V2_0 = 175
|
||||
PPC32_750_V2_0 = 176
|
||||
PPC32_740_V2_1 = 177
|
||||
PPC32_750_V2_1 = 178
|
||||
PPC32_740_V2_2 = 179
|
||||
PPC32_750_V2_2 = 180
|
||||
PPC32_740_V3_0 = 181
|
||||
PPC32_750_V3_0 = 182
|
||||
PPC32_740_V3_1 = 183
|
||||
PPC32_750_V3_1 = 184
|
||||
PPC32_740E = 185
|
||||
PPC32_750E = 186
|
||||
PPC32_740P = 187
|
||||
PPC32_750P = 188
|
||||
PPC32_750CL_V1_0 = 189
|
||||
PPC32_750CL_V2_0 = 190
|
||||
PPC32_750CX_V1_0 = 191
|
||||
PPC32_750CX_V2_0 = 192
|
||||
PPC32_750CX_V2_1 = 193
|
||||
PPC32_750CX_V2_2 = 194
|
||||
PPC32_750CXE_V2_1 = 195
|
||||
PPC32_750CXE_V2_2 = 196
|
||||
PPC32_750CXE_V2_3 = 197
|
||||
PPC32_750CXE_V2_4 = 198
|
||||
PPC32_750CXE_V2_4B = 199
|
||||
PPC32_750CXE_V3_0 = 200
|
||||
PPC32_750CXE_V3_1 = 201
|
||||
PPC32_750CXE_V3_1B = 202
|
||||
PPC32_750CXR = 203
|
||||
PPC32_750FL = 204
|
||||
PPC32_750FX_V1_0 = 205
|
||||
PPC32_750FX_V2_0 = 206
|
||||
PPC32_750FX_V2_1 = 207
|
||||
PPC32_750FX_V2_2 = 208
|
||||
PPC32_750FX_V2_3 = 209
|
||||
PPC32_750GL = 210
|
||||
PPC32_750GX_V1_0 = 211
|
||||
PPC32_750GX_V1_1 = 212
|
||||
PPC32_750GX_V1_2 = 213
|
||||
PPC32_750L_V2_0 = 214
|
||||
PPC32_750L_V2_1 = 215
|
||||
PPC32_750L_V2_2 = 216
|
||||
PPC32_750L_V3_0 = 217
|
||||
PPC32_750L_V3_2 = 218
|
||||
PPC32_745_V1_0 = 219
|
||||
PPC32_755_V1_0 = 220
|
||||
PPC32_745_V1_1 = 221
|
||||
PPC32_755_V1_1 = 222
|
||||
PPC32_745_V2_0 = 223
|
||||
PPC32_755_V2_0 = 224
|
||||
PPC32_745_V2_1 = 225
|
||||
PPC32_755_V2_1 = 226
|
||||
PPC32_745_V2_2 = 227
|
||||
PPC32_755_V2_2 = 228
|
||||
PPC32_745_V2_3 = 229
|
||||
PPC32_755_V2_3 = 230
|
||||
PPC32_745_V2_4 = 231
|
||||
PPC32_755_V2_4 = 232
|
||||
PPC32_745_V2_5 = 233
|
||||
PPC32_755_V2_5 = 234
|
||||
PPC32_745_V2_6 = 235
|
||||
PPC32_755_V2_6 = 236
|
||||
PPC32_745_V2_7 = 237
|
||||
PPC32_755_V2_7 = 238
|
||||
PPC32_745_V2_8 = 239
|
||||
PPC32_755_V2_8 = 240
|
||||
PPC32_7400_V1_0 = 241
|
||||
PPC32_7400_V1_1 = 242
|
||||
PPC32_7400_V2_0 = 243
|
||||
PPC32_7400_V2_1 = 244
|
||||
PPC32_7400_V2_2 = 245
|
||||
PPC32_7400_V2_6 = 246
|
||||
PPC32_7400_V2_7 = 247
|
||||
PPC32_7400_V2_8 = 248
|
||||
PPC32_7400_V2_9 = 249
|
||||
PPC32_7410_V1_0 = 250
|
||||
PPC32_7410_V1_1 = 251
|
||||
PPC32_7410_V1_2 = 252
|
||||
PPC32_7410_V1_3 = 253
|
||||
PPC32_7410_V1_4 = 254
|
||||
PPC32_7448_V1_0 = 255
|
||||
PPC32_7448_V1_1 = 256
|
||||
PPC32_7448_V2_0 = 257
|
||||
PPC32_7448_V2_1 = 258
|
||||
PPC32_7450_V1_0 = 259
|
||||
PPC32_7450_V1_1 = 260
|
||||
PPC32_7450_V1_2 = 261
|
||||
PPC32_7450_V2_0 = 262
|
||||
PPC32_7450_V2_1 = 263
|
||||
PPC32_7441_V2_1 = 264
|
||||
PPC32_7441_V2_3 = 265
|
||||
PPC32_7451_V2_3 = 266
|
||||
PPC32_7441_V2_10 = 267
|
||||
PPC32_7451_V2_10 = 268
|
||||
PPC32_7445_V1_0 = 269
|
||||
PPC32_7455_V1_0 = 270
|
||||
PPC32_7445_V2_1 = 271
|
||||
PPC32_7455_V2_1 = 272
|
||||
PPC32_7445_V3_2 = 273
|
||||
PPC32_7455_V3_2 = 274
|
||||
PPC32_7445_V3_3 = 275
|
||||
PPC32_7455_V3_3 = 276
|
||||
PPC32_7445_V3_4 = 277
|
||||
PPC32_7455_V3_4 = 278
|
||||
PPC32_7447_V1_0 = 279
|
||||
PPC32_7457_V1_0 = 280
|
||||
PPC32_7447_V1_1 = 281
|
||||
PPC32_7457_V1_1 = 282
|
||||
PPC32_7457_V1_2 = 283
|
||||
PPC32_7447A_V1_0 = 284
|
||||
PPC32_7457A_V1_0 = 285
|
||||
PPC32_7447A_V1_1 = 286
|
||||
PPC32_7457A_V1_1 = 287
|
||||
PPC32_7447A_V1_2 = 288
|
||||
PPC32_7457A_V1_2 = 289
|
||||
|
||||
|
||||
class RISCV_CPU_MODEL(Enum):
|
||||
RISCV32_ANY = 0
|
||||
RISCV32_BASE32 = 1
|
||||
RISCV32_SIFIVE_E31 = 2
|
||||
RISCV32_SIFIVE_U34 = 3
|
||||
|
||||
|
||||
class RISCV64_CPU_MODEL(Enum):
|
||||
RISCV64_ANY = 0
|
||||
RISCV64_BASE64 = 1
|
||||
RISCV64_SIFIVE_E51 = 2
|
||||
RISCV64_SIFIVE_U54 = 3
|
||||
|
||||
|
||||
QL_CPU = Union[
|
||||
X86_CPU_MODEL,
|
||||
ARM_CPU_MODEL,
|
||||
ARM64_CPU_MODEL,
|
||||
MIPS_CPU_MODEL,
|
||||
PPC_CPU_MODEL,
|
||||
RISCV_CPU_MODEL,
|
||||
RISCV64_CPU_MODEL
|
||||
]
|
||||
|
||||
|
||||
__all__ = [
|
||||
'X86_CPU_MODEL',
|
||||
'ARM_CPU_MODEL',
|
||||
'ARM64_CPU_MODEL',
|
||||
'MIPS_CPU_MODEL',
|
||||
'PPC_CPU_MODEL',
|
||||
'RISCV_CPU_MODEL',
|
||||
'RISCV64_CPU_MODEL',
|
||||
'QL_CPU'
|
||||
]
|
||||
+12
-1
@@ -4,13 +4,16 @@
|
||||
#
|
||||
|
||||
from functools import cached_property
|
||||
from typing import Optional
|
||||
|
||||
from unicorn import Uc, UC_ARCH_PPC, UC_MODE_PPC32, UC_MODE_BIG_ENDIAN
|
||||
from capstone import Cs, CS_ARCH_PPC, CS_MODE_32, CS_MODE_BIG_ENDIAN
|
||||
from keystone import Ks, KS_ARCH_PPC, KS_MODE_PPC32, KS_MODE_BIG_ENDIAN
|
||||
|
||||
from qiling import Qiling
|
||||
from qiling.arch.arch import QlArch
|
||||
from qiling.arch import ppc_const
|
||||
from qiling.arch.models import PPC_CPU_MODEL
|
||||
from qiling.arch.register import QlRegisterManager
|
||||
from qiling.const import QL_ARCH, QL_ENDIAN
|
||||
|
||||
@@ -19,9 +22,17 @@ class QlArchPPC(QlArch):
|
||||
type = QL_ARCH.PPC
|
||||
bits = 32
|
||||
|
||||
def __init__(self, ql: Qiling, *, cputype: Optional[PPC_CPU_MODEL] = None):
|
||||
super().__init__(ql, cputype=cputype)
|
||||
|
||||
@cached_property
|
||||
def uc(self) -> Uc:
|
||||
return Uc(UC_ARCH_PPC, UC_MODE_PPC32 + UC_MODE_BIG_ENDIAN)
|
||||
obj = Uc(UC_ARCH_PPC, UC_MODE_PPC32 + UC_MODE_BIG_ENDIAN)
|
||||
|
||||
if self.cpu is not None:
|
||||
obj.ctl_set_cpu_model(self.cpu.value)
|
||||
|
||||
return obj
|
||||
|
||||
@cached_property
|
||||
def regs(self) -> QlRegisterManager:
|
||||
|
||||
+13
-2
@@ -4,12 +4,15 @@
|
||||
#
|
||||
|
||||
from functools import cached_property
|
||||
from typing import Optional
|
||||
|
||||
from unicorn import Uc, UC_ARCH_RISCV, UC_MODE_RISCV32
|
||||
from capstone import Cs
|
||||
from keystone import Ks
|
||||
|
||||
from qiling import Qiling
|
||||
from qiling.arch.arch import QlArch
|
||||
from qiling.arch.models import RISCV_CPU_MODEL
|
||||
from qiling.arch.register import QlRegisterManager
|
||||
from qiling.arch import riscv_const
|
||||
from qiling.arch.riscv_const import *
|
||||
@@ -21,9 +24,17 @@ class QlArchRISCV(QlArch):
|
||||
type = QL_ARCH.RISCV
|
||||
bits = 32
|
||||
|
||||
def __init__(self, ql: Qiling, *, cputype: Optional[RISCV_CPU_MODEL] = None):
|
||||
super().__init__(ql, cputype=cputype)
|
||||
|
||||
@cached_property
|
||||
def uc(self) -> Uc:
|
||||
return Uc(UC_ARCH_RISCV, UC_MODE_RISCV32)
|
||||
obj = Uc(UC_ARCH_RISCV, UC_MODE_RISCV32)
|
||||
|
||||
if self.cpu is not None:
|
||||
obj.ctl_set_cpu_model(self.cpu.value)
|
||||
|
||||
return obj
|
||||
|
||||
@cached_property
|
||||
def regs(self) -> QlRegisterManager:
|
||||
@@ -61,7 +72,7 @@ class QlArchRISCV(QlArch):
|
||||
def init_context(self):
|
||||
self.regs.pc = 0x08000000
|
||||
|
||||
def unicorn_exception_handler(self, ql, intno):
|
||||
def unicorn_exception_handler(self, ql: Qiling, intno: int):
|
||||
if intno == 2:
|
||||
ql.log.warning(f'[{hex(self.regs.arch_pc)}] Illegal instruction')
|
||||
|
||||
|
||||
+12
-1
@@ -4,11 +4,14 @@
|
||||
#
|
||||
|
||||
from functools import cached_property
|
||||
from typing import Optional
|
||||
|
||||
from unicorn import Uc, UC_ARCH_RISCV, UC_MODE_RISCV64
|
||||
from capstone import Cs
|
||||
from keystone import Ks
|
||||
|
||||
from qiling import Qiling
|
||||
from qiling.arch.models import RISCV64_CPU_MODEL
|
||||
from qiling.arch.riscv_const import *
|
||||
from qiling.const import QL_ARCH
|
||||
from qiling.exception import QlErrorNotImplemented
|
||||
@@ -20,9 +23,17 @@ class QlArchRISCV64(QlArchRISCV):
|
||||
type = QL_ARCH.RISCV64
|
||||
bits = 64
|
||||
|
||||
def __init__(self, ql: Qiling, *, cputype: Optional[RISCV64_CPU_MODEL] = None):
|
||||
super().__init__(ql, cputype=cputype)
|
||||
|
||||
@cached_property
|
||||
def uc(self) -> Uc:
|
||||
return Uc(UC_ARCH_RISCV, UC_MODE_RISCV64)
|
||||
obj = Uc(UC_ARCH_RISCV, UC_MODE_RISCV64)
|
||||
|
||||
if self.cpu is not None:
|
||||
obj.ctl_set_cpu_model(self.cpu.value)
|
||||
|
||||
return obj
|
||||
|
||||
@cached_property
|
||||
def disassembler(self) -> Cs:
|
||||
|
||||
+15
-2
@@ -28,9 +28,22 @@ class QlArchUtils:
|
||||
|
||||
@lru_cache(maxsize=64)
|
||||
def get_base_and_name(self, addr: int) -> Tuple[int, str]:
|
||||
for begin, end, _, name, _ in self.ql.mem.map_info:
|
||||
# executable images may be mapped in multiple consecutive regions, so locating
|
||||
# an address within a region doesn't mean its base address is the base of the
|
||||
# image. here we iterate through memory map regions as if they have been coalesced
|
||||
# by label to find the image base address.
|
||||
|
||||
base_label = '?'
|
||||
base_addr = -1
|
||||
|
||||
for begin, end, _, label, _ in self.ql.mem.map_info:
|
||||
# reached a different image?
|
||||
if label != base_label:
|
||||
base_addr = begin
|
||||
base_label = label
|
||||
|
||||
if begin <= addr < end:
|
||||
return begin, basename(name)
|
||||
return base_addr, basename(label)
|
||||
|
||||
return addr, '-'
|
||||
|
||||
|
||||
+21
-12
@@ -4,19 +4,40 @@
|
||||
#
|
||||
|
||||
from functools import cached_property
|
||||
from typing import Optional
|
||||
|
||||
from unicorn import Uc, UC_ARCH_X86, UC_MODE_16, UC_MODE_32, UC_MODE_64
|
||||
from capstone import Cs, CS_ARCH_X86, CS_MODE_16, CS_MODE_32, CS_MODE_64
|
||||
from keystone import Ks, KS_ARCH_X86, KS_MODE_16, KS_MODE_32, KS_MODE_64
|
||||
|
||||
from qiling import Qiling
|
||||
from qiling.arch.arch import QlArch
|
||||
from qiling.arch.msr import QlMsrManager
|
||||
from qiling.arch.models import X86_CPU_MODEL
|
||||
from qiling.arch.register import QlRegisterManager
|
||||
from qiling.arch import x86_const
|
||||
from qiling.const import QL_ARCH, QL_ENDIAN
|
||||
|
||||
|
||||
class QlArchIntel(QlArch):
|
||||
def __init__(self, ql: Qiling, *, cputype: Optional[X86_CPU_MODEL] = None):
|
||||
super().__init__(ql, cputype=cputype)
|
||||
|
||||
@cached_property
|
||||
def uc(self) -> Uc:
|
||||
mode = {
|
||||
16: UC_MODE_16,
|
||||
32: UC_MODE_32,
|
||||
64: UC_MODE_64
|
||||
}[self.bits]
|
||||
|
||||
obj = Uc(UC_ARCH_X86, mode)
|
||||
|
||||
if self.cpu is not None:
|
||||
obj.ctl_set_cpu_model(self.cpu.value)
|
||||
|
||||
return obj
|
||||
|
||||
@property
|
||||
def endian(self) -> QL_ENDIAN:
|
||||
return QL_ENDIAN.EL
|
||||
@@ -33,10 +54,6 @@ class QlArchA8086(QlArchIntel):
|
||||
type = QL_ARCH.A8086
|
||||
bits = 16
|
||||
|
||||
@cached_property
|
||||
def uc(self) -> Uc:
|
||||
return Uc(UC_ARCH_X86, UC_MODE_16)
|
||||
|
||||
@cached_property
|
||||
def regs(self) -> QlRegisterManager:
|
||||
regs_map = dict(
|
||||
@@ -63,10 +80,6 @@ class QlArchX86(QlArchIntel):
|
||||
type = QL_ARCH.X86
|
||||
bits = 32
|
||||
|
||||
@cached_property
|
||||
def uc(self) -> Uc:
|
||||
return Uc(UC_ARCH_X86, UC_MODE_32)
|
||||
|
||||
@cached_property
|
||||
def regs(self) -> QlRegisterManager:
|
||||
regs_map = dict(
|
||||
@@ -97,10 +110,6 @@ class QlArchX8664(QlArchIntel):
|
||||
type = QL_ARCH.X8664
|
||||
bits = 64
|
||||
|
||||
@cached_property
|
||||
def uc(self) -> Uc:
|
||||
return Uc(UC_ARCH_X86, UC_MODE_64)
|
||||
|
||||
@cached_property
|
||||
def regs(self) -> QlRegisterManager:
|
||||
regs_map = dict(
|
||||
|
||||
+2
-1
@@ -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,
|
||||
|
||||
+3
-1
@@ -20,6 +20,7 @@ if TYPE_CHECKING:
|
||||
from .hw.hw import QlHwManager
|
||||
from .loader.loader import QlLoader
|
||||
|
||||
from .arch.models import QL_CPU
|
||||
from .const import QL_ARCH, QL_ENDIAN, QL_OS, QL_STATE, QL_STOP, QL_VERBOSE, QL_ARCH_INTERPRETER, QL_OS_BAREMETAL
|
||||
from .exception import QlErrorFileNotFound, QlErrorArch, QlErrorOsType
|
||||
from .host import QlHost
|
||||
@@ -38,6 +39,7 @@ class Qiling(QlCoreHooks, QlCoreStructs):
|
||||
code: Optional[bytes] = None,
|
||||
ostype: Optional[QL_OS] = None,
|
||||
archtype: Optional[QL_ARCH] = None,
|
||||
cputype: Optional[QL_CPU] = None,
|
||||
verbose: QL_VERBOSE = QL_VERBOSE.DEFAULT,
|
||||
profile: Optional[Union[str, Mapping]] = None,
|
||||
console: bool = True,
|
||||
@@ -147,7 +149,7 @@ class Qiling(QlCoreHooks, QlCoreStructs):
|
||||
if endian is None:
|
||||
endian = QL_ENDIAN.EL
|
||||
|
||||
self._arch = select_arch(archtype, endian, thumb)(self)
|
||||
self._arch = select_arch(archtype, cputype, endian, thumb)(self)
|
||||
|
||||
# Once we finish setting up arch, we can init QlCoreStructs and QlCoreHooks
|
||||
if not self.interpreter:
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
#
|
||||
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
|
||||
#
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
from qiling import Qiling
|
||||
|
||||
|
||||
class QlBaseCoverage(ABC):
|
||||
"""
|
||||
@@ -12,14 +14,16 @@ class QlBaseCoverage(ABC):
|
||||
To add support for a new coverage format, just derive from this class and implement
|
||||
all the methods marked with the @abstractmethod decorator.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
|
||||
def __init__(self, ql: Qiling):
|
||||
super().__init__()
|
||||
|
||||
self.ql = ql
|
||||
|
||||
@property
|
||||
@staticmethod
|
||||
@abstractmethod
|
||||
def FORMAT_NAME():
|
||||
def FORMAT_NAME() -> str:
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
@@ -31,6 +35,5 @@ class QlBaseCoverage(ABC):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def dump_coverage(self, coverage_file):
|
||||
def dump_coverage(self, coverage_file: str):
|
||||
pass
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
#
|
||||
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
|
||||
#
|
||||
|
||||
@@ -17,6 +17,7 @@ class bb_entry(Structure):
|
||||
("mod_id", c_uint16)
|
||||
]
|
||||
|
||||
|
||||
class QlDrCoverage(QlBaseCoverage):
|
||||
"""
|
||||
Collects emulated code coverage and formats it in accordance with the DynamoRIO based
|
||||
@@ -29,12 +30,12 @@ class QlDrCoverage(QlBaseCoverage):
|
||||
FORMAT_NAME = "drcov"
|
||||
|
||||
def __init__(self, ql):
|
||||
super().__init__()
|
||||
self.ql = ql
|
||||
super().__init__(ql)
|
||||
|
||||
self.drcov_version = 2
|
||||
self.drcov_flavor = 'drcov'
|
||||
self.basic_blocks = []
|
||||
self.bb_callback = None
|
||||
self.drcov_flavor = 'drcov'
|
||||
self.basic_blocks = []
|
||||
self.bb_callback = None
|
||||
|
||||
@staticmethod
|
||||
def block_callback(ql, address, size, self):
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from __future__ import annotations
|
||||
from typing import List, Tuple, TYPE_CHECKING, Union, Optional, Any
|
||||
from typing import List, Sequence, Tuple, TYPE_CHECKING, Union, Optional, Any
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from qiling import Qiling
|
||||
@@ -24,8 +24,8 @@ class History:
|
||||
self.track_block_coverage()
|
||||
|
||||
def clear_history(self) -> None:
|
||||
"""Clears the current state of the history
|
||||
|
||||
"""Clears the current state of the history
|
||||
|
||||
"""
|
||||
self.history.clear()
|
||||
|
||||
@@ -33,7 +33,7 @@ class History:
|
||||
"""Clears the current history hook from the Qiling instance
|
||||
|
||||
Returns:
|
||||
None
|
||||
None
|
||||
"""
|
||||
|
||||
self.ql.hook_del(self.history_hook_handle)
|
||||
@@ -56,13 +56,13 @@ class History:
|
||||
try:
|
||||
self.history.append(next(self.md.disasm(ins_bytes, address)))
|
||||
except StopIteration:
|
||||
# if this ever happens, then the unicorn/qiling is going to crash because it tried to execute
|
||||
# if this ever happens, then the unicorn/qiling is going to crash because it tried to execute
|
||||
# an instruction that it cant, so we are just not going to do anything
|
||||
pass
|
||||
|
||||
def track_block_coverage(self) -> None:
|
||||
"""Configures the history plugin to track all of the basic blocks that are executed. Removes any existing hooks
|
||||
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
@@ -73,7 +73,7 @@ class History:
|
||||
|
||||
def track_instruction_coverage(self) -> None:
|
||||
"""Configures the history plugin to track all of the instructions that are executed. Removes any existing hooks
|
||||
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
@@ -82,9 +82,9 @@ class History:
|
||||
|
||||
self.history_hook_handle = self.ql.hook_code(self.__hook_block)
|
||||
|
||||
def get_ins_only_lib(self, libs: List[str]) -> List[CsInsn]:
|
||||
def get_ins_only_lib(self, libs: Union[str, Sequence[str]]) -> List[CsInsn]:
|
||||
"""Returns a list of addresses that have been executed that are only in mmaps for objects that match the regex of items in the list
|
||||
|
||||
|
||||
Args:
|
||||
libs (List[str]): A list of regex strings to match against the library names in the memory maps
|
||||
|
||||
@@ -93,18 +93,18 @@ class History:
|
||||
|
||||
Examples:
|
||||
>>> history.get_ins_only_lib([".*libc.so.*", ".*libpthread.so.*"])
|
||||
"""
|
||||
"""
|
||||
|
||||
executable_maps = self.get_regex_matching_exec_maps(libs)
|
||||
|
||||
|
||||
return [x for x in self.history if any(start <= x.address <= end for start, end, _, _, _ in executable_maps)]
|
||||
|
||||
def get_ins_exclude_lib(self, libs: List[str]) -> List[CsInsn]:
|
||||
def get_ins_exclude_lib(self, libs: Union[str, Sequence[str]]) -> List[CsInsn]:
|
||||
'''Returns a list of history instructions that are not in the libraries that match the regex in the libs list
|
||||
|
||||
|
||||
Args:
|
||||
libs (List[str]): A list of regex strings to match against the library names in the memory maps
|
||||
|
||||
|
||||
Returns:
|
||||
List[capstone.CsInsn]: A list of CsInsn that have been executed and are not in the memory maps that match the regex
|
||||
|
||||
@@ -115,7 +115,7 @@ class History:
|
||||
executable_maps = self.get_regex_matching_exec_maps(libs)
|
||||
return [h for h in self.history if not any(start <= h.address <= end for start, end, _, _, _ in executable_maps)]
|
||||
|
||||
def get_mem_map_from_addr(self, ins: Union[int, CsInsn]) -> Optional[Tuple]:
|
||||
def get_mem_map_from_addr(self, ins: Union[int, CsInsn]) -> Optional[Tuple[int, int, str, str, str]]:
|
||||
'''Returns the memory map that contains the instruction
|
||||
|
||||
Args:
|
||||
@@ -125,7 +125,7 @@ class History:
|
||||
Optional[Tuple]: A tuple that contains the memory map that contains the instruction
|
||||
this tuple is in the format of (start_addr, end_addr, perms, name, path)
|
||||
|
||||
Examples:
|
||||
Examples:
|
||||
>>> history.get_mem_map_from_addr(0x7ffff7dd1b97)
|
||||
'''
|
||||
|
||||
@@ -134,12 +134,12 @@ class History:
|
||||
|
||||
assert isinstance(ins, int)
|
||||
|
||||
return next((x for x in self.ql.mem.get_mapinfo() if x[0] <= ins and x[1] >= ins), None)
|
||||
return next((x for x in self.ql.mem.get_mapinfo() if x[0] <= ins <= x[1]), None)
|
||||
|
||||
def get_regex_matching_exec_maps(self, libs: List[str]) -> List[Tuple]:
|
||||
def get_regex_matching_exec_maps(self, libs: Union[str, Sequence[str]]) -> List[Tuple[int, int, str, str, str]]:
|
||||
'''Returns a list of tuples for current mmaps whose names match the regex of libs in the list
|
||||
|
||||
This is a wrapper around ql.mem.get_mapinfo() and just filters the results by the regex of the library names
|
||||
|
||||
This is a wrapper around ql.mem.get_mapinfo() and just filters the results by the regex of the library names
|
||||
and also only returns maps that are executable
|
||||
|
||||
Args:
|
||||
@@ -158,9 +158,9 @@ class History:
|
||||
libs = [libs]
|
||||
|
||||
# filter the history list by the library name, using a list of regex
|
||||
regex = [re.compile(lib) for lib in libs]
|
||||
regex = [re.compile(lib) for lib in libs]
|
||||
|
||||
# filter the list of tuples
|
||||
# filter the list of tuples
|
||||
# so that we return only the ones where the library name matches the regex
|
||||
regex_matching_libs = [x for x in self.ql.mem.get_mapinfo() if any(r.match(x[3]) for r in regex)]
|
||||
|
||||
|
||||
@@ -1,15 +1,25 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
#
|
||||
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
|
||||
#
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from contextlib import contextmanager
|
||||
from typing import Dict, TYPE_CHECKING, List, Type, TypeVar
|
||||
|
||||
from .formats import *
|
||||
from .formats.base import QlBaseCoverage
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from qiling import Qiling
|
||||
|
||||
CT = TypeVar('CT', bound=QlBaseCoverage)
|
||||
|
||||
|
||||
# Returns subclasses recursively.
|
||||
def get_all_subclasses(cls):
|
||||
def get_all_subclasses(cls: Type[CT]) -> List[Type[CT]]:
|
||||
all_subclasses = []
|
||||
|
||||
for subclass in cls.__subclasses__():
|
||||
@@ -18,21 +28,24 @@ def get_all_subclasses(cls):
|
||||
|
||||
return all_subclasses
|
||||
|
||||
class CoverageFactory():
|
||||
|
||||
class CoverageFactory:
|
||||
def __init__(self):
|
||||
self.coverage_collectors = {subcls.FORMAT_NAME:subcls for subcls in get_all_subclasses(base.QlBaseCoverage)}
|
||||
self.coverage_collectors: Dict[str, Type[QlBaseCoverage]] = {subcls.FORMAT_NAME: subcls for subcls in get_all_subclasses(QlBaseCoverage)}
|
||||
|
||||
@property
|
||||
def formats(self):
|
||||
return self.coverage_collectors.keys()
|
||||
|
||||
def get_coverage_collector(self, ql, name):
|
||||
def get_coverage_collector(self, ql: Qiling, name: str) -> QlBaseCoverage:
|
||||
return self.coverage_collectors[name](ql)
|
||||
|
||||
|
||||
factory = CoverageFactory()
|
||||
|
||||
|
||||
@contextmanager
|
||||
def collect_coverage(ql, name, coverage_file):
|
||||
def collect_coverage(ql: Qiling, name: str, coverage_file: str):
|
||||
"""
|
||||
Context manager for emulating a given piece of code with coverage collection turned on.
|
||||
Example:
|
||||
@@ -42,9 +55,9 @@ def collect_coverage(ql, name, coverage_file):
|
||||
|
||||
cov = factory.get_coverage_collector(ql, name)
|
||||
cov.activate()
|
||||
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
cov.deactivate()
|
||||
if coverage_file:
|
||||
cov.dump_coverage(coverage_file)
|
||||
cov.dump_coverage(coverage_file)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
# Lazymio ([email protected])
|
||||
|
||||
from typing import Dict, List
|
||||
from unicorn import *
|
||||
from unicorn.x86_const import UC_X86_REG_EIP, UC_X86_REG_RIP
|
||||
from unicorn.arm64_const import UC_ARM64_REG_PC
|
||||
@@ -16,6 +15,9 @@ import gevent.threadpool
|
||||
import gevent.lock
|
||||
import threading
|
||||
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
|
||||
# This class is named UnicornTask be design since it's not a
|
||||
# real thread. The expected usage is to inherit this class
|
||||
# and overwrite specific methods.
|
||||
@@ -23,10 +25,10 @@ import threading
|
||||
# This class is a friend class of MultiTaskUnicorn
|
||||
class UnicornTask:
|
||||
|
||||
def __init__(self, uc: Uc, begin: int, end: int, task_id = None):
|
||||
def __init__(self, uc: Uc, begin: int, end: int, task_id=None):
|
||||
self._uc = uc
|
||||
self._begin = begin
|
||||
self._end = end
|
||||
self._end = end
|
||||
self._stop_request = False
|
||||
self._ctx = None
|
||||
self._task_id = None
|
||||
@@ -34,46 +36,55 @@ class UnicornTask:
|
||||
self._mode = self._uc._mode
|
||||
|
||||
@property
|
||||
def pc(self):
|
||||
def pc(self) -> int:
|
||||
""" Get current PC of the thread. This property should only be accessed when
|
||||
the task is running.
|
||||
"""
|
||||
raw_pc = self._raw_pc()
|
||||
if (self._uc.reg_read(UC_ARM_REG_CPSR) & (1 << 5)):
|
||||
return raw_pc | 1
|
||||
else:
|
||||
return raw_pc
|
||||
|
||||
def _raw_pc(self):
|
||||
if self._arch == UC_ARCH_ARM:
|
||||
return raw_pc | ((self._uc.reg_read(UC_ARM_REG_CPSR) >> 5) & 0b1)
|
||||
|
||||
return raw_pc
|
||||
|
||||
def _raw_pc(self) -> int:
|
||||
# This extension is designed to be independent of Qiling, so let's
|
||||
# do this manually...
|
||||
|
||||
pc_reg = 0 # invalid reg
|
||||
|
||||
if self._arch == UC_ARCH_X86:
|
||||
if (self._mode & UC_MODE_32) != 0:
|
||||
return self._uc.reg_read(UC_X86_REG_EIP)
|
||||
elif (self._mode & UC_MODE_64) != 0:
|
||||
return self._uc.reg_read(UC_X86_REG_RIP)
|
||||
if self._mode & UC_MODE_32:
|
||||
pc_reg = UC_X86_REG_EIP
|
||||
elif self._mode & UC_MODE_64:
|
||||
pc_reg = UC_X86_REG_RIP
|
||||
|
||||
elif self._arch == UC_ARCH_MIPS:
|
||||
return self._uc.reg_read(UC_MIPS_REG_PC)
|
||||
pc_reg = UC_MIPS_REG_PC
|
||||
|
||||
elif self._arch == UC_ARCH_ARM:
|
||||
return self._uc.reg_read(UC_ARM_REG_PC)
|
||||
pc_reg = UC_ARM_REG_PC
|
||||
|
||||
elif self._arch == UC_ARCH_ARM64:
|
||||
return self._uc.reg_read(UC_ARM64_REG_PC)
|
||||
elif self._arch == UC_ARCH_PPC:
|
||||
return self._uc.reg_read(UC_PPC_REG_PC)
|
||||
elif self._arch == UC_ARCH_M68K:
|
||||
return self._uc.reg_read(UC_M68K_REG_PC)
|
||||
elif self._arch == UC_ARCH_SPARC:
|
||||
return self._uc.reg_read(UC_SPARC_REG_PC)
|
||||
elif self._arch == UC_ARCH_RISCV:
|
||||
return self._uc.reg_read(UC_RISCV_REG_PC)
|
||||
pc_reg = UC_ARM64_REG_PC
|
||||
|
||||
# Really?
|
||||
return 0
|
||||
elif self._arch == UC_ARCH_PPC:
|
||||
pc_reg = UC_PPC_REG_PC
|
||||
|
||||
elif self._arch == UC_ARCH_M68K:
|
||||
pc_reg = UC_M68K_REG_PC
|
||||
|
||||
elif self._arch == UC_ARCH_SPARC:
|
||||
pc_reg = UC_SPARC_REG_PC
|
||||
|
||||
elif self._arch == UC_ARCH_RISCV:
|
||||
pc_reg = UC_RISCV_REG_PC
|
||||
|
||||
return self._uc.reg_read(pc_reg) if pc_reg else 0
|
||||
|
||||
def _reach_end(self):
|
||||
# We may stop due to the scheduler asks us to, so check it manually.
|
||||
#print(f"{hex(self._raw_pc())} {hex(self._end)}")
|
||||
# print(f"{hex(self._raw_pc())} {hex(self._end)}")
|
||||
return self._raw_pc() == self._end
|
||||
|
||||
def save(self):
|
||||
@@ -106,13 +117,14 @@ class UnicornTask:
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
# This manages nested uc_emu_start calls and is designed as a friend
|
||||
# class of MultiTaskUnicorn.
|
||||
class NestedCounter:
|
||||
|
||||
def __init__(self, mtuc: "MultiTaskUnicorn"):
|
||||
self._mtuc = mtuc
|
||||
|
||||
|
||||
def __enter__(self, *args, **kwargs):
|
||||
self._mtuc._nested_started += 1
|
||||
return self
|
||||
@@ -120,6 +132,7 @@ class NestedCounter:
|
||||
def __exit__(self, *args, **kwargs):
|
||||
self._mtuc._nested_started -= 1
|
||||
|
||||
|
||||
# This mimic a Unicorn object by maintaining the same interface.
|
||||
# If no task is registered, the behavior is exactly the same as
|
||||
# a normal unicorn.
|
||||
@@ -137,14 +150,18 @@ class NestedCounter:
|
||||
# the same time.
|
||||
class MultiTaskUnicorn(Uc):
|
||||
|
||||
def __init__(self, arch, mode, interval: int = 100):
|
||||
def __init__(self, arch: int, mode: int, cpu: Optional[int], interval: Optional[int] = 100):
|
||||
""" Create a MultiTaskUnicorn object.
|
||||
Interval: Sceduling interval in **ms**. The longger interval, the better
|
||||
performance but less interrupts.
|
||||
"""
|
||||
super().__init__(arch, mode)
|
||||
|
||||
if cpu is not None:
|
||||
self.ctl_set_cpu_model(cpu)
|
||||
|
||||
self._interval = interval
|
||||
self._tasks = {} # type: Dict[int, UnicornTask]
|
||||
self._tasks = {} # type: Dict[int, UnicornTask]
|
||||
self._task_id_counter = 2000
|
||||
self._to_stop = False
|
||||
self._cur_utk_id = None
|
||||
@@ -271,7 +288,7 @@ class MultiTaskUnicorn(Uc):
|
||||
|
||||
def task_exit(self, utk_id):
|
||||
""" Stop a task.
|
||||
|
||||
|
||||
utk_id: The id returned from task_create.
|
||||
"""
|
||||
if utk_id not in self._tasks:
|
||||
@@ -369,4 +386,4 @@ class MultiTaskUnicorn(Uc):
|
||||
gevent.joinall(workset, raise_error=True)
|
||||
|
||||
if len(self._tasks) == 0:
|
||||
self._multitask_enabled = False
|
||||
self._multitask_enabled = False
|
||||
|
||||
+17
-11
@@ -1,15 +1,17 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
#
|
||||
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
|
||||
#
|
||||
|
||||
import sys, traceback
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
from .loader import QlLoader
|
||||
|
||||
from qiling import Qiling
|
||||
from qiling.os.disk import QlDisk
|
||||
|
||||
|
||||
# @see: http://pinvoke.net/default.aspx/Structures.IMAGE_DOS_HEADER
|
||||
class ComParser:
|
||||
'''Most basic COM file parser.
|
||||
@@ -20,17 +22,21 @@ class ComParser:
|
||||
|
||||
nbytes = ql.unpack16(data[2:4]) or 0x200 # number of bytes in last block; 0 means it is fully populated
|
||||
nblocks = ql.unpack16(data[4:6]) # number of blocks used
|
||||
hdrpgs = ql.unpack16(data[8:10]) # number of paragraphs taken up by the header
|
||||
|
||||
self.size = (nblocks - 1) * 0x200 + nbytes
|
||||
self.header_size = hdrpgs * 0x10
|
||||
|
||||
self.init_ss = ql.unpack16(data[14:16])
|
||||
self.init_sp = ql.unpack16(data[16:18])
|
||||
self.init_ip = ql.unpack16(data[20:22])
|
||||
self.init_cs = ql.unpack16(data[22:24])
|
||||
|
||||
|
||||
class QlLoaderDOS(QlLoader):
|
||||
def __init__(self, ql: Qiling):
|
||||
super(QlLoaderDOS, self).__init__(ql)
|
||||
self.ql = ql
|
||||
super().__init__(ql)
|
||||
|
||||
self.old_excepthook = sys.excepthook
|
||||
|
||||
# Hack to print all exceptions if curses has been setup.
|
||||
@@ -38,6 +44,7 @@ class QlLoaderDOS(QlLoader):
|
||||
if self.ql.os.stdscr is not None:
|
||||
tbmsg = "".join(traceback.format_exception(tp, value, tb))
|
||||
self.ql.log.info(f"{tbmsg}")
|
||||
|
||||
self.old_excepthook(tp, value, tb)
|
||||
|
||||
def run(self):
|
||||
@@ -49,9 +56,9 @@ class QlLoaderDOS(QlLoader):
|
||||
with open(path, "rb") as f:
|
||||
content = f.read()
|
||||
|
||||
cs = int(profile.get("COM", "start_cs"), 0)
|
||||
ip = int(profile.get("COM", "start_ip"), 0)
|
||||
sp = int(profile.get("COM", "start_sp"), 0)
|
||||
cs = profile.getint("COM", "start_cs")
|
||||
ip = profile.getint("COM", "start_ip")
|
||||
sp = profile.getint("COM", "start_sp")
|
||||
ss = cs
|
||||
|
||||
base_address = (cs << 4) + ip
|
||||
@@ -69,7 +76,7 @@ class QlLoaderDOS(QlLoader):
|
||||
ss = com.init_ss
|
||||
|
||||
base_address = 0
|
||||
content = content[0x80:]
|
||||
content = content[com.header_size:]
|
||||
|
||||
elif path.endswith(".DOS_MBR"):
|
||||
with open(path, "rb") as f:
|
||||
@@ -100,8 +107,7 @@ class QlLoaderDOS(QlLoader):
|
||||
|
||||
self.stack_address = (ss << 4) + sp
|
||||
self.start_address = (cs << 4) + ip
|
||||
self.stack_size = int(profile.get("COM", "stack_size"), 0)
|
||||
self.ticks_per_second = profile.getfloat("KERNEL", "ticks_per_second")
|
||||
self.stack_size = profile.getint("COM", "stack_size")
|
||||
|
||||
# map the entire system memory
|
||||
self.ql.mem.map(0, 0x100000, info="[FULL]")
|
||||
@@ -110,4 +116,4 @@ class QlLoaderDOS(QlLoader):
|
||||
self.load_address = base_address
|
||||
self.ql.os.entry_point = self.start_address
|
||||
|
||||
sys.excepthook = self.excepthook
|
||||
sys.excepthook = self.excepthook
|
||||
|
||||
+17
-6
@@ -7,7 +7,7 @@ import io
|
||||
import os
|
||||
|
||||
from enum import IntEnum
|
||||
from typing import Optional, Sequence, Mapping, Tuple
|
||||
from typing import AnyStr, Optional, Sequence, Mapping, Tuple
|
||||
|
||||
from elftools.common.utils import preserve_stream_pos
|
||||
from elftools.elf.constants import P_FLAGS, SH_FLAGS
|
||||
@@ -143,7 +143,7 @@ class QlLoaderELF(QlLoader):
|
||||
|
||||
return prot
|
||||
|
||||
def load_with_ld(self, elffile: ELFFile, stack_addr: int, load_address: int, argv: Sequence[str] = [], env: Mapping[str, str] = {}):
|
||||
def load_with_ld(self, elffile: ELFFile, stack_addr: int, load_address: int, argv: Sequence[str] = [], env: Mapping[AnyStr, AnyStr] = {}):
|
||||
|
||||
def load_elf_segments(elffile: ELFFile, load_address: int, info: str):
|
||||
# get list of loadable segments; these segments will be loaded to memory
|
||||
@@ -272,17 +272,23 @@ class QlLoaderELF(QlLoader):
|
||||
elf_table = bytearray()
|
||||
new_stack = stack_addr
|
||||
|
||||
def __push_str(top: int, s: str) -> int:
|
||||
"""Write a string to stack memory and adjust the top of stack accordingly.
|
||||
def __push_bytes(top: int, b: bytes) -> int:
|
||||
"""Write bytes to stack memory and adjust the top of stack accordingly.
|
||||
Top of stack remains aligned to pointer size
|
||||
"""
|
||||
|
||||
data = s.encode('latin') + b'\x00'
|
||||
data = b + b'\x00'
|
||||
top = self.ql.mem.align(top - len(data), self.ql.arch.pointersize)
|
||||
self.ql.mem.write(top, data)
|
||||
|
||||
return top
|
||||
|
||||
def __push_str(top: int, s: str) -> int:
|
||||
"""A convinient method for writing a string to stack memory.
|
||||
"""
|
||||
|
||||
return __push_bytes(top, s.encode('latin'))
|
||||
|
||||
# write argc
|
||||
elf_table.extend(self.ql.pack(len(argv)))
|
||||
|
||||
@@ -296,7 +302,12 @@ class QlLoaderELF(QlLoader):
|
||||
|
||||
# write env
|
||||
for k, v in env.items():
|
||||
new_stack = __push_str(new_stack, f'{k}={v}')
|
||||
_k = k if isinstance(k, bytes) else k.encode('latin')
|
||||
_v = v if isinstance(v, bytes) else v.encode('latin')
|
||||
|
||||
pair = b'='.join((_k, _v))
|
||||
|
||||
new_stack = __push_bytes(new_stack, pair)
|
||||
elf_table.extend(self.ql.pack(new_stack))
|
||||
|
||||
# add a nullptr sentinel
|
||||
|
||||
+6
-6
@@ -198,13 +198,13 @@ def setup_logger(ql: Qiling, log_file: Optional[str], console: bool, log_overrid
|
||||
handler.setFormatter(formatter)
|
||||
log.addHandler(handler)
|
||||
|
||||
log.setLevel(logging.INFO)
|
||||
# optimize logging speed by avoiding the collection of unnecesary logging properties
|
||||
logging._srcfile = None
|
||||
logging.logThreads = False
|
||||
logging.logProcesses = False
|
||||
logging.logMultiprocessing = False
|
||||
|
||||
# optimize logging speed by avoiding the collection of unnecesary logging properties
|
||||
logging._srcfile = None
|
||||
logging.logThreads = False
|
||||
logging.logProcesses = False
|
||||
logging.logMultiprocessing = False
|
||||
log.setLevel(logging.INFO)
|
||||
|
||||
return log
|
||||
|
||||
|
||||
+21
-19
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
#
|
||||
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
|
||||
#
|
||||
|
||||
@@ -15,26 +15,27 @@ from qiling.os.os import QlOs
|
||||
|
||||
from .interrupts import handlers
|
||||
|
||||
|
||||
# @see: https://en.wikipedia.org/wiki/FLAGS_register
|
||||
class Flags(IntEnum):
|
||||
CF = (1 << 0) # carry
|
||||
PF = (1 << 2) # parity
|
||||
AF = (1 << 4) # alignment
|
||||
ZF = (1 << 6) # zero
|
||||
SF = (1 << 7) # sign
|
||||
TF = (1 << 8) # trap
|
||||
IF = (1 << 9) # interrupt
|
||||
DF = (1 << 10) # direction
|
||||
OF = (1 << 11) # overflow
|
||||
IOPL = (3 << 12) # io privilege
|
||||
CF = (1 << 0) # carry
|
||||
PF = (1 << 2) # parity
|
||||
AF = (1 << 4) # alignment
|
||||
ZF = (1 << 6) # zero
|
||||
SF = (1 << 7) # sign
|
||||
TF = (1 << 8) # trap
|
||||
IF = (1 << 9) # interrupt
|
||||
DF = (1 << 10) # direction
|
||||
OF = (1 << 11) # overflow
|
||||
IOPL = (3 << 12) # io privilege
|
||||
|
||||
|
||||
class QlOsDos(QlOs):
|
||||
type = QL_OS.DOS
|
||||
|
||||
def __init__(self, ql: Qiling):
|
||||
super(QlOsDos, self).__init__(ql)
|
||||
super().__init__(ql)
|
||||
|
||||
self.ql = ql
|
||||
self.hook_syscall()
|
||||
|
||||
# used by int 21h
|
||||
@@ -46,7 +47,11 @@ class QlOsDos(QlOs):
|
||||
self.revese_color_pairs = {}
|
||||
|
||||
self.stdscr = None
|
||||
self.dos_ver = int(self.ql.profile.get("KERNEL", "version"), 0)
|
||||
|
||||
kconf = self.ql.profile["KERNEL"]
|
||||
|
||||
self.dos_ver = kconf.getint("version")
|
||||
self.ticks_per_second = kconf.getfloat("ticks_per_second")
|
||||
|
||||
def __del__(self):
|
||||
# resume terminal
|
||||
@@ -83,7 +88,7 @@ class QlOsDos(QlOs):
|
||||
|
||||
func = self.user_defined_api[QL_INTERCEPT.CALL].get(intinfo) or handlers.get(intno)
|
||||
onenter = self.user_defined_api[QL_INTERCEPT.ENTER].get(intinfo)
|
||||
onexit = self.user_defined_api[QL_INTERCEPT.EXIT].get(intinfo)
|
||||
onexit = self.user_defined_api[QL_INTERCEPT.EXIT].get(intinfo)
|
||||
|
||||
if onenter is not None:
|
||||
onenter(ql)
|
||||
@@ -103,14 +108,11 @@ class QlOsDos(QlOs):
|
||||
if self.ql.exit_point is not None:
|
||||
self.exit_point = self.ql.exit_point
|
||||
|
||||
if self.ql.entry_point is not None:
|
||||
self.ql.loader.elf_entry = self.ql.entry_point
|
||||
else:
|
||||
if self.ql.entry_point is None:
|
||||
self.ql.entry_point = self.ql.loader.start_address
|
||||
|
||||
if not self.ql.code:
|
||||
self.start_time = datetime.now()
|
||||
self.ticks_per_second = self.ql.loader.ticks_per_second
|
||||
|
||||
try:
|
||||
self.ql.emu_start(self.ql.entry_point, self.exit_point, self.ql.timeout, self.ql.count)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
#
|
||||
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
|
||||
#
|
||||
|
||||
@@ -10,40 +10,44 @@ from qiling import Qiling
|
||||
|
||||
from .. import utils
|
||||
|
||||
|
||||
class DiskError(IntEnum):
|
||||
NoError = 0
|
||||
BadCommand = 1
|
||||
AddressNotFound = 2
|
||||
DiskWriteProtectError = 3
|
||||
SectorNotFound = 4
|
||||
FixedDiskResetFailed = 5
|
||||
DiskChangedOrRemoved = 6
|
||||
BadFixedDiskParameterTable = 7
|
||||
DMAOverrun = 8
|
||||
DMAAcessAcrossBoundary = 9
|
||||
BadFixedDiskSectorFlag = 10
|
||||
BadFixedDiskCylinder = 11
|
||||
UnsupportedTrack = 12
|
||||
InvalidNumberofSectors = 13
|
||||
FixedDiskControlledDataAdressDetected = 14
|
||||
FixedDiskDMAArbitrationLevelOutofRange = 15
|
||||
ECCErrorOnRead = 16
|
||||
RecoverableFixedDiskDataError = 17
|
||||
ControllerError = 32
|
||||
SeekFailure = 64
|
||||
Timeout = 128
|
||||
FixedDiskDriveNotReady = 170
|
||||
FixedDiskUndefinedError = 187
|
||||
FixedDiskWriteFault = 204
|
||||
FixedDiskStatusError = 224
|
||||
SenseOperationFailed = 255
|
||||
NoError = 0
|
||||
BadCommand = 1
|
||||
AddressNotFound = 2
|
||||
DiskWriteProtectError = 3
|
||||
SectorNotFound = 4
|
||||
FixedDiskResetFailed = 5
|
||||
DiskChangedOrRemoved = 6
|
||||
BadFixedDiskParameterTable = 7
|
||||
DMAOverrun = 8
|
||||
DMAAcessAcrossBoundary = 9
|
||||
BadFixedDiskSectorFlag = 10
|
||||
BadFixedDiskCylinder = 11
|
||||
UnsupportedTrack = 12
|
||||
InvalidNumberofSectors = 13
|
||||
FixedDiskControlledDataAdressDetected = 14
|
||||
FixedDiskDMAArbitrationLevelOutofRange = 15
|
||||
ECCErrorOnRead = 16
|
||||
RecoverableFixedDiskDataError = 17
|
||||
ControllerError = 32
|
||||
SeekFailure = 64
|
||||
Timeout = 128
|
||||
FixedDiskDriveNotReady = 170
|
||||
FixedDiskUndefinedError = 187
|
||||
FixedDiskWriteFault = 204
|
||||
FixedDiskStatusError = 224
|
||||
SenseOperationFailed = 255
|
||||
|
||||
|
||||
def parse_dap(dapbs):
|
||||
return struct.unpack("<BBHHHQ", dapbs)
|
||||
|
||||
|
||||
def __leaf_00(ql: Qiling):
|
||||
ql.os.clear_cf()
|
||||
|
||||
|
||||
def __leaf_02(ql: Qiling):
|
||||
idx = ql.arch.regs.dl
|
||||
|
||||
@@ -142,17 +146,18 @@ def __leaf_43(ql: Qiling):
|
||||
ql.os.clear_cf()
|
||||
ql.arch.regs.ah = 0
|
||||
|
||||
|
||||
# @see: https://en.wikipedia.org/wiki/INT_13H
|
||||
def handler(ql: Qiling):
|
||||
ah = ql.arch.regs.ah
|
||||
|
||||
leaffunc = {
|
||||
0x00 : __leaf_00,
|
||||
0x02 : __leaf_02,
|
||||
0x08 : __leaf_08,
|
||||
0x41 : __leaf_41,
|
||||
0x42 : __leaf_42,
|
||||
0x43 : __leaf_43
|
||||
0x00: __leaf_00,
|
||||
0x02: __leaf_02,
|
||||
0x08: __leaf_08,
|
||||
0x41: __leaf_41,
|
||||
0x42: __leaf_42,
|
||||
0x43: __leaf_43
|
||||
}.get(ah)
|
||||
|
||||
if leaffunc is None:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
#
|
||||
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
|
||||
#
|
||||
|
||||
@@ -9,6 +9,7 @@ from qiling import Qiling
|
||||
|
||||
from .. import utils
|
||||
|
||||
|
||||
def __set_elapsed_ticks(ql: Qiling):
|
||||
now = datetime.now()
|
||||
ticks = int((now - ql.os.start_time).total_seconds() * ql.os.ticks_per_second)
|
||||
@@ -16,14 +17,17 @@ def __set_elapsed_ticks(ql: Qiling):
|
||||
ql.arch.regs.cx = (ticks >> 16) & 0xffff
|
||||
ql.arch.regs.dx = (ticks >> 0) & 0xffff
|
||||
|
||||
|
||||
def __leaf_00(ql: Qiling):
|
||||
__set_elapsed_ticks(ql)
|
||||
|
||||
ql.arch.regs.al = 0
|
||||
|
||||
|
||||
def __leaf_01(ql: Qiling):
|
||||
__set_elapsed_ticks(ql)
|
||||
|
||||
|
||||
def __leaf_02_03(ql: Qiling):
|
||||
now = datetime.now()
|
||||
|
||||
@@ -34,6 +38,7 @@ def __leaf_02_03(ql: Qiling):
|
||||
|
||||
ql.os.clear_cf()
|
||||
|
||||
|
||||
def __leaf_04_05(ql: Qiling):
|
||||
now = datetime.now()
|
||||
|
||||
@@ -45,37 +50,42 @@ def __leaf_04_05(ql: Qiling):
|
||||
|
||||
ql.os.clear_cf()
|
||||
|
||||
|
||||
def __leaf_06_07_09(ql: Qiling):
|
||||
# TODO: Implement clock interrupt.
|
||||
ql.os.set_cf()
|
||||
|
||||
|
||||
def __leaf_08(ql: Qiling):
|
||||
pass
|
||||
|
||||
|
||||
def __leaf_0a(ql: Qiling):
|
||||
now = datetime.now()
|
||||
|
||||
ql.arch.regs.cx = (now - datetime(1980, 1, 1)).days
|
||||
|
||||
|
||||
def __leaf_0b(ql: Qiling):
|
||||
pass
|
||||
|
||||
|
||||
def handler(ql: Qiling):
|
||||
ah = ql.arch.regs.ah
|
||||
|
||||
leaffunc = {
|
||||
0x00 : __leaf_00,
|
||||
0x01 : __leaf_01,
|
||||
0x02 : __leaf_02_03,
|
||||
0x03 : __leaf_02_03,
|
||||
0x04 : __leaf_04_05,
|
||||
0x05 : __leaf_04_05,
|
||||
0x06 : __leaf_06_07_09,
|
||||
0x07 : __leaf_06_07_09,
|
||||
0x08 : __leaf_08,
|
||||
0x09 : __leaf_06_07_09,
|
||||
0x0a : __leaf_0a,
|
||||
0x0b : __leaf_0b
|
||||
0x00: __leaf_00,
|
||||
0x01: __leaf_01,
|
||||
0x02: __leaf_02_03,
|
||||
0x03: __leaf_02_03,
|
||||
0x04: __leaf_04_05,
|
||||
0x05: __leaf_04_05,
|
||||
0x06: __leaf_06_07_09,
|
||||
0x07: __leaf_06_07_09,
|
||||
0x08: __leaf_08,
|
||||
0x09: __leaf_06_07_09,
|
||||
0x0a: __leaf_0a,
|
||||
0x0b: __leaf_0b
|
||||
}.get(ah)
|
||||
|
||||
if leaffunc is None:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
#
|
||||
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
|
||||
#
|
||||
|
||||
@@ -135,23 +135,23 @@ def handler(ql: Qiling):
|
||||
ah = ql.arch.regs.ah
|
||||
|
||||
leaffunc = {
|
||||
0x02 : __leaf_02,
|
||||
0x06 : __leaf_02,
|
||||
0x09 : __leaf_09,
|
||||
0x0c : __leaf_0c,
|
||||
0x25 : __leaf_25,
|
||||
0x26 : __leaf_26,
|
||||
0x30 : __leaf_30,
|
||||
0x33 : __leaf_33,
|
||||
0x35 : __leaf_35,
|
||||
0x3c : __leaf_3c,
|
||||
0x3d : __leaf_3d,
|
||||
0x3e : __leaf_3e,
|
||||
0x3f : __leaf_3f,
|
||||
0x40 : __leaf_40,
|
||||
0x41 : __leaf_41,
|
||||
0x43 : __leaf_43,
|
||||
0x4c : __leaf_4c
|
||||
0x02: __leaf_02,
|
||||
0x06: __leaf_02,
|
||||
0x09: __leaf_09,
|
||||
0x0c: __leaf_0c,
|
||||
0x25: __leaf_25,
|
||||
0x26: __leaf_26,
|
||||
0x30: __leaf_30,
|
||||
0x33: __leaf_33,
|
||||
0x35: __leaf_35,
|
||||
0x3c: __leaf_3c,
|
||||
0x3d: __leaf_3d,
|
||||
0x3e: __leaf_3e,
|
||||
0x3f: __leaf_3f,
|
||||
0x40: __leaf_40,
|
||||
0x41: __leaf_41,
|
||||
0x43: __leaf_43,
|
||||
0x4c: __leaf_4c
|
||||
}.get(ah)
|
||||
|
||||
if leaffunc is None:
|
||||
|
||||
@@ -18,14 +18,20 @@ from qiling.os.macos.events.macos import QlMacOSEvManager
|
||||
from qiling.os.macos.events.macos_policy import QlMacOSPolicy
|
||||
from qiling.os.macos.events.macos_structs import mac_policy_list_t
|
||||
from qiling.os.macos.structs import kmod_info_t, POINTER64
|
||||
from qiling.os.posix.syscall.abi import arm
|
||||
|
||||
|
||||
class QlOsMacos(QlOsPosix):
|
||||
type = QL_OS.MACOS
|
||||
|
||||
def __init__(self, ql: Qiling):
|
||||
super(QlOsMacos, self).__init__(ql)
|
||||
super().__init__(ql)
|
||||
|
||||
# NOTE: it appears that MacOS only supports Intel arch, but we put this
|
||||
# here anyway for completion
|
||||
if ql.arch.type is QL_ARCH.ARM64:
|
||||
self.syscall_abi = arm.QlAArch64MacOS(ql.arch)
|
||||
|
||||
self.ql = ql
|
||||
self.fcall = QlFunctionCall(ql, intel.macosx64(ql.arch))
|
||||
|
||||
self.ql.counter = 0
|
||||
|
||||
+14
-16
@@ -26,7 +26,7 @@ class QlMemoryManager:
|
||||
https://github.com/zeropointdynamics/zelos/blob/master/src/zelos/memory.py
|
||||
"""
|
||||
|
||||
def __init__(self, ql: Qiling):
|
||||
def __init__(self, ql: Qiling, pagesize: int = 0x1000):
|
||||
self.ql = ql
|
||||
self.map_info: List[MapInfoEntry] = []
|
||||
self.mmio_cbs = {}
|
||||
@@ -40,13 +40,10 @@ class QlMemoryManager:
|
||||
if ql.arch.bits not in bit_stuff:
|
||||
raise QlErrorStructConversion("Unsupported Qiling architecture for memory manager")
|
||||
|
||||
max_addr = bit_stuff[ql.arch.bits]
|
||||
|
||||
self.max_addr = max_addr
|
||||
self.max_mem_addr = max_addr
|
||||
self.max_mem_addr = bit_stuff[ql.arch.bits]
|
||||
|
||||
# memory page size
|
||||
self.pagesize = 0x1000
|
||||
self.pagesize = pagesize
|
||||
|
||||
# make sure pagesize is a power of 2
|
||||
assert self.pagesize & (self.pagesize - 1) == 0, 'pagesize has to be a power of 2'
|
||||
@@ -134,7 +131,7 @@ class QlMemoryManager:
|
||||
|
||||
def change_mapinfo(self, mem_s: int, mem_e: int, mem_p: Optional[int] = None, mem_info: Optional[str] = None):
|
||||
tmp_map_info: Optional[MapInfoEntry] = None
|
||||
info_idx: int = None
|
||||
info_idx: int = -1
|
||||
|
||||
for idx, map_info in enumerate(self.map_info):
|
||||
if mem_s >= map_info[0] and mem_e <= map_info[1]:
|
||||
@@ -377,7 +374,7 @@ class QlMemoryManager:
|
||||
|
||||
self.write(addr, __pack(value))
|
||||
|
||||
def search(self, needle: Union[bytes, Pattern[bytes]], begin: Optional[int] = None, end: Optional[int] = None) -> Sequence[int]:
|
||||
def search(self, needle: Union[bytes, Pattern[bytes]], begin: Optional[int] = None, end: Optional[int] = None) -> List[int]:
|
||||
"""Search for a sequence of bytes in memory.
|
||||
|
||||
Args:
|
||||
@@ -398,7 +395,7 @@ class QlMemoryManager:
|
||||
|
||||
assert begin < end, 'search arguments do not make sense'
|
||||
|
||||
# narrow the search down to relevant ranges; mmio ranges are excluded due to potential read size effects
|
||||
# narrow the search down to relevant ranges; mmio ranges are excluded due to potential read side effects
|
||||
ranges = [(max(begin, lbound), min(ubound, end)) for lbound, ubound, _, _, is_mmio in self.map_info if not (end < lbound or ubound < begin or is_mmio)]
|
||||
results = []
|
||||
|
||||
@@ -477,7 +474,6 @@ class QlMemoryManager:
|
||||
|
||||
yield (p_lbound, p_ubound)
|
||||
|
||||
|
||||
def is_available(self, addr: int, size: int) -> bool:
|
||||
"""Query whether the memory range starting at `addr` and is of length of `size` bytes
|
||||
is available for allocation.
|
||||
@@ -527,7 +523,7 @@ class QlMemoryManager:
|
||||
|
||||
# memory space bounds (exclusive)
|
||||
mem_lbound = 0
|
||||
mem_ubound = self.max_addr + 1
|
||||
mem_ubound = self.max_mem_addr + 1
|
||||
|
||||
if minaddr is None:
|
||||
minaddr = mem_lbound
|
||||
@@ -587,8 +583,7 @@ class QlMemoryManager:
|
||||
aligned_size = self.align_up((addr & (self.pagesize - 1)) + size)
|
||||
|
||||
self.ql.uc.mem_protect(aligned_address, aligned_size, perms)
|
||||
self.change_mapinfo(aligned_address, aligned_address + aligned_size, mem_p = perms)
|
||||
|
||||
self.change_mapinfo(aligned_address, aligned_address + aligned_size, perms)
|
||||
|
||||
def map(self, addr: int, size: int, perms: int = UC_PROT_ALL, info: Optional[str] = None):
|
||||
"""Map a new memory range.
|
||||
@@ -640,14 +635,18 @@ class QlMemoryManager:
|
||||
|
||||
self.mmio_cbs[(addr, addr + size)] = (read_cb, write_cb)
|
||||
|
||||
# A Simple Heap Implementation
|
||||
|
||||
class Chunk:
|
||||
def __init__(self, address: int, size: int):
|
||||
self.inuse = True
|
||||
self.address = address
|
||||
self.size = size
|
||||
|
||||
|
||||
class QlMemoryHeap:
|
||||
"""A Simple Heap Implementation.
|
||||
"""
|
||||
|
||||
def __init__(self, ql: Qiling, start_address: int, end_address: int):
|
||||
self.ql = ql
|
||||
self.chunks: List[Chunk] = []
|
||||
@@ -758,8 +757,7 @@ class QlMemoryHeap:
|
||||
|
||||
# clear all memory regions alloc
|
||||
def clear(self):
|
||||
for chunk in self.chunks:
|
||||
chunk.inuse = False
|
||||
self.chunks.clear()
|
||||
|
||||
for addr, size in self.mem_alloc:
|
||||
self.ql.mem.unmap(addr, size)
|
||||
|
||||
@@ -37,15 +37,19 @@ def _flags_mapping(value: int, flags_map: Mapping[str, int]) -> str:
|
||||
|
||||
|
||||
def ql_open_flag_mapping(ql: Qiling, flags: int) -> int:
|
||||
def flag_mapping(flags, mapping_name, mapping_from, mapping_to, host_os, virt_os):
|
||||
def flag_mapping(flags, mapping_name, mapping_from, mapping_to, host_os):
|
||||
ret = 0
|
||||
|
||||
for n in mapping_name:
|
||||
if mapping_from[n] is None or mapping_to[n] is None:
|
||||
continue
|
||||
|
||||
if (flags & mapping_from[n]) == mapping_from[n]:
|
||||
ret = ret | mapping_to[n]
|
||||
if (host_os == QL_OS.WINDOWS and virt_os != QL_OS.WINDOWS):
|
||||
ret = ret | mapping_to['O_BINARY']
|
||||
|
||||
if host_os is QL_OS.WINDOWS:
|
||||
ret = ret | mapping_to['O_BINARY']
|
||||
|
||||
return ret
|
||||
|
||||
f = {}
|
||||
@@ -89,7 +93,7 @@ def ql_open_flag_mapping(ql: Qiling, flags: int) -> int:
|
||||
if f == t:
|
||||
return flags
|
||||
|
||||
return flag_mapping(flags, open_flags_name, f, t, host_os, virt_os)
|
||||
return flag_mapping(flags, open_flags_name, f, t, host_os)
|
||||
|
||||
|
||||
def mmap_flag_mapping(flags):
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
|
||||
#
|
||||
|
||||
from typing import Dict, Tuple, Optional
|
||||
from collections import deque
|
||||
|
||||
from qiling.os.posix.const import MSGMNB
|
||||
|
||||
|
||||
class QlMsgBuf:
|
||||
def __init__(self, mtype: int, mtext: bytes) -> None:
|
||||
self.mtype = mtype
|
||||
self.mtext = mtext
|
||||
|
||||
|
||||
# vaguely reflects a msqid64_ds structure
|
||||
class QlMsqId:
|
||||
def __init__(self, key: int, uid: int, gid: int, mode: int) -> None:
|
||||
# ipc64_perm
|
||||
self.key = key
|
||||
self.uid = uid
|
||||
self.gid = gid
|
||||
self.mode = mode
|
||||
|
||||
self.queue = deque(maxlen=MSGMNB)
|
||||
|
||||
def __len__(self):
|
||||
return len(self.queue)
|
||||
|
||||
|
||||
class QlMsq:
|
||||
def __init__(self) -> None:
|
||||
self.__msq: Dict[int, QlMsqId] = {}
|
||||
self.__id: int = 0x0F000000
|
||||
|
||||
def __len__(self) -> int:
|
||||
return len(self.__msq)
|
||||
|
||||
def add(self, msq: QlMsqId) -> int:
|
||||
msqid = self.__id
|
||||
self.__msq[msqid] = msq
|
||||
|
||||
self.__id += 0x1000
|
||||
|
||||
return msqid
|
||||
|
||||
def remove(self, msqid: int) -> None:
|
||||
del self.__msq[msqid]
|
||||
|
||||
def get_by_key(self, key: int) -> Tuple[int, Optional[QlMsqId]]:
|
||||
return next(((msqid, msqobj) for msqid, msqobj in self.__msq.items() if msqobj.key == key), (-1, None))
|
||||
|
||||
def get_by_id(self, msqid: int) -> Optional[QlMsqId]:
|
||||
return self.__msq.get(msqid, None)
|
||||
+18
-205
@@ -3,68 +3,21 @@
|
||||
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
|
||||
#
|
||||
|
||||
from collections import deque
|
||||
from inspect import signature, Parameter
|
||||
from typing import Dict, TextIO, Tuple, Union, Callable, IO, List, Optional
|
||||
|
||||
from unicorn.arm64_const import UC_ARM64_REG_X8, UC_ARM64_REG_X16
|
||||
from unicorn.arm_const import (
|
||||
UC_ARM_REG_R0, UC_ARM_REG_R1, UC_ARM_REG_R2, UC_ARM_REG_R3,
|
||||
UC_ARM_REG_R4, UC_ARM_REG_R5, UC_ARM_REG_R7, UC_ARM_REG_R12
|
||||
)
|
||||
from unicorn.mips_const import UC_MIPS_REG_V0
|
||||
from unicorn.x86_const import (
|
||||
UC_X86_REG_EAX, UC_X86_REG_EBX, UC_X86_REG_ECX, UC_X86_REG_EDX,
|
||||
UC_X86_REG_ESI, UC_X86_REG_EDI, UC_X86_REG_EBP, UC_X86_REG_RDI,
|
||||
UC_X86_REG_RSI, UC_X86_REG_RDX, UC_X86_REG_R10, UC_X86_REG_R8,
|
||||
UC_X86_REG_R9, UC_X86_REG_RAX
|
||||
)
|
||||
from unicorn.riscv_const import UC_RISCV_REG_A7
|
||||
from unicorn.ppc_const import UC_PPC_REG_0
|
||||
from typing import Dict, TextIO, Union, Callable, IO, List, Optional
|
||||
|
||||
from qiling import Qiling
|
||||
from qiling.cc import QlCC, intel, arm, mips, riscv, ppc
|
||||
from qiling.const import QL_ARCH, QL_OS, QL_INTERCEPT
|
||||
from qiling.const import QL_ARCH, QL_INTERCEPT
|
||||
from qiling.exception import QlErrorSyscallNotFound
|
||||
from qiling.os.os import QlOs
|
||||
from qiling.os.posix.const import MSGMNB, NR_OPEN, errors
|
||||
from qiling.os.posix.const import NR_OPEN, errors
|
||||
from qiling.os.posix.msq import QlMsq
|
||||
from qiling.os.posix.shm import QlShm
|
||||
from qiling.os.posix.syscall.abi import QlSyscallABI, arm, intel, mips, ppc, riscv
|
||||
from qiling.utils import ql_get_module, ql_get_module_function
|
||||
|
||||
SYSCALL_PREF: str = f'ql_syscall_'
|
||||
|
||||
class intel32(intel.QlIntel32):
|
||||
_argregs = (UC_X86_REG_EBX, UC_X86_REG_ECX, UC_X86_REG_EDX, UC_X86_REG_ESI, UC_X86_REG_EDI, UC_X86_REG_EBP)
|
||||
|
||||
class intel64(intel.QlIntel64):
|
||||
_argregs = (UC_X86_REG_RDI, UC_X86_REG_RSI, UC_X86_REG_RDX, UC_X86_REG_R10, UC_X86_REG_R8, UC_X86_REG_R9)
|
||||
|
||||
class aarch32(arm.aarch32):
|
||||
_argregs = (UC_ARM_REG_R0, UC_ARM_REG_R1, UC_ARM_REG_R2, UC_ARM_REG_R3, UC_ARM_REG_R4, UC_ARM_REG_R5)
|
||||
|
||||
class aarch64(arm.aarch64):
|
||||
pass
|
||||
|
||||
class mipso32(mips.mipso32):
|
||||
# TODO: should it be part of the standard mipso32 cc?
|
||||
def setReturnValue(self, value: int):
|
||||
if -1134 < value < 0:
|
||||
a3return = 1
|
||||
value = -value
|
||||
else:
|
||||
a3return = 0
|
||||
|
||||
self.arch.regs.v0 = value
|
||||
self.arch.regs.a3 = a3return
|
||||
|
||||
class riscv32(riscv.riscv):
|
||||
pass
|
||||
|
||||
class riscv64(riscv.riscv):
|
||||
pass
|
||||
|
||||
class ppc(ppc.ppc):
|
||||
pass
|
||||
|
||||
|
||||
class QlFileDes:
|
||||
def __init__(self):
|
||||
@@ -92,104 +45,10 @@ class QlFileDes:
|
||||
self.__fds = fds
|
||||
|
||||
|
||||
# vaguely reflects a shmid64_ds structure
|
||||
class QlShmId:
|
||||
|
||||
def __init__(self, key: int, uid: int, gid: int, mode: int, segsz: int) -> None:
|
||||
# ipc64_perm
|
||||
self.key = key
|
||||
self.uid = uid
|
||||
self.gid = gid
|
||||
self.mode = mode
|
||||
|
||||
self.segsz = segsz
|
||||
|
||||
# track the memory locations this segment is currently attached to
|
||||
self.attach: List[int] = []
|
||||
|
||||
|
||||
class QlShm:
|
||||
def __init__(self) -> None:
|
||||
self.__shm: Dict[int, QlShmId] = {}
|
||||
self.__id: int = 0x0F000000
|
||||
|
||||
def __len__(self) -> int:
|
||||
return len(self.__shm)
|
||||
|
||||
def add(self, shm: QlShmId) -> int:
|
||||
shmid = self.__id
|
||||
self.__shm[shmid] = shm
|
||||
|
||||
self.__id += 0x1000
|
||||
|
||||
return shmid
|
||||
|
||||
def remove(self, shmid: int) -> None:
|
||||
del self.__shm[shmid]
|
||||
|
||||
def get_by_key(self, key: int) -> Tuple[int, Optional[QlShmId]]:
|
||||
return next(((shmid, shmobj) for shmid, shmobj in self.__shm.items() if shmobj.key == key), (-1, None))
|
||||
|
||||
def get_by_id(self, shmid: int) -> Optional[QlShmId]:
|
||||
return self.__shm.get(shmid, None)
|
||||
|
||||
def get_by_attaddr(self, shmaddr: int) -> Optional[QlShmId]:
|
||||
return next((shmobj for shmobj in self.__shm.values() if shmobj.attach.count(shmaddr) > 0), None)
|
||||
|
||||
|
||||
class QlMsgBuf:
|
||||
def __init__(self, mtype: int, mtext: bytes) -> None:
|
||||
self.mtype = mtype
|
||||
self.mtext = mtext
|
||||
|
||||
|
||||
# vaguely reflects a msqid64_ds structure
|
||||
class QlMsqId:
|
||||
def __init__(self, key: int, uid: int, gid: int, mode: int) -> None:
|
||||
# ipc64_perm
|
||||
self.key = key
|
||||
self.uid = uid
|
||||
self.gid = gid
|
||||
self.mode = mode
|
||||
|
||||
self.queue = deque(maxlen=MSGMNB)
|
||||
|
||||
def __len__(self):
|
||||
return len(self.queue)
|
||||
|
||||
|
||||
class QlMsq:
|
||||
def __init__(self) -> None:
|
||||
self.__msq: Dict[int, QlMsqId] = {}
|
||||
self.__id: int = 0x0F000000
|
||||
|
||||
def __len__(self) -> int:
|
||||
return len(self.__msq)
|
||||
|
||||
def add(self, msq: QlMsqId) -> int:
|
||||
msqid = self.__id
|
||||
self.__msq[msqid] = msq
|
||||
|
||||
self.__id += 0x1000
|
||||
|
||||
return msqid
|
||||
|
||||
def remove(self, msqid: int) -> None:
|
||||
del self.__msq[msqid]
|
||||
|
||||
def get_by_key(self, key: int) -> Tuple[int, Optional[QlMsqId]]:
|
||||
return next(((msqid, msqobj) for msqid, msqobj in self.__msq.items() if msqobj.key == key), (-1, None))
|
||||
|
||||
def get_by_id(self, msqid: int) -> Optional[QlMsqId]:
|
||||
return self.__msq.get(msqid, None)
|
||||
|
||||
|
||||
class QlOsPosix(QlOs):
|
||||
|
||||
def __init__(self, ql: Qiling):
|
||||
super().__init__(ql)
|
||||
|
||||
self.ql = ql
|
||||
self.sigaction_act = [0] * 256
|
||||
|
||||
conf = self.profile['KERNEL']
|
||||
@@ -208,34 +67,15 @@ class QlOsPosix(QlOs):
|
||||
QL_INTERCEPT.EXIT: {}
|
||||
}
|
||||
|
||||
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.PPC : UC_PPC_REG_0
|
||||
}[self.ql.arch.type]
|
||||
|
||||
# handle some special cases
|
||||
if (self.ql.arch.type == QL_ARCH.ARM64) and (self.type == QL_OS.MACOS):
|
||||
self.__syscall_id_reg = UC_ARM64_REG_X16
|
||||
|
||||
elif (self.ql.arch.type == QL_ARCH.ARM) and (self.type == 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.PPC : ppc
|
||||
self.syscall_abi: QlSyscallABI = {
|
||||
QL_ARCH.ARM64: arm.QlAArch64,
|
||||
QL_ARCH.ARM: arm.QlAArch32,
|
||||
QL_ARCH.MIPS: mips.QlMipsO32,
|
||||
QL_ARCH.X86: intel.QlIntel32,
|
||||
QL_ARCH.X8664: intel.QlIntel64,
|
||||
QL_ARCH.RISCV: riscv.QlRiscV32,
|
||||
QL_ARCH.RISCV64: riscv.QlRiscV64,
|
||||
QL_ARCH.PPC: ppc.QlPPC
|
||||
}[self.ql.arch.type](self.ql.arch)
|
||||
|
||||
# select syscall mapping function based on emulated OS and architecture
|
||||
@@ -308,7 +148,6 @@ class QlOsPosix(QlOs):
|
||||
else:
|
||||
self.function_hook.add_function_hook(target, handler, intercept)
|
||||
|
||||
|
||||
@staticmethod
|
||||
def getNameFromErrorCode(ret: int) -> str:
|
||||
"""Return the hex representation of a return value and if possible
|
||||
@@ -325,7 +164,7 @@ class QlOsPosix(QlOs):
|
||||
return f'{ret:#x}{f" ({errors[-ret]})" if -ret in errors else f""}'
|
||||
|
||||
def load_syscall(self):
|
||||
syscall_id = self.get_syscall()
|
||||
syscall_id = self.syscall_abi.get_id()
|
||||
syscall_name = self.syscall_mapper(syscall_id)
|
||||
|
||||
# get syscall on-enter hook (if any)
|
||||
@@ -360,7 +199,7 @@ class QlOsPosix(QlOs):
|
||||
param_names = [info.name for info in param_names[1:] if info.kind == Parameter.POSITIONAL_OR_KEYWORD]
|
||||
|
||||
# read parameter values
|
||||
params = [self.__syscall_cc.getRawParam(i) for i in range(len(param_names))]
|
||||
params = self.syscall_abi.get_params(len(param_names))
|
||||
|
||||
try:
|
||||
# if set, fire up the on-enter hook and let it override original args set
|
||||
@@ -382,7 +221,7 @@ class QlOsPosix(QlOs):
|
||||
|
||||
# set return value
|
||||
if retval is not None:
|
||||
self.__syscall_cc.setReturnValue(retval)
|
||||
self.syscall_abi.set_return_value(retval)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
@@ -414,32 +253,6 @@ class QlOsPosix(QlOs):
|
||||
if self.ql.debug_stop:
|
||||
raise QlErrorSyscallNotFound(f'Syscall not found: {syscall_name}')
|
||||
|
||||
def get_syscall(self) -> int:
|
||||
if self.ql.arch.type == QL_ARCH.ARM:
|
||||
# support arm-oabi
|
||||
# @see: https://marcin.juszkiewicz.com.pl/download/tables/syscalls.html
|
||||
# @see: https://github.com/rootkiter/Reverse-bins/blob/master/syscall_header/armv4l_unistd.h
|
||||
# @see: https://github.com/unicorn-engine/unicorn/issues/1137
|
||||
|
||||
# read the instruction we have just emulated
|
||||
isize = 2 if self.ql.arch.is_thumb else self.ql.arch.pointersize
|
||||
ibytes = self.ql.mem.read_ptr(self.ql.arch.regs.arch_pc - isize, isize)
|
||||
|
||||
# mask off the opcode, which is the most significant byte
|
||||
svc_imm = ibytes & ((1 << ((isize - 1) * 8)) - 1)
|
||||
|
||||
# arm-oabi
|
||||
if svc_imm >= 0x900000:
|
||||
return svc_imm - 0x900000
|
||||
|
||||
if svc_imm > 0:
|
||||
return svc_imm
|
||||
|
||||
return self.ql.arch.regs.read(self.__syscall_id_reg)
|
||||
|
||||
def set_syscall_return(self, retval: int):
|
||||
self.__syscall_cc.setReturnValue(retval)
|
||||
|
||||
@property
|
||||
def fd(self):
|
||||
return self._fd
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
|
||||
#
|
||||
|
||||
from typing import Dict, List, Tuple, Optional
|
||||
|
||||
|
||||
# vaguely reflects a shmid64_ds structure
|
||||
class QlShmId:
|
||||
|
||||
def __init__(self, key: int, uid: int, gid: int, mode: int, segsz: int) -> None:
|
||||
# ipc64_perm
|
||||
self.key = key
|
||||
self.uid = uid
|
||||
self.gid = gid
|
||||
self.mode = mode
|
||||
|
||||
self.segsz = segsz
|
||||
|
||||
# track the memory locations this segment is currently attached to
|
||||
self.attach: List[int] = []
|
||||
|
||||
|
||||
class QlShm:
|
||||
def __init__(self) -> None:
|
||||
self.__shm: Dict[int, QlShmId] = {}
|
||||
self.__id: int = 0x0F000000
|
||||
|
||||
def __len__(self) -> int:
|
||||
return len(self.__shm)
|
||||
|
||||
def add(self, shm: QlShmId) -> int:
|
||||
shmid = self.__id
|
||||
self.__shm[shmid] = shm
|
||||
|
||||
self.__id += 0x1000
|
||||
|
||||
return shmid
|
||||
|
||||
def remove(self, shmid: int) -> None:
|
||||
del self.__shm[shmid]
|
||||
|
||||
def get_by_key(self, key: int) -> Tuple[int, Optional[QlShmId]]:
|
||||
return next(((shmid, shmobj) for shmid, shmobj in self.__shm.items() if shmobj.key == key), (-1, None))
|
||||
|
||||
def get_by_id(self, shmid: int) -> Optional[QlShmId]:
|
||||
return self.__shm.get(shmid, None)
|
||||
|
||||
def get_by_attaddr(self, shmaddr: int) -> Optional[QlShmId]:
|
||||
return next((shmobj for shmobj in self.__shm.values() if shmobj.attach.count(shmaddr) > 0), None)
|
||||
@@ -0,0 +1,60 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, ClassVar, Sequence, Tuple
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from qiling.arch.arch import QlArch
|
||||
|
||||
|
||||
class QlSyscallABI:
|
||||
"""System call ABI common implementation.
|
||||
"""
|
||||
|
||||
_idreg: ClassVar[int]
|
||||
_argregs: ClassVar[Sequence[int]]
|
||||
_retreg: ClassVar[int]
|
||||
|
||||
def __init__(self, arch: QlArch) -> None:
|
||||
"""Initialize a system call ABI instance.
|
||||
|
||||
Args:
|
||||
arch: underlying architecture instance
|
||||
"""
|
||||
|
||||
self.arch = arch
|
||||
|
||||
def get_id(self) -> int:
|
||||
"""Read system call ID number.
|
||||
|
||||
Returns: system call number
|
||||
"""
|
||||
|
||||
return self.arch.regs.read(self._idreg) # type: ignore [uc funny annot]
|
||||
|
||||
def get_params(self, count: int) -> Tuple[int, ...]:
|
||||
"""Read system call arguments.
|
||||
|
||||
Args:
|
||||
count: number of arguments to read
|
||||
|
||||
Returns: a tuple containing system call arguments values
|
||||
"""
|
||||
|
||||
if count > len(self._argregs):
|
||||
raise ValueError(f'requested {count} arguments but only {len(self._argregs)} slots are defined')
|
||||
|
||||
return tuple(self.arch.regs.read(reg) for reg in self._argregs[:count]) # type: ignore [uc funny annot]
|
||||
|
||||
def set_return_value(self, value: int) -> None:
|
||||
"""Set the system call return value.
|
||||
|
||||
Args:
|
||||
value: a numeric value to set
|
||||
"""
|
||||
|
||||
self.arch.regs.write(self._retreg, value)
|
||||
@@ -0,0 +1,67 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
|
||||
|
||||
from unicorn.arm64_const import (
|
||||
UC_ARM64_REG_X0, UC_ARM64_REG_X1, UC_ARM64_REG_X2, UC_ARM64_REG_X3,
|
||||
UC_ARM64_REG_X4, UC_ARM64_REG_X5, UC_ARM64_REG_X8, UC_ARM64_REG_X16
|
||||
)
|
||||
|
||||
from unicorn.arm_const import (
|
||||
UC_ARM_REG_R0, UC_ARM_REG_R1, UC_ARM_REG_R2, UC_ARM_REG_R3,
|
||||
UC_ARM_REG_R4, UC_ARM_REG_R5, UC_ARM_REG_R7, UC_ARM_REG_R12
|
||||
)
|
||||
|
||||
from qiling.os.posix.syscall.abi import QlSyscallABI
|
||||
|
||||
|
||||
class QlAArch32(QlSyscallABI):
|
||||
"""System call ABI for ARM-based systems.
|
||||
"""
|
||||
|
||||
_idreg = UC_ARM_REG_R7
|
||||
_argregs = (UC_ARM_REG_R0, UC_ARM_REG_R1, UC_ARM_REG_R2, UC_ARM_REG_R3, UC_ARM_REG_R4, UC_ARM_REG_R5)
|
||||
_retreg = UC_ARM_REG_R0
|
||||
|
||||
def get_id(self) -> int:
|
||||
# the syscall number of a svc / swi instruction needs to be manually extracted.
|
||||
# here we read the instruction we have just emulated and extract the immediate
|
||||
# number by masking off the the opcode
|
||||
|
||||
isize = 2 if self.arch.is_thumb else self.arch.pointersize
|
||||
ibytes = self.arch.utils.ql.mem.read_ptr(self.arch.regs.arch_pc - isize, isize)
|
||||
|
||||
# mask off the opcode, which is the most significant byte
|
||||
svc_imm = ibytes & ((1 << ((isize - 1) * 8)) - 1)
|
||||
|
||||
# arm-oabi
|
||||
if svc_imm >= 0x900000:
|
||||
return svc_imm - 0x900000
|
||||
|
||||
if svc_imm > 0:
|
||||
return svc_imm
|
||||
|
||||
return super().get_id()
|
||||
|
||||
|
||||
class QlAArch32QNX(QlAArch32):
|
||||
"""QNX ABI override
|
||||
"""
|
||||
|
||||
_idreg = UC_ARM_REG_R12
|
||||
|
||||
|
||||
class QlAArch64(QlSyscallABI):
|
||||
"""System call ABI for ARM64-based systems.
|
||||
"""
|
||||
|
||||
_idreg = UC_ARM64_REG_X8
|
||||
_argregs = (UC_ARM64_REG_X0, UC_ARM64_REG_X1, UC_ARM64_REG_X2, UC_ARM64_REG_X3, UC_ARM64_REG_X4, UC_ARM64_REG_X5)
|
||||
_retreg = UC_ARM64_REG_X0
|
||||
|
||||
|
||||
class QlAArch64MacOS(QlAArch64):
|
||||
"""MacOS ABI override
|
||||
"""
|
||||
|
||||
_idreg = UC_ARM64_REG_X16
|
||||
@@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
|
||||
|
||||
from unicorn.x86_const import (
|
||||
UC_X86_REG_EAX, UC_X86_REG_EBX, UC_X86_REG_ECX, UC_X86_REG_EDX,
|
||||
UC_X86_REG_ESI, UC_X86_REG_EDI, UC_X86_REG_EBP, UC_X86_REG_RDI,
|
||||
UC_X86_REG_RSI, UC_X86_REG_RDX, UC_X86_REG_R10, UC_X86_REG_R8,
|
||||
UC_X86_REG_R9, UC_X86_REG_RAX
|
||||
)
|
||||
|
||||
from qiling.os.posix.syscall.abi import QlSyscallABI
|
||||
|
||||
|
||||
class QlIntel32(QlSyscallABI):
|
||||
"""System call ABI for Intel-based 32-bit systems.
|
||||
"""
|
||||
|
||||
_idreg = UC_X86_REG_EAX
|
||||
_argregs = (UC_X86_REG_EBX, UC_X86_REG_ECX, UC_X86_REG_EDX, UC_X86_REG_ESI, UC_X86_REG_EDI, UC_X86_REG_EBP)
|
||||
_retreg = UC_X86_REG_EAX
|
||||
|
||||
|
||||
class QlIntel64(QlSyscallABI):
|
||||
"""System call ABI for Intel-based 64-bit systems.
|
||||
"""
|
||||
|
||||
_idreg = UC_X86_REG_RAX
|
||||
_argregs = (UC_X86_REG_RDI, UC_X86_REG_RSI, UC_X86_REG_RDX, UC_X86_REG_R10, UC_X86_REG_R8, UC_X86_REG_R9)
|
||||
_retreg = UC_X86_REG_RAX
|
||||
@@ -0,0 +1,41 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
|
||||
|
||||
from typing import Tuple
|
||||
from unicorn.mips_const import UC_MIPS_REG_V0, UC_MIPS_REG_A0, UC_MIPS_REG_A1, UC_MIPS_REG_A2, UC_MIPS_REG_A3
|
||||
|
||||
from qiling.os.posix.syscall.abi import QlSyscallABI
|
||||
|
||||
|
||||
class QlMipsO32(QlSyscallABI):
|
||||
"""System call ABI for MIPS O32 systems.
|
||||
|
||||
See: https://www.linux-mips.org/wiki/Syscall
|
||||
"""
|
||||
|
||||
_idreg = UC_MIPS_REG_V0
|
||||
_argregs = (UC_MIPS_REG_A0, UC_MIPS_REG_A1, UC_MIPS_REG_A2, UC_MIPS_REG_A3)
|
||||
_retreg = UC_MIPS_REG_V0
|
||||
|
||||
def __get_stack_params(self, count: int) -> Tuple[int, ...]:
|
||||
"""Get system call parameters passed on stack.
|
||||
"""
|
||||
|
||||
shadowed = len(self._argregs)
|
||||
|
||||
return tuple(self.arch.stack_read(self.arch.pointersize * (i + shadowed)) for i in range(count))
|
||||
|
||||
def get_params(self, count: int) -> Tuple[int, ...]:
|
||||
num_reg_args = len(self._argregs)
|
||||
|
||||
stack_args = self.__get_stack_params(count - num_reg_args)
|
||||
reg_args = super().get_params(min(count, num_reg_args))
|
||||
|
||||
return reg_args + stack_args
|
||||
|
||||
def set_return_value(self, value: int) -> None:
|
||||
a3, v0 = (1, -value) if -1134 < value < 0 else (0, value)
|
||||
|
||||
self.arch.regs.v0 = v0
|
||||
self.arch.regs.a3 = a3
|
||||
@@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
|
||||
|
||||
from unicorn.ppc_const import (
|
||||
UC_PPC_REG_0, UC_PPC_REG_3, UC_PPC_REG_4, UC_PPC_REG_5,
|
||||
UC_PPC_REG_6, UC_PPC_REG_7, UC_PPC_REG_8
|
||||
)
|
||||
|
||||
from qiling.os.posix.syscall.abi import QlSyscallABI
|
||||
|
||||
|
||||
class QlPPC(QlSyscallABI):
|
||||
"""System call ABI for PowerPC systems.
|
||||
"""
|
||||
|
||||
_idreg = UC_PPC_REG_0
|
||||
_argregs = (UC_PPC_REG_3, UC_PPC_REG_4, UC_PPC_REG_5, UC_PPC_REG_6, UC_PPC_REG_7, UC_PPC_REG_8)
|
||||
_retreg = UC_PPC_REG_3
|
||||
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
|
||||
|
||||
from unicorn.riscv_const import (
|
||||
UC_RISCV_REG_A0, UC_RISCV_REG_A1, UC_RISCV_REG_A2, UC_RISCV_REG_A3,
|
||||
UC_RISCV_REG_A4, UC_RISCV_REG_A5, UC_RISCV_REG_A7
|
||||
)
|
||||
|
||||
from qiling.os.posix.syscall.abi import QlSyscallABI
|
||||
|
||||
|
||||
class QlRiscV32(QlSyscallABI):
|
||||
"""System call ABI for RISCV systems.
|
||||
"""
|
||||
|
||||
_idreg = UC_RISCV_REG_A7
|
||||
_argregs = (UC_RISCV_REG_A0, UC_RISCV_REG_A1, UC_RISCV_REG_A2, UC_RISCV_REG_A3, UC_RISCV_REG_A4, UC_RISCV_REG_A5)
|
||||
_retreg = UC_RISCV_REG_A0
|
||||
|
||||
|
||||
QlRiscV64 = QlRiscV32
|
||||
@@ -1,27 +1,24 @@
|
||||
from typing import Optional
|
||||
from qiling import Qiling
|
||||
from qiling.os.posix.const import *
|
||||
from qiling.os.posix.posix import QlMsqId, QlMsgBuf
|
||||
from qiling.os.posix.msq import QlMsqId, QlMsgBuf
|
||||
|
||||
|
||||
def __find_msg(msq: QlMsqId, msgtyp: int, msgflg: int) -> Optional[QlMsgBuf]:
|
||||
# peek at a specific queue item
|
||||
if msgflg & MSG_COPY:
|
||||
if msgtyp >= len(msq.queue):
|
||||
return -1 # ENOMSG
|
||||
|
||||
return msg.queue[msgtyp]
|
||||
return msq.queue[msgtyp] if msgtyp < len(msq.queue) else None
|
||||
|
||||
if msgtyp == 0:
|
||||
predicate = lambda msg: True
|
||||
|
||||
elif msgtype > 0:
|
||||
elif msgtyp > 0:
|
||||
if msgflg & MSG_EXCEPT:
|
||||
predicate = lambda msg: msg.mtype != msgtyp
|
||||
else:
|
||||
predicate = lambda msg: msg.mtype == msgtyp
|
||||
|
||||
elif msgtype < 0:
|
||||
elif msgtyp < 0:
|
||||
predicate = lambda msg: msg.mtype <= -msgtyp
|
||||
|
||||
return next((msg for msg in msq.queue if predicate(msg)), None)
|
||||
@@ -41,12 +38,13 @@ def __perms(ql: Qiling, msq: QlMsqId, flag: int) -> int:
|
||||
if ql.os.uid == msq.uid:
|
||||
granted_mode >>= 6
|
||||
|
||||
# is there some bit set in requested_mode but not in granted_mode?
|
||||
# is there some bit set in requested_mode but not in granted_mode?
|
||||
if request_mode & ~granted_mode & 0o007:
|
||||
return -1 # EACCES
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
def ql_syscall_msgget(ql: Qiling, key: int, msgflg: int):
|
||||
def __create_msq(key: int, flags: int) -> int:
|
||||
"""Create a new message queue for the specified key.
|
||||
@@ -92,6 +90,7 @@ def ql_syscall_msgget(ql: Qiling, key: int, msgflg: int):
|
||||
|
||||
return msqid
|
||||
|
||||
|
||||
def ql_syscall_msgsnd(ql: Qiling, msqid: int, msgp: int, msgsz: int, msgflg: int):
|
||||
msq = ql.os.msq.get_by_id(msqid)
|
||||
|
||||
@@ -122,7 +121,7 @@ def ql_syscall_msgrcv(ql: Qiling, msqid: int, msgp: int, msgsz: int, msgtyp: int
|
||||
|
||||
if msq is None:
|
||||
return -1 # EINVAL
|
||||
|
||||
|
||||
if msgflg & MSG_COPY:
|
||||
if msgflg & MSG_EXCEPT or not (msgflg & IPC_NOWAIT):
|
||||
return -1 # EINVAL
|
||||
@@ -137,6 +136,9 @@ def ql_syscall_msgrcv(ql: Qiling, msqid: int, msgp: int, msgsz: int, msgtyp: int
|
||||
if msg is not None:
|
||||
break
|
||||
|
||||
if msgflg & MSG_COPY:
|
||||
return -1 # ENOMSG
|
||||
|
||||
if msgflg & IPC_NOWAIT:
|
||||
return -1 # ENOMSG
|
||||
|
||||
@@ -156,6 +158,7 @@ def ql_syscall_msgrcv(ql: Qiling, msqid: int, msgp: int, msgsz: int, msgtyp: int
|
||||
|
||||
return sz # Success
|
||||
|
||||
|
||||
__all__ = [
|
||||
'ql_syscall_msgget',
|
||||
'ql_syscall_msgsnd',
|
||||
|
||||
@@ -107,7 +107,7 @@ def ql_syscall_clone(ql: Qiling, flags: int, child_stack: int, parent_tidptr: in
|
||||
ql.arch.regs.arch_pc += list(ql.arch.disassembler.disasm_lite(bytes(ql.mem.read(ql.arch.regs.arch_pc, 4)), ql.arch.regs.arch_pc))[0][1]
|
||||
ql.log.debug(f"Fix pc for child thread to {hex(ql.arch.regs.arch_pc)}")
|
||||
|
||||
ql.os.set_syscall_return(0)
|
||||
ql.os.syscall_abi.set_return_value(0)
|
||||
th.save()
|
||||
|
||||
if th is None or f_th is None:
|
||||
|
||||
@@ -9,7 +9,7 @@ from qiling import Qiling
|
||||
from qiling.const import QL_ARCH
|
||||
from qiling.exception import QlMemoryMappedError
|
||||
from qiling.os.posix.const import *
|
||||
from qiling.os.posix.posix import QlShmId
|
||||
from qiling.os.posix.shm import QlShmId
|
||||
|
||||
|
||||
def ql_syscall_shmget(ql: Qiling, key: int, size: int, shmflg: int):
|
||||
|
||||
@@ -213,7 +213,7 @@ def virtual_abspath_at(ql: Qiling, vpath: str, dirfd: int) -> Optional[str]:
|
||||
|
||||
# syscall params are read as unsigned int by default. until we fix that
|
||||
# broadly, this is a workaround to turn fd into a signed value
|
||||
dirfd = __as_signed(dirfd, ql.arch.bits)
|
||||
dirfd = __as_signed(dirfd & ((1 << 32) - 1), 32)
|
||||
# </WORKAROUND>
|
||||
|
||||
if dirfd == AT_FDCWD:
|
||||
|
||||
@@ -8,6 +8,7 @@ from unicorn import UcError
|
||||
from qiling import Qiling
|
||||
from qiling.arch import arm_utils
|
||||
from qiling.os.posix.posix import QlOsPosix
|
||||
from qiling.os.posix.syscall.abi import arm as arm_abi
|
||||
from qiling.os.qnx.const import NTO_SIDE_CHANNEL, SYSMGR_PID, SYSMGR_CHID, SYSMGR_COID
|
||||
from qiling.os.qnx.helpers import QnxConn
|
||||
from qiling.os.qnx.structs import _thread_local_storage
|
||||
@@ -23,9 +24,10 @@ class QlOsQnx(QlOsPosix):
|
||||
type = QL_OS.QNX
|
||||
|
||||
def __init__(self, ql: Qiling):
|
||||
super(QlOsQnx, self).__init__(ql)
|
||||
super().__init__(ql)
|
||||
|
||||
self.ql = ql
|
||||
if ql.arch.type is QL_ARCH.ARM:
|
||||
self.syscall_abi = arm_abi.QlAArch32QNX(ql.arch)
|
||||
|
||||
cc: QlCC = {
|
||||
QL_ARCH.X86 : intel.cdecl,
|
||||
|
||||
+16
-12
@@ -20,9 +20,10 @@ from typing import TYPE_CHECKING, Any, Callable, Mapping, Optional, Tuple, TypeV
|
||||
|
||||
from unicorn import UC_ERR_READ_UNMAPPED, UC_ERR_FETCH_UNMAPPED
|
||||
|
||||
from qiling.exception import *
|
||||
from qiling.arch.models import QL_CPU
|
||||
from qiling.const import QL_ARCH, QL_ENDIAN, QL_OS, QL_DEBUGGER
|
||||
from qiling.const import debugger_map, arch_map, os_map, arch_os_map
|
||||
from qiling.exception import *
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from qiling import Qiling
|
||||
@@ -293,11 +294,10 @@ def ql_guess_emu_env(path: str) -> Tuple[Optional[QL_ARCH], Optional[QL_OS], Opt
|
||||
|
||||
|
||||
def select_loader(ostype: QL_OS, libcache: bool) -> QlClassInit['QlLoader']:
|
||||
if ostype is QL_OS.WINDOWS:
|
||||
kwargs = {'libcache': libcache}
|
||||
kwargs = {}
|
||||
|
||||
else:
|
||||
kwargs = {}
|
||||
if ostype is QL_OS.WINDOWS:
|
||||
kwargs['libcache'] = libcache
|
||||
|
||||
module = {
|
||||
QL_OS.LINUX : r'elf',
|
||||
@@ -370,17 +370,21 @@ def select_debugger(options: Union[str, bool]) -> Optional[QlClassInit['QlDebugg
|
||||
return None
|
||||
|
||||
|
||||
def select_arch(archtype: QL_ARCH, endian: QL_ENDIAN, thumb: bool) -> QlClassInit['QlArch']:
|
||||
def select_arch(archtype: QL_ARCH, cputype: Optional[QL_CPU], endian: QL_ENDIAN, thumb: bool) -> QlClassInit['QlArch']:
|
||||
kwargs = {}
|
||||
|
||||
# skip cpu model for evm
|
||||
if archtype is not QL_ARCH.EVM:
|
||||
kwargs['cputype'] = cputype
|
||||
|
||||
# set endianess and thumb mode for arm-based archs
|
||||
if archtype is QL_ARCH.ARM:
|
||||
kwargs = {'endian': endian, 'thumb': thumb}
|
||||
kwargs['endian'] = endian
|
||||
kwargs['thumb'] = thumb
|
||||
|
||||
# set endianess for mips arch
|
||||
elif archtype is QL_ARCH.MIPS:
|
||||
kwargs = {'endian': endian}
|
||||
|
||||
else:
|
||||
kwargs = {}
|
||||
kwargs['endian'] = endian
|
||||
|
||||
module = {
|
||||
QL_ARCH.A8086 : r'x86',
|
||||
@@ -430,7 +434,7 @@ def profile_setup(ostype: QL_OS, user_config: Optional[Union[str, dict]]):
|
||||
int_converter = partial(int, base=0)
|
||||
config = ConfigParser(converters={'int': int_converter})
|
||||
|
||||
qiling_home = Path(inspect.getfile(inspect.currentframe())).parent
|
||||
qiling_home = Path(inspect.getfile(profile_setup)).parent
|
||||
os_profile = qiling_home / 'profiles' / f'{ostype.name.lower()}.ql'
|
||||
|
||||
# read default profile first
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
|
||||
#
|
||||
|
||||
import importlib
|
||||
import unittest
|
||||
|
||||
from enum import Enum
|
||||
from typing import Callable, Type
|
||||
|
||||
from qiling.arch.models import *
|
||||
|
||||
|
||||
class CpuModelsTest(unittest.TestCase):
|
||||
"""Make sure all *_CPU_MODEL enums are in sync with Unicorn's constants.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def __get_converter(ql_name: str) -> Callable[[str], str]:
|
||||
"""Create a ql-name to uc-name convertion function.
|
||||
"""
|
||||
|
||||
def wrapped(k: str) -> str:
|
||||
arch, _, name = k.partition('_')
|
||||
|
||||
return ql_name.format(arch=arch, name=name)
|
||||
|
||||
return wrapped
|
||||
|
||||
def __test_cpu_models(self, uc_const_module: str, ql_models_enum: Type[Enum], uc_name: str):
|
||||
ql_to_uc_name = self.__get_converter(uc_name)
|
||||
uc_consts = importlib.import_module(f'unicorn.{uc_const_module}').__dict__
|
||||
|
||||
for k, v in ql_models_enum.__members__.items():
|
||||
uc_const_name = ql_to_uc_name(k)
|
||||
|
||||
# make sure a ql enumeration entry has a cooresponding uc constant
|
||||
self.assertIn(uc_const_name, uc_consts, f'Could not find a matching constant for {k} ({uc_const_name})')
|
||||
|
||||
# make sure their values are equal
|
||||
self.assertEqual(uc_consts[uc_const_name], v.value, f'Unexpected value for {uc_const_name}')
|
||||
|
||||
def test_x86_cpu_models(self):
|
||||
self.__test_cpu_models('x86_const', X86_CPU_MODEL, 'UC_CPU_X86_{name}')
|
||||
|
||||
def test_arm_cpu_models(self):
|
||||
self.__test_cpu_models('arm_const', ARM_CPU_MODEL, 'UC_CPU_ARM_{name}')
|
||||
|
||||
def test_arm64_cpu_models(self):
|
||||
self.__test_cpu_models('arm64_const', ARM64_CPU_MODEL, 'UC_CPU_ARM64_{name}')
|
||||
|
||||
def test_mips_cpu_models(self):
|
||||
self.__test_cpu_models('mips_const', MIPS_CPU_MODEL, 'UC_CPU_MIPS32_{name}')
|
||||
|
||||
def test_ppc_cpu_models(self):
|
||||
self.__test_cpu_models('ppc_const', PPC_CPU_MODEL, 'UC_CPU_PPC32_{name}')
|
||||
|
||||
def test_riscv_cpu_models(self):
|
||||
self.__test_cpu_models('riscv_const', RISCV_CPU_MODEL, 'UC_CPU_RISCV32_{name}')
|
||||
|
||||
def test_riscv64_cpu_models(self):
|
||||
self.__test_cpu_models('riscv_const', RISCV64_CPU_MODEL, 'UC_CPU_RISCV64_{name}')
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
+254
-624
File diff suppressed because it is too large
Load Diff
@@ -74,7 +74,7 @@ class ELFTest(unittest.TestCase):
|
||||
if fd == 1:
|
||||
content = ql.mem.read(write_buf, count)
|
||||
|
||||
logged.append(content.decode())
|
||||
logged.extend(content.decode().splitlines())
|
||||
|
||||
ql = Qiling([fr'{X86_LINUX_ROOTFS}/bin/x86_multithreading'], X86_LINUX_ROOTFS, multithread=True, verbose=QL_VERBOSE.DEBUG)
|
||||
|
||||
@@ -93,7 +93,7 @@ class ELFTest(unittest.TestCase):
|
||||
if fd == 1:
|
||||
content = ql.mem.read(write_buf, count)
|
||||
|
||||
logged.append(content.decode())
|
||||
logged.extend(content.decode().splitlines())
|
||||
|
||||
ql = Qiling([fr'{ARM64_LINUX_ROOTFS}/bin/arm64_multithreading'], ARM64_LINUX_ROOTFS, multithread=True, verbose=QL_VERBOSE.DEBUG)
|
||||
|
||||
@@ -112,7 +112,7 @@ class ELFTest(unittest.TestCase):
|
||||
if fd == 1:
|
||||
content = ql.mem.read(write_buf, count)
|
||||
|
||||
logged.append(content.decode())
|
||||
logged.extend(content.decode().splitlines())
|
||||
|
||||
ql = Qiling([fr'{X64_LINUX_ROOTFS}/bin/x8664_multithreading'], X64_LINUX_ROOTFS, multithread=True, verbose=QL_VERBOSE.DEBUG)
|
||||
|
||||
@@ -131,7 +131,7 @@ class ELFTest(unittest.TestCase):
|
||||
if fd == 1:
|
||||
content = ql.mem.read(write_buf, count)
|
||||
|
||||
logged.append(content.decode())
|
||||
logged.extend(content.decode().splitlines())
|
||||
|
||||
ql = Qiling([fr'{MIPSEB_LINUX_ROOTFS}/bin/mips32_multithreading'], MIPSEB_LINUX_ROOTFS, multithread=True, verbose=QL_VERBOSE.DEBUG)
|
||||
|
||||
@@ -150,7 +150,7 @@ class ELFTest(unittest.TestCase):
|
||||
if fd == 1:
|
||||
content = ql.mem.read(write_buf, count)
|
||||
|
||||
logged.append(content.decode())
|
||||
logged.extend(content.decode().splitlines())
|
||||
|
||||
ql = Qiling([fr'{MIPSEL_LINUX_ROOTFS}/bin/mips32el_multithreading'], MIPSEL_LINUX_ROOTFS, multithread=True, verbose=QL_VERBOSE.DEBUG)
|
||||
|
||||
@@ -169,7 +169,7 @@ class ELFTest(unittest.TestCase):
|
||||
if fd == 1:
|
||||
content = ql.mem.read(write_buf, count)
|
||||
|
||||
logged.append(content.decode())
|
||||
logged.extend(content.decode().splitlines())
|
||||
|
||||
ql = Qiling([fr'{ARM_LINUX_ROOTFS}/bin/arm_multithreading'], ARM_LINUX_ROOTFS, multithread=True, verbose=QL_VERBOSE.DEBUG)
|
||||
|
||||
@@ -189,7 +189,7 @@ class ELFTest(unittest.TestCase):
|
||||
if fd == 1:
|
||||
content = ql.mem.read(write_buf, count)
|
||||
|
||||
logged.append(content.decode())
|
||||
logged.extend(content.decode().splitlines())
|
||||
|
||||
ql = Qiling([fr'{ARMEB_LINUX_ROOTFS}/bin/armeb_multithreading'], ARMEB_LINUX_ROOTFS, multithread=True, verbose=QL_VERBOSE.DEBUG)
|
||||
|
||||
@@ -208,7 +208,7 @@ class ELFTest(unittest.TestCase):
|
||||
if fd == 2:
|
||||
content = ql.mem.read(write_buf, count)
|
||||
|
||||
logged.append(content.decode())
|
||||
logged.extend(content.decode().splitlines())
|
||||
|
||||
ql = Qiling([fr'{X86_LINUX_ROOTFS}/bin/x86_tcp_test', '20000'], X86_LINUX_ROOTFS, multithread=True, verbose=QL_VERBOSE.DEBUG)
|
||||
|
||||
@@ -224,8 +224,8 @@ class ELFTest(unittest.TestCase):
|
||||
# 'server recv() return 14.\n'
|
||||
# 'server send() 14 return 14.\n'
|
||||
|
||||
m = re.search(r'(?P<num>\d+)\.\s+\Z', logged[-2])
|
||||
self.assertIsNotNone(m, 'could not extract numeric value from log message')
|
||||
m = re.search(r'(?P<num>\d+)\.\Z', logged[-2])
|
||||
self.assertIsNotNone(m, f'could not extract numeric value from log message "{logged[-2]}"')
|
||||
|
||||
num = m.group('num')
|
||||
msg = logged[-1].strip()
|
||||
@@ -239,7 +239,7 @@ class ELFTest(unittest.TestCase):
|
||||
if fd == 2:
|
||||
content = ql.mem.read(write_buf, count)
|
||||
|
||||
logged.append(content.decode())
|
||||
logged.extend(content.decode().splitlines())
|
||||
|
||||
ql = Qiling([fr'{X64_LINUX_ROOTFS}/bin/x8664_tcp_test', '20001'], X64_LINUX_ROOTFS, multithread=True, verbose=QL_VERBOSE.DEBUG)
|
||||
|
||||
@@ -255,8 +255,8 @@ class ELFTest(unittest.TestCase):
|
||||
# 'server recv() return 14.\n'
|
||||
# 'server send() 14 return 14.\n'
|
||||
|
||||
m = re.search(r'(?P<num>\d+)\.\s+\Z', logged[-2])
|
||||
self.assertIsNotNone(m, 'could not extract numeric value from log message')
|
||||
m = re.search(r'(?P<num>\d+)\.\Z', logged[-2])
|
||||
self.assertIsNotNone(m, f'could not extract numeric value from log message "{logged[-2]}"')
|
||||
|
||||
num = m.group('num')
|
||||
msg = logged[-1].strip()
|
||||
@@ -270,7 +270,7 @@ class ELFTest(unittest.TestCase):
|
||||
if fd == 2:
|
||||
content = ql.mem.read(write_buf, count)
|
||||
|
||||
logged.append(content.decode())
|
||||
logged.extend(content.decode().splitlines())
|
||||
|
||||
ql = Qiling([fr'{ARM_LINUX_ROOTFS}/bin/arm_tcp_test', '20002'], ARM_LINUX_ROOTFS, multithread=True, verbose=QL_VERBOSE.DEBUG)
|
||||
|
||||
@@ -286,8 +286,8 @@ class ELFTest(unittest.TestCase):
|
||||
# 'server read() return 14.\n'
|
||||
# 'server write() 14 return 14.\n'
|
||||
|
||||
m = re.search(r'(?P<num>\d+)\.\s+\Z', logged[-2])
|
||||
self.assertIsNotNone(m, 'could not extract numeric value from log message')
|
||||
m = re.search(r'(?P<num>\d+)\.\Z', logged[-2])
|
||||
self.assertIsNotNone(m, f'could not extract numeric value from log message "{logged[-2]}"')
|
||||
|
||||
num = m.group('num')
|
||||
msg = logged[-1].strip()
|
||||
@@ -301,7 +301,7 @@ class ELFTest(unittest.TestCase):
|
||||
if fd == 2:
|
||||
content = ql.mem.read(write_buf, count)
|
||||
|
||||
logged.append(content.decode())
|
||||
logged.extend(content.decode().splitlines())
|
||||
|
||||
ql = Qiling([fr'{ARM64_LINUX_ROOTFS}/bin/arm64_tcp_test', '20003'], ARM64_LINUX_ROOTFS, multithread=True, verbose=QL_VERBOSE.DEBUG)
|
||||
|
||||
@@ -317,8 +317,8 @@ class ELFTest(unittest.TestCase):
|
||||
# 'server recv() return 14.\n'
|
||||
# 'server send() 14 return 14.\n'
|
||||
|
||||
m = re.search(r'(?P<num>\d+)\.\s+\Z', logged[-2])
|
||||
self.assertIsNotNone(m, 'could not extract numeric value from log message')
|
||||
m = re.search(r'(?P<num>\d+)\.\Z', logged[-2])
|
||||
self.assertIsNotNone(m, f'could not extract numeric value from log message "{logged[-2]}"')
|
||||
|
||||
num = m.group('num')
|
||||
msg = logged[-1].strip()
|
||||
@@ -332,7 +332,7 @@ class ELFTest(unittest.TestCase):
|
||||
if fd == 2:
|
||||
content = ql.mem.read(write_buf, count)
|
||||
|
||||
logged.append(content.decode())
|
||||
logged.extend(content.decode().splitlines())
|
||||
|
||||
ql = Qiling([fr'{ARMEB_LINUX_ROOTFS}/bin/armeb_tcp_test', '20004'], ARMEB_LINUX_ROOTFS, multithread=True, verbose=QL_VERBOSE.DEBUG)
|
||||
|
||||
@@ -348,8 +348,8 @@ class ELFTest(unittest.TestCase):
|
||||
# 'server recv() return 14.\n'
|
||||
# 'server send() 14 return 14.\n'
|
||||
|
||||
m = re.search(r'(?P<num>\d+)\.\s+\Z', logged[-2])
|
||||
self.assertIsNotNone(m, 'could not extract numeric value from log message')
|
||||
m = re.search(r'(?P<num>\d+)\.\Z', logged[-2])
|
||||
self.assertIsNotNone(m, f'could not extract numeric value from log message "{logged[-2]}"')
|
||||
|
||||
num = m.group('num')
|
||||
msg = logged[-1].strip()
|
||||
@@ -363,7 +363,7 @@ class ELFTest(unittest.TestCase):
|
||||
if fd == 2:
|
||||
content = ql.mem.read(write_buf, count)
|
||||
|
||||
logged.append(content.decode())
|
||||
logged.extend(content.decode().splitlines())
|
||||
|
||||
ql = Qiling([fr'{MIPSEB_LINUX_ROOTFS}/bin/mips32_tcp_test', '20005'], MIPSEB_LINUX_ROOTFS, multithread=True, verbose=QL_VERBOSE.DEBUG)
|
||||
|
||||
@@ -379,8 +379,8 @@ class ELFTest(unittest.TestCase):
|
||||
# 'server recv() return 14.\n'
|
||||
# 'server send() 14 return 14.\n'
|
||||
|
||||
m = re.search(r'(?P<num>\d+)\.\s+\Z', logged[-2])
|
||||
self.assertIsNotNone(m, 'could not extract numeric value from log message')
|
||||
m = re.search(r'(?P<num>\d+)\.\Z', logged[-2])
|
||||
self.assertIsNotNone(m, f'could not extract numeric value from log message "{logged[-2]}"')
|
||||
|
||||
num = m.group('num')
|
||||
msg = logged[-1].strip()
|
||||
@@ -394,7 +394,7 @@ class ELFTest(unittest.TestCase):
|
||||
if fd == 2:
|
||||
content = ql.mem.read(write_buf, count)
|
||||
|
||||
logged.append(content.decode())
|
||||
logged.extend(content.decode().splitlines())
|
||||
|
||||
ql = Qiling([fr'{MIPSEL_LINUX_ROOTFS}/bin/mips32el_tcp_test', '20006'], MIPSEL_LINUX_ROOTFS, multithread=True, verbose=QL_VERBOSE.DEBUG)
|
||||
|
||||
@@ -410,8 +410,8 @@ class ELFTest(unittest.TestCase):
|
||||
# 'server read() return 14.\n'
|
||||
# 'server write() 14 return 14.\n'
|
||||
|
||||
m = re.search(r'(?P<num>\d+)\.\s+\Z', logged[-2])
|
||||
self.assertIsNotNone(m, 'could not extract numeric value from log message')
|
||||
m = re.search(r'(?P<num>\d+)\.\Z', logged[-2])
|
||||
self.assertIsNotNone(m, f'could not extract numeric value from log message "{logged[-2]}"')
|
||||
|
||||
num = m.group('num')
|
||||
msg = logged[-1].strip()
|
||||
@@ -425,7 +425,7 @@ class ELFTest(unittest.TestCase):
|
||||
if fd == 2:
|
||||
content = ql.mem.read(write_buf, count)
|
||||
|
||||
logged.append(content.decode())
|
||||
logged.extend(content.decode().splitlines())
|
||||
|
||||
ql = Qiling([fr'{X86_LINUX_ROOTFS}/bin/x86_udp_test', '20010'], X86_LINUX_ROOTFS, multithread=True, verbose=QL_VERBOSE.DEBUG)
|
||||
|
||||
@@ -441,8 +441,8 @@ class ELFTest(unittest.TestCase):
|
||||
# 'server recvfrom() return 14.\n'
|
||||
# 'server sendto() 14 return 14.\n'
|
||||
|
||||
m = re.search(r'(?P<num>\d+)\.\s+\Z', logged[-2])
|
||||
self.assertIsNotNone(m, 'could not extract numeric value from log message')
|
||||
m = re.search(r'(?P<num>\d+)\.\Z', logged[-2])
|
||||
self.assertIsNotNone(m, f'could not extract numeric value from log message "{logged[-2]}"')
|
||||
|
||||
num = m.group('num')
|
||||
msg = logged[-1].strip()
|
||||
@@ -456,7 +456,7 @@ class ELFTest(unittest.TestCase):
|
||||
if fd == 2:
|
||||
content = ql.mem.read(write_buf, count)
|
||||
|
||||
logged.append(content.decode())
|
||||
logged.extend(content.decode().splitlines())
|
||||
|
||||
ql = Qiling([fr'{X64_LINUX_ROOTFS}/bin/x8664_udp_test', '20011'], X64_LINUX_ROOTFS, multithread=True, verbose=QL_VERBOSE.DEBUG)
|
||||
|
||||
@@ -472,8 +472,8 @@ class ELFTest(unittest.TestCase):
|
||||
# 'server recvfrom() return 14.\n'
|
||||
# 'server sendto() 14 return 14.\n'
|
||||
|
||||
m = re.search(r'(?P<num>\d+)\.\s+\Z', logged[-2])
|
||||
self.assertIsNotNone(m, 'could not extract numeric value from log message')
|
||||
m = re.search(r'(?P<num>\d+)\.\Z', logged[-2])
|
||||
self.assertIsNotNone(m, f'could not extract numeric value from log message "{logged[-2]}"')
|
||||
|
||||
num = m.group('num')
|
||||
msg = logged[-1].strip()
|
||||
@@ -487,7 +487,7 @@ class ELFTest(unittest.TestCase):
|
||||
if fd == 2:
|
||||
content = ql.mem.read(write_buf, count)
|
||||
|
||||
logged.append(content.decode())
|
||||
logged.extend(content.decode().splitlines())
|
||||
|
||||
ql = Qiling([fr'{ARM64_LINUX_ROOTFS}/bin/arm64_udp_test', '20013'], ARM64_LINUX_ROOTFS, multithread=True, verbose=QL_VERBOSE.DEBUG)
|
||||
|
||||
@@ -503,8 +503,8 @@ class ELFTest(unittest.TestCase):
|
||||
# 'server recvfrom() return 14.\n'
|
||||
# 'server sendto() 14 return 14.\n'
|
||||
|
||||
m = re.search(r'(?P<num>\d+)\.\s+\Z', logged[-2])
|
||||
self.assertIsNotNone(m, 'could not extract numeric value from log message')
|
||||
m = re.search(r'(?P<num>\d+)\.\Z', logged[-2])
|
||||
self.assertIsNotNone(m, f'could not extract numeric value from log message "{logged[-2]}"')
|
||||
|
||||
num = m.group('num')
|
||||
msg = logged[-1].strip()
|
||||
@@ -518,7 +518,7 @@ class ELFTest(unittest.TestCase):
|
||||
if fd == 2:
|
||||
content = ql.mem.read(write_buf, count)
|
||||
|
||||
logged.append(content.decode())
|
||||
logged.extend(content.decode().splitlines())
|
||||
|
||||
ql = Qiling([fr'{ARMEB_LINUX_ROOTFS}/bin/armeb_udp_test', '20014'], ARMEB_LINUX_ROOTFS, multithread=True, verbose=QL_VERBOSE.DEBUG)
|
||||
|
||||
@@ -534,8 +534,8 @@ class ELFTest(unittest.TestCase):
|
||||
# 'server recvfrom() return 14.\n'
|
||||
# 'server sendto() 14 return 14.\n'
|
||||
|
||||
m = re.search(r'(?P<num>\d+)\.\s+\Z', logged[-2])
|
||||
self.assertIsNotNone(m, 'could not extract numeric value from log message')
|
||||
m = re.search(r'(?P<num>\d+)\.\Z', logged[-2])
|
||||
self.assertIsNotNone(m, f'could not extract numeric value from log message "{logged[-2]}"')
|
||||
|
||||
num = m.group('num')
|
||||
msg = logged[-1].strip()
|
||||
|
||||
+80
-54
@@ -1,100 +1,126 @@
|
||||
import re
|
||||
import unittest
|
||||
|
||||
from typing import List, Optional, Tuple
|
||||
|
||||
from qiling import Qiling
|
||||
from qiling.const import QL_VERBOSE
|
||||
from qiling.extensions.coverage.formats.history import History
|
||||
from typing import List
|
||||
|
||||
|
||||
class HistoryTest(unittest.TestCase):
|
||||
P_LIBC = r".*libc(-\d\.\d+)?.so.*" # covers both generic (i.e. "libc.so.6") and specific (e.g. "libc-2.27.so")
|
||||
P_LD = r"ld-linux.*"
|
||||
|
||||
@staticmethod
|
||||
def sanitize_mmap_path(mmap: List[tuple]) -> List[tuple]:
|
||||
'''Removes the path from the mmap tuple so that it can be compared to other mmaps
|
||||
currently because the loader is handling loading ld and the main binary, we get the annotation of the path in element 5 of the tuple (index 4)
|
||||
this path is going to be dependent on the users filesystem, so it doesnt quite make sense to test for it
|
||||
'''
|
||||
def sanitize_mmap_path(mmap: List[Tuple]) -> List[Tuple[int, int, str, str]]:
|
||||
"""Removes the path from the mmap tuple so that it can be compared to other mmaps.
|
||||
|
||||
currently because the loader is handling loading ld and the main binary, we get the annotation of the path in
|
||||
element 5 of the tuple (index 4) this path is going to be dependent on the users filesystem, so it doesnt quite
|
||||
make sense to test for it
|
||||
"""
|
||||
|
||||
if isinstance(mmap, tuple):
|
||||
mmap = [mmap]
|
||||
|
||||
return list(map(lambda x: (x[0], x[1], x[2], x[3], ''), mmap))
|
||||
return [tup[:4] for tup in mmap]
|
||||
|
||||
def setUp(self):
|
||||
rootfs = '../examples/rootfs/x8664_linux'
|
||||
argv = [fr'{rootfs}/bin/x8664_hello']
|
||||
|
||||
ql = Qiling(argv, rootfs, verbose=QL_VERBOSE.OFF)
|
||||
|
||||
self.history = History(ql)
|
||||
self.ql = ql
|
||||
|
||||
def get_label(self, basename: str) -> Optional[str]:
|
||||
"""Return the matching label from mapinfo, ignoring boxes and possible different suffixes.
|
||||
|
||||
For example, on some systems libc label may appear as "libc.so.6" and "libc-2.27.so" on others.
|
||||
This method also ignores the boxed prefixes, if exists (e.g. "[mmap]")
|
||||
"""
|
||||
|
||||
p = re.compile(rf'\A(\[.+\]\s+)?{basename}[.-]')
|
||||
|
||||
return next((label for _, _, _, label, _ in self.ql.mem.map_info if re.match(p, label)), None)
|
||||
|
||||
def test_get_regex_matching_exec_maps(self):
|
||||
ql = Qiling(["../examples/rootfs/x8664_linux/bin/x8664_hello"], "../examples/rootfs/x8664_linux", verbose=QL_VERBOSE.OFF)
|
||||
history = History(ql)
|
||||
ql.run()
|
||||
self.ql.run()
|
||||
|
||||
self.assertEqual([(0x7fffb7dd6000, 0x7fffb7fbd000, 'r-x', '[mmap] libc.so.6', '')], history.get_regex_matching_exec_maps(".*libc.so.*"))
|
||||
|
||||
self.assertEqual(
|
||||
self.assertListEqual(
|
||||
[
|
||||
(0x7fffb7dd6000, 0x7fffb7fbd000, 'r-x', '[mmap] libc.so.6', ''),
|
||||
(0x7ffff7dd5000, 0x7ffff7dfc000, 'r-x', 'ld-linux-x86-64.so.2', '')
|
||||
(0x7fffb7dd6000, 0x7fffb7fbd000, 'r-x', self.get_label('libc'), '')
|
||||
],
|
||||
self.sanitize_mmap_path(history.get_regex_matching_exec_maps([".*libc.so.*", "ld.*"]))
|
||||
self.history.get_regex_matching_exec_maps(self.P_LIBC)
|
||||
)
|
||||
|
||||
del ql
|
||||
self.assertListEqual(
|
||||
[
|
||||
(0x7fffb7dd6000, 0x7fffb7fbd000, 'r-x', self.get_label('libc')),
|
||||
(0x7ffff7dd5000, 0x7ffff7dfc000, 'r-x', self.get_label('ld'))
|
||||
],
|
||||
self.sanitize_mmap_path(self.history.get_regex_matching_exec_maps([self.P_LIBC, self.P_LD]))
|
||||
)
|
||||
|
||||
def test_get_mem_map_from_addr(self):
|
||||
ql = Qiling(["../examples/rootfs/x8664_linux/bin/x8664_hello"], "../examples/rootfs/x8664_linux", verbose=QL_VERBOSE.OFF)
|
||||
history = History(ql)
|
||||
ql.run()
|
||||
self.ql.run()
|
||||
|
||||
self.assertEqual(
|
||||
self.sanitize_mmap_path(history.get_mem_map_from_addr(0x7ffff7df4830))[0],
|
||||
(
|
||||
0x7ffff7dd5000,
|
||||
0x7ffff7dfc000,
|
||||
'r-x',
|
||||
'ld-linux-x86-64.so.2',
|
||||
''))
|
||||
|
||||
mmap = self.history.get_mem_map_from_addr(0x7ffff7df4830)
|
||||
self.assertIsNotNone(mmap)
|
||||
|
||||
self.assertTupleEqual(
|
||||
(0x7ffff7dd5000, 0x7ffff7dfc000, 'r-x', self.get_label('ld')),
|
||||
self.sanitize_mmap_path(mmap)[0]
|
||||
)
|
||||
|
||||
def test_get_ins_exclude_lib(self):
|
||||
ql = Qiling(["../examples/rootfs/x8664_linux/bin/x8664_hello"], "../examples/rootfs/x8664_linux", verbose=QL_VERBOSE.OFF)
|
||||
history = History(ql)
|
||||
ql.run(end=0x55555555465a)
|
||||
self.ql.run(end=0x55555555465a)
|
||||
|
||||
non_libc_blocks = history.get_ins_exclude_lib(".*libc.so.*")
|
||||
non_libc_blocks = self.history.get_ins_exclude_lib(self.P_LIBC)
|
||||
self.assertGreater(len(non_libc_blocks), 0)
|
||||
|
||||
# this test is going to take a while but oh well
|
||||
# also assumes that the get_mem_map_from_addr function works
|
||||
for block in non_libc_blocks:
|
||||
map_for_ins = history.get_mem_map_from_addr(block)
|
||||
self.assertNotRegex(map_for_ins[3], ".*libc.so.*")
|
||||
map_for_ins = self.history.get_mem_map_from_addr(block)
|
||||
|
||||
assert len(non_libc_blocks) > 0
|
||||
self.assertIsNotNone(map_for_ins)
|
||||
self.assertNotRegex(map_for_ins[3], self.P_LIBC)
|
||||
|
||||
non_libc_blocks_and_ld = history.get_ins_exclude_lib([".*libc.so.*", "ld-linux.*"])
|
||||
non_libc_blocks_and_ld = self.history.get_ins_exclude_lib([self.P_LIBC, self.P_LD])
|
||||
self.assertGreater(len(non_libc_blocks_and_ld), 0)
|
||||
|
||||
for block in non_libc_blocks_and_ld:
|
||||
map_for_ins = history.get_mem_map_from_addr(block)
|
||||
self.assertNotRegex(map_for_ins[3], ".*libc.so.*|ld-linux.*")
|
||||
|
||||
assert len(non_libc_blocks_and_ld) > 0
|
||||
map_for_ins = self.history.get_mem_map_from_addr(block)
|
||||
|
||||
self.assertIsNotNone(map_for_ins)
|
||||
self.assertNotRegex(map_for_ins[3], '|'.join((self.P_LIBC, self.P_LD)))
|
||||
|
||||
def test_get_ins_only_lib(self):
|
||||
ql = Qiling(["../examples/rootfs/x8664_linux/bin/x8664_hello"], "../examples/rootfs/x8664_linux", verbose=QL_VERBOSE.OFF)
|
||||
history = History(ql)
|
||||
ql.run(end=0x55555555465a)
|
||||
self.ql.run(end=0x55555555465a)
|
||||
|
||||
non_libc_blocks = history.get_ins_only_lib(".*libc.so.*")
|
||||
non_libc_blocks = self.history.get_ins_only_lib(self.P_LIBC)
|
||||
self.assertGreater(len(non_libc_blocks), 0)
|
||||
|
||||
# this test is going to take a while but oh well
|
||||
# also assumes that the get_mem_map_from_addr function works
|
||||
for block in non_libc_blocks:
|
||||
map_for_ins = history.get_mem_map_from_addr(block)
|
||||
self.assertRegex(map_for_ins[3], ".*libc.so.*")
|
||||
map_for_ins = self.history.get_mem_map_from_addr(block)
|
||||
|
||||
assert len(non_libc_blocks) > 0
|
||||
self.assertIsNotNone(map_for_ins)
|
||||
self.assertRegex(map_for_ins[3], self.P_LIBC)
|
||||
|
||||
non_libc_blocks_and_ld = history.get_ins_only_lib([".*libc.so.*", "ld-linux.*"])
|
||||
non_libc_blocks_and_ld = self.history.get_ins_only_lib([self.P_LIBC, self.P_LD])
|
||||
self.assertGreater(len(non_libc_blocks_and_ld), 0)
|
||||
|
||||
for block in non_libc_blocks_and_ld :
|
||||
map_for_ins = history.get_mem_map_from_addr(block)
|
||||
self.assertRegex(map_for_ins[3], ".*libc.so.*|.*ld-linux.*")
|
||||
for block in non_libc_blocks_and_ld:
|
||||
map_for_ins = self.history.get_mem_map_from_addr(block)
|
||||
|
||||
self.assertIsNotNone(map_for_ins)
|
||||
self.assertRegex(map_for_ins[3], '|'.join((self.P_LIBC, self.P_LD)))
|
||||
|
||||
assert len(non_libc_blocks_and_ld) > 0
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
unittest.main()
|
||||
|
||||
+26
-29
@@ -5,50 +5,47 @@
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import re
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
|
||||
class Qltool_Test(unittest.TestCase):
|
||||
def test_qltool_exec_args(self):
|
||||
create = [sys.executable, '../qltool', 'run', '-f', '../examples/rootfs/x8664_linux/bin/x8664_args', '--rootfs', '../examples/rootfs/x8664_linux', '--verbose', 'off', '--args', 'test1', 'test2' ,'test3']
|
||||
p = subprocess.Popen(create, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||
for line in iter(p.stdout.readline, b''):
|
||||
self.stdout = line
|
||||
def __run(self, cmdline: str) -> bytes:
|
||||
try:
|
||||
output = subprocess.check_output([sys.executable] + cmdline.split(), stderr=subprocess.STDOUT)
|
||||
except subprocess.CalledProcessError as e:
|
||||
raise RuntimeError(f"command '{e.cmd}' return with error (code {e.returncode}): {e.output}")
|
||||
else:
|
||||
return output
|
||||
|
||||
self.assertEqual(b'arg 2 test3\n', self.stdout)
|
||||
def test_qltool_exec_args(self):
|
||||
output = self.__run(r'../qltool run -f ../examples/rootfs/x8664_linux/bin/x8664_args --rootfs ../examples/rootfs/x8664_linux --verbose off --args test1 test2 test3')
|
||||
|
||||
self.assertEqual(b'arg 2 test3', output.splitlines()[-1])
|
||||
|
||||
def test_qltool_shellcode(self):
|
||||
create = [sys.executable, '../qltool', 'code', '--os','linux','--arch', 'x86', '--format', 'asm', '-f', '../examples/shellcodes/lin32_execve.asm']
|
||||
try:
|
||||
subprocess.check_output(create,stderr=subprocess.STDOUT)
|
||||
except subprocess.CalledProcessError as e:
|
||||
raise RuntimeError("command '{}' return with error (code {}): {}".format(e.cmd, e.returncode, e.output))
|
||||
self.__run(r'../qltool code --os linux --arch x86 --format asm -f ../examples/shellcodes/lin32_execve.asm')
|
||||
|
||||
def test_qltool_coverage(self):
|
||||
os.makedirs("./log_test", exist_ok=True)
|
||||
create = [sys.executable, '../qltool', 'run', '-f','../examples/rootfs/x8664_efi/bin/TcgPlatformSetupPolicy','--rootfs', '../examples/rootfs/x8664_efi','--coverage-format', 'drcov', '--coverage-file', 'log_test/TcgPlatformSetupPolicy']
|
||||
try:
|
||||
subprocess.check_output(create, stderr=subprocess.STDOUT)
|
||||
except subprocess.CalledProcessError as e:
|
||||
raise RuntimeError("command '{}' return with error (code {}): {}".format(e.cmd, e.returncode, e.output))
|
||||
os.makedirs(r'./log_test', exist_ok=True)
|
||||
|
||||
self.__run(r'../qltool run -f ../examples/rootfs/x8664_efi/bin/TcgPlatformSetupPolicy --rootfs ../examples/rootfs/x8664_efi --coverage-format drcov --coverage-file log_test/TcgPlatformSetupPolicy')
|
||||
|
||||
def test_qltool_json(self):
|
||||
create = [sys.executable, '../qltool', 'run', '-f','../examples/rootfs/x86_linux/bin/x86_hello','--rootfs', '../examples/rootfs/x86_linux','--verbose', 'off', '--json']
|
||||
try:
|
||||
subprocess.check_output(create, stderr=subprocess.STDOUT)
|
||||
except subprocess.CalledProcessError as e:
|
||||
raise RuntimeError("command '{}' return with error (code {}): {}".format(e.cmd, e.returncode, e.output))
|
||||
self.__run(r'../qltool run -f ../examples/rootfs/x86_linux/bin/x86_hello --rootfs ../examples/rootfs/x86_linux --verbose off --json')
|
||||
|
||||
def test_qltool_filter(self):
|
||||
create = [sys.executable, '../qltool', 'run', '-f', '../examples/rootfs/arm_linux/bin/arm_hello', '--rootfs', '../examples/rootfs/arm_linux', '-e', '^(open|brk)', '--log-plain']
|
||||
try:
|
||||
output = subprocess.check_output(create, stderr=subprocess.STDOUT)
|
||||
except subprocess.CalledProcessError as e:
|
||||
raise RuntimeError("command '{}' return with error (code {}): {}".format(e.cmd, e.returncode, e.output))
|
||||
filter_pattern = r'^(open|brk)'
|
||||
output = self.__run(fr'../qltool run -f ../examples/rootfs/arm_linux/bin/arm_hello --rootfs ../examples/rootfs/arm_linux -e {filter_pattern} --log-plain')
|
||||
|
||||
lines = [ line.strip('[=]\t') for line in output.decode().split("\n")]
|
||||
self.assertTrue(all(filter(lambda x: x.startswith("open") or x.startswith("brk"), lines)))
|
||||
# keep only log entries and strip them from log prefix
|
||||
p = re.compile(rb'^\[\S\]\s+')
|
||||
log_entries = (p.sub(b'', line) for line in output.splitlines() if p.match(line))
|
||||
|
||||
# make sure that all log entries are of the expected regex filter pattern
|
||||
p = re.compile(filter_pattern.encode())
|
||||
self.assertTrue(all(p.search(entry) is not None for entry in log_entries))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
+143
-88
@@ -3,17 +3,19 @@
|
||||
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
|
||||
#
|
||||
|
||||
import sys, unittest
|
||||
import ctypes
|
||||
import unittest
|
||||
|
||||
from typing import Optional
|
||||
|
||||
import sys
|
||||
sys.path.append("..")
|
||||
|
||||
import ctypes
|
||||
|
||||
from qiling import Qiling
|
||||
from qiling.const import QL_ARCH, QL_OS
|
||||
from qiling.os.struct import BaseStruct
|
||||
|
||||
|
||||
class DummyInternalStruct(BaseStruct):
|
||||
_fields_ = [
|
||||
('X', ctypes.c_uint32)
|
||||
@@ -35,125 +37,178 @@ class DummyStruct(BaseStruct):
|
||||
|
||||
# we only need context and not going to run anything anyway, so just use whatever
|
||||
NOPSLED = b'\x90' * 8
|
||||
ROOTFS = r'../examples/rootfs/x8664_linux'
|
||||
|
||||
class StructTest(unittest.TestCase):
|
||||
|
||||
def setUp(self) -> None:
|
||||
ql = Qiling(code=NOPSLED, rootfs=ROOTFS, archtype=QL_ARCH.X8664, ostype=QL_OS.LINUX)
|
||||
|
||||
self.ptr = 0x100000
|
||||
self.mem = ql.mem
|
||||
|
||||
self.expected = {
|
||||
'A' : 0xdeadface,
|
||||
'B' : 0x1020304050607080,
|
||||
'C' : DummyInternalStruct(0x11213141),
|
||||
'D' : b'Hello World!',
|
||||
}
|
||||
|
||||
# create a dummy structure with expected values
|
||||
dummy = DummyStruct(**self.expected)
|
||||
|
||||
# emit dummy structure to memory
|
||||
ql.mem.map(self.ptr, ql.mem.align_up(dummy.sizeof()))
|
||||
ql.mem.write(self.ptr, bytes(dummy))
|
||||
|
||||
|
||||
def __read_data(self, offset: int = 0, size: Optional[int] = None) -> bytearray:
|
||||
return self.mem.read(self.ptr + offset, size or DummyStruct.sizeof())
|
||||
class Wrapper:
|
||||
class StructTest(unittest.TestCase):
|
||||
OSTYPE: QL_OS
|
||||
ROOTFS: str
|
||||
|
||||
def setUp(self) -> None:
|
||||
ql = Qiling(code=NOPSLED, rootfs=self.ROOTFS, archtype=QL_ARCH.X8664, ostype=self.OSTYPE)
|
||||
|
||||
def __write_data(self, offset: int, data: bytes) -> None:
|
||||
self.mem.write(self.ptr + offset, data)
|
||||
self.ptr = 0x100000
|
||||
self.mem = ql.mem
|
||||
|
||||
self.expected = {
|
||||
'A': 0xdeadface,
|
||||
'B': 0x1020304050607080,
|
||||
'C': DummyInternalStruct(0x11213141),
|
||||
'D': b'Hello World!',
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def __to_uint(data: bytearray) -> int:
|
||||
return int.from_bytes(data, 'little', signed=False)
|
||||
# create a dummy structure with expected values
|
||||
dummy = DummyStruct(**self.expected)
|
||||
|
||||
# emit dummy structure to memory
|
||||
ql.mem.map(self.ptr, ql.mem.align_up(dummy.sizeof()))
|
||||
ql.mem.write(self.ptr, bytes(dummy))
|
||||
|
||||
def test_load_from(self):
|
||||
dummy = DummyStruct.load_from(self.mem, self.ptr)
|
||||
def __read_data(self, offset: int = 0, size: Optional[int] = None) -> bytearray:
|
||||
return self.mem.read(self.ptr + offset, size or DummyStruct.sizeof())
|
||||
|
||||
self.assertEqual(self.expected['A'], dummy.A)
|
||||
self.assertEqual(self.expected['B'], dummy.B)
|
||||
self.assertEqual(self.expected['C'], dummy.C)
|
||||
self.assertEqual(self.expected['D'], dummy.D)
|
||||
def __write_data(self, offset: int, data: bytes) -> None:
|
||||
self.mem.write(self.ptr + offset, data)
|
||||
|
||||
@staticmethod
|
||||
def __to_uint(data: bytearray) -> int:
|
||||
return int.from_bytes(data, 'little', signed=False)
|
||||
|
||||
def test_save_to(self):
|
||||
dummy = DummyStruct(
|
||||
A=0x0c0a0f0e,
|
||||
B=0x1828384858687888,
|
||||
C=DummyInternalStruct(0x19293949),
|
||||
D=b'Goodbye World!'
|
||||
)
|
||||
def test_load_from(self):
|
||||
dummy = DummyStruct.load_from(self.mem, self.ptr)
|
||||
|
||||
dummy.save_to(self.mem, self.ptr)
|
||||
self.assertEqual(self.expected['A'], dummy.A)
|
||||
self.assertEqual(self.expected['B'], dummy.B)
|
||||
self.assertEqual(self.expected['C'], dummy.C)
|
||||
self.assertEqual(self.expected['D'], dummy.D)
|
||||
|
||||
obj_data = bytes(dummy)
|
||||
mem_data = self.__read_data()
|
||||
def test_save_to(self):
|
||||
dummy = DummyStruct(
|
||||
A=0x0c0a0f0e,
|
||||
B=0x1828384858687888,
|
||||
C=DummyInternalStruct(0x19293949),
|
||||
D=b'Goodbye World!'
|
||||
)
|
||||
|
||||
self.assertEqual(obj_data, mem_data)
|
||||
dummy.save_to(self.mem, self.ptr)
|
||||
|
||||
def test_ref_discard(self):
|
||||
data_before = self.__read_data()
|
||||
obj_data = bytes(dummy)
|
||||
mem_data = self.__read_data()
|
||||
|
||||
unused = []
|
||||
with DummyStruct.ref(self.mem, self.ptr) as dummy:
|
||||
print(f'B = {dummy.B:#x}')
|
||||
print(f'C = {dummy.C}')
|
||||
self.assertEqual(obj_data, mem_data)
|
||||
|
||||
unused.append(dummy.A + 1337)
|
||||
def test_ref_discard(self):
|
||||
data_before = self.__read_data()
|
||||
|
||||
data_after = self.__read_data()
|
||||
unused = []
|
||||
with DummyStruct.ref(self.mem, self.ptr) as dummy:
|
||||
print(f'B = {dummy.B:#x}')
|
||||
print(f'C = {dummy.C}')
|
||||
|
||||
self.assertEqual(data_before, data_after)
|
||||
unused.append(dummy.A + 1337)
|
||||
|
||||
def test_ref_save(self):
|
||||
expected = 0x10303070
|
||||
data_after = self.__read_data()
|
||||
|
||||
with DummyStruct.ref(self.mem, self.ptr) as dummy:
|
||||
print(f'B = {dummy.B:#x}')
|
||||
print(f'C = {dummy.C}')
|
||||
self.assertEqual(data_before, data_after)
|
||||
|
||||
def test_ref_save(self):
|
||||
expected = 0x10303070
|
||||
|
||||
with DummyStruct.ref(self.mem, self.ptr) as dummy:
|
||||
print(f'B = {dummy.B:#x}')
|
||||
print(f'C = {dummy.C}')
|
||||
|
||||
dummy.A = expected
|
||||
|
||||
data = self.__read_data(DummyStruct.offsetof('A'), 4)
|
||||
self.assertEqual(expected, Wrapper.StructTest.__to_uint(data))
|
||||
|
||||
def test_ref_save_internal(self):
|
||||
expected = 0x16363676
|
||||
|
||||
with DummyStruct.ref(self.mem, self.ptr) as dummy:
|
||||
dummy.C.X = expected
|
||||
|
||||
data = self.__read_data(DummyStruct.offsetof('C') + DummyInternalStruct.offsetof('X'), 4)
|
||||
self.assertEqual(expected, Wrapper.StructTest.__to_uint(data))
|
||||
|
||||
def test_volatile_ref(self):
|
||||
dummy = DummyStruct.volatile_ref(self.mem, self.ptr)
|
||||
|
||||
expected = 0x01030307
|
||||
dummy.A = expected
|
||||
data = self.__read_data(DummyStruct.offsetof('A'), 4)
|
||||
self.assertEqual(expected, Wrapper.StructTest.__to_uint(data))
|
||||
|
||||
data = self.__read_data(DummyStruct.offsetof('A'), 4)
|
||||
self.assertEqual(expected, StructTest.__to_uint(data))
|
||||
self.assertEqual(self.expected['B'], dummy.B)
|
||||
self.assertEqual(self.expected['C'], dummy.C)
|
||||
|
||||
def test_ref_save_internal(self):
|
||||
expected = 0x16363676
|
||||
expected = b'Volatility Test!'
|
||||
self.__write_data(DummyStruct.offsetof('D'), expected)
|
||||
self.assertEqual(expected, dummy.D)
|
||||
|
||||
with DummyStruct.ref(self.mem, self.ptr) as dummy:
|
||||
def test_volatile_ref_internal(self):
|
||||
dummy = DummyStruct.volatile_ref(self.mem, self.ptr)
|
||||
|
||||
expected = 0x51535357
|
||||
dummy.C.X = expected
|
||||
data = self.__read_data(DummyStruct.offsetof('C') + DummyInternalStruct.offsetof('X'), 4)
|
||||
self.assertEqual(expected, Wrapper.StructTest.__to_uint(data))
|
||||
|
||||
data = self.__read_data(DummyStruct.offsetof('C') + DummyInternalStruct.offsetof('X'), 4)
|
||||
self.assertEqual(expected, StructTest.__to_uint(data))
|
||||
# BUG: ctype.c_wchar size differs between python for windows and linux.on windows it has the
|
||||
# size of 2 as expected (same as the 'utf-16le' encoding), where on linux the size is 4 (same
|
||||
# as the 'utf-32' encoding), which expands the size of the field and doesn't provide the
|
||||
# expected outcome.
|
||||
#
|
||||
# currently wchar is used only in windows structres, which makes it problematic to emulate
|
||||
# windows programs on top of a linux host. until we implement our own wchar which depends on
|
||||
# the emualted os rather than the hosting os, the following tests are conidered broken.
|
||||
|
||||
def test_volatile_ref(self):
|
||||
dummy = DummyStruct.volatile_ref(self.mem, self.ptr)
|
||||
class WCharTest(unittest.TestCase):
|
||||
WCHAR_ENC: str
|
||||
|
||||
expected = 0x01030307
|
||||
dummy.A = expected
|
||||
data = self.__read_data(DummyStruct.offsetof('A'), 4)
|
||||
self.assertEqual(expected, StructTest.__to_uint(data))
|
||||
@unittest.skip('broken wchar implementation')
|
||||
def test_wchar(self):
|
||||
wchar_enc = self.WCHAR_ENC
|
||||
wchar_size = len('\x00'.encode(wchar_enc))
|
||||
|
||||
self.assertEqual(self.expected['B'], dummy.B)
|
||||
self.assertEqual(self.expected['C'], dummy.C)
|
||||
field_type = ctypes.c_wchar # TODO: replace ctypes.c_wchar with an alternate implementation
|
||||
field_data = 'A'
|
||||
self.assertEqual(len(field_data) * wchar_size, ctypes.sizeof(field_type))
|
||||
|
||||
expected = b'Volatility Test!'
|
||||
self.__write_data(DummyStruct.offsetof('D'), expected)
|
||||
self.assertEqual(expected, dummy.D)
|
||||
obj = field_type(field_data)
|
||||
self.assertEqual(field_data, obj.value)
|
||||
|
||||
def test_volatile_ref_internal(self):
|
||||
dummy = DummyStruct.volatile_ref(self.mem, self.ptr)
|
||||
obj_bytes = bytes(obj)
|
||||
self.assertEqual(field_data.encode(wchar_enc), obj_bytes)
|
||||
|
||||
expected = 0x51535357
|
||||
dummy.C.X = expected
|
||||
data = self.__read_data(DummyStruct.offsetof('C') + DummyInternalStruct.offsetof('X'), 4)
|
||||
self.assertEqual(expected, StructTest.__to_uint(data))
|
||||
@unittest.skip('broken wchar implementation')
|
||||
def test_wchar_array(self):
|
||||
wchar_enc = self.WCHAR_ENC
|
||||
wchar_size = len('\x00'.encode(wchar_enc))
|
||||
|
||||
field_elems = 16
|
||||
field_type = ctypes.c_wchar * field_elems # TODO: replace ctypes.c_wchar with an alternate implementation
|
||||
field_data = 'unicode sucks'
|
||||
self.assertEqual(field_elems * wchar_size, ctypes.sizeof(field_type))
|
||||
|
||||
obj = field_type(*field_data)
|
||||
self.assertEqual(field_data, obj.value)
|
||||
|
||||
obj_bytes = bytes(obj)
|
||||
self.assertEqual(field_data.ljust(field_elems, '\x00').encode(wchar_enc), obj_bytes)
|
||||
|
||||
|
||||
class StructTestLinux(Wrapper.StructTest, Wrapper.WCharTest):
|
||||
OSTYPE = QL_OS.LINUX
|
||||
ROOTFS = r'../examples/rootfs/x8664_linux'
|
||||
WCHAR_ENC = 'utf-32le'
|
||||
|
||||
|
||||
class StructTestWindows(Wrapper.StructTest, Wrapper.WCharTest):
|
||||
OSTYPE = QL_OS.WINDOWS
|
||||
ROOTFS = r'../examples/rootfs/x8664_windows'
|
||||
WCHAR_ENC = 'utf-16le'
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user