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.