qemux 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
qemux-1.0.0/LICENSE ADDED
@@ -0,0 +1,6 @@
1
+ Copyright (c) 2026 Elseware. All rights reserved.
2
+
3
+ This software and its associated documentation are proprietary and
4
+ confidential. No part of this software may be copied, modified,
5
+ distributed, sublicensed, or used without prior written permission from
6
+ the copyright holder.
qemux-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,39 @@
1
+ Metadata-Version: 2.4
2
+ Name: qemux
3
+ Version: 1.0.0
4
+ Summary: Dynamic binary instrumentation platform
5
+ Author-email: Elseware <contact@elseware.io>
6
+ License-Expression: LicenseRef-Proprietary
7
+ Project-URL: homepage, https://elseware.io/qemux/
8
+ Project-URL: documentation, https://qemux.elseware.io/
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3 :: Only
12
+ Classifier: Programming Language :: Python :: 3.9
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Requires-Python: >=3.9
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ Requires-Dist: typing_extensions
21
+ Requires-Dist: psutil
22
+ Requires-Dist: lief
23
+ Requires-Dist: pypcode
24
+ Requires-Dist: claripy
25
+ Dynamic: license-file
26
+
27
+
28
+ [![Test](https://github.com/johneiser/qemux/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/johneiser/qemux/actions/workflows/test.yml)
29
+
30
+ # Qemux
31
+
32
+ Qemux is a dynamic binary instrumentation platform, based on Qemu.
33
+
34
+ ```python
35
+ from qemux import QxPopen, QxHarness
36
+ with QxPopen(["qemu-x86_64", "/usr/bin/true"]) as qp:
37
+ qx = QxHarness(qp)
38
+ qx.step()
39
+ ```
qemux-1.0.0/README.md ADDED
@@ -0,0 +1,13 @@
1
+
2
+ [![Test](https://github.com/johneiser/qemux/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/johneiser/qemux/actions/workflows/test.yml)
3
+
4
+ # Qemux
5
+
6
+ Qemux is a dynamic binary instrumentation platform, based on Qemu.
7
+
8
+ ```python
9
+ from qemux import QxPopen, QxHarness
10
+ with QxPopen(["qemu-x86_64", "/usr/bin/true"]) as qp:
11
+ qx = QxHarness(qp)
12
+ qx.step()
13
+ ```
@@ -0,0 +1,40 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "qemux"
7
+ version = "1.0.0"
8
+ description = "Dynamic binary instrumentation platform"
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = "LicenseRef-Proprietary"
12
+ license-files = ["LICENSE"]
13
+ authors = [{ name = "Elseware", email = "contact@elseware.io" }]
14
+ dependencies = [
15
+ "typing_extensions",
16
+ "psutil",
17
+ "lief",
18
+ "pypcode",
19
+ "claripy",
20
+ ]
21
+ classifiers = [
22
+ "Development Status :: 3 - Alpha",
23
+ "Programming Language :: Python :: 3",
24
+ "Programming Language :: Python :: 3 :: Only",
25
+ "Programming Language :: Python :: 3.9",
26
+ "Programming Language :: Python :: 3.10",
27
+ "Programming Language :: Python :: 3.11",
28
+ "Programming Language :: Python :: 3.12",
29
+ "Programming Language :: Python :: 3.13",
30
+ ]
31
+
32
+ [project.scripts]
33
+ qxpy = "qemux.scripts.qxpy:main"
34
+
35
+ [project.urls]
36
+ homepage = "https://elseware.io/qemux/"
37
+ documentation = "https://qemux.elseware.io/"
38
+
39
+ [tool.setuptools.packages.find]
40
+ where = ["src"]
qemux-1.0.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
File without changes
@@ -0,0 +1,4 @@
1
+ from .scripts.qxpy import main
2
+
3
+ if __name__ == "__main__":
4
+ raise SystemExit(main())
File without changes
@@ -0,0 +1,24 @@
1
+ """A minimal python harness around a qemu process.
2
+
3
+ ```bash
4
+ qxpy -- qemu-x86_64 /usr/bin/true
5
+ ```
6
+ """
7
+ import argparse
8
+ from ..utils.prompt import prompt
9
+
10
+ def run(argv):
11
+ print(argv)
12
+ qx = None
13
+ prompt(qx=qx)
14
+
15
+ def main():
16
+ parser = argparse.ArgumentParser(
17
+ description="A minimal python harness around a qemu process.",
18
+ epilog="example: qxpy -- qemu-x86_64 /usr/bin/true")
19
+ parser.add_argument("argv", nargs="*", type=str, help="qemu process arguments (qemu-* ...)")
20
+ args = parser.parse_args()
21
+ run(args.argv)
22
+
23
+ if __name__ == "__main__":
24
+ raise SystemExit(main())
@@ -0,0 +1,20 @@
1
+ """Knowledge of target cpu registers."""
2
+
3
+ import os, pkgutil
4
+ from ctypes import Structure
5
+ from typing import Type
6
+
7
+ class QxRegisters(Structure):
8
+
9
+ @classmethod
10
+ def __class_getitem__(cls, arch: str) -> Type["QxRegisters"]:
11
+
12
+ # Find subclass matching target architecture
13
+ for c in cls.__subclasses__():
14
+ if c.__name__ == arch:
15
+ return c
16
+
17
+ raise NotImplementedError(cls, arch)
18
+
19
+ # Automatically import residing architectures
20
+ __all__ = [m.name for m in pkgutil.iter_modules([os.path.dirname(__file__)])]
@@ -0,0 +1,35 @@
1
+ """i386 processor specification.
2
+ https://github.com/qemu/qemu/blob/master/target/i386/cpu.h
3
+ """
4
+ __all__ = ["i386", "x86_64"]
5
+
6
+ from . import QxRegisters
7
+ from ctypes import Union, c_uint32, c_uint64
8
+
9
+ class c_uint32_esp(Union):
10
+ _fields_ = [("esp", c_uint32), ("sp", c_uint32)]
11
+
12
+ class i386(QxRegisters):
13
+ _anonymous_ = ("esp",)
14
+ _fields_ = [
15
+ ("eax", c_uint32), ("ecx", c_uint32),
16
+ ("edx", c_uint32), ("ebx", c_uint32),
17
+ ("esp", c_uint32_esp), ("ebp", c_uint32),
18
+ ("esi", c_uint32), ("edi", c_uint32),
19
+ ]
20
+
21
+ class c_uint64_rsp(Union):
22
+ _fields_ = [("rsp", c_uint64), ("sp", c_uint64)]
23
+
24
+ class x86_64(QxRegisters):
25
+ _anonymous_ = ("rsp",)
26
+ _fields_ = [
27
+ ("rax", c_uint64), ("rcx", c_uint64),
28
+ ("rdx", c_uint64), ("rbx", c_uint64),
29
+ ("rsp", c_uint64_rsp), ("rbp", c_uint64),
30
+ ("rsi", c_uint64), ("rdi", c_uint64),
31
+ ("r8", c_uint64), ("r9", c_uint64),
32
+ ("r10", c_uint64), ("r11", c_uint64),
33
+ ("r12", c_uint64), ("r13", c_uint64),
34
+ ("r14", c_uint64), ("r15", c_uint64),
35
+ ]
File without changes
@@ -0,0 +1,36 @@
1
+ __all__ = ["CallableType", "to_callable"]
2
+
3
+ from functools import singledispatch, partial
4
+ from collections.abc import Callable
5
+ from typing import Union, Any
6
+
7
+ CallableType = Union[None, bool, str, Callable]
8
+
9
+ def none(*args: Any, **kwargs: Any) -> None:
10
+ return None
11
+
12
+ def true(*args: Any, **kwargs: Any) -> bool:
13
+ return True
14
+
15
+ def false(*args: Any, **kwargs: Any) -> bool:
16
+ return False
17
+
18
+ @singledispatch
19
+ def to_callable(x: CallableType) -> Callable:
20
+ raise TypeError(type(x))
21
+
22
+ @to_callable.register
23
+ def _(x: None) -> Callable:
24
+ return none
25
+
26
+ @to_callable.register
27
+ def _(x: bool) -> Callable:
28
+ return true if x else false
29
+
30
+ @to_callable.register
31
+ def _(x: str) -> Callable:
32
+ return partial(print, x)
33
+
34
+ @to_callable.register
35
+ def _(x: Callable) -> Callable:
36
+ return x
@@ -0,0 +1,40 @@
1
+ __all__ = ["PatternType", "to_pattern", "PatternDict"]
2
+
3
+ import re, fnmatch
4
+ from typing import Union, Any, TypeVar
5
+ from functools import singledispatch
6
+ from collections.abc import Iterator
7
+
8
+ PatternType = Union[str, re.Pattern]
9
+
10
+ @singledispatch
11
+ def to_pattern(x: PatternType) -> re.Pattern:
12
+ raise TypeError(type(x))
13
+
14
+ @to_pattern.register
15
+ def _(x: str) -> re.Pattern:
16
+ return re.compile(fnmatch.translate(x), re.IGNORECASE)
17
+
18
+ @to_pattern.register
19
+ def _(x: re.Pattern) -> re.Pattern:
20
+ return x
21
+
22
+ K = TypeVar("K", bound=str)
23
+ V = TypeVar("V")
24
+
25
+ class PatternDict(dict[K, V]):
26
+
27
+ def _find(self, item: PatternType) -> Iterator[tuple[K, V]]:
28
+ p = to_pattern(item)
29
+ for k, v in self.items():
30
+ if p.match(k):
31
+ yield k, v
32
+
33
+ def find(self, item: PatternType) -> list[tuple[K, V]]:
34
+ """Find items by pattern.
35
+
36
+ :param str item: item string or pattern
37
+ :return: items
38
+ :rtype: list[tuple[k, v]]
39
+ """
40
+ return list(self._find(item))
@@ -0,0 +1,7 @@
1
+ __all__ = ["pmmap_ext"]
2
+
3
+ import psutil
4
+
5
+ # Fix breaking changes across psutil versions
6
+ _ntp = getattr(psutil, "_ntp", getattr(psutil, "_psplatform"))
7
+ pmmap_ext = _ntp.pmmap_ext
@@ -0,0 +1,10 @@
1
+ __all__ = ["prompt"]
2
+
3
+ import code, readline, rlcompleter
4
+ from pprint import pformat
5
+
6
+ def prompt(**kwargs):
7
+ """Spawn an interactive python prompt with the given keyword arguments."""
8
+ readline.set_completer(rlcompleter.Completer(kwargs).complete)
9
+ readline.parse_and_bind("tab: complete")
10
+ code.interact(banner=pformat(kwargs), local=kwargs)
@@ -0,0 +1,39 @@
1
+ __all__ = ["RangeType", "to_range"]
2
+
3
+ from functools import singledispatch
4
+ from typing import Union
5
+ from .pmmap import pmmap_ext
6
+
7
+ RangeType = Union[int, slice, range, pmmap_ext]
8
+
9
+ @singledispatch
10
+ def to_range(x: RangeType, size: int = 1) -> range:
11
+ raise TypeError(type(x))
12
+
13
+ @to_range.register
14
+ def _(x: int, size: int = 1) -> range:
15
+ return range(x, x + size)
16
+
17
+ @to_range.register
18
+ def _(x: slice, size: int = 1) -> range:
19
+ if x.start is None or x.stop is None:
20
+ raise ValueError(x)
21
+ step = 1 if x.step is None else x.step
22
+ return range(x.start, x.stop, step)
23
+
24
+ @to_range.register
25
+ def _(x: range, size: int = 1) -> range:
26
+ return x
27
+
28
+ # @to_range.register
29
+ # def _(x: tuple[int, int]) -> range:
30
+ # if len(x) != 2:
31
+ # raise ValueError(x)
32
+ # return range(*x)
33
+
34
+ @to_range.register
35
+ def _(x: pmmap_ext) -> range:
36
+ a, b = x.addr.split("-")
37
+ start = int(a, 16)
38
+ end = int(b, 16)
39
+ return range(start, end)
@@ -0,0 +1,20 @@
1
+ __all__ = ["SliceType", "to_slice"]
2
+
3
+ from functools import singledispatch
4
+ from typing import Union, Any
5
+
6
+ SliceType = Union[int, slice]
7
+
8
+ @singledispatch
9
+ def to_slice(x: SliceType, size: int = 1, step: int = 1) -> slice:
10
+ raise TypeError(type(x))
11
+
12
+ @to_slice.register
13
+ def _(x: int, size: int = 1, step: int = 1) -> slice:
14
+ return slice(x, x + size, step)
15
+
16
+ @to_slice.register
17
+ def _(x: slice, size: int = 1, step: int = 1) -> slice:
18
+ if x.start is None or x.stop is None:
19
+ raise ValueError(x)
20
+ return x
@@ -0,0 +1,19 @@
1
+ __all__ = ["WaitReadable"]
2
+
3
+ import asyncio
4
+
5
+ class WaitReadable:
6
+ """Convert a blocking file descriptor into an awaitable object."""
7
+
8
+ def __init__(self, fd):
9
+ self.fd = fd
10
+
11
+ def __enter__(self):
12
+ event = asyncio.Event()
13
+ loop = asyncio.get_running_loop()
14
+ loop.add_reader(self.fd, event.set)
15
+ return asyncio.create_task(event.wait())
16
+
17
+ def __exit__(self, exc_type, exc_value, traceback):
18
+ loop = asyncio.get_running_loop()
19
+ loop.remove_reader(self.fd)
@@ -0,0 +1,39 @@
1
+ Metadata-Version: 2.4
2
+ Name: qemux
3
+ Version: 1.0.0
4
+ Summary: Dynamic binary instrumentation platform
5
+ Author-email: Elseware <contact@elseware.io>
6
+ License-Expression: LicenseRef-Proprietary
7
+ Project-URL: homepage, https://elseware.io/qemux/
8
+ Project-URL: documentation, https://qemux.elseware.io/
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3 :: Only
12
+ Classifier: Programming Language :: Python :: 3.9
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Requires-Python: >=3.9
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ Requires-Dist: typing_extensions
21
+ Requires-Dist: psutil
22
+ Requires-Dist: lief
23
+ Requires-Dist: pypcode
24
+ Requires-Dist: claripy
25
+ Dynamic: license-file
26
+
27
+
28
+ [![Test](https://github.com/johneiser/qemux/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/johneiser/qemux/actions/workflows/test.yml)
29
+
30
+ # Qemux
31
+
32
+ Qemux is a dynamic binary instrumentation platform, based on Qemu.
33
+
34
+ ```python
35
+ from qemux import QxPopen, QxHarness
36
+ with QxPopen(["qemu-x86_64", "/usr/bin/true"]) as qp:
37
+ qx = QxHarness(qp)
38
+ qx.step()
39
+ ```
@@ -0,0 +1,24 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ src/qemux/__init__.py
5
+ src/qemux/__main__.py
6
+ src/qemux.egg-info/PKG-INFO
7
+ src/qemux.egg-info/SOURCES.txt
8
+ src/qemux.egg-info/dependency_links.txt
9
+ src/qemux.egg-info/entry_points.txt
10
+ src/qemux.egg-info/requires.txt
11
+ src/qemux.egg-info/top_level.txt
12
+ src/qemux/scripts/__init__.py
13
+ src/qemux/scripts/qxpy.py
14
+ src/qemux/targets/__init__.py
15
+ src/qemux/targets/i386.py
16
+ src/qemux/utils/__init__.py
17
+ src/qemux/utils/callable.py
18
+ src/qemux/utils/pattern.py
19
+ src/qemux/utils/pmmap.py
20
+ src/qemux/utils/prompt.py
21
+ src/qemux/utils/range.py
22
+ src/qemux/utils/slice.py
23
+ src/qemux/utils/wait.py
24
+ tests/test_cli.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ qxpy = qemux.scripts.qxpy:main
@@ -0,0 +1,5 @@
1
+ typing_extensions
2
+ psutil
3
+ lief
4
+ pypcode
5
+ claripy
@@ -0,0 +1 @@
1
+ qemux
@@ -0,0 +1,10 @@
1
+ import unittest
2
+
3
+ import qemux
4
+
5
+ class TestQemux(unittest.TestCase):
6
+ def test_nop(self) -> None:
7
+ self.assertEqual(True, True)
8
+
9
+ if __name__ == "__main__":
10
+ unittest.main()