As the title suggestes, this post will feature a practical example of cracking obfuscated Java code, namely Allatori 4.7. For the sake of example I’ve chosen an astonishing Java RAT (Remote ‘Administration’ Tool) worth every of the $100 It got sold for, but I will come back to this later. To start, let’s compare the usual workflow of cracking Java programs and then find out why it won’t work for obfuscated code.The ‘usual’ way to crack Java programs is by decompiling, modifying and recompiling the source code. To understand what this means you need to know that Java source code, unlike e.g. C++, doesn’t get directly translated to low-level machine-code interpreted by the processor. Instead, it gets distributed in a more abstracted code set called java bytecode. This code then gets interpreted by a Java Virtual Machine similar to the .NET Common Language Runtime (CLR) on execution.
This offers the ability to distribute programs in a platform independent format (only the VM needs to get adjusted), but as everything, it comes with a downside. Since bytecode has such an abstracted, diverse Instruction set it turns out to be vulnerable to so-called decompiling, which basically means ‘reversing’ the process of compilation by guessing how the original source code might have looked like. Since this is a massive problem of not only application security but also intellectual property there are some efforts to prevent this by obfuscating the code in different ways.
One of the results of these efforts is Alatori, a commercial obfuscator starting at $290. It comes with all the features you would except from a commercial obfuscator, most important for us string obfuscation, flow obfuscation and inserting ‘invalid’ bytecode (bytcode ignored by the VM but crashing/confusing known decompilers/deobfuscators).
Adwind 3 – Overview
Adwind 3 is a Java based RAT supporting not only common desktop operation systems but also Android. It originated from a spain Proof of Concept called Frutas and got recently rebranded and sold as UNRECOM. It’s protected both by a custom login system with serials and hardware identification. Adwind is using the Swing API to draw its GUI. This means that it defines some user input elements (buttons, text boxes, drop downs..) which offer user triggered ‘ActionEvents’ (button click..) and connects them to ActionListenern performing the desired action/method. Our first goal is the ‘Login’ button of the window below.
Adwind gets distributed as an executable Java Archive (JAR) containing not only the bytecode in form of .class files corresponding to the different Java classes but also resources like images and sounds and a Manifest file pointing to the main class function (the one which get’s called on execution of the Jar).
Manifest-Version: 1.0 Ant-Version: Apache Ant 1.9.1 Created-By: 1.7.0_25-b17 (Oracle Corporation) Class-Path: lib/JTattoo-1.6.9.jar X-COMMENT: Main-Class will be added automatically by build Main-Class: cliente.Login
When trying to decompile this main class using JD-GUI, we get just get an ‘// INTERNAL ERROR //‘ instead of the class functions. Of course we could look for a way to fix this, either by hunting down the locations causing them and either patching the decompiler or the class file. But even then, obfuscation techniques like flow obfuscation would result into a source code not really usable.
Messing with bytecode
Today I want to show you another approach – why going through the whole mess of decompiling and recompiling of we could patch the code directly? There are several bytecode editors to make this easier, my favorite one being dirtyJOE, which is written in C++ and supports python-scripts to perform different manipulations. Since dirtyJOE can’t handle Jars we have to unpack the .class file in question first using any zip-utility. You should really pay attention at this point since zip-archives support case-sensitive filenames (for example cG.class, cg.class), but some operating systems like windows don’t. Since Allatori is utilizing this, it may lead to class files getting overwritten without any notice when unpacking the whole archive to one directory.
dirtyJOE welcomes us with some basic information about the class if available, for example it’s parent/super-class, the number of constants (constant_pool_count) and methods. constants are basically all hardcoded references to other functions and strings used by the bytecode. You can find out more about the ClassFile structure here: http://www.javaworld.com/article/2077233/core-java/bytecode-basics.html.
If we switch to the ‘Methods’-tab, we directly see the main-Method – but before digging into it, we should take a short break – Before using any method of a class we need to initialize it to an object, and so does the JVM. In fact, it even needs to initialize the class. This is happening by invoking (object initialization) and (class initialization).
In our case, nothing spectacular is happening in , since the Login class inherits by ‘javax/swing/JFrame’ (User Interface) it initializes the basic Login form and creates the basic form elements:
00000000 : new javax.swing.JComboBox 00000003 : dup 00000004 : invokespecial void javax.swing.JComboBox.() 00000007 : putstatic javax.swing.JComboBox cliente.Login.listalicencias 0000000A : new javax.swing.JPasswordField 0000000D : dup 0000000E : invokespecial void javax.swing.JPasswordField.() 00000011 : putstatic javax.swing.JPasswordField cliente.Login.password 00000014 : new javax.swing.JTextField 00000017 : dup 00000018 : invokespecial void javax.swing.JTextField.() 0000001B : putstatic javax.swing.JTextField cliente.Login.user 0000001E : return
To understand what’s going on, you need a basic understanding of the bytecode instruction set and how it gets interpreted. Going through all of this would exceed the scope of this post, and there are people explaining it way better than I could: http://www.javaworld.com/article/2077233/core-java/bytecode-basics.html
This isn’t the button you’re looking for
With the above basics it should be relatively easy to guess what’s going on. Basically, four Swing GUI elements get created, initialized and its references stored in the corresponding fields of the Login-class.
javax.swing.JComboBox -> cliente.Login.listalicencias ( drop down list of available licenses)
javax.swing.JTextField -> cliente.Login.user (username text-box)
javax.swing.JPasswordField -> cliente.Login.password (masked password text-box)
But hey! Those aren’t all fields, are they? Where are all those buttons, graphics – where is the Login-button we’re looking for? Let’s check the <init> method:
00000000 : aconst_null 00000001 : aload_0 00000002 : dup 00000003 : dup_x2 00000004 : aload_0 00000005 : invokespecial void javax.swing.JFrame.() 00000008 : invokespecial void cliente.Login.B() 0000000B : invokespecial void cliente.Login.ALLATORI_DEMO() 0000000E : invokevirtual void cliente.Login.setLocationRelativeTo(java.awt.Component) 00000011 : return
At first glance, this doesn’t look way better, only a single ‘javax.swing’ is in sight. But let’s take a closer look and figure out what’s going on.
If you don’t know the meaning of an instruction, you can look ut up in Oracles JVM Instruction Set Reference
First, null gets pushed on the stack (aconst_null). Then aload_0 get’s executed. This loads the first (pos 00) local variable onto the stack, which is always a reference to the object itself, similar to the .this directive in Java.The top value of the stack gets duplicated (dup) and the new top-value gets duplicated two values down the stack. Finally, the object reference get’s loaded another time. At this point, the stack should look like this:
this, this, this, this, null
You could theoretically get the same result by doing 1 aconst_0 and four aload_0 s, but that would be way to simple! Those this references are then used to invokespecial (call) 4 methods and in the end null gets returned – Remember the stack is based on the LIFO (last in, first out) principle. All of those functions are void methods, which means that they don’t return anything to the stack. At this pont, you also see another annoying feature of Allatori, the renaming of foreign methods and classes – B() doesn’t ring any bells, does it? And it gets even worse… If we look for the B() method, we notice that there is not just one, but three of them! This is possible because the JVM allows methods of the same name with different descriptors. Those are the three B() method in question:
- static synthetic void B(cliente.Login, java.awt.event.ActionEvent)
- private synthetic void B()
- private synthetic void B(java.awt.event.ActionEvent)
Since we know that no parameters got pushed to the B() method when it was called above we have to look at the second method, void B(). I won’t post all 381 lines but point out some important aspects.
0000004E : new javax.swing.JButton 00000051 : dup 00000052 : invokespecial void javax.swing.JButton.() 00000055 : putfield javax.swing.JButton cliente.Login.g 00000058 : new javax.swing.JPanel
Aha! A button. This leads us in the right direction, but since there are multiple buttons and ‘cliente.Login.g’ isn’t telling us much, we need to look for another way to identify it. How about the text of the button shown to the user? It has to get set somewhere, does it?
[...] 00000052 : invokespecial void javax.swing.JButton.() 00000055 : putfield javax.swing.JButton cliente.Login.g 00000058 : new javax.swing.JPanel [...] 00000105 : getfield javax.swing.JButton cliente.Login.g 00000108 : ldc_w " F0@9" 0000010B : invokestatic java.lang.String Extras.MD5.ALLATORI_DEMO(java.lang.String) 0000010E : invokevirtual void javax.swing.JButton.setText(java.lang.String) 00000111 : getfield javax.swing.JButton cliente.Login.g
String deobfuscation – the lazy way
Well, this part definitively sets some text on the button, and it also loads a string, but the string looks kinda strange, and there is another function in between of the loading of the string (ldc_w ” F0@9″ and the text-setting (invokevirtual .setText(java.lang.String) which also takes a string as its parameter. This means that the mysterious ALLATORI_DEMO(String) function probably takes ‘garbage’ as input and returns a more useful string which then gets used to perform whatever operation. In Java, it might look like this:
This happens for every single usage of strings, only with different methods, which means that we just figured out where strings get deobfuscated. The way Allatori deobfuscates strings got analyzed before, actually it’s pretty simple. The method iterates through the strings characters backwards and performs XOR and AND bit-operations on them – the result gets stored in a array which then gets converted and returned as a string. Due to it’s nature, it’s possible to revert the obfuscation by applying the same method again. However the program which we are looking at is using Allatori 4.7, and here things changed a little. The actual obfuscation is more or less the same, but the key used in it is not simply pushed to the stack but calculated using different stack operations. This, and the fact that Allatori 4.7 uses multiple string obfuscation methods (with different keys) in the same method make it harder to deobfuscate the program by going through all strings automatically.
But there is a good message – we don’t even need to get our hands dirty with all of this! Since class file functions can easily used in other Java programs and Allatori didn’t bother to include some asymmetric encryption method we can import the class file (or the whole Jar) to a new Java program and call the method like any other to both de- and encrypt strings.
System.out.println(Extras.MD5.ALLATORI_DEMO("F0@9")); -> Login System.out.println(Extras.MD5.ALLATORI_DEMO("Login")); -> F0@9
Because some encrypted strings contain line breaks and Java doesn’t support raw strings you might encounter some problems – you can partially fix this by enabling automated escaping when pasting text between string literals in Eclipse (Preferences -> Java -> Editor -> Typing.
As we can see thanks to the deobfuscated string, the button text gets indeed set to ‘Login’. Now we just have to find out which EventHandler get’s associated to it.
00000111 : getfield javax.swing.JButton cliente.Login.g 00000114 : new cliente.i 00000117 : dup 00000118 : aload_0 00000119 : invokespecial void cliente.i.(cliente.Login) 0000011C : invokevirtual void javax.swing.JButton.addActionListener(java.awt.event.ActionListener)
Here we go. A new instance of class i gets created, initialized and ‘added’ to the JButton as an ActionListener. When the button gets clicked, the instances method ‘ActionPerformed’ gets called…
00000000 : aload_0 00000001 : getfield cliente.Login cliente.i.ALLATORI_DEMO 00000004 : aload_1 00000005 : invokestatic void cliente.Login.ALLATORI_DEMO(cliente.Login, java.awt.event.ActionEvent) 00000008 : return
..which calls ALLATORI_DEMO(cliente.Login, java.awt.event.ActionEvent) in our Login-Class…
00000000 : aload_0 00000001 : aload_1 00000002 : invokespecial void cliente.Login.B(java.awt.event.ActionEvent) 00000005 : return
which finally calls cliente.Login.B(ActionEvent). What a ride!
// check if Username-Textfield is empty 00000000 : getstatic javax.swing.JTextField cliente.Login.user 00000003 : invokevirtual java.lang.String javax.swing.JTextField.getText() 00000006 : invokevirtual boolean java.lang.String.isEmpty() // returns boolean 00000009 : ifeq pos.0000000E // if result is 0 (Field is NOT empty -> jump 0000000C : return // avoid 0000000D : pop // check if Password-Field is empty 0000000E : new java.lang.String // create new string 00000011 : dup 00000012 : getstatic javax.swing.JPasswordField cliente.Login.password 00000015 : invokevirtual char javax.swing.JPasswordField.getPassword() 00000018 : invokespecial void java.lang.String.(char) 0000001B : invokevirtual boolean java.lang.String.isEmpty() 0000001E : ifeq pos.00000023 00000021 : return // avoid again // check if any license got selected in ComboBox (Index is not -1 / m1 00000022 : iconst_0 00000023 : getstatic javax.swing.JComboBox cliente.Login.listalicencias 00000026 : invokevirtual int javax.swing.JComboBox.getSelectedIndex() 00000029 : iconst_ml 0000002A : if_icmpne pos.0000002E 0000002D : return // meh. 0000002E : aload_0 // :) // 'hide' the login-window and call Adwind.main(null) -> the main window? 0000002F : invokevirtual void cliente.Login.dispose() 00000032 : aconst_null 00000033 : invokestatic void cliente.Adwind.main(java.lang.String) 00000036 : return
I already made some comments so it should be pretty clear what’s going on here – some checks if there is any input – but no _real_ checks, no web request. If we patch the return above the main(null)call (triggered by no license being selected) to nop (do nothing / 00), run the program and enter some garbage, the progress calls the main method and then closes. This means that the login/license check is not in the Login-class at all but in another castle, somewhere in that / or main() method of Adwind.main
I will cut things down a bit at this point since finding the method isn’t really anything new – after some minutes you should have found an Adwind.B() – call at the top of Adwind.<init> which begins with this lovely VM-Check..
00000000 : invokestatic boolean Extras.Utils.isVMWARE() 00000003 : ifne pos.0000000C // jump if isVMWARE == 1 00000006 : invokestatic boolean Extras.Utils.isVMWARE() 00000009 : ifeq pos.00000010 // jump if isVMWARE == 0 0000000C : iconst_ml 0000000D : invokestatic void java.lang.System.exit(int) // we don't want to land here
..and also the reason why Adwind closed without any warning when entering wrong credentials:
// Store InetAdress "220.127.116.11" in var1 00000033 : ldc_w "0<+7+4" 00000036 : invokestatic java.lang.String cliente.NoIpService.ALLATORI_DEMO(java.lang.String) 00000039 : invokestatic java.net.InetAddress java.net.InetAddress.getByName(java.lang.String) 0000003C : astore_1 // Store InetAdress "adwind.com.mx" -> IP in var 2 0000003D : ldc_w "S(E%\(/]!!J" 00000040 : invokestatic java.lang.String plugins.CargadorPlugins.ALLATORI_DEMO(java.lang.String) 00000043 : invokestatic java.net.InetAddress java.net.InetAddress.getByName(java.lang.String) 00000046 : astore_2 // Load and compare those InetAdresses 00000047 : aload_1 00000048 : aload_2 00000049 : invokevirtual boolean java.net.InetAddress.equals(java.lang.Object) 0000004C : ifeq pos.00000060 // if not equal (If adwind.com.mx doesn't resolve to 18.104.22.168) jump
As you can see, the method checks if ‘adwind.com.mx’ resolves to a hardcoded IP adress, probably to detect DNS-manipulation. Since the domain isn’t up anymore, the request will fail, even for valid customers (great!)
The method also generates a kind of hwid and issues a web request to the login server using a horrible case switch taking about 200 lines, but I will spare you that. Instead, why don’t we just try to nop the invoke of B() in the first place? Since it only checks conditions to exit, this actually works. If we also change the Main-Class in the Jar-manifest, we can even skip the whole login-window.
PS: Found this in the Login.main class:
0000000C ldc_w "s9V%F#@5q9W?<^-K��[?F" // AuditoryCues.playlist <- Java Swing-Ebook CopyPasta 0000000F invokestatic java.lang.String plugins.CargadorPlugins.ALLATORI_DEMO(java.lang.String) 00000012 ldc_w "h"M>]8[.j"L$6E;h"M>]8[.j"L$" // AuditoryCues.allAuditoryCues <- Srsly? Why I am even cracking this... // initialize java swing UI-Manager in an absolute innovative way 00000015 invokestatic java.lang.String Extras.MD5.ALLATORI_DEMO(java.lang.String) 00000018 invokestatic java.lang.Object javax.swing.UIManager.get(java.lang.Object) 0000001B invokestatic java.lang.Object javax.swing.UIManager.put(java.lang.Object, java.lang.Object)
You might wonder what’s the connection of Auditory, a music editor and Adwind, a ‘Premium’RAT. Answer: Complete Copy Pasta.