Just An Application

September 7, 2014

The Mystery Of The Unsigned JAR: Part Three — The Little JAR That Hid

This is the bytecode for a method called load with everything removed except the calls to methods in the Java APIs.

As you can see, it is possible to deduce what the method is doing solely on the basis of the Java API method calls.


    public void load();
      Code:
         0: aload_0
         1: dup
         2: invokevirtual #6                  // Method java/lang/Object.getClass:()Ljava/lang/Class;
         
            ...
            
        11: invokevirtual #7                  // Method java/lang/Class.getResourceAsStream:(Ljava/lang/String;)Ljava/io/InputStream;
        
            ...
        
        47: new           #12                 // class java/util/jar/JarInputStream
        50: dup
        51: new           #13                 // class java/io/ByteArrayInputStream
        54: dup
        55: aload_1
        56: invokespecial #14                 // Method java/io/ByteArrayInputStream."<init>":([B)V
        59: invokespecial #15                 // Method java/util/jar/JarInputStream."<init>":(Ljava/io/InputStream;)V
        62: astore_2
        
            ...
            
        75: aload_2
        76: invokevirtual #16                 // Method java/util/jar/JarInputStream.getNextJarEntry:()Ljava/util/jar/JarEntry;
        79: dup
        80: astore        4
        82: ifnull        235
        85: aload         4
        87: invokevirtual #17                 // Method java/util/jar/JarEntry.getName:()Ljava/lang/String;
        90: dup
        91: astore        5
        
            ...
            
        99: invokevirtual #18                 // Method java/lang/String.endsWith:(Ljava/lang/String;)Z
       102: ifeq          172
       
            ...
            
       117: iconst_0
       118: iconst_1
       119: dup
       120: pop2
       121: aload         5
       123: dup_x1
       124: invokevirtual #19                 // Method java/lang/String.length:()I
       127: bipush        6
       129: iconst_1
       130: dup
       131: pop2
       132: isub
       133: invokevirtual #20                 // Method java/lang/String.substring:(II)Ljava/lang/String;
       136: bipush        47
       138: iconst_1
       139: dup
       140: pop2
       141: bipush        46
       143: iconst_1
       144: dup
       145: pop2
       146: invokevirtual #21                 // Method java/lang/String.replace:(CC)Ljava/lang/String;
       149: astore        5
       
            ...
            
       163: aload_3
       164: invokevirtual #22                 // Method java/util/HashMap.put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
       167: pop
       168: goto          75
       171: pop
       172: aload         4
       174: invokevirtual #23                 // Method java/util/jar/JarEntry.isDirectory:()Z
       177: ifeq          185
       180: aload_2
       181: goto          76
      
            ...

The calls to the JarInputStream and JarEntry methods make it clear that the method is iterating over the contents of a JAR

The 6 in

    127: bipush        6

just happens to be the length of the suffix

    .class

The 47 in

    136: bipush        47

is the solidus character (‘/‘) and the 46 in

    141: bipush        46

is the dot character (‘.‘).

The sequences following the iconst_0 instruction and each bipush instruction

    117: iconst_0
    118: iconst_1
    119: dup
    120: pop2
    
    ...

    127: bipush        6
    129: iconst_1
    130: dup
    131: pop2
    
    ...
    
    136: bipush        47
    138: iconst_1
    139: dup
    140: pop2
    
    ...

    141: bipush        46
    143: iconst_1
    144: dup
    145: pop2

are all either pretty feeble attempts at obfuscation or evidence of a severely confused compiler as they have no effect at all.

From time immemorial Java class loaders have been converting the JarEntry names of class files into class names and that is what this method is doing as well.

If you have a class, for example,

    xper.mcm.CrashPow

then the name of the JarEntry for the class file in a JAR will be

    xper/mcm/CrashPow.class

