Introduction
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([Enlace externo eliminado para invitados]), 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([Enlace externo eliminado para invitados]). 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).
Código: Seleccionar todo
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
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([Enlace externo eliminado para invitados]), 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: [Enlace externo eliminado para invitados].
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:
Código: Seleccionar todo
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
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.
Código: Seleccionar todo
javax.swing.JComboBox -> cliente.Login.listalicencias ( drop down list of available licenses)
Código: Seleccionar todo
javax.swing.JTextField -> cliente.Login.user (username text-box)
Código: Seleccionar todo
javax.swing.JPasswordField -> cliente.Login.password (masked password text-box)
Código: Seleccionar todo
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
Código: Seleccionar todo
If you don’t know the meaning of an instruction, you can look ut up in Oracles JVM Instruction Set Reference(http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html)
Código: Seleccionar todo
this, this, this, this, null
Código: Seleccionar todo
static synthetic void B(cliente.Login, java.awt.event.ActionEvent)
private synthetic void B()
private synthetic void B(java.awt.event.ActionEvent)
Código: Seleccionar todo
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
Código: Seleccionar todo
[...]
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
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:
Código: Seleccionar todo
g.setText(Extras.MD5.ALLATORI_DEMO(" F0@9"));
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.
Código: Seleccionar todo
System.out.println(Extras.MD5.ALLATORI_DEMO("F0@9")); -> Login
System.out.println(Extras.MD5.ALLATORI_DEMO("Login")); -> F0@9
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.
Código: Seleccionar todo
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)
Código: Seleccionar todo
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
Código: Seleccionar todo
00000000 : aload_0
00000001 : aload_1
00000002 : invokespecial void cliente.Login.B(java.awt.event.ActionEvent)
00000005 : return
Código: Seleccionar todo
// 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
Another castle
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..
Código: Seleccionar todo
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
Código: Seleccionar todo
// Store InetAdress "65.99.225.111" 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 65.99.225.111) jump
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.
Link: [Enlace externo eliminado para invitados]
PS: Found this in the Login.main class:
Código: Seleccionar todo
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)
[Enlace externo eliminado para invitados]
FUENTE: [Enlace externo eliminado para invitados]
no me borren el post