This is part 2 of our series of blogs showing how to use JEB’s API to manipulate decompiled Java syntax trees. (Missed Part 1?)
Let’s see how the API can be leveraged to decrypt strings, and plug the decrypted strings back into the source code.
Download the script
Demo video
As shown in the video, we are going to focus on a protected version of Cyanide. The strings are encrypted, and that the decompiled Java code does not look pretty:
... protected void onCreate(Bundle arg5) { super.onCreate(arg5); this.setContentView(2130903040); if(new File(MainActivity.鷭(-387, -15, 608)).exists()) { MainActivity.鷭(MainActivity.鷭(-389, 52, 159)); MainActivity.鷭(MainActivity.鷭(-333, 37, 17)); MainActivity.鷭(MainActivity.鷭(-407, 53, 629), MainActivity.鷭(-395, -15, 0), this); MainActivity.鷭(MainActivity.鷭(-398, 53, 92), MainActivity.鷭(-386, -15, 586), this); MainActivity.鷭(MainActivity.鷭(-402, 52, 102), MainActivity.鷭(-378, -15, 665), this); MainActivity.鷭(MainActivity.鷭(-368, 37, 119)); MainActivity.鷭(this); return; } ... ...
MainActivity.鷭(x, y, z) is the decryptor method. The parameters indirectly reference a static array of bytes, that contains the encrypted strings for the class.
Our script is going to do the following:
- Search the encrypted byte array
- Enumerate the fields of the class
- Look for a byte[] field marked private static final
- Verify that this field is referenced in <clinit>, the static {…} initializer for the class
- The field should also be referenced in another method: the decryptor
- Check the structure of <clinit>
- It should look like: encrypted_strings = new byte[]{………}
- Retrieve the encrypted bytes
- The decryptor was analyzed in a previous blog post
- The decryptor constants need to be extracted manually (let’s keep the script simple)
- Then, for every method of the class, we will:
- Enumerate the statements and sub-elements of the AST recursively
- Look for Call elements
- If the Call matches the decryptor method, we extract the argument provided to the Call
- We use these arguments to decrypt the string
- Finally, we replace the Call by a newly created Constant element that represent the decrypted string
(Note: The JEB python script is just a little over 100 lines, and took less than 1 hour to write. It could be greatly improved, for instance, the decryptor constants could be found programmatically, but this added complexity is out of the scope of this introductory blog post.)
Here what the deobfuscated code snippet looks like:
... protected void onCreate(Bundle arg5) { super.onCreate(arg5); this.setContentView(2130903040); if(new File("/data/last_alog/onboot").exists()) { MainActivity.鷭("rm /data/last_alog/*"); MainActivity.鷭("cat /system/etc/install-recovery.sh > /system/etc/install-recovery.sh.backup"); MainActivity.鷭("su", "/system/etc/su", this); MainActivity.鷭("supersu.apk", "/system/etc/supersu.apk", this); MainActivity.鷭("root.sh", "/system/etc/install-recovery.sh", this); MainActivity.鷭("chmod 755 /system/etc/install-recovery.sh"); MainActivity.鷭(this); return; } ... ...
In part 3, we will show how to defeat a complex obfuscation scheme used by many bytecode protectors: reflection.
2 thoughts on “Decompiled Java Code Manipulation using JEB API – Part 2: Decrypting Strings”