Chop off the .class suffix

    117: iconst_0

      0
      
    118: iconst_1
    
      0, 1
      
    119: dup
    
      0, 1, 1

    120: pop2
    
      0
      
    121: aload         5
    
      0, entry_name
      
    123: dup_x1
    
      entry_name, 0, entry_name
      
    124: invokevirtual #19                 // Method java/lang/String.length:()I
    
      entry_name, 0, length(entry_name)
      
    127: bipush        6
    
      entry_name, 0, length(entry_name), 6
      
    129: iconst_1
    
      entry_name, 0, length(entry_name), 6, 1
      
    130: dup
    
      entry_name, 0, length(entry_name), 6, 1, 1
    
    131: pop2
    
      entry_name, 0, length(entry_name), 6
    
    132: isub
    
      entry_name, 0, length(entry_name)_minus_6
      
    133: invokevirtual #20                 // Method java/lang/String.substring:(II)Ljava/lang/String;

      substring(entry_name, 0, length(entry_name)_minus_6))

and replace the occurrences of ‘/‘ with ‘.

    136: bipush        47
    
      substring(...), 47
      
    138: iconst_1
    
      substring(...), 47, 1
      
    139: dup
    
      substring(...), 47, 1, 1
      
    140: pop2
    
      substring(...), 47
      
    141: bipush        46
    
       substring(...), 47, 46
       
    143: iconst_1
    
      substring(...), 47, 46, 1
      
    144: dup
    
      substring(...), 47, 46, 1, 1

    145: pop2
    
      substring(...), 47, 46
       
    146: invokevirtual #21                 // Method java/lang/String.replace:(CC)Ljava/lang/String;
    
      substring(...).replace(47, 46)
        
    149: astore        5

and you get the canonical Java class name back again

The method is storing the contents of the class files in the JAR it is iterating over in a HashMap with the class names as the keys.

Now where’s the JAR ?


Copyright (c) 2014 By Simon Lewis. All Rights Reserved.

Unauthorized use and/or duplication of this material without express and written permission from this blog’s author and owner Simon Lewis is strictly prohibited.

Excerpts and links may be used, provided that full and clear credit is given to Simon Lewis and justanapplication.wordpress.com with appropriate and specific direction to the original content.

September 6, 2014

The Mystery Of The Unsigned JAR: Part Two — The Sekrit Method

The ‘sekrit method’ takes a single String argument and returns a String and it could not be more obvious if it tried.

Unlike the other methods in the class it has a non-meaningful name. Instead its name features a large subset of the lower case letters of the alphabet in order. I don’t think there is any coding standard that requires that, ‘tho I suppose there could be.

It is always passed a String literal and the String literal is always gibberish, yet the return values are often passed to factory methods in the Java API which take specific well-formed names.

The inference is obvious. The method takes obfuscated Strings and turns them into unobfuscated Strings.

The nice thing about Java VM bytecode is that you can decompile it using nothing more than a pencil and a piece of paper, oh, and a knowledge of the Java VM opcodes !

Being stack based it is pretty easy to work out what is going on by tracing the state of the stack and the local variables after each instruction.

Of course if you don’t have a piece of paper and a pencil handy you can always write some code to do the same thing.

The following shows the stack/local variable state after each instruction for a large part of the sekrit method.

