mirror of
https://github.com/qilingframework/qiling.git
synced 2026-10-11 21:39:59 +00:00
@@ -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
|
||||
|
||||
@@ -70,3 +70,9 @@ poetry.toml
|
||||
|
||||
# LSP config files
|
||||
pyrightconfig.json
|
||||
|
||||
# AI
|
||||
CLAUDE.md
|
||||
.claude
|
||||
.codex
|
||||
.AGENT.md
|
||||
|
||||
+427
@@ -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
@@ -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"]
|
||||
|
||||
@@ -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 +0,0 @@
|
||||
Features request and TODO please refer to issue 333 https://github.com/qilingframework/qiling/issues/333
|
||||
@@ -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
|
||||
@@ -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])
|
||||
|
||||
@@ -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
@@ -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
Submodule examples/rootfs updated: 6d4d654fdc...df3fa4dfc0
+1
-1
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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."
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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 $@
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
[CODE]
|
||||
ram_size = 0xa00000
|
||||
load_address = 0x80800000
|
||||
entry_point = 0x80800000
|
||||
heap_address = 0xa0000000
|
||||
heap_size = 0x300000
|
||||
|
||||
|
||||
|
||||
Generated
+187
-240
@@ -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
@@ -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"
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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]
|
||||
|
||||
@@ -1,3 +1 @@
|
||||
from .debugger import QlDebugger
|
||||
# from .disassember import QlDisassember
|
||||
# from .utils import QlReadELF
|
||||
|
||||
@@ -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
|
||||
@@ -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:
|
||||
|
||||
+7
-1
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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'
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
"""
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
})
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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('---------------------------------------------------------------------------------------')
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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",
|
||||
}
|
||||
|
||||
@@ -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
@@ -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:
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user