# qilingframework/qiling: emulating an operating system so you can instrument a binary

> A Python binary emulation framework built on Unicorn, spanning eight architectures and eight platforms, with a GPL license field that disagrees with its own README about the or-later clause.

**qilingframework/qiling** — A True Instrumentable Binary Emulation Framework

- Repository: https://github.com/qilingframework/qiling
- Website: https://qiling.io
- Stars: 6,123 · Forks: 802
- Language: Python
- License: GPL-2.0
- Published: 2026-10-08 · Updated: 2026-10-08 · Language: en
- Canonical page: https://hysenlabs.com/projects/qilingframework-qiling

## What the framework claims to cover

The repository description is four words: a true instrumentable binary emulation framework. That phrase sets the scope, because instrumentable is the operative word. Qiling is not trying to be a hypervisor or a virtual machine manager. It is trying to give you a Python object you can interrogate, hook and patch while a binary runs.

The feature list in the README is organized around coverage. Platforms include Windows, macOS, Linux, Android, BSD, UEFI, DOS and MBR. Architectures include 8086, X86, X86_64, ARM, ARM64, MIPS, RISC-V and PowerPC. File formats cover PE, Mach-O, ELF, COM and MBR. Kernel-level support for Windows drivers, Linux kernel modules and macOS kernel extensions is offered through a separate project called Demigod rather than through this repository.

The instrumentation claims are more specific. Hooks are available at the instruction, basic-block, memory-access, exception, syscall and IO levels. There is a virtual machine level API for saving and restoring execution state, so a run can be snapshotted and resumed. Debugging crosses both architecture and platform, and the built-in debugger includes reverse debugging. Running code can be hot patched on the fly, including already-loaded libraries.

That combination explains the shape of the codebase. The tree holds a `qiling/` package directory, a `qltool` command, a `qltui.py` terminal UI, an `examples/` directory with named scripts like `crackme_x86_windows.py`, `hello_8086_dos.py` and `hello_arm_uboot.py`, plus separate `ARCHITECTURE/` and `ARCHITECTURE.md` documentation. There are also `AGENT.md` and `CLAUDE.md` files at the root, and v1.4.11's changelog lists adding AGENT documentation as its first entry.

The project states its pedigree openly, listing talks at Black Hat USA, Europe, Asia, EU and MEA, at DEFCON, HITB and Nullcon between 2019 and 2022. The repository carries 6,107 stars, 798 forks and 111 open issues, is written in Python, uses the `master` branch as default, and was last pushed to on 2026-09-13.

## The README explains itself through two comparisons

The most useful section of the README is not the feature list but the comparison with Unicorn and QEMU user mode, and it is worth reading closely because it defines the project's actual boundary.

Against Unicorn, the argument is about altitude. The README is explicit that Unicorn is just a CPU emulator, focused on emulating CPU instructions, and that it understands emulator memory. Beyond that, it says, Unicorn is not aware of higher level concepts such as dynamic libraries, system calls, IO handling or executable formats like PE, Mach-O or ELF. The consequence is that Unicorn can only emulate raw machine instructions, without operating system context.

Qiling is described as the higher level framework that uses Unicorn for instruction emulation while understanding the OS, through executable format loaders for PE, Mach-O and ELF, dynamic linkers that load and relocate shared libraries, and syscall and IO handlers. The stated result is that Qiling can run an executable binary without requiring its native OS. Qiling is backed by Unicorn Engine, and `pyproject.toml` pins `unicorn` to exactly 2.1.3.

Against QEMU user mode, the differences are about being a framework rather than a tool. The README argues that QEMU user mode does a similar thing, emulating whole executables cross-architecture, but that QEMU is a tool while Qiling is a framework you build dynamic analysis tools on, in Python. Qiling performs dynamic instrumentation and can hot patch at runtime, and QEMU does neither. The cross-platform claim is the sharpest one: Qiling can run a Linux ELF on top of Windows, while QEMU user mode only runs binaries of the same OS, because of how it forwards syscalls from emulated code to the native OS. QEMU user mode is also limited to Linux and BSD, against Qiling's Windows, macOS, Linux and BSD.

Read together, the two comparisons explain why the dependency list is as heavy as it is. `capstone` for disassembly, `pefile` for PE parsing, `pyelftools` for ELF, `python-registry` for Windows registry access, `keystone-engine` for assembly, `python-fx` for symbolic execution style instrumentation, and `gevent` for cooperative concurrency across the emulation threads. Optional extras go further: `r2libr` for reverse engineering integration, and a `fuzz` extra pulling in `unicornafl` and `fuzzercorn`.

## The shortest useful example

The README gives one complete example for emulating a Windows executable, and it is short enough to read as the framework's actual API surface.

```python
from qiling import Qiling

if __name__ == "__main__":
    # initialize Qiling instance, specifying the executable to emulate and the emulated system root.
    # note that the current working directory is assumed to be Qiling home
    ql = Qiling([r'examples/rootfs/x86_windows/bin/x86_hello.exe'], r'examples/rootfs/x86_windows')

    # start emulation
    ql.run()
```