The top of the stack is on the right.

    ...

    
    34: invokevirtual #232                // Method java/lang/StringBuffer.toString:()Ljava/lang/String;
    
        S
    
    37: dup
    
        S, S
    
    38: invokevirtual #19                 // Method java/lang/String.length:()I
    
        S, length(S)
    
    41: iconst_1
    
        S, length(S), 0x01
    
    42: isub
    
        S, length(S)_minus_one
    
    43: iconst_2
    
        S, length(S)_minus_one, 0x02
    
    44: iconst_3
    
        S, length(S)_minus_one, 0x02, 0x03
    
    45: ishl
    
        S, length(S)_minus_one, 0x10
    
    46: iconst_3
    
        S, length(S)_minus_one, 0x10, 0x03
    
    47: iconst_5
    
        S, length(S)_minus_one, 0x10, 0x03, 0x05
    
    48: ixor
    
        S, length(S)_minus_one, 0x10, 0x06
    
    49: ixor
    
        S, length(S)_minus_one, 0x16
    
    50: iconst_2
    
        S, length(S)_minus_one, 0x16, 0x02
    
    51: iconst_5
    
        S, length(S)_minus_one, 0x16, 0x02, 0x05
    
    52: ixor
    
        S, length(S)_minus_one, 0x16, 0x07
    
    53: iconst_3
    
        S, length(S)_minus_one, 0x16, 0x07, 0x03
    
    54: ishl
    
        S, length(S)_minus_one, 0x16, 0x38
    
    55: iconst_2
    
        S, length(S)_minus_one, 0x16, 0x38, 0x02
    
    56: ixor
    
        S, length(S)_minus_one, 0x16, 0x3A
    
    57: iconst_2
    
        S, length(S)_minus_one, 0x16, 0x3A, 0x02
    
    58: iconst_5
    
        S, length(S)_minus_one, 0x16, 0x3A, 0x02, 0x05
    
    59: ixor
    
        S, length(S)_minus_one, 0x16, 0x3A, 0x07
    
    60: iconst_4
    
        S, length(S)_minus_one, 0x16, 0x3A, 0x07, 0x04
    
    61: ishl
    
        S, length(S)_minus_one, 0x16, 0x3A, 0x70
    
    62: iconst_1
    
        S, length(S)_minus_one, 0x16, 0x3A, 0x70, 0x01
    
    63: dup
    
        S, length(S)_minus_one, 0x16, 0x3A, 0x70, 0x01, 0x01
    
    64: ishl
    
        S, length(S)_minus_one, 0x16, 0x3A, 0x70, 0x02
    
    65: ixor
    
        S, length(S)_minus_one, 0x16, 0x3A, 0x72
    
    66: aload_0
    
        S, length(S)_minus_one, 0x16, 0x3A, 0x72, avar0
    
    67: invokevirtual #19                 // Method java/lang/String.length:()I
    
        S, length(S)_minus_one, 0x16, 0x3A, 0x72, length(avar0)
    
    70: dup
    
        S, length(S)_minus_one, 0x16, 0x3A, 0x72, length(avar0), length(avar0)
    
    71: newarray       char
    
        S, length(S)_minus_one, 0x16, 0x3A, 0x72, length(avar0), char[length(avar0)]
    
    73: iconst_1
    
        S, length(S)_minus_one, 0x16, 0x3A, 0x72, length(avar0), char[length(avar0)], 0x01
    
    74: dup
    
        S, length(S)_minus_one, 0x16, 0x3A, 0x72, length(avar0), char[length(avar0)], 0x01, 0x01
    
    75: pop2
    
        S, length(S)_minus_one, 0x16, 0x3A, 0x72, length(avar0), char[length(avar0)]
    
    76: swap
    
        S, length(S)_minus_one, 0x16, 0x3A, 0x72, char[length(avar0)], length(avar0)
    
    77: iconst_1
    
        S, length(S)_minus_one, 0x16, 0x3A, 0x72, char[length(avar0)], length(avar0), 0x01
    
    78: isub
    
        S, length(S)_minus_one, 0x16, 0x3A, 0x72, char[length(avar0)], length(avar0)_minus_1
    
    79: dup_x2
    
        S, length(S)_minus_one, 0x16, 0x3A, length(avar0)_minus_1, 0x72, char[length(avar0)], length(avar0)_minus_1
    
    80: istore_1
    
        S, length(S)_minus_one, 0x16, 0x3A, length(avar0)_minus_1, 0x72, char[length(avar0)]
    
        {
            ivar1 == length(avar0)_minus_1
        }
    
    81: astore_3
    
        S, length(S)_minus_one, 0x16, 0x3A, length(avar0)_minus_1, 0x72
    
        {
            ivar1 == length(avar0)_minus_1
            avar3 == char[length(avar0)]
        }
    
    
    82: istore        7
    
        S, length(S)_minus_one, 0x16, 0x3A, length(avar0)_minus_1
    
        {
            ivar1 == length(avar0)_minus_1
            avar3 == char[length(avar0)]
            ivar7 == 0x72
        }
    
    84: dup_x2
    
        S, length(S)_minus_one, length(avar0)_minus_1, 0x16, 0x3A, length(avar0)_minus_1
    
    85: pop
    
        S, length(S)_minus_one, length(avar0)_minus_1, 0x16, 0x3A
    
    86: istore        4
    
        S, length(S)_minus_one, length(avar0)_minus_1, 0x16
    
        {
            ivar1 == length(avar0)_minus_1
            avar3 == char[length(avar0)]
            ivar4 == 0X3A
            ivar7 == 0x72
        }
    
    88: pop
    
        S, length(S)_minus_one, length(avar0)_minus_1
    
    89: swap
    
        S, length(avar0)_minus_1, length(S)_minus_one
    
    90: dup
    
        S, length(avar0)_minus_1, length(S)_minus_one, length(S)_minus_one
    
    91: istore_2
    
        S, length(avar0)_minus_1, length(S)_minus_one
    
        {
            ivar1 == length(avar0)_minus_1
            ivar2 == length(S)_minus_one
            avar3 == char[length(avar0)]
            ivar4 == 0X3A
            ivar7 == 0x72
        }
    
    92: istore        5
    
        S, length(avar0)_minus_1
    
        {
            ivar1 == length(avar0)_minus_1
            ivar2 == length(S)_minus_one
            avar3 == char[length(avar0)]
            ivar4 == 0X3A
            ivar5 == length(S)_minus_one
            ivar7 == 0x72
    }
    
    94: swap
    
        length(avar0)_minus_1, S
    
    95: astore        6
    
        length(avar0)_minus_1
    
        {
            ivar1 == length(avar0)_minus_1
            ivar2 == length(S)_minus_one
            avar3 == char[length(avar0)]
            ivar4 == 0x3A
            ivar5 == length(S)_minus_one
            avar6 == S
            ivar7 == 0x72
        }
    
    97: goto          163
    
    
    100: aload_3
    
        char[length(avar0)]
    
    101: iload         4
    
        char[length(avar0)], 0x3A
    
    103: aload_0
    
        char[length(avar0)], 0x3A, avar0
    
    104: iload_1
    
        char[length(avar0)], 0x3A, avar0, ivar1
    
    105: iinc          1, -1
    
        char[length(avar0)], 0x3A, avar0, ivar1
    
    108: dup_x2
    
        char[length(avar0)], ivar1, 0x3A, avar0, ivar1
    
    109: invokevirtual #236                // Method java/lang/String.charAt:(I)C
    
        char[length(avar0)], ivar1, 0x3A, c
    
    112: aload         6
    
        char[length(avar0)], ivar1, 0x3A, c, avar6
    
    114: iload_2
    
        char[length(avar0)], ivar1, 0x3A, c, avar6, ivar2
    
    115: invokevirtual #236                // Method java/lang/String.charAt:(I)C
    
        char[length(avar0)], ivar1, 0x3A, c, d
    
    118: ixor
    
        char[length(avar0)], ivar1, 0x3A, c_xor_d
    
    119: ixor
    
        char[length(avar0)], ivar1, c_xor_d_xor_0x3A
    
    120: i2c
    
        char[length(avar0)], ivar1, c_xor_d_xor_0x3A
    
    121: castore
    
    
    122: iload_1
    
        ivar1
    
    123: ifge          130
    126: aload_3
    127: goto          167
    
    
    130: aload_3
    
        avar3
    
    131: iload         7
    
        avar3, 0x72
    
    133: aload_0
    
        avar3, 0x72, avar0
    
    134: iload_1
    
        avar3, 0x72, avar0, ivar1
    
    135: dup_x2
    
        avar3, ivar1, 0x72, avar0, ivar1
    
    136: invokevirtual #236                // Method java/lang/String.charAt:(I)C
    
        avar3, ivar1, 0x72, c
    
    139: aload         6
    
        avar3, ivar1, 0x72, c, avar6
    
    141: iload_2
    
        avar3, ivar1, 0x72, c, avar6, ivar2
    
    142: invokevirtual #236                // Method java/lang/String.charAt:(I)C
    
        avar3, ivar1, 0x72, c, d
        
    145: ixor
    
        avar3, ivar1, 0x72, c_xor_d
    
    146: ixor
    
        avar3, ivar1, c_xor_d_xor_0x72
    
    147: i2c
    
        avar3, ivar1, c_xor_d_xor_0x72
    
    148: iinc          1, -1
    151: iinc          2, -1
    154: castore
    
    
    155: iload_2
    
        ivar2
    
    156: ifge          162
    
    159: iload         5
    
        ivar5
        
    161: istore_2
    
    
    162: iload_1
    
        ivar1
        
    163: ifge          100
    
    
    166: aload_3
    
        avar3
        
    167: invokespecial #239                // Method java/lang/String."<init>":([C)V
        
        
    170: areturn

