Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
26 changes: 22 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
# Droid ASC: R8 Compiler Optimization as a DeCompiler Primitive

> **Rust version:** This project has been rewritten 1:1 in Rust as [rust_ASC](https://github.com/MG1937/rust_ASC). The Rust version is 10× faster and supports full APK decompilation: with 16 threads, it decompiles a 383 MB APK in just 4 seconds. ASC and rust_ASC will be updated in sync going forward.
> **Rust version:** This project has been rewritten 1:1 in Rust as [rust_ASC](https://github.com/MG1937/rust_ASC). The Rust version is 10× faster and supports full APK decompilation: with 16 threads, it decompiles a 383 MB APK in just 4 seconds.
>
> If your workflow is written in Python, you can continue integrating with this project; it remains the fastest Python Android decompiler available. If you only need a binary, please use [rust_ASC](https://github.com/MG1937/rust_ASC).
>
> ASC and rust_ASC will be maintained in parallel, so users can choose the integration style that best fits their workflow.

https://blackhat.com/europe/arsenal/schedule/index.html#droid-asc-r8-compiler-optimization-as-a-decompiler-primitive-54834

Expand Down Expand Up @@ -77,14 +79,16 @@ pip install .
After installation, the `droidasc` CLI command is available globally:

```
usage: droidasc [-h] {getclass,listclass,getmanifest,findrefs} ...
usage: droidasc [-h] {getclass,disassemble,listclass,getmanifest,findrefs} ...

ASC tooling entry (v0.1.2.post1).

positional arguments:
{getclass,listclass,getmanifest,findrefs}
{getclass,disassemble,listclass,getmanifest,findrefs}
getclass Locate the target class in APK, extract one DEX in
memory, then decompile.
disassemble Extract a class, rebuild a small DEX, then disassemble
it with dadguard.
listclass List classes across all DEX entries; use --prefix to
filter by package/class prefix.
getmanifest Decode AndroidManifest.xml from APK and print it as
Expand All @@ -99,8 +103,14 @@ examples:
droidasc app.apk --gui
droidasc getclass app.apk Lcom/poc/Main; -o Main.java
droidasc getclass app.apk com.poc.Main --threads 16
droidasc disassemble app.apk Lcom/poc/Main; -o Main.smali
droidasc disassemble app.apk com.poc.Main --debug
droidasc disassemble app.apk com.poc.Main --debuginfo
droidasc disassemble app.apk com.poc.Main --method 'run(I)V'
droidasc listclass app.apk
droidasc listclass app.apk -o classes.txt
droidasc listclass app.apk --prefix com.poc
droidasc getmanifest app.apk
droidasc getmanifest app.apk -o AndroidManifest.xml
droidasc findrefs app.apk string token -o string_refs.txt
droidasc findrefs app.apk type com.poc.Main
Expand All @@ -124,6 +134,14 @@ line. With `-o`, output is written to the selected file instead of stdout.
`--prefix com.poc` filters by `Lcom/poc`; an already normalized prefix such as
`Lcom/poc` is kept unchanged.

`disassemble` accepts the same dotted or Dalvik class formats as `getclass`.
It extracts the target class from the APK, rebuilds a small DEX in memory, and
disassembles the rebuilt DEX with `dadguard`. `--debug` enables timing output
and instruction offset comments; `--debuginfo` enables DEX `.line` and local
variable directives. `--method 'run(I)V'` restricts output to one exact method
signature; a bare method name is rejected. All commands that support `-o` write
to stdout when `-o` is omitted.

You can also use `python main.py` as before — it delegates to the same entry point.

# Sponsors
Expand All @@ -143,4 +161,4 @@ ASC is built with the hope that it can genuinely help everyone improve their rev

Thanks to the [Androguard](https://github.com/androguard/androguard) project
for providing the Android reverse-engineering and DEX analysis foundation used
by Droid ASC.
by Droid ASC.
104 changes: 48 additions & 56 deletions dadguard/core/dex/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -568,7 +568,9 @@ def __init__(self, size, buff: BinaryIO, cm: ClassManager) -> None:
except (UnicodeDecodeError, ValueError):
self.dex_version = 35 # assume a common version...

if zlib.adler32(read_at(buff, self.offset + 12)) != self.checksum:
if self.CM.vm.verify_checksum and zlib.adler32(
read_at(buff, self.offset + 12)
) != self.checksum:
raise ValueError("Wrong Adler32 checksum for DEX file!")

if self.file_size != buff.raw.getbuffer().nbytes:
Expand Down Expand Up @@ -4473,7 +4475,7 @@ def get_kind(cm: ClassManager, kind: int, value: int) -> str:
method = cm.get_method_ref(value)
class_name = method.get_class_name()
name = method.get_name()
descriptor = method.get_descriptor()
descriptor = method.get_descriptor()

return "{}->{}{}".format(class_name, name, descriptor)

Expand All @@ -4486,7 +4488,7 @@ def get_kind(cm: ClassManager, kind: int, value: int) -> str:

elif kind == Kind.FIELD:
class_name, proto, field_name = cm.get_field(value)
return "{}->{} {}".format(class_name, field_name, proto)
return "{}->{}:{}".format(class_name, field_name, proto)

elif kind == Kind.TYPE:
return cm.get_type(value)
Expand All @@ -4510,6 +4512,25 @@ def get_kind(cm: ClassManager, kind: int, value: int) -> str:
return None


def _format_method_reference(cm: ClassManager, value: int) -> str:
method = cm.get_method_ref(value)
descriptor = (
method.get_real_descriptor()
if hasattr(method, "get_real_descriptor")
else "".join(method.get_descriptor().split())
)
return "{}->{}{}".format(
method.get_class_name(),
method.get_name(),
descriptor,
)


def _format_proto_descriptor(cm: ClassManager, value: int) -> str:
proto = cm.get_proto(value)
return "{}{}".format("".join(proto[0].split()), proto[1])


class Instruction:
"""
This class represents a Dalvik instruction
Expand Down Expand Up @@ -5131,34 +5152,11 @@ def __init__(self, cm: ClassManager, buff: bytes) -> None:

def get_output(self, idx: int = -1) -> str:
kind = get_kind(self.cm, self.get_kind(), self.BBBB)

if self.A == 0:
return "%s" % kind
elif self.A == 1:
return "v%d, %s" % (self.C, kind)
elif self.A == 2:
return "v%d, v%d, %s" % (self.C, self.D, kind)
elif self.A == 3:
return "v%d, v%d, v%d, %s" % (self.C, self.D, self.E, kind)
elif self.A == 4:
return "v%d, v%d, v%d, v%d, %s" % (
self.C,
self.D,
self.E,
self.F,
kind,
)
elif self.A == 5:
return "v%d, v%d, v%d, v%d, v%d, %s" % (
self.C,
self.D,
self.E,
self.F,
self.G,
kind,
)

return ''
registers = [self.C, self.D, self.E, self.F, self.G][: self.A]
register_text = "{{{}}}".format(
", ".join("v{}".format(register) for register in registers)
)
return "{}, {}".format(register_text, kind)

def get_operands(self, idx: int = -1) -> list[tuple]:
l = []
Expand Down Expand Up @@ -5951,9 +5949,10 @@ def get_output(self, idx: int = -1) -> str:
kind = get_kind(self.cm, self.get_kind(), self.BBBB)

if self.CCCC == self.NNNN:
return "v{}, {}".format(self.CCCC, kind)
else:
return "v{} ... v{}, {}".format(self.CCCC, self.NNNN, kind)
register_text = "{{v{}}}".format(self.CCCC)
else:
register_text = "{{v{} .. v{}}}".format(self.CCCC, self.NNNN)
return "{}, {}".format(register_text, kind)

def get_operands(self, idx: int = -1) -> list[tuple[Operand, int]]:
kind = get_kind(self.cm, self.get_kind(), self.BBBB)
Expand Down Expand Up @@ -6549,25 +6548,15 @@ def get_raw(self) -> bytes:
)

def get_output(self, idx: int = -1) -> str:
# FIXME get_kind of BBBB (method) and HHHH (proto)
if self.A == 1:
return 'v{}, {}, {}'.format(self.C, self.BBBB, self.HHHH)
if self.A == 2:
return 'v{}, v{}, {}, {}'.format(
self.C, self.D, self.BBBB, self.HHHH
)
if self.A == 3:
return 'v{}, v{}, v{}, {}, {}'.format(
self.C, self.D, self.E, self.BBBB, self.HHHH
)
if self.A == 4:
return 'v{}, v{}, v{}, v{}, {}, {}'.format(
self.C, self.D, self.E, self.F, self.BBBB, self.HHHH
)
if self.A == 5:
return 'v{}, v{}, v{}, v{}, v{}, {}, {}'.format(
self.C, self.D, self.E, self.F, self.G, self.BBBB, self.HHHH
)
registers = [self.C, self.D, self.E, self.F, self.G][: self.A]
register_text = "{{{}}}".format(
", ".join("v{}".format(register) for register in registers)
)
return "{}, {}, {}".format(
register_text,
_format_method_reference(self.cm, self.BBBB),
_format_proto_descriptor(self.cm, self.HHHH),
)

def get_operands(self):
# FIXME
Expand Down Expand Up @@ -6595,9 +6584,10 @@ def get_raw(self) -> bytes:
)

def get_output(self, idx: int = -1) -> str:
# FIXME get_kind of BBBB (meth) and HHHH (proto)
return 'v{} .. v{} {} {}'.format(
self.CCCC, self.NNNN, self.BBBB, self.HHHH
return "{}, {}, {}".format(
"{{v{} .. v{}}}".format(self.CCCC, self.NNNN),
_format_method_reference(self.cm, self.BBBB),
_format_proto_descriptor(self.cm, self.HHHH),
)

def get_operands(self):
Expand Down Expand Up @@ -8293,6 +8283,7 @@ def __init__(
decompiler: Union[DecompilerDAD, None] = None,
config=None,
using_api: Union[int, None] = None,
verify_checksum: bool = True,
) -> None:

# to allow to pass apk object ==> we do not need to pass additionally target version
Expand All @@ -8305,6 +8296,7 @@ def __init__(
else:
self.api_version = CONF["DEFAULT_API"]

self.verify_checksum = verify_checksum
self.raw = io.BufferedReader(io.BytesIO(buff))

self._flush()
Expand Down
2 changes: 2 additions & 0 deletions dadguard/decompiler/writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,8 @@ def write_method(self):
params = self.method.lparams
if 'static' not in access:
params = params[1:]
if len(params) != len(self.method.params_type):
params = list(range(len(self.method.params_type)))
proto = ''
self.write_ext(('PARENTHESIS_START', '('))
if self.method.params_type:
Expand Down
Loading
Loading