Four things are worth noticing. The import is a single name from the top-level `qiling` package. The constructor takes a list of paths to the binaries to emulate, even when there is only one, and a second argument that is the emulated system root. The example root is a directory under `examples/rootfs/`, which is the root filesystem the emulated OS sees, not a path on your own disk. And the comment states that the current working directory is assumed to be the Qiling home, which is the kind of assumption that will cost you an afternoon if you run the script from the wrong place.

The emulated root is the concept that trips people up most often. When the binary being emulated opens a file, resolves a DLL or reads a registry key, those operations resolve against the emulated root, not your machine. That is the whole isolation story and the whole safety story, and it is why an analysis tool that executes untrusted code does not need a VM to be safe, provided the root is set correctly.

Installation is where the README is least helpful. The Installation section contains no commands at all and points to a setup guide in the project wiki. The concrete build recipe lives in the repository's Dockerfile instead, which is a three-stage build on `python:3.13-slim-trixie`. The builder stage installs `cmake build-essential gcc git pkg-config` and then runs `poetry install --no-root --no-directory` before the source is copied in, so the dependency layer caches independently of the code. The final stage installs the built wheel and sets `CMD ["bash"]`, meaning the image is a shell you enter rather than a service you run.

## Three places where the metadata disagrees with itself

None of these are bugs, and each one is the kind of detail that matters if you are deciding whether to build tooling on top of Qiling.

The first is the license. GitHub reports the repository license as GPL-2.0. The `pyproject.toml` says `license = "GPL-2.0-or-later"`. The README says the program is free software that you may redistribute and modify 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. The or-later clause is present in two of the three places and absent from the repository field, which is the kind of mismatch GitHub's license detector produces when a `COPYING` file holds the full text without the SPDX identifier spelled out. There is a `COPYING` file at the root of the tree. Read that file rather than the repository badge, and remember that GPL terms reach anything you link against.

The second is the maturity marker. The project classifiers include `Development Status :: 4 - Beta`, and the version in `pyproject.toml` is `1.4.11`. Those are not contradictory in the way a stale field would be, but they do describe a project that has shipped a dozen or so 1.4 releases and still labels itself beta. If you are deciding how much test coverage your own integration should have, treat that classifier as the maintainers' own signal.

The third is release tag formatting, which is genuinely inconsistent. The newest tag is `v1.4.11`, published 2026-09-06. The two before it are `1.4.10` from 2026-04-28 and `1.4.7` from 2025-03-08, with no v prefix. And the 1.4.10 changelog links a compare view spanning `1.4.7...1.4.8`, which is a version that never appears as a tag in the list. If you script anything against the releases API, match on the numbers and tolerate both forms.

What v1.4.11 itself contains is worth noting for anyone deciding whether to upgrade. It adds qdb RISC-V support and a RISC-V debugger support, both from the same contributor. It fixes gdb stop-replies for step, for signalled vCont resume, and for ctrl-c interrupt. It repairs a broken `__future__` import in the IDA plugin custom script. It updates the ARCHITECTURE docs, and bumps setuptools from 78.1.1 to 83.0.0 through dependabot.

## Version pinning, dependencies, and what a lockfile buys you

The dependency block in `pyproject.toml` has an inconsistency worth noticing, because it affects whether you can safely upgrade anything at all.

`unicorn` is pinned to the exact string `2.1.3`, with no caret and no range. Almost everything else uses a range: `capstone` at `^5`, `keystone-engine` at `^0.9.2`, `pyyaml` at `^6.0.1`, `python-registry` at `^1.3.1`, `gevent` at `>=24.10`, `multiprocess` at `>=0.70.12.2`, and `pefile` and `pyelftools` with open-ended lower bounds. `unicorn` is the emulator underneath the whole framework, so an exact pin is defensible on its own terms. The practical consequence is that this dependency will not move on its own, and anyone bumping it is testing the framework's core.

Two dependencies are conditionally installed. `windows-curses` is gated on `platform_system == 'Windows'`, and `unicornafl` is gated on the platform being anything other than Windows, with `fuzzercorn` gated on Linux. Both are optional and grouped into a `fuzz` extra, alongside `r2libr` behind an `RE` extra.

The interpreter constraint is `python = "^3.10"`, which under Poetry's caret semantics allows 3.10 through 3.x but not 4. The Dockerfile builds on `python:3.13-slim-trixie`, which sits comfortably inside that range, so the two are consistent even though the README never mentions a Python version at all. If you are pinning an interpreter for your own tooling, follow the Dockerfile rather than the README, since the README says nothing.

The console entry point is declared as `qltool = "qiling.cli:run"`, and the packaging includes the `qiling` package plus `qltui.py`, with the `qltool` directory shipped in the sdist format specifically. That split tells you the CLI has parts that are useful to source distributions but not to a wheel install.

