Analysing Suspicious File "Outstanding Payment.jar" - Part 1

Is antivirus a 100% protection against malicious files? What techniques are used by authors of malware to avoid detection? A series of articles describes our procedure during the static analysis of a suspicious Java file and reveals interesting findings about its structure as well as about the process of analysis as such.

Not so long ago, we received a suspicious file called “Outstanding Payment.jar”. Some of the antivirus marked this file as malware, but we were interested in more details. Most of all, we wanted to find out which specific activities are executed on the infected station by this malware or as the case may be, to identify the counterparts it communicates with.

File analysis by Virus Total
File analysis by Virus Total

After extracting the archive, it was obvious at the first sight from the names of the individual files that this is an obfuscated Java application. The archive included:

  • classes p5569.class to p5595.class,
  • 3 binary files:
    • n720175702
    • n1436460589
    • n1139827387

Naturally, decompilation was the first step using the Java Decompiler program (https://github.com/java-decompiler/jd-gui).

Java Decompiler
Java Decompiler

The names of the methods and variables were also obfuscated, naturally, resulting in the unreadable code of the application.

Layer I – Strings Obfuscation

One of the first things I focus on in such cases are text strings. It turned out that there is a number of short methods in the application and these, after a short arithmetic calculation, return probably a decoded string:

publicstaticStringm481768778533406(){returnnewString(newBigInteger("4jeg9121941i687a1gbe73i8j4",20).add(BigInteger.valueOf(685346116L)).divide(BigInteger.valueOf(185L)).divide(BigInteger.valueOf(255L)).divide(BigInteger.valueOf(252L)).toByteArray());}publicstaticStringm481768780200993(){returnnewString(newBigInteger("-29357j83g",25).divide(BigInteger.valueOf(-393L)).subtract(BigInteger.valueOf(-1309792666L)).subtract(BigInteger.valueOf(-352014025L)).subtract(BigInteger.valueOf(197930807L)).subtract(BigInteger.valueOf(495429948L)).toByteArray());}

Since every method uses a different manner of calculation (different set and sequence of operations), it wasn’t possible to create a unified decoding algorithm. On the other hand, the source code of each decoding method was at our disposal. Thus, it was enough to gather all these methods, write a Java code calling them, compile, and save the resulting strings.

For these purposes, I wrote a short Python script which scanned all source .java files and found all the methods (name and return expression) with a simple regular expression

/String (.*?)\(\)[\{\n\s]*.*?return (new String.*?);/

It then generated the Java program StringDecoder.java from the gathered methods, which, after it was started, decoded all strings and produced an output in the following format: “METHOD_NAME [TAB] DECODED_STRING”. Since the total number of strings exceeded a thousand, I hit the limitation for the maximum length of a method in Java. Therefore, I had to divide the decoding calls into several auxiliary blocks (methods m0-m11).

importjava.math.BigInteger;publicclassStringDecoder{publicstaticvoidmain(String[]args){m0();m1();m2();}publicstaticvoidm0(){System.out.println("m481768246224778	"+newString(newBigInteger("61645375321174103340046020127427",8).divide(BigInteger.valueOf(463L)).toByteArray()));System.out.println("m481768254833617	"+newString(newBigInteger("19ii868571al8ad74754hl529m3h",24).subtract(BigInteger.valueOf(981117809L)).divide(BigInteger.valueOf(-83L)).divide(BigInteger.valueOf(456L)).divide(BigInteger.valueOf(-75L)).toByteArray()));System.out.println("m481769630835738	"+newString(newBigInteger("-dbf7k67bbb4i0",21).divide(BigInteger.valueOf(271L)).divide(BigInteger.valueOf(-9L)).divide(BigInteger.valueOf(322L)).divide(BigInteger.valueOf(43L)).subtract(BigInteger.valueOf(1066591023L)).toByteArray()));
Generated program for string decoding

The majority of decoded strings included the names of classes and methods; therefore, we could assume that we will meet a number of reflexive calls. Beside others, there were also cryptographic methods indicating encryption:

m481768246224778	n1436460589
m481768254833617	setAccessible
m481769630835738	push
m481769631469437	javax.crypto.spec.SecretKeySpec
m481769632835956	f481768237511843
m481769634291287	java.lang.reflect.Array
m481769635523836	java.lang.Object

Last, but not least, the list of strings included this line:

m481768664717772	?boot-main-class:operational.Jrat

Thus, even in this step it was clear (or at least very probable) that this is a variant of Java malware called jRAT (Remote Administration Tool written in Java).

Because the names of decoded methods were unique across the entire application, it was possible to replace their calls by the decoded strings with yet another short Python script. However, this did not help from the functional analysis point of view – the flow of the program (control flow) was not visible mainly due to a number of short methods consisting of a few lines with meaningless names.

Layer I – Control Flow Obfuscation

So the next step was to unpack the short method calls (a process commonly performed by the compiler for inline function calls). It was to my advantage that all methods were static, so again it was enough to use several regular expressions to extract definitions and replace the calls directly with the body of the function.

It turned out that the whole application core is made of a state machine:

while(condition){switch(((Integer)p5595.f481768237511843).intValue()){case4035:java.util.LinkedList.getDeclaredMethod("push",newClass[]{java.lang.Object}).invoke(p5595.f481768237438446,newObject[]{Integer.valueOf(4089)});p5595.f481768237511843=Integer.valueOf(5979);break;case5857:((java.lang.reflect.Method)p5595.f481768237798877).invoke(p5578.f481768476627625,newObject[]{p5587.f481768598564702});p5595.f481768237511843=Integer.valueOf(5860);break;case5424:if(((Boolean)p5584.f481768548009292).booleanValue()){p5595.f481768237511843=Integer.valueOf(5465);}else{p5595.f481768237511843=Integer.valueOf(5794);}break;...}
Controlling state machine

At the beginning of the program, the state variable set itself to the initial value, and then one calculation step was performed in each iteration of the cycle and a new value of the state variable was set.

Apparently, the originally sequenced code was transformed in a way that unique states were assigned to individual commands (or short blocks of code), and their order was subsequently reversed:

Control flow transformation
Control flow transformation

This technique is commonly used by some automated obfuscators and when looking at such code, it is difficult, practically almost impossible, to monitor the flow of the program (sequence of steps, branching, cycles ...).

Moreover, in this case, the flow of the program was not controlled by only one state variable. In addition to it, a stack was used where future states were stored and later taken out, which would make the manual step-by-step analysis very difficult.

Fortunately, it was possible to isolate the operations adjusting the state of the machine. These were the following 3 types of operations:

  • assigning a numeric value to a state variable (p5595.f481768237511843 = ...),
  • entering a new value to the stack
    (java.util.LinkedList.getDeclaredMethod(„push“, ...)),
  • taking a value from the top of the stack
    (java.util.LinkedList.getDeclaredMethod(„pop“, ...).

So yet again, it was enough to write a short Python script loading the whole machine and gradually interpreting the operations modifying the current state, while outputting other commands in individual states. It resulted in a much better readable sequence code, suitable for manual analysis:

p5582.f481771201719702=java.lang.System.out;p5574.f481768415949647=java.lang.Class.forName(newObject[]{"p5595";})p5572.f481768374256940=/*java.lang.Class*/p5574.f481768415949647.getProtectionDomain(newObject[0])p5576.f481768456904606=/*java.lang.Class*/p5574.f481768415949647.getClassLoader(newObject[0])p5570.f481768327573006=java.lang.String.getDeclaredMethod("getBytes",newClass[0]).invoke("2173853979931072";,newObject[0]);p5576.f481768456850592=java.io.ByteArrayOutputStream.getConstructor(newClass[0]).newInstance(newObject[0]);p5589.f481768623886303="n720175702";;p5592.f481768700379315=Integer.valueOf(303016);;p5595.f481768237942623=Integer.valueOf(1);;p5594.f481768739066596=/*java.lang.ClassLoader*/p5576.f481768456904606.getResourceAsStream(newObject[]{p5589.f481768623886303})p5572.f481768374193814=java.io.DataInputStream.getConstructor(newClass[]{java.io.InputStream}).newInstance(newObject[]{p5594.f481768739066596});/*java.io.DataInputStream*/p5572.f481768374193814.skipBytes(newObject[]{p5592.f481768700379315})p5581.f481768521696645=p5572.f481768374193814;p5573.f481768391322862=javax.crypto.Cipher.DECRYPT_MODE;...

Layer I – Resources Encryption

It turned out that the application uploads its 3 sources, and gradually decodes the files from them in a sophisticated way. Each resulting file is composed of several parts spread out between 3 sources – at different positions, with different sizes – and encrypted by the AES algorithm. Individual fragments are defined with strings in the following format: “size:offset:resId;size:offset:resId...”.

Encrypted files fragments
Encrypted files fragments

For a start, 3 files were decoded:

Loader$Handler.class:1:303016:n720175702;1:266016:n1139827387;988:266208:n1139827387
Loader$Connection.class:1:303179:n720175702;1:303340:n720175702;1:22210:n1436460589;1:22377:n1436460589;1425:22747:n1436460589
Loader.class:1:303502:n720175702;1:23652:n1436460589;1:23813:n1436460589;1:266852:n1139827387;1:303663:n720175702;6291:24591:n143646058

The Loader class is then used to decode more data, while a file map is read and decoded first (a serialized Java object of the java.util.Map<String, String> type):

1:300918:n720175702;1:301078:n720175702;8223:301462:n720175702

After decoding, it is possible to read it in Java with the ObjectInputStream class:

Map<String,String>addresses=((Map)newObjectInputStream(newByteArrayInputStream(decodedBytes)).readObject());

This map contains descriptions of the fragments (size:offset:resId;size:offset:resId...) and file names, while the Loader class reads and decodes them one by one. The AES key is hard-coded in the application code and is the same for all decoded files.

Successful decoding and connecting all fragments revealed a whole lot of new files:

Decoded files
Decoded files

Unfortunately, this is probably one more layer of obfuscation – you can see a number of classes with nonsensical names.

In any case, at this stage, I was able to crack the first layer of malware. Its main task was to deliver malicious code to the target station and avoid detection.

The malicious code itself was encrypted and fragmented into 3 binary files. And for these reasons, the antivirus was not able to recognize any signature.

The envelope code used 3 primary techniques to disguise itself:

  • reflexive calls
    • java.lang.String.getDeclaredMethod("getBytes", new Class[0]).invoke("2173853979931072", new Object[0]);
  • obfuscating text strings
  • transforming the flow of the program (control flow)

I will address the analysis of the files that I managed to obtain in the second part of this article.