The method is basically is a loop which starts at

    100: aload_3

and ends at

    163: ifge          100

Everything before the loop is initialization code, some of which seems to have been partially obfuscated by the use of various bitwise operators acting on small integers.

Following the initialization we can deduce the following.

The local variable avar0 holds the obfuscated String passed as the argument.

The local variable avar3 holds an array of chars which will contain the unobfuscated characters.

The local variable ivar1 holds the index used to access the characters in avar0 and avar3. Note that it being decremented

    105: iinc          1, -1

and, compared against 0

    123: ifge          130

so both avar0 and avar3 are being iterated over backwards.

The local variable ivar2 is used in the same way to iterate backwards over the String in avar6.

Each time round the loop a character is added to avar3. The character is the result of XORing a character from avar0 and a character from avar6 and the constant 0x3A. [Instructions 100 to 121]

If there are still characters to be added a second character is added which is the result of XORing a character from avar0 and a character from avar6 and the constant 0x72. [Instructions 130 to 154]

Note that ivar1 is being decremented twice each time round the loop but ivar2 only once.

The String in avar6 is constructed using the name of the calling method so the obfuscation is context sensitive.

If you convert all of the above back into Java you will probably end up with something like this

        ...
    
        int     lengthA = a.length();
        int     lengthS = s.length();
        char[]  chars   = new char[lengthA];
        int     i       = lengthA - 1;
        int     j       = lengthS - 1;
    
        while (true)
        {
            char c = a.charAt(i);
            char d = s.charAt(j);
    
            chars[i] = (char)(c ^ d ^ 0x3a);
    
            --i;
            if (i < 0)
            {
                break;
            }
    
            c = a.charAt(i);
            d = s.charAt(j);
    
            chars[i] = (char)(c ^ d ^ 0x72);
    
            --i;
            --j;
            if (j < 0)
            {
                j = lengthS -1;
            }
            if (i < 0)
            {
                break;
            }
        }
        return new String(chars);

where a is the obfuscated String being passed as the argument and s is the context sensitive ‘mask’.

I doubt whether it compiles into exactly the same bytecode but it definitely deobfuscates the String literals in the class files correctly.


Copyright (c) 2014 By Simon Lewis. All Rights Reserved.

Unauthorized use and/or duplication of this material without express and written permission from this blog’s author and owner Simon Lewis is strictly prohibited.

Excerpts and links may be used, provided that full and clear credit is given to Simon Lewis and justanapplication.wordpress.com with appropriate and specific direction to the original content.

Blog at WordPress.com.

%d bloggers like this: