deaced 0.1.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.
- deaced-0.1.0/CHANGELOG.md +50 -0
- deaced-0.1.0/CONTRIBUTING.md +31 -0
- deaced-0.1.0/LICENSE +22 -0
- deaced-0.1.0/MANIFEST.in +11 -0
- deaced-0.1.0/NOTICE +43 -0
- deaced-0.1.0/PKG-INFO +151 -0
- deaced-0.1.0/README.md +112 -0
- deaced-0.1.0/pyproject.toml +71 -0
- deaced-0.1.0/setup.cfg +4 -0
- deaced-0.1.0/src/deaced/__init__.py +49 -0
- deaced-0.1.0/src/deaced/__main__.py +6 -0
- deaced-0.1.0/src/deaced/cli.py +94 -0
- deaced-0.1.0/src/deaced/errors.py +27 -0
- deaced-0.1.0/src/deaced/jfloat.py +86 -0
- deaced-0.1.0/src/deaced/model.py +209 -0
- deaced-0.1.0/src/deaced/mutf8.py +70 -0
- deaced-0.1.0/src/deaced/parser.py +699 -0
- deaced-0.1.0/src/deaced/py.typed +0 -0
- deaced-0.1.0/src/deaced/reader.py +63 -0
- deaced-0.1.0/src/deaced/render/__init__.py +17 -0
- deaced-0.1.0/src/deaced/render/_safe.py +18 -0
- deaced-0.1.0/src/deaced/render/json.py +151 -0
- deaced-0.1.0/src/deaced/render/pretty.py +137 -0
- deaced-0.1.0/src/deaced/render/text.py +430 -0
- deaced-0.1.0/src/deaced/tags.py +49 -0
- deaced-0.1.0/src/deaced.egg-info/PKG-INFO +151 -0
- deaced-0.1.0/src/deaced.egg-info/SOURCES.txt +63 -0
- deaced-0.1.0/src/deaced.egg-info/dependency_links.txt +1 -0
- deaced-0.1.0/src/deaced.egg-info/entry_points.txt +2 -0
- deaced-0.1.0/src/deaced.egg-info/requires.txt +9 -0
- deaced-0.1.0/src/deaced.egg-info/top_level.txt +1 -0
- deaced-0.1.0/tests/data/arraylist.golden +42 -0
- deaced-0.1.0/tests/data/arraylist.ser +0 -0
- deaced-0.1.0/tests/data/arrays.golden +174 -0
- deaced-0.1.0/tests/data/arrays.ser +0 -0
- deaced-0.1.0/tests/data/classobj.golden +18 -0
- deaced-0.1.0/tests/data/classobj.ser +0 -0
- deaced-0.1.0/tests/data/enumv.golden +63 -0
- deaced-0.1.0/tests/data/enumv.ser +0 -0
- deaced-0.1.0/tests/data/floats.golden +134 -0
- deaced-0.1.0/tests/data/floats.ser +0 -0
- deaced-0.1.0/tests/data/inherit.golden +59 -0
- deaced-0.1.0/tests/data/inherit.ser +0 -0
- deaced-0.1.0/tests/data/longstring.golden +8 -0
- deaced-0.1.0/tests/data/longstring.ser +0 -0
- deaced-0.1.0/tests/data/primarrays.golden +212 -0
- deaced-0.1.0/tests/data/primarrays.ser +0 -0
- deaced-0.1.0/tests/data/prims.golden +94 -0
- deaced-0.1.0/tests/data/prims.ser +0 -0
- deaced-0.1.0/tests/data/proxy.golden +62 -0
- deaced-0.1.0/tests/data/proxy.ser +0 -0
- deaced-0.1.0/tests/data/refs.golden +30 -0
- deaced-0.1.0/tests/data/refs.ser +0 -0
- deaced-0.1.0/tests/test_cli.py +95 -0
- deaced-0.1.0/tests/test_dump.py +97 -0
- deaced-0.1.0/tests/test_golden.py +28 -0
- deaced-0.1.0/tests/test_jfloat.py +73 -0
- deaced-0.1.0/tests/test_mutf8.py +62 -0
- deaced-0.1.0/tests/test_protocol.py +233 -0
- deaced-0.1.0/tests/test_reader.py +41 -0
- deaced-0.1.0/tests/test_render.py +133 -0
- deaced-0.1.0/tests/test_robustness.py +139 -0
- deaced-0.1.0/tools/GenerateFixtures.java +157 -0
- deaced-0.1.0/tools/README.md +24 -0
- deaced-0.1.0/tools/regen_goldens.sh +35 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here. The format is based on
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/), and this project adheres to
|
|
5
|
+
[Semantic Versioning](https://semver.org/).
|
|
6
|
+
|
|
7
|
+
## [0.1.0] - 2026-06-27
|
|
8
|
+
|
|
9
|
+
First public release.
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- Python port of [NickstaDB/SerializationDumper](https://github.com/NickstaDB/SerializationDumper)
|
|
13
|
+
as a dependency-free library + CLI.
|
|
14
|
+
- Public API: `dump(data, *, format="text"|"json"|"pretty", offsets=False)` and
|
|
15
|
+
`parse(data) -> Stream`, which returns a typed semantic AST (nodes mirror
|
|
16
|
+
protocol entities; back-references are resolved against a handle table).
|
|
17
|
+
- Text dump byte-for-byte compatible with the patched upstream jar; `--offsets`
|
|
18
|
+
prefixes each line with its byte offset.
|
|
19
|
+
- JSON renderer (structured, machine-readable) and pretty renderer (compact data
|
|
20
|
+
tree); CLI `deaced -r/-f/-x [-o FILE] [-F text|json|pretty] [--offsets]`, stdin.
|
|
21
|
+
- Layered architecture: `reader` (byte reader), `tags` (`TC`/`SC` enums),
|
|
22
|
+
`parser` (stream → AST), `model` (the AST), `render/` (visitors), with
|
|
23
|
+
dedicated exceptions that carry the byte offset.
|
|
24
|
+
|
|
25
|
+
### Fixed / improved (vs upstream)
|
|
26
|
+
- `(long)`/`(double)` integer-shift bug: bytes are widened before shifting, so
|
|
27
|
+
`0xFFFFFFFFFFFFFFFF` reads as `-1` (not `-4294967297`).
|
|
28
|
+
- `TC_LONGSTRING` accepted as an object-field value; `TC_BLOCKDATA` /
|
|
29
|
+
`TC_BLOCKDATALONG` accepted as array-field values.
|
|
30
|
+
- Strings decoded as Java modified UTF-8 (`U+0000` as `C0 80`, supplementary
|
|
31
|
+
characters as surrogate pairs); the original decoded byte-by-byte.
|
|
32
|
+
- `TC_RESET` (`0x79`) and `TC_EXCEPTION` (`0x7b`) are parsed; upstream aborts on
|
|
33
|
+
both. `TC_RESET` restarts handle numbering as Java does.
|
|
34
|
+
- `float`/`double` rendered with Java's exact `Double.toString` /
|
|
35
|
+
`Float.toString` notation (e.g. `1.0E7`, `1.5E-10`), not Python's `repr`.
|
|
36
|
+
- `long` field type code is labelled `Long - J` (the JVM type code, JVMS 4.3.2);
|
|
37
|
+
upstream's `Long - L` is a typo (the dumped hex byte `0x4a` is already `J`).
|
|
38
|
+
|
|
39
|
+
### Hardened
|
|
40
|
+
- Output is always valid UTF-8: lone UTF-16 surrogates render as `?` (matching
|
|
41
|
+
the jar); previously such a value could crash the CLI on write.
|
|
42
|
+
- Structurally-invalid streams (negative array sizes, field/interface counts, or
|
|
43
|
+
block-data/string lengths) are rejected with a clear `SerDumpError` instead of
|
|
44
|
+
a misleading dump or a huge allocation attempt.
|
|
45
|
+
- Deeply nested streams parse with an enlarged worker-thread stack and raise a
|
|
46
|
+
clean error past a generous depth limit, instead of crashing the interpreter.
|
|
47
|
+
- Parse errors distinguish end-of-stream (`EOF`) from a literal `0x00` tag.
|
|
48
|
+
- `--offsets` is validated as a text-format-only option.
|
|
49
|
+
|
|
50
|
+
[0.1.0]: https://github.com/gmarav/DeACED/releases/tag/v0.1.0
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Contributing to DeACED
|
|
2
|
+
|
|
3
|
+
Thanks for your interest!
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install -e ".[dev]"
|
|
9
|
+
pre-commit install
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Before opening a PR
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
ruff check .
|
|
16
|
+
ruff format --check .
|
|
17
|
+
mypy
|
|
18
|
+
pytest -q
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Guidelines
|
|
22
|
+
|
|
23
|
+
- Keep the core dependency-free (standard library only).
|
|
24
|
+
- All code, comments and commit messages in English.
|
|
25
|
+
- Add a test for every fix or new feature. Fixtures must be small and synthetic
|
|
26
|
+
(no third-party data) — the repo is public.
|
|
27
|
+
- Preserve the text dump format unless intentionally changing it (it is covered by
|
|
28
|
+
a golden test); discuss format changes in an issue first.
|
|
29
|
+
- Golden fixtures live in `tests/data`; regenerate them with `tools/regen_goldens.sh`
|
|
30
|
+
(needs a JDK and the patched jar -- see `tools/README.md`).
|
|
31
|
+
- Update `CHANGELOG.md` and, when behaviour diverges from upstream, `NOTICE`.
|
deaced-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2017 Nicky Bloor (original SerializationDumper, https://github.com/NickstaDB/SerializationDumper)
|
|
4
|
+
Copyright (c) 2026 Alexander Gmar (Python port, https://github.com/gmarav/DeACED)
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
deaced-0.1.0/MANIFEST.in
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Source distribution contents. The wheel ships only the package (src/deaced);
|
|
2
|
+
# the sdist additionally carries the tests + their fixtures and the dev tools so
|
|
3
|
+
# it is self-contained and auditable, and `pytest` works from an unpacked sdist.
|
|
4
|
+
include LICENSE
|
|
5
|
+
include NOTICE
|
|
6
|
+
include README.md
|
|
7
|
+
include CHANGELOG.md
|
|
8
|
+
include CONTRIBUTING.md
|
|
9
|
+
recursive-include tests *.py
|
|
10
|
+
recursive-include tests/data *.ser *.golden
|
|
11
|
+
recursive-include tools *.java *.sh *.md
|
deaced-0.1.0/NOTICE
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
DeACED
|
|
2
|
+
======
|
|
3
|
+
|
|
4
|
+
DeACED is a Python port of SerializationDumper by Nicky Bloor.
|
|
5
|
+
|
|
6
|
+
Original work: SerializationDumper
|
|
7
|
+
Author: Nicky Bloor (@NickstaDB)
|
|
8
|
+
Source: https://github.com/NickstaDB/SerializationDumper
|
|
9
|
+
License: MIT (Copyright (c) 2017 Nicky Bloor)
|
|
10
|
+
|
|
11
|
+
Python port: Alexander Gmar (https://github.com/gmarav/DeACED)
|
|
12
|
+
Port license: MIT (Copyright (c) 2026 Alexander Gmar)
|
|
13
|
+
|
|
14
|
+
The output format (the text dump) mirrors the original so existing dumps remain
|
|
15
|
+
comparable. This port additionally fixes and extends the original:
|
|
16
|
+
|
|
17
|
+
* (long)/(double) fields: the original shifts byte operands as 32-bit ints
|
|
18
|
+
(b1 << 56 == b1 << 24) and sign-extends int masks, so 0xFFFFFFFFFFFFFFFF
|
|
19
|
+
was read as -4294967297 instead of -1 and some doubles were corrupted.
|
|
20
|
+
DeACED widens each byte to long before shifting (correct values).
|
|
21
|
+
* TC_LONGSTRING (0x7c) accepted as an object-field value.
|
|
22
|
+
* TC_BLOCKDATA (0x77) / TC_BLOCKDATALONG (0x7a) accepted as array-field values.
|
|
23
|
+
* Strings decoded as Java modified UTF-8 (the encoding serialization actually
|
|
24
|
+
uses): U+0000 as C0 80 and supplementary characters as surrogate pairs are
|
|
25
|
+
decoded correctly. The original decoded byte-by-byte, corrupting non-ASCII.
|
|
26
|
+
* TC_RESET (0x79) and TC_EXCEPTION (0x7b) are parsed; the original aborts on
|
|
27
|
+
both with "Illegal content element type".
|
|
28
|
+
* float/double values are rendered with Java's exact Double.toString /
|
|
29
|
+
Float.toString rules (shortest round-trip digits; decimal vs. scientific by
|
|
30
|
+
magnitude). This matches Java 19+ except for the smallest subnormals (a small
|
|
31
|
+
cluster near Double/Float.MIN_VALUE), where Java itself does not emit the
|
|
32
|
+
shortest digits; DeACED's output still round-trips to the identical value.
|
|
33
|
+
* The field type code for long is labelled "Long - J" (the JVM type code, per
|
|
34
|
+
JVMS 4.3.2). The original prints "Long - L", which is a typo -- the dumped
|
|
35
|
+
hex byte, 0x4a, is already ASCII 'J'.
|
|
36
|
+
* Lone UTF-16 surrogates in string/char values are rendered as '?', so output
|
|
37
|
+
is always valid UTF-8 (the original could emit an unencodable character).
|
|
38
|
+
* Structurally-invalid streams (negative array sizes, field/interface counts,
|
|
39
|
+
or block-data/string lengths) are rejected with a clear error rather than
|
|
40
|
+
silently producing a misleading partial dump.
|
|
41
|
+
* Optional byte-offset annotation mode.
|
|
42
|
+
|
|
43
|
+
The "rebuild" (-b) mode of the original is not (yet) ported.
|
deaced-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: deaced
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Dump and inspect Java Object Serialization (0xAC 0xED) streams and RMI packets in human-readable form.
|
|
5
|
+
Author: Alexander Gmar
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/gmarav/DeACED
|
|
8
|
+
Project-URL: Repository, https://github.com/gmarav/DeACED
|
|
9
|
+
Project-URL: Documentation, https://github.com/gmarav/DeACED#readme
|
|
10
|
+
Project-URL: Issues, https://github.com/gmarav/DeACED/issues
|
|
11
|
+
Project-URL: Changelog, https://github.com/gmarav/DeACED/blob/main/CHANGELOG.md
|
|
12
|
+
Keywords: java,serialization,deserialization,rmi,security,aced,dump
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Environment :: Console
|
|
16
|
+
Classifier: Topic :: Security
|
|
17
|
+
Classifier: Topic :: Software Development :: Disassemblers
|
|
18
|
+
Classifier: Topic :: Utilities
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
26
|
+
Requires-Python: >=3.10
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
License-File: LICENSE
|
|
29
|
+
License-File: NOTICE
|
|
30
|
+
Provides-Extra: dev
|
|
31
|
+
Requires-Dist: pytest>=7; extra == "dev"
|
|
32
|
+
Requires-Dist: pytest-cov>=4; extra == "dev"
|
|
33
|
+
Requires-Dist: ruff>=0.4; extra == "dev"
|
|
34
|
+
Requires-Dist: mypy>=1.8; extra == "dev"
|
|
35
|
+
Requires-Dist: build>=1; extra == "dev"
|
|
36
|
+
Requires-Dist: twine>=5; extra == "dev"
|
|
37
|
+
Requires-Dist: pre-commit>=3; extra == "dev"
|
|
38
|
+
Dynamic: license-file
|
|
39
|
+
|
|
40
|
+
# DeACED
|
|
41
|
+
|
|
42
|
+
[](https://github.com/gmarav/DeACED/actions/workflows/ci.yml)
|
|
43
|
+
[](LICENSE)
|
|
44
|
+

|
|
45
|
+
|
|
46
|
+
> Dump and inspect **Java Object Serialization** (`0xAC 0xED`) streams — and Java RMI
|
|
47
|
+
> packet contents — in a clear, human-readable, hierarchical form.
|
|
48
|
+
|
|
49
|
+
`DeACED` (a nod to the `AC ED` stream magic, and to *de*-serialization) is a small,
|
|
50
|
+
**dependency-free** Python library and CLI. It is a port of
|
|
51
|
+
[SerializationDumper](https://github.com/NickstaDB/SerializationDumper) by Nicky Bloor,
|
|
52
|
+
with several correctness fixes (see [NOTICE](NOTICE)).
|
|
53
|
+
|
|
54
|
+
## Install
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pip install deaced # once published
|
|
58
|
+
# or from source:
|
|
59
|
+
pip install .
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## CLI
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
deaced -r dump.bin # raw serialized file
|
|
66
|
+
cat dump.bin | deaced -r - # from stdin
|
|
67
|
+
deaced -x aced0005740004414243... # hex on the command line
|
|
68
|
+
deaced -f hexdump.txt # file of hex-ascii bytes
|
|
69
|
+
deaced -r dump.bin --offsets # prefix each line with '@<byte-offset>|'
|
|
70
|
+
deaced -r dump.bin -o out.txt # write to a file
|
|
71
|
+
deaced -r dump.bin -F json # structured, machine-readable JSON
|
|
72
|
+
deaced -r dump.bin -F pretty # compact, human-readable data tree
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Example:
|
|
76
|
+
|
|
77
|
+
```text
|
|
78
|
+
$ deaced -x aced0005740004414243447071007e0000
|
|
79
|
+
|
|
80
|
+
STREAM_MAGIC - 0xac ed
|
|
81
|
+
STREAM_VERSION - 0x00 05
|
|
82
|
+
Contents
|
|
83
|
+
TC_STRING - 0x74
|
|
84
|
+
newHandle 0x00 7e 00 00
|
|
85
|
+
Length - 4 - 0x00 04
|
|
86
|
+
Value - ABCD - 0x41424344
|
|
87
|
+
TC_NULL - 0x70
|
|
88
|
+
TC_REFERENCE - 0x71
|
|
89
|
+
Handle - 8257536 - 0x00 7e 00 00
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Library
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
from deaced import dump, parse
|
|
96
|
+
|
|
97
|
+
data = open("dump.bin", "rb").read()
|
|
98
|
+
|
|
99
|
+
print(dump(data)) # text dump (default)
|
|
100
|
+
print(dump(data, offsets=True)) # with '@<offset>|' prefixes
|
|
101
|
+
print(dump(data, format="json")) # structured JSON
|
|
102
|
+
print(dump(data, format="pretty")) # compact human-readable tree
|
|
103
|
+
|
|
104
|
+
tree = parse(data) # the semantic AST (deaced.model.Stream)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Why a port?
|
|
108
|
+
|
|
109
|
+
Removes the JVM dependency (the original is a Java jar), runs anywhere Python does,
|
|
110
|
+
integrates into Python tooling, and fixes real bugs in the original — most notably
|
|
111
|
+
the `(long)`/`(double)` integer-shift bug (`0xFFFFFFFFFFFFFFFF` was shown as
|
|
112
|
+
`-4294967297` instead of `-1`). See [NOTICE](NOTICE) for the full list.
|
|
113
|
+
|
|
114
|
+
## Safety
|
|
115
|
+
|
|
116
|
+
DeACED is built to inspect **untrusted, attacker-controlled** serialization
|
|
117
|
+
streams (that is the usual reason to dump one), so:
|
|
118
|
+
|
|
119
|
+
- **It never deserializes Java objects.** DeACED only reads and decodes the byte
|
|
120
|
+
stream into a descriptive tree — it does not load classes, instantiate objects,
|
|
121
|
+
or invoke `readObject`. The classic Java deserialization gadget chains cannot
|
|
122
|
+
fire through it.
|
|
123
|
+
- **It fails cleanly on malformed input.** Truncated or structurally-invalid
|
|
124
|
+
streams raise a `SerDumpError` carrying the byte offset; negative
|
|
125
|
+
lengths/counts are rejected rather than driving huge allocations; deeply nested
|
|
126
|
+
streams raise an error instead of crashing the interpreter.
|
|
127
|
+
|
|
128
|
+
Resource use is still proportional to input size — apply your own limits when
|
|
129
|
+
dumping data straight off the network. See [SECURITY.md](SECURITY.md).
|
|
130
|
+
|
|
131
|
+
## Development
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
pip install -e ".[dev]"
|
|
135
|
+
pytest # tests (tiny synthetic fixtures)
|
|
136
|
+
ruff check .
|
|
137
|
+
ruff format --check .
|
|
138
|
+
mypy
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Contributing
|
|
142
|
+
|
|
143
|
+
Contributions welcome — see [CONTRIBUTING](CONTRIBUTING.md). The stream "rebuild"
|
|
144
|
+
(`-b`) mode of the original is intentionally not ported (see [NOTICE](NOTICE)).
|
|
145
|
+
|
|
146
|
+
## Credits & license
|
|
147
|
+
|
|
148
|
+
Python port by **Alexander Gmar** ([@gmarav](https://github.com/gmarav)) —
|
|
149
|
+
[github.com/gmarav/DeACED](https://github.com/gmarav/DeACED).
|
|
150
|
+
Based on **SerializationDumper** by **Nicky Bloor** ([@NickstaDB](https://github.com/NickstaDB)).
|
|
151
|
+
Licensed under the [MIT License](LICENSE); port © 2026 Alexander Gmar, original © 2017 Nicky Bloor.
|
deaced-0.1.0/README.md
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# DeACED
|
|
2
|
+
|
|
3
|
+
[](https://github.com/gmarav/DeACED/actions/workflows/ci.yml)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
> Dump and inspect **Java Object Serialization** (`0xAC 0xED`) streams — and Java RMI
|
|
8
|
+
> packet contents — in a clear, human-readable, hierarchical form.
|
|
9
|
+
|
|
10
|
+
`DeACED` (a nod to the `AC ED` stream magic, and to *de*-serialization) is a small,
|
|
11
|
+
**dependency-free** Python library and CLI. It is a port of
|
|
12
|
+
[SerializationDumper](https://github.com/NickstaDB/SerializationDumper) by Nicky Bloor,
|
|
13
|
+
with several correctness fixes (see [NOTICE](NOTICE)).
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pip install deaced # once published
|
|
19
|
+
# or from source:
|
|
20
|
+
pip install .
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## CLI
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
deaced -r dump.bin # raw serialized file
|
|
27
|
+
cat dump.bin | deaced -r - # from stdin
|
|
28
|
+
deaced -x aced0005740004414243... # hex on the command line
|
|
29
|
+
deaced -f hexdump.txt # file of hex-ascii bytes
|
|
30
|
+
deaced -r dump.bin --offsets # prefix each line with '@<byte-offset>|'
|
|
31
|
+
deaced -r dump.bin -o out.txt # write to a file
|
|
32
|
+
deaced -r dump.bin -F json # structured, machine-readable JSON
|
|
33
|
+
deaced -r dump.bin -F pretty # compact, human-readable data tree
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Example:
|
|
37
|
+
|
|
38
|
+
```text
|
|
39
|
+
$ deaced -x aced0005740004414243447071007e0000
|
|
40
|
+
|
|
41
|
+
STREAM_MAGIC - 0xac ed
|
|
42
|
+
STREAM_VERSION - 0x00 05
|
|
43
|
+
Contents
|
|
44
|
+
TC_STRING - 0x74
|
|
45
|
+
newHandle 0x00 7e 00 00
|
|
46
|
+
Length - 4 - 0x00 04
|
|
47
|
+
Value - ABCD - 0x41424344
|
|
48
|
+
TC_NULL - 0x70
|
|
49
|
+
TC_REFERENCE - 0x71
|
|
50
|
+
Handle - 8257536 - 0x00 7e 00 00
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Library
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
from deaced import dump, parse
|
|
57
|
+
|
|
58
|
+
data = open("dump.bin", "rb").read()
|
|
59
|
+
|
|
60
|
+
print(dump(data)) # text dump (default)
|
|
61
|
+
print(dump(data, offsets=True)) # with '@<offset>|' prefixes
|
|
62
|
+
print(dump(data, format="json")) # structured JSON
|
|
63
|
+
print(dump(data, format="pretty")) # compact human-readable tree
|
|
64
|
+
|
|
65
|
+
tree = parse(data) # the semantic AST (deaced.model.Stream)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Why a port?
|
|
69
|
+
|
|
70
|
+
Removes the JVM dependency (the original is a Java jar), runs anywhere Python does,
|
|
71
|
+
integrates into Python tooling, and fixes real bugs in the original — most notably
|
|
72
|
+
the `(long)`/`(double)` integer-shift bug (`0xFFFFFFFFFFFFFFFF` was shown as
|
|
73
|
+
`-4294967297` instead of `-1`). See [NOTICE](NOTICE) for the full list.
|
|
74
|
+
|
|
75
|
+
## Safety
|
|
76
|
+
|
|
77
|
+
DeACED is built to inspect **untrusted, attacker-controlled** serialization
|
|
78
|
+
streams (that is the usual reason to dump one), so:
|
|
79
|
+
|
|
80
|
+
- **It never deserializes Java objects.** DeACED only reads and decodes the byte
|
|
81
|
+
stream into a descriptive tree — it does not load classes, instantiate objects,
|
|
82
|
+
or invoke `readObject`. The classic Java deserialization gadget chains cannot
|
|
83
|
+
fire through it.
|
|
84
|
+
- **It fails cleanly on malformed input.** Truncated or structurally-invalid
|
|
85
|
+
streams raise a `SerDumpError` carrying the byte offset; negative
|
|
86
|
+
lengths/counts are rejected rather than driving huge allocations; deeply nested
|
|
87
|
+
streams raise an error instead of crashing the interpreter.
|
|
88
|
+
|
|
89
|
+
Resource use is still proportional to input size — apply your own limits when
|
|
90
|
+
dumping data straight off the network. See [SECURITY.md](SECURITY.md).
|
|
91
|
+
|
|
92
|
+
## Development
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
pip install -e ".[dev]"
|
|
96
|
+
pytest # tests (tiny synthetic fixtures)
|
|
97
|
+
ruff check .
|
|
98
|
+
ruff format --check .
|
|
99
|
+
mypy
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Contributing
|
|
103
|
+
|
|
104
|
+
Contributions welcome — see [CONTRIBUTING](CONTRIBUTING.md). The stream "rebuild"
|
|
105
|
+
(`-b`) mode of the original is intentionally not ported (see [NOTICE](NOTICE)).
|
|
106
|
+
|
|
107
|
+
## Credits & license
|
|
108
|
+
|
|
109
|
+
Python port by **Alexander Gmar** ([@gmarav](https://github.com/gmarav)) —
|
|
110
|
+
[github.com/gmarav/DeACED](https://github.com/gmarav/DeACED).
|
|
111
|
+
Based on **SerializationDumper** by **Nicky Bloor** ([@NickstaDB](https://github.com/NickstaDB)).
|
|
112
|
+
Licensed under the [MIT License](LICENSE); port © 2026 Alexander Gmar, original © 2017 Nicky Bloor.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "deaced"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Dump and inspect Java Object Serialization (0xAC 0xED) streams and RMI packets in human-readable form."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE", "NOTICE"]
|
|
13
|
+
authors = [{ name = "Alexander Gmar" }]
|
|
14
|
+
keywords = ["java", "serialization", "deserialization", "rmi", "security", "aced", "dump"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 4 - Beta",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"Environment :: Console",
|
|
19
|
+
"Topic :: Security",
|
|
20
|
+
"Topic :: Software Development :: Disassemblers",
|
|
21
|
+
"Topic :: Utilities",
|
|
22
|
+
"Programming Language :: Python :: 3",
|
|
23
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
24
|
+
"Programming Language :: Python :: 3.10",
|
|
25
|
+
"Programming Language :: Python :: 3.11",
|
|
26
|
+
"Programming Language :: Python :: 3.12",
|
|
27
|
+
"Programming Language :: Python :: 3.13",
|
|
28
|
+
"Programming Language :: Python :: 3.14",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[project.urls]
|
|
32
|
+
Homepage = "https://github.com/gmarav/DeACED"
|
|
33
|
+
Repository = "https://github.com/gmarav/DeACED"
|
|
34
|
+
Documentation = "https://github.com/gmarav/DeACED#readme"
|
|
35
|
+
Issues = "https://github.com/gmarav/DeACED/issues"
|
|
36
|
+
Changelog = "https://github.com/gmarav/DeACED/blob/main/CHANGELOG.md"
|
|
37
|
+
|
|
38
|
+
[project.scripts]
|
|
39
|
+
deaced = "deaced.cli:main"
|
|
40
|
+
|
|
41
|
+
[project.optional-dependencies]
|
|
42
|
+
dev = ["pytest>=7", "pytest-cov>=4", "ruff>=0.4", "mypy>=1.8", "build>=1", "twine>=5", "pre-commit>=3"]
|
|
43
|
+
|
|
44
|
+
[tool.setuptools.packages.find]
|
|
45
|
+
where = ["src"]
|
|
46
|
+
|
|
47
|
+
[tool.setuptools.package-data]
|
|
48
|
+
deaced = ["py.typed"]
|
|
49
|
+
|
|
50
|
+
[tool.ruff]
|
|
51
|
+
line-length = 100
|
|
52
|
+
target-version = "py310"
|
|
53
|
+
|
|
54
|
+
[tool.ruff.lint]
|
|
55
|
+
select = ["E", "F", "I", "UP", "B"]
|
|
56
|
+
|
|
57
|
+
[tool.mypy]
|
|
58
|
+
python_version = "3.10"
|
|
59
|
+
strict = true
|
|
60
|
+
files = ["src/deaced"]
|
|
61
|
+
|
|
62
|
+
[tool.pytest.ini_options]
|
|
63
|
+
testpaths = ["tests"]
|
|
64
|
+
pythonpath = ["src"]
|
|
65
|
+
|
|
66
|
+
[tool.coverage.run]
|
|
67
|
+
source = ["deaced"]
|
|
68
|
+
|
|
69
|
+
[tool.coverage.report]
|
|
70
|
+
show_missing = true
|
|
71
|
+
fail_under = 90
|
deaced-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""DeACED - dump and inspect Java Object Serialization (``0xAC 0xED``) streams.
|
|
2
|
+
|
|
3
|
+
A small, dependency-free library and CLI that turns a Java serialization stream
|
|
4
|
+
(and Java RMI packet contents) into a human-readable, hierarchical text dump.
|
|
5
|
+
|
|
6
|
+
This is a Python port of SerializationDumper by Nicky Bloor (MIT); see NOTICE.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from .model import Stream
|
|
12
|
+
from .parser import Parser, parse, run_deep
|
|
13
|
+
from .render import render_json, render_pretty, render_text
|
|
14
|
+
|
|
15
|
+
__all__ = ["dump", "parse", "__version__"]
|
|
16
|
+
__version__ = "0.1.0"
|
|
17
|
+
|
|
18
|
+
_FORMATS = ("text", "json", "pretty")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def dump(data: bytes, *, format: str = "text", offsets: bool = False) -> str:
|
|
22
|
+
"""Parse ``data`` and render it.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
data: The raw serialization stream.
|
|
26
|
+
format: Output format -- ``"text"`` (the default hierarchical dump,
|
|
27
|
+
byte-for-byte compatible with the patched upstream jar), ``"json"``
|
|
28
|
+
(a structured machine-readable view), or ``"pretty"`` (a compact
|
|
29
|
+
human-readable data tree).
|
|
30
|
+
offsets: When true (``text`` only), prefix every line with
|
|
31
|
+
``@<byte-offset>|``.
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
The rendered output as a single string.
|
|
35
|
+
"""
|
|
36
|
+
if format not in _FORMATS:
|
|
37
|
+
raise ValueError(f"unknown format: {format!r}")
|
|
38
|
+
if offsets and format != "text":
|
|
39
|
+
raise ValueError("offsets are only supported for the 'text' format")
|
|
40
|
+
|
|
41
|
+
def render() -> str:
|
|
42
|
+
node: Stream = Parser(data).parse()
|
|
43
|
+
if format == "text":
|
|
44
|
+
return render_text(node, offsets=offsets)
|
|
45
|
+
if format == "json":
|
|
46
|
+
return render_json(node)
|
|
47
|
+
return render_pretty(node)
|
|
48
|
+
|
|
49
|
+
return run_deep(render)
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""Command-line interface for DeACED."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
from . import __version__, dump
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _read_input(args: argparse.Namespace) -> bytes:
|
|
12
|
+
if args.hex is not None:
|
|
13
|
+
return bytes.fromhex("".join(args.hex.split()))
|
|
14
|
+
if args.hex_file is not None:
|
|
15
|
+
with open(args.hex_file, "rb") as f:
|
|
16
|
+
txt = f.read().decode("latin-1")
|
|
17
|
+
return bytes.fromhex("".join(c for c in txt if c in "0123456789abcdefABCDEF"))
|
|
18
|
+
# raw bytes ('-' = stdin)
|
|
19
|
+
if args.raw == "-":
|
|
20
|
+
return sys.stdin.buffer.read()
|
|
21
|
+
with open(args.raw, "rb") as f:
|
|
22
|
+
return f.read()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def main(argv: list[str] | None = None) -> int:
|
|
26
|
+
p = argparse.ArgumentParser(
|
|
27
|
+
prog="deaced",
|
|
28
|
+
description="Dump and inspect Java Object Serialization (0xAC 0xED) streams "
|
|
29
|
+
"and RMI packets in human-readable form.",
|
|
30
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
31
|
+
epilog=(
|
|
32
|
+
"examples:\n"
|
|
33
|
+
" deaced -r dump.bin dump a raw serialized file\n"
|
|
34
|
+
" cat dump.bin | deaced -r - read from stdin\n"
|
|
35
|
+
" deaced -x aced0005740004414243... decode hex from the command line\n"
|
|
36
|
+
" deaced -f hexdump.txt read a file of hex-ascii bytes\n"
|
|
37
|
+
" deaced -r dump.bin -F json emit structured JSON\n"
|
|
38
|
+
" deaced -r dump.bin -F pretty emit a compact data tree\n"
|
|
39
|
+
" deaced -r dump.bin --offsets annotate each line with its byte offset\n"
|
|
40
|
+
" deaced -r dump.bin -o out.txt write the dump to a file"
|
|
41
|
+
),
|
|
42
|
+
)
|
|
43
|
+
src = p.add_mutually_exclusive_group(required=True)
|
|
44
|
+
src.add_argument(
|
|
45
|
+
"-r", "--raw", metavar="FILE", help="raw binary serialization file ('-' for stdin)"
|
|
46
|
+
)
|
|
47
|
+
src.add_argument(
|
|
48
|
+
"-f", "--hex-file", metavar="FILE", dest="hex_file", help="file of hex-ascii bytes"
|
|
49
|
+
)
|
|
50
|
+
src.add_argument("-x", "--hex", metavar="HEX", help="hex-ascii bytes on the command line")
|
|
51
|
+
p.add_argument("-o", "--output", metavar="FILE", help="write the dump to FILE (default stdout)")
|
|
52
|
+
p.add_argument(
|
|
53
|
+
"-F",
|
|
54
|
+
"--format",
|
|
55
|
+
choices=["text", "json", "pretty"],
|
|
56
|
+
default="text",
|
|
57
|
+
help="output format (default: text)",
|
|
58
|
+
)
|
|
59
|
+
p.add_argument(
|
|
60
|
+
"--offsets",
|
|
61
|
+
action="store_true",
|
|
62
|
+
help="prefix each line with '@<byte-offset>|' (text format only)",
|
|
63
|
+
)
|
|
64
|
+
p.add_argument("-V", "--version", action="version", version=f"deaced {__version__}")
|
|
65
|
+
args = p.parse_args(argv)
|
|
66
|
+
|
|
67
|
+
if args.offsets and args.format != "text":
|
|
68
|
+
p.error("--offsets is only valid with -F text")
|
|
69
|
+
|
|
70
|
+
try:
|
|
71
|
+
data = _read_input(args)
|
|
72
|
+
except (OSError, ValueError) as e:
|
|
73
|
+
print(f"deaced: cannot read input: {e}", file=sys.stderr)
|
|
74
|
+
return 2
|
|
75
|
+
|
|
76
|
+
try:
|
|
77
|
+
text = dump(data, format=args.format, offsets=args.offsets)
|
|
78
|
+
except Exception as e: # parser errors carry an offset in the message
|
|
79
|
+
print(f"deaced: parse error: {e}", file=sys.stderr)
|
|
80
|
+
return 1
|
|
81
|
+
|
|
82
|
+
if args.output:
|
|
83
|
+
with open(args.output, "w", encoding="utf-8") as f:
|
|
84
|
+
f.write(text)
|
|
85
|
+
else:
|
|
86
|
+
reconfigure = getattr(sys.stdout, "reconfigure", None)
|
|
87
|
+
if reconfigure is not None:
|
|
88
|
+
reconfigure(encoding="utf-8")
|
|
89
|
+
sys.stdout.write(text)
|
|
90
|
+
return 0
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
if __name__ == "__main__": # pragma: no cover
|
|
94
|
+
raise SystemExit(main())
|