Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
6f5d1e9
Remove v1 code
MatrixEditor Jan 28, 2025
2fc9ad6
Change dependencies and edition to 2021
MatrixEditor Feb 5, 2025
eae7038
Generic errors for validating DEX files
MatrixEditor Feb 5, 2025
627dd75
Wrapper functions to parse leb128 integers
MatrixEditor Feb 5, 2025
a4d0635
UTF module to parse mutf8
MatrixEditor Feb 5, 2025
6dfc224
Main library entry
MatrixEditor Feb 5, 2025
81eaa27
Dex file structs (incomplete)
MatrixEditor Feb 5, 2025
3f65228
main DexFile struct, class accessor and dex verifier (incomplete)
MatrixEditor Feb 5, 2025
a042f07
CodeItem accessor and instructions
MatrixEditor Feb 5, 2025
9fa71e8
Register access for instructions
MatrixEditor Feb 6, 2025
c39b8b5
Implementation for dumping instructions
MatrixEditor Feb 7, 2025
cc24a59
Rewrite DexFile internal buffer to support inmemory data
MatrixEditor Feb 8, 2025
ac4ea43
Fuzzing test for main DEX parsing method
MatrixEditor Feb 8, 2025
b1b8868
updated dump.rs to match updated DexFile implementation
MatrixEditor Feb 8, 2025
7e3a65d
renamed pretty_opts to prettify
MatrixEditor Feb 8, 2025
b202c67
TryItem implementation and mutable dex file preparations
MatrixEditor Feb 8, 2025
7e8119d
Add benchmark support
MatrixEditor Feb 8, 2025
c446ac2
Annotation accessor for class data
MatrixEditor Feb 8, 2025
f92dc80
fuzzung for instruction related operations
MatrixEditor Feb 8, 2025
cf7d74c
Updated fuzzing tests for class accessor
MatrixEditor Feb 8, 2025
ac0aa5b
EncodedValue implementation and annotation accessor changes
MatrixEditor Feb 9, 2025
b7b9ac4
Added some examples along with MUTF8 fuzzing
MatrixEditor Feb 9, 2025
dad0ccb
Added a check to make parsing mutf8 sequences fuzzing resistant (at l…
MatrixEditor Feb 10, 2025
02b35f9
Added (rudimentary) support for Python extension
MatrixEditor Feb 12, 2025
f7fe1e0
Removed obsolte main
MatrixEditor Feb 12, 2025
6a4f1e4
Updated Python API to always utilize container objects
MatrixEditor Feb 13, 2025
aa22371
Added tests for Python API
MatrixEditor Feb 13, 2025
630b964
Updated debug info parser (currently unstable)
MatrixEditor Feb 13, 2025
067380e
Reordered Python extension to include type stubs
MatrixEditor Feb 13, 2025
33df152
Added python interface for common structs
MatrixEditor Feb 15, 2025
ba910bf
Moved Python API into Rust code files
MatrixEditor Feb 15, 2025
cce89de
Add binding for Instruction related API
MatrixEditor Feb 16, 2025
2a3d621
Added binding for EncodedValue
MatrixEditor Feb 16, 2025
f79ee4c
Added parsing support for complex opcodes
MatrixEditor Feb 18, 2025
b6ce9ee
Add complex instruction to pretty dump
MatrixEditor Feb 18, 2025
1a72911
Add support for TryItem and EncodedCatchHandler
MatrixEditor Feb 21, 2025
f7cfe53
Fix CatchHandlerData offset calculation
MatrixEditor Feb 23, 2025
d53a005
Updated Rust API for CatchHandlerData parser
MatrixEditor Feb 23, 2025
bcbbe3c
Add annotation API
MatrixEditor Mar 1, 2025
a7e72a3
Rust API code cleamup
MatrixEditor Mar 1, 2025
a4a3cd5
feat: dex rewrite + CLI support
MatrixEditor Apr 6, 2026
c6fb661
update LICENSE
MatrixEditor Apr 6, 2026
f560fce
feat(python): update stub files
MatrixEditor Apr 6, 2026
ac9059e
docs: update docstrings
MatrixEditor Apr 6, 2026
63ef6d3
docs: update readme
MatrixEditor Apr 6, 2026
01fd39c
docs: update license
MatrixEditor Apr 6, 2026
fce44f2
Merge branch 'master' into dev/v2-rewrite
MatrixEditor Apr 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -178,4 +178,6 @@ cython_debug/
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
_build/
_build/

*.class
48 changes: 40 additions & 8 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,13 +1,45 @@
[package]
name = "dexrs"
version = "0.1.0"
edition = "2024"
version = "1.0.0"
edition = "2021"

[dependencies]
adler32 = "1.2.0"
binrw = "0.13.3"
bitflags = "2.5.0"
byteorder = "1.5.0"
lazy_static = "1.4.0"
leb128 = "0.2.5"
openssl = "0.10.64"
anyhow = "1"
clap = { version = "4", features = ["derive"] }
comfy-table = "7"
crossterm = "0.28"
ratatui = { version = "0.29", optional = true }
leb128fmt = "0.1.0"
memmap2 = "0.9.5"
plain = "0.2.3"
pyo3 = { version = "0.23.4", optional = true, features = ["extension-module"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
thiserror = "2.0.11"
varint-simd = "0.4.1"

[features]
default = []
python = ["pyo3"]
vdex = []
tui = ["ratatui"]

[lib]
name = "dexrs"
crate-type = ["cdylib", "rlib"]

[[bin]]
name = "dexrs"
path = "src/bin/dexrs/main.rs"

[dev-dependencies]
criterion = { version = "0.5", features = ["html_reports"] }

[[bench]]
name = "parse"
harness = false

[[bench]]
name = "edit"
harness = false
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2024, MatrixEditor
Copyright (c) 2024-2026, MatrixEditor

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
208 changes: 172 additions & 36 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,74 +2,210 @@

> [!IMPORTANT]
> Branch `v2-rewrite` contains a complete rewrite of this library including a Python binding. Installation is as follows:
>
>
> Crate:
> ```bash
> # add to project
> cargo add --git https://github.com/MatrixEditor/dexrs --branch dev/v2-rewrite
> # install lib
> cargo install --git https://github.com/MatrixEditor/dexrs --branch dev/v2-rewrite
> ```
>
> Python Binding
> Python:
> ```bash
> pip install -v git+https://github.com/MatrixEditor/dexrs@dev/v2-rewrite
> ```

**DEXrs** is an exploratory project in Rust aimed at developing a decompiler for Android executable files (DEX files).
**DEXrs** is a Rust library and CLI tool for parsing, inspecting, and modifying Android DEX files. It covers a zero-copy parser, a Dalvik disassembler, a full-featured terminal UI, a DEX modification API, and Python bindings via PyO3.

#### What this project already covers:
#### What this project covers

- [x] A (*blazingly fast*) DEX file parser using lazy parsing
- [x] A simple disassembler for Dalvik byte code
- [x] A simplistic Smali decompiler
- [x] Zero-copy, lazy DEX file parser (fuzzing-hardened)
- [x] Dalvik bytecode disassembler
- [x] `dexrs` CLI — 12 subcommands for inspection and modification
- [x] Interactive TUI (`dexrs inspect`) via ratatui/crossterm
- [x] DEX modification API — in-place patching and structural editing
- [x] Python extension via PyO3 (parser + editor)
- [ ] Benchmarks (WIP)
- [ ] Smali disassembler / decompiler

#### Roadmap

- [ ] Basic Java decompiler
- [ ] Bytecode modification and DEX rebuild
## Installation

```bash
# CLI binary
cargo install --git https://github.com/MatrixEditor/dexrs dexrs
# with TUI
cargo install --git https://github.com/MatrixEditor/dexrs -F tui dexrs

# Python package
pip install -v git+https://github.com/MatrixEditor/dexrs@dev/v2-rewrite
```

## Installation

## CLI — `dexrs`

```
dexrs [--no-color] [--no-verify] [--json] <COMMAND> <FILE> [OPTIONS]
```

### Inspection

| Command | Description |
|---|---|
| `info` | File header, integrity hashes, section counts |
| `map` | Map list — all section types and their offsets |
| `classes` | All class definitions with access flags |
| `class --class <NAME>` | Single class (fields + methods) |
| `methods` | All methods across all classes |
| `fields` | All fields across all classes |
| `disasm --class <C> --method <M>` | Disassemble one method |
| `strings` | Full string pool |
| `types` | All type descriptors |
| `inspect` | Interactive TUI (see below) |

Install DEXrs using Cargo:
```bash
cargo install --git https://github.com/MatrixEditor/dexrs dexrs
dexrs info classes.dex
dexrs classes classes.dex --no-color
dexrs class classes.dex --class LMain;
dexrs disasm classes.dex --class LMain; --method main --json
```

## Usage
### Modification

#### `patch` — in-place (overwrites source file)

```bash
# Set class access flags
dexrs patch flags <FILE> --class <CLASS> --flags <HEX>

# Overwrite a single instruction word
dexrs patch insn <FILE> --offset <HEX_OFFSET> --pc <CODE_UNIT> --word <HEX>
```

```bash
dexrs patch flags classes.dex --class LMain; --flags 0x11 # public final
```

#### `edit` — structural (writes to `--output`)

```bash
dexrs edit rename-class <FILE> <OLD> <NEW> --output <OUT>
dexrs edit set-flags <FILE> --class <C> --flags <F> --output <OUT>
dexrs edit set-method-flags <FILE> --class <C> --method <M> --flags <F> --output <OUT>
dexrs edit clear-hiddenapi <FILE> --output <OUT>
```

```bash
dexrs edit rename-class classes.dex LMain; LRenamedMain; --output out.dex
dexrs edit set-flags classes.dex --class LMain; --flags 0x21 --output out.dex
```

### Disassembling DEX files

Here’s a quick example of how to disassemble a DEX file:
## Rust API

### Parsing

```rust
let mut f = File::open("classes.dex").expect("file not found");
// parse DEX input and verify its contents
let mut dex = Dex::read(&mut f, true)?;

let class = dex.get_class_def(0)?;
if let Some(method) = class.get_direct_method(0) {
for insn in method.disasm(&mut dex)? {
println!(" {:#06x}: {:?}", insn.range.start, insn);
}
}
use dexrs::file::{verifier::VerifyPreset, DexFile, DexFileContainer, DexLocation};

// From a file with verification
let file = std::fs::File::open("classes.dex")?;
let dex = DexFileContainer::new(&file)
.verify(true)
.verify_checksum(true)
.open()?;

// From memory
let dex = DexFile::open(data, DexLocation::InMemory, VerifyPreset::None)?;
```

## Decompilation to Smali
See `examples/parse_dex_file.rs` and `examples/dex_basic_ops.rs` for full usage.

### DEX modification — `DexEditor`

`DexEditor` owns the DEX bytes and exposes named mutations. Finalise with
`build()` -> `Vec<u8>` or `write_to(path)` — both recalculate the Adler32 checksum.

```rust
use dexrs::smali::SmaliWrite;
use std::path::Path;
use dexrs::file::DexEditor;

let mut editor = DexEditor::from_file(Path::new("classes.dex"))?;
// or: DexEditor::from_bytes(bytes)?

// Accepts dotted ("com.example.Foo"), slash, or descriptor ("Lcom/example/Foo;") form
editor.set_class_access_flags("LMain;", 0x0011 /* public final */)?;
editor.rename_class("LMain;", "LRenamedMain;")?;
editor.set_method_access_flags("LMain;", "main", 0x0009 /* public static */)?;
editor.clear_hiddenapi_flags().ok(); // no-op if section absent

// Finalise
let bytes: Vec<u8> = editor.build()?;
// or:
editor.write_to(Path::new("out.dex"))?;
```

### Low-level checksum

```rust
use dexrs::file::patch::update_checksum;

let mut raw = std::fs::read("classes.dex")?;
// ... raw byte mutations ...
update_checksum(&mut raw); // recalculate Adler32 in-place
```

See `examples/dex_edit.rs` for a complete runnable example.


let mut f = File::open("classes.dex").expect("file not found");
let mut dex = Dex::read(&mut f, true)?;
## Python API

let class = dex.get_class_def(0)?;
let mut stdout = std::io::stdout();
stdout.write_class(&class, &mut dex)?;
### Parsing

```python
from dexrs import DexFile, VerifyPreset, FileDexContainer

container = FileDexContainer("classes.dex")
dex = DexFile.from_container(container, verify=VerifyPreset.All)

for cls in dex.get_class_defs():
print(cls)
```

### DEX modification — `DexEditor`

```python
from dexrs import DexEditor

editor = DexEditor.from_file("classes.dex")
# or: DexEditor.from_bytes(open("classes.dex","rb").read())

editor.set_class_access_flags("LMain;", 0x0001) # public
editor.rename_class("LMain;", "LRenamedMain;") # rebuild string pool
editor.set_method_access_flags("LMain;", "main", 0x0009) # public static
editor.clear_hiddenapi_flags() # strip hidden-API metadata

# Get bytes
data = editor.build()
open("out.dex", "wb").write(data)

# Or write directly (editor is consumed)
editor.write_to("out.dex")
```

#### Common access flag values

| Value | Meaning |
|---|---|
| `0x0001` | `public` |
| `0x0002` | `private` |
| `0x0004` | `protected` |
| `0x0008` | `static` |
| `0x0010` | `final` |
| `0x0100` | `native` |
| `0x0400` | `abstract` |
| `0x1000` | `synthetic` |


## License

This project is licensed under the [MIT license](LICENSE)
This project is licensed under the [MIT license](LICENSE).
71 changes: 71 additions & 0 deletions benches/edit.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use dexrs::file::{patch::update_checksum, DexEditor, DexFile, DexLocation};

const PRIME: &[u8] = include_bytes!("../tests/prime/prime.dex");

fn bench_editor_from_bytes(c: &mut Criterion) {
c.bench_function("editor_from_bytes", |b| {
b.iter(|| black_box(DexEditor::from_bytes(PRIME.to_vec()).unwrap()))
});
}

fn bench_set_class_flags(c: &mut Criterion) {
c.bench_function("set_class_flags_and_build", |b| {
b.iter(|| {
let mut ed = DexEditor::from_bytes(PRIME.to_vec()).unwrap();
ed.set_class_access_flags(black_box("Lprime/prime;"), black_box(0x0011u32)).unwrap();
black_box(ed.build().unwrap());
})
});
}

fn bench_rename_same_length(c: &mut Criterion) {
c.bench_function("rename_class_same_length", |b| {
b.iter(|| {
let mut ed = DexEditor::from_bytes(PRIME.to_vec()).unwrap();
ed.rename_class(black_box("Lprime/prime;"), black_box("Lprime/other;")).unwrap();
black_box(ed.build().unwrap());
})
});
}

fn bench_rename_different_length(c: &mut Criterion) {
c.bench_function("rename_class_different_length", |b| {
b.iter(|| {
let mut ed = DexEditor::from_bytes(PRIME.to_vec()).unwrap();
ed.rename_class(black_box("Lprime/prime;"), black_box("Lprime/renamed;")).unwrap();
black_box(ed.build().unwrap());
})
});
}

fn bench_update_checksum(c: &mut Criterion) {
c.bench_function("update_checksum", |b| {
let mut buf = PRIME.to_vec();
b.iter(|| update_checksum(black_box(&mut buf)))
});
}

fn bench_full_pipeline(c: &mut Criterion) {
c.bench_function("full_edit_pipeline", |b| {
b.iter(|| {
let mut ed = DexEditor::from_bytes(PRIME.to_vec()).unwrap();
ed.set_class_access_flags("Lprime/prime;", 0x0011).unwrap();
ed.rename_class("Lprime/prime;", "Lprime/renamed;").unwrap();
let bytes = ed.build().unwrap();
let dex = DexFile::from_raw_parts(black_box(&bytes), DexLocation::InMemory).unwrap();
black_box(dex.num_class_defs());
})
});
}

criterion_group!(
benches,
bench_editor_from_bytes,
bench_set_class_flags,
bench_rename_same_length,
bench_rename_different_length,
bench_update_checksum,
bench_full_pipeline,
);
criterion_main!(benches);
Loading