Merge pull request #1623 from qilingframework/dev

Preparing 1.4.9
This commit is contained in:
xwings
2026-04-28 15:54:29 +08:00
committed by GitHub
145 changed files with 9048 additions and 3527 deletions
+8 -9
View File
@@ -10,11 +10,11 @@ jobs:
fail-fast: false
matrix:
#os: [windows-2019, macos-10.15, ubuntu-18.04, ubuntu-20.04]
os: [windows-latest, ubuntu-22.04]
python-version: ["3.9", "3.11"]
os: [windows-latest, ubuntu-latest]
python-version: ["3.11", "3.13"]
include:
- os: ubuntu-22.04
python-version: 3.9
- os: ubuntu-latest
python-version: "3.13"
container: Docker
steps:
@@ -72,13 +72,12 @@ jobs:
cd ../qiling
cd ../examples/rootfs/x86_linux/kernel && unzip -P infected m0hamed_rootkit.ko.zip
cd ../../../../
pip3 install -e .[RE]
if [ ${{ matrix.os }} == 'ubuntu-18.04' ] and [ ${{ matrix.python-version }} == '3.9' ]; then
pip3 install -e .
pip3 install poetry
if [ ${{ matrix.contrainer }} != "" ]; then
docker run -it --rm -v ${GITHUB_WORKSPACE}:/qiling qilingframework/qiling:dev bash -c "cd tests && ./test_onlinux.sh"
else
pip3 install poetry
cd tests && ./test_onlinux.sh
cd tests && ./test_onlinux.sh
fi
# - name: mac run tests
+6
View File
@@ -70,3 +70,9 @@ poetry.toml
# LSP config files
pyrightconfig.json
# AI
CLAUDE.md
.claude
.codex
.AGENT.md
+427
View File
@@ -0,0 +1,427 @@
# Qiling Framework Architecture
Qiling is a cross-platform, multi-architecture binary emulation framework built on top of the [Unicorn](https://www.unicorn-engine.org/) CPU emulation engine. It adds OS-level abstractions (syscalls, file systems, loaders) on top of raw CPU emulation, enabling full binary execution without native hardware.
## High-Level Overview
```
┌──────────────────────────────────────────────────┐
│ User Script / qltool │
├──────────────────────────────────────────────────┤
│ Qiling Core (core.py) │
│ hooks · state snapshots · patches │
├────────────┬─────────────┬───────────────────────┤
│ OS Layer │ Loader │ Memory Manager │
│ (QlOs) │ (QlLoader) │ (QlMemoryManager) │
│ syscalls │ ELF/PE/ │ map · read · write │
│ APIs │ MachO/etc │ MMIO callbacks │
├────────────┴─────────────┴───────────────────────┤
│ Architecture Layer (QlArch) │
│ registers · disassembly · calling conventions │
├──────────────────────────────────────────────────┤
│ Unicorn Engine (CPU) │
│ instruction-level emulation │
└──────────────────────────────────────────────────┘
```
## Directory Structure
```
qiling/
├── qiling/ # Core framework package
│ ├── core.py # Qiling class — main entry point and orchestrator
│ ├── core_hooks.py # Hook system (code, memory, interrupt, address hooks)
│ ├── core_hooks_types.py # Hook type definitions and dispatch
│ ├── core_struct.py # Endian-aware struct packing utilities
│ ├── const.py # Enumerations: QL_ARCH, QL_OS, QL_ENDIAN, etc.
│ ├── exception.py # Custom exception hierarchy
│ ├── utils.py # Component selection (select_arch, select_os, etc.)
│ ├── host.py # Host platform interface
│ ├── log.py # Logging configuration
│ │
│ ├── arch/ # Architecture implementations
│ │ ├── arch.py # QlArch — abstract base class
│ │ ├── x86.py # x86 / x86-64 / 8086
│ │ ├── arm.py # ARMv7 (32-bit)
│ │ ├── arm64.py # ARMv8 (64-bit)
│ │ ├── mips.py # MIPS32
│ │ ├── riscv.py # RISC-V 32-bit
│ │ ├── riscv64.py # RISC-V 64-bit
│ │ ├── ppc.py # PowerPC 32-bit
│ │ ├── cortex_m.py # ARM Cortex-M (MCU)
│ │ ├── register.py # Register management
│ │ └── models.py # CPU model definitions
│ │
│ ├── os/ # Operating system implementations
│ │ ├── os.py # QlOs — abstract base class
│ │ ├── memory.py # QlMemoryManager
│ │ ├── fcall.py # Function call interface (read params, set return)
│ │ ├── mapper.py # Syscall/API mapping
│ │ ├── path.py # Virtual filesystem path resolution
│ │ ├── filestruct.py # File descriptor abstraction
│ │ ├── thread.py # Threading primitives
│ │ ├── posix/ # POSIX shared layer (syscall handlers)
│ │ ├── linux/ # Linux-specific OS
│ │ ├── freebsd/ # FreeBSD-specific OS
│ │ ├── macos/ # macOS-specific OS
│ │ ├── qnx/ # QNX RTOS
│ │ ├── windows/ # Windows (Win32/Win64 API emulation)
│ │ ├── uefi/ # UEFI firmware services
│ │ ├── dos/ # DOS (8086 interrupts)
│ │ ├── mcu/ # Bare-metal microcontroller
│ │ └── blob/ # Raw binary blob execution
│ │
│ ├── loader/ # Binary format loaders
│ │ ├── loader.py # QlLoader — abstract base class
│ │ ├── elf.py # ELF (Linux, FreeBSD, QNX)
│ │ ├── pe.py # PE (Windows)
│ │ ├── pe_uefi.py # PE for UEFI
│ │ ├── macho.py # Mach-O (macOS)
│ │ ├── dos.py # DOS COM/EXE
│ │ ├── mcu.py # MCU firmware images
│ │ └── blob.py # Raw binary blobs
│ │
│ ├── cc/ # Calling conventions
│ │ ├── intel.py # cdecl, stdcall, ms64
│ │ ├── arm.py # aarch32, aarch64
│ │ ├── mips.py # MIPS o32
│ │ ├── riscv.py # RISC-V ABI
│ │ └── ppc.py # PowerPC ABI
│ │
│ ├── hw/ # Hardware peripheral emulation (MCU)
│ │ ├── peripheral.py # Base peripheral class
│ │ ├── hw.py # Hardware manager
│ │ ├── gpio/ # GPIO pins and interrupts
│ │ ├── timer/ # Timers, PWM, counters
│ │ ├── char/ # UART serial
│ │ ├── spi/ # SPI bus
│ │ ├── i2c/ # I2C bus
│ │ ├── net/ # Network interfaces
│ │ ├── analog/ # ADC/DAC
│ │ ├── intc/ # Interrupt controllers
│ │ ├── flash/ # Flash memory
│ │ ├── dma/ # DMA controllers
│ │ └── ... # Power, SD, misc peripherals
│ │
│ ├── debugger/ # Debugger subsystem
│ │ ├── gdb/ # GDB remote protocol server
│ │ └── qdb/ # Qiling native debugger (with reverse debugging)
│ │
│ ├── extensions/ # Optional extensions
│ │ ├── multitask.py # gevent-based multithreading
│ │ ├── trace.py # Instruction tracing
│ │ ├── coverage/ # Code coverage collection
│ │ ├── sanitizers/ # Memory sanitizers
│ │ ├── afl/ # AFL fuzzer integration
│ │ ├── r2/ # Radare2 integration
│ │ └── idaplugin/ # IDA Pro plugin
│ │
│ └── profiles/ # Default OS configuration files (.ql)
│ ├── linux.ql # Stack/heap addresses, kernel params
│ ├── windows.ql
│ ├── macos.ql
│ └── ...
│
├── qltool # CLI tool for running binaries
├── qltui.py # TUI interface
├── examples/ # Usage examples and sample scripts
├── tests/ # Test suite
└── docs/ # Documentation
```
## Core Components
### `Qiling` (core.py)
The central class. Every emulation session creates one `Qiling` instance that owns and wires together all other components:
```python
ql = Qiling(
argv=["/path/to/binary", "arg1"], # binary + arguments
rootfs="/path/to/rootfs", # virtual filesystem root
ostype=QL_OS.LINUX, # target OS (auto-detected if omitted)
archtype=QL_ARCH.X8664, # target arch (auto-detected if omitted)
)
```
`Qiling` inherits from `QlCoreHooks` (hook management) and `QlCoreStructs` (endian-aware packing). Key properties:
| Property | Type | Description |
|----------|------|-------------|
| `ql.arch` | `QlArch` | CPU architecture — registers, disassembly |
| `ql.os` | `QlOs` | Operating system — syscalls, I/O, APIs |
| `ql.loader` | `QlLoader` | Binary loader — parses and maps the executable |
| `ql.mem` | `QlMemoryManager` | Memory — map, read, write, MMIO |
| `ql.uc` | `unicorn.Uc` | Underlying Unicorn engine instance |
### Architecture Layer (`qiling/arch/`)
`QlArch` is the abstract base. Each architecture subclass configures:
- **Unicorn engine** mode and architecture constants
- **Register** access via `ql.arch.regs` (read/write by name)
- **Disassembler** (Capstone) and **assembler** (Keystone)
- **Stack operations** — push, pop, pointer-width-aware
- **Endianness** and **bit width** (16/32/64)
Supported: x86, x86-64, 8086, ARM, ARM64, MIPS, RISC-V (32/64), PowerPC, Cortex-M.
### OS Layer (`qiling/os/`)
`QlOs` is the abstract base. Each OS subclass provides:
- **Syscall/interrupt dispatch** — routes CPU interrupts to handler functions
- **I/O streams** — `stdin`, `stdout`, `stderr` (interceptable)
- **Virtual filesystem** — path mapping through `rootfs`
- **Function call interface** (`ql.os.fcall`) — read params, set return values
- **API interception** — `set_api()` for hooking library functions
**POSIX subsystem** (`os/posix/`): Shared syscall implementation for Linux, FreeBSD, macOS, and QNX. Individual syscall handlers live under `os/posix/syscall/`.
**Windows** (`os/windows/`): Emulates Win32/Win64 API by hooking DLL imports. Includes registry, thread, handle, and fiber support.
**UEFI** (`os/uefi/`): Emulates UEFI Boot Services, Runtime Services, and SMM. Uses a GUID database and protocol framework.
### Loader Layer (`qiling/loader/`)
`QlLoader` is the abstract base. Loaders parse a binary format, map segments into memory, resolve symbols, load dependencies, and set initial CPU state (PC, SP).
| Loader | Format | Used By |
|--------|--------|---------|
| `QlLoaderELF` | ELF | Linux, FreeBSD, QNX |
| `QlLoaderPE` | PE/COFF | Windows, UEFI |
| `QlLoaderMacho` | Mach-O | macOS |
| `QlLoaderDOS` | COM/EXE | DOS |
| `QlLoaderMCU` | Firmware | Cortex-M MCU |
| `QlLoaderBlob` | Raw bytes | Shellcode / blob |
### Memory Manager (`qiling/os/memory.py`)
Wraps Unicorn's memory model with higher-level operations:
- `map(addr, size, perms)` / `unmap(addr, size)` — region management
- `read(addr, size)` / `write(addr, data)` — data access
- `read_ptr(addr)` / `write_ptr(addr, val)` — pointer-width-aware access
- `read_cstring(addr)` — null-terminated string read
- MMIO callback support for memory-mapped peripherals
### Calling Conventions (`qiling/cc/`)
Each architecture has calling convention classes that abstract argument passing and return values. The `QlOs.fcall` interface uses these to provide a uniform way to read function parameters regardless of platform.
## Execution Flow
### 1. Initialization (`Qiling.__init__`)
```
Qiling(argv, rootfs)
│
├─ Detect arch/OS from binary headers (ql_guess_emu_env)
│ ELF magic → parse e_machine, OSABI
│ PE magic → parse Machine, Subsystem
│ MachO magic → parse CPU type
│
├─ Create QlArch (select_arch) → initializes Unicorn engine
├─ Create QlLoader (select_loader)
├─ Create QlMemoryManager
├─ Create QlOs (select_os)
│
└─ loader.run()
├─ Parse binary format (headers, segments, sections)
├─ Map segments into memory
├─ Load shared libraries / DLLs
├─ Setup stack, heap, TLS, auxiliary vectors
└─ Set initial PC (entry point) and SP
```
### 2. Execution (`ql.run()`)
```
ql.run(begin, end, timeout, count)
│
├─ Apply binary patches (ql.patch)
├─ Write exit trap (guard address)
│
└─ os.run()
└─ uc.emu_start(entry_point, exit_point)
│
├─ Unicorn executes instructions
│
├─ Hooks fire on:
│ ├─ Every instruction (hook_code)
│ ├─ Basic blocks (hook_block)
│ ├─ Memory access (hook_mem_read/write)
│ ├─ Interrupts (hook_intno) → syscall dispatch
│ ├─ Specific addresses (hook_address)
│ └─ Specific instructions (hook_insn)
│
└─ Stops when PC reaches exit point, timeout,
ql.emu_stop(), or unhandled exception
```
### 3. Syscall Handling
When the emulated binary issues a syscall (via `int 0x80`, `syscall`, `svc`, etc.):
```
CPU interrupt/instruction
→ Unicorn interrupt hook
→ QlOs syscall dispatcher
→ Look up handler by syscall number
→ Handler reads args via calling convention
→ Emulates syscall behavior
→ Sets return value in registers
```
## Component Selection
Components are selected dynamically at runtime based on `QL_ARCH` and `QL_OS` enums. The `qiling/utils.py` module provides:
- `select_arch(archtype)` → architecture class
- `select_os(ostype)` → OS class
- `select_loader(ostype)` → loader class
- `select_debugger(options)` → debugger class
This makes it possible to support diverse platform combinations from a unified codebase.
## Hook System
The hook system (`core_hooks.py`) wraps Unicorn's callback mechanism:
| Hook Type | Trigger |
|-----------|---------|
| `hook_code` | Every instruction (optionally within address range) |
| `hook_block` | Every basic block entry |
| `hook_address` | Specific address reached |
| `hook_intno` | CPU interrupt/exception |
| `hook_insn` | Specific instruction type (e.g., `syscall`) |
| `hook_mem_read` | Memory read |
| `hook_mem_write` | Memory write |
| `hook_mem_invalid` | Invalid memory access |
Hooks can be scoped to address ranges and return `QL_HOOK_BLOCK` to suppress further hooks in the chain.
## Key Extension Points
- **Custom syscall handlers** — replace or extend any syscall
- **API hooking** — `ql.os.set_api(name, callback)` to intercept library calls
- **Binary patching** — `ql.patch(offset, data)` for runtime patching
- **State snapshots** — `ql.save()` / `ql.restore()` for checkpointing
- **Debugger attachment** — GDB remote protocol or native QDB debugger
- **Coverage/tracing** — `extensions/coverage/` and `extensions/trace.py`
- **Fuzzing** — AFL integration via `extensions/afl/`
- **Hardware peripherals** — register custom MCU peripherals in `hw/`
## Dependencies
| Package | Role |
|---------|------|
| `unicorn` (2.1.3) | CPU emulation engine |
| `capstone` | Disassembly |
| `keystone-engine` | Assembly |
| `pyelftools` | ELF parsing |
| `pefile` | PE parsing |
| `python-registry` | Windows registry emulation |
| `gevent` | Cooperative multithreading |
| `pyyaml` | Configuration parsing |
Optional: `unicornafl` / `fuzzercorn` (fuzzing), `r2libr` (Radare2 integration).
## Supported Platforms
**Architectures:** x86, x86-64, 8086, ARM, ARM64, MIPS, RISC-V (32/64), PowerPC, Cortex-M
**Operating Systems:** Linux, FreeBSD, macOS, Windows, UEFI, DOS, QNX, MCU (bare-metal), Blob
## Improvement: Hybrid Kernel Architecture
> Detailed implementation plan and task tracking: [TODO.md](TODO.md)
### The Problem
Qiling reimplements Linux kernel behavior syscall-by-syscall in Python. This works
for simple operations (file I/O, memory management, stat) but fundamentally cannot
scale to the full kernel surface:
- **Networking**: No epoll. Sockets are proxied to host sockets with no isolation.
No real TCP state machine, no multicast, no raw/netlink sockets.
- **Multithreading**: Gevent greenlets are cooperative and single-threaded. No
preemption, no real concurrency. Futex is a gevent Event. Programs using pthreads,
mutexes, or condition variables don't behave correctly.
- **Signals**: `signal()`, `sigaction()`, `kill()` are mostly stubbed. No delivery,
no `EINTR`, no `SA_RESTART`.
- **Long tail**: capabilities, cgroups, namespaces, io_uring, seccomp, eBPF — the
kernel API surface is vast and growing.
### The Solution
A **hybrid architecture** that keeps Unicorn for CPU emulation and Qiling for
instrumentation, but offloads complex kernel subsystems to a real Linux kernel via
a **kernel proxy** helper process. Simple syscalls stay emulated in Python.
```
Syscall interrupt
→ load_syscall() [UNCHANGED — existing dispatch in posix.py]
→ check posix_syscall_hooks[CALL]
→ proxy hook registered? → forward to kernel proxy
→ no proxy hook? → existing Python handler [UNCHANGED]
```
The user explicitly chooses which missing syscalls to forward. Nothing is automatic —
by default Qiling behaves exactly as today. The integration uses the **existing
`set_syscall()` CALL hook mechanism** (`posix.py:128-143`), so `load_syscall()` and
all existing dispatch code remain completely unchanged.
```python
proxy = KernelProxy(ql)
proxy.forward_syscall("epoll_create", returns_fd=True)
proxy.forward_syscall("epoll_ctl")
proxy.forward_syscall("epoll_wait")
ql.run()
```
Under the hood, `forward_syscall()` registers a CALL hook that serializes the
arguments and sends them to a helper process (the kernel proxy) which executes
the real syscall and returns the result. For syscalls that return FDs, the result
is wrapped in a `ql_proxy_fd` object and stored in Qiling's FD table. Since the
FD table is already polymorphic (`ql_socket`, `ql_file`, `ql_pipe`), existing
handlers like `ql_syscall_read` and `ql_syscall_close` dispatch through the proxy
FD's `.read()`/`.close()` methods automatically — no changes needed.
### Phases
| Phase | Scope | Risk | Goal |
|-------|-------|------|------|
| 0 | Proof of concept | Low | User manually forwards specific syscalls — zero existing code changed |
| 1 | Networking foundation | Low-Med | Specific hooks for socket syscalls, `ql_proxy_fd`, TCP works |
| 2 | Complete networking | Medium | epoll, poll/select, network namespaces |
| 3 | Real threading | **High** | One Unicorn per thread, shared memory, real futex |
| 4 | Signals | Medium | Real signal delivery, EINTR, handler execution |
| 5 | Integration | Low | API polish, fallback, platform support, benchmarks |
Phase 0 gives users explicit control — they identify which missing syscalls to
forward and the proxy handles them. Phases 1-2 add pointer-aware forwarding for
networking with pre-built forwarders so users don't have to wire up each syscall.
Phase 3 (threading) is the highest-risk change and is deferred until networking is
stable. Each phase preserves backward compatibility — hybrid mode is opt-in, default
behavior is unchanged.
### Alternatives Considered
- **Run a real kernel in Unicorn**: Unicorn doesn't emulate hardware (interrupt
controllers, MMU page tables, timers). Would require rebuilding QEMU system mode.
- **ptrace-based execution**: Run natively, intercept syscalls. Fast, but no
cross-architecture support and limited instruction-level hooks.
- **User-Mode Linux (UML)**: Run the kernel as a userspace process. x86-only,
somewhat unmaintained, complex syscall bridge.
- **Auto-forward all unimplemented syscalls**: Forward every missing syscall
automatically. Convenient but unpredictable — hard to debug, may forward syscalls
that shouldn't be (security, state leaks). Explicit user control is safer.
The hybrid approach was chosen because it preserves Qiling's core value
(instrumentation + cross-arch emulation) while getting real kernel behavior where
it matters most — without modifying the existing dispatch path.
## Testing
Tests live in `tests/` and are organized by platform: `test_elf.py`, `test_pe.py`, `test_macho.py`, `test_dos.py`, `test_mcu.py`, etc. They use binaries from `examples/rootfs/` as test fixtures.
+3 -3
View File
@@ -1,4 +1,4 @@
FROM python:3-slim AS base
FROM python:3.13-slim-trixie AS base
WORKDIR /qiling
@@ -12,7 +12,7 @@ RUN apt-get update && apt-get -y upgrade && rm -rf /var/lib/apt/lists/*
FROM base AS builder
RUN apt-get update && apt-get install -y --no-install-recommends \
cmake build-essential gcc git \
cmake build-essential gcc git pkg-config \
&& rm -rf /var/lib/apt/lists/*
COPY pyproject.toml poetry.lock ./
@@ -33,7 +33,7 @@ WORKDIR /qiling
RUN apt-get update \
&& apt-get install -y --no-install-recommends unzip apt-utils \
&& rm -rf /var/lib/apt/lists/* \
&& pip3 install --no-deps --no-cache-dir dist/*.whl \
&& pip3 install --no-cache-dir dist/*.whl \
&& rm -rf ./dist/
CMD ["bash"]
+1 -1
View File
@@ -114,7 +114,7 @@ However, Qiling offers some important differences against QEMU user mode:
#### Installation
Please see [setup guide](https://docs.qiling.io/en/latest/install/) file for how to install Qiling Framework.
Please see [setup guide](https://github.com/qilingframework/qiling/wiki/Installation) file for how to install Qiling Framework.
---
-1
View File
@@ -1 +0,0 @@
Features request and TODO please refer to issue 333 https://github.com/qilingframework/qiling/issues/333
+693
View File
@@ -0,0 +1,693 @@
# Qiling Framework TODO
Features request and TODO please refer to issue 333 https://github.com/qilingframework/qiling/issues/333
---
## Hybrid Kernel Architecture
### The Problem
Qiling reimplements Linux kernel behavior syscall-by-syscall in Python. Simple
syscalls (file I/O, memory, stat) work well. Complex subsystems do not:
- **Networking**: No epoll. Sockets are host sockets with no isolation. No TCP
state machine, no multicast, no raw/netlink sockets.
- **Threading**: Gevent greenlets — cooperative, single-threaded. No preemption.
Futex is a gevent Event. pthreads don't behave correctly.
- **Signals**: `signal()`, `sigaction()`, `kill()` are stubs. No delivery, no EINTR.
Reimplementing the full kernel is not realistic. Instead, offload complex subsystems
to a real Linux kernel while keeping Qiling's instrumentation intact.
### Key Insight: Two-Layer Forwarding
There are two integration points, each serving a different purpose:
**Layer 1 — Generic fallback** (catches the long tail):
The user explicitly chooses which missing syscalls to forward. Nothing is automatic.
By default Qiling behaves exactly as it does today — if a syscall is not implemented,
it fails. The user then tells the proxy which specific syscalls to forward:
```python
ql = Qiling(argv=["/bin/myserver"], rootfs="rootfs/x8664_linux")
proxy = KernelProxy(ql) # start proxy process
proxy.forward_syscall("epoll_create") # forward this one to real kernel
proxy.forward_syscall("epoll_ctl") # and this one
proxy.forward_syscall("epoll_wait") # and this one
ql.run()
```
Under the hood, `forward_syscall("epoll_create")` registers a CALL hook via the
existing `set_syscall()` mechanism (`posix.py:128-143`). The hook reads args from
the emulated registers, sends them to the proxy process, and returns the real result.
**Zero changes to `load_syscall()` or any existing dispatch code.**
For syscalls that return FDs (socket, epoll_create, eventfd, etc.), the proxy
wraps the returned FD in a `ql_proxy_fd` object and stores it in the FD table.
The FD table (`QlFileDes`) already stores polymorphic objects — `ql_socket`,
`ql_file`, `ql_pipe` — so `ql_proxy_fd` slots in naturally. When existing handlers
like `ql_syscall_read` or `ql_syscall_close` hit a proxy FD, they dispatch through
`ql_proxy_fd.read()` / `.close()` which forwards to the proxy.
```
Syscall interrupt
→ load_syscall() [UNCHANGED]
→ has CALL hook? (user hook or proxy-registered hook)
→ yes: use it
→ has Python handler? (existing code)
→ yes: use it [unchanged — file I/O, memory, stat, etc.]
→ neither?
→ log warning [existing behavior, unchanged — no auto-forwarding]
```
### What Changes vs What Doesn't
| Component | Changes? | Notes |
|-----------|----------|-------|
| `load_syscall()` dispatch | **NO** | Entirely unchanged |
| Existing syscall handlers | NO | Python handlers stay as-is |
| `QlFileDes` FD table | NO | Already polymorphic, new FD type slots in |
| `set_syscall()` user hooks | NO | User CALL/ENTER/EXIT hooks still work |
| `ql.run()` / `ql.emu_start()` | NO | Unicorn execution loop untouched |
| Hook system (`core_hooks.py`) | NO | Standard Unicorn hook mechanism |
| New: kernel proxy process | YES | New module, new files only |
| New: `ql_proxy_fd` FD type | YES | New class, same interface as `ql_socket` |
| New: `KernelProxy` class | YES | User-facing API, registers CALL hooks |
---
## Phase 0: Proof of Concept
**Goal**: User can explicitly forward specific unimplemented syscalls to a real Linux
kernel. No automatic behavior. No changes to existing Qiling dispatch code.
### Usage
```python
from qiling import Qiling
from qiling.os.posix.kernel_proxy import KernelProxy
ql = Qiling(argv=["/bin/myserver"], rootfs="rootfs/x8664_linux")
# Start a kernel proxy — a helper process that executes real syscalls
proxy = KernelProxy(ql)
# Binary needs epoll but Qiling doesn't implement it.
# User identifies the 3 missing syscalls and forwards them:
proxy.forward_syscall("epoll_create")
proxy.forward_syscall("epoll_ctl")
proxy.forward_syscall("epoll_wait")
ql.run()
# epoll_create/ctl/wait are handled by real kernel.
# Everything else (read, write, open, mmap, ...) uses existing Qiling handlers.
```
If the user does NOT set up a proxy, Qiling behaves exactly as it does today.
No surprises, no magic.
### 0.1 Kernel proxy process
A standalone Python process that executes real Linux syscalls on behalf of Qiling.
Communicates via Unix socketpair.
- [ ] New directory: `qiling/os/posix/kernel_proxy/`
- [ ] `proxy.py` — proxy subprocess entry point
- Main loop: read request from socket → `libc.syscall(nr, *args)` → write response
- Uses `ctypes.CDLL("libc.so.6").syscall()` for raw syscall execution
- Manages its own FD table (proxy-side FDs)
- [ ] IPC protocol (binary, over socketpair):
```
Request: { type: SYSCALL, syscall_nr: u32, args: [u64; 6] }
Response: { return_value: i64, errno: i32 }
Request: { type: FD_OP, op: READ|WRITE|CLOSE, proxy_fd: i32, length: u32, data?: bytes }
Response: { return_value: i64, errno: i32, data?: bytes }
```
Two message types: raw syscall forwarding, and FD operations (for read/write/close
on proxy-owned FDs).
- [ ] Lifecycle: started by `KernelProxy.__init__()`, killed on `ql.run()` exit
### 0.2 `KernelProxy` class — user-facing API
The main integration class. Lives in `qiling/os/posix/kernel_proxy/__init__.py`.
```python
class KernelProxy:
def __init__(self, ql: Qiling):
"""Start the proxy subprocess."""
self.ql = ql
self._proxy_process = ... # start subprocess
self._ipc = ... # socketpair connection
self._forwarded = {} # syscall_name → syscall_nr
def forward_syscall(self, name: str, returns_fd: bool = False):
"""Register a CALL hook that forwards this syscall to the proxy.
Args:
name: syscall name (e.g. "epoll_create", "eventfd")
returns_fd: if True, wrap the return value in ql_proxy_fd
and store in the FD table
"""
# Look up syscall number from the architecture's syscall table
# Register a CALL hook via ql.os.set_syscall()
ql.os.set_syscall(name, self._make_forwarder(name, returns_fd))
def _make_forwarder(self, name, returns_fd):
"""Create a CALL hook function for this syscall."""
syscall_nr = self._resolve_syscall_nr(name)
def forwarder(ql, *args):
# Send all args as raw integers to proxy
retval = self._ipc.forward(syscall_nr, args)
if returns_fd and retval >= 0:
# Wrap proxy FD and store in Qiling's FD table
proxy_fd = ql_proxy_fd(self._ipc, retval)
guest_fd = self._alloc_fd(ql, proxy_fd)
return guest_fd
return retval
return forwarder
```
Key points:
- `forward_syscall()` uses the existing `set_syscall()` mechanism — a standard
CALL hook. No special dispatch path, no changes to `load_syscall()`.
- User explicitly passes `returns_fd=True` for FD-returning syscalls. No heuristics.
- User ENTER/EXIT hooks still fire around the forwarded syscall (existing behavior
of the hook chain in `load_syscall()` lines 206-224).
- [ ] Implement `KernelProxy` class
- [ ] Implement `_resolve_syscall_nr()` — look up syscall number from arch tables
- [ ] Implement `_make_forwarder()` — create CALL hook closure
- [ ] Implement `_alloc_fd()` — find empty slot in `ql.os.fd[]`, store `ql_proxy_fd`
### 0.3 `ql_proxy_fd` — proxy-side FD wrapper
When a forwarded syscall returns an FD (e.g., `epoll_create` returns 5 in the proxy),
we store a `ql_proxy_fd` in Qiling's FD table. This object forwards read/write/close
to the proxy, matching the interface of `ql_socket` (`filestruct.py:14`).
```python
class ql_proxy_fd:
"""FD whose real file/socket lives in the kernel proxy process."""
def __init__(self, ipc, proxy_fd: int):
self._ipc = ipc
self._proxy_fd = proxy_fd
def read(self, length: int) -> bytes:
return self._ipc.fd_read(self._proxy_fd, length)
def write(self, data: bytes) -> int:
return self._ipc.fd_write(self._proxy_fd, data)
def close(self) -> None:
self._ipc.fd_close(self._proxy_fd)
def fileno(self) -> int:
return -1 # not a real host FD
```
Because `ql_syscall_read` / `ql_syscall_write` / `ql_syscall_close` already dispatch
through `ql.os.fd[fd].read()` / `.write()` / `.close()`, **these existing handlers
need no changes**. When the binary calls `read(fd, buf, n)` on a proxy FD, the
existing read handler calls `ql_proxy_fd.read(n)`, gets data back, and writes it
to guest memory as usual.
- [ ] Implement `ql_proxy_fd` class in `qiling/os/posix/kernel_proxy/proxy_fd.py`
- [ ] Verify `ql_syscall_read` works with `ql_proxy_fd` — no changes needed
- [ ] Verify `ql_syscall_write` works with `ql_proxy_fd` — no changes needed
- [ ] Verify `ql_syscall_close` works with `ql_proxy_fd` — no changes needed
### 0.4 Pointer-bearing syscalls (Phase 0 scope: one example)
Some forwarded syscalls take pointers to guest memory. The proxy can't read guest
memory directly. For Phase 0, implement ONE pointer-bearing forwarder as an example
to prove the pattern works. `epoll_ctl` is a good candidate:
```
epoll_ctl(epfd, op, fd, struct epoll_event *event)
```
The forwarder must:
1. Read `struct epoll_event` (8 bytes) from guest memory at the `event` pointer
2. Send the struct data along with the integer args to the proxy
3. Proxy reconstructs the struct in its own memory, calls real `epoll_ctl`
- [ ] Extend IPC protocol for buffer-carrying requests:
```
Request: { type: SYSCALL_WITH_BUFS, syscall_nr, args[6], buffers: [(arg_idx, direction, data)] }
Response: { return_value, errno, buffers: [(arg_idx, data)] }
```
`direction` is IN (guest→proxy), OUT (proxy→guest), or INOUT.
- [ ] Implement `forward_syscall_with_buffers()` API for pointer-bearing syscalls
- [ ] Implement `epoll_ctl` forwarder as the working example
### 0.5 Validation
- [ ] Test: binary that uses `epoll_create` + `epoll_ctl` + `epoll_wait` on a
timerfd or eventfd. User forwards all 4 syscalls. Verify it works end-to-end.
- [ ] Test: same binary WITHOUT proxy — Qiling fails as it does today. No regression.
- [ ] Test: binary that calls `socket()` — existing Qiling handler runs (user did
NOT forward `socket`). Verify no interference.
- [ ] Test: user `set_syscall("epoll_create", my_hook)` — user hook takes priority
over proxy hook (user hook registered after proxy hook overwrites it via
`set_syscall`). Verify user control is preserved.
- [ ] Test: proxy process crash — verify Qiling reports error cleanly, doesn't hang.
**Existing code modified**: NONE. All new files in `qiling/os/posix/kernel_proxy/`.
Integration is purely through `set_syscall()`.
**Risk**: LOW — new code in new module. Existing behavior completely unchanged unless
user explicitly creates a `KernelProxy` and calls `forward_syscall()`.
---
## Phase 1: Networking Foundation
**Goal**: Forward all socket syscalls. Make a TCP client work end-to-end.
### 1.1 `ql_proxy_socket` FD type
New class in `qiling/os/posix/filestruct.py` (or new file alongside it). Must match
`ql_socket` interface so generic I/O dispatches correctly:
```python
class ql_proxy_socket:
"""Socket FD whose real socket lives in the kernel proxy process."""
def read(self, length: int) -> bytes:
# Forward to proxy: recv(self.proxy_fd, length)
def write(self, data: bytes) -> int:
# Forward to proxy: send(self.proxy_fd, data)
def close(self) -> None:
# Forward to proxy: close(self.proxy_fd)
def fileno(self) -> int:
# Return a sentinel — not a real host FD
# Socket-specific methods forwarded to proxy:
def connect(self, address) -> None: ...
def bind(self, address) -> None: ...
def listen(self, backlog) -> None: ...
def accept(self) -> tuple: ...
def shutdown(self, how) -> None: ...
def setsockopt(self, level, optname, value) -> None: ...
def getsockopt(self, level, optname) -> ...: ...
```
Because `ql_syscall_read` / `ql_syscall_write` / `ql_syscall_close` already dispatch
through `ql.os.fd[fd].read()` / `.write()` / `.close()`, these **existing handlers
need no changes** — the proxy socket object handles forwarding internally.
- [ ] Implement `ql_proxy_socket` class
- [ ] IPC client method for each operation
- [ ] Verify generic `read(fd, ...)` and `write(fd, ...)` work on proxy sockets
without modifying `ql_syscall_read` or `ql_syscall_write`
### 1.2 Socket syscall CALL hooks
Register CALL hooks for socket-specific syscalls. These are needed because socket
syscalls (bind, connect, listen, accept, etc.) have special argument handling
(sockaddr structs, address lengths) that goes beyond generic read/write.
- [ ] `socket()` — create proxy socket, store `ql_proxy_socket` in FD table
- [ ] `bind(fd, addr, addrlen)` — read sockaddr from guest memory, forward to proxy
- [ ] `connect(fd, addr, addrlen)` — same pattern
- [ ] `listen(fd, backlog)` — forward
- [ ] `accept(fd, addr, addrlen)` — forward, create new `ql_proxy_socket` for client FD
- [ ] `send/sendto/sendmsg` — read buffer from guest memory, forward
- [ ] `recv/recvfrom/recvmsg` — forward, write received data to guest memory
- [ ] `setsockopt/getsockopt` — forward with option translation
- [ ] `getpeername/getsockname` — forward, write sockaddr to guest memory
- [ ] `shutdown` — forward
- [ ] `socketpair` — forward, create two `ql_proxy_socket` objects
- [ ] `close` on proxy sockets — handled by `ql_proxy_socket.close()`,
but also register hook to detect close on proxy FDs if needed
**Struct translation**: sockaddr family (AF_INET, AF_INET6, AF_UNIX) is the same
across architectures. Network byte order is architecture-independent. The main
concern is pointer width (32-bit guest on 64-bit host) — read the right number
of bytes from guest memory based on `ql.arch.pointersize`.
### 1.3 `socketcall()` multiplexer (x86 32-bit)
On x86 32-bit, all socket operations go through a single `socketcall()` syscall
(`qiling/os/posix/syscall/net.py`). The existing multiplexer dispatches to individual
handlers. Since we hook the individual handlers (bind, connect, etc.), this works
automatically. But verify:
- [ ] Test that x86 32-bit socket operations are correctly forwarded via the
existing socketcall → individual handler → our CALL hook chain
### 1.4 Validation
- [ ] TCP client: connect to a server, send/receive data, close
- [ ] TCP server: bind, listen, accept, handle client, close
- [ ] UDP: sendto/recvfrom
- [ ] Unix domain sockets (path-based)
- [ ] Existing non-network tests still pass (regression check)
**Risk**: LOW-MEDIUM — new code + new FD type, but existing handlers and dispatch
untouched. Main risk is FD lifecycle bugs (leak, double-close).
---
## Phase 2: Complete Networking
**Goal**: epoll, network namespaces, advanced operations. Real-world network
binaries work.
### 2.1 epoll forwarding
epoll is currently **not implemented at all** — mapped in the syscall table but
no handler. This is new functionality, not a change to existing behavior.
- [ ] `epoll_create` / `epoll_create1` — forward, return proxy epoll FD
(new FD type or reuse `ql_proxy_socket` with a flag)
- [ ] `epoll_ctl(epfd, op, fd, event)` — forward; `fd` must be translated to
proxy FD space. Read `epoll_event` struct from guest memory.
- [ ] `epoll_wait(epfd, events, maxevents, timeout)` — forward. **This blocks**
in the proxy. For single-threaded programs this is correct (binary would be
blocked anyway). Write returned events to guest memory.
- [ ] `epoll_pwait` — same as epoll_wait + signal mask
**Blocking concern**: when the proxy is blocked on `epoll_wait`, the Unicorn
emulation is paused. This is correct for single-threaded programs. For multithreaded
programs, we need real threading (Phase 3) where each thread has its own Unicorn
and can block independently.
### 2.2 poll/select integration
Currently `poll()` and `select()` use host `select.poll()`/`select.select()` directly,
which won't work for proxy FDs (no host FD to poll).
- [ ] Hook `poll()` and `select()` — for FD sets containing proxy FDs, forward the
entire operation to the proxy
- [ ] For mixed FD sets (some proxy, some local): forward the proxy FDs to the proxy,
poll local FDs locally, merge results. This is complex — consider forwarding all
FDs to the proxy as the simpler approach.
### 2.3 Network namespace isolation
- [ ] Proxy process runs in its own network namespace (`unshare(CLONE_NEWNET)`)
- [ ] Configurable modes:
- `host`: proxy shares host network (default, simplest)
- `isolated`: separate namespace, no connectivity
- `bridged`: veth pair with NAT to host
- [ ] DNS: mount a resolv.conf in the proxy's mount namespace if needed
### 2.4 Advanced operations (lower priority)
- [ ] `sendmmsg` / `recvmmsg` — batch send/receive
- [ ] Raw sockets / packet sockets (`AF_PACKET`)
- [ ] Netlink sockets (`AF_NETLINK`) — for binaries that call `ip`, `route`, etc.
- [ ] `SCM_RIGHTS` (FD passing over Unix sockets) — requires FD translation
- [ ] IPv6 multicast
### 2.5 Validation
- [ ] epoll-based TCP echo server
- [ ] HTTP client (wget/curl-like binary)
- [ ] Binary that uses poll() with mixed file + socket FDs
- [ ] Network namespace: verify proxy and emulated binary are isolated from host
- [ ] Performance: measure latency overhead of IPC per syscall
**Risk**: MEDIUM — epoll is new functionality (no regression risk), but poll/select
changes for proxy FDs touch existing handlers. The mixed-FD-set case is the main
complexity.
---
## Phase 3: Real Threading
**Goal**: Real concurrency with one Unicorn engine per thread.
**This phase is high-risk and should only start after Phase 2 is stable.** It touches
the Unicorn integration, memory manager, thread lifecycle, and scheduler — all core
components. Needs a detailed design document before implementation begins.
### Prerequisites
- [ ] Phase 1-2 networking is stable and tested
- [ ] Detailed design document covering memory sharing, thread lifecycle,
and failure modes
- [ ] Prototype benchmark: measure overhead of multiple Unicorn instances
sharing memory via `mem_map_ptr`
### 3.1 Shared memory backing for Unicorn
Currently `QlMemoryManager.map()` calls `uc.mem_map()` which allocates internal
Unicorn memory. For shared threading, all Unicorn instances must see the same memory.
- [ ] Change memory backing to use `mmap(MAP_SHARED)` + `uc.mem_map_ptr()`
- [ ] This affects: `QlMemoryManager.map()`, `QlMemoryManager.protect()`,
`QlMemoryManager.unmap()`
- [ ] Loader changes: ELF/PE/MachO loaders must write segments into shared-backed
memory regions
- [ ] MMIO regions stay callback-based (not shared)
- [ ] **Critical**: This must be done as a standalone change that passes ALL existing
tests before moving to 3.2. If existing tests break, the shared memory
implementation is wrong.
Files: `qiling/os/memory.py`, `qiling/loader/elf.py`, `qiling/loader/pe.py`
### 3.2 One Unicorn instance per thread
Replace gevent Greenlets with real OS threads, each owning a Unicorn instance.
- [ ] New thread class: `QlLinuxRealThread` (alongside existing `QlLinuxThread`)
- Creates a new `Uc` instance on spawn
- Maps all shared memory regions into the new Uc via `mem_map_ptr`
- Copies parent registers to child Uc
- Sets child's SP, TLS, return value
- Runs in a real `threading.Thread`
- [ ] Modify `clone()` handler: when hybrid threading is enabled, create
`QlLinuxRealThread` instead of gevent Greenlet
- [ ] Per-thread hook context: each Unicorn instance needs its own hooks registered.
User-defined hooks must be replicated to all instances.
- [ ] Remove the 32337-instruction cooperative scheduling loop — real OS scheduler
handles preemption
**What breaks**: The current model assumes ONE `ql.uc` instance. Code that accesses
`ql.uc` directly will see only one thread's Unicorn. Need to audit all `ql.uc`
references and route to the current thread's instance.
Risky references:
- `ql.arch.regs` reads/writes `ql.uc` registers — must route to current thread's Uc
- `ql.mem.read/write` calls `ql.uc.mem_read/write` — with shared memory, any Uc works
- `ql.hook_*` registers on `ql.uc` — must register on all Uc instances
- `ql.save()/restore()` snapshots `ql.uc` — must snapshot correct thread
### 3.3 Synchronization primitives via real kernel
With real OS threads sharing real memory, kernel synchronization works natively.
- [ ] Forward `futex()` to kernel — `FUTEX_WAIT`/`FUTEX_WAKE` operate on the shared
memory addresses directly
- [ ] Remove gevent Event-based futex emulation (`qiling/os/linux/futex.py`)
- [ ] Forward `set_robust_list`, `get_robust_list`
- [ ] `pthread_mutex_*`, `pthread_cond_*` — these use futex internally, so forwarding
futex is sufficient
### 3.4 Thread safety for shared state
With real concurrent threads, shared mutable state needs synchronization.
- [ ] `QlMemoryManager`: lock `map_info` list mutations (map, unmap, protect)
- read/write don't need locks if backed by shared mmap (atomic at OS level)
- [ ] `QlFileDes`: lock FD table mutations (open, close, dup)
- [ ] Hook lists: lock registration/deregistration (hooks are usually set up before
`run()`, so contention should be minimal)
- [ ] Logging: thread-safe log handler with thread ID prefix
### 3.5 Validation
- [ ] pthread_create / pthread_join
- [ ] Mutex: two threads incrementing a shared counter with proper locking
- [ ] Condition variables: producer-consumer
- [ ] Futex: custom futex-based synchronization
- [ ] Thread-local storage (TLS) correctness per architecture
- [ ] Stress test: 10+ threads doing concurrent work
- [ ] ALL existing single-threaded tests still pass
- [ ] ALL existing gevent-threaded tests still pass (gevent mode preserved as fallback)
**Risk**: HIGH — changes to memory manager, Unicorn integration, and thread model.
Keep the existing gevent threading as a fallback mode. The new threading is opt-in.
---
## Phase 4: Signals
**Depends on Phase 3** (real threads required for proper signal delivery).
### 4.1 Signal handler registration
- [ ] Forward `sigaction(signum, act, oldact)` to kernel proxy
- [ ] Forward `sigprocmask` / `rt_sigprocmask`
- [ ] Forward `sigaltstack`
### 4.2 Signal delivery
When a signal is delivered to a proxy thread:
- [ ] Proxy catches the signal and sends notification to Qiling via IPC
- [ ] Qiling calls `emu_stop()` on the target thread's Unicorn
- [ ] Save thread context (registers)
- [ ] Build signal frame on emulated stack (architecture-specific)
- [ ] Set PC to the registered signal handler
- [ ] Resume Unicorn — handler executes in emulated code
- [ ] On `sigreturn` / `rt_sigreturn`: restore saved context, resume normal execution
### 4.3 Signal-syscall interaction
- [ ] `EINTR` on interrupted blocking syscalls
- [ ] `SA_RESTART` flag: automatically restart interrupted syscalls
- [ ] `kill()`, `tgkill()`, `tkill()` → forward to kernel
### 4.4 Validation
- [ ] SIGALRM handler (timer-based)
- [ ] SIGCHLD on child exit
- [ ] SIGPIPE on broken pipe
- [ ] Signal interrupting `read()` — verify EINTR
- [ ] Custom signal handler that modifies emulated state
**Risk**: MEDIUM — signal frame construction is architecture-specific and fiddly,
but the mechanism is well-understood. Main risk is getting the frame layout exactly
right for each architecture.
---
## Phase 5: Integration and Polish
### 5.1 User-facing API
```python
# Opt-in to hybrid kernel
ql = Qiling(argv=[...], rootfs="...")
# Enable kernel proxy for networking (Phase 1-2)
ql.os.kernel_proxy.enable(networking=True)
# Enable real threading (Phase 3) — requires networking=True
ql.os.kernel_proxy.enable(networking=True, threading=True)
# Enable signals (Phase 4) — requires threading=True
ql.os.kernel_proxy.enable(networking=True, threading=True, signals=True)
# Configure network namespace
ql.os.kernel_proxy.network_mode = "bridged" # "host" | "isolated" | "bridged"
# User hooks still work — they fire before/after proxy forwarding
ql.os.set_syscall("connect", my_connect_hook, QL_INTERCEPT.ENTER)
```
### 5.2 Backward compatibility
- [ ] Default behavior: no proxy, existing Python handlers — zero regression
- [ ] All existing tests pass with proxy disabled
- [ ] All existing tests pass with proxy enabled (forwarded syscalls should
produce equivalent results)
- [ ] `set_syscall()` user hooks fire correctly in both modes
- [ ] Existing gevent threading preserved as fallback when real threading not enabled
### 5.3 Fallback on failure
- [ ] If proxy process crashes: log error, fall back to Python handlers, warn user
- [ ] If proxy not available (non-Linux host): use Python handlers, warn user
- [ ] Graceful degradation: never crash, always fall back
### 5.4 Platform support
- [ ] Linux host: full support (namespaces, real threading)
- [ ] macOS host: proxy via Docker/Lima (networking only, no native namespaces)
- [ ] Windows host: proxy via WSL2 (networking only)
- [ ] Document host requirements
### 5.5 Performance
- [ ] Benchmark: syscall latency (Python handler vs proxy round-trip)
- [ ] Optimize IPC: shared memory ring buffer for high-frequency syscalls
- [ ] Batch small syscalls where possible
- [ ] Profile and tune for common workloads (network servers, threaded computation)
---
## Existing Issues (Independent of Hybrid Architecture)
These should be fixed regardless of the hybrid work.
### Bare except blocks swallowing errors
10+ bare `except:` blocks silently hide failures:
- `qiling/utils.py:242` — PE detection
- `qiling/debugger/qdb/qdb.py:128,352,598` — debugger operations
- `qiling/os/posix/filestruct.py:62,173,179` — fcntl/ioctl
- `qiling/os/posix/syscall/select.py:78` — select failures
- `qiling/os/windows/registry.py:127,185` — registry operations
### Asserts used for validation
Assertions disabled with `python -O`. Replace with exceptions:
- `qiling/os/memory.py` — page alignment, size, mapping checks
- `qiling/arch/x86_utils.py` — GDT/segment validation
- `qiling/cc/__init__.py` — calling convention validation
### Memory manager: string label parsing
`qiling/os/memory.py:209-218` — `get_lib_base()` uses regex on info strings.
Needs a proper mapping structure.
### ARM Thumb mode detection
`core.py:753` — fragile `_init_thumb` flag. Needs upstream Unicorn fix.
### x86 GDT privilege levels
`qiling/arch/x86_utils.py:147,178` — ring 3 forced to ring 0.
### Unbounded `read_cstring`
`qiling/os/memory.py:51-63` — no length limit. Can hang on MMIO.
### Incomplete save/restore
`QlOs.save()/restore()` empty in base class. UEFI and Windows don't implement it.
### Incomplete Windows emulation
Fiber, registry, handle management, DLL resolution gaps.
See `qiling/os/windows/` TODO comments.
### macOS and UEFI gaps
- macOS kext: 5 FIXMEs in `macos.py:79-117`
- UEFI variables: `uefi/rt.py:204-205`
### Hook system cleanup
- `type()` vs `isinstance()` in `core_hooks.py`
- Unclear return value semantics
- Non-intuitive `begin=1, end=0` for "entire memory"
### Hardcoded magic numbers
- Exit points in `os/os.py:84-87`
- Guard page `0x9000000` in `core.py:525`
### Test coverage
- ARM test skipped (`test_elf.py:411`)
- Multithread test skipped (`test_elf_multithread.py:185`)
- Broken wchar (`test_struct.py:170,185`)
- PowerPC, QNX, DOS, MCU: minimal coverage
+4
View File
@@ -0,0 +1,4 @@
[CODE]
load_address = 0x10000000
entry_point = 0x10000008
ram_size = 0xa00000
@@ -5,7 +5,7 @@
# Everything about the bug and firmware https://www.exploit-db.com/exploits/33863
import os,sys
import sys
sys.path.append("../../..")
from qiling import Qiling
@@ -13,7 +13,7 @@ from qiling.const import QL_VERBOSE
from qiling.extensions.afl import ql_afl_fuzz
def main(input_file, enable_trace=False):
def main(input_file: str):
env_vars = {
"REQUEST_METHOD": "POST",
@@ -24,40 +24,36 @@ def main(input_file, enable_trace=False):
# "CONTENT_LENGTH": "8", # no needed
}
ql = Qiling(["./rootfs/htdocs/web/hedwig.cgi"], "./rootfs",
verbose=QL_VERBOSE.DEBUG, env=env_vars, console=enable_trace)
ql = Qiling(["./rootfs/htdocs/web/hedwig.cgi"], "./rootfs", verbose=QL_VERBOSE.DISABLED, env=env_vars)
def place_input_callback(ql: Qiling, input: bytes, _: int):
env_var = ("HTTP_COOKIE=uid=1234&password=").encode()
env_vars = env_var + input + b"\x00" + (ql.path).encode() + b"\x00"
ql.mem.write(ql.target_addr, env_vars)
def place_input_callback(ql: Qiling, data: bytes, _: int) -> bool:
# construct the payload
payload = b''.join((b"HTTP_COOKIE=uid=1234&password=", bytes(data), b"\x00", ql_path, b"\x00"))
# patch the value of 'HTTP_COOKIE' in memory
ql.mem.write(target_addr, payload)
# payload is in place, we are good to go
return True
def start_afl(_ql: Qiling):
"""
Callback from inside
"""
ql_afl_fuzz(_ql, input_file=input_file, place_input_callback=place_input_callback, exits=[ql.os.exit_point])
addr = ql.mem.search("HTTP_COOKIE=uid=1234&password=".encode())
ql.target_addr = addr[0]
addr = ql.mem.search(b"HTTP_COOKIE=uid=1234&password=")
target_addr = addr[0]
ql_path = ql.path.encode()
main_addr = ql.loader.elf_entry
ql.hook_address(callback=start_afl, address=main_addr)
ql.hook_address(start_afl, ql.loader.elf_entry)
try:
ql.run()
os._exit(0)
except:
if enable_trace:
print("\nFuzzer Went Shit")
os._exit(0)
ql.run()
if __name__ == "__main__":
if len(sys.argv) == 1:
if len(sys.argv) < 2:
raise ValueError("No input file provided.")
if len(sys.argv) > 2 and sys.argv[1] == "-t":
main(sys.argv[2], enable_trace=True)
else:
main(sys.argv[1])
main(sys.argv[1])
+101
View File
@@ -0,0 +1,101 @@
##############################################################################
# This example is meant to demonstrate the modifications necessary
# to enable code coverage when emulating small code snippets or bare-metal
# code.
##############################################################################
from qiling import Qiling
from qiling.const import QL_ARCH, QL_OS, QL_VERBOSE
from qiling.extensions.coverage import utils as cov_utils
from qiling.loader.loader import Image
import os
BASE_ADDRESS = 0x10000000
CHECKSUM_FUNC_ADDR = BASE_ADDRESS + 0x8
END_ADDRESS = 0x100000ba
DATA_ADDR = 0xa0000000 # Arbitrary address for data
STACK_ADDR = 0xb0000000 # Arbitrary address for stack
# Python implementation of the checksum function being emulated
# This checksum function is intended to have different code paths based on the input
# which is useful for observing code coverage
def checksum_function(input_data_buffer: bytes):
expected_checksum_python = 0
input_data_len = len(input_data_buffer)
if input_data_len >= 1 and input_data_buffer[0] == 0xDE: # MAGIC_VALUE_1
for i in range(min(input_data_len, 4)):
expected_checksum_python += input_data_buffer[i]
expected_checksum_python += 0x10
elif input_data_len >= 2 and input_data_buffer[1] == 0xAD: # MAGIC_VALUE_2
for i in range(input_data_len):
expected_checksum_python ^= input_data_buffer[i]
expected_checksum_python += 0x20
else:
for i in range(input_data_len):
expected_checksum_python += input_data_buffer[i]
expected_checksum_python &= 0xFF # Ensure it's a single byte
return expected_checksum_python
def unmapped_handler(ql: Qiling, type: int, addr: int, size: int, value: int) -> None:
print(f"Unmapped Memory R/W, trying to access {size:d} bytes at {addr:#010x} from {ql.arch.regs.pc:#010x}")
def emulate_checksum_function(input_data_buffer: bytes) -> None:
print(f"\n--- Testing with input: {input_data_buffer.hex()} ---")
test_file = "rootfs/blob/example_raw.bin"
with open(test_file, "rb") as f:
raw_code: bytes = f.read()
ql: Qiling = Qiling(
code=raw_code,
archtype=QL_ARCH.ARM,
ostype=QL_OS.BLOB,
profile="blob_raw.ql",
verbose=QL_VERBOSE.DEBUG,
thumb=True
)
''' monkeypatch - Correcting the loader image name, used for coverage collection
removing all images with name 'blob_code' that were created by the blob loader.
This is necessary because some code coverage visualization tools require the
module name to match that of the input file '''
ql.loader.images = [img for img in ql.loader.images if img.path != 'blob_code']
ql.loader.images.append(Image(ql.loader.load_address, ql.loader.load_address + ql.os.code_ram_size, os.path.basename(test_file)))
input_data_len: int = len(input_data_buffer)
# Map memory for the data and stack
ql.mem.map(STACK_ADDR, 0x2000)
ql.mem.map(DATA_ADDR, ql.mem.align_up(input_data_len + 0x100)) # Map enough space for data
# Write input data
ql.mem.write(DATA_ADDR, input_data_buffer)
# Set up the stack pointer
ql.arch.regs.sp = STACK_ADDR + 0x2000 - 4
# Set up argument registers
ql.arch.regs.r0 = DATA_ADDR
ql.arch.regs.r1 = input_data_len
# Set the program counter to the function's entry point
ql.arch.regs.pc = CHECKSUM_FUNC_ADDR
# Set the return address (LR) to a dummy address.
ql.arch.regs.lr = 0xbebebebe
ql.hook_mem_unmapped(unmapped_handler)
#ql.debugger="gdb:127.0.0.1:9999"
# Start emulation
print(f"Starting emulation at PC: {hex(ql.arch.regs.pc)}")
try:
with cov_utils.collect_coverage(ql, 'drcov', 'output.cov'):
ql.run(begin=CHECKSUM_FUNC_ADDR, end=END_ADDRESS)
except Exception as e:
print(f"Emulation error: {e}")
print(f"Emulated checksum: {hex(ql.arch.regs.r0)}")
if __name__ == "__main__":
data = b"\x01\x02\x03\x04\x05" # Example input data
emulate_checksum_function(data)
+59 -45
View File
@@ -8,68 +8,82 @@ sys.path.append("..")
from qiling.core import Qiling
from qiling.const import QL_ARCH, QL_OS, QL_VERBOSE
from qiling.os.const import STRING
from qiling.os.const import STRING, SIZE_T, POINTER
def get_kaimendaji_password():
def my_getenv(ql: Qiling):
env = {
"ID" : b"000000000000000",
"ethaddr" : b"11:22:33:44:55:66"
}
def my_getenv(ql: Qiling):
env = {
"ID" : b"000000000000000",
"ethaddr" : b"11:22:33:44:55:66"
}
params = ql.os.resolve_fcall_params({'key': STRING})
value = env.get(params["key"], b"")
params = ql.os.resolve_fcall_params({'key': STRING})
value = env.get(params["key"], b"")
value_addr = ql.os.heap.alloc(len(value))
ql.mem.write(value_addr, value)
value_addr = ql.os.heap.alloc(len(value))
ql.mem.write(value_addr, value)
ql.arch.regs.r0 = value_addr
ql.arch.regs.arch_pc = ql.arch.regs.lr
ql.arch.regs.r0 = value_addr
ql.arch.regs.arch_pc = ql.arch.regs.lr
def get_password(ql: Qiling):
password_raw = ql.mem.read(ql.arch.regs.r0, ql.arch.regs.r2)
password = ''
for item in password_raw:
if 0 <= item <= 9:
password += chr(item + 48)
else:
password += chr(item + 87)
def get_password(ql: Qiling):
# we land on a memcmp call, where the real password is being compared to
# the one provided by the user. we can follow the arguments to read the
# real password
print("The password is: %s" % password)
params = ql.os.resolve_fcall_params({
'ptr1': POINTER, # points to real password
'ptr2': POINTER, # points to user provided password
'size': SIZE_T # comparison length
})
def partial_run_init(ql: Qiling):
# argv prepare
ql.arch.regs.arch_sp -= 0x30
arg0_ptr = ql.arch.regs.arch_sp
ql.mem.write(arg0_ptr, b"kaimendaji")
ptr1 = params['ptr1']
size = params['size']
ql.arch.regs.arch_sp -= 0x10
arg1_ptr = ql.arch.regs.arch_sp
ql.mem.write(arg1_ptr, b"000000") # arbitrary password
password_raw = ql.mem.read(ptr1, size)
ql.arch.regs.arch_sp -= 0x20
argv_ptr = ql.arch.regs.arch_sp
ql.mem.write_ptr(argv_ptr, arg0_ptr)
ql.mem.write_ptr(argv_ptr + ql.arch.pointersize, arg1_ptr)
def __hex_digit(ch: int) -> str:
off = ord('0') if ch in range(10) else ord('a') - 10
ql.arch.regs.r2 = 2
ql.arch.regs.r3 = argv_ptr
return chr(ch + off)
with open("../examples/rootfs/blob/u-boot.bin.img", "rb") as f:
uboot_code = f.read()
# should be: "013f1f"
password = "".join(__hex_digit(ch) for ch in password_raw)
ql = Qiling(code=uboot_code[0x40:], archtype=QL_ARCH.ARM, ostype=QL_OS.BLOB, profile="uboot_bin.ql", verbose=QL_VERBOSE.OFF)
print(f'The password is: {password}')
image_base_addr = ql.loader.load_address
ql.hook_address(my_getenv, image_base_addr + 0x13AC0)
ql.hook_address(get_password, image_base_addr + 0x48634)
partial_run_init(ql)
def partial_run_init(ql: Qiling):
# argv prepare
ql.arch.regs.arch_sp -= 0x30
arg0_ptr = ql.arch.regs.arch_sp
ql.mem.write(arg0_ptr, b"kaimendaji")
ql.run(image_base_addr + 0x486B4, image_base_addr + 0x48718)
ql.arch.regs.arch_sp -= 0x10
arg1_ptr = ql.arch.regs.arch_sp
ql.mem.write(arg1_ptr, b"000000") # arbitrary password
ql.arch.regs.arch_sp -= 0x20
argv_ptr = ql.arch.regs.arch_sp
ql.mem.write_ptr(argv_ptr, arg0_ptr)
ql.mem.write_ptr(argv_ptr + ql.arch.pointersize, arg1_ptr)
ql.arch.regs.r2 = 2
ql.arch.regs.r3 = argv_ptr
if __name__ == "__main__":
get_kaimendaji_password()
with open("../examples/rootfs/blob/u-boot.bin.img", "rb") as f:
uboot_code = f.read()
ql = Qiling(code=uboot_code[0x40:], archtype=QL_ARCH.ARM, ostype=QL_OS.BLOB, profile="uboot_bin.ql", verbose=QL_VERBOSE.DEBUG)
imgbase = ql.loader.images[0].base
ql.hook_address(my_getenv, imgbase + 0x13AC0)
ql.hook_address(get_password, imgbase + 0x48634)
partial_run_init(ql)
ql.run(imgbase + 0x486B4, imgbase + 0x48718)
+1 -1
View File
@@ -159,7 +159,7 @@ def hook_StartServiceA(ql: Qiling, address: int, params):
init_unseen_symbols(ql.amsint32_driver, ntoskrnl.base+0xb7695, b"NtTerminateProcess", 0, "ntoskrnl.exe")
#ql.amsint32_driver.debugger= ":9999"
try:
ql.amsint32_driver.load()
ql.amsint32_driver.run()
return 1
except UcError as e:
print("Load driver error: ", e)
+8
View File
@@ -80,6 +80,7 @@ CALL :collect_dll32 setupapi.dll
CALL :collect_dll32 shell32.dll
CALL :collect_dll32 shlwapi.dll
CALL :collect_dll32 sspicli.dll
CALL :collect_dll32 symcryptk.dll
CALL :collect_dll32 ucrtbase.dll
CALL :collect_dll32 ucrtbased.dll
CALL :collect_dll32 urlmon.dll
@@ -94,6 +95,9 @@ CALL :collect_dll32 wininet.dll
CALL :collect_dll32 winmm.dll
CALL :collect_dll32 ws2_32.dll
CALL :collect_dll32 wsock32.dll
CALL :collect_dll32 msvcp140.dll
CALL :collect_dll32 msvcp140_1.dll
CALL :collect_dll32 msvcp140_2.dll
CALL :collect_dll32 downlevel\api-ms-win-core-fibers-l1-1-1.dll
CALL :collect_dll32 downlevel\api-ms-win-core-localization-l1-2-1.dll
@@ -122,6 +126,7 @@ CALL :collect_dll64 rpcrt4.dll
CALL :collect_dll64 sechost.dll
CALL :collect_dll64 shell32.dll
CALL :collect_dll64 shlwapi.dll
CALL :collect_dll64 symcryptk.dll
CALL :collect_dll64 user32.dll
CALL :collect_dll64 vcruntime140.dll
CALL :collect_dll64 vcruntime140d.dll
@@ -131,6 +136,9 @@ CALL :collect_dll64 win32u.dll
CALL :collect_dll64 winhttp.dll
CALL :collect_dll64 wininet.dll
CALL :collect_dll64 ws2_32.dll
CALL :collect_dll64 msvcp140.dll
CALL :collect_dll64 msvcp140_1.dll
CALL :collect_dll64 msvcp140_2.dll
CALL :collect_dll64 downlevel\api-ms-win-crt-heap-l1-1-0.dll
CALL :collect_dll64 downlevel\api-ms-win-crt-locale-l1-1-0.dll
+52
View File
@@ -0,0 +1,52 @@
# Makefile for Bare-Metal ARM Checksum Calculator
# --- Toolchain Definitions ---
TOOLCHAIN_PREFIX = arm-none-eabi
# Compiler, Linker, and Objcopy executables
CC = $(TOOLCHAIN_PREFIX)-gcc
LD = $(TOOLCHAIN_PREFIX)-gcc
OBJCOPY = $(TOOLCHAIN_PREFIX)-objcopy
# --- Source and Output Files ---
SRCS = example_raw.c
OBJS = $(SRCS:.c=.o) # Convert .c to .o
ELF = example_raw.elf
BIN = example_raw.bin
# --- Linker Script ---
LDSCRIPT = linker.ld
# --- Compiler Flags ---
CFLAGS = -c -O0 -mcpu=cortex-a7 -mthumb -ffreestanding -nostdlib
# --- Linker Flags ---
LDFLAGS = -T $(LDSCRIPT) -nostdlib
# --- Objcopy Flags ---
OBJCOPYFLAGS = -O binary
# --- Default Target ---
.PHONY: all clean
all: $(BIN)
# Rule to build the raw binary (.bin) from the ELF file
$(BIN): $(ELF)
$(OBJCOPY) $(OBJCOPYFLAGS) $< $@
@echo "Successfully created $(BIN)"
# Rule to link the object file into an ELF executable
$(ELF): $(OBJS) $(LDSCRIPT)
$(LD) $(LDFLAGS) $(OBJS) -o $@
@echo "Successfully linked $(ELF)"
# Rule to compile the C source file into an object file
%.o: %.c
$(CC) $(CFLAGS) $< -o $@
@echo "Successfully compiled $<"
# --- Clean Rule ---
clean:
rm -f $(OBJS) $(ELF) $(BIN)
@echo "Cleaned build artifacts."
+56
View File
@@ -0,0 +1,56 @@
// example checksum algorithm to demonstrate raw binary code coverage in qiling
// example_raw.c
// Define some magic values
#define MAGIC_VALUE_1 0xDE
#define MAGIC_VALUE_2 0xAD
// This function calculates a checksum with branches based on input data
// It takes a pointer to data and its length
// Returns the checksum (unsigned char to fit in a byte)
unsigned char calculate_checksum(const unsigned char *data, unsigned int length) {
unsigned char checksum = 0;
// Branch 1: Check for MAGIC_VALUE_1 at the start
if (length >= 1 && data[0] == MAGIC_VALUE_1) {
// If first byte is MAGIC_VALUE_1, do a simple sum of first 4 bytes
// (or up to length if less than 4)
for (unsigned int i = 0; i < length && i < 4; i++) {
checksum += data[i];
}
// Add a fixed offset to make this path distinct
checksum += 0x10;
}
// Branch 2: Check for MAGIC_VALUE_2 at the second byte
else if (length >= 2 && data[1] == MAGIC_VALUE_2) {
// If second byte is MAGIC_VALUE_2, do a XOR sum of all bytes
for (unsigned int i = 0; i < length; i++) {
checksum ^= data[i];
}
// Add a fixed offset to make this path distinct
checksum += 0x20;
}
// Default Branch: Standard byte sum checksum
else {
for (unsigned int i = 0; i < length; i++) {
checksum += data[i];
}
}
return checksum;
}
// Minimal entry point for bare-metal.
// This function will not be called directly during Qiling emulation,
// but it's needed for the linker to have an entry point.
__attribute__((section(".text.startup")))
void _start() {
// In a real bare-metal application, this would initialize hardware,
// set up stacks, etc. For this example, it's just a placeholder.
// We'll call calculate_checksum directly from our Qiling script.
while (1) {
// Do nothing, or perhaps put the CPU to sleep
asm volatile ("wfi"); // Wait For Interrupt (ARM instruction)
}
}
+39
View File
@@ -0,0 +1,39 @@
/* linker.ld */
ENTRY(_start) /* Define the entry point of our program */
/* Define memory regions - simple RAM region for this example */
MEMORY
{
ram (rwx) : ORIGIN = 0x10000000, LENGTH = 64K /* 64KB of RAM for our program */
}
SECTIONS
{
/* Define the start of our program in memory.
*/
. = 0x10000000;
.text : {
KEEP(*(.text.startup)) /* Keep the _start function */
*(.text) /* All other code */
*(.text.*)
*(.rodata) /* Read-only data */
*(.rodata.*)
. = ALIGN(4);
} > ram /* Place .text section in the 'ram' region */
.data : {
. = ALIGN(4);
*(.data) /* Initialized data */
*(.data.*)
. = ALIGN(4);
} > ram
.bss : {
. = ALIGN(4);
*(.bss)
*(.bss.*)
. = ALIGN(4);
} > ram
}
+4
View File
@@ -33,6 +33,7 @@ TARGETS = \
x8664_hello_cpp \
x8664_hello_cpp_static \
x8664_cloexec_test \
x8664_linux_onestraw \
patch_test.bin
.PHONY: all clean
@@ -124,7 +125,10 @@ libpatch_test.so: patch_test.so.h patch_test.so.c
$(CC) $(CPPFLAGS) -Wall -s -O0 -shared -fpic -o $@ patch_test.so.c
patch_test.bin: patch_test.bin.c libpatch_test.so
$(CC) $(CPPFLAGS) $(CFLAGS) -o $@ $^
x8664_onestraw_server: x8664_linux_onestraw.c
$(CC) $(CPPFLAGS) $(CFLAGS) -m64 -o $@ $<
$(OBJS):%.o:%.c
$(CC) $(CFLAGS) -c $< -o $@
+54
View File
@@ -0,0 +1,54 @@
#define MAX_EVENTS 5
#define READ_SIZE 10
#include <stdio.h> // for fprintf()
#include <unistd.h> // for close(), read()
#include <sys/epoll.h> // for epoll_create1(), epoll_ctl(), struct epoll_event
#include <string.h> // for strncmp
int main()
{
//setvbuf(stdin, NULL, _IONBF, 0);
int running = 1, event_count, i;
size_t bytes_read;
char read_buffer[READ_SIZE + 1];
struct epoll_event event, events[MAX_EVENTS];
int epoll_fd = epoll_create1(0);
if (epoll_fd == -1) {
fprintf(stderr, "Failed to create epoll file descriptor\n");
return 1;
}
event.events = EPOLLIN;
event.data.fd = 0;
if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, 0, &event))
{
fprintf(stderr, "Failed to add file descriptor to epoll\n");
close(epoll_fd);
return 1;
}
while (running) {
printf("\nPolling for input...\n");
event_count = epoll_wait(epoll_fd, events, MAX_EVENTS, 30000);
printf("%d ready events\n", event_count);
for (i = 0; i < event_count; i++) {
printf("Reading file descriptor '%d' -- ", events[i].data.fd);
bytes_read = read(events[i].data.fd, read_buffer, READ_SIZE);
printf("%zd bytes read.\n", bytes_read);
read_buffer[bytes_read] = '\0';
printf("Read '%s'\n", read_buffer);
if(!strncmp(read_buffer, "stop\n", 5))
running = 0;
}
}
if (close(epoll_fd)) {
fprintf(stderr, "Failed to close epoll file descriptor\n");
return 1;
}
return 0;
}
+199
View File
@@ -0,0 +1,199 @@
/*
* Attention:
* To keep things simple, do not handle socket/bind/listen/.../epoll_create/epoll_wait API error
*/
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/epoll.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#define DEFAULT_PORT 8000
#define MAX_CONN 16
#define MAX_EVENTS 32
#define BUF_SIZE 16
#define MAX_LINE 256
void server_run();
void client_run();
int main(int argc, char *argv[])
{
int opt;
char role = 's';
while ((opt = getopt(argc, argv, "cs")) != -1) {
switch (opt) {
case 'c':
role = 'c';
break;
case 's':
break;
default:
printf("usage: %s [-cs]\n", argv[0]);
exit(1);
}
}
if (role == 's') {
server_run();
} else {
client_run();
}
return 0;
}
/*
* register events of fd to epfd
*/
static void epoll_ctl_add(int epfd, int fd, uint32_t events)
{
struct epoll_event ev;
ev.events = events;
ev.data.fd = fd;
if (epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev) == -1) {
perror("epoll_ctl()\n");
exit(1);
}
}
static void set_sockaddr(struct sockaddr_in *addr)
{
bzero((char *)addr, sizeof(struct sockaddr_in));
addr->sin_family = AF_INET;
addr->sin_addr.s_addr = INADDR_ANY;
addr->sin_port = htons(DEFAULT_PORT);
}
static int setnonblocking(int sockfd)
{
if (fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFL, 0) | O_NONBLOCK) ==
-1) {
return -1;
}
return 0;
}
/*
* epoll echo server
*/
void server_run()
{
int i;
int n;
int epfd;
int nfds;
int listen_sock;
int conn_sock;
int socklen;
char buf[BUF_SIZE];
struct sockaddr_in srv_addr;
struct sockaddr_in cli_addr;
struct epoll_event events[MAX_EVENTS];
listen_sock = socket(AF_INET, SOCK_STREAM, 0);
set_sockaddr(&srv_addr);
bind(listen_sock, (struct sockaddr *)&srv_addr, sizeof(srv_addr));
setnonblocking(listen_sock);
listen(listen_sock, MAX_CONN);
epfd = epoll_create(1);
epoll_ctl_add(epfd, listen_sock, EPOLLIN | EPOLLOUT | EPOLLET);
socklen = sizeof(cli_addr);
int a;
for (a = 0; a < 2; a++) {
nfds = epoll_wait(epfd, events, MAX_EVENTS, -1);
for (i = 0; i < nfds; i++) {
if (events[i].data.fd == listen_sock) {
/* handle new connection */
conn_sock =
accept(listen_sock,
(struct sockaddr *)&cli_addr,
&socklen);
inet_ntop(AF_INET, (char *)&(cli_addr.sin_addr),
buf, sizeof(cli_addr));
printf("[+] connected with %s:%d\n", buf,
ntohs(cli_addr.sin_port));
setnonblocking(conn_sock);
epoll_ctl_add(epfd, conn_sock,
EPOLLIN | EPOLLET | EPOLLRDHUP |
EPOLLHUP);
} else if (events[i].events & EPOLLIN) {
/* handle EPOLLIN event */
for (;;) {
bzero(buf, sizeof(buf));
n = read(events[i].data.fd, buf,
sizeof(buf));
if (n <= 0 /* || errno == EAGAIN */ ) {
break;
} else {
printf("[+] data: %s\n", buf);
write(events[i].data.fd, buf,
strlen(buf));
}
}
} else {
printf("[+] unexpected\n");
}
/* check if the connection is closing */
if (events[i].events & (EPOLLRDHUP | EPOLLHUP)) {
printf("[+] connection closed\n");
epoll_ctl(epfd, EPOLL_CTL_DEL,
events[i].data.fd, NULL);
close(events[i].data.fd);
continue;
}
}
}
}
/*
* test clinet
*/
void client_run()
{
int n;
int c;
int sockfd;
char buf[MAX_LINE];
struct sockaddr_in srv_addr;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
set_sockaddr(&srv_addr);
if (connect(sockfd, (struct sockaddr *)&srv_addr, sizeof(srv_addr)) < 0) {
perror("connect()");
exit(1);
}
for (;;) {
printf("input: ");
fgets(buf, sizeof(buf), stdin);
c = strlen(buf) - 1;
buf[c] = '\0';
write(sockfd, buf, c + 1);
bzero(buf, sizeof(buf));
while (errno != EAGAIN
&& (n = read(sockfd, buf, sizeof(buf))) > 0) {
printf("echo: %s\n", buf);
bzero(buf, sizeof(buf));
c -= n;
if (c <= 0) {
break;
}
}
}
close(sockfd);
}
@@ -0,0 +1,11 @@
// This is the default Hello World program generated by Visual Studio 2022.
#include <iostream>
int main()
{
std::cout << "Hello World!\n";
return 0;
}
+3
View File
@@ -0,0 +1,3 @@
In this folder: Sources for programs intended to help test C++ features and software exceptions.
Compile with MSVC (Visual Studio 2022)
+95
View File
@@ -0,0 +1,95 @@
#include <iostream>
#include <cstdlib>
/*
* Test simple try..catch.
*/
void test1()
{
std::cout << "y";
try {
std::cout << "y";
throw (unsigned int)0x12345678;
std::cout << "n";
}
catch(unsigned int n) {
n;
std::cout << "y";
}
std::cout << "y";
}
/*
* Test simple try..catch with throw.
*/
void test2()
{
std::cout << "y";
try {
std::cout << "y";
throw (unsigned int)0x12345679;
std::cout << "n";
}
catch (unsigned int n) {
n;
if (n == 0x12345679) {
std::cout << "y";
}
else {
std::cout << "n";
}
}
std::cout << "y";
}
/*
* Test nested try..catch with throw.
*/
void test3()
{
std::cout << "y";
try {
std::cout << "y";
try {
std::cout << "y";
throw (unsigned int)0x1234567A;
std::cout << "n";
}
catch (unsigned int n) {
n;
if (n == 0x1234567A) {
std::cout << "y";
}
else {
std::cout << "n";
}
}
std::cout << "y";
}
catch (unsigned int n) {
n;
std::cout << "n";
}
std::cout << "y";
}
int main()
{
/*
* For this program, all subtests successful will print:
* - 14 'y'
* - 0 'n'
*/
test1();
test2();
test3();
}
@@ -0,0 +1,46 @@
#include <windows.h>
#include <cstdio>
LONG WINAPI CustomExceptionFilter(EXCEPTION_POINTERS* ExceptionInfo) {
printf("Inside exception filter (GOOD)\n");
DWORD exceptionCode = (DWORD)ExceptionInfo->ExceptionRecord->ExceptionCode;
printf("Exception Code: 0x%X\n", exceptionCode);
if (exceptionCode == 0xE06D7363) { // code for C++ exception
printf("Exception code DOES match, GOOD\n");
}
else {
printf("Exception code DOES NOT match, BAD\n");
}
printf("Exception Address: 0x%llx\n", (ULONGLONG)ExceptionInfo->ExceptionRecord->ExceptionAddress);
printf("After printing exception: (GOOD)\n");
return EXCEPTION_EXECUTE_HANDLER;
}
int main() {
/*
* For this program, all subtests successful will print:
* - 3 'GOOD'
* - 0 'BAD'
*
* It is expected that the program terminates abnormally
* with status code 0xE06D7363 (C++ exception)
*/
// Set the custom top-level exception filter
SetUnhandledExceptionFilter(CustomExceptionFilter);
// Throw an unhandled exception.
// It should be caught by our filter.
throw (unsigned int)5;
// We should never reach this point, because the exception
// dispatcher should terminate the program after our unhandled
// exception filter is called.
printf("After exception filter (BAD)\n");
return 0;
}
@@ -0,0 +1,21 @@
#include <iostream>
#include <cstdio>
int main()
{
/*
* For this program, all subtests successful will print:
* - 1 'GOOD'
* - 0 'BAD'
*
* It is expected that the program terminates abnormally
* with status code 0xC0000409 (stack buffer overrun/security
* check failure)
*/
printf("Before throw (GOOD)\n");
throw (unsigned int)5;
printf("After throw (BAD)\n");
}
@@ -0,0 +1,93 @@
#include <iostream>
struct TestStruct {
float q;
};
class TestClass {
public:
int x, y;
virtual ~TestClass() {
std::cout << "TestClass destructor, GOOD" << std::endl;
};
void yyy() {
std::cout << "REALLY GOOD" << std::endl;
}
};
class Something {
public:
char z;
virtual ~Something() {
std::cout << "Something destructor, GOOD" << std::endl;
};
virtual void zzz() {
std::cout << "BAD" << std::endl;
};
};
class TestClass2 : public TestClass, public Something {
public:
int z;
virtual ~TestClass2() {
std::cout << "TestClass2 destructor, GOOD" << std::endl;
};
virtual void zzz() {
std::cout << "GOOD" << std::endl;
};
};
int main()
{
/*
* For this program, all subtests successful will print:
* - 12 'GOOD'
* - 0 'BAD'
*/
int x = 5;
TestClass p;
TestStruct s;
std::cout << typeid(x).name() << std::endl;
if (strcmp(typeid(x).name(), "int") == 0) {
std::cout << "typeid(x) is int, GOOD" << std::endl;
}
else {
std::cout << "typeid(x) is NOT int, BAD" << std::endl;
}
std::cout << typeid(p).name() << std::endl;
if (strcmp(typeid(p).name(), "class TestClass") == 0) {
std::cout << "typeid(p) is \"class TestClass\", GOOD" << std::endl;
}
else {
std::cout << "typeid(p) is NOT \"class TestClass\", BAD" << std::endl;
}
std::cout << typeid(s).name() << std::endl;
if (strcmp(typeid(s).name(), "struct TestStruct") == 0) {
std::cout << "typeid(s) is \"struct TestStruct\", GOOD" << std::endl;
}
else {
std::cout << "typeid(s) is NOT \"struct TestStruct\", BAD" << std::endl;
}
std::cout << "Reached virtual methods and dynamic_cast test. GOOD" << std::endl;
TestClass2* kz = new TestClass2;
Something* ks = static_cast<Something*>(kz);
ks->zzz();
TestClass* pk = dynamic_cast<TestClass*>(ks);
pk->yyy();
std::cout << "Reached virtual destructor test. GOOD" << std::endl;
delete pk;
std::cout << "Finished all tests. GOOD" << std::endl;
}
@@ -0,0 +1,45 @@
#include <windows.h>
#include <cstdio>
void test1() {
__try {
printf("Inside __try block. (GOOD)\n");
RaiseException(
0xE0000001,
0,
0,
nullptr
);
printf("After RaiseException. (BAD)\n");
}
__except (EXCEPTION_EXECUTE_HANDLER) {
printf("In __except block. (GOOD)\n");
unsigned long excepCode = GetExceptionCode();
printf("Exception code=0x%x\n", excepCode);
if (excepCode == 0xE0000001) {
printf("Exception code IS same, GOOD\n");
}
else {
printf("Exception code DOES NOT MATCH, BAD\n");
}
}
printf("After __except block. (GOOD)\n");
}
int main() {
/*
* For this program, all subtests successful will print:
* - 4 'GOOD'
* - 0 'BAD'
*/
test1();
return 0;
}
+2
View File
@@ -78,6 +78,8 @@ def my_sandbox(path, rootfs):
ql.os.set_syscall('vfork', __vfork)
os.unlink(fr'{ROOTFS}/proc/sys/kernel/core_pattern')
ql.run()
+2
View File
@@ -1,6 +1,8 @@
[CODE]
ram_size = 0xa00000
load_address = 0x80800000
entry_point = 0x80800000
heap_address = 0xa0000000
heap_size = 0x300000
Generated
+187 -240
View File
@@ -1,12 +1,12 @@
# This file is automatically @generated by Poetry and should not be changed by hand.
# This file is automatically @generated by Poetry 2.3.4 and should not be changed by hand.
[[package]]
name = "antlr4-python3-runtime"
version = "4.13.2"
description = "ANTLR 4.13.2 runtime for Python 3"
category = "main"
optional = false
python-versions = "*"
groups = ["main"]
files = [
{file = "antlr4_python3_runtime-4.13.2-py3-none-any.whl", hash = "sha256:fe3835eb8d33daece0e799090eda89719dbccee7aa39ef94eed3818cafa5a7e8"},
{file = "antlr4_python3_runtime-4.13.2.tar.gz", hash = "sha256:909b647e1d2fc2b70180ac586df3933e38919c85f98ccc656a96cd3f25ef3916"},
@@ -16,9 +16,9 @@ files = [
name = "asciimatics"
version = "1.15.0"
description = "A cross-platform package to replace curses (mouse/keyboard input & text colours/positioning) and create ASCII animations"
category = "main"
optional = false
python-versions = ">= 3.8"
groups = ["main"]
files = [
{file = "asciimatics-1.15.0-py3-none-any.whl", hash = "sha256:0fe068a6bed522929bd04bb5b8a2fb6ebf0aef1b7a9b3843cf71030a34bc38d5"},
{file = "asciimatics-1.15.0.tar.gz", hash = "sha256:cfdd398042727519d8b73e62b8ef82c0becfed4eb420899c3b96c98d0b96821a"},
@@ -32,26 +32,33 @@ wcwidth = "*"
[[package]]
name = "capstone"
version = "4.0.2"
version = "5.0.7"
description = "Capstone disassembly engine"
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "capstone-4.0.2-py2.py3-none-manylinux1_i686.whl", hash = "sha256:da442f979414cf27e4621e70e835880878c858ea438c4f0e957e132593579e37"},
{file = "capstone-4.0.2-py2.py3-none-manylinux1_x86_64.whl", hash = "sha256:9d1a9096c5f875b11290317722ed44bb6e7c52e50cc79d791f142bce968c49aa"},
{file = "capstone-4.0.2-py2.py3-none-win32.whl", hash = "sha256:c3d9b443d1adb40ee2d9a4e7341169b76476ddcf3a54c03793b16cdc7cd35c5a"},
{file = "capstone-4.0.2-py2.py3-none-win_amd64.whl", hash = "sha256:0d65ffe8620920976ceadedc769f22318f6f150a592368d8a735612367ac8a1a"},
{file = "capstone-4.0.2.tar.gz", hash = "sha256:2842913092c9b69fd903744bc1b87488e1451625460baac173056e1808ec1c66"},
{file = "capstone-5.0.7-py3-none-macosx_10_9_universal2.whl", hash = "sha256:388af4ddb9224d3b4f9269673ee575b3f94f77774d48b3f1a283ad13c29a106a"},
{file = "capstone-5.0.7-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:a9f64e3d75d8c4d7b3d26bba153b2992aadcf6b8d57674b4ef176b4ecdd9822f"},
{file = "capstone-5.0.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:acb89f5bf6f625745a104a3a44819d3acea173228055c1eadc60d2282ae490bb"},
{file = "capstone-5.0.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c58546c814567c95e4b9a63bdb8624c960cb8508855c7c767d5f108d7bc09ce2"},
{file = "capstone-5.0.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b809a9654844ce0d35099121a851ddd2ab2689df1ff6687037babcedcaae6391"},
{file = "capstone-5.0.7-py3-none-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:80b0f1b93fc703c419fda8cf84cfa017fd8909be62a4e88024273126ab16f006"},
{file = "capstone-5.0.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:467716e6555d50cb3526b290f0dbdccb5f961839b1f1e299b484fb5d814173e6"},
{file = "capstone-5.0.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:e551311d4b6dc344fe5518ef6decf4c2dfafe37bba9ad027a53a406930bc5c63"},
{file = "capstone-5.0.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a13437b28b136c886600e88bee192d25adf56ba1db5597ff5a0bec758bb9c533"},
{file = "capstone-5.0.7-py3-none-win_amd64.whl", hash = "sha256:4ab8bcb7da8f221ff45926ca168ca33e76f7237d06fbf3c10780002faa2670e1"},
{file = "capstone-5.0.7.tar.gz", hash = "sha256:796bdd69b05fa124fc2aa2e74b9a0b3d4c4e7f3e02add5e583cf2f3bca282ede"},
]
[[package]]
name = "cffi"
version = "1.17.1"
description = "Foreign Function Interface for Python calling C code."
category = "main"
optional = false
python-versions = ">=3.8"
groups = ["main"]
markers = "platform_python_implementation == \"CPython\" and sys_platform == \"win32\""
files = [
{file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"},
{file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"},
@@ -129,9 +136,9 @@ pycparser = "*"
name = "click"
version = "8.1.7"
description = "Composable command line interface toolkit"
category = "main"
optional = false
python-versions = ">=3.7"
groups = ["main"]
files = [
{file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"},
{file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"},
@@ -144,9 +151,10 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""}
name = "colorama"
version = "0.4.6"
description = "Cross-platform colored terminal text."
category = "main"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
groups = ["main"]
markers = "sys_platform == \"win32\" or platform_system == \"Windows\""
files = [
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
@@ -156,9 +164,9 @@ files = [
name = "dacite"
version = "1.8.1"
description = "Simple creation of data classes from dictionaries."
category = "main"
optional = false
python-versions = ">=3.6"
groups = ["main"]
files = [
{file = "dacite-1.8.1-py3-none-any.whl", hash = "sha256:cc31ad6fdea1f49962ea42db9421772afe01ac5442380d9a99fcf3d188c61afe"},
]
@@ -170,9 +178,9 @@ dev = ["black", "coveralls", "mypy", "pre-commit", "pylint", "pytest (>=5)", "py
name = "dill"
version = "0.3.9"
description = "serialize all of Python"
category = "main"
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "dill-0.3.9-py3-none-any.whl", hash = "sha256:468dff3b89520b474c0397703366b7b95eebe6303f108adf9b19da1f702be87a"},
{file = "dill-0.3.9.tar.gz", hash = "sha256:81aa267dddf68cbfe8029c42ca9ec6a4ab3b22371d1c450abc54422577b4512c"},
@@ -186,9 +194,9 @@ profile = ["gprof2dot (>=2022.7.29)"]
name = "enum-compat"
version = "0.0.3"
description = "enum/enum34 compatibility package"
category = "main"
optional = false
python-versions = "*"
groups = ["main"]
files = [
{file = "enum-compat-0.0.3.tar.gz", hash = "sha256:3677daabed56a6f724451d585662253d8fb4e5569845aafa8bb0da36b1a8751e"},
{file = "enum_compat-0.0.3-py3-none-any.whl", hash = "sha256:88091b617c7fc3bbbceae50db5958023c48dc40b50520005aa3bf27f8f7ea157"},
@@ -198,9 +206,9 @@ files = [
name = "first"
version = "2.0.2"
description = "Return the first true value of an iterable."
category = "main"
optional = false
python-versions = "*"
groups = ["main"]
files = [
{file = "first-2.0.2-py2.py3-none-any.whl", hash = "sha256:8d8e46e115ea8ac652c76123c0865e3ff18372aef6f03c22809ceefcea9dec86"},
{file = "first-2.0.2.tar.gz", hash = "sha256:ff285b08c55f8c97ce4ea7012743af2495c9f1291785f163722bd36f6af6d3bf"},
@@ -210,9 +218,10 @@ files = [
name = "fuzzercorn"
version = "0.0.1"
description = "Libfuzzer bindings for Unicorn.."
category = "main"
optional = true
python-versions = "*"
groups = ["main"]
markers = "platform_system == \"Linux\" and extra == \"fuzz\""
files = [
{file = "fuzzercorn-0.0.1-py3-none-manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:304ddcd19803c779a8e4a6a900c51f7c3f5ba0e8c3322bd9502d59ea0825d424"},
{file = "fuzzercorn-0.0.1-py3-none-manylinux1_i686.whl", hash = "sha256:7fa9cbffbcbf45c0af5707abc86ee8fa0d3a396f0ff48ce67c355ef9d3047c4f"},
@@ -226,188 +235,143 @@ unicorn = ">=2.0.0rc5"
[[package]]
name = "gevent"
version = "24.2.1"
version = "26.4.0"
description = "Coroutine-based network library"
category = "main"
optional = false
python-versions = ">=3.8"
python-versions = ">=3.9"
groups = ["main"]
files = [
{file = "gevent-24.2.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:6f947a9abc1a129858391b3d9334c45041c08a0f23d14333d5b844b6e5c17a07"},
{file = "gevent-24.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bde283313daf0b34a8d1bab30325f5cb0f4e11b5869dbe5bc61f8fe09a8f66f3"},
{file = "gevent-24.2.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5a1df555431f5cd5cc189a6ee3544d24f8c52f2529134685f1e878c4972ab026"},
{file = "gevent-24.2.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:14532a67f7cb29fb055a0e9b39f16b88ed22c66b96641df8c04bdc38c26b9ea5"},
{file = "gevent-24.2.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd23df885318391856415e20acfd51a985cba6919f0be78ed89f5db9ff3a31cb"},
{file = "gevent-24.2.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:ca80b121bbec76d7794fcb45e65a7eca660a76cc1a104ed439cdbd7df5f0b060"},
{file = "gevent-24.2.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b9913c45d1be52d7a5db0c63977eebb51f68a2d5e6fd922d1d9b5e5fd758cc98"},
{file = "gevent-24.2.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:918cdf8751b24986f915d743225ad6b702f83e1106e08a63b736e3a4c6ead789"},
{file = "gevent-24.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:3d5325ccfadfd3dcf72ff88a92fb8fc0b56cacc7225f0f4b6dcf186c1a6eeabc"},
{file = "gevent-24.2.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:03aa5879acd6b7076f6a2a307410fb1e0d288b84b03cdfd8c74db8b4bc882fc5"},
{file = "gevent-24.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8bb35ce57a63c9a6896c71a285818a3922d8ca05d150fd1fe49a7f57287b836"},
{file = "gevent-24.2.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d7f87c2c02e03d99b95cfa6f7a776409083a9e4d468912e18c7680437b29222c"},
{file = "gevent-24.2.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:968581d1717bbcf170758580f5f97a2925854943c45a19be4d47299507db2eb7"},
{file = "gevent-24.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7899a38d0ae7e817e99adb217f586d0a4620e315e4de577444ebeeed2c5729be"},
{file = "gevent-24.2.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:f5e8e8d60e18d5f7fd49983f0c4696deeddaf6e608fbab33397671e2fcc6cc91"},
{file = "gevent-24.2.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fbfdce91239fe306772faab57597186710d5699213f4df099d1612da7320d682"},
{file = "gevent-24.2.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cdf66977a976d6a3cfb006afdf825d1482f84f7b81179db33941f2fc9673bb1d"},
{file = "gevent-24.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:1dffb395e500613e0452b9503153f8f7ba587c67dd4a85fc7cd7aa7430cb02cc"},
{file = "gevent-24.2.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:6c47ae7d1174617b3509f5d884935e788f325eb8f1a7efc95d295c68d83cce40"},
{file = "gevent-24.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7cac622e11b4253ac4536a654fe221249065d9a69feb6cdcd4d9af3503602e0"},
{file = "gevent-24.2.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bf5b9c72b884c6f0c4ed26ef204ee1f768b9437330422492c319470954bc4cc7"},
{file = "gevent-24.2.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f5de3c676e57177b38857f6e3cdfbe8f38d1cd754b63200c0615eaa31f514b4f"},
{file = "gevent-24.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4faf846ed132fd7ebfbbf4fde588a62d21faa0faa06e6f468b7faa6f436b661"},
{file = "gevent-24.2.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:368a277bd9278ddb0fde308e6a43f544222d76ed0c4166e0d9f6b036586819d9"},
{file = "gevent-24.2.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f8a04cf0c5b7139bc6368b461257d4a757ea2fe89b3773e494d235b7dd51119f"},
{file = "gevent-24.2.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9d8d0642c63d453179058abc4143e30718b19a85cbf58c2744c9a63f06a1d388"},
{file = "gevent-24.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:94138682e68ec197db42ad7442d3cf9b328069c3ad8e4e5022e6b5cd3e7ffae5"},
{file = "gevent-24.2.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:8f4b8e777d39013595a7740b4463e61b1cfe5f462f1b609b28fbc1e4c4ff01e5"},
{file = "gevent-24.2.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:141a2b24ad14f7b9576965c0c84927fc85f824a9bb19f6ec1e61e845d87c9cd8"},
{file = "gevent-24.2.1-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:9202f22ef811053077d01f43cc02b4aaf4472792f9fd0f5081b0b05c926cca19"},
{file = "gevent-24.2.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2955eea9c44c842c626feebf4459c42ce168685aa99594e049d03bedf53c2800"},
{file = "gevent-24.2.1-cp38-cp38-win32.whl", hash = "sha256:44098038d5e2749b0784aabb27f1fcbb3f43edebedf64d0af0d26955611be8d6"},
{file = "gevent-24.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:117e5837bc74a1673605fb53f8bfe22feb6e5afa411f524c835b2ddf768db0de"},
{file = "gevent-24.2.1-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:2ae3a25ecce0a5b0cd0808ab716bfca180230112bb4bc89b46ae0061d62d4afe"},
{file = "gevent-24.2.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a7ceb59986456ce851160867ce4929edaffbd2f069ae25717150199f8e1548b8"},
{file = "gevent-24.2.1-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:2e9ac06f225b696cdedbb22f9e805e2dd87bf82e8fa5e17756f94e88a9d37cf7"},
{file = "gevent-24.2.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:90cbac1ec05b305a1b90ede61ef73126afdeb5a804ae04480d6da12c56378df1"},
{file = "gevent-24.2.1-cp39-cp39-win32.whl", hash = "sha256:782a771424fe74bc7e75c228a1da671578c2ba4ddb2ca09b8f959abdf787331e"},
{file = "gevent-24.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:3adfb96637f44010be8abd1b5e73b5070f851b817a0b182e601202f20fa06533"},
{file = "gevent-24.2.1-pp310-pypy310_pp73-macosx_11_0_universal2.whl", hash = "sha256:7b00f8c9065de3ad226f7979154a7b27f3b9151c8055c162332369262fc025d8"},
{file = "gevent-24.2.1.tar.gz", hash = "sha256:432fc76f680acf7cf188c2ee0f5d3ab73b63c1f03114c7cd8a34cebbe5aa2056"},
{file = "gevent-26.4.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:079dca3fe5e048714e93ab5ce7cbf2ec31709c860979feb117abbbf2b8ae6ad0"},
{file = "gevent-26.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:df208cd53c7382a4cc8470d39a92fc73b3cf2f0f3379d6c88bb556823a26ccb7"},
{file = "gevent-26.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:ccaa0f002cfd69c03621ac05243c1a00ed77cee97b363d0108b0e36663e4ca33"},
{file = "gevent-26.4.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:201323a5fb9a0646a0c7b384395ca55d60ee83200677919229df0648c4b78e6c"},
{file = "gevent-26.4.0-cp311-cp311-manylinux_2_28_ppc64le.whl", hash = "sha256:82d68a60a4207826db295b4e80a204c9d392ce78ccc15679195faeb9e29d8388"},
{file = "gevent-26.4.0-cp311-cp311-manylinux_2_28_s390x.whl", hash = "sha256:35b037b415ed38369717800250fe5974249525953b46026bef9def20f946dfb0"},
{file = "gevent-26.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:6d973735d2067607a32cd182893978755eee829a0dc268087592d3b715e63fad"},
{file = "gevent-26.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4fbd3ff28a7babbfee750684c4f46ba6eedb3bce69365dd146726986b79fa6c1"},
{file = "gevent-26.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f0a03650ca60c4c5774cbe21333905b95f2f5abd98ea5a3dbf28d93f2a7a5a84"},
{file = "gevent-26.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:d00d8c4ca1afab90e478b79679dd53787082c6da8d2f4fdc7667a4440d1e1a7a"},
{file = "gevent-26.4.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:318a0a73f664113e8d86d0cb0e328e7650e2d7d9c2e045418ab6fb1285831ad3"},
{file = "gevent-26.4.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:ce7aa033a3f68beb6732d1450a80c1af29e63e0c2d01abad7918cf2507f72fa6"},
{file = "gevent-26.4.0-cp312-cp312-manylinux_2_28_ppc64le.whl", hash = "sha256:a1b897c952baefd72232efaeb3bdb1ca2fa7ae94cbfe68ac21201b03e843190a"},
{file = "gevent-26.4.0-cp312-cp312-manylinux_2_28_s390x.whl", hash = "sha256:7eef2ea508ce41795e20587a5fc868ae4919543097c81a40fbdfd65bc479f54f"},
{file = "gevent-26.4.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:f7e12fdd28cc9f39a463d8df5172d698c64a8ed385a21d98e7092fd8308a139a"},
{file = "gevent-26.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d48e3ee13d7678c24c22f19d441ad6bc220a79f23662d03ff36fae0d62efdb59"},
{file = "gevent-26.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c58c8e034f94329be4dc0979fba3301005a433dbab42cea0b2c33fd736946872"},
{file = "gevent-26.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:1c737e6ac6ce1398df0e3f41c58d982e397c993cbe73ac05b7edbe39e128c9cb"},
{file = "gevent-26.4.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:1fe581d41c63cd1d8b12c69561ce53a48ad0d8763b254740d7bfea997335a38c"},
{file = "gevent-26.4.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:c503b0c0a681e795255a13e5bb4e41615c3b020c1db93b8dfa04cfeb8f19d5a9"},
{file = "gevent-26.4.0-cp313-cp313-manylinux_2_28_ppc64le.whl", hash = "sha256:684256c29e3e5d4d0c4d06b772d00574d0dc859dfbb2fd13d318c512b16e1f89"},
{file = "gevent-26.4.0-cp313-cp313-manylinux_2_28_s390x.whl", hash = "sha256:73eafd06b158d511f1ec6e5902a45e0ae3b48e745f35e9df97d25f809f537d88"},
{file = "gevent-26.4.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:1a18e543c830a1c07a2efeb33786a57ccac360af70cb42bbaf5a6f5f7ca49300"},
{file = "gevent-26.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:74f1e3a460c43aefcb4ff9ef91aac15abc0b42e5233771e1956574d14ba9cac6"},
{file = "gevent-26.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:954258873ae0bcc97fb41e48db25284fb73454bfefe27db8ceb89225da5502fb"},
{file = "gevent-26.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:4a9a64c064457c1afaf93ee2815fe0f38be6ecbb92806a6a712f12afc3e26cf5"},
{file = "gevent-26.4.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:7ab0f183a6fd2369eef619832eef14f1f2f69c605163c3f2dc41deb799af4a71"},
{file = "gevent-26.4.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:7e5906860e632bf965e1966c57e6bfc19dcb79dc262f04fdb0a9d7c12147bf69"},
{file = "gevent-26.4.0-cp314-cp314-manylinux_2_28_ppc64le.whl", hash = "sha256:297a361071dc6708115d4544859321e93b02a6cd5823ba02c0a909530a519d45"},
{file = "gevent-26.4.0-cp314-cp314-manylinux_2_28_s390x.whl", hash = "sha256:7e74f59e5c9011afa2a9cb7106bb9a59f2a1f74c3d7b272c1b852eb0bc0b8f90"},
{file = "gevent-26.4.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:45d6010a6a981f5a2b3411c4e38fbe305a1b46e4b12db3b4914775927dea7ba4"},
{file = "gevent-26.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:dc38137ba2f43794c488615aafa2eefd0cc142f484a8274d4c827ed7a031a1e2"},
{file = "gevent-26.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:29a225d2d4da37e20c7a246754a64442d0e43e4534b8cc764f89530bb22a4237"},
{file = "gevent-26.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:d1c08bc9bb6bd79732a26710a99588b5e9b67b668e165dd609704b876f41baab"},
{file = "gevent-26.4.0.tar.gz", hash = "sha256:288d03addfccf0d1c67268358b6759b04392bf3bc35d26f3d9a45c82899c292d"},
]
[package.dependencies]
cffi = {version = ">=1.12.2", markers = "platform_python_implementation == \"CPython\" and sys_platform == \"win32\""}
greenlet = [
{version = ">=2.0.0", markers = "platform_python_implementation == \"CPython\" and python_version < \"3.11\""},
{version = ">=3.0rc3", markers = "platform_python_implementation == \"CPython\" and python_version >= \"3.11\""},
]
cffi = {version = ">=1.17.1", markers = "platform_python_implementation == \"CPython\" and sys_platform == \"win32\""}
greenlet = {version = ">=3.2.2", markers = "platform_python_implementation == \"CPython\""}
"zope.event" = "*"
"zope.interface" = "*"
[package.extras]
dnspython = ["dnspython (>=1.16.0,<2.0)", "idna"]
dnspython = ["dnspython (>=1.16.0,<2.0) ; python_version < \"3.10\"", "idna ; python_version < \"3.10\""]
docs = ["furo", "repoze.sphinx.autointerface", "sphinx", "sphinxcontrib-programoutput", "zope.schema"]
monitor = ["psutil (>=5.7.0)"]
recommended = ["cffi (>=1.12.2)", "dnspython (>=1.16.0,<2.0)", "idna", "psutil (>=5.7.0)"]
test = ["cffi (>=1.12.2)", "coverage (>=5.0)", "dnspython (>=1.16.0,<2.0)", "idna", "objgraph", "psutil (>=5.7.0)", "requests"]
monitor = ["psutil (>=6.0.0) ; sys_platform != \"win32\" or platform_python_implementation == \"CPython\""]
recommended = ["cffi (>=1.17.1) ; platform_python_implementation == \"CPython\"", "dnspython (>=1.16.0,<2.0) ; python_version < \"3.10\"", "idna ; python_version < \"3.10\"", "psutil (>=6.0.0) ; sys_platform != \"win32\" or platform_python_implementation == \"CPython\""]
test = ["cffi (>=1.17.1) ; platform_python_implementation == \"CPython\"", "coverage (>=5.0,<7.13) ; sys_platform != \"win32\"", "dnspython (>=1.16.0,<2.0) ; python_version < \"3.10\"", "idna ; python_version < \"3.10\"", "objgraph", "psutil (>=6.0.0) ; sys_platform != \"win32\" or platform_python_implementation == \"CPython\"", "requests"]
[[package]]
name = "greenlet"
version = "3.1.1"
version = "3.5.0"
description = "Lightweight in-process concurrent programming"
category = "main"
optional = false
python-versions = ">=3.7"
python-versions = ">=3.10"
groups = ["main"]
markers = "platform_python_implementation == \"CPython\""
files = [
{file = "greenlet-3.1.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:0bbae94a29c9e5c7e4a2b7f0aae5c17e8e90acbfd3bf6270eeba60c39fce3563"},
{file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fde093fb93f35ca72a556cf72c92ea3ebfda3d79fc35bb19fbe685853869a83"},
{file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:36b89d13c49216cadb828db8dfa6ce86bbbc476a82d3a6c397f0efae0525bdd0"},
{file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94b6150a85e1b33b40b1464a3f9988dcc5251d6ed06842abff82e42632fac120"},
{file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93147c513fac16385d1036b7e5b102c7fbbdb163d556b791f0f11eada7ba65dc"},
{file = "greenlet-3.1.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:da7a9bff22ce038e19bf62c4dd1ec8391062878710ded0a845bcf47cc0200617"},
{file = "greenlet-3.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b2795058c23988728eec1f36a4e5e4ebad22f8320c85f3587b539b9ac84128d7"},
{file = "greenlet-3.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ed10eac5830befbdd0c32f83e8aa6288361597550ba669b04c48f0f9a2c843c6"},
{file = "greenlet-3.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:77c386de38a60d1dfb8e55b8c1101d68c79dfdd25c7095d51fec2dd800892b80"},
{file = "greenlet-3.1.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:e4d333e558953648ca09d64f13e6d8f0523fa705f51cae3f03b5983489958c70"},
{file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09fc016b73c94e98e29af67ab7b9a879c307c6731a2c9da0db5a7d9b7edd1159"},
{file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d5e975ca70269d66d17dd995dafc06f1b06e8cb1ec1e9ed54c1d1e4a7c4cf26e"},
{file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b2813dc3de8c1ee3f924e4d4227999285fd335d1bcc0d2be6dc3f1f6a318ec1"},
{file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e347b3bfcf985a05e8c0b7d462ba6f15b1ee1c909e2dcad795e49e91b152c383"},
{file = "greenlet-3.1.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e8f8c9cb53cdac7ba9793c276acd90168f416b9ce36799b9b885790f8ad6c0a"},
{file = "greenlet-3.1.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:62ee94988d6b4722ce0028644418d93a52429e977d742ca2ccbe1c4f4a792511"},
{file = "greenlet-3.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1776fd7f989fc6b8d8c8cb8da1f6b82c5814957264d1f6cf818d475ec2bf6395"},
{file = "greenlet-3.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:48ca08c771c268a768087b408658e216133aecd835c0ded47ce955381105ba39"},
{file = "greenlet-3.1.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:4afe7ea89de619adc868e087b4d2359282058479d7cfb94970adf4b55284574d"},
{file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f406b22b7c9a9b4f8aa9d2ab13d6ae0ac3e85c9a809bd590ad53fed2bf70dc79"},
{file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c3a701fe5a9695b238503ce5bbe8218e03c3bcccf7e204e455e7462d770268aa"},
{file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2846930c65b47d70b9d178e89c7e1a69c95c1f68ea5aa0a58646b7a96df12441"},
{file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99cfaa2110534e2cf3ba31a7abcac9d328d1d9f1b95beede58294a60348fba36"},
{file = "greenlet-3.1.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1443279c19fca463fc33e65ef2a935a5b09bb90f978beab37729e1c3c6c25fe9"},
{file = "greenlet-3.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b7cede291382a78f7bb5f04a529cb18e068dd29e0fb27376074b6d0317bf4dd0"},
{file = "greenlet-3.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:23f20bb60ae298d7d8656c6ec6db134bca379ecefadb0b19ce6f19d1f232a942"},
{file = "greenlet-3.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:7124e16b4c55d417577c2077be379514321916d5790fa287c9ed6f23bd2ffd01"},
{file = "greenlet-3.1.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:05175c27cb459dcfc05d026c4232f9de8913ed006d42713cb8a5137bd49375f1"},
{file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:935e943ec47c4afab8965954bf49bfa639c05d4ccf9ef6e924188f762145c0ff"},
{file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:667a9706c970cb552ede35aee17339a18e8f2a87a51fba2ed39ceeeb1004798a"},
{file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8a678974d1f3aa55f6cc34dc480169d58f2e6d8958895d68845fa4ab566509e"},
{file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efc0f674aa41b92da8c49e0346318c6075d734994c3c4e4430b1c3f853e498e4"},
{file = "greenlet-3.1.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0153404a4bb921f0ff1abeb5ce8a5131da56b953eda6e14b88dc6bbc04d2049e"},
{file = "greenlet-3.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:275f72decf9932639c1c6dd1013a1bc266438eb32710016a1c742df5da6e60a1"},
{file = "greenlet-3.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c4aab7f6381f38a4b42f269057aee279ab0fc7bf2e929e3d4abfae97b682a12c"},
{file = "greenlet-3.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:b42703b1cf69f2aa1df7d1030b9d77d3e584a70755674d60e710f0af570f3761"},
{file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1695e76146579f8c06c1509c7ce4dfe0706f49c6831a817ac04eebb2fd02011"},
{file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7876452af029456b3f3549b696bb36a06db7c90747740c5302f74a9e9fa14b13"},
{file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ead44c85f8ab905852d3de8d86f6f8baf77109f9da589cb4fa142bd3b57b475"},
{file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8320f64b777d00dd7ccdade271eaf0cad6636343293a25074cc5566160e4de7b"},
{file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6510bf84a6b643dabba74d3049ead221257603a253d0a9873f55f6a59a65f822"},
{file = "greenlet-3.1.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:04b013dc07c96f83134b1e99888e7a79979f1a247e2a9f59697fa14b5862ed01"},
{file = "greenlet-3.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:411f015496fec93c1c8cd4e5238da364e1da7a124bcb293f085bf2860c32c6f6"},
{file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47da355d8687fd65240c364c90a31569a133b7b60de111c255ef5b606f2ae291"},
{file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:98884ecf2ffb7d7fe6bd517e8eb99d31ff7855a840fa6d0d63cd07c037f6a981"},
{file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1d4aeb8891338e60d1ab6127af1fe45def5259def8094b9c7e34690c8858803"},
{file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db32b5348615a04b82240cc67983cb315309e88d444a288934ee6ceaebcad6cc"},
{file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dcc62f31eae24de7f8dce72134c8651c58000d3b1868e01392baea7c32c247de"},
{file = "greenlet-3.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1d3755bcb2e02de341c55b4fca7a745a24a9e7212ac953f6b3a48d117d7257aa"},
{file = "greenlet-3.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:b8da394b34370874b4572676f36acabac172602abf054cbc4ac910219f3340af"},
{file = "greenlet-3.1.1-cp37-cp37m-win32.whl", hash = "sha256:a0dfc6c143b519113354e780a50381508139b07d2177cb6ad6a08278ec655798"},
{file = "greenlet-3.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:54558ea205654b50c438029505def3834e80f0869a70fb15b871c29b4575ddef"},
{file = "greenlet-3.1.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:346bed03fe47414091be4ad44786d1bd8bef0c3fcad6ed3dee074a032ab408a9"},
{file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfc59d69fc48664bc693842bd57acfdd490acafda1ab52c7836e3fc75c90a111"},
{file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d21e10da6ec19b457b82636209cbe2331ff4306b54d06fa04b7c138ba18c8a81"},
{file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:37b9de5a96111fc15418819ab4c4432e4f3c2ede61e660b1e33971eba26ef9ba"},
{file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ef9ea3f137e5711f0dbe5f9263e8c009b7069d8a1acea822bd5e9dae0ae49c8"},
{file = "greenlet-3.1.1-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85f3ff71e2e60bd4b4932a043fbbe0f499e263c628390b285cb599154a3b03b1"},
{file = "greenlet-3.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:95ffcf719966dd7c453f908e208e14cde192e09fde6c7186c8f1896ef778d8cd"},
{file = "greenlet-3.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:03a088b9de532cbfe2ba2034b2b85e82df37874681e8c470d6fb2f8c04d7e4b7"},
{file = "greenlet-3.1.1-cp38-cp38-win32.whl", hash = "sha256:8b8b36671f10ba80e159378df9c4f15c14098c4fd73a36b9ad715f057272fbef"},
{file = "greenlet-3.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:7017b2be767b9d43cc31416aba48aab0d2309ee31b4dbf10a1d38fb7972bdf9d"},
{file = "greenlet-3.1.1-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:396979749bd95f018296af156201d6211240e7a23090f50a8d5d18c370084dc3"},
{file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca9d0ff5ad43e785350894d97e13633a66e2b50000e8a183a50a88d834752d42"},
{file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f6ff3b14f2df4c41660a7dec01045a045653998784bf8cfcb5a525bdffffbc8f"},
{file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94ebba31df2aa506d7b14866fed00ac141a867e63143fe5bca82a8e503b36437"},
{file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73aaad12ac0ff500f62cebed98d8789198ea0e6f233421059fa68a5aa7220145"},
{file = "greenlet-3.1.1-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63e4844797b975b9af3a3fb8f7866ff08775f5426925e1e0bbcfe7932059a12c"},
{file = "greenlet-3.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7939aa3ca7d2a1593596e7ac6d59391ff30281ef280d8632fa03d81f7c5f955e"},
{file = "greenlet-3.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d0028e725ee18175c6e422797c407874da24381ce0690d6b9396c204c7f7276e"},
{file = "greenlet-3.1.1-cp39-cp39-win32.whl", hash = "sha256:5e06afd14cbaf9e00899fae69b24a32f2196c19de08fcb9f4779dd4f004e5e7c"},
{file = "greenlet-3.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:3319aa75e0e0639bc15ff54ca327e8dc7a6fe404003496e3c6925cd3142e0e22"},
{file = "greenlet-3.1.1.tar.gz", hash = "sha256:4ce3ac6cdb6adf7946475d7ef31777c26d94bccc377e070a7986bd2d5c515467"},
{file = "greenlet-3.5.0-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:29ea813b2e1f45fa9649a17853b2b5465c4072fbcb072e5af6cd3a288216574a"},
{file = "greenlet-3.5.0-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:804a70b328e706b785c6ef16187051c394a63dd1a906d89be24b6ad77759f13f"},
{file = "greenlet-3.5.0-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:884f649de075b84739713d41dd4dfd41e2b910bfb769c4a3ea02ec1da52cd9bb"},
{file = "greenlet-3.5.0-cp310-cp310-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4d0eadc7e4d9ffb2af4247b606cae307be8e448911e5a0d0b16d72fc3d224cfd"},
{file = "greenlet-3.5.0-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4b28037cb07768933c54d81bfe47a85f9f402f57d7d69743b991a713b63954eb"},
{file = "greenlet-3.5.0-cp310-cp310-manylinux_2_39_riscv64.whl", hash = "sha256:f8c30c2225f40dd76c50790f0eb3b5c7c18431efb299e2782083e1981feed243"},
{file = "greenlet-3.5.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cda05425526240807408156b6960a17a79a0c760b813573b67027823be760977"},
{file = "greenlet-3.5.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9c615f869163e14bb1ced20322d8038fb680b08236521ac3f30cd4c1288785a0"},
{file = "greenlet-3.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:ba8f0bdc2fae6ce915dfd0c16d2d00bca7e4247c1eae4416e06430e522137858"},
{file = "greenlet-3.5.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:8f1cc966c126639cd152fdaa52624d2655f492faa79e013fea161de3e6dda082"},
{file = "greenlet-3.5.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:362624e6a8e5bca3b8233e45eef33903a100e9539a2b995c364d595dbc4018b3"},
{file = "greenlet-3.5.0-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5ecd83806b0f4c2f53b1018e0005cd82269ea01d42befc0368730028d850ed1c"},
{file = "greenlet-3.5.0-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fa94cb2288681e3a11645958f1871d48ee9211bd2f66628fdace505927d6e564"},
{file = "greenlet-3.5.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0ff251e9a0279522e62f6176412869395a64ddf2b5c5f782ff609a8216a4e662"},
{file = "greenlet-3.5.0-cp311-cp311-manylinux_2_39_riscv64.whl", hash = "sha256:64d6ac45f7271f48e45f67c95b54ef73534c52ec041fcda8edf520c6d811f4bc"},
{file = "greenlet-3.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6d874e79afd41a96e11ff4c5d0bc90a80973e476fda1c2c64985667397df432b"},
{file = "greenlet-3.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0ed006e4b86c59de7467eb2601cd1b77b5a7d657d1ee55e30fe30d76451edba4"},
{file = "greenlet-3.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:703cb211b820dbffbbc55a16bfc6e4583a6e6e990f33a119d2cc8b83211119c8"},
{file = "greenlet-3.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:6c18dfb59c70f5a94acd271c72e90128c3c776e41e5f07767908c8c1b74ad339"},
{file = "greenlet-3.5.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:db2910d3c809444e0a20147361f343fe2798e106af8d9d8506f5305302655a9f"},
{file = "greenlet-3.5.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ec9ea74e7268ace7f9aab1b1a4e730193fc661b39a993cd91c606c32d4a3628"},
{file = "greenlet-3.5.0-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54d243512da35485fc7a6bf3c178fdda6327a9d6506fcdd62b1abd1e41b2927b"},
{file = "greenlet-3.5.0-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:41353ec2ecedf7aa8f682753a41919f8718031a6edac46b8d3dc7ed9e1ceb136"},
{file = "greenlet-3.5.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d280a7f5c331622c69f97eb167f33577ff2d1df282c41cd15907fc0a3ca198c"},
{file = "greenlet-3.5.0-cp312-cp312-manylinux_2_39_riscv64.whl", hash = "sha256:58c1c374fe2b3d852f9b6b11a7dff4c85404e51b9a596fd9e89cf904eb09866d"},
{file = "greenlet-3.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1eb67d5adefb5bd2e182d42678a328979a209e4e82eb93575708185d31d1f588"},
{file = "greenlet-3.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2628d6c86f6cb0cb45e0c3c54058bbec559f57eaae699447748cb3928150577e"},
{file = "greenlet-3.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:d4d9f0624c775f2dfc56ba54d515a8c771044346852a918b405914f6b19d7fd8"},
{file = "greenlet-3.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:83ed9f27f1680b50e89f40f6df348a290ea234b249a4003d366663a12eab94f2"},
{file = "greenlet-3.5.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:5a5ed18de6a0f6cc7087f1563f6bd93fc7df1c19165ca01e9bde5a5dc281d106"},
{file = "greenlet-3.5.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a717fbc46d8a354fa675f7c1e813485b6ba3885f9bef0cd56e5ba27d758ff5b"},
{file = "greenlet-3.5.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ddc090c5c1792b10246a78e8c2163ebbe04cf877f9d785c230a7b27b39ad038e"},
{file = "greenlet-3.5.0-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4964101b8585c144cbda5532b1aa644255126c08a265dae90c16e7a0e63aaa9d"},
{file = "greenlet-3.5.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2094acd54b272cb6eae8c03dd87b3fa1820a4cef18d6889c378d503500a1dc13"},
{file = "greenlet-3.5.0-cp313-cp313-manylinux_2_39_riscv64.whl", hash = "sha256:7022615368890680e67b9965d33f5773aade330d5343bbe25560135aaa849eae"},
{file = "greenlet-3.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5e05ba267789ea87b5a155cf0e810b1ab88bf18e9e8740813945ceb8ee4350ba"},
{file = "greenlet-3.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0ecec963079cd58cbd14723582384f11f166fd58883c15dcbfb342e0bc9b5846"},
{file = "greenlet-3.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:728d9667d8f2f586644b748dbd9bb67e50d6a9381767d1357714ea6825bb3bf5"},
{file = "greenlet-3.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:47422135b1d308c14b2c6e758beedb1acd33bb91679f5670edf77bf46244722b"},
{file = "greenlet-3.5.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:f35807464c4c58c55f0d31dfa83c541a5615d825c2fe3d2b95360cf7c4e3c0a8"},
{file = "greenlet-3.5.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55fa7ea52771be44af0de27d8b80c02cd18c2c3cddde6c847ecebdf72418b6a1"},
{file = "greenlet-3.5.0-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a97e4821aa710603f94de0da25f25096454d78ffdace5dc77f3a006bc01abba3"},
{file = "greenlet-3.5.0-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bf2d8a80bec89ab46221ae45c5373d5ba0bd36c19aa8508e85c6cd7e5106cd37"},
{file = "greenlet-3.5.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f52a464e4ed91780bdfbbdd2b97197f3accaa629b98c200f4dffada759f3ae7"},
{file = "greenlet-3.5.0-cp314-cp314-manylinux_2_39_riscv64.whl", hash = "sha256:1bae92a1dd94c5f9d9493c3a212dd874c202442047cf96446412c862feca83a2"},
{file = "greenlet-3.5.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:762612baf1161ccb8437c0161c668a688223cba28e1bf038f4eb47b13e39ccdf"},
{file = "greenlet-3.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:57a43c6079a89713522bc4bcb9f75070ecf5d3dbad7792bfe42239362cbf2a16"},
{file = "greenlet-3.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:3bc59be3945ae9750b9e7d45067d01ae3fe90ea5f9ade99239dabdd6e28a5033"},
{file = "greenlet-3.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:a96fcee45e03fe30a62669fd16ab5c9d3c172660d3085605cb1e2d1280d3c988"},
{file = "greenlet-3.5.0-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:a10a732421ab4fec934783ce3e54763470d0181db6e3468f9103a275c3ed1853"},
{file = "greenlet-3.5.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7fc391b1566f2907d17aaebe78f8855dc45675159a775fcf9e61f8ee0078e87f"},
{file = "greenlet-3.5.0-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:680bd0e7ad5e8daa8a4aa89f68fd6adc834b8a8036dc256533f7e08f4a4b01f7"},
{file = "greenlet-3.5.0-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1aa4ce8debcd4ea7fb2e150f3036588c41493d1d52c43538924ae1819003f4ce"},
{file = "greenlet-3.5.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ddb36c7d6c9c0a65f18c7258634e0c416c6ab59caac8c987b96f80c2ebda0112"},
{file = "greenlet-3.5.0-cp314-cp314t-manylinux_2_39_riscv64.whl", hash = "sha256:728a73687e39ae9ca34e4694cbf2f049d3fbc7174639468d0f67200a97d8f9e2"},
{file = "greenlet-3.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e5ddf316ced87539144621453c3aef229575825fe60c604e62bedc4003f372b2"},
{file = "greenlet-3.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4a448128607be0de65342dc9b31be7f948ef4cc0bc8832069350abefd310a8f2"},
{file = "greenlet-3.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d60097128cb0a1cab9ea541186ea13cd7b847b8449a7787c2e2350da0cb82d86"},
{file = "greenlet-3.5.0.tar.gz", hash = "sha256:d419647372241bc68e957bf38d5c1f98852155e4146bd1e4121adea81f4f01e4"},
]
[package.extras]
docs = ["Sphinx", "furo"]
test = ["objgraph", "psutil"]
[[package]]
name = "importlib-resources"
version = "6.4.5"
description = "Read resources from Python packages"
category = "main"
optional = false
python-versions = ">=3.8"
files = [
{file = "importlib_resources-6.4.5-py3-none-any.whl", hash = "sha256:ac29d5f956f01d5e4bb63102a5a19957f1b9175e45649977264a1416783bb717"},
{file = "importlib_resources-6.4.5.tar.gz", hash = "sha256:980862a1d16c9e147a59603677fa2aa5fd82b87f223b6cb870695bcfce830065"},
]
[package.dependencies]
zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""}
[package.extras]
check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"]
cover = ["pytest-cov"]
doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
enabler = ["pytest-enabler (>=2.2)"]
test = ["jaraco.test (>=5.4)", "pytest (>=6,!=8.1.*)", "zipp (>=3.17)"]
type = ["pytest-mypy"]
test = ["objgraph", "psutil", "setuptools"]
[[package]]
name = "jsonpath-ng"
version = "1.6.1"
description = "A final implementation of JSONPath for Python that aims to be standard compliant, including arithmetic and binary comparison operators and providing clear AST for metaprogramming."
category = "main"
optional = false
python-versions = "*"
groups = ["main"]
files = [
{file = "jsonpath-ng-1.6.1.tar.gz", hash = "sha256:086c37ba4917304850bd837aeab806670224d3f038fe2833ff593a672ef0a5fa"},
{file = "jsonpath_ng-1.6.1-py3-none-any.whl", hash = "sha256:8f22cd8273d7772eea9aaa84d922e0841aa36fdb8a2c6b7f6c3791a16a9bc0be"},
@@ -420,9 +384,9 @@ ply = "*"
name = "keystone-engine"
version = "0.9.2"
description = "Keystone assembler engine"
category = "main"
optional = false
python-versions = "*"
groups = ["main"]
files = [
{file = "keystone-engine-0.9.2.tar.gz", hash = "sha256:2f7af62dab0ce6c2732dbb4f31cfa2184a8a149e280b96b92ebc0db84c6e50f5"},
{file = "keystone_engine-0.9.2-py2.py3-none-macosx_10_14_x86_64.whl", hash = "sha256:dafcc3d9450c239cbc54148855b79c4b387777099c6d054005c835768cf955f2"},
@@ -436,9 +400,9 @@ files = [
name = "loguru"
version = "0.7.2"
description = "Python logging made (stupidly) simple"
category = "main"
optional = false
python-versions = ">=3.5"
groups = ["main"]
files = [
{file = "loguru-0.7.2-py3-none-any.whl", hash = "sha256:003d71e3d3ed35f0f8984898359d65b79e5b21943f78af86aa5491210429b8eb"},
{file = "loguru-0.7.2.tar.gz", hash = "sha256:e671a53522515f34fd406340ee968cb9ecafbc4b36c679da03c18fd8d0bd51ac"},
@@ -449,15 +413,15 @@ colorama = {version = ">=0.3.4", markers = "sys_platform == \"win32\""}
win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""}
[package.extras]
dev = ["Sphinx (==7.2.5)", "colorama (==0.4.5)", "colorama (==0.4.6)", "exceptiongroup (==1.1.3)", "freezegun (==1.1.0)", "freezegun (==1.2.2)", "mypy (==v0.910)", "mypy (==v0.971)", "mypy (==v1.4.1)", "mypy (==v1.5.1)", "pre-commit (==3.4.0)", "pytest (==6.1.2)", "pytest (==7.4.0)", "pytest-cov (==2.12.1)", "pytest-cov (==4.1.0)", "pytest-mypy-plugins (==1.9.3)", "pytest-mypy-plugins (==3.0.0)", "sphinx-autobuild (==2021.3.14)", "sphinx-rtd-theme (==1.3.0)", "tox (==3.27.1)", "tox (==4.11.0)"]
dev = ["Sphinx (==7.2.5) ; python_version >= \"3.9\"", "colorama (==0.4.5) ; python_version < \"3.8\"", "colorama (==0.4.6) ; python_version >= \"3.8\"", "exceptiongroup (==1.1.3) ; python_version >= \"3.7\" and python_version < \"3.11\"", "freezegun (==1.1.0) ; python_version < \"3.8\"", "freezegun (==1.2.2) ; python_version >= \"3.8\"", "mypy (==0.910) ; python_version < \"3.6\"", "mypy (==0.971) ; python_version == \"3.6\"", "mypy (==1.4.1) ; python_version == \"3.7\"", "mypy (==1.5.1) ; python_version >= \"3.8\"", "pre-commit (==3.4.0) ; python_version >= \"3.8\"", "pytest (==6.1.2) ; python_version < \"3.8\"", "pytest (==7.4.0) ; python_version >= \"3.8\"", "pytest-cov (==2.12.1) ; python_version < \"3.8\"", "pytest-cov (==4.1.0) ; python_version >= \"3.8\"", "pytest-mypy-plugins (==1.9.3) ; python_version >= \"3.6\" and python_version < \"3.8\"", "pytest-mypy-plugins (==3.0.0) ; python_version >= \"3.8\"", "sphinx-autobuild (==2021.3.14) ; python_version >= \"3.9\"", "sphinx-rtd-theme (==1.3.0) ; python_version >= \"3.9\"", "tox (==3.27.1) ; python_version < \"3.8\"", "tox (==4.11.0) ; python_version >= \"3.8\""]
[[package]]
name = "multiprocess"
version = "0.70.17"
description = "better multiprocessing and multithreading in Python"
category = "main"
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "multiprocess-0.70.17-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7ddb24e5bcdb64e90ec5543a1f05a39463068b6d3b804aa3f2a4e16ec28562d6"},
{file = "multiprocess-0.70.17-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d729f55198a3579f6879766a6d9b72b42d4b320c0dcb7844afb774d75b573c62"},
@@ -484,9 +448,9 @@ dill = ">=0.3.9"
name = "overrides"
version = "7.7.0"
description = "A decorator to automatically detect mismatch when overriding a method."
category = "main"
optional = false
python-versions = ">=3.6"
groups = ["main"]
files = [
{file = "overrides-7.7.0-py3-none-any.whl", hash = "sha256:c7ed9d062f78b8e4c1a7b70bd8796b35ead4d9f510227ef9c5dc7626c60d7e49"},
{file = "overrides-7.7.0.tar.gz", hash = "sha256:55158fa3d93b98cc75299b1e67078ad9003ca27945c76162c1c0766d6f91820a"},
@@ -496,9 +460,9 @@ files = [
name = "pefile"
version = "2024.8.26"
description = "Python PE parsing module"
category = "main"
optional = false
python-versions = ">=3.6.0"
groups = ["main"]
files = [
{file = "pefile-2024.8.26-py3-none-any.whl", hash = "sha256:76f8b485dcd3b1bb8166f1128d395fa3d87af26360c2358fb75b80019b957c6f"},
{file = "pefile-2024.8.26.tar.gz", hash = "sha256:3ff6c5d8b43e8c37bb6e6dd5085658d658a7a0bdcd20b6a07b1fcfc1c4e9d632"},
@@ -508,9 +472,9 @@ files = [
name = "pillow"
version = "10.4.0"
description = "Python Imaging Library (Fork)"
category = "main"
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "pillow-10.4.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:4d9667937cfa347525b319ae34375c37b9ee6b525440f3ef48542fcf66f2731e"},
{file = "pillow-10.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:543f3dc61c18dafb755773efc89aae60d06b6596a63914107f75459cf984164d"},
@@ -599,16 +563,16 @@ docs = ["furo", "olefile", "sphinx (>=7.3)", "sphinx-copybutton", "sphinx-inline
fpx = ["olefile"]
mic = ["olefile"]
tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"]
typing = ["typing-extensions"]
typing = ["typing-extensions ; python_version < \"3.10\""]
xmp = ["defusedxml"]
[[package]]
name = "ply"
version = "3.11"
description = "Python Lex & Yacc"
category = "main"
optional = false
python-versions = "*"
groups = ["main"]
files = [
{file = "ply-3.11-py2.py3-none-any.whl", hash = "sha256:096f9b8350b65ebd2fd1346b12452efe5b9607f7482813ffca50c22722a807ce"},
{file = "ply-3.11.tar.gz", hash = "sha256:00c7c1aaa88358b9c765b6d3000c6eec0ba42abca5351b095321aef446081da3"},
@@ -618,9 +582,9 @@ files = [
name = "prompt-toolkit"
version = "3.0.50"
description = "Library for building powerful interactive command lines in Python"
category = "main"
optional = false
python-versions = ">=3.8.0"
groups = ["main"]
files = [
{file = "prompt_toolkit-3.0.50-py3-none-any.whl", hash = "sha256:9b6427eb19e479d98acff65196a307c555eb567989e6d88ebbb1b509d9779198"},
{file = "prompt_toolkit-3.0.50.tar.gz", hash = "sha256:544748f3860a2623ca5cd6d2795e7a14f3d0e1c3c9728359013f79877fc89bab"},
@@ -633,9 +597,10 @@ wcwidth = "*"
name = "pycparser"
version = "2.22"
description = "C parser in Python"
category = "main"
optional = false
python-versions = ">=3.8"
groups = ["main"]
markers = "platform_python_implementation == \"CPython\" and sys_platform == \"win32\""
files = [
{file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"},
{file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"},
@@ -645,9 +610,9 @@ files = [
name = "pyelftools"
version = "0.32"
description = "Library for analyzing ELF files and DWARF debugging information"
category = "main"
optional = false
python-versions = "*"
groups = ["main"]
files = [
{file = "pyelftools-0.32-py3-none-any.whl", hash = "sha256:013df952a006db5e138b1edf6d8a68ecc50630adbd0d83a2d41e7f846163d738"},
{file = "pyelftools-0.32.tar.gz", hash = "sha256:6de90ee7b8263e740c8715a925382d4099b354f29ac48ea40d840cf7aa14ace5"},
@@ -657,9 +622,9 @@ files = [
name = "pyfiglet"
version = "1.0.2"
description = "Pure-python FIGlet implementation"
category = "main"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
{file = "pyfiglet-1.0.2-py3-none-any.whl", hash = "sha256:889b351d79c99e50a3f619c8f8e6ffdb27fd8c939fc43ecbd7559bd57d5f93ea"},
{file = "pyfiglet-1.0.2.tar.gz", hash = "sha256:758788018ab8faaddc0984e1ea05ff330d3c64be663c513cc1f105f6a3066dab"},
@@ -669,9 +634,9 @@ files = [
name = "pyperclip"
version = "1.9.0"
description = "A cross-platform clipboard module for Python. (Only handles plain text for now.)"
category = "main"
optional = false
python-versions = "*"
groups = ["main"]
files = [
{file = "pyperclip-1.9.0.tar.gz", hash = "sha256:b7de0142ddc81bfc5c7507eea19da920b92252b548b96186caf94a5e2527d310"},
]
@@ -680,9 +645,9 @@ files = [
name = "python-fx"
version = "0.3.2"
description = "A python-native fx-alike terminal JSON viewer."
category = "main"
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "python_fx-0.3.2-py3-none-any.whl", hash = "sha256:5498475b0f391b1649732328b58d188d9fc4b3f90f5bfb77d5c6e2ece2432c5f"},
{file = "python_fx-0.3.2.tar.gz", hash = "sha256:9646f58c716e2db6698bff3dfa55fa721b8b0cb741506287a87bc08055a96ceb"},
@@ -711,9 +676,9 @@ yamale = {version = "5.2.1", markers = "python_version >= \"3.8\""}
name = "python-registry"
version = "1.3.1"
description = "Read access to Windows Registry files."
category = "main"
optional = false
python-versions = "*"
groups = ["main"]
files = [
{file = "python-registry-1.3.1.tar.gz", hash = "sha256:99185f67d5601be3e7843e55902d5769aea1740869b0882f34ff1bd4b43b1eb2"},
{file = "python_registry-1.3.1-py2-none-any.whl", hash = "sha256:59d3b00c04bca0c4e1a12be0404da6ccf76b87537ee3a3ad2d8fc1bccf6f63ca"},
@@ -728,9 +693,10 @@ unicodecsv = "*"
name = "pywin32"
version = "308"
description = "Python for Window Extensions"
category = "main"
optional = false
python-versions = "*"
groups = ["main"]
markers = "sys_platform == \"win32\""
files = [
{file = "pywin32-308-cp310-cp310-win32.whl", hash = "sha256:796ff4426437896550d2981b9c2ac0ffd75238ad9ea2d3bfa67a1abd546d262e"},
{file = "pywin32-308-cp310-cp310-win_amd64.whl", hash = "sha256:4fc888c59b3c0bef905ce7eb7e2106a07712015ea1c8234b703a088d46110e8e"},
@@ -756,9 +722,9 @@ files = [
name = "pyyaml"
version = "6.0.2"
description = "YAML parser and emitter for Python"
category = "main"
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"},
{file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"},
@@ -819,9 +785,9 @@ files = [
name = "questionary"
version = "2.1.0"
description = "Python library to build pretty command line user prompts ⭐️"
category = "main"
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "questionary-2.1.0-py3-none-any.whl", hash = "sha256:44174d237b68bc828e4878c763a9ad6790ee61990e0ae72927694ead57bab8ec"},
{file = "questionary-2.1.0.tar.gz", hash = "sha256:6302cdd645b19667d8f6e6634774e9538bfcd1aad9be287e743d96cacaf95587"},
@@ -834,9 +800,10 @@ prompt_toolkit = ">=2.0,<4.0"
name = "r2libr"
version = "5.7.4"
description = "Yet anohter radare2 python bindings."
category = "main"
optional = true
python-versions = ">=3.6"
groups = ["main"]
markers = "extra == \"re\""
files = [
{file = "r2libr-5.7.4-py3-none-macosx_10_15_universal2.whl", hash = "sha256:c752e57085fed9d34527d2ff6692068c2a162c3477c0539e9e61cd7eb5acdcfc"},
{file = "r2libr-5.7.4-py3-none-manylinux1_x86_64.whl", hash = "sha256:80df7902492de77e2cb770ecb7ffbe215908192962d95f0e43b42d3c3d464096"},
@@ -847,30 +814,30 @@ files = [
name = "setuptools"
version = "75.3.0"
description = "Easily download, build, install, upgrade, and uninstall Python packages"
category = "main"
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "setuptools-75.3.0-py3-none-any.whl", hash = "sha256:f2504966861356aa38616760c0f66568e535562374995367b4e69c7143cf6bcd"},
{file = "setuptools-75.3.0.tar.gz", hash = "sha256:fba5dd4d766e97be1b1681d98712680ae8f2f26d7881245f2ce9e40714f1a686"},
]
[package.extras]
check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.5.2)"]
core = ["importlib-metadata (>=6)", "importlib-resources (>=5.10.2)", "jaraco.collections", "jaraco.functools", "jaraco.text (>=3.7)", "more-itertools", "more-itertools (>=8.8)", "packaging", "packaging (>=24)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"]
check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.5.2) ; sys_platform != \"cygwin\""]
core = ["importlib-metadata (>=6) ; python_version < \"3.10\"", "importlib-resources (>=5.10.2) ; python_version < \"3.9\"", "jaraco.collections", "jaraco.functools", "jaraco.text (>=3.7)", "more-itertools", "more-itertools (>=8.8)", "packaging", "packaging (>=24)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "wheel (>=0.43.0)"]
cover = ["pytest-cov"]
doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"]
enabler = ["pytest-enabler (>=2.2)"]
test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test (>=5.5)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"]
type = ["importlib-metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (>=1.12.0,<1.13.0)", "pytest-mypy"]
test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test (>=5.5)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf ; sys_platform != \"cygwin\"", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"]
type = ["importlib-metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (==1.12.*)", "pytest-mypy"]
[[package]]
name = "termcolor"
version = "2.4.0"
description = "ANSI color formatting for output in terminal"
category = "main"
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "termcolor-2.4.0-py3-none-any.whl", hash = "sha256:9297c0df9c99445c2412e832e882a7884038a25617c60cea2ad69488d4040d63"},
{file = "termcolor-2.4.0.tar.gz", hash = "sha256:aab9e56047c8ac41ed798fa36d892a37aca6b3e9159f3e0c24bc64a9b3ac7b7a"},
@@ -883,9 +850,9 @@ tests = ["pytest", "pytest-cov"]
name = "typing-extensions"
version = "4.12.2"
description = "Backported and Experimental Type Hints for Python 3.8+"
category = "main"
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"},
{file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"},
@@ -895,9 +862,9 @@ files = [
name = "unicodecsv"
version = "0.14.1"
description = "Python2's stdlib csv module is nice, but it doesn't support unicode. This module is a drop-in replacement which *does*."
category = "main"
optional = false
python-versions = "*"
groups = ["main"]
files = [
{file = "unicodecsv-0.14.1.tar.gz", hash = "sha256:018c08037d48649a0412063ff4eda26eaa81eff1546dbffa51fa5293276ff7fc"},
]
@@ -906,9 +873,9 @@ files = [
name = "unicorn"
version = "2.1.3"
description = "Unicorn CPU emulator engine"
category = "main"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
groups = ["main"]
files = [
{file = "unicorn-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6cbf99c139a238ee6ccfaadea35e65a88461c0ae0dcf78058c8266ff90f8866c"},
{file = "unicorn-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a8e7b9396a7b76503b1d32c4b83d35e03e8b2ee81e80a2c7aee77dac7b71f25c"},
@@ -985,19 +952,17 @@ files = [
{file = "unicorn-2.1.3.tar.gz", hash = "sha256:0c06456cf550c228f2003cc70366afa4aece2e6e7e4c32d8f4b22c717ba6b729"},
]
[package.dependencies]
importlib_resources = {version = "*", markers = "python_version < \"3.9\""}
[package.extras]
test = ["capstone (==5.0.1)", "capstone (==6.0.0a2)"]
test = ["capstone (==5.0.1) ; python_version <= \"3.7\"", "capstone (==6.0.0a2) ; python_version > \"3.7\""]
[[package]]
name = "unicornafl"
version = "2.1.0"
description = "Unicornafl"
category = "main"
optional = true
python-versions = "*"
groups = ["main"]
markers = "platform_system != \"Windows\" and extra == \"fuzz\""
files = [
{file = "unicornafl-2.1.0-py3-none-macosx_11_7_x86_64.whl", hash = "sha256:8827c010376274730776b85f884cf9037d08ca6cd866b364bcfea8eee6db6090"},
{file = "unicornafl-2.1.0-py3-none-manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa3130b3c811291719874cfd7e728ce0c8147cc63c8853069a5195e528bafb43"},
@@ -1014,9 +979,9 @@ unicorn = ">=2.0.1"
name = "urwid"
version = "2.6.15"
description = "A full-featured console (xterm et al.) user interface library"
category = "main"
optional = false
python-versions = ">3.7"
groups = ["main"]
files = [
{file = "urwid-2.6.15-py3-none-any.whl", hash = "sha256:71b3171cabaa0092902f556768756bd2f2ebb24c0da287ee08f081d235340cb7"},
{file = "urwid-2.6.15.tar.gz", hash = "sha256:9ecc57330d88c8d9663ffd7092a681674c03ff794b6330ccfef479af7aa9671b"},
@@ -1027,7 +992,7 @@ typing-extensions = "*"
wcwidth = "*"
[package.extras]
curses = ["windows-curses"]
curses = ["windows-curses ; sys_platform == \"win32\""]
glib = ["PyGObject"]
lcd = ["pyserial"]
serial = ["pyserial"]
@@ -1040,9 +1005,9 @@ zmq = ["zmq"]
name = "wcwidth"
version = "0.2.13"
description = "Measures the displayed width of unicode strings in a terminal"
category = "main"
optional = false
python-versions = "*"
groups = ["main"]
files = [
{file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"},
{file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"},
@@ -1052,24 +1017,26 @@ files = [
name = "win32-setctime"
version = "1.2.0"
description = "A small Python utility to set file creation time on Windows"
category = "main"
optional = false
python-versions = ">=3.5"
groups = ["main"]
markers = "sys_platform == \"win32\""
files = [
{file = "win32_setctime-1.2.0-py3-none-any.whl", hash = "sha256:95d644c4e708aba81dc3704a116d8cbc974d70b3bdb8be1d150e36be6e9d1390"},
{file = "win32_setctime-1.2.0.tar.gz", hash = "sha256:ae1fdf948f5640aae05c511ade119313fb6a30d7eabe25fef9764dca5873c4c0"},
]
[package.extras]
dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"]
dev = ["black (>=19.3b0) ; python_version >= \"3.6\"", "pytest (>=4.6.2)"]
[[package]]
name = "windows-curses"
version = "2.4.1"
description = "Support for the standard curses module on Windows"
category = "main"
optional = false
python-versions = "*"
groups = ["main"]
markers = "platform_system == \"Windows\""
files = [
{file = "windows_curses-2.4.1-cp310-cp310-win32.whl", hash = "sha256:53d711e07194d0d3ff7ceff29e0955b35479bc01465d46c3041de67b8141db2f"},
{file = "windows_curses-2.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:325439cd4f37897a1de8a9c068a5b4c432f9244bf9c855ee2fbeb3fa721a770c"},
@@ -1093,9 +1060,9 @@ files = [
name = "yamale"
version = "5.2.1"
description = "A schema and validator for YAML."
category = "main"
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "yamale-5.2.1-py3-none-any.whl", hash = "sha256:e44cd30cf3055ee4b34c1c71d6fe35490a127dcbd36f82f27859d105a9989922"},
{file = "yamale-5.2.1.tar.gz", hash = "sha256:19bbe713d588f07177bc519a46070c0793ed126ea37f425a76055b99703f835a"},
@@ -1104,33 +1071,13 @@ files = [
[package.dependencies]
pyyaml = "*"
[[package]]
name = "zipp"
version = "3.20.2"
description = "Backport of pathlib-compatible object wrapper for zip files"
category = "main"
optional = false
python-versions = ">=3.8"
files = [
{file = "zipp-3.20.2-py3-none-any.whl", hash = "sha256:a817ac80d6cf4b23bf7f2828b7cabf326f15a001bea8b1f9b49631780ba28350"},
{file = "zipp-3.20.2.tar.gz", hash = "sha256:bc9eb26f4506fda01b81bcde0ca78103b6e62f991b381fec825435c836edbc29"},
]
[package.extras]
check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"]
cover = ["pytest-cov"]
doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
enabler = ["pytest-enabler (>=2.2)"]
test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"]
type = ["pytest-mypy"]
[[package]]
name = "zope-event"
version = "5.0"
description = "Very basic event publishing system"
category = "main"
optional = false
python-versions = ">=3.7"
groups = ["main"]
files = [
{file = "zope.event-5.0-py3-none-any.whl", hash = "sha256:2832e95014f4db26c47a13fdaef84cef2f4df37e66b59d8f1f4a8f319a632c26"},
{file = "zope.event-5.0.tar.gz", hash = "sha256:bac440d8d9891b4068e2b5a2c5e2c9765a9df762944bda6955f96bb9b91e67cd"},
@@ -1147,9 +1094,9 @@ test = ["zope.testrunner"]
name = "zope-interface"
version = "7.2"
description = "Interfaces for Python"
category = "main"
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "zope.interface-7.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ce290e62229964715f1011c3dbeab7a4a1e4971fd6f31324c4519464473ef9f2"},
{file = "zope.interface-7.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:05b910a5afe03256b58ab2ba6288960a2892dfeef01336dc4be6f1b9ed02ab0a"},
@@ -1199,10 +1146,10 @@ test = ["coverage[toml]", "zope.event", "zope.testing"]
testing = ["coverage[toml]", "zope.event", "zope.testing"]
[extras]
fuzz = ["unicornafl", "fuzzercorn"]
fuzz = ["fuzzercorn", "unicornafl"]
re = ["r2libr"]
[metadata]
lock-version = "2.0"
python-versions = "^3.8"
content-hash = "f0a6ca5220bfd011fc4c9e54e151f7752bcbd5b4cf3d80698cfc4f3a53665aa4"
lock-version = "2.1"
python-versions = "^3.10"
content-hash = "b65631e55580e06f60d535d41d2c2d0c4ee786609c7232988ceda8ce6b586a6d"
+5 -7
View File
@@ -1,16 +1,14 @@
[tool.poetry]
urls = { homepage = "https://qiling.io" }
name = "qiling"
version = "1.4.8"
version = "1.4.9"
description = "Qiling is an advanced binary emulation framework that cross-platform-architecture"
authors = ["KaiJern Lau (xwings) <[email protected]>"]
license = "GPLv2"
readme = "README.md"
classifiers = [
"Development Status :: 3 - Beta",
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"Topic :: Software Development :: Disassemblers",
"Topic :: System :: Emulators",
"Topic :: System :: Operating System",
@@ -27,14 +25,14 @@ keywords = [
]
[tool.poetry.dependencies]
python = "^3.8"
capstone = "^4"
python = "^3.10"
capstone = "^5"
unicorn = "2.1.3"
pefile = ">=2022.5.30"
python-registry = "^1.3.1"
keystone-engine = "^0.9.2"
pyelftools = ">=0.28"
gevent = ">=20.9.0"
gevent = ">=24.10"
multiprocess = ">=0.70.12.2"
windows-curses = { version = "^2.1.0", markers = "platform_system == 'Windows'" }
pyyaml = "^6.0.1"
+2 -1
View File
@@ -45,7 +45,8 @@ class QlArchARM64(QlArch):
**arm64_const.reg_map_q,
**arm64_const.reg_map_s,
**arm64_const.reg_map_w,
**arm64_const.reg_map_v
**arm64_const.reg_map_v,
**arm64_const.reg_map_fp
)
pc_reg = 'pc'
+6
View File
@@ -68,6 +68,7 @@ reg_map = {
"pc": UC_ARM64_REG_PC,
"lr": UC_ARM64_REG_LR,
"cpacr_el1": UC_ARM64_REG_CPACR_EL1,
"pstate": UC_ARM64_REG_PSTATE,
}
reg_map_b = {
@@ -313,3 +314,8 @@ reg_map_v = {
"v30": UC_ARM64_REG_V30,
"v31": UC_ARM64_REG_V31
}
reg_map_fp = {
"fpcr": UC_ARM64_REG_FPCR,
"fpsr": UC_ARM64_REG_FPSR
}
+7 -4
View File
@@ -48,7 +48,7 @@ class QlArchUtils:
return addr, '-'
def disassembler(self, ql: Qiling, address: int, size: int):
data = ql.mem.read(address, size)
data = memoryview(ql.mem.read(address, size))
# knowing that all binary sections are aligned to page boundary allows
# us to 'cheat' and search for the containing image using the aligned
@@ -64,11 +64,14 @@ class QlArchUtils:
ba, name = self.get_base_and_name(ql.mem.align(address))
anibbles = ql.arch.bits // 4
pos = 0
for insn in ql.arch.disassembler.disasm(data, address):
offset = insn.address - ba
for iaddr, isize, mnem, ops in ql.arch.disassembler.disasm_lite(data, address):
offset = iaddr - ba
ibytes = data[pos:pos + isize]
ql.log.info(f'{insn.address:0{anibbles}x} [{name:20s} + {offset:#08x}] {insn.bytes.hex(" "):20s} {insn.mnemonic:20s} {insn.op_str}')
ql.log.info(f'{iaddr:0{anibbles}x} [{name:20s} + {offset:#08x}] {ibytes.hex():22s} {mnem:16s} {ops}')
pos += isize
if ql.verbose >= QL_VERBOSE.DUMP:
for reg in ql.arch.regs.register_mapping:
+42 -6
View File
@@ -5,6 +5,7 @@ from typing import Optional
from qiling import Qiling
from qiling.arch.x86 import QlArchIntel
from qiling.arch.x86_const import *
from qiling.const import QL_ARCH
from qiling.exception import QlGDTError, QlMemoryMappedError
from qiling.os.memory import QlMemoryManager
@@ -60,6 +61,8 @@ class GDTManager:
# setup GDT by writing to GDTR
ql.arch.regs.write(UC_X86_REG_GDTR, (0, base, limit, 0x0))
self.is_long_mode = ql.arch.type is QL_ARCH.X8664
self.array = GDTArray(ql.mem, base, num_entries)
@staticmethod
@@ -93,7 +96,18 @@ class GDTManager:
return (idx << 3) | QL_X86_SEGSEL_TI_GDT | rpl
def register_gdt_segment(self, index: int, seg_base: int, seg_limit: int, access: int) -> int:
flags = QL_X86_F_OPSIZE_32
is_code = access & QL_X86_A_CODE
if is_code and self.is_long_mode:
# If this is a code segment and 64-bit long mode is enabled,
# then set the long segment descriptor bit.
# This prevents some strange CPU errors encountered with
# intra-privilege level IRET instructions used for
# context switching on 64-bit Windows.
flags = QL_X86_F_LONG
else:
# Otherwise, OPSIZE_32 should be set.
flags = QL_X86_F_OPSIZE_32
# is this a huge segment?
if seg_limit > (1 << 16):
@@ -138,16 +152,21 @@ class SegmentManager:
class SegmentManager86(SegmentManager):
def setup_cs_ds_ss_es(self, base: int, size: int) -> None:
# While debugging the linux kernel segment, the cs segment was found on the third segment of gdt.
# TODO: 64-bit code segment access bits were adjusted, removing the conforming bit.
# Perhaps make the same change for x86?
access = QL_X86_A_PRESENT | QL_X86_A_PRIV_3 | QL_X86_A_DESC_CODE | QL_X86_A_CODE | QL_X86_A_CODE_C | QL_X86_A_CODE_R
# While debugging the linux kernel segment, the cs segment was found on the third segment of gdt.
selector = self.gdtm.register_gdt_segment(3, base, size - 1, access)
self.arch.regs.cs = selector
# TODO : The section permission here should be QL_X86_A_PRIV_3, but I do n’t know why it can only be set to QL_X86_A_PRIV_0.
# TODO: 64-bit data segment access bits were adjusted, removing the direction bit.
# After this change, there were no problems changing the privilege level to ring 3.
# Perhaps make the same change for x86?
access = QL_X86_A_PRESENT | QL_X86_A_PRIV_0 | QL_X86_A_DESC_DATA | QL_X86_A_DATA | QL_X86_A_DATA_E | QL_X86_A_DATA_W
# While debugging the Linux kernel segment, I found that the three segments DS, SS, and ES all point to the same location in the GDT table.
# This position is the fifth segment table of GDT.
access = QL_X86_A_PRESENT | QL_X86_A_PRIV_0 | QL_X86_A_DESC_DATA | QL_X86_A_DATA | QL_X86_A_DATA_E | QL_X86_A_DATA_W
selector = self.gdtm.register_gdt_segment(5, base, size - 1, access)
self.arch.regs.ds = selector
@@ -169,15 +188,32 @@ class SegmentManager86(SegmentManager):
class SegmentManager64(SegmentManager):
def setup_cs_ds_ss_es(self, base: int, size: int) -> None:
# Code segment access bits:
# * QL_X86_A_PRESENT : Present
# * QL_X86_A_PRIV_3 : Ring 3 (user-mode)
# * QL_X86_A_DESC_CODE : Segment describes a code segment
# * QL_X86_A_CODE : Executable bit set
# * QL_X86_A_CODE_R : Readable
# Not set:
# * QL_X86_A_CODE_C : Conforming bit
# -> unset means code in this segment can only be executed from the ring set in DPL.
access = QL_X86_A_PRESENT | QL_X86_A_PRIV_3 | QL_X86_A_DESC_CODE | QL_X86_A_CODE | QL_X86_A_CODE_R
# While debugging the linux kernel segment, the cs segment was found on the sixth segment of gdt.
access = QL_X86_A_PRESENT | QL_X86_A_PRIV_3 | QL_X86_A_DESC_CODE | QL_X86_A_CODE | QL_X86_A_CODE_C | QL_X86_A_CODE_R
selector = self.gdtm.register_gdt_segment(6, base, size - 1, access)
self.arch.regs.cs = selector
# TODO : The section permission here should be QL_X86_A_PRIV_3, but I do n’t know why it can only be set to QL_X86_A_PRIV_0.
# Data segment access bits:
# * QL_X86_A_PRESENT : Present
# * QL_X86_A_PRIV_3 : Ring 3 (user-mode)
# * QL_X86_A_DESC_DATA : Segment describes a data segment
# * QL_X86_A_DATA : Executable bit NOT set
# * QL_X86_A_DATA_W : Writable
# Not set:
# * QL_X86_A_DATA_E : Direction bit
# -> unset means the data segment grows upward, rather than downward.
access = QL_X86_A_PRESENT | QL_X86_A_PRIV_3 | QL_X86_A_DESC_DATA | QL_X86_A_DATA | QL_X86_A_DATA_W
# When I debug the Linux kernel, I find that only the SS is set to the fifth segment table, and the rest are not set.
access = QL_X86_A_PRESENT | QL_X86_A_PRIV_0 | QL_X86_A_DESC_DATA | QL_X86_A_DATA | QL_X86_A_DATA_E | QL_X86_A_DATA_W
selector = self.gdtm.register_gdt_segment(5, base, size - 1, access)
# self.arch.regs.ds = selector
+6
View File
@@ -70,6 +70,12 @@ class QlCC:
raise NotImplementedError
def getReturnAddress(self) -> int:
"""Get function return address.
"""
raise NotImplementedError
def setReturnAddress(self, addr: int) -> None:
"""Set function return address.
+6 -1
View File
@@ -21,17 +21,22 @@ class QlArmBaseCC(QlCommonBaseCC):
def getNumSlots(argbits: int) -> int:
return 1
def getReturnAddress(self) -> int:
return self.arch.regs.lr
def setReturnAddress(self, addr: int) -> None:
self.arch.regs.lr = addr
def unwind(self, nslots: int) -> int:
# TODO: cleanup?
return self.arch.regs.lr
return self.getReturnAddress()
class aarch64(QlArmBaseCC):
_retreg = UC_ARM64_REG_X0
_argregs = make_arg_list(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_X6, UC_ARM64_REG_X7)
class aarch32(QlArmBaseCC):
_retreg = UC_ARM_REG_R0
_argregs = make_arg_list(UC_ARM_REG_R0, UC_ARM_REG_R1, UC_ARM_REG_R2, UC_ARM_REG_R3)
+3
View File
@@ -15,6 +15,9 @@ class QlIntelBaseCC(QlCommonBaseCC):
Supports arguments passing over registers and stack.
"""
def getReturnAddress(self) -> int:
return self.arch.stack_read(0)
def setReturnAddress(self, addr: int) -> None:
self.arch.stack_push(addr)
+3
View File
@@ -12,6 +12,9 @@ class mipso32(QlCommonBaseCC):
_shadow = 4
_retaddr_on_stack = False
def getReturnAddress(self) -> int:
return self.arch.regs.ra
def setReturnAddress(self, addr: int):
self.arch.regs.ra = addr
+3
View File
@@ -22,5 +22,8 @@ class ppc(QlCommonBaseCC):
def getNumSlots(argbits: int):
return 1
def getReturnAddress(self) -> int:
return self.arch.regs.lr
def setReturnAddress(self, addr: int):
self.arch.regs.lr = addr
+3
View File
@@ -22,5 +22,8 @@ class riscv(QlCommonBaseCC):
def getNumSlots(argbits: int):
return 1
def getReturnAddress(self) -> int:
return self.arch.regs.ra
def setReturnAddress(self, addr: int):
self.arch.regs.ra = addr
+25 -25
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
#
#
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
#
@@ -25,14 +25,14 @@ class QlCoreStructs:
QL_ENDIAN.EB: '>'
}[endian]
self._fmt8 = f'{modifier}B'
self._fmt8s = f'{modifier}b'
self._fmt16 = f'{modifier}H'
self._fmt16s = f'{modifier}h'
self._fmt32 = f'{modifier}I'
self._fmt32s = f'{modifier}i'
self._fmt64 = f'{modifier}Q'
self._fmt64s = f'{modifier}q'
self._fmt8 = struct.Struct(f'{modifier}B')
self._fmt8s = struct.Struct(f'{modifier}b')
self._fmt16 = struct.Struct(f'{modifier}H')
self._fmt16s = struct.Struct(f'{modifier}h')
self._fmt32 = struct.Struct(f'{modifier}I')
self._fmt32s = struct.Struct(f'{modifier}i')
self._fmt64 = struct.Struct(f'{modifier}Q')
self._fmt64s = struct.Struct(f'{modifier}q')
handlers = {
64 : (self.pack64, self.pack64s, self.unpack64, self.unpack64s),
@@ -51,49 +51,49 @@ class QlCoreStructs:
self.unpacks = ups
def pack64(self, x: int, /) -> bytes:
return struct.pack(self._fmt64, x)
return self._fmt64.pack(x)
def pack64s(self, x: int, /) -> bytes:
return struct.pack(self._fmt64s, x)
return self._fmt64s.pack(x)
def unpack64(self, x: ReadableBuffer, /) -> int:
return struct.unpack(self._fmt64, x)[0]
return self._fmt64.unpack(x)[0]
def unpack64s(self, x: ReadableBuffer, /) -> int:
return struct.unpack(self._fmt64s, x)[0]
return self._fmt64s.unpack(x)[0]
def pack32(self, x: int, /) -> bytes:
return struct.pack(self._fmt32, x)
return self._fmt32.pack(x)
def pack32s(self, x: int, /) -> bytes:
return struct.pack(self._fmt32s, x)
return self._fmt32s.pack(x)
def unpack32(self, x: ReadableBuffer, /) -> int:
return struct.unpack(self._fmt32, x)[0]
return self._fmt32.unpack(x)[0]
def unpack32s(self, x: ReadableBuffer, /) -> int:
return struct.unpack(self._fmt32s, x)[0]
return self._fmt32s.unpack(x)[0]
def pack16(self, x: int, /) -> bytes:
return struct.pack(self._fmt16, x)
return self._fmt16.pack(x)
def pack16s(self, x: int, /) -> bytes:
return struct.pack(self._fmt16s, x)
return self._fmt16s.pack(x)
def unpack16(self, x: ReadableBuffer, /) -> int:
return struct.unpack(self._fmt16, x)[0]
return self._fmt16.unpack(x)[0]
def unpack16s(self, x: ReadableBuffer, /) -> int:
return struct.unpack(self._fmt16s, x)[0]
return self._fmt16s.unpack(x)[0]
def pack8(self, x: int, /) -> bytes:
return struct.pack(self._fmt8, x)
return self._fmt8.pack(x)
def pack8s(self, x: int, /) -> bytes:
return struct.pack(self._fmt8s, x)
return self._fmt8s.pack(x)
def unpack8(self, x: ReadableBuffer, /) -> int:
return struct.unpack(self._fmt8, x)[0]
return self._fmt8.unpack(x)[0]
def unpack8s(self, x: ReadableBuffer, /) -> int:
return struct.unpack(self._fmt8s, x)[0]
return self._fmt8s.unpack(x)[0]
-2
View File
@@ -1,3 +1 @@
from .debugger import QlDebugger
# from .disassember import QlDisassember
# from .utils import QlReadELF
-55
View File
@@ -1,55 +0,0 @@
#!/usr/bin/env python3
#
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
#
from elftools.elf.elffile import ELFFile
from qiling import Qiling
from qiling.const import *
from capstone import *
class QlDisassember():
def __init__(self, ql:Qiling):
self.ql = ql
def disasm_all_lines(self):
disasm_result = []
if self.ql.os.type == QL_OS.LINUX:
disasm_result = self.disasm_elf()
return disasm_result
def disasm_elf(self, seg_name='.text'):
def disasm(ql, address, size):
md = ql.arch.disassembler
md.detail = True
return md.disasm(ql.mem.read(address, size), address)
disasm_result = []
if self.ql.arch.type == QL_ARCH.X86:
BASE = int(self.ql.profile.get("OS32", "load_address"), 16)
seg_start = 0x0
seg_end = 0x0
f = open(self.ql.path, 'rb')
elffile = ELFFile(f)
elf_header = elffile.header
reladyn = elffile.get_section_by_name(seg_name)
# No PIE
if elf_header['e_type'] == 'ET_EXEC':
seg_start = reladyn.header.sh_addr
seg_end = seg_start + reladyn.data_size
# PIE
elif elf_header['e_type'] == 'ET_DYN':
seg_start = BASE + reladyn.header.sh_addr
seg_end = seg_start + reladyn.data_size
for insn in disasm(ql, seg_start, seg_end-seg_start):
disasm_result.append(insn)
return disasm_result
+3 -1
View File
@@ -183,6 +183,7 @@ class QlGdb(QlDebugger):
from unicorn.arm_const import UC_ARM_REG_R11
from unicorn.arm64_const import UC_ARM64_REG_X29
from unicorn.mips_const import UC_MIPS_REG_INVALID
from unicorn.ppc_const import UC_PPC_REG_31
arch_uc_bp = {
QL_ARCH.X86 : UC_X86_REG_EBP,
@@ -191,7 +192,8 @@ class QlGdb(QlDebugger):
QL_ARCH.ARM64 : UC_ARM64_REG_X29,
QL_ARCH.MIPS : UC_MIPS_REG_INVALID, # skipped
QL_ARCH.A8086 : UC_X86_REG_EBP,
QL_ARCH.CORTEX_M : UC_ARM_REG_R11
QL_ARCH.CORTEX_M : UC_ARM_REG_R11,
QL_ARCH.PPC : UC_PPC_REG_31
}[self.ql.arch.type]
def __get_reg_idx(ucreg: int) -> int:
@@ -25,4 +25,10 @@
<reg name="pc" bitsize="32" type="code_ptr"/>
<reg name="xpsr" bitsize="32" regnum="25"/>
</feature>
<reg name="msp" bitsize="32"/>
<reg name="psp" bitsize="32"/>
<reg name="primask" bitsize="32"/>
<reg name="basepri" bitsize="32"/>
<reg name="faultmask" bitsize="32"/>
<reg name="control" bitsize="32"/>
</feature>
@@ -0,0 +1,12 @@
<?xml version="1.0"?>
<!-- Copyright (C) 2009-2016 Free Software Foundation, Inc.
*!Copying and distribution of this file, with or without modification,
*!are permitted in any medium without royalty provided the copyright
*!notice and this notice are preserved. -->
<!DOCTYPE target SYSTEM "gdb-target.dtd">
<target xmlns:xi="http://www.w3.org/2001/XInclude">
<architecture>armv7-m</architecture>
<xi:include href="arm-m-profile.xml"/>
</target>
+51
View File
@@ -0,0 +1,51 @@
<?xml version="1.0"?>
<!-- Copyright (C) 2007-2020 Free Software Foundation, Inc.
Copying and distribution of this file, with or without modification,
are permitted in any medium without royalty provided the copyright
notice and this notice are preserved. -->
<!DOCTYPE feature SYSTEM "gdb-target.dtd">
<feature name="org.gnu.gdb.power.core">
<reg name="r0" bitsize="32" type="uint32"/>
<reg name="r1" bitsize="32" type="uint32"/>
<reg name="r2" bitsize="32" type="uint32"/>
<reg name="r3" bitsize="32" type="uint32"/>
<reg name="r4" bitsize="32" type="uint32"/>
<reg name="r5" bitsize="32" type="uint32"/>
<reg name="r6" bitsize="32" type="uint32"/>
<reg name="r7" bitsize="32" type="uint32"/>
<reg name="r8" bitsize="32" type="uint32"/>
<reg name="r9" bitsize="32" type="uint32"/>
<reg name="r10" bitsize="32" type="uint32"/>
<reg name="r11" bitsize="32" type="uint32"/>
<reg name="r12" bitsize="32" type="uint32"/>
<reg name="r13" bitsize="32" type="uint32"/>
<reg name="r14" bitsize="32" type="uint32"/>
<reg name="r15" bitsize="32" type="uint32"/>
<reg name="r16" bitsize="32" type="uint32"/>
<reg name="r17" bitsize="32" type="uint32"/>
<reg name="r18" bitsize="32" type="uint32"/>
<reg name="r19" bitsize="32" type="uint32"/>
<reg name="r20" bitsize="32" type="uint32"/>
<reg name="r21" bitsize="32" type="uint32"/>
<reg name="r22" bitsize="32" type="uint32"/>
<reg name="r23" bitsize="32" type="uint32"/>
<reg name="r24" bitsize="32" type="uint32"/>
<reg name="r25" bitsize="32" type="uint32"/>
<reg name="r26" bitsize="32" type="uint32"/>
<reg name="r27" bitsize="32" type="uint32"/>
<reg name="r28" bitsize="32" type="uint32"/>
<reg name="r29" bitsize="32" type="uint32"/>
<reg name="r30" bitsize="32" type="uint32"/>
<reg name="r31" bitsize="32" type="uint32"/>
<reg name="cr" bitsize="32" type="uint32"/>
<reg name="lr" bitsize="32" type="code_ptr"/>
<reg name="pc" bitsize="32" type="code_ptr"/>
<reg name="msr" bitsize="32" type="uint32"/>
<reg name="ctr" bitsize="32" type="uint32"/>
<reg name="xer" bitsize="32" type="uint32"/>
</feature>
+12
View File
@@ -0,0 +1,12 @@
<?xml version="1.0"?>
<!-- Copyright (C) 2009-2016 Free Software Foundation, Inc.
*!Copying and distribution of this file, with or without modification,
*!are permitted in any medium without royalty provided the copyright
*!notice and this notice are preserved. -->
<!DOCTYPE target SYSTEM "gdb-target.dtd">
<target xmlns:xi="http://www.w3.org/2001/XInclude">
<architecture>powerpc:common</architecture>
<xi:include href="ppc-core.xml"/>
</target>
+17 -4
View File
@@ -13,13 +13,21 @@ from qiling.arch.arm_const import (
reg_map_q as arm_regs_q,
reg_map_s as arm_regs_s
)
from qiling.arch.cortex_m_const import (
reg_map as cortex_m_regs
)
from qiling.arch.arm64_const import (
reg_map as arm64_regs,
reg_map_v as arm64_regs_v
reg_map_v as arm64_regs_v,
reg_map_fp as arm64_reg_map_fp
)
from qiling.arch.mips_const import (
reg_map as mips_regs_gpr
)
from qiling.arch.x86_const import (
reg_map_32 as x86_regs_32,
reg_map_64 as x86_regs_64,
@@ -30,6 +38,10 @@ from qiling.arch.x86_const import (
reg_map_ymm as x86_regs_ymm
)
from qiling.arch.ppc_const import (
reg_map as ppc_regs
)
from qiling.const import QL_ARCH, QL_OS
RegEntry = Tuple[Optional[int], int, int]
@@ -132,9 +144,10 @@ class QlGdbFeatures:
QL_ARCH.X86: dict(**x86_regs_32, **x86_regs_misc, **x86_regs_cr, **x86_regs_st, **x86_regs_xmm),
QL_ARCH.X8664: dict(**x86_regs_64, **x86_regs_misc, **x86_regs_cr, **x86_regs_st, **x86_regs_xmm, **x86_regs_ymm),
QL_ARCH.ARM: dict(**arm_regs, **arm_regs_vfp, **arm_regs_q, **arm_regs_s),
QL_ARCH.CORTEX_M: arm_regs,
QL_ARCH.ARM64: dict(**arm64_regs, **arm64_regs_v),
QL_ARCH.MIPS: dict(**mips_regs_gpr)
QL_ARCH.CORTEX_M: dict(**cortex_m_regs),
QL_ARCH.ARM64: dict(**arm64_regs, **arm64_regs_v, **arm64_reg_map_fp),
QL_ARCH.MIPS: dict(**mips_regs_gpr),
QL_ARCH.PPC: dict(**ppc_regs)
}[archtype]
regsinfo = sorted(QlGdbFeatures.__walk_xml_regs(xmltree))
+2 -3
View File
@@ -3,7 +3,6 @@
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
#
from .arch_x86 import ArchX86
from .arch_mips import ArchMIPS
from .arch_arm import ArchARM, ArchCORTEX_M
from .arch_x8664 import ArchX8664
from .arch_intel import ArchIntel, ArchX86, ArchX64
from .arch_mips import ArchMIPS
+66 -17
View File
@@ -3,32 +3,81 @@
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
#
from typing import Collection, Dict, Mapping, Optional, TypeVar
from qiling.const import QL_ARCH
from unicorn import UC_ERR_READ_UNMAPPED
import unicorn
T = TypeVar('T')
class Arch:
"""
base class for arch
"""Arch base class.
"""
def __init__(self):
pass
def __init__(self, regs: Collection[str], swaps: Mapping[str, str], asize: int, isize: int) -> None:
"""Initialize architecture instance.
Args:
regs : collection of registers names to include in context
asize : native address size in bytes
isize : instruction size in bytes
swaps : readable register names alternatives, may be empty
"""
self._regs = regs
self._swaps = swaps
self._asize = asize
self._isize = isize
@property
def arch_insn_size(self):
return 4
def regs(self) -> Collection[str]:
"""Collection of registers names.
"""
return self._regs
@property
def archbit(self):
return 4
def isize(self) -> int:
"""Native instruction size.
"""
def read_insn(self, address: int):
try:
result = self.read_mem(address, self.arch_insn_size)
except unicorn.unicorn.UcError as err:
result = None
return self._isize
return result
@property
def asize(self) -> int:
"""Native pointer size.
"""
return self._asize
def swap_regs(self, mapping: Mapping[str, T]) -> Dict[str, T]:
"""Swap default register names with their aliases.
Args:
mapping: regsiters names mapped to their values
Returns: a new dictionary where all swappable names were swapped with their aliases
"""
return {self._swaps.get(k, k): v for k, v in mapping.items()}
def unalias(self, name: str) -> str:
"""Get original register name for the specified alias.
Args:
name: aliaes register name
Returns: original name of aliased register, or same name if not an alias
"""
# perform a reversed lookup in swaps to find the original name for given alias
return next((org for org, alt in self._swaps.items() if name == alt), name)
def read_insn(self, address: int) -> Optional[bytearray]:
"""Read a single instruction from given address.
Args:
address: memory address to read from
Returns: instruction bytes, or None if memory could not be read
"""
return self.try_read_mem(address, self.isize)
+112 -72
View File
@@ -3,105 +3,145 @@
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
#
from typing import Mapping
from typing import ClassVar, Dict, Optional
from .arch import Arch
class ArchARM(Arch):
def __init__(self):
super().__init__()
self._regs = (
"r0", "r1", "r2", "r3",
"r4", "r5", "r6", "r7",
"r8", "r9", "r10", "r11",
"r12", "sp", "lr", "pc",
)
_flags_reg: ClassVar[str] = 'cpsr'
@property
def regs(self):
return self._regs
def __init__(self) -> None:
regs = (
'r0', 'r1', 'r2', 'r3',
'r4', 'r5', 'r6', 'r7',
'r8', 'r9', 'r10', 'r11',
'r12', 'sp', 'lr', 'pc'
)
@regs.setter
def regs(self, regs):
self._regs += regs
aliases = {
'r9' : 'sb',
'r10': 'sl',
'r12': 'ip',
'r11': 'fp'
}
@property
def regs_need_swapped(self):
return {
"sl": "r10",
"ip": "r12",
"fp": "r11",
}
asize = 4
isize = 4
super().__init__(regs, aliases, asize, isize)
@staticmethod
def get_flags(bits: int) -> Mapping[str, bool]:
"""
get flags for ARM
"""
def get_mode(bits: int) -> int:
"""
get operating mode for ARM
"""
return {
0b10000: "User",
0b10001: "FIQ",
0b10010: "IRQ",
0b10011: "Supervisor",
0b10110: "Monitor",
0b10111: "Abort",
0b11010: "Hypervisor",
0b11011: "Undefined",
0b11111: "System",
}.get(bits & 0x00001f)
def get_flags(bits: int) -> Dict[str, bool]:
return {
"mode": get_mode(bits),
"thumb": bits & 0x00000020 != 0,
"fiq": bits & 0x00000040 != 0,
"irq": bits & 0x00000080 != 0,
"neg": bits & 0x80000000 != 0,
"zero": bits & 0x40000000 != 0,
"carry": bits & 0x20000000 != 0,
"overflow": bits & 0x10000000 != 0,
}
'thumb': bits & (0b1 << 5) != 0,
'fiq': bits & (0b1 << 6) != 0,
'irq': bits & (0b1 << 7) != 0,
'overflow': bits & (0b1 << 28) != 0,
'carry': bits & (0b1 << 29) != 0,
'zero': bits & (0b1 << 30) != 0,
'neg': bits & (0b1 << 31) != 0
}
@staticmethod
def get_mode(bits: int) -> str:
modes = {
0b10000: 'User',
0b10001: 'FIQ',
0b10010: 'IRQ',
0b10011: 'Supervisor',
0b10110: 'Monitor',
0b10111: 'Abort',
0b11010: 'Hypervisor',
0b11011: 'Undefined',
0b11111: 'System'
}
return modes.get(bits & 0b11111, '?')
@property
def thumb_mode(self) -> bool:
"""
helper function for checking thumb mode
def is_thumb(self) -> bool:
"""Query whether the processor is currently in thumb mode.
"""
return self.ql.arch.is_thumb
@property
def isize(self) -> int:
return 2 if self.is_thumb else self._isize
def read_insn(self, address: int) -> bytes:
"""
read instruction depending on current operating mode
@staticmethod
def __is_wide_insn(data: bytes) -> bool:
"""Determine whether a sequence of bytes respresents a wide thumb instruction.
"""
def thumb_read(address: int) -> bytes:
assert len(data) in (2, 4), f'unexpected instruction length: {len(data)}'
first_two = self.ql.mem.read_ptr(address, 2)
result = self.ql.pack16(first_two)
# determine whether this is a wide instruction by inspecting the 5 most
# significant bits in the first half-word
return (data[1] >> 3) & 0b11111 in (0b11101, 0b11110, 0b11111)
# to judge it's thumb mode or not
if any([
first_two & 0xf000 == 0xf000,
first_two & 0xf800 == 0xf800,
first_two & 0xe800 == 0xe800,
]):
def __read_thumb_insn_fail(self, address: int) -> Optional[bytearray]:
"""A failsafe method for reading thumb instructions. This method is needed for
rare cases in which a narrow instruction is on a page boundary where the next
page is unavailable.
"""
latter_two = self.ql.mem.read_ptr(address+2, 2)
result += self.ql.pack16(latter_two)
lo_half = self.try_read_mem(address, 2)
return result
if lo_half is None:
return None
return super().read_insn(address) if not self.thumb_mode else thumb_read(address)
data = lo_half
if ArchARM.__is_wide_insn(data):
hi_half = self.try_read_mem(address + 2, 2)
# fail if higher half-word was required but could not be read
if hi_half is None:
return None
data.extend(hi_half)
return data
def __read_thumb_insn(self, address: int) -> Optional[bytearray]:
"""Read one instruction in thumb mode.
Thumb instructions may be either 2 or 4 bytes long, depending on encoding of
the first word. However, reading two chunks of two bytes each is slower. For
most cases reading all four bytes in advance will be safe and quicker.
"""
data = self.try_read_mem(address, 4)
if data is None:
# there is a slight chance we could not read 4 bytes because only 2
# are available. try the failsafe method to find out
return self.__read_thumb_insn_fail(address)
if ArchARM.__is_wide_insn(data):
return data
return data[:2]
def read_insn(self, address: int) -> Optional[bytearray]:
"""Read one instruction worth of bytes.
"""
if self.is_thumb:
return self.__read_thumb_insn(address)
return super().read_insn(address)
class ArchCORTEX_M(ArchARM):
_flags_reg: ClassVar[str] = 'xpsr'
def __init__(self):
super().__init__()
self.regs += ("xpsr", "control", "primask", "basepri", "faultmask")
self._regs += (
'xpsr', 'control', 'primask',
'basepri', 'faultmask'
)
+59
View File
@@ -0,0 +1,59 @@
#!/usr/bin/env python3
#
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
#
from typing import Collection, Dict
from .arch import Arch
class ArchIntel(Arch):
"""Arch base class for Intel architecture.
"""
def __init__(self, regs: Collection[str], asize: int) -> None:
super().__init__(regs, {}, asize, 15)
@staticmethod
def get_flags(bits: int) -> Dict[str, bool]:
return {
'CF' : bits & (0b1 << 0) != 0, # carry
'PF' : bits & (0b1 << 2) != 0, # parity
'AF' : bits & (0b1 << 4) != 0, # adjust
'ZF' : bits & (0b1 << 6) != 0, # zero
'SF' : bits & (0b1 << 7) != 0, # sign
'IF' : bits & (0b1 << 9) != 0, # interrupt enable
'DF' : bits & (0b1 << 10) != 0, # direction
'OF' : bits & (0b1 << 11) != 0 # overflow
}
@staticmethod
def get_iopl(bits: int) -> int:
return bits & (0b11 << 12)
class ArchX86(ArchIntel):
def __init__(self) -> None:
regs = (
'eax', 'ebx', 'ecx', 'edx',
'ebp', 'esp', 'esi', 'edi',
'eip', 'eflags' ,'ss', 'cs',
'ds', 'es', 'fs', 'gs'
)
super().__init__(regs, 4)
class ArchX64(ArchIntel):
def __init__(self) -> None:
regs = (
'rax', 'rbx', 'rcx', 'rdx',
'rbp', 'rsp', 'rsi', 'rdi',
'r8', 'r9', 'r10', 'r11',
'r12', 'r13', 'r14', 'r15',
'rip', 'eflags', 'ss', 'cs',
'ds', 'es', 'fs', 'gs'
)
super().__init__(regs, 8)
+19 -21
View File
@@ -3,29 +3,27 @@
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
#
from .arch import Arch
class ArchMIPS(Arch):
def __init__(self):
super().__init__()
def __init__(self) -> None:
regs = (
'gp', 'at', 'v0', 'v1',
'a0', 'a1', 'a2', 'a3',
't0', 't1', 't2', 't3',
't4', 't5', 't6', 't7',
't8', 't9', 'sp', 's8',
's0', 's1', 's2', 's3',
's4', 's5', 's6', 's7',
'ra', 'k0', 'k1', 'pc'
)
@property
def regs(self):
return (
"gp", "at", "v0", "v1",
"a0", "a1", "a2", "a3",
"t0", "t1", "t2", "t3",
"t4", "t5", "t6", "t7",
"t8", "t9", "sp", "s8",
"s0", "s1", "s2", "s3",
"s4", "s5", "s6", "s7",
"ra", "k0", "k1", "pc",
)
aliases = {
's8': 'fp'
}
@property
def regs_need_swapped(self):
return {
"fp": "s8",
}
asize = 4
isize = 4
super().__init__(regs, aliases, asize, isize)
-47
View File
@@ -1,47 +0,0 @@
#!/usr/bin/env python3
#
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
#
from typing import Mapping
from .arch import Arch
class ArchX86(Arch):
def __init__(self):
super().__init__()
@property
def arch_insn_size(self):
return 15
@property
def regs(self):
return (
"eax", "ebx", "ecx", "edx",
"esp", "ebp", "esi", "edi",
"eip", "ss", "cs", "ds", "es",
"fs", "gs", "eflags",
)
def read_insn(self, address: int) -> bytes:
# due to the variadic lengh of x86 instructions ( 1~15 )
# always assume the maxium size for disassembler to tell
# what is it exactly.
return self.read_mem(address, self.arch_insn_size)
@staticmethod
def get_flags(bits: int) -> Mapping[str, bool]:
"""
get flags from ql.reg.eflags
"""
return {
"CF" : bits & 0x0001 != 0, # CF, carry flag
"PF" : bits & 0x0004 != 0, # PF, parity flag
"AF" : bits & 0x0010 != 0, # AF, adjust flag
"ZF" : bits & 0x0040 != 0, # ZF, zero flag
"SF" : bits & 0x0080 != 0, # SF, sign flag
"OF" : bits & 0x0800 != 0, # OF, overflow flag
}
-66
View File
@@ -1,66 +0,0 @@
#!/usr/bin/env python3
#
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
#
from typing import Mapping
from .arch import Arch
class ArchX8664(Arch):
'''
This is currently mostly just a copy of x86 - other than the size of archbits. Some of this may be wrong.
'''
def __init__(self):
super().__init__()
@property
def arch_insn_size(self):
'''
Architecture maximum instruction size. x86_64 instructions are a maximum size of 15 bytes.
@returns bytes
'''
return 15
@property
def regs(self):
return (
"rax", "rbx", "rcx", "rdx",
"rsp", "rbp", "rsi", "rdi",
"rip", "r8", "r9", "r10",
"r11", "r12", "r13", "r14",
"r15", "ss", "cs", "ds", "es",
"fs", "gs", "eflags"
)
@property
def archbit(self):
'''
Architecture maximum register size. x86 is a maximum of 4 bytes.
@returns bytes
'''
return 8
def read_insn(self, address: int) -> bytes:
# Due to the variadicc length of x86 instructions
# always assume the maximum size for disassembler to tell
# what it is.
return self.read_mem(address, self.arch_insn_size)
@staticmethod
def get_flags(bits: int) -> Mapping[str, bool]:
return {
"CF" : bits & 0x0001 != 0, # CF, carry flag
"PF" : bits & 0x0004 != 0, # PF, parity flag
"AF" : bits & 0x0010 != 0, # AF, adjust flag
"ZF" : bits & 0x0040 != 0, # ZF, zero flag
"SF" : bits & 0x0080 != 0, # SF, sign flag
"OF" : bits & 0x0800 != 0, # OF, overflow flag
}
@@ -4,7 +4,13 @@
#
from .branch_predictor import BranchPredictor
from .branch_predictor_x86 import BranchPredictorX86
from .branch_predictor_mips import BranchPredictorMIPS
from .branch_predictor_arm import BranchPredictorARM, BranchPredictorCORTEX_M
from .branch_predictor_x8664 import BranchPredictorX8664
from .branch_predictor_intel import BranchPredictorX86, BranchPredictorX64
from .branch_predictor_mips import BranchPredictorMIPS
__all__ = [
'BranchPredictor',
'BranchPredictorARM', 'BranchPredictorCORTEX_M',
'BranchPredictorX86', 'BranchPredictorX64',
'BranchPredictorMIPS'
]
@@ -4,37 +4,82 @@
#
from abc import abstractmethod
from typing import ClassVar, NamedTuple, Optional
from capstone import CS_GRP_JUMP, CS_GRP_CALL, CS_GRP_RET, CS_GRP_BRANCH_RELATIVE
from ..context import Context
from ..misc import InvalidInsn
class Prophecy:
"""
container for storing result of the predictor
@going: indicate the certian branch will be taken or not
@where: where will it go if going is true
class Prophecy(NamedTuple):
"""Simple container for storing prediction results.
"""
def __init__(self):
self.going = False
self.where = None
going: bool
"""Indicate whether the certian branch is taken or not.
"""
where: Optional[int]
"""Branch target in case it is taken.
Target may be `None` if it should have been read from memory, but that memory location
could not be reached.
"""
def __iter__(self):
return iter((self.going, self.where))
class BranchPredictor(Context):
"""
Base class for predictor
"""Branch predictor base class.
"""
def read_reg(self, reg_name):
"""
read specific register value
stop: ClassVar[str]
"""Instruction mnemonic that can be used to determine program's end.
"""
def has_ended(self) -> bool:
"""Determine whether the program has ended by inspecting the currnet instruction.
"""
return self.ql.arch.regs.read(reg_name)
insn = self.disasm_lite(self.cur_addr)
if not insn:
return False
# (address, size, mnemonic, op_str)
return insn[2] == self.stop
def is_branch(self) -> bool:
"""Determine whether the current instruction is a branching instruction.
This does not provide indication whether the branch is going to be taken or not.
"""
insn = self.disasm(self.cur_addr, True)
# invalid instruction; definitely not a branch
if isinstance(insn, InvalidInsn):
return False
branching = (
CS_GRP_JUMP,
CS_GRP_CALL,
CS_GRP_RET,
CS_GRP_BRANCH_RELATIVE
)
return any(grp in branching for grp in insn.groups)
def is_fcall(self) -> bool:
"""Determine whether the current instruction is a function call.
"""
insn = self.disasm(self.cur_addr, True)
# invalid instruction; definitely not a function call
if isinstance(insn, InvalidInsn):
return False
return insn.group(CS_GRP_CALL)
@abstractmethod
def predict(self) -> Prophecy:
"""
Try to predict certian branch will be taken or not based on current context
"""Predict whether a certian branch will be taken or not based on current context.
"""
@@ -3,255 +3,264 @@
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
#
from typing import Callable, Dict, List, Optional, Tuple
from capstone import CS_OP_IMM, CS_OP_MEM, CS_OP_REG
from capstone.arm import ArmOp, ArmOpMem
from capstone.arm_const import (
ARM_CC_EQ, ARM_CC_NE, ARM_CC_HS, ARM_CC_LO,
ARM_CC_MI, ARM_CC_PL, ARM_CC_VS, ARM_CC_VC,
ARM_CC_HI, ARM_CC_LS, ARM_CC_GE, ARM_CC_LT,
ARM_CC_GT, ARM_CC_LE, ARM_CC_AL
)
from .branch_predictor import *
from ..arch import ArchARM
from ..misc import read_int
from unicorn.arm_const import UC_ARM_REG_PC
from .branch_predictor import BranchPredictor, Prophecy
from ..arch import ArchARM, ArchCORTEX_M
from ..misc import InvalidInsn
class BranchPredictorARM(BranchPredictor, ArchARM):
"""
predictor for ARM
"""Branch Predictor for ARM.
"""
def __init__(self, ql):
super().__init__(ql)
ArchARM.__init__(self)
stop = 'udf'
self.INST_SIZE = 4
self.THUMB_INST_SIZE = 2
self.CODE_END = "udf"
def read_reg(self, reg_name):
reg_name = reg_name.replace("ip", "r12").replace("fp", "r11")
return getattr(self.ql.arch.regs, reg_name)
def regdst_eq_pc(self, op_str):
return op_str.partition(", ")[0] == "pc"
@staticmethod
def get_cpsr(bits: int) -> (bool, bool, bool, bool):
"""
get flags from ql.reg.cpsr
def get_cond_flags(self) -> Tuple[bool, bool, bool, bool]:
"""Get condition status flags from CPSR / xPSR.
"""
cpsr = self.read_reg(self._flags_reg)
return (
bits & 0x10000000 != 0, # V, overflow flag
bits & 0x20000000 != 0, # C, carry flag
bits & 0x40000000 != 0, # Z, zero flag
bits & 0x80000000 != 0, # N, sign flag
)
(cpsr & (0b1 << 28)) != 0, # V, overflow flag
(cpsr & (0b1 << 29)) != 0, # C, carry flag
(cpsr & (0b1 << 30)) != 0, # Z, zero flag
(cpsr & (0b1 << 31)) != 0 # N, sign flag
)
def predict(self, pref_addr=None):
prophecy = Prophecy()
cur_addr = self.cur_addr if pref_addr is None else pref_addr
line = self.disasm(cur_addr)
def predict(self) -> Prophecy:
insn = self.disasm(self.cur_addr, True)
if line.mnemonic == self.CODE_END: # indicates program exited
prophecy.where = True
return prophecy
going = False
where = 0
jump_table = {
# unconditional branch
"b" : (lambda *_: True),
"bl" : (lambda *_: True),
"bx" : (lambda *_: True),
"blx" : (lambda *_: True),
"b.w" : (lambda *_: True),
# invalid instruction; nothing to predict
if isinstance(insn, InvalidInsn):
return Prophecy(going, where)
# branch on equal, Z == 1
"beq" : (lambda V, C, Z, N: Z == 1),
"bxeq" : (lambda V, C, Z, N: Z == 1),
"beq.w": (lambda V, C, Z, N: Z == 1),
# iname is the instruction's basename stripped from all optional suffixes.
# this greatly simplifies the case handling
iname: str = insn.insn_name() or ''
operands: List[ArmOp] = insn.operands
# branch on not equal, Z == 0
"bne" : (lambda V, C, Z, N: Z == 0),
"bxne" : (lambda V, C, Z, N: Z == 0),
"bne.w": (lambda V, C, Z, N: Z == 0),
# branch instructions
branches = ('b', 'bl', 'bx', 'blx')
# branch on signed greater than, Z == 0 and N == V
"bgt" : (lambda V, C, Z, N: (Z == 0 and N == V)),
"bgt.w": (lambda V, C, Z, N: (Z == 0 and N == V)),
# reg-based conditional branches
conditional_reg: Dict[str, Callable[[int], bool]] = {
'cbz' : lambda r: r == 0,
'cbnz': lambda r: r != 0
}
# branch on signed less than, N != V
"blt" : (lambda V, C, Z, N: N != V),
def __read_reg(reg: int) -> Optional[int]:
"""[internal] Read register value where register is provided as a Unicorn constant.
"""
# branch on signed greater than or equal, N == V
"bge" : (lambda V, C, Z, N: N == V),
# name will be None in case of an invalid register. this is expected in some cases
# and should not raise an exception, but rather silently dropped
name = insn.reg_name(reg)
# branch on signed less than or queal
"ble" : (lambda V, C, Z, N: Z == 1 or N != V),
# pc reg value needs adjustment
adj = (2 * self.isize) if reg == UC_ARM_REG_PC else 0
# branch on unsigned higher or same (or carry set), C == 1
"bhs" : (lambda V, C, Z, N: C == 1),
"bcs" : (lambda V, C, Z, N: C == 1),
return name and self.read_reg(self.unalias(name)) + adj
# branch on unsigned lower (or carry clear), C == 0
"bcc" : (lambda V, C, Z, N: C == 0),
"blo" : (lambda V, C, Z, N: C == 0),
"bxlo" : (lambda V, C, Z, N: C == 0),
"blo.w": (lambda V, C, Z, N: C == 0),
def __read_mem(mem: ArmOpMem, size: int = 0, *, signed: bool = False) -> Optional[int]:
"""[internal] Attempt to read memory contents. By default memory accesses are in
native size and values are unsigned.
"""
# branch on negative or minus, N == 1
"bmi" : (lambda V, C, Z, N: N == 1),
base = __read_reg(mem.base) or 0
index = __read_reg(mem.index) or 0
scale = mem.scale
disp = mem.disp
# branch on positive or plus, N == 0
"bpl" : (lambda V, C, Z, N: N == 0),
return self.try_read_pointer(base + index * scale + disp, size, signed=signed)
# branch on signed overflow
"bvs" : (lambda V, C, Z, N: V == 1),
def __parse_op(op: ArmOp, *args, **kwargs) -> Optional[int]:
"""[internal] Parse an operand and return its value. Register references will be
substitued with the corresponding register value, while memory dereferences will
be substitued by the effective address they refer to.
"""
# branch on no signed overflow
"bvc" : (lambda V, C, Z, N: V == 0),
if op.type == CS_OP_REG:
value = __read_reg(op.reg)
# branch on unsigned higher
"bhi" : (lambda V, C, Z, N: (Z == 0 and C == 1)),
"bxhi" : (lambda V, C, Z, N: (Z == 0 and C == 1)),
"bhi.w": (lambda V, C, Z, N: (Z == 0 and C == 1)),
elif op.type == CS_OP_IMM:
value = op.imm
# branch on unsigned lower
"bls" : (lambda V, C, Z, N: (C == 0 or Z == 1)),
"bls.w": (lambda V, C, Z, N: (C == 0 or Z == 1)),
}
elif op.type == CS_OP_MEM:
value = __read_mem(op.mem, *args, **kwargs)
cb_table = {
# branch on equal to zero
"cbz" : (lambda r: r == 0),
# branch on not equal to zero
"cbnz": (lambda r: r != 0),
}
if line.mnemonic in jump_table:
prophecy.going = jump_table.get(line.mnemonic)(*self.get_cpsr(self.ql.arch.regs.cpsr))
elif line.mnemonic in cb_table:
prophecy.going = cb_table.get(line.mnemonic)(self.read_reg(line.op_str.split(", ")[0]))
if prophecy.going:
if "#" in line.op_str:
prophecy.where = read_int(line.op_str.split("#")[-1])
else:
prophecy.where = self.read_reg(line.op_str)
# we are not expecting any other operand type, including floating point (CS_OP_FP)
raise RuntimeError(f'unexpected operand type: {op.type}')
if self.regdst_eq_pc(line.op_str):
next_addr = cur_addr + line.size
n2_addr = next_addr + len(self.read_insn(next_addr))
prophecy.where += len(self.read_insn(n2_addr)) + len(self.read_insn(next_addr))
# LSR
if op.shift.type == 1:
value *= (1 >> op.shift.value)
elif line.mnemonic.startswith("it"):
# handle IT block here
# LSL
elif op.shift.type == 2:
value *= (1 << op.shift.value)
cond_met = {
"eq": lambda V, C, Z, N: (Z == 1),
"ne": lambda V, C, Z, N: (Z == 0),
"ge": lambda V, C, Z, N: (N == V),
"hs": lambda V, C, Z, N: (C == 1),
"lo": lambda V, C, Z, N: (C == 0),
"mi": lambda V, C, Z, N: (N == 1),
"pl": lambda V, C, Z, N: (N == 0),
"ls": lambda V, C, Z, N: (C == 0 or Z == 1),
"le": lambda V, C, Z, N: (Z == 1 or N != V),
"hi": lambda V, C, Z, N: (Z == 0 and C == 1),
}.get(line.op_str)(*self.get_cpsr(self.ql.arch.regs.cpsr))
# ROR ?
it_block_range = [each_char for each_char in line.mnemonic[1:]]
return value
next_addr = cur_addr + self.THUMB_INST_SIZE
for each in it_block_range:
_insn = self.read_insn(next_addr)
n2_addr = self.predict(ql, next_addr)
def __is_taken(cc: int) -> Tuple[bool, Tuple[bool, ...]]:
pred = predicate[cc]
flags = self.get_cond_flags()
if (cond_met and each == "t") or (not cond_met and each == "e"):
if n2_addr != (next_addr+len(_insn)): # branch detected
break
return pred(*flags), flags
next_addr += len(_insn)
# conditions predicate selector
predicate: Dict[int, Callable[..., bool]] = {
ARM_CC_EQ: lambda V, C, Z, N: Z,
ARM_CC_NE: lambda V, C, Z, N: not Z,
ARM_CC_HS: lambda V, C, Z, N: C,
ARM_CC_LO: lambda V, C, Z, N: not C,
ARM_CC_MI: lambda V, C, Z, N: N,
ARM_CC_PL: lambda V, C, Z, N: not N,
ARM_CC_VS: lambda V, C, Z, N: V,
ARM_CC_VC: lambda V, C, Z, N: not V,
ARM_CC_HI: lambda V, C, Z, N: (not Z) and C,
ARM_CC_LS: lambda V, C, Z, N: (not C) or Z,
ARM_CC_GE: lambda V, C, Z, N: (N == V),
ARM_CC_LT: lambda V, C, Z, N: (N != V),
ARM_CC_GT: lambda V, C, Z, N: not Z and (N == V),
ARM_CC_LE: lambda V, C, Z, N: Z or (N != V),
ARM_CC_AL: lambda V, C, Z, N: True
}
prophecy.where = next_addr
# implementation of simple binary arithmetic and bitwise operations
binop: Dict[str, Callable[[int, int, int], int]] = {
'add': lambda a, b, _: a + b,
'adc': lambda a, b, c: a + b + c,
'sub': lambda a, b, _: a - b,
'rsb': lambda a, b, _: b - a,
'sbc': lambda a, b, c: a - b - (1 - c),
'rsc': lambda a, b, c: b - a - (1 - c),
'mul': lambda a, b, _: a * b,
'and': lambda a, b, _: a & b,
'orr': lambda a, b, _: a | b,
'eor': lambda a, b, _: a ^ b
}
elif line.mnemonic in ("ldr",):
# is this a branch?
if iname in branches:
going, _ = __is_taken(insn.cc)
if self.regdst_eq_pc(line.op_str):
_, _, rn_offset = line.op_str.partition(", ")
r, _, imm = rn_offset.strip("[]!").partition(", #")
if going:
where = __parse_op(operands[0])
if "]" in rn_offset.split(", ")[1]: # pre-indexed immediate
prophecy.where = self.unpack32(self.read_mem(read_int(imm) + self.read_reg(r), self.INST_SIZE))
return Prophecy(going, where)
else: # post-indexed immediate
# FIXME: weired behavior, immediate here does not apply
prophecy.where = self.unpack32(self.read_mem(self.read_reg(r), self.INST_SIZE))
if iname in conditional_reg:
is_taken = conditional_reg[iname]
reg = __parse_op(operands[0])
assert reg is not None, 'unrecognized reg'
elif line.mnemonic in ("addls", "addne", "add") and self.regdst_eq_pc(line.op_str):
V, C, Z, N = self.get_cpsr(self.ql.arch.regs.cpsr)
r0, r1, r2, *imm = line.op_str.split(", ")
going = is_taken(reg)
# program counter is awalys 8 bytes ahead when it comes with pc, need to add extra 8 bytes
extra = 8 if 'pc' in (r0, r1, r2) else 0
if going:
where = __parse_op(operands[1])
if imm:
expr = imm[0].split()
# TODO: should support more bit shifting and rotating operation
if expr[0] == "lsl": # logical shift left
n = read_int(expr[-1].strip("#")) * 2
return Prophecy(going, where)
if line.mnemonic == "addls" and (C == 0 or Z == 1):
prophecy.where = extra + self.read_reg(r1) + self.read_reg(r2) * n
# instruction is not a branch; check whether pc is affected by this instruction.
#
# insn.regs_write doesn't work well, so we use insn.regs_access instead
if UC_ARM_REG_PC in insn.regs_access()[1]:
elif line.mnemonic == "add" or (line.mnemonic == "addne" and Z == 0):
prophecy.where = extra + self.read_reg(r1) + (self.read_reg(r2) * n if imm else self.read_reg(r2))
if iname == 'mov':
going = True
where = __parse_op(operands[1])
elif line.mnemonic in ("tbh", "tbb"):
elif iname.startswith('ldr'):
suffix: str = insn.mnemonic[3:]
cur_addr += self.INST_SIZE
r0, r1, *imm = line.op_str.strip("[]").split(", ")
# map possible ldr suffixes to kwargs required for the memory access.
#
# to improve readability we also address the case where ldr has no suffix
# and no special kwargs are required. all strings start with '', so it
# serves as a safe default case
msize: Dict[str, Dict] = {
'b' : {'size': 1, 'signed': False},
'h' : {'size': 2, 'signed': False},
'sb': {'size': 1, 'signed': True},
'sh': {'size': 2, 'signed': True},
'' : {}
}
if imm:
expr = imm[0].split()
if expr[0] == "lsl": # logical shift left
n = read_int(expr[-1].strip("#")) * 2
# ldr has different variations that affect the memory access size and
# whether the value should be signed or not.
suffix = next(s for s in msize if suffix.startswith(s))
if line.mnemonic == "tbh":
going, _ = __is_taken(insn.cc)
r1 = self.read_reg(r1) * n
if going:
where = __parse_op(operands[1], **msize[suffix])
elif line.mnemonic == "tbb":
elif iname in binop:
going, flags = __is_taken(insn.cc)
r1 = self.read_reg(r1)
if going:
operator = binop[iname]
op1 = __parse_op(operands[1])
op2 = __parse_op(operands[2])
carry = int(flags[1])
to_add = int.from_bytes(self.read_mem(cur_addr+r1, 2 if line.mnemonic == "tbh" else 1), byteorder="little") * n
prophecy.where = cur_addr + to_add
where = (op1 and op2) and operator(op1, op2, carry)
elif line.mnemonic.startswith("pop") and "pc" in line.op_str:
elif iname == 'pop':
going, _ = __is_taken(insn.cc)
prophecy.where = self.ql.stack_read(line.op_str.strip("{}").split(", ").index("pc") * self.INST_SIZE)
if not { # step to next instruction if cond does not meet
"pop" : lambda *_: True,
"pop.w": lambda *_: True,
"popeq": lambda V, C, Z, N: (Z == 1),
"popne": lambda V, C, Z, N: (Z == 0),
"pophi": lambda V, C, Z, N: (C == 1),
"popge": lambda V, C, Z, N: (N == V),
"poplt": lambda V, C, Z, N: (N != V),
}.get(line.mnemonic)(*self.get_cpsr(self.ql.arch.regs.cpsr)):
if going:
# find pc position within pop regs list
idx = next(i for i, op in enumerate(operands) if (op.type == CS_OP_REG) and (op.reg == UC_ARM_REG_PC))
prophecy.where = cur_addr + self.INST_SIZE
# read the corresponding stack entry
where = self.ql.stack_read(idx * self.asize)
elif line.mnemonic == "sub" and self.regdst_eq_pc(line.op_str):
_, r, imm = line.op_str.split(", ")
prophecy.where = self.read_reg(r) - read_int(imm.strip("#"))
else:
# left here for users to provide feedback when encountered
raise RuntimeWarning(f'instruction affects pc but was not considered: {insn.mnemonic}')
elif line.mnemonic == "mov" and self.regdst_eq_pc(line.op_str):
_, r = line.op_str.split(", ")
prophecy.where = self.read_reg(r)
# for some reason capstone does not consider pc to be affected by 'tbb' and 'tbh'
# so we need to test for them specifically
if prophecy.where is not None:
prophecy.where &= ~0b1
# table branch byte
elif iname == 'tbb':
offset = __read_mem(operands[0].mem, 1)
pc = __read_reg(UC_ARM_REG_PC)
return prophecy
going = True
where = (offset and pc) and (pc + offset * 2)
class BranchPredictorCORTEX_M(BranchPredictorARM):
def __init__(self, ql):
super().__init__(ql)
# table branch half-word
elif iname == 'tbh':
offset = __read_mem(operands[0].mem, 2)
pc = __read_reg(UC_ARM_REG_PC)
going = True
where = (offset and pc) and (pc + offset * 2)
return Prophecy(going, where)
class BranchPredictorCORTEX_M(BranchPredictorARM, ArchCORTEX_M):
"""Branch Predictor for ARM Cortex-M.
"""
@@ -0,0 +1,181 @@
#!/usr/bin/env python3
#
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
#
from typing import Callable, Dict, List, Optional, Tuple
from capstone.x86 import X86Op
from capstone.x86_const import X86_OP_REG, X86_OP_IMM, X86_OP_MEM, X86_INS_LEA
from .branch_predictor import Prophecy, BranchPredictor
from ..arch import ArchX86, ArchX64
from ..misc import InvalidInsn
class BranchPredictorIntel(BranchPredictor):
"""Branch Predictor base class for Intel architecture.
"""
stop = 'hlt'
def get_eflags(self) -> Tuple[int, int, int, int, int]:
eflags = self.read_reg('eflags')
return (
(eflags & (0b1 << 0)) != 0, # carry
(eflags & (0b1 << 2)) != 0, # parity
(eflags & (0b1 << 6)) != 0, # zero
(eflags & (0b1 << 7)) != 0, # sign
(eflags & (0b1 << 11)) != 0 # overflow
)
def predict(self) -> Prophecy:
insn = self.disasm(self.cur_addr, True)
going = False
where = 0
# invalid instruction; nothing to predict
if isinstance(insn, InvalidInsn):
return Prophecy(going, where)
mnem: str = insn.mnemonic
operands: List[X86Op] = insn.operands
# unconditional branches
unconditional = ('call', 'jmp')
# flags-based conditional branches
conditional: Dict[str, Callable[..., bool]] = {
'jb' : lambda C, P, Z, S, O: C,
'jc' : lambda C, P, Z, S, O: C,
'jnae': lambda C, P, Z, S, O: C,
'jnb' : lambda C, P, Z, S, O: not C,
'jnc' : lambda C, P, Z, S, O: not C,
'jae' : lambda C, P, Z, S, O: not C,
'jp' : lambda C, P, Z, S, O: P,
'jpe' : lambda C, P, Z, S, O: P,
'jnp' : lambda C, P, Z, S, O: not P,
'jpo' : lambda C, P, Z, S, O: not P,
'je' : lambda C, P, Z, S, O: Z,
'jz' : lambda C, P, Z, S, O: Z,
'jne' : lambda C, P, Z, S, O: not Z,
'jnz' : lambda C, P, Z, S, O: not Z,
'js' : lambda C, P, Z, S, O: S,
'jns' : lambda C, P, Z, S, O: not S,
'jo' : lambda C, P, Z, S, O: O,
'jno' : lambda C, P, Z, S, O: not O,
'jbe' : lambda C, P, Z, S, O: C or Z,
'jna' : lambda C, P, Z, S, O: C or Z,
'ja' : lambda C, P, Z, S, O: (not C) and (not Z),
'jnbe': lambda C, P, Z, S, O: (not C) and (not Z),
'jl' : lambda C, P, Z, S, O: S != O,
'jnge': lambda C, P, Z, S, O: S != O,
'jge' : lambda C, P, Z, S, O: S == O,
'jnl' : lambda C, P, Z, S, O: S == O,
'jle' : lambda C, P, Z, S, O: Z or (S != O),
'jng' : lambda C, P, Z, S, O: Z or (S != O),
'jg' : lambda C, P, Z, S, O: (not Z) or (not S),
'jnle': lambda C, P, Z, S, O: (not Z) or (not S)
}
# reg-based conditional branches
conditional_reg = {
"jcxz" : 'cx',
"jecxz" : 'ecx',
"jrcxz" : 'rcx'
}
def __read_reg(reg: int) -> Optional[int]:
"""Read register value where register is provided as a Unicorn constant.
"""
# name will be None in case of an illegal or unknown register
name = insn.reg_name(reg)
return name and self.read_reg(name)
def __parse_op(op: X86Op) -> Optional[int]:
"""Parse an operand and return its value. Memory dereferences will be
substitued by the effective address they refer to.
"""
if op.type == X86_OP_REG:
value = __read_reg(op.reg)
elif op.type == X86_OP_IMM:
value = op.imm
elif op.type == X86_OP_MEM:
mem = op.mem
base = __read_reg(mem.base) or 0
index = __read_reg(mem.index) or 0
scale = mem.scale
disp = mem.disp
seg = __read_reg(mem.segment) or 0
ea = seg * 0x10 + (base + index * scale + disp)
# lea does not really dereference memory
value = ea if insn.id == X86_INS_LEA else self.try_read_pointer(ea)
else:
raise RuntimeError(f'unexpected operand type: {op.type}')
return value
# is this an unconditional branch?
if mnem in unconditional:
going = True
where = __parse_op(operands[0])
# is this a return from a function call?
elif mnem == 'ret':
going = True
where = self.ql.arch.stack_read(0)
# is this a flags-based branch?
elif mnem in conditional:
predict = conditional[mnem]
eflags = self.get_eflags()
going = predict(*eflags)
if going:
where = __parse_op(operands[0])
elif mnem in conditional_reg:
reg = conditional_reg[mnem]
predict = lambda c: c == 0
going = predict(self.read_reg(reg))
if going:
where = __parse_op(operands[0])
return Prophecy(going, where)
class BranchPredictorX86(BranchPredictorIntel, ArchX86):
"""Branch Predictor for x86.
"""
class BranchPredictorX64(BranchPredictorIntel, ArchX64):
"""Branch Predictor for x86-64.
"""
@@ -3,88 +3,95 @@
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
#
from typing import Optional
from capstone.mips import MipsOp, MIPS_OP_REG, MIPS_OP_IMM
from .branch_predictor import *
from .branch_predictor import BranchPredictor, Prophecy
from ..arch import ArchMIPS
from ..misc import InvalidInsn
class BranchPredictorMIPS(BranchPredictor, ArchMIPS):
"""
predictor for MIPS
"""Branch Predictor for MIPS 32.
"""
def __init__(self, ql):
super().__init__(ql)
ArchMIPS.__init__(self)
self.CODE_END = "break"
self.INST_SIZE = 4
@staticmethod
def signed_val(val: int) -> int:
"""
signed value convertion
"""
def is_negative(i: int) -> int:
"""
check wether negative value or not
"""
return i & (1 << 31)
return (val-1 << 32) if is_negative(val) else val
def read_reg(self, reg_name):
reg_name = reg_name.strip("$").replace("fp", "s8")
return self.signed_val(getattr(self.ql.arch.regs, reg_name))
stop = 'break'
def predict(self):
prophecy = Prophecy()
line = self.disasm(self.cur_addr)
insn = self.disasm(self.cur_addr, True)
if line.mnemonic == self.CODE_END: # indicates program extied
prophecy.where = True
return prophecy
going = False
where = 0
prophecy.where = self.cur_addr + self.INST_SIZE
if line.mnemonic.startswith('j') or line.mnemonic.startswith('b'):
# invalid instruction; nothing to predict
if isinstance(insn, InvalidInsn):
return Prophecy(going, where)
# make sure at least delay slot executed
prophecy.where += self.INST_SIZE
unconditional = ('j', 'jr', 'jal', 'jalr', 'b', 'bl', 'bal')
# get registers or memory address from op_str
targets = [
self.read_reg(each)
if '$' in each else read_int(each)
for each in line.op_str.split(", ")
]
conditional = {
'beq' : lambda r0, r1: r0 == r1, # branch on equal
'bne' : lambda r0, r1: r0 != r1, # branch on not equal
'blt' : lambda r0, r1: r0 < r1, # branch on r0 less than r1
'bgt' : lambda r0, r1: r0 > r1, # branch on r0 greater than r1
'ble' : lambda r0, r1: r0 <= r1, # branch on r0 less than or equal to r1
'bge' : lambda r0, r1: r0 >= r1, # branch on r0 greater than or equal to r1
prophecy.going = {
"j" : (lambda _: True), # unconditional jump
"jr" : (lambda _: True), # unconditional jump
"jal" : (lambda _: True), # unconditional jump
"jalr" : (lambda _: True), # unconditional jump
"b" : (lambda _: True), # unconditional branch
"bl" : (lambda _: True), # unconditional branch
"bal" : (lambda _: True), # unconditional branch
"beq" : (lambda r0, r1, _: r0 == r1), # branch on equal
"bne" : (lambda r0, r1, _: r0 != r1), # branch on not equal
"blt" : (lambda r0, r1, _: r0 < r1), # branch on r0 less than r1
"bgt" : (lambda r0, r1, _: r0 > r1), # branch on r0 greater than r1
"ble" : (lambda r0, r1, _: r0 <= r1), # brach on r0 less than or equal to r1
"bge" : (lambda r0, r1, _: r0 >= r1), # branch on r0 greater than or equal to r1
"beqz" : (lambda r, _: r == 0), # branch on equal to zero
"bnez" : (lambda r, _: r != 0), # branch on not equal to zero
"bgtz" : (lambda r, _: r > 0), # branch on greater than zero
"bltz" : (lambda r, _: r < 0), # branch on less than zero
"bltzal" : (lambda r, _: r < 0), # branch on less than zero and link
"blez" : (lambda r, _: r <= 0), # branch on less than or equal to zero
"bgez" : (lambda r, _: r >= 0), # branch on greater than or equal to zero
"bgezal" : (lambda r, _: r >= 0), # branch on greater than or equal to zero and link
}.get(line.mnemonic)(*targets)
'beqz' : lambda r: r == 0, # branch on equal to zero
'bnez' : lambda r: r != 0, # branch on not equal to zero
'bgtz' : lambda r: r > 0, # branch on greater than zero
'bltz' : lambda r: r < 0, # branch on less than zero
'bltzal': lambda r: r < 0, # branch on less than zero and link
'blez' : lambda r: r <= 0, # branch on less than or equal to zero
'bgez' : lambda r: r >= 0, # branch on greater than or equal to zero
'bgezal': lambda r: r >= 0 # branch on greater than or equal to zero and link
}
if prophecy.going:
# target address is always the rightmost one
prophecy.where = targets[-1]
def __as_signed(val: int) -> int:
"""Get the signed integer representation of a given value.
"""
return prophecy
msb = 0b1 << 31
return (val & ~msb) - (val & msb)
def __read_reg(reg: int) -> Optional[int]:
"""Read register value where register is provided as a Unicorn constant.
"""
# name will be None in case of an illegal or unknown register
name = insn.reg_name(reg)
return name and __as_signed(self.read_reg(self.unalias(name)))
def __parse_op(op: MipsOp) -> Optional[int]:
"""Parse an operand and return its value.
"""
if op.type == MIPS_OP_REG:
value = __read_reg(op.reg)
elif op.type == MIPS_OP_IMM:
value = op.imm
else:
raise RuntimeError(f'unexpected operand type: {op.type}')
return value
# get operands. target address is always the rightmost one
if insn.operands:
*operands, target = insn.operands
if insn.mnemonic in unconditional:
going = True
elif insn.mnemonic in conditional:
predict = conditional[insn.mnemonic]
going = predict(*(__parse_op(op) for op in operands))
if going:
where = __parse_op(target)
return Prophecy(going, where)
@@ -1,128 +0,0 @@
#!/usr/bin/env python3
#
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
#
import re
from .branch_predictor import *
from ..arch import ArchX86
from ..misc import check_and_eval
class BranchPredictorX86(BranchPredictor, ArchX86):
"""
predictor for X86
"""
class ParseError(Exception):
"""
indicate parser error
"""
pass
def __init__(self, ql):
super().__init__(ql)
ArchX86.__init__(self)
def predict(self):
prophecy = Prophecy()
line = self.disasm(self.cur_addr)
jump_table = {
# conditional jump
"jo" : (lambda C, P, A, Z, S, O: O == 1),
"jno" : (lambda C, P, A, Z, S, O: O == 0),
"js" : (lambda C, P, A, Z, S, O: S == 1),
"jns" : (lambda C, P, A, Z, S, O: S == 0),
"je" : (lambda C, P, A, Z, S, O: Z == 1),
"jz" : (lambda C, P, A, Z, S, O: Z == 1),
"jne" : (lambda C, P, A, Z, S, O: Z == 0),
"jnz" : (lambda C, P, A, Z, S, O: Z == 0),
"jb" : (lambda C, P, A, Z, S, O: C == 1),
"jc" : (lambda C, P, A, Z, S, O: C == 1),
"jnae" : (lambda C, P, A, Z, S, O: C == 1),
"jnb" : (lambda C, P, A, Z, S, O: C == 0),
"jnc" : (lambda C, P, A, Z, S, O: C == 0),
"jae" : (lambda C, P, A, Z, S, O: C == 0),
"jbe" : (lambda C, P, A, Z, S, O: C == 1 or Z == 1),
"jna" : (lambda C, P, A, Z, S, O: C == 1 or Z == 1),
"ja" : (lambda C, P, A, Z, S, O: C == 0 and Z == 0),
"jnbe" : (lambda C, P, A, Z, S, O: C == 0 and Z == 0),
"jl" : (lambda C, P, A, Z, S, O: S != O),
"jnge" : (lambda C, P, A, Z, S, O: S != O),
"jge" : (lambda C, P, A, Z, S, O: S == O),
"jnl" : (lambda C, P, A, Z, S, O: S == O),
"jle" : (lambda C, P, A, Z, S, O: Z == 1 or S != O),
"jng" : (lambda C, P, A, Z, S, O: Z == 1 or S != O),
"jg" : (lambda C, P, A, Z, S, O: Z == 0 or S == O),
"jnle" : (lambda C, P, A, Z, S, O: Z == 0 or S == O),
"jp" : (lambda C, P, A, Z, S, O: P == 1),
"jpe" : (lambda C, P, A, Z, S, O: P == 1),
"jnp" : (lambda C, P, A, Z, S, O: P == 0),
"jpo" : (lambda C, P, A, Z, S, O: P == 0),
# unconditional jump
"call" : (lambda *_: True),
"jmp" : (lambda *_: True),
}
jump_reg_table = {
"jcxz" : (lambda cx: cx == 0),
"jecxz" : (lambda ecx: ecx == 0),
"jrcxz" : (lambda rcx: rcx == 0),
}
if line.mnemonic in jump_table:
eflags = self.get_flags(self.ql.arch.regs.eflags).values()
prophecy.going = jump_table.get(line.mnemonic)(*eflags)
elif line.mnemonic in jump_reg_table:
prophecy.going = jump_reg_table.get(line.mnemonic)(self.ql.arch.regs.ecx)
if prophecy.going:
takeaway_list = ["ptr", "dword", "[", "]"]
if len(line.op_str.split()) > 1:
new_line = line.op_str.replace(":", "+")
for each in takeaway_list:
new_line = new_line.replace(each, " ")
new_line = " ".join(new_line.split())
for each_reg in filter(lambda r: len(r) == 3, self.ql.arch.regs.register_mapping.keys()):
if each_reg in new_line:
new_line = re.sub(each_reg, hex(self.read_reg(each_reg)), new_line)
for each_reg in filter(lambda r: len(r) == 2, self.ql.arch.regs.register_mapping.keys()):
if each_reg in new_line:
new_line = re.sub(each_reg, hex(self.read_reg(each_reg)), new_line)
prophecy.where = check_and_eval(new_line)
elif line.op_str in self.ql.arch.regs.register_mapping:
prophecy.where = self.ql.arch.regs.read(line.op_str)
else:
prophecy.where = read_int(line.op_str)
else:
prophecy.where = self.cur_addr + line.size
return prophecy
@@ -1,127 +0,0 @@
#!/usr/bin/env python3
#
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
#
import re
from .branch_predictor import *
from ..arch import ArchX8664
from ..misc import check_and_eval
class BranchPredictorX8664(BranchPredictor, ArchX8664):
"""
predictor for X86
"""
class ParseError(Exception):
"""
indicate parser error
"""
pass
def __init__(self, ql):
super().__init__(ql)
ArchX8664.__init__(self)
def predict(self):
prophecy = Prophecy()
line = self.disasm(self.cur_addr)
jump_table = {
# conditional jump
"jo" : (lambda C, P, A, Z, S, O: O == 1),
"jno" : (lambda C, P, A, Z, S, O: O == 0),
"js" : (lambda C, P, A, Z, S, O: S == 1),
"jns" : (lambda C, P, A, Z, S, O: S == 0),
"je" : (lambda C, P, A, Z, S, O: Z == 1),
"jz" : (lambda C, P, A, Z, S, O: Z == 1),
"jne" : (lambda C, P, A, Z, S, O: Z == 0),
"jnz" : (lambda C, P, A, Z, S, O: Z == 0),
"jb" : (lambda C, P, A, Z, S, O: C == 1),
"jc" : (lambda C, P, A, Z, S, O: C == 1),
"jnae" : (lambda C, P, A, Z, S, O: C == 1),
"jnb" : (lambda C, P, A, Z, S, O: C == 0),
"jnc" : (lambda C, P, A, Z, S, O: C == 0),
"jae" : (lambda C, P, A, Z, S, O: C == 0),
"jbe" : (lambda C, P, A, Z, S, O: C == 1 or Z == 1),
"jna" : (lambda C, P, A, Z, S, O: C == 1 or Z == 1),
"ja" : (lambda C, P, A, Z, S, O: C == 0 and Z == 0),
"jnbe" : (lambda C, P, A, Z, S, O: C == 0 and Z == 0),
"jl" : (lambda C, P, A, Z, S, O: S != O),
"jnge" : (lambda C, P, A, Z, S, O: S != O),
"jge" : (lambda C, P, A, Z, S, O: S == O),
"jnl" : (lambda C, P, A, Z, S, O: S == O),
"jle" : (lambda C, P, A, Z, S, O: Z == 1 or S != O),
"jng" : (lambda C, P, A, Z, S, O: Z == 1 or S != O),
"jg" : (lambda C, P, A, Z, S, O: Z == 0 or S == O),
"jnle" : (lambda C, P, A, Z, S, O: Z == 0 or S == O),
"jp" : (lambda C, P, A, Z, S, O: P == 1),
"jpe" : (lambda C, P, A, Z, S, O: P == 1),
"jnp" : (lambda C, P, A, Z, S, O: P == 0),
"jpo" : (lambda C, P, A, Z, S, O: P == 0),
# unconditional jump
"call" : (lambda *_: True),
"jmp" : (lambda *_: True),
}
jump_reg_table = {
"jcxz" : (lambda cx: cx == 0),
"jecxz" : (lambda ecx: ecx == 0),
"jrcxz" : (lambda rcx: rcx == 0),
}
if line.mnemonic in jump_table:
eflags = self.get_flags(self.ql.arch.regs.eflags).values()
prophecy.going = jump_table.get(line.mnemonic)(*eflags)
elif line.mnemonic in jump_reg_table:
prophecy.going = jump_reg_table.get(line.mnemonic)(self.ql.arch.regs.ecx)
if prophecy.going:
takeaway_list = ["ptr", "dword", "qword", "[", "]"]
if len(line.op_str.split()) > 1:
new_line = line.op_str.replace(":", "+")
for each in takeaway_list:
new_line = new_line.replace(each, " ")
new_line = " ".join(new_line.split())
for each_reg in filter(lambda r: len(r) == 3, self.ql.arch.regs.register_mapping.keys()):
if each_reg in new_line:
new_line = re.sub(each_reg, hex(self.read_reg(each_reg)), new_line)
for each_reg in filter(lambda r: len(r) == 2, self.ql.arch.regs.register_mapping.keys()):
if each_reg in new_line:
new_line = re.sub(each_reg, hex(self.read_reg(each_reg)), new_line)
prophecy.where = check_and_eval(new_line)
elif line.op_str in self.ql.arch.regs.register_mapping:
prophecy.where = self.ql.arch.regs.read(line.op_str)
else:
prophecy.where = read_int(line.op_str)
else:
prophecy.where = self.cur_addr + line.size
return prophecy
+19 -17
View File
@@ -1,23 +1,25 @@
from enum import IntEnum
class color:
"""
class for colorful prints
"""
CYAN = '\033[96m'
PURPLE = '\033[95m'
BLUE = '\033[94m'
YELLOW = '\033[93m'
GREEN = '\033[92m'
RED = '\033[91m'
DARKGRAY = '\033[90m'
WHITE = '\033[48m'
DARKCYAN = '\033[36m'
BLACK = '\033[35m'
UNDERLINE = '\033[4m'
BOLD = '\033[1m'
END = '\033[0m'
RESET = '\x1b[39m'
"""
class for colorful prints
"""
DARKGRAY = '\033[90m'
RED = '\033[91m'
GREEN = '\033[92m'
YELLOW = '\033[93m'
BLUE = '\033[94m'
PURPLE = '\033[95m'
CYAN = '\033[96m'
WHITE = '\033[48m'
BLACK = '\033[35m'
DARKCYAN = '\033[36m'
UNDERLINE = '\033[4m'
BOLD = '\033[1m'
END = '\033[0m'
RESET = '\033[39m'
class QDB_MSG(IntEnum):
ERROR = 10
+104 -59
View File
@@ -3,102 +3,147 @@
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
#
from typing import Optional
from __future__ import annotations
from unicorn import UC_ERR_READ_UNMAPPED
import unicorn
from typing import TYPE_CHECKING, Optional, Tuple, Union
from unicorn import UcError
from capstone import CsInsn
from .misc import InvalidInsn
if TYPE_CHECKING:
from qiling import Qiling
from .misc import InsnLike
from .misc import read_int, InvalidInsn
class Context:
"""
base class for accessing context of running qiling instance
"""Emulation context accessor.
"""
def __init__(self, ql):
def __init__(self, ql: Qiling):
# make sure mixin classes are properly initialized
super().__init__()
self.ql = ql
self.pointersize = self.ql.arch.pointersize
self.unpack = ql.unpack
self.unpack16 = ql.unpack16
self.unpack32 = ql.unpack32
self.unpack64 = ql.unpack64
@property
def cur_addr(self):
"""
program counter of qiling instance
def cur_addr(self) -> int:
"""Read current program counter register.
"""
return self.ql.arch.regs.arch_pc
def read_mem(self, address: int, size: int):
@property
def cur_sp(self) -> int:
"""Read current stack pointer register.
"""
read data from memory of qiling instance
return self.ql.arch.regs.arch_sp
def read_reg(self, reg: Union[str, int]) -> int:
"""Get register value.
"""
return self.ql.arch.regs.read(reg)
def write_reg(self, reg: Union[str, int], value: int) -> None:
"""Set register value.
"""
self.ql.arch.regs.write(reg, value)
def disasm(self, address: int, detail: bool = False) -> InsnLike:
"""Helper function for disassembling.
"""
insn_bytes = self.read_insn(address) or b''
insn = None
if insn_bytes:
md = self.ql.arch.disassembler
md.detail = detail
insn = next(md.disasm(insn_bytes, address, 1), None)
return insn or InvalidInsn(insn_bytes, address)
def disasm_lite(self, address: int) -> Tuple[int, int, str, str]:
"""Helper function for light disassembling, when details are not required.
Returns:
A tuple of: instruction address, size, mnemonic and operands
"""
insn_bytes = self.read_insn(address) or b''
insn = None
if insn_bytes:
md = self.ql.arch.disassembler
insn = next(md.disasm_lite(insn_bytes, address, 1), None)
return insn or tuple()
def read_mem(self, address: int, size: int) -> bytearray:
"""Read data of a certain size from specified memory location.
"""
return self.ql.mem.read(address, size)
def disasm(self, address: int, detail: bool = False) -> Optional[CsInsn]:
"""
helper function for disassembling
def try_read_mem(self, address: int, size: int) -> Optional[bytearray]:
"""Attempt to read data from memory.
"""
md = self.ql.arch.disassembler
md.detail = detail
if (bytes_read := self.read_insn(address)):
return next(md.disasm(bytes_read, address), InvalidInsn(bytes_read, address))
return InvalidInsn(bytes_read, address)
def try_read(self, address: int, size: int) -> Optional[bytes]:
"""
try to read data from ql.mem
"""
result = None
err_msg = ""
try:
result = self.read_mem(address, size)
data = self.read_mem(address, size)
except UcError:
data = None
except unicorn.unicorn.UcError as err:
if err.errno == UC_ERR_READ_UNMAPPED: # Invalid memory read (UC_ERR_READ_UNMAPPED)
err_msg = f"Can not access memory at address 0x{address:08x}"
return data
except:
pass
return (result, err_msg)
def try_read_pointer(self, address: int) -> Optional[bytes]:
"""
try to read pointer size of data from ql.mem
def read_pointer(self, address: int, size: int = 0, *, signed: bool = False) -> int:
"""Attempt to read a native-size integer from memory.
"""
return self.try_read(address, self.archbit)
return self.ql.mem.read_ptr(address, size, signed=signed)
def try_read_pointer(self, address: int, size: int = 0, *, signed: bool = False) -> Optional[int]:
"""Attempt to read a native-size integer from memory.
"""
try:
value = self.read_pointer(address, size, signed=signed)
except UcError:
value = None
return value
def read_string(self, address: int) -> Optional[str]:
"""
read string from memory of qiling instance
"""Read string from memory.
"""
return self.ql.mem.string(address)
def try_read_string(self, address: int) -> Optional[str]:
"""
try to read string from memory of qiling instance
"""Attempt to read a string from memory.
"""
s = None
try:
s = self.read_string(address)
except:
pass
except UcError:
s = None
@staticmethod
def read_int(s: str) -> int:
return read_int(s)
return s
if __name__ == "__main__":
pass
def get_deref(self, ptr: int) -> Union[int, str, None]:
"""Get content referenced by a pointer.
If dereferenced data is printable, a string will be returned. Otherwise
an integer value is retgurned. If the specified address is not reachable
None is returned.
"""
val = self.try_read_string(ptr)
return val if val and val.isprintable() else self.try_read_pointer(ptr)
+252
View File
@@ -0,0 +1,252 @@
#!/usr/bin/env python3
#
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
#
from __future__ import annotations
import re
from typing import TYPE_CHECKING, List, Tuple
from qiling.const import QL_ARCH
from .context import Context
from .arch import ArchCORTEX_M, ArchARM, ArchMIPS, ArchX86, ArchX64
if TYPE_CHECKING:
from re import Match
from qiling import Qiling
from .misc import InsnLike
def setup_command_helper(ql: Qiling):
atypes = {
QL_ARCH.X86: ArchX86,
QL_ARCH.X8664: ArchX64,
QL_ARCH.MIPS: ArchMIPS,
QL_ARCH.ARM: ArchARM,
QL_ARCH.CORTEX_M: ArchCORTEX_M
}
ret = type('CommandHelper', (CommandHelper, atypes[ql.arch.type]), {})
return ret(ql)
# pre-compile the safe arithmetics and bitwise pattern
__arith_pattern = re.compile(r'^(0[xX][0-9a-fA-F]+|0[0-7]+|\d+|[\+\-\*/\(\)|&^~\s])+$')
def safe_arith(expr: str) -> int:
"""Safely evaluate an arithmetic expression. The expression may include only
digits, arithmetic and bitwise operators, parantheses, whitespaces, hexadecimal
and octal values.
Args:
expr: arithmetic expression to evaluate
Returns: integer result
Raises:
ValueError: if disallowed tokens are included in `expr`
SyntaxError: in case the arithmetic expression does not make sense
"""
if not __arith_pattern.fullmatch(expr):
raise ValueError
# adjust gdb-style octal values to python: 0644 -> 0o644
re.sub(r'0([0-7]+)', r'0o\1', expr)
# safely evaluate the expression
return eval(expr, {}, {})
class CommandHelper(Context):
"""
memory manager for handing memory access
"""
def __init__(self, ql: Qiling):
super().__init__(ql)
# default values for the examine ('x') command
self.x_defaults = {
'n': '1', # number of units to read
'f': 'x', # output format
'u': 'w' # unit type
}
def sub_reg_values(self, expr: str) -> str:
def __sub_reg(m: Match[str]) -> str:
reg = m.group(1).lower()
return f'{self.read_reg(self.unalias(reg)):#x}'
# replace reg names with their actual values
return re.sub(r'\$(\w+)', __sub_reg, expr)
def resolve_expr(self, expr: str) -> int:
"""Resolve an arithmetic expression that might include register names.
Registers names will be substituted with their current value before
proceeding to evaluate the expression.
Args:
expr: an expression to evaluate
Returns:
final evaluation result
Raises:
KeyError: if `expr` contains an unrecognized register name
ValueError: if `expr` contains disallowed tokens
SyntaxError: if `expr` contains a broken arithmetic syntax
"""
try:
# look for registers names and replace them with their actual values
expr = self.sub_reg_values(expr)
# expr contains an unrecognized register name
except KeyError as ex:
raise KeyError(f'unrecognized register name: {ex.args[0]}') from ex
try:
# expr should contain only values and aithmetic tokens by now; attempt to evaluate it
res = safe_arith(expr)
# expr contains a disallowed token
except ValueError as ex:
raise ValueError('only integers, hexadecimals, octals, arithmetic and bitwise operators are allowed') from ex
# arithmetic syntax is broken
except SyntaxError as ex:
raise SyntaxError('error evaluating arithmetic expression') from ex
return res
def handle_set(self, line: str) -> Tuple[str, int]:
"""
set register value of current context
"""
# set $a = b
m = re.match(r'\s*\$(?P<reg>\w+)\s*=\s*(?P<expr>.+)', line)
if m is None:
raise SyntaxError('illegal command syntax')
if not m['reg']:
raise KeyError('error parsing input: invalid lhand expression')
if not m['expr']:
raise SyntaxError('error parsing input: invalid rhand expression')
reg = self.unalias(m['reg'])
expr = self.resolve_expr(m['expr'])
self.write_reg(reg, expr)
return (reg, expr)
def handle_i(self, addr: int, count: int) -> List[InsnLike]:
result = []
for _ in range(count):
insn = self.disasm(addr)
addr += insn.size
result.append(insn)
return result
def handle_examine(self, line: str) -> None:
# examples:
# x/xw address
# x/4xw $esp
# x/4xg $rsp
# x/i $eip - 0x10
# x $sp
# x $sp + 0xc
m = re.match(r'(?:/(?P<n>\d+)?(?P<f>[oxdutfacis])?(?P<u>[bhwg])?)?\s*(?P<target>.+)?', line)
# there should be always a match, at least for target, but let's be on the safe side
if m is None:
raise ValueError('unexpected examine command syntax')
n = m['n'] or self.x_defaults['n']
f = m['f'] or self.x_defaults['f']
u = m['u'] or self.x_defaults['u']
target = m['target']
# if target was specified, determine its value. otherwise use the current address
target = self.resolve_expr(target) if target else self.cur_addr
n = int(n)
if f == r'i':
for insn in self.handle_i(target, n):
print(f"{insn.address:#010x}: {insn.mnemonic:10s} {insn.op_str}")
# handle read c-style string
elif f == r's':
s = self.try_read_string(target)
if s is None:
raise ValueError(f'error reading c-style string at {target:#010x}')
print(f"{target:#010x}: {s}")
else:
def __to_size(u: str) -> int:
"""Convert a gdb unit name to its corresponding size in bytes.
"""
sizes = {
'b': 1, # byte
'h': 2, # halfword
'w': 4, # word
'g': 8 # giant
}
# assume u is in sizes
return sizes[u]
def __to_py_spec(f: str, size: int) -> Tuple[str, str, str]:
"""Convert a gdb format specifier to its corresponding python format,
prefix and padding specifiers.
"""
specs = {
'o': ('o', '0', ''), # octal
'x': ('x', '0x', f'0{size * 2}'), # hex
'd': ('d', '', ''), # decimal
'u': ('u', '', ''), # unsigned decimal
't': ('b', '', f'0{size * 8}'), # binary
'f': ('f', '', ''), # float
'a': ('x', '0x', f'0{size * 2}'), # address
'c': ('c', '', ''), # char
}
# assume f is in specs
return specs[f]
size = __to_size(u)
pyfmt, prefix, pad = __to_py_spec(f, size)
values = [self.try_read_pointer(target + (i * size), size) for i in range(n)]
ipr = 4 # number of items to display per row
for i in range(0, len(values), ipr):
vset = values[i:i + ipr]
print(f'{target + i * size:#10x}:', end='\t')
for v in vset:
print('?' if v is None else f'{prefix}{v:{pad}{pyfmt}}', end='\t')
print()
-204
View File
@@ -1,204 +0,0 @@
#!/usr/bin/env python3
#
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
#
from qiling.const import QL_ARCH
from .context import Context
from .arch import ArchCORTEX_M, ArchARM, ArchMIPS, ArchX86, ArchX8664
from .misc import check_and_eval
import re, math
def setup_memory_Manager(ql):
arch_type = {
QL_ARCH.X86: ArchX86,
QL_ARCH.X8664: ArchX8664,
QL_ARCH.MIPS: ArchMIPS,
QL_ARCH.ARM: ArchARM,
QL_ARCH.CORTEX_M: ArchCORTEX_M,
}.get(ql.arch.type)
ret = type(
"MemoryManager",
(MemoryManager, arch_type),
{}
)
return ret(ql)
class MemoryManager(Context):
"""
memory manager for handing memory access
"""
def __init__(self, ql):
super().__init__(ql)
@property
def get_default_fmt(self):
return ('x', 4, 1)
@property
def get_format_letter(self):
return {
"o", # octal
"x", # hex
"d", # decimal
"u", # unsigned decimal
"t", # binary
"f", # float
"a", # address
"i", # instruction
"c", # char
"s", # string
"z", # hex, zero padded on the left
}
@property
def get_size_letter(self):
return {
"b": 1, # 1-byte, byte
"h": 2, # 2-byte, halfword
"w": 4, # 4-byte, word
"g": 8, # 8-byte, giant
}
def extract_count(self, t):
return "".join([s for s in t if s.isdigit()])
def get_fmt(self, text):
f, s, c = self.get_default_fmt
if self.extract_count(text):
c = int(self.extract_count(text))
for char in text.strip(str(c)):
if char in self.get_size_letter.keys():
s = self.get_size_letter.get(char)
elif char in self.get_format_letter:
f = char
return (f, s, c)
def fmt_unpack(self, bs: bytes, sz: int) -> int:
return {
1: lambda x: x[0],
2: self.unpack16,
4: self.unpack32,
8: self.unpack64,
}.get(sz)(bs)
def handle_i(self, addr, ct=1):
result = []
for offset in range(addr, addr+ct*4, 4):
if (line := self.disasm(offset)):
result.append(line)
return result
def parse(self, line: str):
# test case
# x/wx address
# x/i address
# x $sp
# x $sp +0xc
# x $sp+0xc
# x $sp + 0xc
if line.startswith("/"): # followed by format letter and size letter
fmt, *rest = line.strip("/").split()
fmt = self.get_fmt(fmt)
else:
args = line.split()
rest = [args[0]] if len(args) == 1 else args
fmt = self.get_default_fmt
if len(rest) == 0:
return
line = []
if (regs_dict := getattr(self, "regs_need_swapped", None)):
for each in rest:
for reg in regs_dict:
if each in regs_dict:
line.append(regs_dict[each])
else:
line.append(each)
else:
line = rest
# for simple calculation with register and address
line = " ".join(line)
# substitue register name with real value
for each_reg in filter(lambda r: len(r) == 3, self.ql.arch.regs.register_mapping):
reg = f"${each_reg}"
if reg in line:
line = re.sub(f"\\{reg}", hex(self.ql.arch.regs.read(each_reg)), line)
for each_reg in filter(lambda r: len(r) == 2, self.ql.arch.regs.register_mapping):
reg = f"${each_reg}"
if reg in line:
line = re.sub(f"\\{reg}", hex(self.ql.arch.regs.read(each_reg)), line)
ft, sz, ct = fmt
try:
addr = check_and_eval(line)
except:
return "something went wrong ..."
if ft == "i":
output = self.handle_i(addr, ct)
for each in output:
print(f"0x{each.address:x}: {each.mnemonic}\t{each.op_str}")
elif ft == "s":
# handle read c-style string
try:
print(f"0x{addr:08x}: {self.ql.os.utils.read_cstring(addr)}")
except:
return f"error reading c-style string at 0x{addr:08x}"
else:
lines = 1 if ct <= 4 else math.ceil(ct / 4)
# parse command
prefix = "0x" if ft in ("x", "a") else ""
pad = '0' + str(sz*2) if ft in ('x', 'a', 't') else ''
ft = ft.lower() if ft in ("x", "o", "b", "d") else ft.lower().replace("t", "b").replace("a", "x")
mem_read = []
for offset in range(ct):
# append data if read successfully, otherwise return error message
if (data := self.try_read(addr+(offset*sz), sz))[0] is not None:
mem_read.append(data[0])
else:
return data[1]
for line in range(lines):
offset = line * sz * 4
print(f"0x{addr+offset:x}:\t", end="")
idx = line * self.ql.arch.pointersize
for each in mem_read[idx:idx+self.ql.arch.pointersize]:
data = self.fmt_unpack(each, sz)
print(f"{prefix}{data:{pad}{ft}}\t", end="")
print()
return True
+35 -59
View File
@@ -3,92 +3,68 @@
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
#
from typing import AnyStr, Callable, Optional
from typing import Optional, Union
from dataclasses import dataclass
from capstone import CsInsn
import ast
def check_and_eval(line: str):
"""
This function will valid all type of nodes and evaluate it if nothing went wrong
"""
class AST_checker(ast.NodeVisitor):
def generic_visit(self, node):
if type(node) in (ast.Module, ast.Expr, ast.BinOp, ast.Constant, ast.Add, ast.Mult, ast.Sub):
ast.NodeVisitor.generic_visit(self, node)
else:
raise ParseError("malform or invalid ast node")
checker = AST_checker()
ast_tree = ast.parse(line)
checker.visit(ast_tree)
return eval(line)
@dataclass
class InvalidInsn:
"""
class for displaying invalid instruction
"""
bytes: bytes
address: bytes
mnemonic: str = 'invalid'
address: int
mnemonic: str = '(invalid)'
op_str: str = ''
def __post_init__(self):
self.size = len(self.bytes)
self.size = len(self.bytes) if self.bytes else 1
class Breakpoint:
"""Dummy class for breakpoints.
"""
dummy class for breakpoint
"""
def __init__(self, addr: int):
# monotonically increasing index counter
_counter = 0
def __init__(self, addr: int, temp: bool = False):
"""Initialize a breakpoint object.
Args:
addr: address to break upon arrival
temp: whether this is a temporary breakpoint. temporary breakpoints
get removed after they get hit for the first time
"""
self.index = Breakpoint._counter
Breakpoint._counter += 1
self.addr = addr
self.hitted = False
self.temp = temp
self.enabled = True
class TempBreakpoint(Breakpoint):
def read_int(s: str, /) -> int:
"""Turn a numerical string into its integer value.
"""
dummy class for temporay breakpoint
"""
def __init__(self, addr: int):
super().__init__(addr)
def read_int(s: str) -> int:
"""
parse unsigned integer from string
"""
return int(s, 0)
def try_read_int(s: AnyStr) -> Optional[int]:
"""
try to read string as integer is possible
def try_read_int(s: str, /) -> Optional[int]:
"""Attempt to convert string to an integer value.
"""
try:
ret = read_int(s)
except:
ret = None
val = read_int(s)
except (ValueError, TypeError):
val = None
return ret
return val
def parse_int(func: Callable) -> Callable:
"""
function dectorator for parsing argument as integer
"""
def wrap(qdb, s: str = "") -> int:
assert type(s) is str
ret = try_read_int(s)
return func(qdb, ret)
return wrap
if __name__ == "__main__":
pass
InsnLike = Union[CsInsn, InvalidInsn]
+428 -350
View File
File diff suppressed because it is too large Load Diff
+1 -2
View File
@@ -4,7 +4,6 @@
#
from .render import ContextRender
from .render_x86 import ContextRenderX86
from .render_intel import ContextRenderX86, ContextRenderX64
from .render_mips import ContextRenderMIPS
from .render_arm import ContextRenderARM, ContextRenderCORTEX_M
from .render_x8664 import ContextRenderX8664
+164 -143
View File
@@ -3,168 +3,184 @@
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
#
"""Context Render for rendering UI
"""
from capstone import CsInsn
from typing import Mapping
import os, copy
from __future__ import annotations
import os
from typing import TYPE_CHECKING, Callable, Collection, Dict, Iterator, List, Mapping, Optional, Sequence, Tuple, Union
from ..context import Context
from ..const import color
if TYPE_CHECKING:
from qiling.core import Qiling
from ..branch_predictor.branch_predictor import BranchPredictor, Prophecy
from ..misc import InsnLike
"""
Context Render for rendering UI
COLORS = (
color.DARKCYAN,
color.BLUE,
color.RED,
color.YELLOW,
color.GREEN,
color.PURPLE,
color.CYAN,
color.WHITE
)
"""
RARROW = '\u2192'
RULER = '\u2500'
CURSOR = '\u25ba' # current instruction cursor
GOING_DN = '\u2ba6' # branching downward to a higher address
GOING_UP = '\u2ba4' # branching upward to a lower address
COLORS = (color.DARKCYAN, color.BLUE, color.RED, color.YELLOW, color.GREEN, color.PURPLE, color.CYAN, color.WHITE)
class Render:
"""
base class for rendering related functions
"""Base class for graphical rendering functionality.
Render objects are agnostic to current emulation state.
"""
def divider_printer(field_name, ruler="─"):
def __init__(self):
# make sure mixin classes are properly initialized
super().__init__()
self.regs_a_row = 4 # number of regs to display per row
self.stack_num = 8 # number of stack entries to display in context
self.disasm_num = 4 # number of instructions to display in context before and after current pc
@staticmethod
def divider_printer(header: str, footer: bool = False):
"""
decorator function for printing divider and field name
"""
def decorator(context_dumper):
def decorator(wrapped: Callable):
def wrapper(*args, **kwargs):
try:
width, _ = os.get_terminal_size()
except OSError:
width = 130
bar = (width - len(field_name)) // 2 - 1
print(ruler * bar, field_name, ruler * bar)
context_dumper(*args, **kwargs)
if "DISASM" in field_name:
print(ruler * width)
print(header.center(width, RULER))
wrapped(*args, **kwargs)
if footer:
print(RULER * width)
return wrapper
return decorator
def __init__(self):
self.regs_a_row = 4
self.stack_num = 10
self.disasm_num = 0x10
self.color = color
def reg_diff(self, cur_regs, saved_reg_dump):
def reg_diff(self, curr: Mapping[str, int], prev: Mapping[str, int]) -> List[str]:
"""
helper function for highlighting register changed during execution
"""
if saved_reg_dump:
reg_dump = copy.deepcopy(saved_reg_dump)
if getattr(self, "regs_need_swapped", None):
reg_dump = self.swap_reg_name(reg_dump)
return [k for k in curr if curr[k] != prev[k]] if prev else []
return [k for k in cur_regs if cur_regs[k] != reg_dump[k]]
def render_regs_dump(self, regs, diff_reg=None):
"""
helper function for redering registers dump
def render_regs_dump(self, regs: Mapping[str, int], diff_reg: Collection[str]) -> None:
"""Helper function for rendering registers dump.
"""
lines = ""
for idx, r in enumerate(regs, 1):
line = "{}{}: 0x{{:08x}} {}\t".format(COLORS[(idx-1) // self.regs_a_row], r, color.END)
# find the length of the longest reg name to have all regs aligned in columns
longest = max(len(name) for name in regs)
if diff_reg and r in diff_reg:
line = f"{color.UNDERLINE}{color.BOLD}{line}"
def __render_regs_line() -> Iterator[str]:
elements = []
if idx % self.regs_a_row == 0 and idx != 32:
line += "\n"
for idx, (name, value) in enumerate(regs.items()):
line_color = f'{COLORS[idx // self.regs_a_row]}'
lines += line
if name in diff_reg:
line_color = f'{color.UNDERLINE}{color.BOLD}{line_color}'
print(lines.format(*regs.values()))
elements.append(f'{line_color}{name:{longest}s}: {value:#010x}{color.END}')
def render_stack_dump(self, arch_sp: int) -> None:
"""
helper function for redering stack dump
if (idx + 1) % self.regs_a_row == 0:
yield '\t'.join(elements)
elements.clear()
for line in __render_regs_line():
print(line)
def render_flags(self, flags: Mapping[str, int], before: str = ''):
def __set(f: str) -> str:
return f'{color.BLUE}{f.upper()}{color.END}'
def __cleared(f: str) -> str:
return f'{color.GREEN}{f.lower()}{color.END}'
s_before = f"[{before}] " if before else ""
s_flags = " ".join(__set(f) if val else __cleared(f) for f, val in flags.items())
print(f'{s_before}[flags: {s_flags}]')
def render_stack_dump(self, sp: int, dump: Sequence[Tuple[int, int, Union[int, str, None]]]) -> None:
"""Helper function for rendering stack dump.
"""
# Loops over stack range (last 10 addresses)
for idx in range(self.stack_num):
addr = arch_sp + idx * self.pointersize
# number of hexadecimal nibbles to display per value
nibbles = self.pointersize * 2
'''
@NOTE: Implemented new class arch_x8664 in order to bugfix issue with only dereferencing 32-bit pointers
on 64-bit emulation passes.
'''
if (val := self.try_read_pointer(addr)[0]): # defined to be try_read_pointer(addr)[0] - dereferneces pointer
for address, value, deref in dump:
offset = address - sp
# @TODO: Bug here where the values on the stack are being displayed in 32-bit format
print(f"SP + 0x{idx*self.pointersize:02x}│ [0x{addr:08x}] —▸ 0x{self.unpack(val):08x}", end="")
value_str = '(unreachable)' if value is None else f'{value:#0{nibbles + 2}x}'
# try to dereference wether it's a pointer
if (buf := self.try_read_pointer(addr))[0] is not None:
if isinstance(deref, int):
deref_str = f'{deref:#0{nibbles + 2}x}'
if (addr := self.unpack(buf[0])):
elif isinstance(deref, str):
deref_str = f'"{deref}"'
# try to dereference again
if (buf := self.try_read_pointer(addr))[0] is not None:
s = self.try_read_string(addr)
else:
deref_str = ''
if s and s.isprintable():
print(f" ◂— {self.read_string(addr)}", end="")
else:
print(f" ◂— 0x{self.unpack(buf[0]):08x}", end="")
print()
print(f'SP + {offset:#04x} │ {address:#010x} : {value_str}{f" {RARROW} {deref_str}" if deref_str else ""}')
def render_assembly(self, lines) -> None:
"""
helper function for rendering assembly
def render_assembly(self, listing: Sequence[InsnLike], pc: int, prediction: Prophecy) -> None:
"""Helper function for rendering assembly.
"""
# assembly before current location
if (backward := lines.get("backward", None)):
for line in backward:
self.print_asm(line)
def __render_asm_line(insn: InsnLike) -> str:
"""Helper function for rendering assembly instructions, indicates where we are and
the branch prediction provided by branch predictor
"""
# assembly for current location
if (cur_insn := lines.get("current", None)):
prophecy = self.predictor.predict()
self.print_asm(cur_insn, to_jump=prophecy.going)
trace_line = f"{insn.address:#010x} │ {insn.bytes.hex():18s} {insn.mnemonic:12} {insn.op_str:35s}"
# assembly after current location
if (forward := lines.get("forward", None)):
for line in forward:
self.print_asm(line)
cursor = '' # current instruction cursor
brmark = '' # branching mark
def swap_reg_name(self, cur_regs: Mapping[str, int], extra_dict=None) -> Mapping[str, int]:
"""
swap register name with more readable register name
"""
if insn.address == pc:
cursor = CURSOR
target_items = extra_dict.items() if extra_dict else self.regs_need_swapped.items()
if prediction.going:
# branch target might be None in case it should have been
# read from memory but that memory could not be reached
bmark = '?' if prediction.where is None else (GOING_DN if prediction.where > pc else GOING_UP)
for old_reg, new_reg in target_items:
cur_regs.update({old_reg: cur_regs.pop(new_reg)})
# apply some colors
brmark = f'{color.RED}{bmark}{color.RESET}'
return cur_regs
# <DEBUG>
where = '?' if prediction.where is None else f'{prediction.where:#010x}'
def print_asm(self, insn: CsInsn, to_jump: bool = False) -> None:
"""
helper function for printing assembly instructions, indicates where we are and the branch prediction
provided by BranchPredictor
"""
print(f'prediction: {f"taken, {where}" if prediction.going else "not taken"}')
# </DEBUG>
opcode = "".join(f"{b:02x}" for b in insn.bytes)
return f"{brmark:1s} {cursor:1s} {color.DARKGRAY}{trace_line}{color.RESET}"
trace_line = f"0x{insn.address:08x} │ {opcode:15s} {insn.mnemonic:10} {insn.op_str:35s}"
cursor = "►" if self.cur_addr == insn.address else " "
jump_sign = f"{color.RED}✓{color.END}" if to_jump else " "
print(f"{jump_sign} {cursor} {color.DARKGRAY}{trace_line}{color.END}")
for insn in listing:
print(__render_asm_line(insn))
class ContextRender(Context, Render):
@@ -172,17 +188,17 @@ class ContextRender(Context, Render):
base class for context render
"""
def __init__(self, ql, predictor):
def __init__(self, ql: Qiling, predictor: BranchPredictor):
super().__init__(ql)
Render.__init__(self)
self.predictor = predictor
self.prev_regs: Dict[str, int] = {}
def dump_regs(self) -> Mapping[str, int]:
"""
dump all registers
def get_regs(self) -> Dict[str, int]:
"""Save current registers state.
"""
return {reg_name: self.ql.arch.regs.read(reg_name) for reg_name in self.regs}
return {reg_name: self.read_reg(reg_name) for reg_name in self.regs}
@Render.divider_printer("[ STACK ]")
def context_stack(self) -> None:
@@ -190,50 +206,55 @@ class ContextRender(Context, Render):
display context stack dump
"""
self.render_stack_dump(self.ql.arch.regs.arch_sp)
sp = self.cur_sp
stack_dump = []
for i in range(self.stack_num):
address = sp + i * self.asize
# attempt to read current stack entry
value = self.try_read_pointer(address)
# treat stack entry as a pointer and attempt to dereference it
deref = None if value is None else self.get_deref(value)
stack_dump.append((address, value, deref))
self.render_stack_dump(sp, stack_dump)
@Render.divider_printer("[ REGISTERS ]")
def context_reg(self, saved_states: Mapping["str", int]) -> None:
"""
display context registers dump
def context_reg(self) -> None:
"""Rendering registers context.
"""
return NotImplementedError
curr = self.get_regs()
prev = self.prev_regs
@Render.divider_printer("[ DISASM ]")
curr = self.swap_regs(curr)
prev = self.swap_regs(prev)
diff_reg = self.reg_diff(curr, prev)
self.render_regs_dump(curr, diff_reg)
self.print_mode_info()
@Render.divider_printer("[ DISASM ]", footer=True)
def context_asm(self) -> None:
"""
read context assembly and render with render_assembly
"""Disassemble srrounding instructions.
"""
lines = {}
past_list = []
from_addr = self.cur_addr - self.disasm_num
to_addr = self.cur_addr + self.disasm_num
address = self.cur_addr
prediction = self.predictor.predict()
cur_addr = from_addr
while cur_addr <= to_addr:
insn = self.disasm(cur_addr)
cur_addr += insn.size
past_list.append(insn)
# assuming a single instruction is in the same size of a native pointer.
# this is not true for all architectures.
ptr = address - self.pointersize * self.disasm_num
listing = []
bk_list = []
fd_list = []
cur_insn = None
for each in past_list:
if each.address < self.cur_addr:
bk_list.append(each)
# taking disasm_num instructions before, current, and disasm_num instructions after
for _ in range(self.disasm_num * 2 + 1):
insn = self.disasm(ptr)
listing.append(insn)
elif each.address > self.cur_addr:
fd_list.append(each)
ptr += insn.size
elif each.address == self.cur_addr:
cur_insn = each
lines.update({
"backward": bk_list,
"forward": fd_list,
"current": cur_insn,
})
self.render_assembly(lines)
self.render_assembly(listing, address, prediction)
+46 -53
View File
@@ -3,73 +3,66 @@
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
#
from typing import Iterator
from .render import *
from .render import Render, ContextRender
from ..arch import ArchARM, ArchCORTEX_M
from ..misc import InsnLike
class ContextRenderARM(ContextRender, ArchARM):
"""
context render for ARM
"""Context renderer for ARM architecture.
"""
def __init__(self, ql, predictor):
super().__init__(ql, predictor)
ArchARM.__init__(self)
self.disasm_num = 8
def print_mode_info(self) -> None:
cpsr = self.read_reg(self._flags_reg)
@staticmethod
def print_mode_info(bits):
flags = ArchARM.get_flags(bits)
flags = ArchARM.get_flags(cpsr)
mode = ArchARM.get_mode(cpsr)
print(f"[{flags.pop('mode')} mode] ", end="")
for key, val in flags.items():
if val:
print(f"{color.BLUE}{key.upper()} ", end="")
else:
print(f"{color.GREEN}{key.lower()} ", end="")
self.render_flags(flags, f'{mode} mode')
print(color.END)
def __disasm_all(self, rng: range) -> Iterator[InsnLike]:
addr = rng.start
@Render.divider_printer("[ REGISTERS ]")
def context_reg(self, saved_reg_dump):
"""
redering context registers
while addr in rng:
insn = self.disasm(addr)
yield insn
addr += insn.size
@Render.divider_printer("[ DISASM ]", footer=True)
def context_asm(self) -> None:
"""Disassemble srrounding instructions.
"""
cur_regs = self.dump_regs()
cur_regs = self.swap_reg_name(cur_regs)
diff_reg = self.reg_diff(cur_regs, saved_reg_dump)
self.render_regs_dump(cur_regs, diff_reg=diff_reg)
self.print_mode_info(self.ql.arch.regs.cpsr)
address = self.cur_addr
prediction = self.predictor.predict()
# arm thumb may mix narrow and wide instructions so we can never know for
# sure where we need to start reading instructions from. to work around
# that we assume all instructions are wide, and then take the most recent
# ones into consideration.
listing = []
begin = address - self.asize * self.disasm_num
end = address
# disassemble all instructions in range, but keep only the last ones
listing.extend(self.__disasm_all(range(begin, end)))
listing = listing[-self.disasm_num:]
begin = address
end = address + self.asize * (self.disasm_num + 1)
# disassemble all instructions in range, but keep only the first ones
listing.extend(self.__disasm_all(range(begin, end)))
listing = listing[:self.disasm_num * 2 + 1]
self.render_assembly(listing, address, prediction)
class ContextRenderCORTEX_M(ContextRenderARM, ArchCORTEX_M):
"""Context renderer for ARM Cortex-M architecture.
"""
context render for cortex_m
"""
def __init__(self, ql, predictor):
super().__init__(ql, predictor)
ArchCORTEX_M.__init__(self)
self.regs_a_row = 3
@Render.divider_printer("[ REGISTERS ]")
def context_reg(self, saved_reg_dump):
cur_regs = self.dump_regs()
cur_regs = self.swap_reg_name(cur_regs)
# for re-order
extra_dict = {
"xpsr": "xpsr",
"control": "control",
"primask": "primask",
"faultmask": "faultmask",
"basepri": "basepri",
}
cur_regs = self.swap_reg_name(cur_regs, extra_dict=extra_dict)
diff_reg = self.reg_diff(cur_regs, saved_reg_dump)
self.render_regs_dump(cur_regs, diff_reg=diff_reg)
self.print_mode_info(self.ql.arch.regs.cpsr)
@@ -0,0 +1,55 @@
#!/usr/bin/env python3
#
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
#
from typing import Optional
from .render import Render, ContextRender
from ..arch import ArchIntel, ArchX86, ArchX64
class ContextRenderIntel(ContextRender):
"""Context renderer base class for Intel architecture.
"""
def print_mode_info(self) -> None:
eflags = self.read_reg('eflags')
flags = ArchIntel.get_flags(eflags)
iopl = ArchIntel.get_iopl(eflags)
self.render_flags(flags, f'iopl: {iopl}')
@Render.divider_printer("[ DISASM ]", footer=True)
def context_asm(self) -> None:
"""Disassemble srrounding instructions.
"""
address = self.cur_addr
prediction = self.predictor.predict()
ptr = address
listing = []
# since intel architecture has instructions with varying sizes, it is
# difficult to tell what were the preceding instructions. for that reason
# we display instructions only from current address and on.
for _ in range(9):
insn = self.disasm(ptr)
listing.append(insn)
ptr += insn.size
self.render_assembly(listing, address, prediction)
class ContextRenderX86(ContextRenderIntel, ArchX86):
"""Context renderer for x86 architecture.
"""
class ContextRenderX64(ContextRenderIntel, ArchX64):
"""Context renderer for x86-64 architecture.
"""
+5 -19
View File
@@ -3,27 +3,13 @@
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
#
from .render import *
from .render import ContextRender
from ..arch import ArchMIPS
class ContextRenderMIPS(ContextRender, ArchMIPS):
"""
context render for MIPS
"""Context renderer for MIPS architecture.
"""
def __init__(self, ql, predictor):
super().__init__(ql, predictor)
ArchMIPS.__init__(self)
@Render.divider_printer("[ REGISTERS ]")
def context_reg(self, saved_reg_dump):
"""
redering context registers
"""
cur_regs = self.dump_regs()
cur_regs = self.swap_reg_name(cur_regs)
diff_reg = self.reg_diff(cur_regs, saved_reg_dump)
self.render_regs_dump(cur_regs, diff_reg=diff_reg)
def print_mode_info(self) -> None:
pass
-68
View File
@@ -1,68 +0,0 @@
#!/usr/bin/env python3
#
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
#
from .render import *
from ..arch import ArchX86
class ContextRenderX86(ContextRender, ArchX86):
"""
context render for X86
"""
def __init__(self, ql, predictor):
super().__init__(ql, predictor)
ArchX86.__init__(self)
@Render.divider_printer("[ REGISTERS ]")
def context_reg(self, saved_reg_dump):
cur_regs = self.dump_regs()
diff_reg = self.reg_diff(cur_regs, saved_reg_dump)
self.render_regs_dump(cur_regs, diff_reg=diff_reg)
flags = self.get_flags(self.ql.arch.regs.eflags)
print("EFLAGS: ", end="")
print(color.GREEN, end="")
for key, val in flags.items():
if val:
print(f"{color.BLUE}{key.upper()} ", end="")
else:
print(f"{color.GREEN}{key.lower()} ", end="")
print(color.END)
@Render.divider_printer("[ DISASM ]")
def context_asm(self):
lines = {}
past_list = []
cur_addr = self.cur_addr
while len(past_list) < 10:
line = self.disasm(cur_addr)
past_list.append(line)
cur_addr += line.size
fd_list = []
cur_insn = None
for each in past_list:
if each.address > self.cur_addr:
fd_list.append(each)
elif each.address == self.cur_addr:
cur_insn = each
"""
only forward and current instruction will be printed,
because we don't have a solid method to disasm backward instructions,
since it's x86 instruction length is variadic
"""
lines.update({
"current": cur_insn,
"forward": fd_list,
})
self.render_assembly(lines)
@@ -1,58 +0,0 @@
#!/usr/bin/env python3
#
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
#
from .render import *
from ..arch import ArchX8664
class ContextRenderX8664(ContextRender, ArchX8664):
"""
Context render for X86_64
"""
def __init__(self, ql, predictor):
super().__init__(ql, predictor)
ArchX8664.__init__(self)
@Render.divider_printer("[ REGISTERS ]")
def context_reg(self, saved_reg_dump):
cur_regs = self.dump_regs()
diff_reg = self.reg_diff(cur_regs, saved_reg_dump)
self.render_regs_dump(cur_regs, diff_reg=diff_reg)
print(color.GREEN, "EFLAGS: [CF: {flags[CF]}, PF: {flags[PF]}, AF: {flags[AF]}, ZF: {flags[ZF]}, SF: {flags[SF]}, OF: {flags[OF]}]".format(flags=self.get_flags(self.ql.arch.regs.eflags)), color.END, sep="")
@Render.divider_printer("[ DISASM ]")
def context_asm(self):
lines = {}
past_list = []
cur_addr = self.cur_addr
while len(past_list) < 10:
line = self.disasm(cur_addr)
past_list.append(line)
cur_addr += line.size
fd_list = []
cur_insn = None
for each in past_list:
if each.address > self.cur_addr:
fd_list.append(each)
elif each.address == self.cur_addr:
cur_insn = each
"""
only forward and current instruction will be printed,
because we don't have a solid method to disasm backward instructions,
since it's x86 instruction length is variadic
"""
lines.update({
"current": cur_insn,
"forward": fd_list,
})
self.render_assembly(lines)
+224 -205
View File
@@ -4,16 +4,16 @@
#
from __future__ import annotations
from typing import TYPE_CHECKING, Callable, Dict, Mapping, Tuple, Type
from capstone import CsInsn
from enum import Enum
from typing import TYPE_CHECKING, Any, Callable, Dict, List, Mapping, Optional, Tuple, Type, TypeVar, Union
from qiling.const import QL_ARCH
from .render import (
ContextRender,
ContextRenderX86,
ContextRenderX8664,
ContextRenderX64,
ContextRenderARM,
ContextRenderCORTEX_M,
ContextRenderMIPS
@@ -22,7 +22,7 @@ from .render import (
from .branch_predictor import (
BranchPredictor,
BranchPredictorX86,
BranchPredictorX8664,
BranchPredictorX64,
BranchPredictorARM,
BranchPredictorCORTEX_M,
BranchPredictorMIPS,
@@ -36,81 +36,69 @@ if TYPE_CHECKING:
from .qdb import QlQdb
def qdb_print(msgtype: QDB_MSG, msg: str) -> None:
"""
color printing
_K = TypeVar('_K')
_V = TypeVar('_V')
def qdb_print(level: QDB_MSG, msg: str) -> None:
"""Log printing.
"""
def print_error(msg):
return f"{color.RED}[!] {msg}{color.END}"
decorations = {
QDB_MSG.ERROR: ('!', color.RED),
QDB_MSG.INFO : ('+', color.CYAN),
}
def print_info(msg):
return f"{color.CYAN}[+] {msg}{color.END}"
tag, col = decorations[level]
color_coated = {
QDB_MSG.ERROR: print_error,
QDB_MSG.INFO : print_info,
}.get(msgtype)(msg)
print(color_coated)
print(f'{col}[{tag}] {msg}{color.END}')
def setup_address_marker():
class Marker:
"""provide the ability to mark an address as a more easier rememberable alias
"""
class Marker:
"""provide the ability to mark an address as a more easier rememberable alias
def __init__(self):
self._mark_list: Dict[str, int] = {}
def get_address(self, sym: str) -> Optional[int]:
"""
get the mapped address to a symbol if it's in the mark_list
"""
def __init__(self):
self._mark_list = {}
return self._mark_list.get(sym)
def get_symbol(self, sym):
"""
get the mapped address to a symbol if it's in the mark_list
"""
@property
def mark_list(self):
"""
get a list about what we marked
"""
return self._mark_list.get(sym, None)
return self._mark_list.items()
@property
def mark_list(self):
"""
get a list about what we marked
"""
def gen_sym_name(self) -> str:
"""
generating symbol name automatically
"""
return self._mark_list.items()
syms = len(self._mark_list)
def gen_sym_name(self):
"""
generating symbol name automatically
"""
# find the next available 'sym#'
return next((f'sym{i}' for i in range(syms) if f'sym{i}' not in self._mark_list), f'sym{syms}')
sym_name, idx = "sym0", 0
while sym_name in self._mark_list:
idx += 1
sym_name = f"sym{idx}"
def mark(self, loc: int, sym: Optional[str] = None) -> str:
"""
mark loc as sym
"""
return sym_name
sym = sym or self.gen_sym_name()
def mark_only_loc(self, loc):
"""
mark when location provided only
"""
if sym in self._mark_list:
return ''
sym_name = self.gen_sym_name()
self.mark(sym_name, loc)
return sym_name
self._mark_list[sym] = loc
def mark(self, sym: str, loc: int):
"""
mark loc as sym
"""
if sym not in self.mark_list:
self._mark_list.update({sym: loc})
else:
return f"dumplicated symbol name: {sym} at address: 0x{loc:08x}"
return Marker()
return sym
# helper functions for setting proper branch predictor and context render depending on different arch
@@ -120,7 +108,7 @@ def setup_branch_predictor(ql: Qiling) -> BranchPredictor:
preds: Dict[QL_ARCH, Type[BranchPredictor]] = {
QL_ARCH.X86: BranchPredictorX86,
QL_ARCH.X8664: BranchPredictorX8664,
QL_ARCH.X8664: BranchPredictorX64,
QL_ARCH.ARM: BranchPredictorARM,
QL_ARCH.CORTEX_M: BranchPredictorCORTEX_M,
QL_ARCH.MIPS: BranchPredictorMIPS
@@ -136,7 +124,7 @@ def setup_context_render(ql: Qiling, predictor: BranchPredictor) -> ContextRende
rends: Dict[QL_ARCH, Type[ContextRender]] = {
QL_ARCH.X86: ContextRenderX86,
QL_ARCH.X8664: ContextRenderX8664,
QL_ARCH.X8664: ContextRenderX64,
QL_ARCH.ARM: ContextRenderARM,
QL_ARCH.CORTEX_M: ContextRenderCORTEX_M,
QL_ARCH.MIPS: ContextRenderMIPS
@@ -146,121 +134,137 @@ def setup_context_render(ql: Qiling, predictor: BranchPredictor) -> ContextRende
return r(ql, predictor)
def run_qdb_script(qdb: QlQdb, filename: str) -> None:
with open(filename) as fd:
for line in iter(fd.readline, ""):
# skip commented and empty line
if line.startswith("#") or line == "\n":
continue
class MemDiff(Enum):
ADD = '+'
REM = '-'
MOD = '*'
cmd, arg, _ = qdb.parseline(line)
func = getattr(qdb, f"do_{cmd}")
if arg:
func(arg)
else:
func()
RamKey = Tuple[int, int]
RamVal = Tuple[int, str, bytes]
RamDiffKey = Tuple[int, int]
RamDiffVal = Tuple[MemDiff, Tuple[int, str, Union[bytes, Tuple]]]
class DiffedState:
"""
internal container for storing diffed state
"""
def __init__(self, reg, xreg, ram, loader):
self.reg: Dict[str, int] = reg
self.xreg: Dict[str, int] = xreg
self.ram: Dict[RamDiffKey, RamDiffVal] = ram
self.loader: Dict[str, Any] = loader
class State:
"""
internal container for storing raw state from qiling
"""
def __init__(self, saved: Mapping[str, Mapping]):
self.reg: Dict[str, int] = saved.get("reg") or {}
self.xreg: Dict[str, int] = saved.get("cpr") or saved.get("msr") or {}
mem = saved.get("mem") or {}
ram = mem.get("ram") or []
# saved ram lists might not match in order, we turn them into dicts to work around
# that. in these dicts every memory content is mapped to its memory entry's properties
self.ram: Dict[RamKey, RamVal] = {(lbound, ubound): (perms, label, data) for lbound, ubound, perms, label, data in ram}
self.loader: Dict[str, Any] = saved.get('loader') or {}
@staticmethod
def __dict_diff(d0: Mapping[_K, _V], d1: Mapping[_K, _V]) -> Dict[_K, _V]:
return {k: v for k, v in d0.items() if v != d1.get(k)}
def _diff_reg(self, other: State) -> Dict[str, int]:
return State.__dict_diff(self.reg, other.reg)
def _diff_xreg(self, other: State) -> Dict[str, int]:
return State.__dict_diff(self.xreg, other.xreg)
def _diff_ram(self, other: State) -> Dict[RamDiffKey, RamDiffVal]:
ram0 = self.ram
ram1 = other.ram
ram_diff: Dict[RamDiffKey, RamDiffVal] = {}
removed = [rng for rng in ram0 if rng not in ram1]
added = [rng for rng in ram1 if rng not in ram0]
modified = [rng for rng in ram0 if rng in ram1 and ram0[rng] != ram1[rng]]
# memory regions that got removed should be re-added
for rng in removed:
ram_diff[rng] = (MemDiff.ADD, ram0[rng])
# memory regions that got added should be removed
for rng in added:
_, label, _ = ram1[rng]
# though we discard data as it is not required anymore, label is still required
# to determine the method of removing the region: brk, mmap, or ordinary map
ram_diff[rng] = (MemDiff.REM, (-1, label, b''))
# memory regions that fot modified should be reverted back
for rng in modified:
perms0, label0, data0 = ram0[rng]
perms1, label1, data1 = ram1[rng]
perms = -1 if perms0 == perms1 else perms0
assert label0 == label1, 'memory region label changed unexpectedly'
assert len(data0) == len(data1), 'memory contents differ in size'
# scan both data chunks and keep the index and byte value of the unmatched ones.
# if memory contents are identical, this will result in an empty tuple
data_diff = tuple((i, b0) for i, (b0, b1) in enumerate(zip(data0, data1)) if b0 != b1)
ram_diff[rng] = (MemDiff.MOD, (perms, label0, data_diff))
# <DEBUG>
# for rng, (opcode, diff) in sorted(ram_diff.items()):
# lbound, ubound = rng
# perms, label, data = diff
#
# print(f'{opcode.name} {lbound:010x} - {ubound:010x} {perms:03b} {label:24s} ~{len(data)}')
# </DEBUG>
return ram_diff
def diff(self, other: State) -> DiffedState:
"""Diff between previous and current state.
"""
return DiffedState(
self._diff_reg(other),
self._diff_xreg(other),
self._diff_ram(other),
self.loader
)
class SnapshotManager:
"""for functioning differential snapshot
Supports Qdb features like:
1. record/replay debugging
2. memory access in gdb-style
"""Differential snapshot object.
"""
class State:
"""
internal container for storing raw state from qiling
"""
def __init__(self, saved_state):
self.reg, self.ram, self.xreg = SnapshotManager.transform(saved_state)
class DiffedState:
"""
internal container for storing diffed state
"""
def __init__(self, diffed_st):
self.reg, self.ram, self.xreg = diffed_st
@staticmethod
def transform(st):
"""
transform saved context into binary set
"""
reg = st.get("reg", {})
mem = st.get("mem", [])
xreg = st.get("cpr") or st.get("msr") or {}
ram = []
for mem_seg in mem["ram"]:
lbound, ubound, perms, label, raw_bytes = mem_seg
rb_set = {(idx, val) for idx, val in enumerate(raw_bytes)}
ram.append((lbound, ubound, perms, label, rb_set))
return (reg, ram, xreg)
def __init__(self, ql):
def __init__(self, ql: Qiling):
self.ql = ql
self.layers = []
self.layers: List[DiffedState] = []
def _save(self) -> State:
def save(self) -> State:
"""
acquire current State by wrapping saved context from ql.save()
"""
return self.State(self.ql.save())
return State(self.ql.save(reg=True, mem=True, loader=True))
def diff_reg(self, prev_reg, cur_reg):
"""
diff two register values
"""
diffed = filter(lambda t: t[0] != t[1], zip(prev_reg.items(), cur_reg.items()))
return {prev[0]: prev[1] for prev, _ in diffed}
def diff_ram(self, prev_ram, cur_ram):
"""
diff two ram data if needed
"""
if any((cur_ram is None, prev_ram is None, prev_ram == cur_ram)):
return
ram = []
paired = zip(prev_ram, cur_ram)
for each in paired:
# lbound, ubound, perm, label, data
*prev_others, prev_rb_set = each[0]
*cur_others, cur_rb_set = each[1]
if prev_others == cur_others and cur_rb_set != prev_rb_set:
diff_set = prev_rb_set - cur_rb_set
else:
continue
ram.append((*cur_others, diff_set))
return ram
def diff(self, before_st, after_st):
"""
diff between previous and current state
"""
# prev_st = self.layers.pop()
diffed_reg = self.diff_reg(before_st.reg, after_st.reg)
diffed_ram = self.diff_ram(before_st.ram, after_st.ram)
diffed_xreg = self.diff_reg(before_st.xreg, after_st.xreg)
# diffed_reg = self.diff_reg(prev_st.reg, cur_st.reg)
# diffed_ram = self.diff_ram(prev_st.ram, cur_st.ram)
return self.DiffedState((diffed_reg, diffed_ram, diffed_xreg))
def snapshot(func):
@staticmethod
def snapshot(func: Callable) -> Callable:
"""
decorator function for saving differential context on certian qdb command
"""
@@ -268,17 +272,16 @@ class SnapshotManager:
def magic(self: QlQdb, *args, **kwargs):
if self.rr:
# save State before execution
p_st = self.rr._save()
before = self.rr.save()
# certian execution to be snapshot
func(self, *args, **kwargs)
# save State after execution
q_st = self.rr._save()
after = self.rr.save()
# merge two saved States into a DiffedState
st = self.rr.diff(p_st, q_st)
self.rr.layers.append(st)
self.rr.layers.append(before.diff(after))
else:
func(self, *args, **kwargs)
@@ -289,49 +292,65 @@ class SnapshotManager:
helper function for restoring running state from an existing incremental snapshot
"""
prev_st = self.layers.pop()
cur_st = self._save()
prev_st = self.layers.pop() # DiffedState
curr_st = self.save() # State, expected to be identical to 'after' State in snapshot method
for reg_name, reg_value in prev_st.reg.items():
cur_st.reg[reg_name] = reg_value
curr_st.reg.update(prev_st.reg)
curr_st.xreg.update(prev_st.xreg)
for reg_name, reg_value in prev_st.xreg.items():
cur_st.xreg[reg_name] = reg_value
if prev_st.ram:
diff_ram = prev_st.ram
curr_ram = curr_st.ram
to_be_restored = {
"reg": cur_st.reg,
# we must begin by removing unwanted memory regions, otherwise we would not be able to
# add new ones in case they overlap. here we iterate over the diff dictionary but handle
# only remove opcodes
for rng, (opcode, props) in diff_ram.items():
lbound, ubound = rng
size = ubound - lbound
if opcode is MemDiff.REM:
# NOTE: it doesn't seem like distinguishing between brk, mmap, mmap annonymous
# and regular maps is actually required
self.ql.mem.unmap(lbound, size)
# doind a second pass, but this time handling add and modify opcodes
for rng, (opcode, props) in diff_ram.items():
lbound, ubound = rng
perms, label, data = props
size = ubound - lbound
if opcode is MemDiff.ADD:
# TODO: distinguish between brk, mmap, mmap annonymous and regular maps
self.ql.mem.map(lbound, size, perms, label)
self.ql.mem.write(lbound, data)
elif opcode is MemDiff.MOD:
if perms != -1:
self.ql.mem.protect(lbound, size, perms)
# is there a diff for this memory range?
if data:
# get current memory content
_, _, curr_data = curr_ram[rng]
curr_data = bytearray(curr_data)
# patch with existing diff
for i, b in data:
curr_data[i] = b
# write patched data
self.ql.mem.write(lbound, bytes(curr_data))
self.ql.restore({
'reg': curr_st.reg,
# though we have arch-specific context to restore, we want to keep this arch-agnostic.
# one way to work around that is to include 'xreg' both as msr (intel) and cpr (arm).
# only the relevant one will be picked up while the other one will be discarded
"msr": cur_st.xreg,
"cpr": cur_st.xreg
}
'msr': curr_st.xreg,
'cpr': curr_st.xreg,
# FIXME: not sure how this one even works. while curr_st is a fresh qiling snapshot,
# prev_st is a DiffedState which does not hold a complete state but only a diff between
# two points which seem to be unrelated here.
#
# this code only patches current memory content with the diff between points a and b while
# we may be already be at point c.
if getattr(prev_st, "ram", None) and prev_st.ram != cur_st.ram:
ram = []
# lbound, ubound, perm, label, data
for each in prev_st.ram:
*prev_others, prev_rb_set = each
for *cur_others, cur_rb_set in cur_st.ram:
if prev_others == cur_others:
cur_rb_dict = dict(cur_rb_set)
for idx, val in prev_rb_set:
cur_rb_dict[idx] = val
bs = bytes(dict(sorted(cur_rb_dict.items())).values())
ram.append((*cur_others, bs))
to_be_restored["mem"] = {
"ram": ram,
"mmio": {}
}
self.ql.restore(to_be_restored)
'loader': prev_st.loader
})
-344
View File
@@ -1,344 +0,0 @@
#!/usr/bin/env python3
#
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
#
from elftools.common.exceptions import ELFError
from elftools.common.py3compat import (
ifilter, byte2int, bytes2str, itervalues, str2bytes, iterbytes)
from elftools.elf.elffile import ELFFile
from elftools.elf.dynamic import DynamicSection, DynamicSegment
from elftools.elf.enums import ENUM_D_TAG
from elftools.elf.segments import InterpSegment
from elftools.elf.sections import NoteSection, SymbolTableSection
from elftools.elf.gnuversions import (
GNUVerSymSection, GNUVerDefSection,
GNUVerNeedSection,
)
from elftools.elf.relocation import RelocationSection
from elftools.elf.descriptions import (
describe_ei_class, describe_ei_data, describe_ei_version,
describe_ei_osabi, describe_e_type, describe_e_machine,
describe_e_version_numeric, describe_p_type, describe_p_flags,
describe_sh_type, describe_sh_flags,
describe_symbol_type, describe_symbol_bind, describe_symbol_visibility,
describe_symbol_shndx, describe_reloc_type, describe_dyn_tag,
describe_dt_flags, describe_dt_flags_1, describe_ver_flags, describe_note,
describe_attr_tag_arm
)
from elftools.elf.constants import E_FLAGS
from elftools.elf.constants import E_FLAGS_MASKS
from qiling import Qiling
class QlReadELF(object):
def __init__(self, ql:Qiling, elf_stream):
self.ql = ql
self.elffile = ELFFile(elf_stream)
self._versioninfo = None
def elf_file_header(self):
elf_header = {}
def add_info(key, value):
elf_header[key] = value
header = self.elffile.header
e_ident = header['e_ident']
add_info('Magic', ' '.join('%2.2x' % byte2int(b)
for b in self.elffile.e_ident_raw))
add_info('Class',describe_ei_class(e_ident['EI_CLASS']))
add_info('Data', describe_ei_data(e_ident['EI_DATA']))
add_info('Version', e_ident['EI_VERSION'])
add_info('OS/ABI', describe_ei_osabi(e_ident['EI_OSABI']))
add_info('ABI Version', e_ident['EI_ABIVERSION'])
add_info('Type', describe_e_type(header['e_type']))
add_info('Machine', describe_e_machine(header['e_machine']))
add_info('Version_e', describe_e_version_numeric(header['e_version']))
add_info('Entry point address', self._format_hex(header['e_entry']))
add_info('Start of program headers', header['e_phoff'])
add_info('Start of section headers', header['e_shoff'])
add_info('Flags', [self._format_hex(header['e_flags']),
self.decode_flags(header['e_flags'])])
add_info('Size of this header', header['e_ehsize'])
add_info('Size of program headers', header['e_phentsize'])
add_info('Number of program headers', header['e_phnum'])
add_info('Size of section headers', header['e_shentsize'])
add_info('Number of section headers', header['e_shnum'])
add_info('Section header string table index', header['e_shstrndx'])
return elf_header
def elf_program_headers(self):
program_headers = []
def add_info(dic):
program_headers.append(dic)
if self.elffile.num_segments() == 0:
return None
for segment in self.elffile.iter_segments():
program_hdr = {}
program_hdr['Type'] = describe_p_type(segment['p_type'])
program_hdr['Offset'] = self._format_hex(segment['p_offset'], fieldsize=6)
program_hdr['VirtAddr'] = self._format_hex(segment['p_vaddr'], fullhex=True)
program_hdr['PhysAddr'] = self._format_hex(segment['p_paddr'], fullhex=True)
program_hdr['FileSiz'] = self._format_hex(segment['p_filesz'], fieldsize=5)
program_hdr['MemSiz'] = self._format_hex(segment['p_memsz'], fieldsize=5)
program_hdr['Flg'] = describe_p_flags(segment['p_flags'])
program_hdr['Align'] = self._format_hex(segment['p_align'])
add_info(program_hdr)
return program_headers
def elf_section_headers(self):
section_headers = []
def add_info(dic):
section_headers.append(dic)
if self.elffile.num_sections() == 0:
return None
for nsec, section in enumerate(self.elffile.iter_sections()):
section_hdr = {}
section_hdr['index'] = nsec
section_hdr['Name'] = section.name
section_hdr['Type'] = describe_sh_type(section['sh_type'])
section_hdr['Addr'] = self._format_hex(section['sh_addr'], fieldsize=8, lead0x=False)
section_hdr['Offset'] = self._format_hex(section['sh_offset'], fieldsize=6, lead0x=False)
section_hdr['Size'] = self._format_hex(section['sh_size'], fieldsize=6, lead0x=False)
section_hdr['ES'] = self._format_hex(section['sh_entsize'], fieldsize=2, lead0x=False)
section_hdr['Flag'] = describe_sh_flags(section['sh_flags'])
section_hdr['Lk'] = section['sh_link']
section_hdr['Inf'] = section['sh_info']
section_hdr['Al'] = section['sh_addralign']
add_info(section_hdr)
return section_headers
def elf_symbol_tables(self):
symbol_tables_list = []
def add_info(dic):
symbol_tables_list.append(dic)
self._init_versioninfo()
symbol_tables = [s for s in self.elffile.iter_sections()
if isinstance(s, SymbolTableSection)]
if not symbol_tables and self.elffile.num_sections() == 0:
return None
for section in symbol_tables:
if not isinstance(section, SymbolTableSection):
continue
if section['sh_entsize'] == 0:
continue
for nsym, symbol in enumerate(section.iter_symbols()):
version_info = ''
if (section['sh_type'] == 'SHT_DYNSYM' and
self._versioninfo['type'] == 'GNU'):
version = self._symbol_version(nsym)
if (version['name'] != symbol.name and
version['index'] not in ('VER_NDX_LOCAL',
'VER_NDX_GLOBAL')):
if version['filename']:
# external symbol
version_info = '@%(name)s (%(index)i)' % version
else:
# internal symbol
if version['hidden']:
version_info = '@%(name)s' % version
else:
version_info = '@@%(name)s' % version
symbol_info = {}
symbol_info['index'] = nsym
symbol_info['Value'] = self._format_hex(
symbol['st_value'], fullhex=True, lead0x=False)
symbol_info['Size'] = symbol['st_size']
symbol_info['Type'] = describe_symbol_type(symbol['st_info']['type'])
symbol_info['Bind'] = describe_symbol_bind(symbol['st_info']['bind'])
symbol_info['Vis'] = describe_symbol_visibility(symbol['st_other']['visibility'])
symbol_info['Ndx'] = describe_symbol_shndx(symbol['st_shndx'])
symbol_info['Name'] = symbol.name
symbol_info['version_info'] = version_info
add_info(symbol_info)
return symbol_tables_list
def decode_flags(self, flags):
description = ""
if self.elffile['e_machine'] == "EM_ARM":
eabi = flags & E_FLAGS.EF_ARM_EABIMASK
flags &= ~E_FLAGS.EF_ARM_EABIMASK
if flags & E_FLAGS.EF_ARM_RELEXEC:
description += ', relocatable executabl'
flags &= ~E_FLAGS.EF_ARM_RELEXEC
if eabi == E_FLAGS.EF_ARM_EABI_VER5:
EF_ARM_KNOWN_FLAGS = E_FLAGS.EF_ARM_ABI_FLOAT_SOFT|E_FLAGS.EF_ARM_ABI_FLOAT_HARD|E_FLAGS.EF_ARM_LE8|E_FLAGS.EF_ARM_BE8
description += ', Version5 EABI'
if flags & E_FLAGS.EF_ARM_ABI_FLOAT_SOFT:
description += ", soft-float ABI"
elif flags & E_FLAGS.EF_ARM_ABI_FLOAT_HARD:
description += ", hard-float ABI"
if flags & E_FLAGS.EF_ARM_BE8:
description += ", BE8"
elif flags & E_FLAGS.EF_ARM_LE8:
description += ", LE8"
if flags & ~EF_ARM_KNOWN_FLAGS:
description += ', <unknown>'
else:
description += ', <unrecognized EABI>'
elif self.elffile['e_machine'] == "EM_MIPS":
if flags & E_FLAGS.EF_MIPS_NOREORDER:
description += ", noreorder"
if flags & E_FLAGS.EF_MIPS_PIC:
description += ", pic"
if flags & E_FLAGS.EF_MIPS_CPIC:
description += ", cpic"
if (flags & E_FLAGS.EF_MIPS_ABI2):
description += ", abi2"
if (flags & E_FLAGS.EF_MIPS_32BITMODE):
description += ", 32bitmode"
if (flags & E_FLAGS_MASKS.EFM_MIPS_ABI_O32):
description += ", o32"
elif (flags & E_FLAGS_MASKS.EFM_MIPS_ABI_O64):
description += ", o64"
elif (flags & E_FLAGS_MASKS.EFM_MIPS_ABI_EABI32):
description += ", eabi32"
elif (flags & E_FLAGS_MASKS.EFM_MIPS_ABI_EABI64):
description += ", eabi64"
if (flags & E_FLAGS.EF_MIPS_ARCH) == E_FLAGS.EF_MIPS_ARCH_1:
description += ", mips1"
if (flags & E_FLAGS.EF_MIPS_ARCH) == E_FLAGS.EF_MIPS_ARCH_2:
description += ", mips2"
if (flags & E_FLAGS.EF_MIPS_ARCH) == E_FLAGS.EF_MIPS_ARCH_3:
description += ", mips3"
if (flags & E_FLAGS.EF_MIPS_ARCH) == E_FLAGS.EF_MIPS_ARCH_4:
description += ", mips4"
if (flags & E_FLAGS.EF_MIPS_ARCH) == E_FLAGS.EF_MIPS_ARCH_5:
description += ", mips5"
if (flags & E_FLAGS.EF_MIPS_ARCH) == E_FLAGS.EF_MIPS_ARCH_32R2:
description += ", mips32r2"
if (flags & E_FLAGS.EF_MIPS_ARCH) == E_FLAGS.EF_MIPS_ARCH_64R2:
description += ", mips64r2"
if (flags & E_FLAGS.EF_MIPS_ARCH) == E_FLAGS.EF_MIPS_ARCH_32:
description += ", mips32"
if (flags & E_FLAGS.EF_MIPS_ARCH) == E_FLAGS.EF_MIPS_ARCH_64:
description += ", mips64"
return description
def _format_hex(self, addr, fieldsize=None, fullhex=False, lead0x=True,
alternate=False):
""" Format an address into a hexadecimal string.
fieldsize:
Size of the hexadecimal field (with leading zeros to fit the
address into. For example with fieldsize=8, the format will
be %08x
If None, the minimal required field size will be used.
fullhex:
If True, override fieldsize to set it to the maximal size
needed for the elfclass
lead0x:
If True, leading 0x is added
alternate:
If True, override lead0x to emulate the alternate
hexadecimal form specified in format string with the #
character: only non-zero values are prefixed with 0x.
This form is used by readelf.
"""
if alternate:
if addr == 0:
lead0x = False
else:
lead0x = True
fieldsize -= 2
s = '0x' if lead0x else ''
if fullhex:
fieldsize = 8 if self.elffile.elfclass == 32 else 16
if fieldsize is None:
field = '%x'
else:
field = '%' + '0%sx' % fieldsize
return s + field % addr
def _init_versioninfo(self):
""" Search and initialize informations about version related sections
and the kind of versioning used (GNU or Solaris).
"""
if self._versioninfo is not None:
return
self._versioninfo = {'versym': None, 'verdef': None,
'verneed': None, 'type': None}
for section in self.elffile.iter_sections():
if isinstance(section, GNUVerSymSection):
self._versioninfo['versym'] = section
elif isinstance(section, GNUVerDefSection):
self._versioninfo['verdef'] = section
elif isinstance(section, GNUVerNeedSection):
self._versioninfo['verneed'] = section
elif isinstance(section, DynamicSection):
for tag in section.iter_tags():
if tag['d_tag'] == 'DT_VERSYM':
self._versioninfo['type'] = 'GNU'
break
if not self._versioninfo['type'] and (
self._versioninfo['verneed'] or self._versioninfo['verdef']):
self._versioninfo['type'] = 'Solaris'
def _symbol_version(self, nsym):
""" Return a dict containing information on the
or None if no version information is available
"""
self._init_versioninfo()
symbol_version = dict.fromkeys(('index', 'name', 'filename', 'hidden'))
if (not self._versioninfo['versym'] or
nsym >= self._versioninfo['versym'].num_symbols()):
return None
symbol = self._versioninfo['versym'].get_symbol(nsym)
index = symbol.entry['ndx']
if not index in ('VER_NDX_LOCAL', 'VER_NDX_GLOBAL'):
index = int(index)
if self._versioninfo['type'] == 'GNU':
# In GNU versioning mode, the highest bit is used to
# store wether the symbol is hidden or not
if index & 0x8000:
index &= ~0x8000
symbol_version['hidden'] = True
if (self._versioninfo['verdef'] and
index <= self._versioninfo['verdef'].num_versions()):
_, verdaux_iter = \
self._versioninfo['verdef'].get_version(index)
symbol_version['name'] = next(verdaux_iter).name
else:
verneed, vernaux = \
self._versioninfo['verneed'].get_version(index)
symbol_version['name'] = vernaux.name
symbol_version['filename'] = verneed.name
symbol_version['index'] = index
return symbol_version
+3
View File
@@ -74,6 +74,9 @@ class QlMemoryMappedError(QlErrorBase):
class QlGDTError(QlErrorBase):
pass
class QlProxyConnectionError(QlErrorBase):
pass
class QlSyscallError(QlErrorBase):
def __init__(self, errno, msg):
super(QlSyscallError, self).__init__(msg)
+2 -2
View File
@@ -96,8 +96,8 @@ def ql_afl_fuzz_custom(ql: Qiling,
def __place_input_wrapper(uc: Uc, input_bytes: Array[c_char], iters: int, context: Any) -> bool:
return place_input_callback(ql, input_bytes.raw, iters)
def __validate_crash_wrapper(uc: Uc, result: int, input_bytes: bytes, iters: int, context: Any) -> bool:
return validate_crash_callback(ql, result, input_bytes, iters)
def __validate_crash_wrapper(uc: Uc, result: int, input_bytes: Array[c_char], iters: int, context: Any) -> bool:
return validate_crash_callback(ql, result, input_bytes.raw, iters)
def __fuzzing_wrapper(uc: Uc, context: Any) -> int:
return fuzzing_callback(ql)
+12 -11
View File
@@ -3,9 +3,14 @@
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
#
from abc import ABC, abstractmethod
from __future__ import annotations
from qiling import Qiling
from abc import ABC, abstractmethod
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from qiling import Qiling
class QlBaseCoverage(ABC):
@@ -15,25 +20,21 @@ class QlBaseCoverage(ABC):
all the methods marked with the @abstractmethod decorator.
"""
FORMAT_NAME: str
def __init__(self, ql: Qiling):
super().__init__()
self.ql = ql
@property
@staticmethod
@abstractmethod
def FORMAT_NAME() -> str:
raise NotImplementedError
@abstractmethod
def activate(self):
def activate(self) -> None:
pass
@abstractmethod
def deactivate(self):
def deactivate(self) -> None:
pass
@abstractmethod
def dump_coverage(self, coverage_file: str):
def dump_coverage(self, coverage_file: str) -> None:
pass
+56 -23
View File
@@ -3,12 +3,20 @@
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
#
from ctypes import Structure
from ctypes import c_uint32, c_uint16
from __future__ import annotations
from ctypes import Structure, c_uint32, c_uint16
from functools import lru_cache
from typing import TYPE_CHECKING, BinaryIO, Dict, Tuple
from .base import QlBaseCoverage
if TYPE_CHECKING:
from qiling import Qiling
from qiling.loader.loader import QlLoader
# Adapted from https://www.ayrx.me/drcov-file-format
class bb_entry(Structure):
_fields_ = [
@@ -29,36 +37,61 @@ class QlDrCoverage(QlBaseCoverage):
FORMAT_NAME = "drcov"
def __init__(self, ql):
def __init__(self, ql: Qiling):
super().__init__(ql)
self.drcov_version = 2
self.drcov_flavor = 'drcov'
self.basic_blocks = []
self.basic_blocks: Dict[int, bb_entry] = {}
self.bb_callback = None
@staticmethod
def block_callback(ql, address, size, self):
for mod_id, mod in enumerate(ql.loader.images):
if mod.base <= address <= mod.end:
ent = bb_entry(address - mod.base, size, mod_id)
self.basic_blocks.append(ent)
break
@lru_cache(maxsize=64)
def _get_img_base(self, loader: QlLoader, address: int) -> Tuple[int, int]:
"""Retrieve the containing image of a given address.
def activate(self):
self.bb_callback = self.ql.hook_block(self.block_callback, user_data=self)
Addresses are expected to be aligned to page boundary, and cached for faster retrieval.
"""
def deactivate(self):
self.ql.hook_del(self.bb_callback)
return next((i, img.base) for i, img in enumerate(loader.images) if img.base <= address < img.end)
def block_callback(self, ql: Qiling, address: int, size: int):
if address not in self.basic_blocks:
try:
# we rely on the fact that images are allocated on page size boundary and
# use it to speed up image retrieval. we align the basic block address to
# page boundary, knowing basic blocks within the same page belong to the
# same image. then we use the aligned address to retreive the containing
# image. returned values are cached so subsequent retrievals for basic
# blocks within the same page will return the cached value instead of
# going through the retreival process again (up to maxsize cached pages)
i, img_base = self._get_img_base(ql.loader, address & ~(0x1000 - 1))
except StopIteration:
pass
else:
self.basic_blocks[address] = bb_entry(address - img_base, size, i)
def activate(self) -> None:
self.bb_callback = self.ql.hook_block(self.block_callback)
def deactivate(self) -> None:
if self.bb_callback:
self.ql.hook_del(self.bb_callback)
def dump_coverage(self, coverage_file: str) -> None:
def __write_line(bio: BinaryIO, line: str) -> None:
bio.write(f'{line}\n'.encode())
def dump_coverage(self, coverage_file):
with open(coverage_file, "wb") as cov:
cov.write(f"DRCOV VERSION: {self.drcov_version}\n".encode())
cov.write(f"DRCOV FLAVOR: {self.drcov_flavor}\n".encode())
cov.write(f"Module Table: version {self.drcov_version}, count {len(self.ql.loader.images)}\n".encode())
cov.write("Columns: id, base, end, entry, checksum, timestamp, path\n".encode())
__write_line(cov, f"DRCOV VERSION: {self.drcov_version}")
__write_line(cov, f"DRCOV FLAVOR: {self.drcov_flavor}")
__write_line(cov, f"Module Table: version {self.drcov_version}, count {len(self.ql.loader.images)}")
__write_line(cov, "Columns: id, base, end, entry, checksum, timestamp, path")
for mod_id, mod in enumerate(self. ql.loader.images):
cov.write(f"{mod_id}, {mod.base}, {mod.end}, 0, 0, 0, {mod.path}\n".encode())
cov.write(f"BB Table: {len(self.basic_blocks)} bbs\n".encode())
for bb in self.basic_blocks:
__write_line(cov, f"{mod_id}, {mod.base}, {mod.end}, 0, 0, 0, {mod.path}")
__write_line(cov, f"BB Table: {len(self.basic_blocks)} bbs")
for bb in self.basic_blocks.values():
cov.write(bytes(bb))
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
#
#
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
#
@@ -17,10 +17,6 @@ class QlDrCoverageExact(QlDrCoverage):
FORMAT_NAME = "drcov_exact"
def __init__(self, ql):
super().__init__(ql)
def activate(self):
def activate(self) -> None:
# We treat every instruction as a block on its own.
self.bb_callback = self.ql.hook_code(self.block_callback, user_data=self)
self.bb_callback = self.ql.hook_code(self.block_callback)
+35 -23
View File
@@ -1,19 +1,30 @@
#!/usr/bin/env python3
#
#
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
#
from collections import namedtuple
from os.path import basename
from __future__ import annotations
import os
from typing import Any, TYPE_CHECKING, List, NamedTuple
from .base import QlBaseCoverage
if TYPE_CHECKING:
from qiling import Qiling
# Adapted from https://github.com/nccgroup/Cartographer/blob/main/EZCOV.md#coverage-data
class bb_entry(namedtuple('bb_entry', 'offset size mod_id')):
def csvline(self):
offset = '0x{:08x}'.format(self.offset)
class bb_entry(NamedTuple):
offset: int
size: int
mod_id: Any
def as_csv(self) -> str:
offset = f'{self.offset:#010x}'
mod_id = f"[ {self.mod_id if self.mod_id is not None else ''} ]"
return f"{offset},{self.size},{mod_id}\n"
class QlEzCoverage(QlBaseCoverage):
@@ -27,29 +38,30 @@ class QlEzCoverage(QlBaseCoverage):
FORMAT_NAME = "ezcov"
def __init__(self, ql):
def __init__(self, ql: Qiling):
super().__init__(ql)
self.ezcov_version = 1
self.ezcov_flavor = 'ezcov'
self.basic_blocks = []
self.bb_callback = None
self.ezcov_flavor = 'ezcov'
self.basic_blocks: List[bb_entry] = []
self.bb_callback = None
@staticmethod
def block_callback(ql, address, size, self):
mod = ql.loader.find_containing_image(address)
if mod is not None:
ent = bb_entry(address - mod.base, size, basename(mod.path))
self.basic_blocks.append(ent)
def block_callback(self, ql: Qiling, address: int, size: int):
img = ql.loader.find_containing_image(address)
def activate(self):
self.bb_callback = self.ql.hook_block(self.block_callback, user_data=self)
if img is not None:
self.basic_blocks.append(bb_entry(address - img.base, size, os.path.basename(img.path)))
def deactivate(self):
self.ql.hook_del(self.bb_callback)
def activate(self) -> None:
self.bb_callback = self.ql.hook_block(self.block_callback)
def dump_coverage(self, coverage_file):
def deactivate(self) -> None:
if self.bb_callback:
self.ql.hook_del(self.bb_callback)
def dump_coverage(self, coverage_file: str) -> None:
with open(coverage_file, "w") as cov:
cov.write(f"EZCOV VERSION: {self.ezcov_version}\n")
cov.write("# Qiling EZCOV exporter tool\n")
for bb in self.basic_blocks:
cov.write(bb.csvline())
cov.writelines(bb.as_csv() for bb in self.basic_blocks)
+163 -104
View File
@@ -5,7 +5,6 @@ UseAsScript = True
import sys
import collections
import time
import struct
import re
import logging
@@ -37,9 +36,6 @@ import ida_auto
import ida_netnode
import ida_hexrays
import ida_range
# PyQt
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtWidgets import (QPushButton, QHBoxLayout)
# Qiling
from qiling import Qiling
@@ -55,7 +51,6 @@ from qiling import __version__ as QLVERSION
from qiling.os.filestruct import ql_file
from keystone import *
QilingHomePage = 'https://www.qiling.io'
QilingStableVersionURL = 'https://raw.githubusercontent.com/qilingframework/qiling/master/qiling/__version__.py'
logging.basicConfig(level=logging.INFO, format='[%(levelname)s][%(module)s:%(lineno)d] %(message)s')
@@ -69,7 +64,27 @@ class Colors(Enum):
Gray = 0xd9d9d9
Beige = 0xCCF2FF
class IDA:
def _load_qt_bindings():
if IDA_SDK_VERSION >= 900:
try:
from PySide6 import QtCore, QtWidgets
from PySide6.QtWidgets import (QPushButton, QHBoxLayout)
logging.info("Using PySide6 for Qt bindings (IDA >= 9).")
return QtCore, QtWidgets, QPushButton, QHBoxLayout
except Exception as e:
logging.warning("Failed to import PySide6: %s. Trying PyQt5 fallback.", e)
try:
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtWidgets import (QPushButton, QHBoxLayout)
logging.info("Using PyQt5 for Qt bindings (IDA < 9 or fallback).")
return QtCore, QtWidgets, QPushButton, QHBoxLayout
except Exception as e:
logging.error("Failed to import PyQt bindings: %s", e)
raise
QtCore, QtWidgets, QPushButton, QHBoxLayout = _load_qt_bindings()
class IDABase:
def __init__(self):
pass
@@ -79,15 +94,15 @@ class IDA:
@staticmethod
def get_function_start(addr):
return IDA.get_function(addr).start_ea
return IDABase.get_function(addr).start_ea
@staticmethod
def get_function_end(addr):
return IDA.get_function(addr).end_ea
return IDABase.get_function(addr).end_ea
@staticmethod
def get_function_framesize(addr):
return IDA.get_function(addr).frsize
return IDABase.get_function(addr).frsize
@staticmethod
def get_function_name(addr):
@@ -95,7 +110,7 @@ class IDA:
@staticmethod
def get_functions():
return [IDA.get_function(func) for func in idautils.Functions()]
return [IDABase.get_function(func) for func in idautils.Functions()]
@staticmethod
def set_color(addr, what, color):
@@ -104,7 +119,7 @@ class IDA:
@staticmethod
def color_block(bb, color):
for i in range(bb.start_ea, bb.end_ea):
IDA.set_color(i, idc.CIC_ITEM, color)
IDABase.set_color(i, idc.CIC_ITEM, color)
# note:
# corresponds to IDA graph view
@@ -113,8 +128,8 @@ class IDA:
# arg can be a function or a (start, end) tuple or an address in the function
@staticmethod
def get_flowchart(arg):
if type(arg) is int:
func = IDA.get_function(arg)
if isinstance(arg, int):
func = IDABase.get_function(arg)
if func is None:
return None
return ida_gdl.FlowChart(func)
@@ -122,7 +137,9 @@ class IDA:
@staticmethod
def get_block(addr):
flowchart = IDA.get_flowchart(addr)
flowchart = IDABase.get_flowchart(addr)
if flowchart is None:
return None
for bb in flowchart:
if bb.start_ea <= addr and addr < bb.end_ea:
return bb
@@ -143,10 +160,10 @@ class IDA:
@staticmethod
def get_starting_block(addr):
flowchart = IDA.get_flowchart(addr)
flowchart = IDABase.get_flowchart(addr)
if flowchart is None:
return None
func = IDA.get_function(addr)
func = IDABase.get_function(addr)
for bb in flowchart:
if bb.start_ea == func.start_ea:
return bb
@@ -154,8 +171,10 @@ class IDA:
@staticmethod
def get_terminating_blocks(addr):
flowchart = IDA.get_flowchart(addr)
return [bb for bb in flowchart if IDA.block_is_terminating(bb)]
flowchart = IDABase.get_flowchart(addr)
if flowchart is None:
return []
return [bb for bb in flowchart if IDABase.block_is_terminating(bb)]
@staticmethod
def get_prev_head(addr, minea=0):
@@ -180,46 +199,45 @@ class IDA:
@staticmethod
def __addr_in_seg(addr):
segs = IDA.get_segments()
segs = IDABase.get_segments()
for seg in segs:
if addr < seg.end_ea and addr >= seg.start_ea:
return seg
return None
# note: accept name and address in the segment
@staticmethod
def get_segment(arg):
if type(arg) is int:
return IDA.__addr_in_seg(arg)
else: # str
return IDA.get_segment_by_name(arg)
if isinstance(arg, int):
return IDABase.__addr_in_seg(arg)
else:
return IDABase.get_segment_by_name(arg)
@staticmethod
def get_segment_start(arg):
seg = IDA.get_segment(arg)
seg = IDABase.get_segment(arg)
if seg is not None:
return seg.start_ea
return None
@staticmethod
def get_segment_end(arg):
seg = IDA.get_segment(arg)
seg = IDABase.get_segment(arg)
if seg is not None:
return seg.end_ea
return None
@staticmethod
def get_segment_perm(arg):
seg = IDA.get_segment(arg)
seg = IDABase.get_segment(arg)
if seg is not None:
return seg.perm # RWX e.g. 0b101 = R + X
return seg.perm
return None
@staticmethod
def get_segment_type(arg):
seg = IDA.get_segment(arg)
seg = IDABase.get_segment(arg)
if seg is not None:
return seg.type # 0x1 SEG_DATA 0x2 SEG_CODE See doc for details
return seg.type
return None
@staticmethod
@@ -229,12 +247,10 @@ class IDA:
return None
return r
# immidiate value
@staticmethod
def get_operand(addr, n):
return (idc.get_operand_type(addr, n), idc.get_operand_value(addr, n))
# eax, ecx, etc
@staticmethod
def print_operand(addr, n):
return idc.print_operand(addr, n)
@@ -248,7 +264,7 @@ class IDA:
p = begin
cnt = 0
while p < end:
sz = IDA.get_instruction_size(p)
sz = IDABase.get_instruction_size(p)
cnt += 1
p += sz
return cnt
@@ -293,96 +309,34 @@ class IDA:
def get_input_file_path():
return ida_nalt.get_input_file_path()
@staticmethod
def get_info_structure():
return ida_idaapi.get_inf_structure()
@staticmethod
def get_main_address():
return IDA.get_info_structure().main
@staticmethod
def get_max_address():
return IDA.get_info_structure().max_ea
@staticmethod
def get_min_address():
return IDA.get_info_structure().min_ea
@staticmethod
def is_big_endian():
return IDA.get_info_structure().is_be()
@staticmethod
def is_little_endian():
return not IDA.is_big_endian()
@staticmethod
def get_filetype():
info = IDA.get_info_structure()
ftype = info.filetype
if ftype == ida_ida.f_MACHO:
return "macho"
elif ftype == ida_ida.f_PE or ftype == ida_ida.f_EXE or ftype == ida_ida.f_EXE_old: # is this correct?
return "pe"
elif ftype == ida_ida.f_ELF:
return "elf"
else:
return None
@staticmethod
def get_ql_arch_string():
info = IDA.get_info_structure()
proc = info.procname.lower()
result = None
if proc == "metapc":
result = "x86"
if info.is_64bit():
result = "x8664"
elif "mips" in proc:
result = "mips"
elif "arm" in proc:
result = "arm32"
if info.is_64bit():
result = "arm64"
# That's all we support :(
return result
@staticmethod
def get_current_address():
return ida_kernwin.get_screen_ea()
# return (?, start, end)
@staticmethod
def get_last_selection():
return ida_kernwin.read_range_selection(None)
# Use with skipcalls
# note that the address is the end of target instruction
# e.g.:
# 0x1 push eax
# 0x4 mov eax, 0
# call get_frame_sp_delta(0x4) and get -4.
@staticmethod
def get_frame_sp_delta(addr):
return ida_frame.get_sp_delta(IDA.get_function(addr), addr)
return ida_frame.get_sp_delta(IDABase.get_function(addr), addr)
@staticmethod
def patch_bytes(addr, bs):
return ida_bytes.patch_bytes(addr, bs)
@staticmethod
def fill_bytes(start, end, bs = b'\x90'):
def fill_bytes(start, end, bs=b'\x90'):
return ida_bytes.patch_bytes(start, bs*(end-start))
@staticmethod
def nop_selection():
_, start, end = IDA.get_last_selection()
return IDA.fill_bytes(start, end)
_, start, end = IDABase.get_last_selection()
return IDABase.fill_bytes(start, end)
@staticmethod
def fill_block(bb, bs=b'\x90'):
return IDA.fill_bytes(bb.start_ea, bb.end_ea, bs)
return IDABase.fill_bytes(bb.start_ea, bb.end_ea, bs)
@staticmethod
def assemble(ea, cs, ip, use32, line):
@@ -394,7 +348,7 @@ class IDA:
@staticmethod
def create_bytes_array(start, end):
return IDA.create_data(start, ida_bytes.byte_flag(), end-start)
return IDABase.create_data(start, ida_bytes.byte_flag(), end-start)
@staticmethod
def create_byte(ea, length, force=False):
@@ -418,13 +372,12 @@ class IDA:
@staticmethod
def get_item(ea):
return (IDA.get_item_head(ea), IDA.get_item_end(ea))
return (IDABase.get_item_head(ea), IDABase.get_item_end(ea))
@staticmethod
def is_colored_item(ea):
return ida_nalt.is_colored_item(ea)
# NOTE: The [start, end) range should include all control flows except long calls.
@staticmethod
def get_micro_code_mba(start, end, decomp_flags=ida_hexrays.DECOMP_WARNINGS, maturity=7):
mbrgs = ida_hexrays.mba_ranges_t()
@@ -444,6 +397,112 @@ class IDA:
cur = cur.next
return
class IDA7(IDABase):
@staticmethod
def get_info_structure():
return ida_idaapi.get_inf_structure()
@staticmethod
def get_main_address():
return IDA7.get_info_structure().main
@staticmethod
def get_max_address():
return IDA7.get_info_structure().max_ea
@staticmethod
def get_min_address():
return IDA7.get_info_structure().min_ea
@staticmethod
def is_big_endian():
return IDA7.get_info_structure().is_be
@staticmethod
def is_little_endian():
return not IDA7.is_big_endian()
@staticmethod
def get_filetype():
ftype = IDA7.get_info_structure().filetype
if ftype in (ida_ida.f_PE, ida_ida.f_EXE, ida_ida.f_EXE_old):
return "pe"
elif ftype == ida_ida.f_MACHO:
return "macho"
elif ftype == ida_ida.f_ELF:
return "elf"
return None
@staticmethod
def get_ql_arch_string():
proc = IDA7.get_info_structure().procname.lower()
is_64_bit = IDA7.get_info_structure().is_64bit()
if proc == "metapc":
return "x8664" if is_64_bit else "x86"
if "mips" in proc:
return "mips"
if "arm" in proc:
return "arm64" if is_64_bit else "arm32"
return None
class IDA9(IDABase):
@staticmethod
def get_info_structure():
return ida_idaapi.get_inf_structure()
@staticmethod
def get_main_address():
return ida_ida.inf_get_main()
@staticmethod
def get_max_address():
return ida_ida.inf_get_max_ea()
@staticmethod
def get_min_address():
return ida_ida.inf_get_min_ea()
@staticmethod
def is_big_endian():
return ida_ida.inf_is_be()
@staticmethod
def is_little_endian():
return not ida_ida.inf_is_be()
@staticmethod
def get_filetype():
ftype = ida_ida.inf_get_filetype()
if ftype in (ida_ida.f_PE, ida_ida.f_EXE, ida_ida.f_EXE_old):
return "pe"
elif ftype == ida_ida.f_MACHO:
return "macho"
elif ftype == ida_ida.f_ELF:
return "elf"
return None
@staticmethod
def get_ql_arch_string():
proc = ida_ida.inf_get_procname().lower()
is_64_bit = ida_ida.inf_is_64bit()
if proc == "metapc":
return "x8664" if is_64_bit else "x86"
if "mips" in proc:
return "mips"
if "arm" in proc:
return "arm64" if is_64_bit else "arm32"
return None
def get_ida_instance():
if IDA_SDK_VERSION >= 900:
logging.info("Using IDA9 compatibility layer")
return IDA9()
else:
logging.info("Using IDA7 compatibility layer")
return IDA7()
IDA = get_ida_instance()
### View Class
class QlEmuRegView(simplecustviewer_t):
@@ -1006,7 +1065,7 @@ class QlEmuPlugin(plugin_t, UI_Hooks):
def init(self):
# init data
logging.info('---------------------------------------------------------------------------------------')
logging.info('Qiling Emulator Plugin For IDA, by Qiling Team. Version {0}, 2020'.format(QLVERSION))
logging.info('Qiling Emulator Plugin For IDA, by Qiling Team. Version {0}, 2025'.format(QLVERSION))
logging.info('Based on Qiling v{0}'.format(QLVERSION))
logging.info('Find more information about Qiling at https://qiling.io')
logging.info('---------------------------------------------------------------------------------------')
+7 -11
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
#
#
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
# This code structure is copied and modified from the coverage extension
@@ -12,24 +12,20 @@ class QlBaseTrace(ABC):
To add support for a new coverage format, just derive from this class and implement
all the methods marked with the @abstractmethod decorator.
"""
FORMAT_NAME: str
def __init__(self):
super().__init__()
@property
@staticmethod
@abstractmethod
def FORMAT_NAME():
raise NotImplementedError
@abstractmethod
def activate(self):
def activate(self) -> None:
pass
@abstractmethod
def deactivate(self):
def deactivate(self) -> None:
pass
@abstractmethod
def dump_trace(self, trace_file):
def dump_trace(self, trace_file: str) -> None:
pass
+108 -59
View File
@@ -1,26 +1,70 @@
#!/usr/bin/env python3
#
#
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
#
import ctypes
from functools import cached_property
from typing import Any, Dict, List, Optional, Tuple
from qiling.core import Qiling
from qiling import Qiling
from qiling.hw.peripheral import QlPeripheral
from qiling.utils import ql_get_module_function
from qiling.exception import QlErrorModuleFunctionNotFound
# should adhere to the QlMmioHandler interface, but not extend it directly to
# avoid potential pickling issues
class QlPripheralHandler:
def __init__(self, hwman: "QlHwManager", base: int, size: int, label: str) -> None:
self._hwman = hwman
self._base = base
self._size = size
self._label = label
def __getstate__(self):
state = self.__dict__.copy()
del state['_hwman'] # remove non-pickleable reference
return state
@cached_property
def _mmio(self) -> bytearray:
"""Get memory buffer used to back non-mapped hardware mmio regions.
"""
return bytearray(self._size)
def read(self, ql: Qiling, offset: int, size: int) -> int:
address = self._base + offset
hardware = self._hwman.find(address)
if hardware:
return hardware.read(address - hardware.base, size)
else:
ql.log.debug('[%s] read non-mapped hardware [%#010x]', self._label, address)
return int.from_bytes(self._mmio[offset:offset + size], byteorder='little')
def write(self, ql: Qiling, offset: int, size: int, value: int) -> None:
address = self._base + offset
hardware = self._hwman.find(address)
if hardware:
hardware.write(address - hardware.base, size, value)
else:
ql.log.debug('[%s] write non-mapped hardware [%#010x] = %#010x', self._label, address, value)
self._mmio[offset:offset + size] = value.to_bytes(size, 'little')
class QlHwManager:
def __init__(self, ql: Qiling):
self.ql = ql
self.entity = {}
self.region = {}
self.entity: Dict[str, QlPeripheral] = {}
self.region: Dict[str, List[Tuple[int, int]]] = {}
self.stepable = {}
def create(self, label: str, struct: str=None, base: int=None, kwargs: dict={}) -> "QlPeripheral":
def create(self, label: str, struct: Optional[str] = None, base: Optional[int] = None, kwargs: Optional[Dict[str, Any]] = None) -> QlPeripheral:
""" Create the peripheral accroding the label and envs.
struct: Structure of the peripheral. Use defualt ql structure if not provide.
@@ -30,39 +74,45 @@ class QlHwManager:
if struct is None:
struct, base, kwargs = self.load_env(label.upper())
try:
entity = ql_get_module_function('qiling.hw', struct)(self.ql, label, **kwargs)
self.entity[label] = entity
if hasattr(entity, 'step'):
self.stepable[label] = entity
if kwargs is None:
kwargs = {}
try:
entity = ql_get_module_function('qiling.hw', struct)(self.ql, label, **kwargs)
except QlErrorModuleFunctionNotFound:
self.ql.log.warning(f'could not create {struct}({label}): implementation not found')
else:
assert isinstance(entity, QlPeripheral)
assert isinstance(base, int)
self.entity[label] = entity
self.region[label] = [(lbound + base, rbound + base) for (lbound, rbound) in entity.region]
return entity
except QlErrorModuleFunctionNotFound:
self.ql.log.debug(f'The {struct}({label}) has not been implemented')
def delete(self, label: str):
# FIXME: what should we do if struct is not implemented? is it OK to return None , or we fail?
def delete(self, label: str) -> None:
""" Remove the peripheral
"""
if label in self.entity:
self.entity.pop(label)
self.region.pop(label)
if label in self.stepable:
self.stepable.pop(label)
def load_env(self, label: str):
if label in self.entity:
del self.entity[label]
if label in self.region:
del self.region[label]
def load_env(self, label: str) -> Tuple[str, int, Dict[str, Any]]:
""" Get peripheral information (structure, base address, initialization list) from env.
Args:
label (str): Peripheral Label
"""
args = self.ql.env[label]
return args['struct'], args['base'], args.get("kwargs", {})
def load_all(self):
@@ -70,48 +120,30 @@ class QlHwManager:
if args['type'] == 'peripheral':
self.create(label.lower(), args['struct'], args['base'], args.get("kwargs", {}))
def find(self, address: int):
# TODO: this is wasteful. device mapping is known at creation time. at least we could cache lru entries
def find(self, address: int) -> Optional[QlPeripheral]:
""" Find the peripheral at `address`
"""
for label in self.entity.keys():
for lbound, rbound in self.region[label]:
if lbound <= address < rbound:
return self.entity[label]
return None
def step(self):
""" Update all peripheral's state
""" Update all peripheral's state
"""
for entity in self.stepable.values():
entity.step()
def setup_mmio(self, begin, size, info=""):
mmio = ctypes.create_string_buffer(size)
for ent in self.entity.values():
if hasattr(ent, 'step'):
ent.step()
def mmio_read_cb(ql, offset, size):
address = begin + offset
hardware = self.find(address)
if hardware:
return hardware.read(address - hardware.base, size)
else:
ql.log.debug('%s Read non-mapped hardware [0x%08x]' % (info, address))
buf = ctypes.create_string_buffer(size)
ctypes.memmove(buf, ctypes.addressof(mmio) + offset, size)
return int.from_bytes(buf.raw, byteorder='little')
def setup_mmio(self, begin: int, size: int, info: str) -> None:
dev = QlPripheralHandler(self, begin, size, info)
def mmio_write_cb(ql, offset, size, value):
address = begin + offset
hardware = self.find(address)
if hardware:
hardware.write(address - hardware.base, size, value)
else:
ql.log.debug('%s Write non-mapped hardware [0x%08x] = 0x%08x' % (info, address, value))
ctypes.memmove(ctypes.addressof(mmio) + offset, (value).to_bytes(size, 'little'), size)
self.ql.mem.map_mmio(begin, size, mmio_read_cb, mmio_write_cb, info=info)
self.ql.mem.map_mmio(begin, size, dev, info)
def show_info(self):
self.ql.log.info(f'{"Start":8s} {"End":8s} {"Label":8s} {"Class"}')
@@ -131,8 +163,25 @@ class QlHwManager:
return self.entity.get(key)
def save(self):
return {label : entity.save() for label, entity in self.entity.items()}
return {
'entity': {label: entity.save() for label, entity in self.entity.items()},
'region': self.region
}
def restore(self, saved_state):
for label, data in saved_state.items():
entity = saved_state['entity']
assert isinstance(entity, dict)
region = saved_state['region']
assert isinstance(region, dict)
for label, data in entity.items():
self.entity[label].restore(data)
self.region = region
# a dirty hack to rehydrate non-pickleable hwman
# a proper fix would require a deeper refactoring to how peripherals are created and managed
for *_, ph in self.ql.mem.map_info:
if isinstance(ph, QlPripheralHandler):
setattr(ph, '_hwman', self)
+13 -10
View File
@@ -4,8 +4,8 @@
#
from qiling import Qiling
from qiling.loader.loader import QlLoader
from qiling.os.memory import QlMemoryHeap
from qiling.loader.loader import QlLoader, Image
class QlLoaderBLOB(QlLoader):
def __init__(self, ql: Qiling):
@@ -14,15 +14,18 @@ class QlLoaderBLOB(QlLoader):
self.load_address = 0
def run(self):
self.load_address = self.ql.os.entry_point # for consistency
self.load_address = self.ql.os.load_address
self.entry_point = self.ql.os.entry_point
self.ql.mem.map(self.ql.os.entry_point, self.ql.os.code_ram_size, info="[code]")
self.ql.mem.write(self.ql.os.entry_point, self.ql.code)
code_begins = self.load_address
code_size = self.ql.os.code_ram_size
code_ends = code_begins + code_size
heap_address = self.ql.os.entry_point + self.ql.os.code_ram_size
heap_size = int(self.ql.os.profile.get("CODE", "heap_size"), 16)
self.ql.os.heap = QlMemoryHeap(self.ql, heap_address, heap_address + heap_size)
self.ql.mem.map(code_begins, code_size, info="[code]")
self.ql.mem.write(code_begins, self.ql.code)
self.ql.arch.regs.arch_sp = heap_address - 0x1000
# allow image-related functionalities
self.images.append(Image(code_begins, code_ends, 'blob_code'))
return
# FIXME: stack pointer should be a configurable profile setting
self.ql.arch.regs.arch_sp = code_ends - 0x1000
+14 -2
View File
@@ -7,7 +7,7 @@ import io
import os
from enum import IntEnum
from typing import AnyStr, Optional, Sequence, Mapping, Tuple
from typing import Any, AnyStr, Optional, Sequence, Mapping, Tuple
from elftools.common.utils import preserve_stream_pos
from elftools.elf.constants import P_FLAGS, SH_FLAGS
@@ -330,7 +330,7 @@ class QlLoaderELF(QlLoader):
hwcap_values = {
(QL_ARCH.ARM, QL_ENDIAN.EL, 32): 0x001fb8d7,
(QL_ARCH.ARM, QL_ENDIAN.EB, 32): 0xd7b81f00,
(QL_ARCH.ARM64, QL_ENDIAN.EL, 64): 0x078bfbfd
(QL_ARCH.ARM64, QL_ENDIAN.EL, 64): 0x078bfafd
}
# determine hwcap value by arch properties; if not found default to 0
@@ -701,3 +701,15 @@ class QlLoaderELF(QlLoader):
elfdata_mapping.extend(sec.data())
return bytes(elfdata_mapping)
def save(self) -> Mapping[str, Any]:
saved = super().save()
saved['brk_address'] = self.brk_address
return saved
def restore(self, saved_state: Mapping[str, Any]):
self.brk_address = saved_state['brk_address']
super().restore(saved_state)
+33 -29
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
#
#
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
# Built on top of Unicorn emulator (www.unicorn-engine.org)
# Built on top of Unicorn emulator (www.unicorn-engine.org)
import io
@@ -27,7 +27,7 @@ class IhexParser:
if addr != begin + len(stream):
self.segments.append((begin, stream))
begin, stream = addr, data
else:
stream += data
@@ -36,13 +36,13 @@ class IhexParser:
def parse_line(self, line):
if len(line) < 9:
return
desc = line[7: 9]
size = int(line[1: 3], 16)
size = int(line[1: 3], 16)
addr = bytes.fromhex(line[3: 7])
data = bytes.fromhex(line[9: 9 + size * 2])
data = bytes.fromhex(line[9: 9 + size * 2])
if desc == '00': # Data
offset = int.from_bytes(addr, byteorder='big')
self.mem.append((self.base + offset, data))
@@ -52,20 +52,20 @@ class IhexParser:
elif desc == '04': # Extended Linear Address
self.base = int.from_bytes(data, byteorder='big') * 0x10000
class QlLoaderMCU(QlLoader):
def __init__(self, ql:Qiling):
super().__init__(ql)
super().__init__(ql)
self.entry_point = 0
self.load_address = 0
self.filetype = self.guess_filetype()
if self.filetype == 'elf':
with open(self.ql.path, 'rb') as infile:
self.elf = ELFFile(io.BytesIO(infile.read()))
elif self.filetype == 'bin':
self.map_address = self.argv[1]
@@ -74,8 +74,8 @@ class QlLoaderMCU(QlLoader):
def guess_filetype(self):
if self.ql.path.endswith('.elf'):
return 'elf'
return 'elf'
if self.ql.path.endswith('.bin'):
return 'bin'
@@ -83,7 +83,7 @@ class QlLoaderMCU(QlLoader):
return 'hex'
return 'elf'
def reset(self):
if self.filetype == 'elf':
for segment in self.elf.iter_segments(type='PT_LOAD'):
@@ -99,7 +99,7 @@ class QlLoaderMCU(QlLoader):
for begin, data in self.ihex.segments:
self.ql.mem.write(begin, data)
self.ql.arch.init_context()
self.entry_point = self.ql.arch.regs.read('pc')
@@ -109,30 +109,34 @@ class QlLoaderMCU(QlLoader):
def load_env(self):
for name, args in self.env.items():
memtype = args['type']
if memtype == 'memory':
size = args['size']
base = args['base']
self.ql.mem.map(base, size, info=f'[{name}]')
if memtype == 'remap':
# elif memtype == 'remap':
# size = args['size']
# base = args['base']
# alias = args['alias']
# self.ql.hw.setup_remap(alias, base, size, info=f'[{name}]')
elif memtype == 'mmio':
size = args['size']
base = args['base']
alias = args['alias']
self.ql.hw.setup_remap(alias, base, size, info=f'[{name}]')
self.ql.hw.setup_mmio(base, size, name)
if memtype == 'mmio':
size = args['size']
base = args['base']
self.ql.hw.setup_mmio(base, size, info=f'[{name}]')
if memtype == 'core':
elif memtype == 'core':
self.ql.hw.create(name.lower())
else:
self.ql.log.debug(f'ignoring unknown memory type "{memtype}" for {name}')
def run(self):
self.load_profile()
self.load_env()
## Handle interrupt from instruction execution
self.ql.hook_intr(self.ql.arch.unicorn_exception_handler)
self.reset()
+163 -4
View File
@@ -10,7 +10,8 @@ import pefile
import pickle
import secrets
import ntpath
from typing import TYPE_CHECKING, Any, Dict, MutableMapping, NamedTuple, Optional, Mapping, Sequence, Tuple, Union
from collections import namedtuple
from typing import TYPE_CHECKING, Any, Dict, List, MutableMapping, NamedTuple, Optional, Mapping, Sequence, Tuple, Union
from unicorn import UcError
from unicorn.x86_const import UC_X86_REG_CR4, UC_X86_REG_CR8
@@ -29,6 +30,13 @@ if TYPE_CHECKING:
from logging import Logger
from qiling import Qiling
class ForwardedExport(NamedTuple):
source_dll: str
source_ordinal: str
source_symbol: str
target_dll: str
target_symbol: str
class QlPeCacheEntry(NamedTuple):
ba: int
@@ -79,6 +87,16 @@ class Process:
export_symbols: MutableMapping[int, Dict[str, Any]]
libcache: Optional[QlPeCache]
# maps image base to RVA of its function table
function_table_lookup: Dict[int, int]
# maps image base to its list of function table entries
function_tables: MutableMapping[int, List]
# List of exports which have been forwarded from
# one DLL to another.
forwarded_exports: List[ForwardedExport]
def __init__(self, ql: Qiling):
self.ql = ql
@@ -105,6 +123,108 @@ class Process:
vpath = ntpath.join(dirname, basename)
return self.ql.os.path.virtual_to_host_path(vpath), basename.casefold()
def init_function_tables(self, pe: pefile.PE, image_base: int):
"""Parse function table data for the given PE file.
Only really relevant for non-x86 images.
Args:
pe: the PE image whose function data should be parsed
image_base: the absolute address at which the image was loaded
"""
if self.ql.arch.type is not QL_ARCH.X86:
# Check if the PE file has an exception directory
if hasattr(pe, 'DIRECTORY_ENTRY_EXCEPTION'):
exception_dir = pe.OPTIONAL_HEADER.DATA_DIRECTORY[
pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_EXCEPTION']
]
self.function_table_lookup[image_base] = exception_dir.VirtualAddress
runtime_function_list = list(pe.DIRECTORY_ENTRY_EXCEPTION)
if image_base not in self.function_tables:
self.function_tables[image_base] = []
self.function_tables[image_base].extend(runtime_function_list)
self.ql.log.debug(f'Parsed {len(runtime_function_list)} exception directory entries')
else:
self.ql.log.debug(f'Image has no exception directory; skipping exception data')
def lookup_function_entry(self, base_addr: int, control_pc: int):
"""Look up a RUNTIME_FUNCTION entry and its index in a module's
function table, such that the given program counter falls within
the entry's begin and end range.
Args:
base_addr: The base address of the image whose exception directory to search.
control_pc: The program counter.
Returns:
A tuple (index, runtime_function)
"""
function_table = self.function_tables[base_addr]
# Initiate a search of the function table for a RUNTIME_FUNCTION
# entry such that the provided PC falls within its start and end range.
return next(((i, rtfunc) for i, rtfunc in enumerate(function_table)
if rtfunc.struct.BeginAddress <= control_pc - base_addr < rtfunc.struct.EndAddress),
(None, None))
def resolve_forwarded_exports(self):
while self.forwarded_exports:
forwarded_export = self.forwarded_exports.pop()
source_dll = forwarded_export.source_dll
source_ordinal = forwarded_export.source_ordinal
source_symbol = forwarded_export.source_symbol
target_dll = forwarded_export.target_dll
target_symbol = forwarded_export.target_symbol
if not source_symbol:
# Some DLLs (shlwapi.dll) have a bunch of forwarded
# exports with ordinals but no symbols.
# These are really annoying to deal with, but they are
# used extremely rarely, so we will ignore them.
continue
target_iat = self.import_address_table.get(target_dll)
if not target_iat:
# If IAT was not found, it is probably a virtual library.
continue
# If we have an existing entry in the process IAT for the code
# this entry forwards to, then we will point the symbol there
# rather than the symbol string in the exporter's data section.
forward_ea = target_iat.get(target_symbol)
if not forward_ea:
self.ql.log.warning(f"Forwarding symbol {source_dll}.{source_symbol} to {target_dll}.{target_symbol}: Failed to resolve address")
continue
self.import_address_table[source_dll][source_symbol] = forward_ea
self.import_address_table[source_dll][source_ordinal] = forward_ea
# Register the new address as having the source symbol/ordinal.
# This way, hooks on forward source symbols will function
# correctly.
self.import_symbols[forward_ea] = {
'name' : source_symbol,
'ordinal' : source_ordinal,
'dll' : source_dll.split('.')[0]
}
# TODO: With the above code, hooks on functions which are
# forward targets may not work correctly.
# The most correct way to resolve this would be to add
# support for addresses to be associated with multiple symbols.
self.ql.log.debug(f"Forwarding symbol {source_dll}.{source_symbol} to {target_dll}.{target_symbol}: Resolved symbol to ({forward_ea:#x})")
def load_dll(self, name: str, is_driver: bool = False) -> int:
dll_path, dll_name = self.__get_path_elements(name)
@@ -195,6 +315,9 @@ class Process:
with ShowProgress(self.ql.log, 0.1337):
dll.relocate_image(image_base)
# initialize the function tables only after possible relocation
self.init_function_tables(dll, image_base)
data = bytearray(dll.get_memory_mapped_image())
assert image_size >= len(data)
@@ -203,6 +326,31 @@ class Process:
for sym in dll.DIRECTORY_ENTRY_EXPORT.symbols:
ea = image_base + sym.address
if sym.forwarder:
# Some exports are forwarders, meaning they
# actually refer to code in other libraries.
#
# For example, calls to
# kernel32.InterlockedPushEntrySList
# should be forwarded to
# ntdll.RtlInterlockedPushEntrySList
#
# If we do not properly account for forwarders then
# calls to these symbols will land in the exporter's
# data section and cause a lot of problems.
forward_str = sym.forwarder
if b'.' in forward_str:
target_dll_name, target_symbol_name = forward_str.split(b'.', 1)
target_dll_filename = (target_dll_name.lower() + b'.dll').decode()
# Remember the forwarded export for later.
forwarded_export = ForwardedExport(dll_name, sym.ordinal, sym.name,
target_dll_filename, target_symbol_name)
self.forwarded_exports.append(forwarded_export)
import_symbols[ea] = {
'name' : sym.name,
'ordinal' : sym.ordinal,
@@ -227,6 +375,8 @@ class Process:
self.import_address_table[dll_name] = import_table
self.import_symbols.update(import_symbols)
self.resolve_forwarded_exports()
dll_base = image_base
dll_len = image_size
@@ -281,8 +431,8 @@ class Process:
# the blacklist may be revisited from time to time to see if any of the file
# can be safely unlisted.
blacklist = {
32 : ('gdi32.dll',),
64 : ('gdi32.dll',)
32 : ('gdi32.dll','user32.dll',),
64 : ('gdi32.dll','user32.dll',)
}[self.ql.arch.bits]
if dll_name in blacklist:
@@ -494,6 +644,7 @@ class Process:
# DLLs that seem to contain most of the requested symbols
key_dlls = (
'kernel32.dll',
'ntdll.dll',
'kernelbase.dll',
'ucrtbase.dll'
@@ -674,12 +825,14 @@ class QlLoaderPE(QlLoader, Process):
def run(self):
self.init_dlls = (
'ntdll.dll',
'kernel32.dll',
'kernelbase.dll', # kernel32 forwards some exports to kernelbase
'kernel32.dll', # for efficiency, load kernelbase first
'user32.dll'
)
self.sys_dlls = (
'ntdll.dll',
'kernelbase.dll',
'kernel32.dll',
'mscoree.dll',
'ucrtbase.dll'
@@ -709,6 +862,9 @@ class QlLoaderPE(QlLoader, Process):
self.export_symbols = {}
self.import_address_table = {}
self.ldr_list = []
self.function_tables = {}
self.function_table_lookup = {}
self.forwarded_exports = []
self.pe_image_address = 0
self.pe_image_size = 0
self.dll_size = 0
@@ -841,6 +997,9 @@ class QlLoaderPE(QlLoader, Process):
# set up call frame for DllMain
self.ql.os.fcall.call_native(self.entry_point, args, None)
# Initialize the function tables
super().init_function_tables(pe, image_base)
elif pe is None:
self.ql.mem.map(self.entry_point, self.ql.os.code_ram_size, info="[shellcode]")
+16 -5
View File
@@ -8,6 +8,8 @@ from qiling.cc import QlCC, intel, arm, mips, riscv, ppc
from qiling.const import QL_ARCH, QL_OS
from qiling.os.fcall import QlFunctionCall
from qiling.os.os import QlOs
from qiling.os.memory import QlMemoryHeap
class QlOsBlob(QlOs):
""" QlOsBlob for bare barines.
@@ -21,7 +23,7 @@ class QlOsBlob(QlOs):
type = QL_OS.BLOB
def __init__(self, ql: Qiling):
super(QlOsBlob, self).__init__(ql)
super().__init__(ql)
self.ql = ql
@@ -39,11 +41,20 @@ class QlOsBlob(QlOs):
self.fcall = QlFunctionCall(ql, cc)
def run(self):
if self.ql.entry_point:
# if entry point was set explicitly, override the default one
if self.ql.entry_point is not None:
self.entry_point = self.ql.entry_point
self.exit_point = self.ql.loader.load_address + len(self.ql.code)
if self.ql.exit_point:
self.exit_point = self.ql.exit_point
self.exit_point = self.load_address + len(self.ql.code)
# if exit point was set explicitly, override the default one
if self.ql.exit_point is not None:
self.exit_point = self.ql.exit_point
# if heap info is provided in profile, create heap
heap_base = self.profile.getint('CODE', 'heap_address', fallback=None)
heap_size = self.profile.getint('CODE', 'heap_size', fallback=None)
if heap_base is not None and heap_size is not None:
self.heap = QlMemoryHeap(self.ql, heap_base, heap_base + heap_size)
self.ql.emu_start(self.entry_point, self.exit_point, self.ql.timeout, self.ql.count)
+40 -37
View File
@@ -1,23 +1,33 @@
#!/usr/bin/env python3
#
#
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
#
from typing import AnyStr, Optional, Union
from .mapper import QlFsMappedObject
ReadableBuffer = Union[bytes, bytearray, memoryview]
# Open a file as a Disk
# host_path: The file path on the host machine.
# drive_path: The drive path on the emulated system. e.g. /dev/sda \\.\PHYSICALDRIVE0 0x80
#
#
# Note: CHS and LBA support is very limited since a raw file doesn't contain enough information.
# We simply assume that it is a disk with 1 head, 1 cylinder and (filesize/512) sectors.
#
# See: https://en.wikipedia.org/wiki/Cylinder-head-sector
# https://en.wikipedia.org/wiki/Logical_block_addressing
# http://www.uruk.org/orig-grub/PC_partitioning.txt
class QlDisk(QlFsMappedObject):
def __init__(self, host_path, drive_path, n_heads=1, n_cylinders=1, sector_size=512):
self._host_path = host_path
# 512 bytes/sector
# 63 sectors/track
# 255 heads (tracks/cylinder)
# 1024 cylinders
def __init__(self, host_path: AnyStr, drive_path, n_cylinders: int = 1, n_heads: int = 1, sector_size: int = 512):
self._drive_path = drive_path
self._fp = open(host_path, "rb+")
self._n_heads = n_heads
@@ -25,7 +35,7 @@ class QlDisk(QlFsMappedObject):
self._sector_size = sector_size
self.lseek(0, 2)
self._filesize = self.tell()
self._n_sectors = (self._filesize - 1)// self.sector_size + 1
self._n_sectors = (self._filesize - 1) // self.sector_size + 1
def __del__(self):
if not self.fp.closed:
@@ -51,50 +61,43 @@ class QlDisk(QlFsMappedObject):
def sector_size(self):
return self._sector_size
@property
def host_path(self):
return self._host_path
@property
def drive_path(self):
return self._drive_path
@property
def fp(self):
return self._fp
# Methods from FsMappedObject
def read(self, l):
return self.fp.read(l)
def write(self, bs):
return self.fp.write(bs)
def read(self, size: Optional[int]) -> bytes:
return self.fp.read(size)
def lseek(self, offset, origin):
def write(self, buffer: ReadableBuffer) -> int:
return self.fp.write(buffer)
def lseek(self, offset: int, origin: int) -> int:
return self.fp.seek(offset, origin)
def tell(self):
def tell(self) -> int:
return self.fp.tell()
def close(self):
return self.fp.close()
def close(self) -> None:
self.fp.close()
# Methods for QlDisk
def lba(self, cylinder, head, sector):
def lba(self, cylinder: int, head: int, sector: int) -> int:
return (cylinder * self.n_heads + head) * self._n_sectors + sector - 1
def read_sectors(self, lba, cnt):
def read_sectors(self, lba: int, cnt: int) -> bytes:
self.lseek(self.sector_size * lba, 0)
return self.read(self.sector_size*cnt)
def read_chs(self, cylinder, head, sector, cnt):
return self.read(self.sector_size * cnt)
def read_chs(self, cylinder: int, head: int, sector: int, cnt: int) -> bytes:
return self.read_sectors(self.lba(cylinder, head, sector), cnt)
def write_sectors(self, lba, cnt, buffer):
if len(buffer) > self.sector_size * cnt:
buffer = buffer[:self.sector_size*cnt]
def write_sectors(self, lba: int, cnt: int, buffer: ReadableBuffer) -> int:
buffer = memoryview(buffer)
self.lseek(self.sector_size * lba, 0)
return self.write(buffer)
def write_chs(self, cylinder, head, sector, cnt, buffer):
return self.write_sectors(self.lba(cylinder, head, sector), cnt, buffer)
return self.write(buffer[:self.sector_size * cnt])
def write_chs(self, cylinder: int, head: int, sector: int, cnt: int, buffer: ReadableBuffer):
return self.write_sectors(self.lba(cylinder, head, sector), cnt, buffer)
+39 -5
View File
@@ -9,11 +9,6 @@ from qiling import Qiling
from .. import utils
# exit
def __leaf_4c(ql: Qiling):
ql.log.info("Program terminated gracefully")
ql.emu_stop()
# write a character to screen
def __leaf_02(ql: Qiling):
ch = ql.arch.regs.dl
@@ -131,6 +126,45 @@ def __leaf_43(ql: Qiling):
ql.arch.regs.cx = 0xffff
ql.os.clear_cf()
def __leaf_48(ql: Qiling):
"""Allocate memory.
"""
size = ql.arch.regs.bx * 0x10
# announce it but do not do anything really
ql.log.debug(f'allocating memory block at {addr:#06x} to {size:#x} bytes')
# success
ql.os.clear_cf()
def __leaf_49(ql: Qiling):
"""Deallocate memory.
"""
...
def __leaf_4a(ql: Qiling):
"""Modify memory allocation.
"""
addr = ql.arch.regs.es
size = ql.arch.regs.bx * 0x10
# announce it but do not do anything really
ql.log.debug(f'resizing memory block at {addr:#06x} to {size:#x} bytes')
# success
ql.os.clear_cf()
def __leaf_4c(ql: Qiling):
ql.log.info("Program terminated gracefully")
ql.emu_stop()
def handler(ql: Qiling):
ah = ql.arch.regs.ah
+55 -23
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
#
#
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
#
@@ -53,31 +53,63 @@ def hook_mcount(ql, address, params):
return 0
@linux_kernel_api(params={
"Ptr": POINTER
})
def hook___x86_indirect_thunk_rax(ql, address, params):
return 0
def __x86_indirect_thunk(ql: Qiling, dest: int):
ql.log.debug('retpoline to %#010x', dest)
ql.arch.regs.arch_pc = dest
# using passthru as a hack to avoid syscall handler overwrite instruction pointer
@linux_kernel_api(passthru=True)
def hook___x86_indirect_thunk_rax(ql: Qiling, address: int, params):
__x86_indirect_thunk(ql, ql.arch.regs.rax)
@linux_kernel_api(params={
"Ptr": POINTER
})
def hook__copy_to_user(ql, address, params):
return 0
@linux_kernel_api(params={
"Ptr": POINTER
})
def hook__copy_from_user(ql, address, params):
return 0
@linux_kernel_api(params={
"Ptr": POINTER
})
@linux_kernel_api(passthru=True)
def hook___x86_indirect_thunk_r14(ql, address, params):
__x86_indirect_thunk(ql, ql.arch.regs.r14)
@linux_kernel_api(params={
"ubuf": POINTER,
"kbuf": POINTER,
"count": SIZE_T
})
def hook__copy_to_user(ql: Qiling, address: int, params) -> int:
ubuf = params['ubuf']
kbuf = params['kbuf']
count = params['count']
# if user-mode buffer is not available, fail
# TODO: also fail if destination is not writeable
if not ql.mem.is_mapped(ubuf, count):
return count
data = ql.mem.read(kbuf, count)
ql.mem.write(ubuf, data)
return 0
@linux_kernel_api(params={
"kbuf": POINTER,
"ubuf": POINTER,
"count": SIZE_T
})
def hook__copy_from_user(ql: Qiling, address: int, params) -> int:
ubuf = params['ubuf']
kbuf = params['kbuf']
count = params['count']
# if user-mode buffer is not available, fail
# TODO: also fail if source is not readable
if not ql.mem.is_mapped(ubuf, count):
return count
data = ql.mem.read(ubuf, count)
ql.mem.write(kbuf, data)
return 0
+60
View File
@@ -2223,6 +2223,7 @@ riscv32_syscall_table = {
432: "fsmount",
433: "fspick",
434: "pidfd_open",
435: "clone3",
436: "close_range",
437: "openat2",
438: "pidfd_getfd",
@@ -2234,7 +2235,22 @@ riscv32_syscall_table = {
444: "landlock_create_ruleset",
445: "landlock_add_rule",
446: "landlock_restrict_self",
447: "memfd_secret",
448: "process_mrelease",
449: "futex_waitv",
450: "set_mempolicy_home_node",
451: "cachestat",
452: "fchmodat2",
453: "map_shadow_stack",
454: "futex_wake",
455: "futex_wait",
456: "futex_requeue",
457: "statmount",
458: "listmount",
459: "lsm_get_self_attr",
460: "lsm_set_self_attr",
461: "lsm_list_modules",
462: "mseal",
1024: "open",
1025: "link",
1026: "unlink",
@@ -2549,7 +2565,22 @@ riscv64_syscall_table = {
444: "landlock_create_ruleset",
445: "landlock_add_rule",
446: "landlock_restrict_self",
447: "memfd_secret",
448: "process_mrelease",
449: "futex_waitv",
450: "set_mempolicy_home_node",
451: "cachestat",
452: "fchmodat2",
453: "map_shadow_stack",
454: "futex_wake",
455: "futex_wait",
456: "futex_requeue",
457: "statmount",
458: "listmount",
459: "lsm_get_self_attr",
460: "lsm_set_self_attr",
461: "lsm_list_modules",
462: "mseal",
}
ppc_syscall_table = {
@@ -2968,4 +2999,33 @@ ppc_syscall_table = {
431: "fsconfig",
432: "fsmount",
433: "fspick",
434: "pidfd_open",
435: "clone3",
436: "close_range",
437: "openat2",
438: "pidfd_getfd",
439: "faccessat2",
440: "process_madvise",
441: "epoll_pwait2",
442: "mount_setattr",
443: "quotactl_fd",
444: "landlock_create_ruleset",
445: "landlock_add_rule",
446: "landlock_restrict_self",
447: "memfd_secret",
448: "process_mrelease",
449: "futex_waitv",
450: "set_mempolicy_home_node",
451: "cachestat",
452: "fchmodat2",
453: "map_shadow_stack",
454: "futex_wake",
455: "futex_wait",
456: "futex_requeue",
457: "statmount",
458: "listmount",
459: "lsm_get_self_attr",
460: "lsm_set_self_attr",
461: "lsm_list_modules",
462: "mseal",
}
+6 -2
View File
@@ -27,9 +27,13 @@ class MCUTask(UnicornTask):
def on_interrupted(self, ucerr: int):
self._begin = self.pc
# And don't restore anything.
# Real silicon would vector to HardFault here; we don't model that, so log and
# request a clean stop instead of raising — otherwise fast-mode timeouts surface
# firmware faults as test errors.
if ucerr != UC_ERR_OK:
raise UcError(ucerr)
self.ql.log.warning(f'fast mode halted: {UcError(ucerr)} at PC=0x{self.pc:08x}')
self._stop_request = True
return
self.ql.hw.step()
+100 -80
View File
@@ -6,15 +6,32 @@
import bisect
import os
import re
from typing import Any, Callable, Iterator, List, Mapping, Optional, Pattern, Sequence, Tuple, Union
from typing import Any, Callable, Dict, Iterator, List, Mapping, Optional, Pattern, Protocol, Sequence, Tuple, Union
from unicorn import UC_PROT_NONE, UC_PROT_READ, UC_PROT_WRITE, UC_PROT_EXEC, UC_PROT_ALL
from qiling import Qiling
from qiling.exception import *
# tuple: range start, range end, permissions mask, range label, is mmio?
MapInfoEntry = Tuple[int, int, int, str, bool]
class QlMmioHandler(Protocol):
"""A simple MMIO handler boilerplate that can be used to implement memory mapped devices.
This should be extended to implement mapped devices state machines. Note that the read and write
methods are optional, where their existance indicates whether the device supports the corresponding
operation. That is, an unimplemented method means the corresponding operation will be silently
dropped.
"""
def read(self, ql: Qiling, offset: int, size: int) -> int:
...
def write(self, ql: Qiling, offset: int, size: int, value: int) -> None:
...
# tuple: range start, range end, permissions mask, range label, mmio hander object (if mmio range)
MapInfoEntry = Tuple[int, int, int, str, Optional[QlMmioHandler]]
MmioReadCallback = Callable[[Qiling, int, int], int]
MmioWriteCallback = Callable[[Qiling, int, int, int], None]
@@ -29,7 +46,6 @@ class QlMemoryManager:
def __init__(self, ql: Qiling, pagesize: int = 0x1000):
self.ql = ql
self.map_info: List[MapInfoEntry] = []
self.mmio_cbs = {}
bit_stuff = {
64: (1 << 64) - 1,
@@ -48,6 +64,31 @@ class QlMemoryManager:
# make sure pagesize is a power of 2
assert self.pagesize & (self.pagesize - 1) == 0, 'pagesize has to be a power of 2'
self._packers = {
(1, True): ql.pack8s,
(2, True): ql.pack16s,
(4, True): ql.pack32s,
(8, True): ql.pack64s,
(1, False): ql.pack8,
(2, False): ql.pack16,
(4, False): ql.pack32,
(8, False): ql.pack64
}
self._unpackers = {
(1, True): ql.unpack8s,
(2, True): ql.unpack16s,
(4, True): ql.unpack32s,
(8, True): ql.unpack64s,
(1, False): ql.unpack8,
(2, False): ql.unpack16,
(4, False): ql.unpack32,
(8, False): ql.unpack64
}
def __read_string(self, addr: int) -> str:
ret = bytearray()
c = self.read(addr, 1)
@@ -57,7 +98,7 @@ class QlMemoryManager:
addr += 1
c = self.read(addr, 1)
return ret.decode()
return ret.decode('latin1')
def __write_string(self, addr: int, s: str, encoding: str):
self.write(addr, bytes(s, encoding) + b'\x00')
@@ -80,7 +121,7 @@ class QlMemoryManager:
self.__write_string(addr, value, encoding)
def add_mapinfo(self, mem_s: int, mem_e: int, mem_p: int, mem_info: str, is_mmio: bool = False):
def add_mapinfo(self, mem_s: int, mem_e: int, mem_p: int, mem_info: str, mmio_ctx: Optional[QlMmioHandler] = None):
"""Add a new memory range to map.
Args:
@@ -88,10 +129,10 @@ class QlMemoryManager:
mem_e: memory range end
mem_p: permissions mask
mem_info: map entry label
is_mmio: memory range is mmio
mmio_ctx: mmio handler object; if specified the range will be treated as mmio
"""
bisect.insort(self.map_info, (mem_s, mem_e, mem_p, mem_info, is_mmio))
bisect.insort(self.map_info, (mem_s, mem_e, mem_p, mem_info, mmio_ctx))
def del_mapinfo(self, mem_s: int, mem_e: int):
"""Subtract a memory range from map.
@@ -105,13 +146,13 @@ class QlMemoryManager:
def __split_overlaps():
for idx in overlap_ranges:
lbound, ubound, perms, label, is_mmio = self.map_info[idx]
lbound, ubound, perms, label, mmio_ctx = self.map_info[idx]
if lbound < mem_s:
yield (lbound, mem_s, perms, label, is_mmio)
yield (lbound, mem_s, perms, label, mmio_ctx)
if mem_e < ubound:
yield (mem_e, ubound, perms, label, is_mmio)
yield (mem_e, ubound, perms, label, mmio_ctx)
# indices of first and last overlapping ranges. since map info is always
# sorted, we know that all overlapping rages are consecutive, so i1 > i0
@@ -129,27 +170,30 @@ class QlMemoryManager:
for entry in new_entries:
bisect.insort(self.map_info, entry)
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 = -1
def change_mapinfo(self, mem_s: int, mem_e: int, *, new_perms: Optional[int] = None, new_info: Optional[str] = None) -> None:
if new_perms is None and new_info is None:
# nothing to do
return
for idx, map_info in enumerate(self.map_info):
if mem_s >= map_info[0] and mem_e <= map_info[1]:
tmp_map_info = map_info
info_idx = idx
break
if tmp_map_info is None:
try:
# locate the map info entry to change
entry = next(entry for entry in self.map_info if mem_s >= entry[0] and mem_e <= entry[1])
except StopIteration:
self.ql.log.error(f'Cannot change mapinfo at {mem_s:#08x}-{mem_e:#08x}')
return
if mem_p is not None:
self.del_mapinfo(mem_s, mem_e)
self.add_mapinfo(mem_s, mem_e, mem_p, mem_info if mem_info else tmp_map_info[3])
return
_, _, perms, info, mmio_ctx = entry
if mem_info is not None:
self.map_info[info_idx] = (tmp_map_info[0], tmp_map_info[1], tmp_map_info[2], mem_info, tmp_map_info[4])
# caller wants to change perms?
if new_perms is not None:
perms = new_perms
# caller wants to change info?
if new_info is not None:
info = new_info
self.del_mapinfo(mem_s, mem_e)
self.add_mapinfo(mem_s, mem_e, perms, info, mmio_ctx)
def get_mapinfo(self) -> Sequence[Tuple[int, int, str, str, str]]:
"""Get memory map info.
@@ -168,18 +212,18 @@ class QlMemoryManager:
return ''.join(val if idx & ps else '-' for idx, val in perms_d.items())
def __process(lbound: int, ubound: int, perms: int, label: str, is_mmio: bool) -> Tuple[int, int, str, str, str]:
perms_str = __perms_mapping(perms)
def __process(entry: MapInfoEntry) -> Tuple[int, int, str, str, str]:
lbound, ubound, perms, label, mmio_ctx = entry
if hasattr(self.ql, 'loader'):
image = self.ql.loader.find_containing_image(lbound)
container = image.path if image and not is_mmio else ''
container = image.path if image and mmio_ctx is None else ''
else:
container = ''
return (lbound, ubound, perms_str, label, container)
return (lbound, ubound, __perms_mapping(perms), label, container)
return tuple(__process(*entry) for entry in self.map_info)
return tuple(__process(entry) for entry in self.map_info)
def get_formatted_mapinfo(self) -> Sequence[str]:
"""Get memory map info in a nicely formatted table.
@@ -270,12 +314,13 @@ class QlMemoryManager:
"mmio" : []
}
for lbound, ubound, perm, label, is_mmio in self.map_info:
if is_mmio:
mem_dict['mmio'].append((lbound, ubound, perm, label, *self.mmio_cbs[(lbound, ubound)]))
for lbound, ubound, perm, label, mmio_ctx in self.map_info:
if mmio_ctx is None:
key, data = 'ram', bytes(self.read(lbound, ubound - lbound))
else:
data = self.read(lbound, ubound - lbound)
mem_dict['ram'].append((lbound, ubound, perm, label, bytes(data)))
key, data = 'mmio', mmio_ctx
mem_dict[key].append((lbound, ubound, perm, label, data))
return mem_dict
@@ -294,12 +339,12 @@ class QlMemoryManager:
self.ql.log.debug(f'writing {len(data):#x} bytes at {lbound:#08x}')
self.write(lbound, data)
for lbound, ubound, perms, label, read_cb, write_cb in mem_dict['mmio']:
for lbound, ubound, perms, label, handler in mem_dict['mmio']:
self.ql.log.debug(f"restoring mmio range: {lbound:#08x} {ubound:#08x} {label}")
size = ubound - lbound
if not self.is_mapped(lbound, size):
self.map_mmio(lbound, size, read_cb, write_cb, info=label)
self.map_mmio(lbound, size, handler, label)
def read(self, addr: int, size: int) -> bytearray:
"""Read bytes from memory.
@@ -328,22 +373,12 @@ class QlMemoryManager:
if not size:
size = self.ql.arch.pointersize
__unpack = ({
1: self.ql.unpack8s,
2: self.ql.unpack16s,
4: self.ql.unpack32s,
8: self.ql.unpack64s
} if signed else {
1: self.ql.unpack8,
2: self.ql.unpack16,
4: self.ql.unpack32,
8: self.ql.unpack64
}).get(size)
if __unpack is None:
try:
_unpack = self._unpackers[(size, signed)]
except KeyError:
raise QlErrorStructConversion(f"Unsupported pointer size: {size}")
return __unpack(self.read(addr, size))
return _unpack(self.read(addr, size))
def write(self, addr: int, data: bytes) -> None:
"""Write bytes to a memory.
@@ -369,22 +404,12 @@ class QlMemoryManager:
if not size:
size = self.ql.arch.pointersize
__pack = ({
1: self.ql.pack8s,
2: self.ql.pack16s,
4: self.ql.pack32s,
8: self.ql.pack64s
} if signed else {
1: self.ql.pack8,
2: self.ql.pack16,
4: self.ql.pack32,
8: self.ql.pack64
}).get(size)
if __pack is None:
try:
_pack = self._packers[(size, signed)]
except KeyError:
raise QlErrorStructConversion(f"Unsupported pointer size: {size}")
self.write(addr, __pack(value))
self.write(addr, _pack(value))
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.
@@ -408,7 +433,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 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)]
ranges = [(max(begin, lbound), min(ubound, end)) for lbound, ubound, _, _, mmio_ctx in self.map_info if not (end < lbound or ubound < begin or mmio_ctx is not None)]
results = []
# if needle is a bytes sequence use it verbatim, not as a pattern
@@ -434,9 +459,6 @@ class QlMemoryManager:
self.del_mapinfo(addr, addr + size)
self.ql.uc.mem_unmap(addr, size)
if (addr, addr + size) in self.mmio_cbs:
del self.mmio_cbs[(addr, addr+size)]
def unmap_between(self, mem_s: int, mem_e: int) -> None:
"""Reclaim any allocated memory region within the specified range.
@@ -595,7 +617,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, perms)
self.change_mapinfo(aligned_address, aligned_address + aligned_size, new_perms=perms)
def map(self, addr: int, size: int, perms: int = UC_PROT_ALL, info: Optional[str] = None):
"""Map a new memory range.
@@ -617,17 +639,17 @@ class QlMemoryManager:
raise QlMemoryMappedError('Requested memory is unavailable')
self.ql.uc.mem_map(addr, size, perms)
self.add_mapinfo(addr, addr + size, perms, info or '[mapped]', is_mmio=False)
self.add_mapinfo(addr, addr + size, perms, info or '[mapped]', None)
def map_mmio(self, addr: int, size: int, read_cb: Optional[MmioReadCallback], write_cb: Optional[MmioWriteCallback], info: str = '[mmio]'):
def map_mmio(self, addr: int, size: int, handler: QlMmioHandler, info: str = '[mmio]'):
# TODO: mmio memory overlap with ram? Is that possible?
# TODO: Can read_cb or write_cb be None? How uc handle that access?
prot = UC_PROT_NONE
if read_cb:
if hasattr(handler, 'read'):
prot |= UC_PROT_READ
if write_cb:
if hasattr(handler, 'write'):
prot |= UC_PROT_WRITE
# generic mmio read wrapper
@@ -642,10 +664,8 @@ class QlMemoryManager:
cb(self.ql, offset, size, value)
self.ql.uc.mmio_map(addr, size, __mmio_read, read_cb, __mmio_write, write_cb)
self.add_mapinfo(addr, addr + size, prot, info, is_mmio=True)
self.mmio_cbs[(addr, addr + size)] = (read_cb, write_cb)
self.ql.uc.mmio_map(addr, size, __mmio_read, handler.read, __mmio_write, handler.write)
self.add_mapinfo(addr, addr + size, prot, info, handler)
class Chunk:
+1
View File
@@ -89,6 +89,7 @@ class QlOs:
if self.ql.code:
# this shellcode entrypoint does not work for windows
# windows shellcode entry point will comes from pe loader
self.load_address = self.profile.getint('CODE', 'load_address')
self.entry_point = self.profile.getint('CODE', 'entry_point')
self.code_ram_size = self.profile.getint('CODE', 'ram_size')

Some files were not shown because too many files have changed in this diff Show More