├── LICENSE.txt ├── README.md └── annotate.py /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Andras Veres-Szentkiralyi 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Android parameter annotator for Dalvik/Smali disassembly 2 | ======================================================== 3 | 4 | Debugging applications without access to the source code always has its 5 | problems, especially with debuggers that were built with developers in 6 | mind, who obviously doesn't have this restriction. 7 | 8 | The Dalvik implementation of JDWP refuses to give any information about 9 | parameters at all if the DEX file was built without local variable 10 | information, making debugging with JDB difficult. 11 | 12 | This simple Python script reads each Smali file and populates this 13 | metadata that can be extracted from the mangled function name found in 14 | the Dalvik bytecode. 15 | 16 | Read more in [our blog post about this script](https://blog.silentsignal.eu/2016/06/16/accessing-local-variables-in-proguarded-android-apps/) 17 | 18 | Usage 19 | ----- 20 | 21 | python3 annotate.py 22 | 23 | Dependencies 24 | ------------ 25 | 26 | - Python 3.x (tested on 3.5.1) 27 | 28 | License 29 | ------- 30 | 31 | The whole project is available under MIT license, see `LICENSE.txt`. 32 | -------------------------------------------------------------------------------- /annotate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from mmap import mmap, ACCESS_READ 4 | from contextlib import closing 5 | from pathlib import Path 6 | import re 7 | 8 | METHOD_RE = re.compile(br'^\.method([a-z ]*) ([^ (]+)\((.*?)\).*\n\s*\.locals \d+$', re.MULTILINE) 9 | PARAM_RE = re.compile(br'\[*(?:L.+?;|[^L])') 10 | 11 | def process_dir(path): 12 | for entry in Path(path).rglob('*.smali'): 13 | original_path = str(entry) 14 | print('[-] File name:', original_path) 15 | with entry.open() as f: 16 | with closing(mmap(f.fileno(), 0, access=ACCESS_READ)) as smali: 17 | if re.search(br'\.(?:param|local) ', smali): 18 | raise RuntimeError('Parameters are already annotated') 19 | param_inserts = list(find_methods(smali)) 20 | if not param_inserts: 21 | continue 22 | entry.rename(original_path + '~') 23 | last_pos = 0 24 | with open(original_path, 'wb') as output: 25 | for offset, params, is_static in param_inserts: 26 | output.write(smali[last_pos:offset]) 27 | wide_offset = 0 28 | for n, t in enumerate(params, 0 if is_static else 1): 29 | output.write('\n .param p{0}, "p{0}" # {1}'.format(n + wide_offset, 30 | t.decode('ascii')).encode('ascii')) 31 | if t == b'J' or t == b'D': 32 | wide_offset += 1 33 | last_pos = offset 34 | output.write(smali[last_pos:]) 35 | print('[+] Closed', original_path) 36 | 37 | def find_methods(smali): 38 | for method in METHOD_RE.finditer(smali): 39 | is_static = b'static' in method.group(1) 40 | print('[-] |- Method:', method.group(2)) 41 | params = PARAM_RE.findall(method.group(3)) 42 | if params: 43 | yield method.end(), params, is_static 44 | 45 | if __name__ == '__main__': 46 | from sys import argv 47 | process_dir(argv[1]) 48 | --------------------------------------------------------------------------------