One more detail from the Dockerfile deserves attention if you deploy it. The base stage sets `ENV AM_I_IN_A_DOCKER_CONTAINER=True` and `ENV DEBIAN_FRONTEND=noninteractive`. A framework that checks an environment variable to detect its own container is making an assumption about root filesystem resolution, and it is worth confirming which behavior you get when you run the same code on a bare host.

## Where to start, and what to check first

Because this is an execution framework rather than a library you import quietly, the first run is where most of the practical knowledge comes from. Four steps in order.

Run the unmodified example. Clone the repository, install per the wiki, and execute the Windows hello example with the working directory set to the repository root, because the example path is relative and the README comment says the current working directory is assumed to be the Qiling home. If that fails, the failure is almost always the emulated root rather than Qiling itself.

Confirm the isolation boundary before you trust it. Point a test at a path that exists on your host but not in the emulated root, and confirm the emulated binary cannot see it. This is the property that lets you run untrusted code without a VM, and verifying it once is worth more than reading about it.

Then add one hook. The README advertises hooks at instruction, basic-block, memory-access, exception, syscall and IO levels, so start with a syscall or IO hook rather than an instruction hook. Instruction-level hooks are the most expensive and the least informative for a first experiment; the OS-level ones are where a framework like this earns its keep over raw Unicorn.

Finally, decide how you will pin. If you are scripting against releases, handle both the `v` prefixed and bare tag forms. If you are installing from PyPI, note that the exact `unicorn` pin means a resolver upgrade is a deliberate act rather than a routine one. And if your use case is fuzzing or reverse engineering, install the relevant extra rather than assuming the base package covers it.

The `examples/` directory is the best argument for the framework. The names alone, `crackme_x86_windows_setcallback.py`, `crackme_x86_windows_unpatch.py`, `adcache_x86_windows_debug.py`, `hello_8086_dos.py`, `hello_mips32el_linux_function_hook.py`, `hello_arm_linux_custom_syscall.py` and a `blob_raw.ql` file, describe the workflows the project is built around. Read three of them before writing your own.

## Conclusion

Qiling earns its place by solving a specific problem that neither Unicorn nor QEMU solves: running a whole executable, with its loader, linker, syscalls and filesystem, somewhere it does not belong. That is what makes it the right tool for firmware work, cross-platform malware triage and offline exploration of a binary you would rather not execute. Three things to settle before you build a pipeline on it. First, the license: GitHub reports GPL-2.0, while `pyproject.toml` says `GPL-2.0-or-later` and the README spells out version 2 or any later version at your option. Read the `COPYING` file at the root and decide on that basis, because GPL terms propagate through anything you link. Second, installation is not documented in the README at all, which points you to the wiki; the Dockerfile in the repository is the more concrete reference and builds a wheel on `python:3.13-slim-trixie`. Third, pin your version deliberately. The 1.4.11 release is recent and adds RISC-V debugger support, but `unicorn` is pinned to the exact version 2.1.3 while most other dependencies use caret ranges, so let the lockfile resolve rather than upgrading one dependency on its own. To evaluate it, run the Windows hello example unmodified first, confirm the root filesystem path resolves, and only then add hooks.

## FAQ

### What license is Qiling actually under?

GitHub reports GPL-2.0 for this repository, but `pyproject.toml` declares `GPL-2.0-or-later` and the README states you may use version 2 of the License or, at your option, any later version. Both the README and the package metadata carry the or-later clause. Read the COPYING file at the repository root to settle it, and note that GPL terms propagate to anything you link against.

### How is Qiling different from the Unicorn engine it is built on?

Unicorn is a CPU emulator that understands instructions and emulator memory but not operating system concepts such as dynamic libraries, system calls, IO handling or executable formats. Qiling adds executable format loaders for PE, Mach-O and ELF, dynamic linkers that load and relocate shared libraries, and syscall and IO handlers, which is what allows it to run a binary without its native OS. Unicorn is pinned at exactly 2.1.3 in the package metadata.

### Why would I use Qiling instead of QEMU user mode?

The README argues two distinctions. QEMU user mode is a tool while Qiling is a framework you build analysis tools on in Python, and Qiling can perform dynamic instrumentation and hot patch code at runtime while QEMU does neither. Qiling is also cross-platform rather than only cross-architecture, so it can run a Linux ELF on Windows, whereas QEMU user mode only runs binaries of the same OS because of how it forwards syscalls.

### How do I install Qiling?

The README contains no installation commands and points to a setup guide in the project wiki. The repository's Dockerfile is the more concrete reference: a three-stage build on `python:3.13-slim-trixie` that installs poetry, resolves dependencies before copying the source, builds a wheel and installs it. The package metadata requires Python 3.10 or later within the 3.x range.

## Sources

- [License: GPL-2.0](https://github.com/qilingframework/qiling/blob/master/LICENSE)
- [Project website](https://qiling.io)
- [qilingframework/qiling on GitHub](https://github.com/qilingframework/qiling)
- [README](https://github.com/qilingframework/qiling/blob/master/README.md)
- [Releases](https://github.com/qilingframework/qiling/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/qilingframework-qiling
