mirror of
https://github.com/qilingframework/qiling.git
synced 2026-10-12 02:21:40 +00:00
fix documents and minor changes
This commit is contained in:
@@ -11,10 +11,10 @@ jobs:
|
||||
matrix:
|
||||
#os: [windows-2019, macos-10.15, ubuntu-18.04, ubuntu-20.04]
|
||||
os: [windows-latest, ubuntu-latest]
|
||||
python-version: ["3.9", "3.11"]
|
||||
python-version: ["3.11", "3.13"]
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
python-version: 3.9
|
||||
python-version: "3.13"
|
||||
container: Docker
|
||||
|
||||
steps:
|
||||
|
||||
@@ -70,3 +70,9 @@ poetry.toml
|
||||
|
||||
# LSP config files
|
||||
pyrightconfig.json
|
||||
|
||||
# AI
|
||||
CLAUDE.md
|
||||
.claude
|
||||
.codex
|
||||
.AGENT.md
|
||||
|
||||
+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"]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
[](https://github.com/qilingframework/qiling/wiki)
|
||||
[](https://docs.qiling.io)
|
||||
[](https://pepy.tech/project/qiling)
|
||||
[](https://t.me/qilingframework)
|
||||
|
||||
@@ -8,43 +8,24 @@
|
||||
<img width="150" height="150" src="https://raw.githubusercontent.com/qilingframework/qiling/master/docs/qiling2_logo_small.png">
|
||||
</p>
|
||||
|
||||
# Qiling Framework
|
||||
[Qiling's use case, blog and related work](https://github.com/qilingframework/qiling/issues/134)
|
||||
|
||||
Qiling is an advanced binary emulation framework that allows you to emulate and sandbox code in an isolated environment across multiple platforms and architectures. Built on top of Unicorn Engine, Qiling provides a higher-level framework that understands operating system contexts, executable formats, and dynamic linking.
|
||||
Qiling is an advanced binary emulation framework, with the following features:
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Features](#features)
|
||||
- [Appearance](#Appearance)
|
||||
- [Use Cases](#use-cases)
|
||||
- [Quick Start](#quick-start)
|
||||
- [Installation](#installation)
|
||||
- [Basic Usage](#basic-usage)
|
||||
- [Qiling vs. Other Emulators](#qiling-vs-other-emulators)
|
||||
- [Qiling vs. Unicorn Engine](#qiling-vs-unicorn-engine)
|
||||
- [Qiling vs. QEMU User Mode](#qiling-vs-qemu-user-mode)
|
||||
- [Examples](#examples)
|
||||
- [Qltool](#qltool)
|
||||
- [Contributing](#contributing)
|
||||
- [License](#license)
|
||||
- [Contact](#contact)
|
||||
- [Core Developers & Contributors](#core-developers--contributors)
|
||||
|
||||
## Features
|
||||
|
||||
- **Multi-platform Emulation**: Windows, macOS, Linux, Android, BSD, UEFI, DOS, MBR.
|
||||
- **Multi-architecture Emulation**: 8086, X86, X86_64, ARM, ARM64, MIPS, RISC-V, PowerPC.
|
||||
- **Multiple File Format Support**: PE, Mach-O, ELF, COM, MBR.
|
||||
- **Kernel Module Emulation**: Supports Windows Driver (.sys), Linux Kernel Module (.ko) & macOS Kernel (.kext) via [Demigod](https://groundx.io/demigod/).
|
||||
- **Isolated Sandboxing**: Emulates & sandboxes code in an isolated environment with a fully configurable sandbox.
|
||||
- **In-depth API**: Provides in-depth memory, register, OS level, and filesystem level API.
|
||||
- **Fine-grain Instrumentation**: Allows hooks at various levels (instruction/basic-block/memory-access/exception/syscall/IO/etc.).
|
||||
- **Virtual Machine Level API**: Supports saving and restoring the current execution state.
|
||||
- **Debugging Capabilities**: Supports cross-architecture and platform debugging, including a built-in debugger with reverse debugging capability.
|
||||
- **Dynamic Hot Patching**: Allows dynamic hot patching of on-the-fly running code, including loaded libraries.
|
||||
- **Python Framework**: A true framework in Python, making it easy to build customized security analysis tools.
|
||||
|
||||
## Appearance
|
||||
- Emulate multi-platforms: Windows, macOS, Linux, Android, BSD, UEFI, DOS, MBR.
|
||||
- Emulate multi-architectures: 8086, X86, X86_64, ARM, ARM64, MIPS, RISC-V, PowerPC.
|
||||
- Support multiple file formats: PE, Mach-O, ELF, COM, MBR.
|
||||
- Support Windows Driver (.sys), Linux Kernel Module (.ko) & macOS Kernel (.kext) via [Demigod](https://groundx.io/demigod/).
|
||||
- Emulates & sandbox code in an isolated environment.
|
||||
- Provides a fully configurable sandbox.
|
||||
- Provides in-depth memory, register, OS level and filesystem level API.
|
||||
- Fine-grain instrumentation: allows hooks at various levels
|
||||
(instruction/basic-block/memory-access/exception/syscall/IO/etc.)
|
||||
- Provides virtual machine level API such as saving and restoring the current execution state.
|
||||
- Supports cross architecture and platform debugging capabilities.
|
||||
- Built-in debugger with reverse debugging capability.
|
||||
- Allows dynamic hot patch on-the-fly running code, including the loaded library.
|
||||
- True framework in Python, making it easy to build customized security analysis tools on top.
|
||||
|
||||
Qiling also made its way to various international conferences.
|
||||
|
||||
@@ -68,37 +49,79 @@ Qiling also made its way to various international conferences.
|
||||
- [Nullcon](https://nullcon.net/website/goa-2020/speakers/kaijern-lau.php)
|
||||
|
||||
2019:
|
||||
|
||||
- [DEFCON, USA](https://www.defcon.org/html/defcon-27/dc-27-demolabs.html#QiLing)
|
||||
- [Hitcon](https://hitcon.org/2019/CMT/agenda)
|
||||
- [Zeronights](https://zeronights.ru/report-en/qiling-io-advanced-binary-emulation-framework/)
|
||||
|
||||
## Use Cases
|
||||
|
||||
Qiling has been presented at various international conferences, showcasing its versatility in:
|
||||
Qiling is backed by [Unicorn Engine](http://www.unicorn-engine.org).
|
||||
|
||||
- Binary analysis and reverse engineering.
|
||||
- Malware analysis and sandboxing.
|
||||
- Firmware analysis and emulation.
|
||||
- Security research and vulnerability discovery.
|
||||
- CTF challenges and exploit development.
|
||||
Visit our [website](https://www.qiling.io) for more information.
|
||||
|
||||
For more details on Qiling's use cases, blog posts, and related work, please refer to [Qiling's use case, blog and related work](https://github.com/qilingframework/qiling/issues/134).
|
||||
---
|
||||
#### License
|
||||
|
||||
## Quick Start
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
### Installation
|
||||
---
|
||||
|
||||
Qiling requires Python 3.8 or newer. You can install it using pip:
|
||||
#### Qiling vs. other Emulators
|
||||
|
||||
```bash
|
||||
pip install qiling
|
||||
```
|
||||
There are many open-source emulators, but two projects closest to Qiling
|
||||
are [Unicorn](http://www.unicorn-engine.org) & [QEMU user mode](https://qemu.org).
|
||||
This section explains the main differences of Qiling against them.
|
||||
|
||||
For more detailed installation instructions and dependencies, please refer to the [official documentation](https://github.com/qilingframework/qiling/wiki/Installation).
|
||||
##### Qiling vs. Unicorn engine
|
||||
|
||||
### Basic Usage
|
||||
Built on top of Unicorn, but Qiling & Unicorn are two different animals.
|
||||
|
||||
The example below shows how to use Qiling framework in the most straightforward way to emulate a Windows executable.
|
||||
- Unicorn is just a CPU emulator, so it focuses on emulating CPU instructions,
|
||||
that can understand emulator memory.
|
||||
Beyond that, Unicorn is not aware of higher level concepts, such as dynamic
|
||||
libraries, system calls, I/O handling or executable formats like PE, Mach-O
|
||||
or ELF. As a result, Unicorn can only emulate raw machine instructions,
|
||||
without Operating System (OS) context.
|
||||
- Qiling is designed as a higher level framework, that leverages Unicorn to
|
||||
emulate CPU instructions, but can understand OS: it has executable format
|
||||
loaders (for PE, Mach-O & ELF currently), dynamic linkers (so we can
|
||||
load & relocate shared libraries), syscall & IO handlers. For this reason,
|
||||
Qiling can run executable binary without requiring its native OS.
|
||||
|
||||
##### Qiling vs. QEMU user mode
|
||||
|
||||
QEMU user mode does a similar thing to our emulator, that is, to emulate whole
|
||||
executable binaries in a cross-architecture way.
|
||||
However, Qiling offers some important differences against QEMU user mode:
|
||||
|
||||
- Qiling is a true analysis framework,
|
||||
that allows you to build your own dynamic analysis tools on top (in Python).
|
||||
Meanwhile, QEMU is just a tool, not a framework.
|
||||
- Qiling can perform dynamic instrumentation, and can even hot patch code at
|
||||
runtime. QEMU does neither.
|
||||
- Not only working cross-architecture, Qiling is also cross-platform.
|
||||
For example, you can run Linux ELF file on top of Windows.
|
||||
In contrast, QEMU user mode only runs binary of the same OS, such as Linux
|
||||
ELF on Linux, due to the way it forwards syscall from emulated code to
|
||||
native OS.
|
||||
- Qiling supports more platforms, including Windows, macOS, Linux & BSD. QEMU
|
||||
user mode can only handle Linux & BSD.
|
||||
|
||||
---
|
||||
|
||||
#### Installation
|
||||
|
||||
Please see [setup guide](https://github.com/qilingframework/qiling/wiki/Installation) file for how to install Qiling Framework.
|
||||
|
||||
---
|
||||
|
||||
#### Examples
|
||||
|
||||
The example below shows how to use Qiling framework in the most
|
||||
straightforward way to emulate a Windows executable.
|
||||
|
||||
```python
|
||||
from qiling import Qiling
|
||||
@@ -112,30 +135,8 @@ if __name__ == "__main__":
|
||||
ql.run()
|
||||
```
|
||||
|
||||
## Qiling vs. Other Emulators
|
||||
|
||||
There are many open-source emulators, but two projects closest to Qiling are [Unicorn](http://www.unicorn-engine.org) & [QEMU user mode](https://qemu.org). This section explains the main differences of Qiling against them.
|
||||
|
||||
### Qiling vs. Unicorn Engine
|
||||
|
||||
Built on top of Unicorn, but Qiling & Unicorn are two different animals.
|
||||
|
||||
- **Unicorn** is just a CPU emulator, so it focuses on emulating CPU instructions, that can understand emulator memory. Beyond that, Unicorn is not aware of higher level concepts, such as dynamic libraries, system calls, I/O handling or executable formats like PE, Mach-O or ELF. As a result, Unicorn can only emulate raw machine instructions, without Operating System (OS) context.
|
||||
- **Qiling** is designed as a higher level framework, that leverages Unicorn to emulate CPU instructions, but can understand OS: it has executable format loaders (for PE, Mach-O & ELF currently), dynamic linkers (so we can load & relocate shared libraries), syscall & IO handlers. For this reason, Qiling can run executable binary without requiring its native OS.
|
||||
|
||||
### Qiling vs. QEMU User Mode
|
||||
|
||||
QEMU user mode does a similar thing to our emulator, that is, to emulate whole executable binaries in a cross-architecture way.
|
||||
However, Qiling offers some important differences against QEMU user mode:
|
||||
|
||||
- **Qiling is a true analysis framework**, that allows you to build your own dynamic analysis tools on top (in Python). Meanwhile, QEMU is just a tool, not a framework.
|
||||
- **Qiling can perform dynamic instrumentation**, and can even hot patch code at runtime. QEMU does neither.
|
||||
- Not only working cross-architecture, **Qiling is also cross-platform**. For example, you can run Linux ELF file on top of Windows. In contrast, QEMU user mode only runs binary of the same OS, such as Linux ELF on Linux, due to the way it forwards syscall from emulated code to native OS.
|
||||
- **Qiling supports more platforms**, including Windows, macOS, Linux & BSD. QEMU user mode can only handle Linux & BSD.
|
||||
|
||||
## Examples
|
||||
|
||||
- The following example shows how a Windows crackme may be patched dynamically to make it always display the “Congratulation” dialog.
|
||||
- The following example shows how a Windows crackme may be patched dynamically
|
||||
to make it always display the “Congratulation” dialog.
|
||||
|
||||
```python
|
||||
from qiling import Qiling
|
||||
@@ -176,13 +177,15 @@ The below YouTube video shows how the above example works.
|
||||
|
||||
#### Emulating ARM router firmware on Ubuntu x64 host
|
||||
|
||||
Qiling Framework hot-patches and emulates an ARM router's `/usr/bin/httpd` on an x86_64 Ubuntu host.
|
||||
Qiling Framework hot-patches and emulates an ARM router's `/usr/bin/httpd` on
|
||||
an x86_64 Ubuntu host.
|
||||
|
||||
[](https://www.youtube.com/watch?v=e3_T3KLhNUs)
|
||||
[](https://www.youtube.com/watch?v=e3_T3KLh2NU)
|
||||
|
||||
#### Qiling's IDA Pro Plugin: Instrument and Decrypt Mirai's Secret
|
||||
|
||||
This video demonstrates how Qiling's IDA Pro plugin can make IDA Pro run with Qiling instrumentation engine.
|
||||
This video demonstrates how Qiling's IDA Pro plugin can make IDA Pro run with
|
||||
Qiling instrumentation engine.
|
||||
|
||||
[](http://www.youtube.com/watch?v=ZWMWTq2WTXk)
|
||||
|
||||
@@ -192,62 +195,63 @@ Solving a simple CTF challenge with Qiling Framework and IDA Pro
|
||||
|
||||
[](https://www.youtube.com/watch?v=SPjVAt2FkKA)
|
||||
|
||||
|
||||
#### Emulating MBR
|
||||
|
||||
Qiling Framework emulates MBR
|
||||
|
||||
[](https://github.com/qilingframework/theme.qiling.io/blob/master/source/img/mbr.png?raw=true)
|
||||
|
||||
## Qltool
|
||||
---
|
||||
|
||||
#### Qltool
|
||||
|
||||
Qiling also provides a friendly tool named `qltool` to quickly emulate shellcode & executable binaries.
|
||||
|
||||
With qltool, easy execution can be performed:
|
||||
|
||||
|
||||
With shellcode:
|
||||
|
||||
```bash
|
||||
```
|
||||
$ ./qltool code --os linux --arch arm --format hex -f examples/shellcodes/linarm32_tcp_reverse_shell.hex
|
||||
```
|
||||
|
||||
With binary file:
|
||||
|
||||
```bash
|
||||
```
|
||||
$ ./qltool run -f examples/rootfs/x8664_linux/bin/x8664_hello --rootfs examples/rootfs/x8664_linux/
|
||||
```
|
||||
|
||||
With binary and GDB debugger enabled:
|
||||
|
||||
```bash
|
||||
```
|
||||
$ ./qltool run -f examples/rootfs/x8664_linux/bin/x8664_hello --gdb 127.0.0.1:9999 --rootfs examples/rootfs/x8664_linux
|
||||
```
|
||||
|
||||
With code coverage collection (UEFI only for now):
|
||||
|
||||
```bash
|
||||
```
|
||||
$ ./qltool run -f examples/rootfs/x8664_efi/bin/TcgPlatformSetupPolicy --rootfs examples/rootfs/x8664_efi --coverage-format drcov --coverage-file TcgPlatformSetupPolicy.cov
|
||||
```
|
||||
|
||||
With JSON output (Windows, mainly):
|
||||
|
||||
```bash
|
||||
```
|
||||
$ ./qltool run -f examples/rootfs/x86_windows/bin/x86_hello.exe --rootfs examples/rootfs/x86_windows/ --console False --json
|
||||
```
|
||||
---
|
||||
|
||||
## Contributing
|
||||
|
||||
We welcome contributions from the community! If you're interested in contributing to Qiling Framework, please check out our [GitHub repository](https://github.com/qilingframework/qiling) and look for open issues or submit a pull request.
|
||||
#### Contact
|
||||
|
||||
## License
|
||||
Get the latest info from our website https://www.qiling.io
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
|
||||
Contact us at email info@qiling.io,
|
||||
via Twitter [@qiling_io](https://twitter.com/qiling_io).
|
||||
|
||||
## Contact
|
||||
---
|
||||
|
||||
Get the latest info from our website [https://www.qiling.io](https://www.qiling.io)
|
||||
|
||||
Contact us at email [[email protected]](mailto:[email protected]), or via Twitter [@qiling_io](https://twitter.com/qiling_io).
|
||||
|
||||
## Core Developers & Contributors
|
||||
#### Core developers, Key Contributors and etc.
|
||||
|
||||
Please refer to [CREDITS.md](https://github.com/qilingframework/qiling/blob/dev/CREDITS.md).
|
||||
@@ -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)
|
||||
|
||||
|
||||
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])
|
||||
|
||||
@@ -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)
|
||||
+33
-19
@@ -8,10 +8,9 @@ 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",
|
||||
@@ -27,17 +26,33 @@ def get_kaimendaji_password():
|
||||
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)
|
||||
# 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
|
||||
|
||||
password = ''
|
||||
for item in password_raw:
|
||||
if 0 <= item <= 9:
|
||||
password += chr(item + 48)
|
||||
else:
|
||||
password += chr(item + 87)
|
||||
params = ql.os.resolve_fcall_params({
|
||||
'ptr1': POINTER, # points to real password
|
||||
'ptr2': POINTER, # points to user provided password
|
||||
'size': SIZE_T # comparison length
|
||||
})
|
||||
|
||||
ptr1 = params['ptr1']
|
||||
size = params['size']
|
||||
|
||||
password_raw = ql.mem.read(ptr1, size)
|
||||
|
||||
def __hex_digit(ch: int) -> str:
|
||||
off = ord('0') if ch in range(10) else ord('a') - 10
|
||||
|
||||
return chr(ch + off)
|
||||
|
||||
# should be: "013f1f"
|
||||
password = "".join(__hex_digit(ch) for ch in password_raw)
|
||||
|
||||
print(f'The password is: {password}')
|
||||
|
||||
print("The password is: %s" % password)
|
||||
|
||||
def partial_run_init(ql: Qiling):
|
||||
# argv prepare
|
||||
@@ -57,19 +72,18 @@ def get_kaimendaji_password():
|
||||
ql.arch.regs.r2 = 2
|
||||
ql.arch.regs.r3 = argv_ptr
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
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.OFF)
|
||||
ql = Qiling(code=uboot_code[0x40:], archtype=QL_ARCH.ARM, ostype=QL_OS.BLOB, profile="uboot_bin.ql", verbose=QL_VERBOSE.DEBUG)
|
||||
|
||||
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)
|
||||
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(image_base_addr + 0x486B4, image_base_addr + 0x48718)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
get_kaimendaji_password()
|
||||
ql.run(imgbase + 0x486B4, imgbase + 0x48718)
|
||||
|
||||
+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
+191
-244
@@ -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.2.1 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 (==v0.910) ; python_version < \"3.6\"", "mypy (==v0.971) ; python_version == \"3.6\"", "mypy (==v1.4.1) ; python_version == \"3.7\"", "mypy (==v1.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"},
|
||||
@@ -845,32 +812,32 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "setuptools"
|
||||
version = "75.3.0"
|
||||
version = "78.1.1"
|
||||
description = "Easily download, build, install, upgrade, and uninstall Python packages"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
python-versions = ">=3.9"
|
||||
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"},
|
||||
{file = "setuptools-78.1.1-py3-none-any.whl", hash = "sha256:c3a9c4211ff4c309edb8b8c4f1cbfa7ae324c4ba9f91ff254e3d305b9fd54561"},
|
||||
{file = "setuptools-78.1.1.tar.gz", hash = "sha256:fcc17fd9cd898242f6b4adfaca46137a9edef687f43e6f78469692a5e70d851d"},
|
||||
]
|
||||
|
||||
[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.8.0) ; sys_platform != \"cygwin\""]
|
||||
core = ["importlib_metadata (>=6) ; python_version < \"3.10\"", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "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.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.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.14.*)", "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"
|
||||
|
||||
+3
-5
@@ -1,16 +1,14 @@
|
||||
[tool.poetry]
|
||||
urls = { homepage = "https://qiling.io" }
|
||||
name = "qiling"
|
||||
version = "1.4.8"
|
||||
version = "1.4.11-dev"
|
||||
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",
|
||||
|
||||
"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"
|
||||
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"
|
||||
|
||||
@@ -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,6 +96,17 @@ 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:
|
||||
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?
|
||||
@@ -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
|
||||
|
||||
+24
-24
@@ -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]
|
||||
|
||||
@@ -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:
|
||||
|
||||
+6
@@ -25,4 +25,10 @@
|
||||
<reg name="pc" bitsize="32" type="code_ptr"/>
|
||||
|
||||
<reg name="xpsr" bitsize="32" regnum="25"/>
|
||||
<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,14 +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_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,
|
||||
@@ -31,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]
|
||||
@@ -133,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.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.MIPS: dict(**mips_regs_gpr),
|
||||
QL_ARCH.PPC: dict(**ppc_regs)
|
||||
}[archtype]
|
||||
|
||||
regsinfo = sorted(QlGdbFeatures.__walk_xml_regs(xmltree))
|
||||
|
||||
@@ -3,12 +3,14 @@
|
||||
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
|
||||
#
|
||||
|
||||
from typing import Dict, Optional
|
||||
from typing import ClassVar, Dict, Optional
|
||||
|
||||
from .arch import Arch
|
||||
|
||||
|
||||
class ArchARM(Arch):
|
||||
_flags_reg: ClassVar[str] = 'cpsr'
|
||||
|
||||
def __init__(self) -> None:
|
||||
regs = (
|
||||
'r0', 'r1', 'r2', 'r3',
|
||||
@@ -134,6 +136,8 @@ class ArchARM(Arch):
|
||||
|
||||
|
||||
class ArchCORTEX_M(ArchARM):
|
||||
_flags_reg: ClassVar[str] = 'xpsr'
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ from capstone.arm_const import (
|
||||
from unicorn.arm_const import UC_ARM_REG_PC
|
||||
|
||||
from .branch_predictor import BranchPredictor, Prophecy
|
||||
from ..arch import ArchARM
|
||||
from ..arch import ArchARM, ArchCORTEX_M
|
||||
from ..misc import InvalidInsn
|
||||
|
||||
|
||||
@@ -27,11 +27,11 @@ class BranchPredictorARM(BranchPredictor, ArchARM):
|
||||
|
||||
stop = 'udf'
|
||||
|
||||
def get_cpsr(self) -> Tuple[bool, bool, bool, bool]:
|
||||
"""Get flags map of CPSR.
|
||||
def get_cond_flags(self) -> Tuple[bool, bool, bool, bool]:
|
||||
"""Get condition status flags from CPSR / xPSR.
|
||||
"""
|
||||
|
||||
cpsr = self.read_reg('cpsr')
|
||||
cpsr = self.read_reg(self._flags_reg)
|
||||
|
||||
return (
|
||||
(cpsr & (0b1 << 28)) != 0, # V, overflow flag
|
||||
@@ -122,9 +122,9 @@ class BranchPredictorARM(BranchPredictor, ArchARM):
|
||||
|
||||
def __is_taken(cc: int) -> Tuple[bool, Tuple[bool, ...]]:
|
||||
pred = predicate[cc]
|
||||
cpsr = self.get_cpsr()
|
||||
flags = self.get_cond_flags()
|
||||
|
||||
return pred(*cpsr), cpsr
|
||||
return pred(*flags), flags
|
||||
|
||||
# conditions predicate selector
|
||||
predicate: Dict[int, Callable[..., bool]] = {
|
||||
@@ -215,13 +215,13 @@ class BranchPredictorARM(BranchPredictor, ArchARM):
|
||||
where = __parse_op(operands[1], **msize[suffix])
|
||||
|
||||
elif iname in binop:
|
||||
going, cpsr = __is_taken(insn.cc)
|
||||
going, flags = __is_taken(insn.cc)
|
||||
|
||||
if going:
|
||||
operator = binop[iname]
|
||||
op1 = __parse_op(operands[1])
|
||||
op2 = __parse_op(operands[2])
|
||||
carry = int(cpsr[1])
|
||||
carry = int(flags[1])
|
||||
|
||||
where = (op1 and op2) and operator(op1, op2, carry)
|
||||
|
||||
@@ -261,6 +261,6 @@ class BranchPredictorARM(BranchPredictor, ArchARM):
|
||||
return Prophecy(going, where)
|
||||
|
||||
|
||||
class BranchPredictorCORTEX_M(BranchPredictorARM):
|
||||
class BranchPredictorCORTEX_M(BranchPredictorARM, ArchCORTEX_M):
|
||||
"""Branch Predictor for ARM Cortex-M.
|
||||
"""
|
||||
|
||||
@@ -57,7 +57,7 @@ class Context:
|
||||
"""Helper function for disassembling.
|
||||
"""
|
||||
|
||||
insn_bytes = self.read_insn(address)
|
||||
insn_bytes = self.read_insn(address) or b''
|
||||
insn = None
|
||||
|
||||
if insn_bytes:
|
||||
@@ -75,7 +75,7 @@ class Context:
|
||||
A tuple of: instruction address, size, mnemonic and operands
|
||||
"""
|
||||
|
||||
insn_bytes = self.read_insn(address)
|
||||
insn_bytes = self.read_insn(address) or b''
|
||||
insn = None
|
||||
|
||||
if insn_bytes:
|
||||
|
||||
@@ -136,10 +136,7 @@ class QlQdb(Cmd, QlDebugger):
|
||||
with self.__set_temp(self.ql, 'verbose', QL_VERBOSE.DISABLED):
|
||||
self.ql.os.run()
|
||||
|
||||
if self.ql.os.type is QL_OS.BLOB:
|
||||
self.ql.loader.entry_point = self.ql.loader.load_address
|
||||
|
||||
elif init_hook:
|
||||
if init_hook:
|
||||
for each_hook in init_hook:
|
||||
self.do_breakpoint(each_hook)
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
|
||||
#
|
||||
|
||||
from typing import Iterator, Optional
|
||||
from typing import Iterator
|
||||
|
||||
from .render import Render, ContextRender
|
||||
from ..arch import ArchARM, ArchCORTEX_M
|
||||
@@ -15,7 +15,7 @@ class ContextRenderARM(ContextRender, ArchARM):
|
||||
"""
|
||||
|
||||
def print_mode_info(self) -> None:
|
||||
cpsr = self.read_reg('cpsr')
|
||||
cpsr = self.read_reg(self._flags_reg)
|
||||
|
||||
flags = ArchARM.get_flags(cpsr)
|
||||
mode = ArchARM.get_mode(cpsr)
|
||||
|
||||
@@ -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,8 +3,13 @@
|
||||
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
|
||||
#
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from __future__ import annotations
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from qiling import Qiling
|
||||
|
||||
|
||||
@@ -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):
|
||||
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):
|
||||
def dump_coverage(self, coverage_file: str) -> None:
|
||||
def __write_line(bio: BinaryIO, line: str) -> None:
|
||||
bio.write(f'{line}\n'.encode())
|
||||
|
||||
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))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -3,17 +3,28 @@
|
||||
# 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.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):
|
||||
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):
|
||||
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,79 +309,17 @@ 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):
|
||||
@@ -377,12 +331,12 @@ class IDA:
|
||||
|
||||
@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('---------------------------------------------------------------------------------------')
|
||||
|
||||
@@ -13,23 +13,19 @@ class QlBaseTrace(ABC):
|
||||
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
|
||||
+100
-51
@@ -3,24 +3,68 @@
|
||||
# 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,31 +74,37 @@ class QlHwManager:
|
||||
if struct is None:
|
||||
struct, base, kwargs = self.load_env(label.upper())
|
||||
|
||||
try:
|
||||
if kwargs is None:
|
||||
kwargs = {}
|
||||
|
||||
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
|
||||
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:
|
||||
@@ -70,7 +120,8 @@ 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`
|
||||
"""
|
||||
|
||||
@@ -79,39 +130,20 @@ class QlHwManager:
|
||||
if lbound <= address < rbound:
|
||||
return self.entity[label]
|
||||
|
||||
return None
|
||||
|
||||
def step(self):
|
||||
""" 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)
|
||||
def setup_mmio(self, begin: int, size: int, info: str) -> None:
|
||||
dev = QlPripheralHandler(self, begin, size, info)
|
||||
|
||||
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 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
|
||||
|
||||
@@ -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
|
||||
|
||||
+13
-9
@@ -109,25 +109,29 @@ 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()
|
||||
|
||||
+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
|
||||
|
||||
@@ -106,6 +124,108 @@ class Process:
|
||||
|
||||
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]")
|
||||
|
||||
|
||||
+15
-4
@@ -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.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)
|
||||
|
||||
+29
-26
@@ -3,21 +3,31 @@
|
||||
# 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
|
||||
@@ -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 read(self, size: Optional[int]) -> bytes:
|
||||
return self.fp.read(size)
|
||||
|
||||
def write(self, bs):
|
||||
return self.fp.write(bs)
|
||||
def write(self, buffer: ReadableBuffer) -> int:
|
||||
return self.fp.write(buffer)
|
||||
|
||||
def lseek(self, offset, origin):
|
||||
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):
|
||||
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(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
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
+99
-79
@@ -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)
|
||||
@@ -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')
|
||||
|
||||
|
||||
+7
-1
@@ -3,6 +3,8 @@
|
||||
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
|
||||
#
|
||||
|
||||
import os
|
||||
|
||||
from typing import Optional, Union
|
||||
from pathlib import Path, PurePosixPath, PureWindowsPath
|
||||
|
||||
@@ -328,7 +330,11 @@ class QlOsPath:
|
||||
|
||||
@staticmethod
|
||||
def __host_casefold_path(hostpath: str) -> Optional[str]:
|
||||
# assuming posix host
|
||||
# NT hosts already match paths case-insensitively, so if the caller landed here
|
||||
# (after a failed os.path.exists) the file really does not exist.
|
||||
if os.name == 'nt':
|
||||
return None
|
||||
|
||||
p = PurePosixPath(hostpath)
|
||||
norm = Path(p.anchor)
|
||||
|
||||
|
||||
@@ -1064,3 +1064,29 @@ SHMAT = 21
|
||||
SHMDT = 22
|
||||
SHMGET = 23
|
||||
SHMCTL = 24
|
||||
|
||||
# see: https://elixir.bootlin.com/linux/v5.19.17/source/include/uapi/linux/eventpoll.h
|
||||
EPOLL_CTL_ADD = 1
|
||||
EPOLL_CTL_DEL = 2
|
||||
EPOLL_CTL_MOD = 3
|
||||
|
||||
EPOLLIN = 0x00000001
|
||||
EPOLLPRI = 0x00000002
|
||||
EPOLLOUT = 0x00000004
|
||||
EPOLLERR = 0x00000008
|
||||
EPOLLHUP = 0x00000010
|
||||
EPOLLNVAL = 0x00000020
|
||||
EPOLLRDNORM = 0x00000040
|
||||
EPOLLRDBAND = 0x00000080
|
||||
EPOLLWRNORM = 0x00000100
|
||||
EPOLLWRBAND = 0x00000200
|
||||
EPOLLMSG = 0x00000400
|
||||
EPOLLRDHUP = 0x00002000
|
||||
|
||||
# TODO: should be aligned to emulated system's close-on-exec value
|
||||
EPOLL_CLOEXEC = 0o2000000
|
||||
|
||||
EPOLLEXCLUSIVE = 0b1 << 28
|
||||
EPOLLWAKEUP = 0b1 << 29
|
||||
EPOLLONESHOT = 0b1 << 30
|
||||
EPOLLET = 0b1 << 31
|
||||
|
||||
@@ -0,0 +1,334 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
|
||||
#
|
||||
|
||||
"""
|
||||
Hybrid kernel proxy — forward specific syscalls to a real Linux kernel.
|
||||
|
||||
Usage:
|
||||
from qiling import Qiling
|
||||
from qiling.os.posix.kernel_proxy import KernelProxy, FD, PtrIn, PtrOut
|
||||
|
||||
ql = Qiling(argv=["/bin/myserver"], rootfs="rootfs/x8664_linux")
|
||||
proxy = KernelProxy(ql)
|
||||
|
||||
# integer-arg syscall returning a new FD
|
||||
proxy.forward_syscall("epoll_create1", returns_fd=True)
|
||||
|
||||
# epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
|
||||
proxy.forward_syscall("epoll_ctl",
|
||||
arg_types=(FD, "int", FD, PtrIn(size=12)))
|
||||
|
||||
# epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)
|
||||
proxy.forward_syscall("epoll_wait",
|
||||
arg_types=(FD, PtrOut(size=lambda a: a[2] * 12), "int", "int"))
|
||||
|
||||
ql.run()
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
import socket
|
||||
import subprocess
|
||||
import weakref
|
||||
from typing import Dict, Optional, Sequence, Tuple, TYPE_CHECKING
|
||||
|
||||
from qiling.const import QL_INTERCEPT
|
||||
from qiling.exception import QlErrorArch, QlErrorSyscallError, QlErrorSyscallNotFound
|
||||
from qiling.os.posix.kernel_proxy.argtypes import (
|
||||
INT, FD, PtrIn, PtrOut, PtrInOut, is_pointer,
|
||||
)
|
||||
from qiling.os.posix.kernel_proxy.ipc import ProxyClient
|
||||
from qiling.os.posix.kernel_proxy.proxy_fd import ql_proxy_fd
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from qiling import Qiling
|
||||
|
||||
|
||||
__all__ = ['KernelProxy', 'INT', 'FD', 'PtrIn', 'PtrOut', 'PtrInOut']
|
||||
|
||||
|
||||
class KernelProxy:
|
||||
"""Forward specific syscalls to a real Linux kernel via a helper process.
|
||||
|
||||
The proxy process executes real syscalls and returns results. Integration
|
||||
is through set_syscall() CALL hooks — no changes to Qiling's dispatch code.
|
||||
"""
|
||||
|
||||
def __init__(self, ql: Qiling):
|
||||
if sys.platform != 'linux':
|
||||
raise QlErrorArch("KernelProxy requires a Linux host")
|
||||
|
||||
self.ql = ql
|
||||
self._process: Optional[subprocess.Popen] = None
|
||||
self._client: Optional[ProxyClient] = None
|
||||
self._forwarded: Dict[str, int] = {} # name -> syscall_nr
|
||||
self._reverse_table: Optional[Dict[str, int]] = None # name -> nr (built on first use)
|
||||
|
||||
self._start_proxy()
|
||||
|
||||
def _start_proxy(self):
|
||||
"""Start the proxy subprocess, connected via Unix socketpair."""
|
||||
parent_sock, child_sock = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
|
||||
child_fd = child_sock.fileno()
|
||||
|
||||
# ensure the subprocess can find qiling even when run from a subdirectory
|
||||
env = os.environ.copy()
|
||||
qiling_root = os.path.dirname(os.path.dirname(os.path.dirname(
|
||||
os.path.dirname(os.path.dirname(os.path.abspath(__file__))))))
|
||||
python_path = env.get('PYTHONPATH', '')
|
||||
env['PYTHONPATH'] = f"{qiling_root}:{python_path}" if python_path else qiling_root
|
||||
|
||||
self._process = subprocess.Popen(
|
||||
[sys.executable, '-m', 'qiling.os.posix.kernel_proxy.proxy', str(child_fd)],
|
||||
pass_fds=(child_fd,),
|
||||
close_fds=True,
|
||||
env=env,
|
||||
)
|
||||
child_sock.close()
|
||||
|
||||
self._client = ProxyClient(parent_sock)
|
||||
self.ql.log.info(f"kernel proxy started (pid={self._process.pid})")
|
||||
|
||||
def _build_reverse_table(self) -> Dict[str, int]:
|
||||
"""Build name -> syscall_nr mapping from the guest architecture's syscall table."""
|
||||
if self._reverse_table is not None:
|
||||
return self._reverse_table
|
||||
|
||||
from qiling.const import QL_ARCH
|
||||
|
||||
# get the raw syscall table dict for this architecture
|
||||
arch_tables = {
|
||||
QL_ARCH.ARM64 : 'arm64_syscall_table',
|
||||
QL_ARCH.ARM : 'arm_syscall_table',
|
||||
QL_ARCH.X8664 : 'x8664_syscall_table',
|
||||
QL_ARCH.X86 : 'x86_syscall_table',
|
||||
QL_ARCH.MIPS : 'mips_syscall_table',
|
||||
QL_ARCH.RISCV : 'riscv32_syscall_table',
|
||||
QL_ARCH.RISCV64 : 'riscv64_syscall_table',
|
||||
QL_ARCH.PPC : 'ppc_syscall_table',
|
||||
}
|
||||
|
||||
table_name = arch_tables.get(self.ql.arch.type)
|
||||
if table_name is None:
|
||||
raise QlErrorArch(f"KernelProxy: unsupported architecture {self.ql.arch.type}")
|
||||
|
||||
import qiling.os.linux.map_syscall as mod
|
||||
table = getattr(mod, table_name)
|
||||
|
||||
# reverse: name -> nr
|
||||
self._reverse_table = {name: nr for nr, name in table.items()}
|
||||
return self._reverse_table
|
||||
|
||||
def _resolve_syscall_nr(self, name: str) -> int:
|
||||
"""Resolve a syscall name to its number for the guest architecture."""
|
||||
table = self._build_reverse_table()
|
||||
if name not in table:
|
||||
raise QlErrorSyscallNotFound(
|
||||
f"KernelProxy: syscall '{name}' not found in {self.ql.arch.type.name} syscall table"
|
||||
)
|
||||
return table[name]
|
||||
|
||||
def forward_syscall(self, name: str, returns_fd: bool = False,
|
||||
arg_types: Optional[Sequence] = None):
|
||||
"""Register a CALL hook that forwards this syscall to the kernel proxy.
|
||||
|
||||
Args:
|
||||
name: syscall name (e.g. "epoll_create1", "eventfd2").
|
||||
returns_fd: if True, wrap the return value in ql_proxy_fd and store
|
||||
it in the Qiling FD table. Use this for syscalls that
|
||||
return file descriptors (epoll_create1, eventfd2, etc.).
|
||||
arg_types: optional per-arg descriptors. Each entry is one of:
|
||||
INT (or "int") — pass through unchanged (default).
|
||||
FD (or "fd") — guest FD; translated to the proxy FD.
|
||||
PtrIn(size) — pointer; bytes copied from guest to proxy.
|
||||
PtrOut(size) — pointer; bytes copied back from proxy to guest.
|
||||
PtrInOut(size) — pointer; both directions.
|
||||
If omitted, all arguments are treated as INT.
|
||||
"""
|
||||
nr = self._resolve_syscall_nr(name)
|
||||
self._forwarded[name] = nr
|
||||
|
||||
forwarder = self._make_forwarder(name, nr, returns_fd, arg_types)
|
||||
self.ql.os.set_syscall(name, forwarder, QL_INTERCEPT.CALL)
|
||||
|
||||
kind = []
|
||||
if returns_fd:
|
||||
kind.append('returns FD')
|
||||
if arg_types:
|
||||
kind.append(f'arg_types={tuple(type(a).__name__ if not isinstance(a, str) else a for a in arg_types)}')
|
||||
|
||||
suffix = f" [{', '.join(kind)}]" if kind else ''
|
||||
self.ql.log.info(f"forwarding syscall '{name}' (nr={nr}) to kernel proxy{suffix}")
|
||||
|
||||
def _make_forwarder(self, name: str, guest_nr: int, returns_fd: bool,
|
||||
arg_types: Optional[Sequence]):
|
||||
"""Create a CALL hook closure for one syscall.
|
||||
|
||||
Captures only the data the closure needs (host syscall nr, client, weakref
|
||||
to self) so the registered hook does not keep the KernelProxy alive.
|
||||
"""
|
||||
# resolve once at registration time so the hot path stays simple
|
||||
host_nr = self._get_host_syscall_nr(name)
|
||||
client = self._client
|
||||
weak_self = weakref.ref(self)
|
||||
|
||||
# normalize arg_types to a tuple, treating the string aliases as-is
|
||||
spec = tuple(arg_types) if arg_types else ()
|
||||
has_pointers = any(is_pointer(s) for s in spec)
|
||||
|
||||
def _forwarder(ql, *args):
|
||||
self_ref = weak_self()
|
||||
if self_ref is None:
|
||||
ql.log.error(f"kernel_proxy: {name}() called after proxy was destroyed")
|
||||
return -1
|
||||
|
||||
translated = self_ref._translate_args(name, args, spec)
|
||||
|
||||
if has_pointers:
|
||||
in_bufs, out_specs, out_arg_indices = self_ref._collect_buffers(
|
||||
ql, translated, spec
|
||||
)
|
||||
retval, out_data = client.syscall_ex(host_nr, translated, in_bufs, out_specs)
|
||||
self_ref._writeback_buffers(ql, args, out_arg_indices, out_data)
|
||||
else:
|
||||
retval = client.syscall(host_nr, translated)
|
||||
|
||||
if returns_fd and retval >= 0:
|
||||
proxy_fd_obj = ql_proxy_fd(client, retval)
|
||||
guest_fd = self_ref._alloc_fd(ql, proxy_fd_obj)
|
||||
ql.log.debug(f"kernel_proxy: {name}() -> proxy_fd={retval}, guest_fd={guest_fd}")
|
||||
return guest_fd
|
||||
|
||||
ql.log.debug(f"kernel_proxy: {name}({', '.join(f'{a:#x}' for a in args)}) = {retval}")
|
||||
return retval
|
||||
|
||||
_forwarder.__name__ = f'ql_syscall_{name}'
|
||||
return _forwarder
|
||||
|
||||
def _translate_args(self, name: str, args: Tuple[int, ...],
|
||||
spec: Tuple) -> Tuple[int, ...]:
|
||||
"""Translate guest FD args to proxy FD numbers; pad to 6 args.
|
||||
|
||||
Pointer args are left untouched here — _collect_buffers replaces them
|
||||
with the proxy-side buffer addresses just before invocation.
|
||||
"""
|
||||
out = list(args) + [0] * (6 - len(args))
|
||||
|
||||
for idx, kind in enumerate(spec):
|
||||
if kind == FD:
|
||||
guest_fd = args[idx]
|
||||
fd_obj = self.ql.os.fd[guest_fd] if 0 <= guest_fd < len(self.ql.os.fd) else None
|
||||
|
||||
if not isinstance(fd_obj, ql_proxy_fd):
|
||||
raise QlErrorSyscallError(
|
||||
f"kernel_proxy: {name}() arg{idx} guest_fd={guest_fd} "
|
||||
f"does not refer to a proxy-owned FD"
|
||||
)
|
||||
|
||||
out[idx] = fd_obj._proxy_fd
|
||||
|
||||
return tuple(out[:6])
|
||||
|
||||
def _collect_buffers(self, ql, args: Tuple[int, ...], spec: Tuple):
|
||||
"""Read PtrIn/PtrInOut buffers from guest memory; collect PtrOut sizes."""
|
||||
in_bufs = []
|
||||
out_specs = []
|
||||
out_arg_indices = []
|
||||
|
||||
for idx, kind in enumerate(spec):
|
||||
if isinstance(kind, (PtrIn, PtrInOut)):
|
||||
size = kind.resolve(args)
|
||||
if size > 0:
|
||||
data = bytes(ql.mem.read(args[idx], size))
|
||||
in_bufs.append((idx, data))
|
||||
|
||||
if isinstance(kind, (PtrOut, PtrInOut)):
|
||||
size = kind.resolve(args)
|
||||
if size > 0:
|
||||
out_specs.append((idx, size))
|
||||
out_arg_indices.append(idx)
|
||||
|
||||
return in_bufs, out_specs, out_arg_indices
|
||||
|
||||
@staticmethod
|
||||
def _writeback_buffers(ql, args: Tuple[int, ...],
|
||||
out_arg_indices: Sequence[int],
|
||||
out_data: Sequence[bytes]):
|
||||
"""Write PtrOut/PtrInOut response buffers back into guest memory."""
|
||||
for idx, data in zip(out_arg_indices, out_data):
|
||||
if data:
|
||||
ql.mem.write(args[idx], data)
|
||||
|
||||
def _get_host_syscall_nr(self, name: str) -> int:
|
||||
"""Get the syscall number on the HOST architecture."""
|
||||
# we are running on Linux — read from the host's syscall table
|
||||
if not hasattr(self, '_host_table'):
|
||||
self._host_table = self._load_host_syscall_table()
|
||||
|
||||
if name not in self._host_table:
|
||||
raise QlErrorSyscallNotFound(f"KernelProxy: syscall '{name}' not available on host")
|
||||
|
||||
return self._host_table[name]
|
||||
|
||||
def _load_host_syscall_table(self) -> Dict[str, int]:
|
||||
"""Load the host's syscall name->nr mapping.
|
||||
|
||||
Uses the same Qiling tables, indexed by the host architecture.
|
||||
"""
|
||||
import platform
|
||||
import qiling.os.linux.map_syscall as mod
|
||||
|
||||
machine = platform.machine()
|
||||
host_arch_map = {
|
||||
'x86_64': 'x8664_syscall_table',
|
||||
'aarch64': 'arm64_syscall_table',
|
||||
'armv7l': 'arm_syscall_table',
|
||||
'mips': 'mips_syscall_table',
|
||||
'riscv64': 'riscv64_syscall_table',
|
||||
'ppc': 'ppc_syscall_table',
|
||||
}
|
||||
|
||||
table_name = host_arch_map.get(machine)
|
||||
if table_name is None:
|
||||
raise QlErrorArch(f"KernelProxy: unsupported host architecture '{machine}'")
|
||||
|
||||
table = getattr(mod, table_name)
|
||||
return {name: nr for nr, name in table.items()}
|
||||
|
||||
@staticmethod
|
||||
def _alloc_fd(ql, fd_obj) -> int:
|
||||
"""Find next free slot in Qiling's FD table and store fd_obj."""
|
||||
for i in range(len(ql.os.fd)):
|
||||
if ql.os.fd[i] is None:
|
||||
ql.os.fd[i] = fd_obj
|
||||
return i
|
||||
|
||||
raise QlErrorSyscallError("kernel_proxy: FD table full")
|
||||
|
||||
def stop(self):
|
||||
"""Stop the proxy process."""
|
||||
if self._client:
|
||||
try:
|
||||
self._client.close()
|
||||
except Exception:
|
||||
pass
|
||||
self._client = None
|
||||
|
||||
if self._process:
|
||||
self._process.terminate()
|
||||
try:
|
||||
self._process.wait(timeout=5)
|
||||
except subprocess.TimeoutExpired:
|
||||
self._process.kill()
|
||||
self._process.wait()
|
||||
self.ql.log.info(f"kernel proxy stopped (pid={self._process.pid})")
|
||||
self._process = None
|
||||
|
||||
def __del__(self):
|
||||
if hasattr(self, '_client'):
|
||||
self.stop()
|
||||
@@ -0,0 +1,73 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
|
||||
#
|
||||
|
||||
"""
|
||||
Argument type descriptors for forwarded syscalls.
|
||||
|
||||
When forwarding a syscall whose arguments are not all plain integers, the user
|
||||
declares each argument's role so the forwarder knows how to marshal it:
|
||||
|
||||
INT — pass through unchanged (the default).
|
||||
FD — guest file descriptor; translate to the proxy-side FD before
|
||||
forwarding. If the FD does not refer to a ql_proxy_fd, an error
|
||||
is raised.
|
||||
PtrIn(s) — pointer to a buffer of `s` bytes. Read from guest memory and
|
||||
copied to the proxy.
|
||||
PtrOut(s) — pointer to a buffer of `s` bytes. Allocated on the proxy and
|
||||
copied back to guest memory after the syscall.
|
||||
PtrInOut — both directions.
|
||||
|
||||
`s` may be an integer or a callable taking the raw arg tuple and returning the
|
||||
buffer length (e.g. for syscalls where the size depends on another argument).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Callable, Tuple, Union
|
||||
|
||||
|
||||
INT = 'int'
|
||||
FD = 'fd'
|
||||
|
||||
SizeSpec = Union[int, Callable[[Tuple[int, ...]], int]]
|
||||
|
||||
|
||||
def _resolve_size(size: SizeSpec, args: Tuple[int, ...]) -> int:
|
||||
if callable(size):
|
||||
return int(size(args))
|
||||
|
||||
return int(size)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class PtrIn:
|
||||
"""Input pointer — buffer of `size` bytes is read from guest memory."""
|
||||
size: SizeSpec
|
||||
|
||||
def resolve(self, args: Tuple[int, ...]) -> int:
|
||||
return _resolve_size(self.size, args)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class PtrOut:
|
||||
"""Output pointer — buffer of `size` bytes is written to guest memory."""
|
||||
size: SizeSpec
|
||||
|
||||
def resolve(self, args: Tuple[int, ...]) -> int:
|
||||
return _resolve_size(self.size, args)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class PtrInOut:
|
||||
"""In/out pointer — buffer is read from guest, then written back."""
|
||||
size: SizeSpec
|
||||
|
||||
def resolve(self, args: Tuple[int, ...]) -> int:
|
||||
return _resolve_size(self.size, args)
|
||||
|
||||
|
||||
def is_pointer(spec) -> bool:
|
||||
return isinstance(spec, (PtrIn, PtrOut, PtrInOut))
|
||||
@@ -0,0 +1,271 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
|
||||
#
|
||||
|
||||
"""
|
||||
IPC protocol between Qiling and the kernel proxy process.
|
||||
|
||||
Three message types:
|
||||
SYSCALL — forward a raw syscall (number + 6 integer args, no buffers)
|
||||
SYSCALL_EX — forward a syscall with input/output buffer marshaling
|
||||
FD_OP — perform an operation on a proxy-side FD (read/write/close/dup/fcntl/ioctl)
|
||||
|
||||
All messages are length-prefixed binary over a Unix socketpair.
|
||||
"""
|
||||
|
||||
import struct
|
||||
import socket
|
||||
from enum import IntEnum
|
||||
from typing import List, Sequence, Tuple
|
||||
|
||||
from qiling.exception import QlProxyConnectionError
|
||||
|
||||
|
||||
class MsgType(IntEnum):
|
||||
SYSCALL = 1
|
||||
FD_OP = 2
|
||||
SYSCALL_EX = 3
|
||||
|
||||
|
||||
class FdOp(IntEnum):
|
||||
READ = 1
|
||||
WRITE = 2
|
||||
CLOSE = 3
|
||||
DUP = 4
|
||||
FCNTL = 5
|
||||
IOCTL = 6
|
||||
|
||||
|
||||
# Wire format:
|
||||
# Request header: [msg_type: u8][payload_len: u32]
|
||||
# SYSCALL payload: [syscall_nr: u32][args: 6 x i64]
|
||||
# SYSCALL_EX payload: [syscall_nr: u32][args: 6 x i64]
|
||||
# [num_in: u8] then num_in * [arg_idx: u8][len: u32][data: bytes]
|
||||
# [num_out: u8] then num_out * [arg_idx: u8][len: u32]
|
||||
# FD_OP payload: [op: u8][proxy_fd: i32][arg1: i64][arg2: i64][data_len: u32][data: bytes]
|
||||
#
|
||||
# Response header: [status: i8][payload_len: u32]
|
||||
# status 0 = success, -1 = error
|
||||
# SYSCALL response payload: [return_value: i64][errno: i32]
|
||||
# SYSCALL_EX response payload: [return_value: i64][errno: i32][num_out: u8]
|
||||
# then num_out * [len: u32][data: bytes]
|
||||
# FD_OP response payload: [return_value: i64][errno: i32][data_len: u32][data: bytes]
|
||||
|
||||
HEADER_FMT = '!BI' # msg_type/status (u8) + payload_len (u32)
|
||||
HEADER_SIZE = struct.calcsize(HEADER_FMT)
|
||||
|
||||
SYSCALL_REQ_FMT = '!I6q' # syscall_nr (u32) + 6 args (i64)
|
||||
SYSCALL_REQ_SIZE = struct.calcsize(SYSCALL_REQ_FMT)
|
||||
|
||||
SYSCALL_RESP_FMT = '!qi' # return_value (i64) + errno (i32)
|
||||
SYSCALL_RESP_SIZE = struct.calcsize(SYSCALL_RESP_FMT)
|
||||
|
||||
SYSCALL_EX_RESP_HEAD_FMT = '!qiB' # return_value (i64) + errno (i32) + num_out (u8)
|
||||
SYSCALL_EX_RESP_HEAD_SIZE = struct.calcsize(SYSCALL_EX_RESP_HEAD_FMT)
|
||||
|
||||
FD_OP_REQ_FMT = '!BiqqI' # op (u8) + proxy_fd (i32) + arg1 (i64) + arg2 (i64) + data_len (u32)
|
||||
FD_OP_REQ_SIZE = struct.calcsize(FD_OP_REQ_FMT)
|
||||
|
||||
FD_OP_RESP_FMT = '!qiI' # return_value (i64) + errno (i32) + data_len (u32)
|
||||
FD_OP_RESP_SIZE = struct.calcsize(FD_OP_RESP_FMT)
|
||||
|
||||
|
||||
def _recvall(sock: socket.socket, n: int) -> bytes:
|
||||
"""Receive exactly n bytes from a socket."""
|
||||
buf = bytearray()
|
||||
while len(buf) < n:
|
||||
chunk = sock.recv(n - len(buf))
|
||||
if not chunk:
|
||||
raise QlProxyConnectionError("kernel proxy connection closed")
|
||||
buf.extend(chunk)
|
||||
return bytes(buf)
|
||||
|
||||
|
||||
class ProxyClient:
|
||||
"""Qiling-side IPC client — sends requests to the proxy process."""
|
||||
|
||||
def __init__(self, sock: socket.socket):
|
||||
self._sock = sock
|
||||
|
||||
def syscall(self, nr: int, args: tuple) -> int:
|
||||
"""Forward a raw syscall. Returns kernel-convention result (negative errno on error)."""
|
||||
padded = tuple(args) + (0,) * (6 - len(args))
|
||||
payload = struct.pack(SYSCALL_REQ_FMT, nr, *padded[:6])
|
||||
|
||||
# send request
|
||||
header = struct.pack(HEADER_FMT, MsgType.SYSCALL, len(payload))
|
||||
self._sock.sendall(header + payload)
|
||||
|
||||
# recv response
|
||||
resp_header = _recvall(self._sock, HEADER_SIZE)
|
||||
_, resp_len = struct.unpack(HEADER_FMT, resp_header)
|
||||
resp_payload = _recvall(self._sock, resp_len)
|
||||
|
||||
retval, errno_val = struct.unpack(SYSCALL_RESP_FMT, resp_payload)
|
||||
return retval
|
||||
|
||||
def syscall_ex(self, nr: int, args: Sequence[int],
|
||||
in_bufs: Sequence[Tuple[int, bytes]],
|
||||
out_specs: Sequence[Tuple[int, int]]) -> Tuple[int, List[bytes]]:
|
||||
"""Forward a syscall with buffer marshaling.
|
||||
|
||||
Args:
|
||||
nr: host syscall number.
|
||||
args: 6 integer arg values; for buffer args these are placeholders —
|
||||
the proxy replaces them with the buffer address before invoking.
|
||||
in_bufs: list of (arg_idx, data) — buffers to copy in.
|
||||
out_specs: list of (arg_idx, length) — buffers to copy out.
|
||||
|
||||
Returns:
|
||||
(retval, out_bufs) where out_bufs is a list aligned with out_specs.
|
||||
"""
|
||||
padded = tuple(args) + (0,) * (6 - len(args))
|
||||
payload = bytearray(struct.pack(SYSCALL_REQ_FMT, nr, *padded[:6]))
|
||||
|
||||
payload.append(len(in_bufs))
|
||||
for arg_idx, data in in_bufs:
|
||||
payload += struct.pack('!BI', arg_idx, len(data))
|
||||
payload += data
|
||||
|
||||
payload.append(len(out_specs))
|
||||
for arg_idx, length in out_specs:
|
||||
payload += struct.pack('!BI', arg_idx, length)
|
||||
|
||||
header = struct.pack(HEADER_FMT, MsgType.SYSCALL_EX, len(payload))
|
||||
self._sock.sendall(header + bytes(payload))
|
||||
|
||||
resp_header = _recvall(self._sock, HEADER_SIZE)
|
||||
_, resp_len = struct.unpack(HEADER_FMT, resp_header)
|
||||
resp_payload = _recvall(self._sock, resp_len)
|
||||
|
||||
retval, _errno, num_out = struct.unpack(
|
||||
SYSCALL_EX_RESP_HEAD_FMT, resp_payload[:SYSCALL_EX_RESP_HEAD_SIZE]
|
||||
)
|
||||
|
||||
out_bufs: List[bytes] = []
|
||||
offset = SYSCALL_EX_RESP_HEAD_SIZE
|
||||
for _ in range(num_out):
|
||||
(length,) = struct.unpack('!I', resp_payload[offset:offset + 4])
|
||||
offset += 4
|
||||
out_bufs.append(resp_payload[offset:offset + length])
|
||||
offset += length
|
||||
|
||||
return retval, out_bufs
|
||||
|
||||
def _fd_op(self, op: FdOp, proxy_fd: int, arg1: int = 0, arg2: int = 0, data: bytes = b'') -> tuple:
|
||||
"""Send an FD operation. Returns (return_value, data)."""
|
||||
payload = struct.pack(FD_OP_REQ_FMT, op, proxy_fd, arg1, arg2, len(data))
|
||||
payload += data
|
||||
|
||||
header = struct.pack(HEADER_FMT, MsgType.FD_OP, len(payload))
|
||||
self._sock.sendall(header + payload)
|
||||
|
||||
resp_header = _recvall(self._sock, HEADER_SIZE)
|
||||
_, resp_len = struct.unpack(HEADER_FMT, resp_header)
|
||||
resp_payload = _recvall(self._sock, resp_len)
|
||||
|
||||
retval, errno_val, data_len = struct.unpack(FD_OP_RESP_FMT, resp_payload[:FD_OP_RESP_SIZE])
|
||||
resp_data = resp_payload[FD_OP_RESP_SIZE:FD_OP_RESP_SIZE + data_len]
|
||||
|
||||
return retval, resp_data
|
||||
|
||||
def fd_read(self, proxy_fd: int, length: int) -> bytes:
|
||||
retval, data = self._fd_op(FdOp.READ, proxy_fd, arg1=length)
|
||||
if retval < 0:
|
||||
return b''
|
||||
return data
|
||||
|
||||
def fd_write(self, proxy_fd: int, data: bytes) -> int:
|
||||
retval, _ = self._fd_op(FdOp.WRITE, proxy_fd, data=data)
|
||||
return retval
|
||||
|
||||
def fd_close(self, proxy_fd: int) -> None:
|
||||
self._fd_op(FdOp.CLOSE, proxy_fd)
|
||||
|
||||
def fd_dup(self, proxy_fd: int) -> int:
|
||||
retval, _ = self._fd_op(FdOp.DUP, proxy_fd)
|
||||
return retval
|
||||
|
||||
def fd_fcntl(self, proxy_fd: int, cmd: int, arg: int) -> int:
|
||||
retval, _ = self._fd_op(FdOp.FCNTL, proxy_fd, arg1=cmd, arg2=arg)
|
||||
return retval
|
||||
|
||||
def fd_ioctl(self, proxy_fd: int, cmd: int, arg: int) -> int:
|
||||
retval, _ = self._fd_op(FdOp.IOCTL, proxy_fd, arg1=cmd, arg2=arg)
|
||||
return retval
|
||||
|
||||
def close(self):
|
||||
self._sock.close()
|
||||
|
||||
|
||||
class ProxyServer:
|
||||
"""Proxy-side IPC server — receives requests, executes real syscalls."""
|
||||
|
||||
def __init__(self, sock: socket.socket):
|
||||
self._sock = sock
|
||||
|
||||
def recv_request(self) -> tuple:
|
||||
"""Receive one request. Returns (msg_type, parsed_fields)."""
|
||||
header = _recvall(self._sock, HEADER_SIZE)
|
||||
msg_type, payload_len = struct.unpack(HEADER_FMT, header)
|
||||
|
||||
payload = _recvall(self._sock, payload_len)
|
||||
|
||||
if msg_type == MsgType.SYSCALL:
|
||||
fields = struct.unpack(SYSCALL_REQ_FMT, payload)
|
||||
return MsgType.SYSCALL, fields # (nr, a0, a1, a2, a3, a4, a5)
|
||||
|
||||
elif msg_type == MsgType.SYSCALL_EX:
|
||||
offset = SYSCALL_REQ_SIZE
|
||||
fixed = struct.unpack(SYSCALL_REQ_FMT, payload[:offset])
|
||||
nr = fixed[0]
|
||||
args = list(fixed[1:])
|
||||
|
||||
num_in = payload[offset]
|
||||
offset += 1
|
||||
in_bufs: List[Tuple[int, bytes]] = []
|
||||
for _ in range(num_in):
|
||||
arg_idx, length = struct.unpack('!BI', payload[offset:offset + 5])
|
||||
offset += 5
|
||||
in_bufs.append((arg_idx, payload[offset:offset + length]))
|
||||
offset += length
|
||||
|
||||
num_out = payload[offset]
|
||||
offset += 1
|
||||
out_specs: List[Tuple[int, int]] = []
|
||||
for _ in range(num_out):
|
||||
arg_idx, length = struct.unpack('!BI', payload[offset:offset + 5])
|
||||
offset += 5
|
||||
out_specs.append((arg_idx, length))
|
||||
|
||||
return MsgType.SYSCALL_EX, (nr, args, in_bufs, out_specs)
|
||||
|
||||
elif msg_type == MsgType.FD_OP:
|
||||
fixed = struct.unpack(FD_OP_REQ_FMT, payload[:FD_OP_REQ_SIZE])
|
||||
op, proxy_fd, arg1, arg2, data_len = fixed
|
||||
data = payload[FD_OP_REQ_SIZE:FD_OP_REQ_SIZE + data_len]
|
||||
return MsgType.FD_OP, (FdOp(op), proxy_fd, arg1, arg2, data)
|
||||
|
||||
else:
|
||||
raise QlProxyConnectionError(f"unknown message type: {msg_type}")
|
||||
|
||||
def send_syscall_response(self, retval: int, errno_val: int):
|
||||
payload = struct.pack(SYSCALL_RESP_FMT, retval, errno_val)
|
||||
header = struct.pack(HEADER_FMT, 0, len(payload))
|
||||
self._sock.sendall(header + payload)
|
||||
|
||||
def send_syscall_ex_response(self, retval: int, errno_val: int, out_bufs: Sequence[bytes]):
|
||||
payload = bytearray(struct.pack(SYSCALL_EX_RESP_HEAD_FMT, retval, errno_val, len(out_bufs)))
|
||||
for buf in out_bufs:
|
||||
payload += struct.pack('!I', len(buf))
|
||||
payload += buf
|
||||
|
||||
header = struct.pack(HEADER_FMT, 0, len(payload))
|
||||
self._sock.sendall(header + bytes(payload))
|
||||
|
||||
def send_fd_op_response(self, retval: int, errno_val: int, data: bytes = b''):
|
||||
payload = struct.pack(FD_OP_RESP_FMT, retval, errno_val, len(data))
|
||||
payload += data
|
||||
header = struct.pack(HEADER_FMT, 0, len(payload))
|
||||
self._sock.sendall(header + payload)
|
||||
@@ -0,0 +1,154 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
|
||||
#
|
||||
|
||||
"""
|
||||
Kernel proxy process — executes real Linux syscalls on behalf of Qiling.
|
||||
|
||||
This runs as a subprocess. It receives syscall requests over a Unix socketpair,
|
||||
executes them via libc.syscall(), and sends back results.
|
||||
|
||||
Usage (internal — started by KernelProxy.__init__):
|
||||
python -m qiling.os.posix.kernel_proxy.proxy <socket_fd>
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
import ctypes
|
||||
import ctypes.util
|
||||
import errno as errno_module
|
||||
import socket
|
||||
|
||||
from qiling.os.posix.kernel_proxy.ipc import (
|
||||
ProxyServer, MsgType, FdOp
|
||||
)
|
||||
|
||||
log = logging.getLogger("qiling.os.posix.kernel_proxy.proxy")
|
||||
|
||||
# load libc for raw syscall()
|
||||
_libc_path = ctypes.util.find_library("c")
|
||||
if _libc_path is None:
|
||||
log.critical("kernel_proxy: cannot find libc")
|
||||
sys.exit(1)
|
||||
|
||||
_libc = ctypes.CDLL(_libc_path, use_errno=True)
|
||||
_libc.syscall.restype = ctypes.c_long
|
||||
_libc.syscall.argtypes = [ctypes.c_long] + [ctypes.c_long] * 6
|
||||
|
||||
|
||||
def raw_syscall(nr: int, a0: int, a1: int, a2: int, a3: int, a4: int, a5: int) -> tuple:
|
||||
"""Execute a real Linux syscall. Returns (return_value, errno) in kernel convention."""
|
||||
ctypes.set_errno(0)
|
||||
result = _libc.syscall(nr, a0, a1, a2, a3, a4, a5)
|
||||
|
||||
if result == -1:
|
||||
err = ctypes.get_errno()
|
||||
if err != 0:
|
||||
return -err, err # kernel convention: negative errno
|
||||
|
||||
return result, 0
|
||||
|
||||
|
||||
def raw_syscall_ex(nr: int, args: list, in_bufs: list, out_specs: list) -> tuple:
|
||||
"""Execute a syscall with buffer marshaling.
|
||||
|
||||
For each in_buf (arg_idx, data): allocate a ctypes buffer initialized with
|
||||
data and place its address in args[arg_idx]. For each out_spec (arg_idx, length):
|
||||
allocate a zeroed buffer and place its address in args[arg_idx]. After the
|
||||
syscall, return the contents of each out buffer.
|
||||
"""
|
||||
keepalive = [] # keep ctypes buffers alive until after the syscall
|
||||
out_buffers = [] # parallel to out_specs
|
||||
|
||||
args = list(args)
|
||||
|
||||
for arg_idx, data in in_bufs:
|
||||
buf = ctypes.create_string_buffer(data, len(data))
|
||||
keepalive.append(buf)
|
||||
args[arg_idx] = ctypes.addressof(buf)
|
||||
|
||||
for arg_idx, length in out_specs:
|
||||
buf = ctypes.create_string_buffer(length)
|
||||
keepalive.append(buf)
|
||||
out_buffers.append(buf)
|
||||
args[arg_idx] = ctypes.addressof(buf)
|
||||
|
||||
retval, err = raw_syscall(nr, *args)
|
||||
|
||||
out_data = [bytes(buf.raw) for buf in out_buffers]
|
||||
return retval, err, out_data
|
||||
|
||||
|
||||
def handle_fd_op(op: FdOp, proxy_fd: int, arg1: int, arg2: int, data: bytes) -> tuple:
|
||||
"""Handle an FD operation on a proxy-side FD. Returns (retval, errno, data)."""
|
||||
try:
|
||||
if op == FdOp.READ:
|
||||
result = os.read(proxy_fd, arg1)
|
||||
return len(result), 0, result
|
||||
|
||||
elif op == FdOp.WRITE:
|
||||
written = os.write(proxy_fd, data)
|
||||
return written, 0, b''
|
||||
|
||||
elif op == FdOp.CLOSE:
|
||||
os.close(proxy_fd)
|
||||
return 0, 0, b''
|
||||
|
||||
elif op == FdOp.DUP:
|
||||
new_fd = os.dup(proxy_fd)
|
||||
return new_fd, 0, b''
|
||||
|
||||
elif op == FdOp.FCNTL:
|
||||
import fcntl
|
||||
result = fcntl.fcntl(proxy_fd, arg1, arg2)
|
||||
return result, 0, b''
|
||||
|
||||
elif op == FdOp.IOCTL:
|
||||
import fcntl
|
||||
result = fcntl.ioctl(proxy_fd, arg1, arg2)
|
||||
return result, 0, b''
|
||||
|
||||
else:
|
||||
return -errno_module.ENOSYS, errno_module.ENOSYS, b''
|
||||
|
||||
except OSError as e:
|
||||
return -e.errno, e.errno, b''
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 2:
|
||||
log.error(f"usage: {sys.argv[0]} <socket_fd>")
|
||||
sys.exit(1)
|
||||
|
||||
sock_fd = int(sys.argv[1])
|
||||
sock = socket.socket(fileno=sock_fd)
|
||||
server = ProxyServer(sock)
|
||||
|
||||
while True:
|
||||
try:
|
||||
msg_type, fields = server.recv_request()
|
||||
except ConnectionError:
|
||||
break
|
||||
|
||||
if msg_type == MsgType.SYSCALL:
|
||||
nr, a0, a1, a2, a3, a4, a5 = fields
|
||||
retval, err = raw_syscall(nr, a0, a1, a2, a3, a4, a5)
|
||||
server.send_syscall_response(retval, err)
|
||||
|
||||
elif msg_type == MsgType.SYSCALL_EX:
|
||||
nr, args, in_bufs, out_specs = fields
|
||||
retval, err, out_bufs = raw_syscall_ex(nr, args, in_bufs, out_specs)
|
||||
server.send_syscall_ex_response(retval, err, out_bufs)
|
||||
|
||||
elif msg_type == MsgType.FD_OP:
|
||||
op, proxy_fd, arg1, arg2, data = fields
|
||||
retval, err, resp_data = handle_fd_op(op, proxy_fd, arg1, arg2, data)
|
||||
server.send_fd_op_response(retval, err, resp_data)
|
||||
|
||||
sock.close()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
|
||||
#
|
||||
|
||||
"""
|
||||
Proxy file descriptor — wraps an FD that lives in the kernel proxy process.
|
||||
|
||||
When a forwarded syscall returns an FD (e.g. epoll_create, eventfd), the real
|
||||
FD lives in the proxy. This wrapper forwards read/write/close to the proxy via
|
||||
IPC, matching the interface of ql_socket and ql_pipe so existing syscall handlers
|
||||
(ql_syscall_read, ql_syscall_write, ql_syscall_close) work without modification.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from qiling.os.posix.kernel_proxy.ipc import ProxyClient
|
||||
|
||||
|
||||
class ql_proxy_fd:
|
||||
def __init__(self, client: ProxyClient, proxy_fd: int):
|
||||
self._client = client
|
||||
self._proxy_fd = proxy_fd
|
||||
|
||||
def read(self, length: int) -> bytes:
|
||||
return self._client.fd_read(self._proxy_fd, length)
|
||||
|
||||
def write(self, data: bytes) -> int:
|
||||
return self._client.fd_write(self._proxy_fd, data)
|
||||
|
||||
def close(self) -> None:
|
||||
self._client.fd_close(self._proxy_fd)
|
||||
|
||||
def fileno(self) -> int:
|
||||
return -1
|
||||
|
||||
def dup(self) -> ql_proxy_fd:
|
||||
new_proxy_fd = self._client.fd_dup(self._proxy_fd)
|
||||
return ql_proxy_fd(self._client, new_proxy_fd)
|
||||
|
||||
def fcntl(self, cmd, arg):
|
||||
return self._client.fd_fcntl(self._proxy_fd, cmd, arg)
|
||||
|
||||
def ioctl(self, cmd, arg):
|
||||
return self._client.fd_ioctl(self._proxy_fd, cmd, arg)
|
||||
@@ -14,6 +14,7 @@ from .prctl import *
|
||||
from .ptrace import *
|
||||
from .random import *
|
||||
from .resource import *
|
||||
from .rseq import *
|
||||
from .sched import *
|
||||
from .select import *
|
||||
from .sendfile import *
|
||||
@@ -30,3 +31,4 @@ from .uio import *
|
||||
from .unistd import *
|
||||
from .utsname import *
|
||||
from .wait import *
|
||||
from .epoll import *
|
||||
@@ -0,0 +1,294 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import ctypes
|
||||
import select
|
||||
|
||||
from typing import TYPE_CHECKING, Dict, KeysView, NamedTuple
|
||||
|
||||
from qiling.os import struct
|
||||
from qiling.os.posix.const import *
|
||||
from qiling.os.filestruct import PersistentQlFile, ql_file
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from qiling import Qiling
|
||||
from qiling.arch.arch import QlArch
|
||||
from qiling.os.posix.posix import QlFileDes
|
||||
|
||||
|
||||
class QlEpollEntry(NamedTuple):
|
||||
"""A named tuple to represent an epoll entry.
|
||||
|
||||
This is used to store the events mask and the data for each entry in
|
||||
the epoll instance.
|
||||
"""
|
||||
|
||||
events: int
|
||||
data: int
|
||||
|
||||
|
||||
@struct.cache
|
||||
def __make_epoll_event(arch: QlArch):
|
||||
"""Create a structure to represent an epoll event.
|
||||
"""
|
||||
|
||||
Struct = struct.get_packed_struct(arch.endian)
|
||||
|
||||
class epoll_event(Struct):
|
||||
_fields_ = (
|
||||
('events', ctypes.c_uint32),
|
||||
('data', ctypes.c_uint64)
|
||||
)
|
||||
|
||||
return epoll_event
|
||||
|
||||
|
||||
class QlEpollObj:
|
||||
def __init__(self, epoll_object: select.epoll):
|
||||
self._epoll_object = epoll_object
|
||||
|
||||
# maps fd to eventmask
|
||||
# keep track of which fds have what eventmasks,
|
||||
# since this isn't directly supported in select.epoll
|
||||
self._fds: Dict[int, QlEpollEntry] = {}
|
||||
|
||||
@property
|
||||
def fds(self) -> KeysView[int]:
|
||||
return self._fds.keys()
|
||||
|
||||
@property
|
||||
def epoll_instance(self) -> select.epoll:
|
||||
return self._epoll_object
|
||||
|
||||
def close(self) -> None:
|
||||
self._epoll_object.close()
|
||||
|
||||
def __getitem__(self, fd: int) -> QlEpollEntry:
|
||||
return self._fds[fd]
|
||||
|
||||
def __setitem__(self, fd: int, entry: QlEpollEntry) -> None:
|
||||
# if fd is already being watched, modify its eventmask.
|
||||
if fd in self:
|
||||
self._epoll_object.modify(fd, entry.events)
|
||||
|
||||
# otherwise, register it with the epoll object
|
||||
else:
|
||||
self._epoll_object.register(fd, entry.events)
|
||||
|
||||
self._fds[fd] = entry
|
||||
|
||||
def __delitem__(self, fd: int) -> None:
|
||||
"""Remove an fd from the epoll instance.
|
||||
"""
|
||||
|
||||
self._fds.pop(fd)
|
||||
self._epoll_object.unregister(fd)
|
||||
|
||||
def __contains__(self, fd: int) -> bool:
|
||||
"""Test whether a specific fd is already being watched by this epoll instance.
|
||||
"""
|
||||
|
||||
return fd in self.fds
|
||||
|
||||
|
||||
def check_epoll_depth(ql_fd_list: QlFileDes) -> None:
|
||||
"""Recursively check each epoll instance's 'watched' fds for an instance of
|
||||
epoll being watched. If a chain of over 5 levels is detected, raise an exception
|
||||
"""
|
||||
|
||||
def __visit_obj(obj: QlEpollObj, depth: int):
|
||||
if depth >= 5:
|
||||
raise RecursionError
|
||||
|
||||
for fd in obj.fds:
|
||||
if isinstance(ql_fd_list[fd], QlEpollObj):
|
||||
__visit_obj(obj, depth + 1)
|
||||
|
||||
for obj in ql_fd_list:
|
||||
if isinstance(obj, QlEpollObj):
|
||||
__visit_obj(obj, 1)
|
||||
|
||||
|
||||
|
||||
def ql_syscall_epoll_ctl(ql: Qiling, epfd: int, op: int, fd: int, event: int):
|
||||
"""Modify an existing epoll.
|
||||
"""
|
||||
|
||||
# not clear from man page, but to be safe don't support 'undefined' ops.
|
||||
if op not in (EPOLL_CTL_ADD, EPOLL_CTL_DEL, EPOLL_CTL_MOD):
|
||||
return -EINVAL
|
||||
|
||||
if epfd == fd:
|
||||
return -EINVAL
|
||||
|
||||
if epfd not in range(NR_OPEN):
|
||||
return -EBADF
|
||||
|
||||
epoll_parent_obj = ql.os.fd[epfd]
|
||||
|
||||
if not isinstance(epoll_parent_obj, QlEpollObj):
|
||||
return -EINVAL
|
||||
|
||||
epoll_obj = epoll_parent_obj.epoll_instance
|
||||
|
||||
if epoll_obj is None:
|
||||
return -EBADF
|
||||
|
||||
if epoll_obj.fileno() == fd:
|
||||
return -ELOOP
|
||||
|
||||
# Qiling doesn't check process capabilities right now, so this case isn't explicitly handled yet
|
||||
# EPOLLWAKEUP (since Linux 3.5)
|
||||
# If EPOLLONESHOT and EPOLLET are clear and the process has the CAP_BLOCK_SUSPEND capability
|
||||
|
||||
fd_obj = ql.os.fd[fd]
|
||||
|
||||
if fd_obj is None:
|
||||
return -EBADF
|
||||
|
||||
# TODO: not sure if qiling supports a way to determine if the target file descriptor is a
|
||||
# directory. Here, check against PersistentQlFile is to ensure that polling stdin, stdout,
|
||||
# stderr is supported
|
||||
|
||||
# The target file fd does not support epoll. This error can occur if fd refers to, for
|
||||
# example, a regular file or a directory.
|
||||
if isinstance(fd_obj, ql_file) and not isinstance(fd_obj, PersistentQlFile):
|
||||
return -EPERM
|
||||
|
||||
|
||||
|
||||
# EPOLLEXCLUSIVE was specified in event and fd refers to an epoll instance
|
||||
if isinstance(fd_obj, QlEpollObj) and (op & EPOLLEXCLUSIVE):
|
||||
return -EINVAL
|
||||
|
||||
# Necessary to iterate over all possible qiling fds to determine if we have a chain of more
|
||||
# than five epolls monitoring each other This may be removed in the future if the QlOsLinux
|
||||
# class had a separate field for reserved for tracking epoll objects.
|
||||
epolls_list = [fobj for fobj in ql.os.fd if isinstance(fobj, QlEpollObj)]
|
||||
|
||||
try:
|
||||
check_epoll_depth(ql.os.fd)
|
||||
except RecursionError:
|
||||
return -ELOOP
|
||||
|
||||
if op == EPOLL_CTL_ADD:
|
||||
# can't add an fd that's already being waited on
|
||||
if fd in epoll_parent_obj:
|
||||
return -EEXIST
|
||||
|
||||
if not event:
|
||||
return -EINVAL
|
||||
|
||||
# dereference the event pointer to get structure fields
|
||||
epoll_event_cls = __make_epoll_event(ql.arch)
|
||||
epoll_event = epoll_event_cls.load_from(ql.mem, event)
|
||||
|
||||
# EPOLLEXCLUSIVE was specified in event and fd refers to an epoll instance
|
||||
if isinstance(fd_obj, QlEpollObj) and (epoll_event.events & EPOLLEXCLUSIVE):
|
||||
return -EINVAL
|
||||
|
||||
epoll_parent_obj[fd] = QlEpollEntry(
|
||||
epoll_event.events,
|
||||
epoll_event.data
|
||||
)
|
||||
|
||||
elif op == EPOLL_CTL_DEL:
|
||||
if fd not in epoll_parent_obj:
|
||||
return -ENOENT
|
||||
|
||||
# remove from fds list and do so in the underlying epoll instance
|
||||
del epoll_parent_obj[fd]
|
||||
|
||||
elif op == EPOLL_CTL_MOD:
|
||||
if fd not in epoll_parent_obj:
|
||||
return -ENOENT
|
||||
|
||||
if not event:
|
||||
return -EINVAL
|
||||
|
||||
# dereference the event pointer to get structure fields
|
||||
epoll_event_cls = __make_epoll_event(ql.arch)
|
||||
epoll_event = epoll_event_cls.load_from(ql.mem, event)
|
||||
|
||||
# EPOLLEXCLUSIVE cannot be set on MOD operation, only on ADD
|
||||
if epoll_event.events & EPOLLEXCLUSIVE:
|
||||
return -EINVAL
|
||||
|
||||
epoll_parent_obj[fd] = QlEpollEntry(
|
||||
epoll_event.events,
|
||||
epoll_event.data
|
||||
)
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
def ql_syscall_epoll_wait(ql: Qiling, epfd: int, epoll_events: int, maxevents: int, timeout: int):
|
||||
"""Wait on an existing epoll for specific events.
|
||||
"""
|
||||
|
||||
if maxevents <= 0:
|
||||
return -EINVAL
|
||||
|
||||
# default value is 0xffffffff, but this fails when passing to epoll.poll()
|
||||
if timeout == 0xFFFFFFFF:
|
||||
timeout = None
|
||||
|
||||
if epfd not in range(NR_OPEN):
|
||||
return -EBADF
|
||||
|
||||
epoll_parent_obj = ql.os.fd[epfd]
|
||||
|
||||
if not isinstance(epoll_parent_obj, QlEpollObj):
|
||||
return -EINVAL
|
||||
|
||||
epoll_obj = epoll_parent_obj.epoll_instance
|
||||
|
||||
if epoll_obj is None:
|
||||
return -EBADF
|
||||
|
||||
ready_fds = epoll_obj.poll(timeout, maxevents)
|
||||
|
||||
epoll_event_cls = __make_epoll_event(ql.arch)
|
||||
|
||||
# Each tuple in ready_fds consists of (file descriptor, eventmask) so we iterate
|
||||
# through these to indicate which fds are ready and 'why'
|
||||
#
|
||||
# FIXME: emulated system fds are not the same as hosted system fds
|
||||
for i, (fd, events) in enumerate(ready_fds):
|
||||
entry = epoll_parent_obj[fd]
|
||||
epoll_event = epoll_event_cls(events, entry.data)
|
||||
|
||||
offset = epoll_event_cls.sizeof() * i
|
||||
ql.mem.write(epoll_events + offset, bytes(epoll_event))
|
||||
|
||||
# if no longer interested in this fd, remove from list
|
||||
if events & EPOLLONESHOT:
|
||||
del epoll_parent_obj[fd]
|
||||
|
||||
return len(ready_fds)
|
||||
|
||||
|
||||
def __epoll_create(ql: Qiling, sizehint: int, flags: int) -> int:
|
||||
# Use select.epoll for underlying implementation, just as select.poll is
|
||||
# used for emulating poll()
|
||||
|
||||
ret = select.epoll(sizehint, flags)
|
||||
|
||||
fd = ret.fileno()
|
||||
ql.os.fd[fd] = QlEpollObj(ret)
|
||||
|
||||
return fd
|
||||
|
||||
|
||||
def ql_syscall_epoll_create1(ql: Qiling, flags: int):
|
||||
if flags != select.EPOLL_CLOEXEC and flags != 0:
|
||||
return -EINVAL
|
||||
|
||||
return __epoll_create(ql, -1, flags)
|
||||
|
||||
|
||||
def ql_syscall_epoll_create(ql: Qiling, size: int):
|
||||
if size < 0:
|
||||
return -EINVAL
|
||||
|
||||
return __epoll_create(ql, size, 0)
|
||||
@@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
|
||||
#
|
||||
|
||||
from qiling import Qiling
|
||||
|
||||
|
||||
def ql_syscall_rseq(ql: Qiling, rseq: int, rseq_len: int, flags: int, sig: int):
|
||||
# indicate rseq is not supported by this kernel
|
||||
# return -ENOSYS
|
||||
|
||||
return 0
|
||||
@@ -415,6 +415,8 @@ def ql_syscall_read(ql: Qiling, fd: int, buf: int, length: int):
|
||||
|
||||
try:
|
||||
data = f.read(length)
|
||||
except IsADirectoryError:
|
||||
return -EISDIR
|
||||
except ConnectionError:
|
||||
ql.log.debug('read failed due to a connection error')
|
||||
return -EIO
|
||||
|
||||
@@ -222,6 +222,26 @@ class BaseStructEB(BaseStruct, ctypes.BigEndianStructure):
|
||||
pass
|
||||
|
||||
|
||||
@cache
|
||||
def get_packed_struct(endian: QL_ENDIAN = QL_ENDIAN.EL) -> Type[BaseStruct]:
|
||||
"""Provide a packed version of BaseStruct based on the emulated
|
||||
architecture endianess.
|
||||
|
||||
Args:
|
||||
archbits: required alignment in bits
|
||||
"""
|
||||
|
||||
Struct = {
|
||||
QL_ENDIAN.EL: BaseStructEL,
|
||||
QL_ENDIAN.EB: BaseStructEB
|
||||
}[endian]
|
||||
|
||||
class PackedStruct(Struct):
|
||||
_pack_ = 1
|
||||
|
||||
return PackedStruct
|
||||
|
||||
|
||||
@cache
|
||||
def get_aligned_struct(archbits: int, endian: QL_ENDIAN = QL_ENDIAN.EL) -> Type[BaseStruct]:
|
||||
"""Provide an aligned version of BaseStruct based on the emulated
|
||||
@@ -229,6 +249,7 @@ def get_aligned_struct(archbits: int, endian: QL_ENDIAN = QL_ENDIAN.EL) -> Type[
|
||||
|
||||
Args:
|
||||
archbits: required alignment in bits
|
||||
endian: required endianness
|
||||
"""
|
||||
|
||||
Struct = {
|
||||
|
||||
@@ -10,6 +10,10 @@ from .ProcessorBind import *
|
||||
from .UefiBaseType import *
|
||||
from .UefiMultiPhase import *
|
||||
|
||||
from .protocols.EfiSimpleTextInProtocol import EFI_SIMPLE_TEXT_INPUT_PROTOCOL
|
||||
from .protocols.EfiSimpleTextOutProtocol import EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL
|
||||
|
||||
|
||||
# definitions for EFI_TIME.Daylight
|
||||
EFI_TIME_ADJUST_DAYLIGHT = (1 << 1)
|
||||
EFI_TIME_IN_DAYLIGHT = (1 << 2)
|
||||
@@ -223,14 +227,6 @@ class EFI_CONFIGURATION_TABLE(STRUCT):
|
||||
('VendorTable', PTR(VOID)),
|
||||
]
|
||||
|
||||
# TODO: to be implemented
|
||||
# @see: MdePkg\Include\Protocol\SimpleTextIn.h
|
||||
EFI_SIMPLE_TEXT_INPUT_PROTOCOL = STRUCT
|
||||
|
||||
# TODO: to be implemented
|
||||
# @see: MdePkg\Include\Protocol\SimpleTextOut.h
|
||||
EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL = STRUCT
|
||||
|
||||
class EFI_SYSTEM_TABLE(STRUCT):
|
||||
_pack_ = 8
|
||||
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
|
||||
#
|
||||
|
||||
from typing import Any, Mapping
|
||||
from typing import Any, Mapping, Optional
|
||||
|
||||
from qiling import Qiling
|
||||
from qiling.const import QL_INTERCEPT
|
||||
|
||||
def dxeapi(params: Mapping[str, Any] = {}):
|
||||
def dxeapi(params: Optional[Mapping[str, Any]] = None, passthru: bool = False):
|
||||
def decorator(func):
|
||||
def wrapper(ql: Qiling):
|
||||
pc = ql.arch.regs.arch_pc
|
||||
@@ -18,7 +18,7 @@ def dxeapi(params: Mapping[str, Any] = {}):
|
||||
onenter = ql.os.user_defined_api[QL_INTERCEPT.ENTER].get(fname)
|
||||
onexit = ql.os.user_defined_api[QL_INTERCEPT.EXIT].get(fname)
|
||||
|
||||
return ql.os.call(pc, f, params, onenter, onexit)
|
||||
return ql.os.call(pc, f, params or {}, onenter, onexit, passthru)
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
|
||||
#
|
||||
|
||||
from qiling.os.const import *
|
||||
from qiling.os.uefi.fncc import dxeapi
|
||||
from qiling.os.uefi.utils import *
|
||||
from qiling.os.uefi.ProcessorBind import *
|
||||
from qiling.os.uefi.UefiBaseType import EFI_STATUS, EFI_EVENT
|
||||
|
||||
|
||||
# @see: MdePkg/Include/Protocol/SimpleTextIn.h
|
||||
class EFI_INPUT_KEY(STRUCT):
|
||||
_fields_ = [
|
||||
('ScanCode', UINT16),
|
||||
('UnicodeChar', CHAR16)
|
||||
]
|
||||
|
||||
class EFI_SIMPLE_TEXT_INPUT_PROTOCOL(STRUCT):
|
||||
EFI_SIMPLE_TEXT_INPUT_PROTOCOL = STRUCT
|
||||
|
||||
_fields_ = [
|
||||
('Reset', FUNCPTR(EFI_STATUS, PTR(EFI_SIMPLE_TEXT_INPUT_PROTOCOL), BOOLEAN)),
|
||||
('ReadKeyStroke', FUNCPTR(EFI_STATUS, PTR(EFI_SIMPLE_TEXT_INPUT_PROTOCOL), PTR(EFI_INPUT_KEY))),
|
||||
('WaitForKey', EFI_EVENT)
|
||||
]
|
||||
|
||||
|
||||
@dxeapi(params={
|
||||
"This": POINTER, # IN PTR(EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL)
|
||||
"ExtendedVerification": BOOL # IN BOOLEAN
|
||||
})
|
||||
def hook_Input_Reset(ql: Qiling, address: int, params):
|
||||
pass
|
||||
|
||||
@dxeapi(params={
|
||||
"This": POINTER, # IN PTR(EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL)
|
||||
"Key": POINTER # OUT PTR(EFI_INPUT_KEY)
|
||||
})
|
||||
def hook_Read_Key_Stroke(ql: Qiling, address: int, params):
|
||||
pass
|
||||
|
||||
|
||||
def initialize(ql: Qiling, gIP: int):
|
||||
descriptor = {
|
||||
'struct': EFI_SIMPLE_TEXT_INPUT_PROTOCOL,
|
||||
'fields': (
|
||||
('Reset', hook_Input_Reset),
|
||||
('ReadKeyStroke', hook_Read_Key_Stroke),
|
||||
('WaitForKey', None)
|
||||
)
|
||||
}
|
||||
|
||||
instance = init_struct(ql, gIP, descriptor)
|
||||
instance.save_to(ql.mem, gIP)
|
||||
@@ -0,0 +1,128 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
|
||||
#
|
||||
|
||||
from qiling.os.const import *
|
||||
from qiling.os.uefi.fncc import dxeapi
|
||||
from qiling.os.uefi.utils import *
|
||||
from qiling.os.uefi.ProcessorBind import *
|
||||
from qiling.os.uefi.UefiBaseType import EFI_STATUS
|
||||
|
||||
|
||||
# @see: MdePkg/Include/Protocol/SimpleTextOut.h
|
||||
class SIMPLE_TEXT_OUTPUT_MODE(STRUCT):
|
||||
_fields_ = [
|
||||
("MaxMode", INT32),
|
||||
("Mode", INT32),
|
||||
("Attribute", INT32),
|
||||
("CursorColumn", INT32),
|
||||
("CursorRow", INT32),
|
||||
("CursorVisible", BOOLEAN),
|
||||
]
|
||||
|
||||
|
||||
class EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL(STRUCT):
|
||||
EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL = STRUCT
|
||||
|
||||
_fields_ = [
|
||||
("Reset", FUNCPTR(EFI_STATUS, PTR(EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL), BOOLEAN)),
|
||||
("OutputString", FUNCPTR(EFI_STATUS, PTR(EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL), PTR(CHAR16))),
|
||||
("TestString", FUNCPTR(EFI_STATUS, PTR(EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL), PTR(CHAR16))),
|
||||
("QueryMode", FUNCPTR(EFI_STATUS, PTR(EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL), UINTN, PTR(UINTN), PTR(UINTN))),
|
||||
("SetMode", FUNCPTR(EFI_STATUS, PTR(EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL), UINTN)),
|
||||
("SetAttribute", FUNCPTR(EFI_STATUS, PTR(EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL), UINTN)),
|
||||
("ClearScreen", FUNCPTR(EFI_STATUS, PTR(EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL))),
|
||||
("SetCursorPosition", FUNCPTR(EFI_STATUS, PTR(EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL), UINTN, UINTN)),
|
||||
("EnableCursor", FUNCPTR(EFI_STATUS, PTR(EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL), BOOLEAN)),
|
||||
("Mode", PTR(SIMPLE_TEXT_OUTPUT_MODE))
|
||||
]
|
||||
|
||||
|
||||
@dxeapi(params={
|
||||
"This": POINTER, # IN PTR(EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL)
|
||||
"ExtendedVerification": BOOL # IN BOOLEAN
|
||||
})
|
||||
def hook_TextReset(ql: Qiling, address: int, params):
|
||||
pass
|
||||
|
||||
@dxeapi(params={
|
||||
"This": POINTER, # IN PTR(EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL)
|
||||
"String": WSTRING # IN PTR(CHAR16)
|
||||
})
|
||||
def hook_OutputString(ql: Qiling, address: int, params):
|
||||
print(params['String'])
|
||||
|
||||
return EFI_SUCCESS
|
||||
|
||||
@dxeapi(params={
|
||||
"This": POINTER, # IN PTR(EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL)
|
||||
"String": WSTRING # IN PTR(CHAR16)
|
||||
})
|
||||
def hook_TestString(ql: Qiling, address: int, params):
|
||||
pass
|
||||
|
||||
@dxeapi(params={
|
||||
"This": POINTER, # IN PTR(EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL)
|
||||
"ModeNumber": ULONGLONG, # IN UINTN
|
||||
"Columns": POINTER, # OUT PTR(UINTN)
|
||||
"Rows": POINTER # OUT PTR(UINTN)
|
||||
})
|
||||
def hook_QueryMode(ql: Qiling, address: int, params):
|
||||
pass
|
||||
|
||||
@dxeapi(params={
|
||||
"This": POINTER, # IN PTR(EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL)
|
||||
"ModeNumber": ULONGLONG # IN UINTN
|
||||
})
|
||||
def hook_SetMode(ql: Qiling, address: int, params):
|
||||
pass
|
||||
|
||||
@dxeapi(params={
|
||||
"This": POINTER, # IN PTR(EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL)
|
||||
"Attribute": ULONGLONG # IN UINTN
|
||||
})
|
||||
def hook_SetAttribute(ql: Qiling, address: int, params):
|
||||
pass
|
||||
|
||||
@dxeapi(params={
|
||||
"This": POINTER # IN PTR(EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL)
|
||||
})
|
||||
def hook_ClearScreen(ql: Qiling, address: int, params):
|
||||
pass
|
||||
|
||||
@dxeapi(params={
|
||||
"This": POINTER, # IN PTR(EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL)
|
||||
"Column": ULONGLONG, # IN UINTN
|
||||
"Row": ULONGLONG # IN UINTN
|
||||
})
|
||||
def hook_SetCursorPosition(ql: Qiling, address: int, params):
|
||||
pass
|
||||
|
||||
@dxeapi(params={
|
||||
"This": POINTER, # IN PTR(EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL)
|
||||
"Visible": BOOL # IN BOOLEAN
|
||||
})
|
||||
def hook_EnableCursor(ql: Qiling, address: int, params):
|
||||
pass
|
||||
|
||||
|
||||
def initialize(ql: Qiling, base: int):
|
||||
descriptor = {
|
||||
'struct': EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL,
|
||||
'fields': (
|
||||
('Reset', hook_TextReset),
|
||||
('OutputString', hook_OutputString),
|
||||
('TestString', hook_TestString),
|
||||
('QueryMode', hook_QueryMode),
|
||||
('SetMode', hook_SetMode),
|
||||
('SetAttribute', hook_SetAttribute),
|
||||
('ClearScreen', hook_ClearScreen),
|
||||
('SetCursorPosition', hook_SetCursorPosition),
|
||||
('EnableCursor', hook_EnableCursor),
|
||||
('Mode', None)
|
||||
)
|
||||
}
|
||||
|
||||
instance = init_struct(ql, base, descriptor)
|
||||
instance.save_to(ql.mem, base)
|
||||
+53
-25
@@ -3,57 +3,80 @@
|
||||
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
|
||||
#
|
||||
|
||||
from qiling import Qiling
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from qiling.os.uefi import bs, rt, ds
|
||||
from qiling.os.uefi.context import UefiContext
|
||||
from qiling.os.uefi.utils import install_configuration_table
|
||||
from qiling.os.uefi.UefiSpec import EFI_SYSTEM_TABLE, EFI_BOOT_SERVICES, EFI_RUNTIME_SERVICES
|
||||
from qiling.os.uefi.UefiSpec import EFI_SYSTEM_TABLE, EFI_SIMPLE_TEXT_INPUT_PROTOCOL, EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL, EFI_BOOT_SERVICES, EFI_RUNTIME_SERVICES
|
||||
|
||||
import qiling.os.uefi.protocols.EfiSimpleTextInProtocol as txt_in
|
||||
import qiling.os.uefi.protocols.EfiSimpleTextOutProtocol as txt_out
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from qiling import Qiling
|
||||
|
||||
# static mem layout:
|
||||
#
|
||||
# +-- EFI_SYSTEM_TABLE ---------+
|
||||
# +-- EFI_SYSTEM_TABLE -----------------+
|
||||
# | |
|
||||
# | ... |
|
||||
# | RuntimeServices* -> (1) |
|
||||
# | BootServices* -> (2) |
|
||||
# | ConIn* -> (1) |
|
||||
# | ConOut* -> (2) |
|
||||
# | RuntimeServices* -> (3) |
|
||||
# | BootServices* -> (4) |
|
||||
# | NumberOfTableEntries |
|
||||
# | ConfigurationTable* -> (4) |
|
||||
# +-----------------------------+
|
||||
# (1) +-- EFI_RUNTIME_SERVICES -----+
|
||||
# | ConfigurationTable* -> (6) |
|
||||
# +-------------------------------------+
|
||||
# (1) +-- EFI_SIMPLE_TEXT_INPUT_PROTOCOL ---+
|
||||
# | |
|
||||
# | ... |
|
||||
# +-----------------------------+
|
||||
# (2) +-- EFI_BOOT_SERVICES --------+
|
||||
# +-------------------------------------+
|
||||
# (2) +-- EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL --+
|
||||
# | |
|
||||
# | ... |
|
||||
# +-----------------------------+
|
||||
# (3) +-- EFI_DXE_SERVICES ---------+
|
||||
# +-------------------------------------+
|
||||
# (3) +-- EFI_RUNTIME_SERVICES -------------+
|
||||
# | |
|
||||
# | ... |
|
||||
# +-----------------------------+
|
||||
# (4) +-- EFI_CONFIGURATION_TABLE --+ of HOB_LIST
|
||||
# +-------------------------------------+
|
||||
# (4) +-- EFI_BOOT_SERVICES ----------------+
|
||||
# | |
|
||||
# | ... |
|
||||
# +-------------------------------------+
|
||||
# (5) +-- EFI_DXE_SERVICES -----------------+
|
||||
# | |
|
||||
# | ... |
|
||||
# +-------------------------------------+
|
||||
# (6) +-- EFI_CONFIGURATION_TABLE ----------+ of HOB_LIST
|
||||
# | VendorGuid |
|
||||
# | VendorTable* -> (7) |
|
||||
# +-------------------------------------+
|
||||
# +-- EFI_CONFIGURATION_TABLE ----------+ of DXE_SERVICE_TABLE
|
||||
# | VendorGuid |
|
||||
# | VendorTable* -> (5) |
|
||||
# +-----------------------------+
|
||||
# +-- EFI_CONFIGURATION_TABLE --+ of DXE_SERVICE_TABLE
|
||||
# | VendorGuid |
|
||||
# | VendorTable* -> (3) |
|
||||
# +-----------------------------+
|
||||
# +-------------------------------------+
|
||||
#
|
||||
# ... the remainder of the chunk may be used for additional EFI_CONFIGURATION_TABLE entries
|
||||
|
||||
#
|
||||
# dynamically allocated (context.conf_table_data_ptr):
|
||||
#
|
||||
# (5) +-- VOID* --------------------+
|
||||
# (7) +-- VOID* ----------------------------+
|
||||
# | ... |
|
||||
# +-----------------------------+
|
||||
# +-------------------------------------+
|
||||
|
||||
|
||||
def initialize(ql: Qiling, context: UefiContext, gST: int):
|
||||
ql.loader.gST = gST
|
||||
|
||||
gBS = gST + EFI_SYSTEM_TABLE.sizeof() # boot services
|
||||
gRT = gBS + EFI_BOOT_SERVICES.sizeof() # runtime services
|
||||
gDS = gRT + EFI_RUNTIME_SERVICES.sizeof() # dxe services
|
||||
sti = gST + EFI_SYSTEM_TABLE.sizeof() # input protocols
|
||||
sto = sti + EFI_SIMPLE_TEXT_INPUT_PROTOCOL.sizeof() # output protocols
|
||||
gRT = sto + EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL.sizeof() # runtime services
|
||||
gBS = gRT + EFI_RUNTIME_SERVICES.sizeof() # boot services
|
||||
gDS = gBS + EFI_BOOT_SERVICES.sizeof() # dxe services
|
||||
cfg = gDS + ds.EFI_DXE_SERVICES.sizeof() # configuration tables array
|
||||
|
||||
ql.log.info(f'Global tables:')
|
||||
@@ -63,11 +86,16 @@ def initialize(ql: Qiling, context: UefiContext, gST: int):
|
||||
ql.log.info(f' | gDS {gDS:#010x}')
|
||||
ql.log.info(f'')
|
||||
|
||||
txt_in.initialize(ql, sti)
|
||||
txt_out.initialize(ql, sto)
|
||||
|
||||
bs.initialize(ql, gBS)
|
||||
rt.initialize(ql, gRT)
|
||||
ds.initialize(ql, gDS)
|
||||
|
||||
EFI_SYSTEM_TABLE(
|
||||
ConIn = sti,
|
||||
ConOut = sto,
|
||||
RuntimeServices = gRT,
|
||||
BootServices = gBS,
|
||||
NumberOfTableEntries = 0,
|
||||
|
||||
@@ -38,6 +38,7 @@ STATUS_INVALID_HANDLE = 0xC0000008
|
||||
STATUS_PROCEDURE_NOT_FOUND = 0xC000007A
|
||||
STATUS_DLL_NOT_FOUND = 0xC0000135
|
||||
STATUS_PORT_NOT_SET = 0xC0000353
|
||||
STATUS_STACK_BUFFER_OVERRUN = 0xC0000409
|
||||
STATUS_NO_YIELD_PERFORMED = 0x40000024
|
||||
# ...
|
||||
|
||||
@@ -638,6 +639,7 @@ ProcessProtectionInformation = 61
|
||||
ProcessDebugObjectHandle = 30
|
||||
ProcessDebugFlags = 31
|
||||
ProcessExecuteFlags = 34
|
||||
ProcessCookie = 36
|
||||
ProcessImageInformation = 37
|
||||
ProcessMitigationPolicy = 52
|
||||
ProcessFaultInformation = 63
|
||||
|
||||
@@ -44,15 +44,6 @@ def hook_GetLastError(ql: Qiling, address: int, params):
|
||||
def hook_SetLastError(ql: Qiling, address: int, params):
|
||||
ql.os.last_error = params['dwErrCode']
|
||||
|
||||
# LONG UnhandledExceptionFilter(
|
||||
# _EXCEPTION_POINTERS *ExceptionInfo
|
||||
# );
|
||||
@winsdkapi(cc=STDCALL, params={
|
||||
'ExceptionInfo' : POINTER
|
||||
})
|
||||
def hook_UnhandledExceptionFilter(ql: Qiling, address: int, params):
|
||||
return 1
|
||||
|
||||
# UINT SetErrorMode(
|
||||
# UINT uMode
|
||||
# );
|
||||
@@ -63,33 +54,6 @@ def hook_SetErrorMode(ql: Qiling, address: int, params):
|
||||
# TODO maybe this need a better implementation
|
||||
return 0
|
||||
|
||||
# __analysis_noreturn VOID RaiseException(
|
||||
# DWORD dwExceptionCode,
|
||||
# DWORD dwExceptionFlags,
|
||||
# DWORD nNumberOfArguments,
|
||||
# const ULONG_PTR *lpArguments
|
||||
# );
|
||||
@winsdkapi(cc=STDCALL, params={
|
||||
'dwExceptionCode' : DWORD,
|
||||
'dwExceptionFlags' : DWORD,
|
||||
'nNumberOfArguments' : DWORD,
|
||||
'lpArguments' : POINTER
|
||||
})
|
||||
def hook_RaiseException(ql: Qiling, address: int, params):
|
||||
nNumberOfArguments = params['nNumberOfArguments']
|
||||
lpArguments = params['lpArguments']
|
||||
|
||||
handle = ql.os.handle_manager.search("TopLevelExceptionHandler")
|
||||
|
||||
if handle is None:
|
||||
ql.log.warning(f'RaiseException: top level exception handler not found')
|
||||
return
|
||||
|
||||
exception_handler = handle.obj
|
||||
args = [(PARAM_INTN, ql.mem.read_ptr(lpArguments + i * ql.arch.pointersize)) for i in range(nNumberOfArguments)] if lpArguments else []
|
||||
|
||||
ql.os.fcall.call_native(exception_handler, args, None)
|
||||
|
||||
# PVOID AddVectoredExceptionHandler(
|
||||
# ULONG First,
|
||||
# PVECTORED_EXCEPTION_HANDLER Handler
|
||||
@@ -151,3 +115,47 @@ def hook_RemoveVectoredExceptionHandler(ql: Qiling, address: int, params):
|
||||
hook.remove()
|
||||
|
||||
return 0
|
||||
|
||||
# VOID RaiseException(
|
||||
# DWORD dwExceptionCode,
|
||||
# DWORD dwExceptionFlags,
|
||||
# DWORD nNumberOfArguments,
|
||||
# CONST ULONG_PTR* lpArguments
|
||||
# );
|
||||
@winsdkapi(cc=STDCALL, params={
|
||||
'dwExceptionCode': DWORD,
|
||||
'dwExceptionFlags': DWORD,
|
||||
'nNumberOfArguments': DWORD,
|
||||
'lpArguments': PVOID
|
||||
}, passthru=True)
|
||||
def hook_RaiseException(ql: Qiling, address: int, params):
|
||||
# On x86_64, RaiseException will call RtlRaiseException,
|
||||
# which calls the exception dispatcher directly. The native
|
||||
# exception dispatching code mostly works correctly
|
||||
# for software exceptions, so we shall simply continue
|
||||
# through to the native dispatcher in this case.
|
||||
if ql.arch.type is not QL_ARCH.X86:
|
||||
return
|
||||
|
||||
# On x86, the situation is different. RtlRaiseException
|
||||
# will call ZwRaiseException, which uses a syscall.
|
||||
# However, Qiling doesn't really support Windows syscalls
|
||||
# right now.
|
||||
# We will treat all exceptions as unhandled exceptions,
|
||||
# which is better than nothing.
|
||||
# TODO: Get kernel exception dispatching working properly,
|
||||
# then first-chance software exceptions, SEH, and C++
|
||||
# exceptions can work on 32-bit Windows too.
|
||||
nNumberOfArguments = params['nNumberOfArguments']
|
||||
lpArguments = params['lpArguments']
|
||||
|
||||
handle = ql.os.handle_manager.search("TopLevelExceptionHandler")
|
||||
|
||||
if handle is None:
|
||||
ql.log.warning(f'RaiseException: top level exception handler not found')
|
||||
return
|
||||
|
||||
exception_handler = handle.obj
|
||||
args = [(PARAM_INTN, ql.mem.read_ptr(lpArguments + i * ql.arch.pointersize)) for i in range(nNumberOfArguments)] if lpArguments else []
|
||||
|
||||
ql.os.fcall.call_native(exception_handler, args, None)
|
||||
|
||||
@@ -49,6 +49,17 @@ def hook_HeapCreate(ql: Qiling, address: int, params):
|
||||
|
||||
return ql.os.heap.alloc(dwInitialSize)
|
||||
|
||||
def _HeapAlloc(ql: Qiling, address: int, params):
|
||||
dwFlags = params["dwFlags"]
|
||||
dwBytes = params["dwBytes"]
|
||||
|
||||
ptr = ql.os.heap.alloc(dwBytes)
|
||||
|
||||
if ptr and (dwFlags & HEAP_ZERO_MEMORY):
|
||||
__zero_mem(ql.mem, ptr, dwBytes)
|
||||
|
||||
return ptr
|
||||
|
||||
# DECLSPEC_ALLOCATOR LPVOID HeapAlloc(
|
||||
# HANDLE hHeap,
|
||||
# DWORD dwFlags,
|
||||
@@ -60,15 +71,45 @@ def hook_HeapCreate(ql: Qiling, address: int, params):
|
||||
'dwBytes' : SIZE_T
|
||||
})
|
||||
def hook_HeapAlloc(ql: Qiling, address: int, params):
|
||||
dwFlags = params["dwFlags"]
|
||||
dwBytes = params["dwBytes"]
|
||||
return _HeapAlloc(ql, address, params)
|
||||
|
||||
ptr = ql.os.heap.alloc(dwBytes)
|
||||
# DECLSPEC_ALLOCATOR LPVOID HeapReAlloc(
|
||||
# HANDLE hHeap,
|
||||
# DWORD dwFlags,
|
||||
# _Frees_ptr_opt_ LPVOID lpMem,
|
||||
# SIZE_T dwBytes
|
||||
# );
|
||||
@winsdkapi(cc=STDCALL, params={
|
||||
'hHeap' : HANDLE,
|
||||
'dwFlags' : DWORD,
|
||||
'lpMem': LPVOID,
|
||||
'dwBytes' : SIZE_T
|
||||
})
|
||||
def hook_HeapReAlloc(ql: Qiling, address: int, params):
|
||||
base = params["lpMem"]
|
||||
newSize = params["dwBytes"]
|
||||
|
||||
if ptr and (dwFlags & HEAP_ZERO_MEMORY):
|
||||
__zero_mem(ql.mem, ptr, dwBytes)
|
||||
if not base:
|
||||
return _HeapAlloc(ql, address, params)
|
||||
|
||||
return ptr
|
||||
if newSize == 0:
|
||||
ql.os.heap.free(base)
|
||||
|
||||
return 0
|
||||
|
||||
oldSize = ql.os.heap.size(base)
|
||||
oldData = bytes(ql.mem.read(base, oldSize))
|
||||
|
||||
ql.os.heap.free(base)
|
||||
|
||||
if newSize < oldSize:
|
||||
oldData = oldData[0:newSize]
|
||||
|
||||
newBase = ql.os.heap.alloc(newSize)
|
||||
if newBase:
|
||||
ql.mem.write(newBase, oldData)
|
||||
|
||||
return newBase
|
||||
|
||||
# SIZE_T HeapSize(
|
||||
# HANDLE hHeap,
|
||||
@@ -120,3 +161,29 @@ def hook_HeapSetInformation(ql: Qiling, address: int, params):
|
||||
@winsdkapi(cc=STDCALL, params={})
|
||||
def hook_GetProcessHeap(ql: Qiling, address: int, params):
|
||||
return ql.os.heap.start_address
|
||||
|
||||
# BOOL HeapValidate(
|
||||
# HANDLE hHeap,
|
||||
# DWORD dwFlags,
|
||||
# LPCVOID lpMem
|
||||
# );
|
||||
@winsdkapi(cc=STDCALL, params={
|
||||
'hHeap': PVOID,
|
||||
'dwFlags': DWORD,
|
||||
'lpMem': PVOID
|
||||
})
|
||||
def hook_HeapValidate(ql: Qiling, address: int, params):
|
||||
hHeap = params['hHeap']
|
||||
lpMem = params['lpMem']
|
||||
|
||||
if not hHeap:
|
||||
return 0
|
||||
|
||||
# TODO: Maybe _find is a heap manager implementation
|
||||
# detail, in which case we shouldn't rely on it.
|
||||
chunk = ql.os.heap._find(lpMem)
|
||||
|
||||
if not chunk:
|
||||
return 0
|
||||
|
||||
return chunk.inuse
|
||||
|
||||
@@ -159,24 +159,6 @@ def hook__lwrite(ql: Qiling, address: int, params):
|
||||
def hook_FatalExit(ql: Qiling, address: int, params):
|
||||
ql.emu_stop()
|
||||
|
||||
# PVOID EncodePointer(
|
||||
# _In_ PVOID Ptr
|
||||
# );
|
||||
@winsdkapi(cc=STDCALL, params={
|
||||
'Ptr' : PVOID
|
||||
})
|
||||
def hook_EncodePointer(ql: Qiling, address: int, params):
|
||||
return params['Ptr']
|
||||
|
||||
# PVOID DecodePointer(
|
||||
# _In_ PVOID Ptr
|
||||
# );
|
||||
@winsdkapi(cc=STDCALL, params={
|
||||
'Ptr' : PVOID
|
||||
})
|
||||
def hook_DecodePointer(ql: Qiling, address: int, params):
|
||||
return params['Ptr']
|
||||
|
||||
# UINT WinExec(
|
||||
# LPCSTR lpCmdLine,
|
||||
# UINT uCmdShow
|
||||
|
||||
@@ -80,18 +80,19 @@ def hook_IsValidCodePage(ql: Qiling, address: int, params):
|
||||
return 1
|
||||
|
||||
def __LCMapString(ql: Qiling, address: int, params, wstring: bool):
|
||||
lpSrcStr: str = params["lpSrcStr"]
|
||||
lpSrcStr: int = params["lpSrcStr"]
|
||||
cchSrc: int = params["cchSrc"]
|
||||
lpDestStr: int = params["lpDestStr"]
|
||||
cchDest: int = params["cchDest"]
|
||||
|
||||
enc = "utf-16le" if wstring else "utf-8"
|
||||
res = f'{lpSrcStr}\x00'
|
||||
char_size = 2 if wstring else 1
|
||||
byte_count = cchSrc * char_size
|
||||
|
||||
if cchDest and lpDestStr:
|
||||
# TODO maybe do some other check, for now is working
|
||||
ql.mem.write(lpDestStr, res.encode(enc))
|
||||
source_bytes = ql.mem.read(lpSrcStr, byte_count)
|
||||
ql.mem.write(lpDestStr, bytes(source_bytes))
|
||||
|
||||
return len(res)
|
||||
return cchSrc
|
||||
|
||||
# int LCMapStringW(
|
||||
# LCID Locale,
|
||||
@@ -104,9 +105,9 @@ def __LCMapString(ql: Qiling, address: int, params, wstring: bool):
|
||||
@winsdkapi(cc=STDCALL, params={
|
||||
'Locale' : LCID,
|
||||
'dwMapFlags' : DWORD,
|
||||
'lpSrcStr' : LPCWSTR,
|
||||
'lpSrcStr' : POINTER,
|
||||
'cchSrc' : INT,
|
||||
'lpDestStr' : LPWSTR,
|
||||
'lpDestStr' : POINTER,
|
||||
'cchDest' : INT
|
||||
})
|
||||
def hook_LCMapStringW(ql: Qiling, address: int, params):
|
||||
@@ -123,9 +124,9 @@ def hook_LCMapStringW(ql: Qiling, address: int, params):
|
||||
@winsdkapi(cc=STDCALL, params={
|
||||
'Locale' : LCID,
|
||||
'dwMapFlags' : DWORD,
|
||||
'lpSrcStr' : LPCSTR,
|
||||
'lpSrcStr' : POINTER,
|
||||
'cchSrc' : INT,
|
||||
'lpDestStr' : LPSTR,
|
||||
'lpDestStr' : POINTER,
|
||||
'cchDest' : INT
|
||||
})
|
||||
def hook_LCMapStringA(ql: Qiling, address: int, params):
|
||||
@@ -145,9 +146,9 @@ def hook_LCMapStringA(ql: Qiling, address: int, params):
|
||||
@winsdkapi(cc=STDCALL, params={
|
||||
'lpLocaleName' : LPCWSTR,
|
||||
'dwMapFlags' : DWORD,
|
||||
'lpSrcStr' : LPCWSTR,
|
||||
'lpSrcStr' : POINTER,
|
||||
'cchSrc' : INT,
|
||||
'lpDestStr' : LPWSTR,
|
||||
'lpDestStr' : POINTER,
|
||||
'cchDest' : INT,
|
||||
'lpVersionInformation' : LPNLSVERSIONINFO,
|
||||
'lpReserved' : LPVOID,
|
||||
|
||||
@@ -10,7 +10,7 @@ from qiling import Qiling
|
||||
from qiling.exception import QlErrorNotImplemented
|
||||
from qiling.os.const import *
|
||||
from qiling.os.windows.fncc import *
|
||||
from qiling.os.windows.const import LOCALE
|
||||
from qiling.os.windows.const import *
|
||||
from qiling.os.windows.handle import Handle
|
||||
|
||||
# void __set_app_type (
|
||||
@@ -135,10 +135,9 @@ def hook__controlfp(ql: Qiling, address: int, params):
|
||||
# );
|
||||
@winsdkapi(cc=CDECL, params={
|
||||
'func' : POINTER
|
||||
})
|
||||
}, passthru=True)
|
||||
def hook_atexit(ql: Qiling, address: int, params):
|
||||
ret = 0
|
||||
return ret
|
||||
return
|
||||
|
||||
# char*** __p__environ(void)
|
||||
@winsdkapi(cc=CDECL, params={})
|
||||
@@ -174,17 +173,6 @@ def hook_puts(ql: Qiling, address: int, params):
|
||||
def hook__cexit(ql: Qiling, address: int, params):
|
||||
pass
|
||||
|
||||
# void __cdecl _initterm(
|
||||
# PVFV *,
|
||||
# PVFV *
|
||||
# );
|
||||
@winsdkapi(cc=CDECL, params={
|
||||
'pfbegin' : POINTER,
|
||||
'pfend' : POINTER
|
||||
})
|
||||
def hook__initterm(ql: Qiling, address: int, params):
|
||||
return 0
|
||||
|
||||
# void exit(
|
||||
# int const status
|
||||
# );
|
||||
@@ -194,17 +182,6 @@ def hook__initterm(ql: Qiling, address: int, params):
|
||||
def hook_exit(ql: Qiling, address: int, params):
|
||||
ql.emu_stop()
|
||||
|
||||
# int __cdecl _initterm_e(
|
||||
# PVFV *,
|
||||
# PVFV *
|
||||
# );
|
||||
@winsdkapi(cc=CDECL, params={
|
||||
'pfbegin' : POINTER,
|
||||
'pfend' : POINTER
|
||||
})
|
||||
def hook__initterm_e(ql: Qiling, address: int, params):
|
||||
return 0
|
||||
|
||||
# char*** __cdecl __p___argv (void);
|
||||
@winsdkapi(cc=CDECL, params={})
|
||||
def hook___p___argv(ql: Qiling, address: int, params):
|
||||
@@ -233,20 +210,12 @@ def hook___p___argc(ql: Qiling, address: int, params):
|
||||
return ret
|
||||
|
||||
# TODO: this one belongs to ucrtbase.dll
|
||||
@winsdkapi(cc=CDECL, params={})
|
||||
@winsdkapi(cc=CDECL, params={}, passthru=True)
|
||||
def hook__get_initial_narrow_environment(ql: Qiling, address: int, params):
|
||||
ret = 0
|
||||
|
||||
for i, (k, v) in enumerate(ql.env.items()):
|
||||
entry = bytes(f'{k}={v}', 'ascii') + b'\x00'
|
||||
p_entry = ql.os.heap.alloc(len(entry))
|
||||
|
||||
ql.mem.write(p_entry, entry)
|
||||
|
||||
if i == 0:
|
||||
ret = p_entry
|
||||
|
||||
return ret
|
||||
# If the native version of this function does not
|
||||
# get to run, then debug versions of the CRT DLLs can fail
|
||||
# their initialization.
|
||||
return
|
||||
|
||||
# int sprintf ( char * str, const char * format, ... );
|
||||
@winsdkapi(cc=CDECL, params={
|
||||
@@ -303,13 +272,6 @@ def hook_wprintf(ql: Qiling, address: int, params):
|
||||
|
||||
return count
|
||||
|
||||
# MSVCRT_FILE * CDECL MSVCRT___acrt_iob_func(unsigned idx)
|
||||
@winsdkapi(cc=CDECL, params={
|
||||
'idx': UINT
|
||||
})
|
||||
def hook___acrt_iob_func(ql: Qiling, address: int, params):
|
||||
return 0
|
||||
|
||||
def __stdio_common_vfprintf(ql: Qiling, address: int, params, wstring: bool):
|
||||
format = params['_Format']
|
||||
arglist = params['_ArgList']
|
||||
@@ -368,6 +330,18 @@ def __stdio_common_vsprintf(ql: Qiling, address: int, params, wstring: bool):
|
||||
def hook___stdio_common_vsprintf(ql: Qiling, address: int, params):
|
||||
return __stdio_common_vsprintf(ql, address, params, False)
|
||||
|
||||
@winsdkapi(cc=CDECL, params={
|
||||
'_Options' : PARAM_INT64,
|
||||
'_Buffer' : POINTER,
|
||||
'_BufferCount' : SIZE_T,
|
||||
'_MaxCount' : SIZE_T,
|
||||
'_Format' : STRING,
|
||||
'_Locale' : DWORD,
|
||||
'_ArgList' : POINTER
|
||||
})
|
||||
def hook___stdio_common_vsnprintf(ql: Qiling, address: int, params):
|
||||
return __stdio_common_vsprintf(ql, address, params, False)
|
||||
|
||||
@winsdkapi(cc=CDECL, params={
|
||||
'_Options' : PARAM_INT64,
|
||||
'_Buffer' : POINTER,
|
||||
@@ -379,6 +353,18 @@ def hook___stdio_common_vsprintf(ql: Qiling, address: int, params):
|
||||
def hook___stdio_common_vswprintf(ql: Qiling, address: int, params):
|
||||
return __stdio_common_vsprintf(ql, address, params, True)
|
||||
|
||||
@winsdkapi(cc=CDECL, params={
|
||||
'_Options' : PARAM_INT64,
|
||||
'_Buffer' : POINTER,
|
||||
'_BufferCount' : SIZE_T,
|
||||
'_MaxCount' : SIZE_T,
|
||||
'_Format' : WSTRING,
|
||||
'_Locale' : DWORD,
|
||||
'_ArgList' : POINTER
|
||||
})
|
||||
def hook___stdio_common_vsnwprintf(ql: Qiling, address: int, params):
|
||||
return __stdio_common_vsprintf(ql, address, params, True)
|
||||
|
||||
# all the "_s" versions are aliases to their non-"_s" counterparts
|
||||
|
||||
@winsdkapi(cc=CDECL, params={
|
||||
@@ -412,6 +398,18 @@ def hook___stdio_common_vfwprintf_s(ql: Qiling, address: int, params):
|
||||
def hook___stdio_common_vsprintf_s(ql: Qiling, address: int, params):
|
||||
return hook___stdio_common_vsprintf.__wrapped__(ql, address, params)
|
||||
|
||||
@winsdkapi(cc=CDECL, params={
|
||||
'_Options' : PARAM_INT64,
|
||||
'_Buffer' : POINTER,
|
||||
'_BufferCount' : SIZE_T,
|
||||
'_MaxCount' : SIZE_T,
|
||||
'_Format' : STRING,
|
||||
'_Locale' : DWORD,
|
||||
'_ArgList' : POINTER
|
||||
})
|
||||
def hook___stdio_common_vsnprintf_s(ql: Qiling, address: int, params):
|
||||
return hook___stdio_common_vsnprintf.__wrapped__(ql, address, params)
|
||||
|
||||
@winsdkapi(cc=CDECL, params={
|
||||
'_Options' : PARAM_INT64,
|
||||
'_Buffer' : POINTER,
|
||||
@@ -423,6 +421,18 @@ def hook___stdio_common_vsprintf_s(ql: Qiling, address: int, params):
|
||||
def hook___stdio_common_vswprintf_s(ql: Qiling, address: int, params):
|
||||
return hook___stdio_common_vswprintf.__wrapped__(ql, address, params)
|
||||
|
||||
@winsdkapi(cc=CDECL, params={
|
||||
'_Options' : PARAM_INT64,
|
||||
'_Buffer' : POINTER,
|
||||
'_BufferCount' : SIZE_T,
|
||||
'_MaxCount' : SIZE_T,
|
||||
'_Format' : WSTRING,
|
||||
'_Locale' : DWORD,
|
||||
'_ArgList' : POINTER
|
||||
})
|
||||
def hook___stdio_common_vsnwprintf_s(ql: Qiling, address: int, params):
|
||||
return hook___stdio_common_vsnwprintf.__wrapped__(ql, address, params)
|
||||
|
||||
@winsdkapi(cc=CDECL, params={})
|
||||
def hook___lconv_init(ql: Qiling, address: int, params):
|
||||
return 0
|
||||
@@ -478,43 +488,42 @@ def hook_strncmp(ql: Qiling, address: int, params):
|
||||
|
||||
return result
|
||||
|
||||
def __malloc(ql: Qiling, address: int, params):
|
||||
size = params['size']
|
||||
|
||||
return ql.os.heap.alloc(size)
|
||||
|
||||
@winsdkapi(cc=CDECL, params={
|
||||
'size' : UINT
|
||||
})
|
||||
}, passthru=True)
|
||||
def hook__malloc_base(ql: Qiling, address: int, params):
|
||||
return __malloc(ql, address, params)
|
||||
return
|
||||
|
||||
# void* malloc(unsigned int size)
|
||||
@winsdkapi(cc=CDECL, params={
|
||||
'size' : UINT
|
||||
})
|
||||
}, passthru=True)
|
||||
def hook_malloc(ql: Qiling, address: int, params):
|
||||
size = params['size']
|
||||
return
|
||||
|
||||
return ql.os.heap.alloc(size)
|
||||
|
||||
def __free(ql: Qiling, address: int, params):
|
||||
address = params['address']
|
||||
|
||||
ql.os.heap.free(address)
|
||||
# void* __cdecl _realloc_base(
|
||||
# void* const block,
|
||||
# size_t const size
|
||||
# )
|
||||
@winsdkapi(cc=CDECL, params={
|
||||
'block' : POINTER,
|
||||
'size' : UINT
|
||||
}, passthru=True)
|
||||
def hook__realloc_base(ql: Qiling, address: int, params):
|
||||
return
|
||||
|
||||
@winsdkapi(cc=CDECL, params={
|
||||
'address': POINTER
|
||||
})
|
||||
}, passthru=True)
|
||||
def hook__free_base(ql: Qiling, address: int, params):
|
||||
return __free(ql, address, params)
|
||||
return
|
||||
|
||||
# void* free(void *address)
|
||||
@winsdkapi(cc=CDECL, params={
|
||||
'address': POINTER
|
||||
})
|
||||
}, passthru=True)
|
||||
def hook_free(ql: Qiling, address: int, params):
|
||||
return __free(ql, address, params)
|
||||
return
|
||||
|
||||
# _onexit_t _onexit(
|
||||
# _onexit_t function
|
||||
@@ -530,6 +539,27 @@ def hook__onexit(ql: Qiling, address: int, params):
|
||||
|
||||
return addr
|
||||
|
||||
# _onexit_t __dllonexit(
|
||||
# _onexit_t func,
|
||||
# _PVFV ** pbegin,
|
||||
# _PVFV ** pend
|
||||
# );
|
||||
@winsdkapi(cc=STDCALL, params={
|
||||
'function': POINTER,
|
||||
'pbegin': POINTER,
|
||||
'pend': POINTER
|
||||
})
|
||||
def hook___dllonexit(ql: Qiling, address: int, params):
|
||||
function = params['function']
|
||||
|
||||
if function:
|
||||
addr = ql.os.heap.alloc(ql.arch.pointersize)
|
||||
ql.mem.write_ptr(addr, function)
|
||||
|
||||
return addr
|
||||
|
||||
return 0
|
||||
|
||||
# void *memset(
|
||||
# void *dest,
|
||||
# int c,
|
||||
@@ -539,32 +569,16 @@ def hook__onexit(ql: Qiling, address: int, params):
|
||||
'dest' : POINTER,
|
||||
'c' : INT,
|
||||
'count' : SIZE_T
|
||||
})
|
||||
}, passthru=True)
|
||||
def hook_memset(ql: Qiling, address: int, params):
|
||||
dest = params["dest"]
|
||||
c = params["c"]
|
||||
count = params["count"]
|
||||
|
||||
ql.mem.write(dest, bytes([c] * count))
|
||||
|
||||
return dest
|
||||
|
||||
def __calloc(ql: Qiling, address: int, params):
|
||||
num = params['num']
|
||||
size = params['size']
|
||||
|
||||
count = num * size
|
||||
ret = ql.os.heap.alloc(count)
|
||||
ql.mem.write(ret, bytes([0] * count))
|
||||
|
||||
return ret
|
||||
return
|
||||
|
||||
@winsdkapi(cc=CDECL, params={
|
||||
'num' : SIZE_T,
|
||||
'size' : SIZE_T
|
||||
})
|
||||
}, passthru=True)
|
||||
def hook__calloc_base(ql: Qiling, address: int, params):
|
||||
return __calloc(ql, address, params)
|
||||
return
|
||||
|
||||
# void *calloc(
|
||||
# size_t num,
|
||||
@@ -573,9 +587,9 @@ def hook__calloc_base(ql: Qiling, address: int, params):
|
||||
@winsdkapi(cc=CDECL, params={
|
||||
'num' : SIZE_T,
|
||||
'size' : SIZE_T
|
||||
})
|
||||
}, passthru=True)
|
||||
def hook_calloc(ql: Qiling, address: int, params):
|
||||
return __calloc(ql, address, params)
|
||||
return
|
||||
|
||||
# void * memmove(
|
||||
# void *dest,
|
||||
@@ -586,12 +600,9 @@ def hook_calloc(ql: Qiling, address: int, params):
|
||||
'dest' : POINTER,
|
||||
'src' : POINTER,
|
||||
'num' : SIZE_T
|
||||
})
|
||||
}, passthru=True)
|
||||
def hook_memmove(ql: Qiling, address: int, params):
|
||||
data = ql.mem.read(params['src'], params['num'])
|
||||
ql.mem.write(params['dest'], bytes(data))
|
||||
|
||||
return params['dest']
|
||||
return
|
||||
|
||||
# int _ismbblead(
|
||||
# unsigned int c
|
||||
@@ -644,3 +655,18 @@ def hook__time64(ql: Qiling, address: int, params):
|
||||
ql.mem.write_ptr(dst, time_wasted, 8)
|
||||
|
||||
return time_wasted
|
||||
|
||||
# void abort( void );
|
||||
@winsdkapi(cc=CDECL, params={})
|
||||
def hook_abort(ql: Qiling, address: int, params):
|
||||
# During testing, it was found that programs terminating abnormally
|
||||
# via abort() terminated with exit code=STATUS_STACK_BUFFER_OVERRUN.
|
||||
# According to Microsoft's devblog, this does not necessarily mean
|
||||
# that a stack buffer overrun occurred.
|
||||
# Rather, it can indicate abnormal program termination in a variety of
|
||||
# situations, including abort().
|
||||
# https://devblogs.microsoft.com/oldnewthing/20190108-00/?p=100655
|
||||
#
|
||||
ql.os.exit_code = STATUS_STACK_BUFFER_OVERRUN
|
||||
|
||||
ql.emu_stop()
|
||||
@@ -17,6 +17,7 @@ from qiling.os.windows.handle import *
|
||||
from qiling.os.windows import structs
|
||||
from qiling.os.windows import utils
|
||||
|
||||
from unicorn.x86_const import *
|
||||
|
||||
# void *memcpy(
|
||||
# void *dest,
|
||||
@@ -39,6 +40,7 @@ def hook_memcpy(ql: Qiling, address: int, params):
|
||||
return dest
|
||||
|
||||
def _QueryInformationProcess(ql: Qiling, address: int, params):
|
||||
handle = params["ProcessHandle"]
|
||||
flag = params["ProcessInformationClass"]
|
||||
obuf_ptr = params["ProcessInformation"]
|
||||
obuf_len = params['ProcessInformationLength']
|
||||
@@ -68,6 +70,24 @@ def _QueryInformationProcess(ql: Qiling, address: int, params):
|
||||
|
||||
res_data = bytes(pci_obj)
|
||||
|
||||
|
||||
elif flag == ProcessCookie:
|
||||
hCurrentProcess = (1 << ql.arch.bits) - 1
|
||||
|
||||
if handle != hCurrentProcess:
|
||||
# If a process attempts to query the cookie of another
|
||||
# process, then QueryInformationProcess returns an error.
|
||||
return STATUS_INVALID_PARAMETER
|
||||
|
||||
# TODO: Change this to something else,
|
||||
# maybe a static randomly generated value.
|
||||
res_data = ql.pack32(0x00000001)
|
||||
|
||||
if obuf_len != len(res_data):
|
||||
# If the buffer length is not ULONG size
|
||||
# then QueryInformationProcess returns an error.
|
||||
return STATUS_INFO_LENGTH_MISMATCH
|
||||
|
||||
else:
|
||||
# TODO: support more info class ("flag") values
|
||||
ql.log.info(f'QueryInformationProcess: no implementation for info class {flag:#04x}')
|
||||
@@ -453,3 +473,428 @@ def hook_wcsstr(ql: Qiling, address: int, params):
|
||||
def hook_CsrGetProcessId(ql: Qiling, address: int, params):
|
||||
pid = ql.os.profile["PROCESSES"].getint("csrss.exe", fallback=12345)
|
||||
return pid
|
||||
|
||||
# NTSYSAPI PVOID RtlPcToFileHeader(
|
||||
# [in] PVOID PcValue,
|
||||
# [out] PVOID *BaseOfImage
|
||||
# );
|
||||
@winsdkapi(cc=STDCALL, params={
|
||||
'PcValue' : PVOID,
|
||||
'BaseOfImage': PVOID
|
||||
})
|
||||
def hook_RtlPcToFileHeader(ql: Qiling, address: int, params):
|
||||
pc = params["PcValue"]
|
||||
base_of_image_ptr = params["BaseOfImage"]
|
||||
|
||||
containing_image = ql.loader.find_containing_image(pc)
|
||||
|
||||
base_addr = containing_image.base if containing_image else 0
|
||||
|
||||
ql.mem.write_ptr(base_of_image_ptr, base_addr)
|
||||
return base_addr
|
||||
|
||||
def _FindImageBaseAndFunctionTable(ql: Qiling, control_pc: int, image_base_ptr: int):
|
||||
"""
|
||||
Helper function to locate a containing image for `control_pc` as well as its
|
||||
function table, while writing the image base to `image_base_ptr` (if non-zero).
|
||||
Returns:
|
||||
(base_addr, function_table_addr)
|
||||
if no image is found, otherwise
|
||||
(0, 0)
|
||||
"""
|
||||
containing_image = ql.loader.find_containing_image(control_pc)
|
||||
|
||||
if containing_image:
|
||||
base_addr = containing_image.base
|
||||
else:
|
||||
base_addr = 0
|
||||
|
||||
# Write base address to the ImageBase pointer, if provided
|
||||
if image_base_ptr != 0:
|
||||
ql.mem.write_ptr(image_base_ptr, base_addr)
|
||||
|
||||
# If we don’t have a valid base, abort now
|
||||
if base_addr == 0:
|
||||
return 0, 0
|
||||
|
||||
# Look up the function-table RVA and compute the absolute address
|
||||
function_table_rva = ql.loader.function_table_lookup.get(base_addr)
|
||||
function_table_addr = base_addr + function_table_rva if function_table_rva else 0
|
||||
|
||||
return base_addr, function_table_addr
|
||||
|
||||
# NTSYSAPI PRUNTIME_FUNCTION RtlLookupFunctionEntry(
|
||||
# [in] DWORD64 ControlPc,
|
||||
# [out] PDWORD64 ImageBase,
|
||||
# [out] PUNWIND_HISTORY_TABLE HistoryTable
|
||||
# );
|
||||
@winsdkapi(cc=STDCALL, params={
|
||||
'ControlPc': PVOID,
|
||||
'ImageBase': PVOID,
|
||||
'HistoryTable': PVOID
|
||||
})
|
||||
def hook_RtlLookupFunctionEntry(ql: Qiling, address: int, params):
|
||||
control_pc = params["ControlPc"]
|
||||
image_base_ptr = params["ImageBase"]
|
||||
|
||||
# TODO: Make use of the history table to optimize this function.
|
||||
# Alternatively, we could add caching to the loader, seeing as the
|
||||
# loader is responsible for lookups in the function table.
|
||||
|
||||
# For simplicity, we are going to ignore the history table.
|
||||
# history_table_ptr = params["HistoryTable"]
|
||||
|
||||
# This function should not be getting called on x86.
|
||||
if ql.arch.type is QL_ARCH.X86:
|
||||
raise QlErrorNotImplemented("RtlLookupFunctionEntry is not implemented for x86")
|
||||
|
||||
base_addr, function_table_addr = _FindImageBaseAndFunctionTable(ql, control_pc, image_base_ptr)
|
||||
|
||||
# If no function table was found, abort.
|
||||
if function_table_addr == 0:
|
||||
return 0
|
||||
|
||||
# Look up the RUNTIME_FUNCTION entry; we are interested in the index in the table
|
||||
# so that we can compute the address.
|
||||
runtime_function_idx, runtime_function = ql.loader.lookup_function_entry(base_addr, control_pc)
|
||||
|
||||
# If a suitable function entry was found,
|
||||
# compute its address and return.
|
||||
if runtime_function:
|
||||
return function_table_addr + runtime_function_idx * 12 # sizeof(RUNTIME_FUNCTION)
|
||||
|
||||
return 0
|
||||
|
||||
# NTSYSAPI
|
||||
# PRUNTIME_FUNCTION
|
||||
# RtlLookupFunctionTable (
|
||||
# IN PVOID ControlPc,
|
||||
# OUT PVOID *ImageBase,
|
||||
# OUT PULONG SizeOfTable
|
||||
# );
|
||||
@winsdkapi(cc=STDCALL, params={
|
||||
'ControlPc': PVOID,
|
||||
'ImageBase': PVOID,
|
||||
'SizeOfTable': PVOID
|
||||
})
|
||||
def hook_RtlLookupFunctionTable(ql: Qiling, address: int, params):
|
||||
control_pc = params["ControlPc"]
|
||||
image_base_ptr = params["ImageBase"]
|
||||
size_of_table_ptr = params["SizeOfTable"]
|
||||
|
||||
# This function should not be getting called on x86.
|
||||
if ql.arch.type is QL_ARCH.X86:
|
||||
raise QlErrorNotImplemented("RtlLookupFunctionTable is not implemented for x86")
|
||||
|
||||
base_addr, function_table_addr = _FindImageBaseAndFunctionTable(ql, control_pc, image_base_ptr)
|
||||
|
||||
# If no function table was found, abort.
|
||||
if function_table_addr == 0:
|
||||
ql.mem.write_ptr(size_of_table_ptr, 0, 4)
|
||||
|
||||
return 0
|
||||
|
||||
# If a valid pointer for the size was provided,
|
||||
# we want to figure out the size of the table.
|
||||
if size_of_table_ptr != 0:
|
||||
# Look up the function table from the loader,
|
||||
# and get the number of entries.
|
||||
function_table = ql.loader.function_tables[base_addr]
|
||||
|
||||
# compute the total size of the table
|
||||
size_of_table = len(function_table) * 12 # sizeof(RUNTIME_FUNCTION)
|
||||
|
||||
# Write the size to memory at the provided pointer.
|
||||
ql.mem.write_ptr(size_of_table_ptr, size_of_table, 4)
|
||||
|
||||
return function_table_addr
|
||||
|
||||
@winsdkapi(cc=STDCALL, params={})
|
||||
def hook_LdrControlFlowGuardEnforced(ql: Qiling, address: int, params):
|
||||
# There are some checks in ntdll for whether CFG is enabled.
|
||||
# We simply bypass these checks by returning 0.
|
||||
# May not be necessary, but we do it just in case.
|
||||
return 0
|
||||
|
||||
# NTSYSAPI
|
||||
# NTSTATUS
|
||||
# ZwRaiseException (
|
||||
# IN PEXCEPTION_RECORD ExceptionRecord,
|
||||
# IN PCONTEXT ContextRecord,
|
||||
# IN BOOLEAN FirstChance
|
||||
# );
|
||||
@winsdkapi(cc=STDCALL, params={
|
||||
'ExceptionRecord': PVOID,
|
||||
'ContextRecord': PVOID,
|
||||
'FirstChance': BOOLEAN
|
||||
}, passthru=True)
|
||||
def hook_ZwRaiseException(ql: Qiling, address: int, params):
|
||||
exception_ptr = params['ExceptionRecord']
|
||||
context_ptr = params['ContextRecord']
|
||||
first_chance = params['FirstChance']
|
||||
|
||||
# The native ZwRaiseException simply uses a syscall to start
|
||||
# the kernel exception dispatcher. However, Windows syscalls
|
||||
# are not really working in Qiling right now.
|
||||
# For now, we just provide a workaround for second-chance
|
||||
# exceptions to work.
|
||||
# TODO: Get some kind of solution for kernel exception
|
||||
# dispatching. This is also needed for first-chance exceptions
|
||||
# to work properly on 32-bit Windows.
|
||||
if first_chance:
|
||||
raise QlErrorNotImplemented("ZwRaiseException is not implemented for first-chance exceptions.")
|
||||
|
||||
# In Windows, an unhandled exception triggers the
|
||||
# top-level unhandled exception filter, after which the process
|
||||
# is terminated and error reporting services are called.
|
||||
# Regardless of whether an unhandled exception filter is present,
|
||||
# the process terminates with the same error code that was raised.
|
||||
|
||||
# Our strategy for this hook is to forward second-chance exceptions
|
||||
# to the registered unhandled exception filter, if one exists.
|
||||
|
||||
if exception_ptr:
|
||||
exception_code = ql.mem.read_ptr(exception_ptr, 4) # exception code is always DWORD
|
||||
ql.log.debug(f"[ZwRaiseException] ExceptionCode: 0x{exception_code:08X}")
|
||||
else:
|
||||
ql.log.debug("[ZwRaiseException] ExceptionRecord is NULL")
|
||||
|
||||
ql.log.debug(f" ContextRecord: 0x{context_ptr:016X}")
|
||||
ql.log.debug(f" FirstChance: {first_chance}")
|
||||
|
||||
handle = ql.os.handle_manager.search("TopLevelExceptionHandler")
|
||||
|
||||
if handle is None:
|
||||
ql.log.debug(f'[ZwRaiseException] No top-level exception filter was found.')
|
||||
ql.log.info(f'The process exited with code 0x{exception_code:08X}.')
|
||||
|
||||
ql.os.exit_code = exception_code
|
||||
|
||||
ql.emu_stop()
|
||||
return
|
||||
|
||||
ret_addr = ql.stack_read(0)
|
||||
|
||||
exception_filter = handle.obj
|
||||
|
||||
# allocate some memory for the EXCEPTION_POINTERS struct
|
||||
epointers_struct = structs.make_exception_pointers(ql.arch.bits)
|
||||
exception_pointers_ptr = ql.os.heap.alloc(epointers_struct.sizeof())
|
||||
|
||||
with epointers_struct.ref(ql.mem, exception_pointers_ptr) as epointers_obj:
|
||||
epointers_obj.ExceptionRecord = exception_ptr
|
||||
epointers_obj.ContextRecord = context_ptr
|
||||
|
||||
exception_filter = handle.obj
|
||||
ql.log.debug(f'[ZwRaiseException] Resuming execution at the top-level exception filter at 0x{exception_filter:08X}.')
|
||||
|
||||
# Hack: We are going to fake that the caller of ZwRaiseException
|
||||
# actually called the unhandled exception filter instead.
|
||||
|
||||
# We will create a hook which will be triggered when the unhandled
|
||||
# exception filter returns, so that we may terminate execution.
|
||||
def __post_exception_filter(ql: Qiling):
|
||||
# Free the exception pointers struct we allocated earlier.
|
||||
# Might not be needed, since we are going to terminate the process
|
||||
# soon, but we might as well free it.
|
||||
ql.os.heap.free(exception_pointers_ptr)
|
||||
|
||||
ql.log.debug(f'[ZwRaiseException] Returned from unhandled exception filter at 0x{exception_filter:08X}.')
|
||||
ql.log.info(f'The process exited with code 0x{exception_code:08X}.')
|
||||
|
||||
ql.os.exit_code = exception_code
|
||||
|
||||
ql.emu_stop()
|
||||
|
||||
ql.hook_address(__post_exception_filter, ret_addr)
|
||||
|
||||
exception_filter_args = [(POINTER, exception_pointers_ptr)]
|
||||
|
||||
# Resume execution at the registered unhandled exception filter.
|
||||
# If a program is using a custom unhandled exception filter as an anti-debugging
|
||||
# trick, then the exception filter might not return.
|
||||
|
||||
# TODO: This relies on the hook being marked 'passthru' so that Qiling
|
||||
# doesn't rewind after it returns. However, this is not entirely intended
|
||||
# behavior of passthru, so this is a bit of a hack. Maybe find some
|
||||
# way to rewrite without passthru.
|
||||
ql.os.fcall.call_native(exception_filter, exception_filter_args, ret_addr)
|
||||
|
||||
# NTSTATUS EtwNotificationRegister(
|
||||
# LPCGUID ProviderGuid,
|
||||
# ULONG Type,
|
||||
# PVOID CallbackFunction,
|
||||
# PVOID CallbackContext,
|
||||
# PVOID* RegistrationHandle
|
||||
# );
|
||||
@winsdkapi(cc=STDCALL, params={
|
||||
'ProviderGuid': PVOID,
|
||||
'Type': DWORD,
|
||||
'CallbackFunction': PVOID,
|
||||
'CallbackContext': PVOID,
|
||||
'RegistrationHandle': PVOID
|
||||
})
|
||||
def hook_EtwNotificationRegister(ql: Qiling, address: int, params):
|
||||
reg_handle_ptr = params['RegistrationHandle']
|
||||
|
||||
# It is very important to have a hook for this function
|
||||
# because it is called by some Windows DLLs (sechost.dll,
|
||||
# advapi32.dll) during initialization when the global
|
||||
# CRT lock is held.
|
||||
# If a DllMain aborts here, then the global CRT lock is never
|
||||
# freed and any attempt to lock the global CRT lock *anywhere*
|
||||
# will crash us.
|
||||
|
||||
# TODO: See if a more thorough implementation
|
||||
# is needed for this function.
|
||||
|
||||
# For now, just create a dummy handle, and return it.
|
||||
handle = Handle()
|
||||
ql.os.handle_manager.append(handle)
|
||||
|
||||
if reg_handle_ptr:
|
||||
ql.mem.write_ptr(reg_handle_ptr, handle.id)
|
||||
|
||||
return STATUS_SUCCESS
|
||||
|
||||
# NTSYSAPI
|
||||
# VOID RtlRaiseException(
|
||||
# PEXCEPTION_RECORD ExceptionRecord
|
||||
# );
|
||||
@winsdkapi(cc=STDCALL, params={
|
||||
'ExceptionRecord': PVOID
|
||||
}, passthru=True)
|
||||
def hook_RtlRaiseException(ql: Qiling, address: int, params):
|
||||
return
|
||||
|
||||
# NTSYSAPI
|
||||
# PRUNTIME_FUNCTION RtlVirtualUnwind(
|
||||
# DWORD HandlerType,
|
||||
# DWORD64 ImageBase,
|
||||
# DWORD64 ControlPc,
|
||||
# PRUNTIME_FUNCTION FunctionEntry,
|
||||
# PCONTEXT ContextRecord,
|
||||
# PVOID* HandlerData,
|
||||
# PDWORD64 EstablisherFrame,
|
||||
# PKNONVOLATILE_CONTEXT_POINTERS ContextPointers
|
||||
# );
|
||||
@winsdkapi(cc=STDCALL, params={
|
||||
'HandlerType': DWORD,
|
||||
'ImageBase': PVOID,
|
||||
'ControlPc': PVOID,
|
||||
'FunctionEntry': PVOID,
|
||||
'ContextRecord': PVOID,
|
||||
'HandlerData': PVOID,
|
||||
'EstablisherFrame': PVOID,
|
||||
'ContextPointers': PVOID
|
||||
}, passthru=True)
|
||||
def hook_RtlVirtualUnwind(ql: Qiling, address: int, params):
|
||||
return
|
||||
|
||||
# NTSYSAPI
|
||||
# VOID RtlUnwindEx(
|
||||
# PVOID TargetFrame,
|
||||
# PVOID TargetIp,
|
||||
# PEXCEPTION_RECORD ExceptionRecord,
|
||||
# PVOID ReturnValue,
|
||||
# PCONTEXT OriginalContext,
|
||||
# PUNWIND_HISTORY_TABLE HistoryTable
|
||||
# );
|
||||
@winsdkapi(cc=STDCALL, params={
|
||||
'TargetFrame': PVOID,
|
||||
'TargetIp': PVOID,
|
||||
'ExceptionRecord': PVOID,
|
||||
'ReturnValue': PVOID,
|
||||
'OriginalContext': PVOID,
|
||||
'HistoryTable': PVOID
|
||||
}, passthru=True)
|
||||
def hook_RtlUnwindEx(ql: Qiling, address: int, params):
|
||||
return
|
||||
|
||||
# NTSYSAPI
|
||||
# BOOLEAN RtlDispatchException(
|
||||
# PEXCEPTION_RECORD ExceptionRecord,
|
||||
# PCONTEXT ContextRecord
|
||||
# );
|
||||
@winsdkapi(cc=STDCALL, params={
|
||||
'ExceptionRecord': PVOID,
|
||||
'ContextRecord': PVOID
|
||||
}, passthru=True)
|
||||
def hook_RtlDispatchException(ql: Qiling, address: int, params):
|
||||
return
|
||||
|
||||
# NTSYSAPI
|
||||
# VOID RtlRestoreContext(
|
||||
# PCONTEXT ContextRecord,
|
||||
# PEXCEPTION_RECORD ExceptionRecord
|
||||
# );
|
||||
@winsdkapi(cc=CDECL, params={
|
||||
'ContextRecord': PVOID,
|
||||
'ExceptionRecord': PVOID
|
||||
}, passthru=True)
|
||||
def hook_RtlRestoreContext(ql: Qiling, address: int, params):
|
||||
return
|
||||
|
||||
# NTSYSAPI
|
||||
# VOID RtlCaptureContext(
|
||||
# PCONTEXT ContextRecord
|
||||
# );
|
||||
@winsdkapi(cc=STDCALL, params={
|
||||
'ContextRecord': PVOID
|
||||
}, passthru=True)
|
||||
def hook_RtlCaptureContext(ql: Qiling, address: int, params):
|
||||
return
|
||||
|
||||
# NTSYSAPI
|
||||
# VOID RtlCaptureContext2(
|
||||
# PCONTEXT ContextRecord,
|
||||
# ULONG Flags
|
||||
# );
|
||||
@winsdkapi(cc=STDCALL, params={
|
||||
'ContextRecord': PVOID,
|
||||
'Flags': DWORD
|
||||
}, passthru=True)
|
||||
def hook_RtlCaptureContext2(ql: Qiling, address: int, params):
|
||||
return
|
||||
|
||||
# NTSYSAPI
|
||||
# NTSTATUS RtlInitializeExtendedContext2(
|
||||
# USHORT Version,
|
||||
# USHORT ContextFlags,
|
||||
# ULONG ExtensionCount,
|
||||
# ULONG *ExtensionSizes,
|
||||
# ULONG BufferSize,
|
||||
# PVOID Buffer,
|
||||
# PCONTEXT Context,
|
||||
# ULONG *LengthReturned
|
||||
# );
|
||||
@winsdkapi(cc=STDCALL, params={
|
||||
'Version': WORD,
|
||||
'ContextFlags': WORD,
|
||||
'ExtensionCount': DWORD,
|
||||
'ExtensionSizes': PVOID,
|
||||
'BufferSize': DWORD,
|
||||
'Buffer': PVOID,
|
||||
'Context': PVOID,
|
||||
'LengthReturned': PVOID
|
||||
}, passthru=True)
|
||||
def hook_RtlInitializeExtendedContext2(ql: Qiling, address: int, params):
|
||||
return
|
||||
|
||||
# NTSYSAPI
|
||||
# NTSTATUS RtlGetExtendedContextLength2(
|
||||
# USHORT Version,
|
||||
# USHORT ContextFlags,
|
||||
# ULONG ExtensionCount,
|
||||
# ULONG *ExtensionSizes,
|
||||
# PULONG Length
|
||||
# );
|
||||
@winsdkapi(cc=STDCALL, params={
|
||||
'Version': WORD,
|
||||
'ContextFlags': WORD,
|
||||
'ExtensionCount': DWORD,
|
||||
'ExtensionSizes': PVOID,
|
||||
'Length': PVOID
|
||||
}, passthru=True)
|
||||
def hook_RtlGetExtendedContextLength2(ql: Qiling, address: int, params):
|
||||
return
|
||||
|
||||
@@ -1545,3 +1545,19 @@ def make_win32_find_data(archbits: int, *, wide: bool):
|
||||
)
|
||||
|
||||
return WIN32_FIND_DATA
|
||||
|
||||
# https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-exception_pointers
|
||||
def make_exception_pointers(archbits: int):
|
||||
"""Generate an EXCEPTION_POINTERS structure class.
|
||||
"""
|
||||
|
||||
native_type = struct.get_native_type(archbits)
|
||||
Struct = struct.get_aligned_struct(archbits)
|
||||
|
||||
class EXCEPTION_POINTERS(Struct):
|
||||
_fields_ = (
|
||||
('ExceptionRecord', native_type),
|
||||
('ContextRecord', native_type)
|
||||
)
|
||||
|
||||
return EXCEPTION_POINTERS
|
||||
@@ -1,6 +1,7 @@
|
||||
[CODE]
|
||||
# ram_size 0xa00000 is 10MB
|
||||
ram_size = 0xa00000
|
||||
load_address = 0x1000000
|
||||
entry_point = 0x1000000
|
||||
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ KI_USER_SHARED_DATA = 0x7ffe0000
|
||||
[CODE]
|
||||
# ram_size 0xa00000 is 10MB
|
||||
ram_size = 0xa00000
|
||||
load_address = 0x1000000
|
||||
entry_point = 0x1000000
|
||||
|
||||
[KERNEL]
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
[CODE]
|
||||
load_address = 0x10000000
|
||||
entry_point = 0x10000008
|
||||
ram_size = 0xa00000
|
||||
@@ -1,6 +1,8 @@
|
||||
[CODE]
|
||||
ram_size = 0xa00000
|
||||
load_address = 0x80800000
|
||||
entry_point = 0x80800000
|
||||
heap_address = 0xa0000000
|
||||
heap_size = 0x300000
|
||||
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user