-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(profiling): Deobfuscate Android profiles' methods' signatures (#…
- Loading branch information
Showing
4 changed files
with
197 additions
and
25 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
from typing import List, Tuple | ||
|
||
JAVA_BASE_TYPES = { | ||
"Z": "boolean", | ||
"B": "byte", | ||
"C": "char", | ||
"S": "short", | ||
"I": "int", | ||
"J": "long", | ||
"F": "float", | ||
"D": "double", | ||
"V": "void", | ||
} | ||
|
||
|
||
# parse_obfuscated_signature will parse an obfuscated signatures into parameter | ||
# and return types that can be then deobfuscated | ||
def parse_obfuscated_signature(signature: str) -> Tuple[List[str], str]: | ||
if signature[0] != "(": | ||
return [], "" | ||
|
||
signature = signature[1:] | ||
parameter_types, return_type = signature.rsplit(")", 1) | ||
types = [] | ||
i = 0 | ||
arrays = 0 | ||
|
||
while i < len(parameter_types): | ||
t = parameter_types[i] | ||
|
||
if t in JAVA_BASE_TYPES: | ||
start_index = i - arrays | ||
types.append(parameter_types[start_index : i + 1]) | ||
arrays = 0 | ||
elif t == "L": | ||
start_index = i - arrays | ||
end_index = parameter_types[i:].index(";") | ||
types.append(parameter_types[start_index : i + end_index + 1]) | ||
arrays = 0 | ||
i += end_index | ||
elif t == "[": | ||
arrays += 1 | ||
else: | ||
arrays = 0 | ||
|
||
i += 1 | ||
|
||
return types, return_type | ||
|
||
|
||
# format_signature formats the types into a human-readable signature | ||
def format_signature(parameter_java_types: List[str], return_java_type: str) -> str: | ||
signature = f"({', '.join(parameter_java_types)})" | ||
if return_java_type and return_java_type != "void": | ||
signature += f": {return_java_type}" | ||
return signature | ||
|
||
|
||
def byte_code_type_to_java_type(mapper, byte_code_type: str) -> str: | ||
if not byte_code_type: | ||
return "" | ||
|
||
token = byte_code_type[0] | ||
if token in JAVA_BASE_TYPES: | ||
return JAVA_BASE_TYPES[token] | ||
elif token == "L": | ||
# invalid signature | ||
if byte_code_type[-1] != ";": | ||
return byte_code_type | ||
obfuscated = byte_code_type[1:-1].replace("/", ".") | ||
mapped = mapper.remap_class(obfuscated) | ||
if mapped: | ||
return mapped | ||
return obfuscated | ||
elif token == "[": | ||
return f"{byte_code_type_to_java_type(mapper, byte_code_type[1:])}[]" | ||
else: | ||
return byte_code_type | ||
|
||
|
||
# map_obfucated_signature will parse then deobfuscated a signature and | ||
# format it appropriately | ||
def deobfuscate_signature(mapper, signature: str) -> str: | ||
if not signature: | ||
return "" | ||
|
||
parameter_types, return_type = parse_obfuscated_signature(signature) | ||
if not (parameter_types or return_type): | ||
return "" | ||
|
||
parameter_java_types = [] | ||
for parameter_type in parameter_types: | ||
new_class = byte_code_type_to_java_type(mapper, parameter_type) | ||
parameter_java_types.append(new_class) | ||
|
||
return_java_type = byte_code_type_to_java_type(mapper, return_type) | ||
return format_signature(parameter_java_types, return_java_type) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
from tempfile import mkstemp | ||
|
||
import pytest | ||
from symbolic.proguard import ProguardMapper | ||
|
||
from sentry.profiles.java import deobfuscate_signature | ||
|
||
PROGUARD_SOURCE = b"""\ | ||
# compiler: R8 | ||
# compiler_version: 2.0.74 | ||
# min_api: 16 | ||
# pg_map_id: 5b46fdc | ||
# common_typos_disable | ||
# {"id":"com.android.tools.r8.mapping","version":"1.0"} | ||
org.slf4j.helpers.Util$ClassContextSecurityManager -> org.a.b.g$a: | ||
65:65:void <init>() -> <init> | ||
67:67:java.lang.Class[] getClassContext() -> a | ||
69:69:java.lang.Class[] getExtraClassContext() -> a | ||
65:65:void <init>(org.slf4j.helpers.Util$1) -> <init> | ||
""" | ||
|
||
|
||
@pytest.fixture | ||
def mapper(): | ||
_, mapping_file_path = mkstemp() | ||
with open(mapping_file_path, "wb") as f: | ||
f.write(PROGUARD_SOURCE) | ||
mapper = ProguardMapper.open(mapping_file_path) | ||
assert mapper.has_line_info | ||
return mapper | ||
|
||
|
||
@pytest.mark.parametrize( | ||
["obfuscated", "expected"], | ||
[ | ||
# invalid signatures | ||
("", ""), | ||
("()", ""), | ||
# valid signatures | ||
("()V", "()"), | ||
("([I)V", "(int[])"), | ||
("(III)V", "(int, int, int)"), | ||
("([Ljava/lang/String;)V", "(java.lang.String[])"), | ||
("([[J)V", "(long[][])"), | ||
("(I)I", "(int): int"), | ||
("([B)V", "(byte[])"), | ||
], | ||
) | ||
def test_deobfuscate_signature(mapper, obfuscated, expected): | ||
assert deobfuscate_signature(mapper, obfuscated) == expected |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters