Skip to content

Commit b75cb67

Browse files
committed
Added Allatori string decrypter
1 parent 5685681 commit b75cb67

File tree

3 files changed

+304
-3
lines changed

3 files changed

+304
-3
lines changed
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package the.bytecode.club.bytecodeviewer.gui;
2+
3+
import java.awt.Dimension;
4+
import java.awt.event.ActionEvent;
5+
import java.awt.event.ActionListener;
6+
7+
import javax.swing.JButton;
8+
import javax.swing.JFrame;
9+
import javax.swing.JLabel;
10+
import javax.swing.JTextField;
11+
12+
import the.bytecode.club.bytecodeviewer.Resources;
13+
import the.bytecode.club.bytecodeviewer.plugin.PluginManager;
14+
import the.bytecode.club.bytecodeviewer.plugin.preinstalled.AllatoriStringDecrypter;
15+
16+
/***************************************************************************
17+
* Bytecode Viewer (BCV) - Java & Android Reverse Engineering Suite *
18+
* Copyright (C) 2014 Kalen 'Konloch' Kinloch - http://bytecodeviewer.com *
19+
* *
20+
* This program is free software: you can redistribute it and/or modify *
21+
* it under the terms of the GNU General Public License as published by *
22+
* the Free Software Foundation, either version 3 of the License, or *
23+
* (at your option) any later version. *
24+
* *
25+
* This program is distributed in the hope that it will be useful, *
26+
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
27+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
28+
* GNU General Public License for more details. *
29+
* *
30+
* You should have received a copy of the GNU General Public License *
31+
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
32+
***************************************************************************/
33+
34+
/**
35+
* The UI for replace strings plugin.
36+
*
37+
* @author Konloch
38+
*
39+
*/
40+
41+
public class AllatoriStringDecrypterOptions extends JFrame {
42+
public AllatoriStringDecrypterOptions() {
43+
this.setIconImages(Resources.iconList);
44+
setSize(new Dimension(250, 120));
45+
setResizable(false);
46+
setTitle("Allatori decrypter");
47+
getContentPane().setLayout(null);
48+
49+
JButton btnNewButton = new JButton("Decrypt");
50+
btnNewButton.setBounds(6, 56, 232, 23);
51+
getContentPane().add(btnNewButton);
52+
53+
54+
JLabel lblNewLabel = new JLabel("Class:");
55+
lblNewLabel.setBounds(6, 20, 67, 14);
56+
getContentPane().add(lblNewLabel);
57+
58+
textField = new JTextField();
59+
textField.setToolTipText("* will search all classes");
60+
textField.setText("*");
61+
textField.setBounds(80, 17, 158, 20);
62+
getContentPane().add(textField);
63+
textField.setColumns(10);
64+
65+
66+
btnNewButton.addActionListener(new ActionListener() {
67+
public void actionPerformed(ActionEvent arg0) {
68+
PluginManager.runPlugin(new AllatoriStringDecrypter(textField.getText()));
69+
dispose();
70+
}
71+
});
72+
this.setLocationRelativeTo(null);
73+
}
74+
75+
private static final long serialVersionUID = -2662514582647810868L;
76+
private JTextField textField;
77+
}

src/the/bytecode/club/bytecodeviewer/gui/MainViewerGUI.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2029,8 +2029,12 @@ public void actionPerformed(ActionEvent e) {
20292029
});
20302030
mntmNewMenuItem_2.addActionListener(new ActionListener() {
20312031
@Override
2032-
public void actionPerformed(ActionEvent e) {
2033-
PluginManager.runPlugin(new AllatoriStringDecrypter());
2032+
public void actionPerformed(ActionEvent arg0) {
2033+
if(BytecodeViewer.getLoadedClasses().isEmpty()) {
2034+
BytecodeViewer.showMessage("First open a class, jar, zip, apk or dex file.");
2035+
return;
2036+
}
2037+
new AllatoriStringDecrypterOptions().setVisible(true);
20342038
}
20352039
});
20362040
mntmNewMenuItem_1.addActionListener(new ActionListener() {

src/the/bytecode/club/bytecodeviewer/plugin/preinstalled/AllatoriStringDecrypter.java

Lines changed: 221 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,27 @@
11
package the.bytecode.club.bytecodeviewer.plugin.preinstalled;
22

3+
import java.io.IOException;
4+
import java.lang.reflect.Method;
35
import java.util.ArrayList;
6+
import java.util.Arrays;
7+
import java.util.List;
48

9+
import javax.swing.JDialog;
10+
import javax.swing.JOptionPane;
11+
12+
import org.objectweb.asm.tree.AbstractInsnNode;
513
import org.objectweb.asm.tree.ClassNode;
14+
import org.objectweb.asm.tree.InsnList;
15+
import org.objectweb.asm.tree.InvokeDynamicInsnNode;
16+
import org.objectweb.asm.tree.LdcInsnNode;
17+
import org.objectweb.asm.tree.MethodInsnNode;
18+
import org.objectweb.asm.tree.MethodNode;
619

720
import the.bytecode.club.bytecodeviewer.BytecodeViewer;
21+
import the.bytecode.club.bytecodeviewer.JarUtils;
22+
import the.bytecode.club.bytecodeviewer.api.ExceptionUI;
823
import the.bytecode.club.bytecodeviewer.api.Plugin;
24+
import the.bytecode.club.bytecodeviewer.api.PluginConsole;
925

1026
/***************************************************************************
1127
* Bytecode Viewer (BCV) - Java & Android Reverse Engineering Suite *
@@ -29,14 +45,218 @@
2945
* Coming soon.
3046
*
3147
* @author Konloch
48+
* @author Szperak
3249
*
3350
*/
3451

3552
public class AllatoriStringDecrypter extends Plugin {
53+
54+
PluginConsole frame = new PluginConsole("Allatori decrypter");
55+
StringBuilder out = new StringBuilder();
56+
57+
private String className;
3658

59+
60+
public AllatoriStringDecrypter(String className) {
61+
this.className = className;
62+
}
63+
3764
@Override
3865
public void execute(ArrayList<ClassNode> classNodeList) {
39-
BytecodeViewer.showMessage("This is a planned feature.");
66+
JOptionPane pane = new JOptionPane(
67+
"WARNING: This will load the classes into the JVM and execute allatori decrypter function"
68+
+ BytecodeViewer.nl
69+
+ "for each class. IF THE FILE YOU'RE LOADING IS MALICIOUS, DO NOT CONTINUE.");
70+
Object[] options = new String[] { "Continue", "Cancel" };
71+
pane.setOptions(options);
72+
JDialog dialog = pane.createDialog(BytecodeViewer.viewer,
73+
"Bytecode Viewer - WARNING");
74+
dialog.setVisible(true);
75+
Object obj = pane.getValue();
76+
int result = -1;
77+
for (int k = 0; k < options.length; k++)
78+
if (options[k].equals(obj))
79+
result = k;
80+
81+
if (result == 0) {
82+
try {
83+
84+
if (!className.equals("*")) {
85+
for (ClassNode classNode : classNodeList) {
86+
if (classNode.name.equals(className))
87+
scanClassNode(classNode);
88+
}
89+
} else {
90+
for (ClassNode classNode : classNodeList) {
91+
scanClassNode(classNode);
92+
}
93+
}
94+
}catch(Exception e){
95+
new ExceptionUI(e, "github.com/Szperak");
96+
} finally {
97+
frame.appendText(out.toString());
98+
frame.setVisible(true);
99+
}
100+
}
101+
}
102+
103+
private void log(String msg){
104+
out.append(msg);
105+
out.append(BytecodeViewer.nl);
106+
}
107+
108+
public void scanClassNode(ClassNode classNode) throws Exception {
109+
for(MethodNode method : classNode.methods){
110+
scanMethodNode(classNode, method);
111+
}
112+
113+
}
114+
115+
116+
public int readUnsignedShort(byte[] b, final int index) {
117+
return ((b[index] & 0xFF) << 8) | (b[index + 1] & 0xFF);
118+
}
119+
private int getConstantPoolSize(String className){
120+
byte[] fileContents = BytecodeViewer.getFileContents(className+".class");
121+
return readUnsignedShort(fileContents, 8);
122+
}
123+
124+
125+
public void scanMethodNode(ClassNode classNode, MethodNode methodNode) throws Exception {
126+
InsnList iList = methodNode.instructions;
127+
128+
log("Scanning method " + methodNode.name+" of " + classNode.name);
129+
130+
LdcInsnNode laststringldconstack = null;
131+
for (AbstractInsnNode i : iList.toArray()) {
132+
if(i instanceof LdcInsnNode) {
133+
LdcInsnNode ldci = (LdcInsnNode) i;
134+
if(ldci.cst instanceof String){
135+
laststringldconstack = ldci;
136+
}
137+
continue;
138+
} else if(i instanceof MethodInsnNode) {
139+
MethodInsnNode methodi = (MethodInsnNode) i;
140+
141+
142+
143+
if(laststringldconstack != null && methodi.opcode() == 0xb8) { // Decryption is always a static call - 0xb8 - invokestatic
144+
String decrypterclassname = methodi.owner;
145+
String decryptermethodname = methodi.name;
146+
147+
if(decrypterclassname.contains("$")) { // Decrypter is always a static method of other class's inner class
148+
byte[] decrypterFileContents = BytecodeViewer.getFileContents(decrypterclassname+".class");
149+
150+
// We have to create new node for editing
151+
// Also, one decrypter method could be used for multiple methods in code, what gives us only part of string decrypted
152+
ClassNode decrypterclassnode = JarUtils.getNode(decrypterFileContents);
153+
154+
if(decrypterclassnode != null) {
155+
MethodNode decryptermethodnode = decrypterclassnode.getMethodByName(decryptermethodname);
156+
if(decryptermethodnode != null) {
157+
158+
String keyString = (getConstantPoolSize(classNode.name)+
159+
classNode.name+
160+
methodNode.name+
161+
getConstantPoolSize(classNode.name)
162+
);
163+
164+
int newHashCode = keyString.hashCode();
165+
166+
scanDecrypter(decryptermethodnode, newHashCode);
167+
168+
try {
169+
System.out.println("loading " + decrypterclassname);
170+
171+
List<Class<?>> decrypterclasslist = the.bytecode.club.bytecodeviewer.api.BytecodeViewer
172+
.loadClassesIntoClassLoader(new ArrayList<ClassNode>(
173+
Arrays.asList(new ClassNode[] { decrypterclassnode })));
174+
175+
String decrypted = invokeDecrypter(decrypterclasslist.get(0), decryptermethodname, (String) laststringldconstack.cst);
176+
177+
if (decrypted != null) {
178+
log("Succesfully invoked decrypter method: "+decrypted);
179+
laststringldconstack.cst = decrypted;
180+
iList.remove(methodi);
181+
}
182+
} catch (IndexOutOfBoundsException | ClassNotFoundException | IOException e) {
183+
e.printStackTrace();
184+
log("Could not load decrypter class: " + decrypterclassname);
185+
}
186+
187+
}else{
188+
log("Could not find decrypter method ("+decryptermethodname+") of class "+decrypterclassname);
189+
}
190+
}else{
191+
log("Could not find decrypter ClassNode of class "+decrypterclassname);
192+
}
193+
}
194+
}
195+
196+
}else if(i instanceof InvokeDynamicInsnNode){
197+
InvokeDynamicInsnNode methodi = (InvokeDynamicInsnNode) i;
198+
if(methodi.opcode() == 0xba){
199+
// TODO: Safe-reflection deobfuscator here
200+
// Allatori replaces invokeinterface and invokestatic with invokedynamic
201+
202+
//log(methodi.bsm.getOwner()+" dot "+methodi.bsm.getName());
203+
//iList.set(methodi, new MethodInsnNode(0xb8, methodi.bsm.getOwner(), methodi.bsm.getName(), methodi.bsm.getDesc(), false));
204+
205+
}
206+
207+
208+
}
209+
laststringldconstack = null;
210+
}
40211
}
41212

213+
214+
private boolean scanDecrypter(MethodNode decryptermethodnode, int newHashCode){
215+
InsnList iList = decryptermethodnode.instructions;
216+
217+
AbstractInsnNode insn = null, removeInsn = null;
218+
for (AbstractInsnNode i : iList.toArray()) {
219+
if(i instanceof MethodInsnNode){
220+
MethodInsnNode methodi = ((MethodInsnNode) i);
221+
if("currentThread".equals(methodi.name)){ // find code form this instruction
222+
insn = i;
223+
break;
224+
}
225+
226+
}
227+
228+
}
229+
if(insn == null){
230+
return false;
231+
}
232+
233+
while(insn != null){
234+
if(insn instanceof MethodInsnNode){
235+
MethodInsnNode methodi = ((MethodInsnNode) insn);
236+
if("hashCode".equals(methodi.name)){ // to this instruction
237+
break;
238+
}
239+
}
240+
removeInsn = insn;
241+
insn = insn.getNext();
242+
iList.remove(removeInsn); // and remove it
243+
}
244+
if(insn == null) return false;
245+
iList.set(insn, new LdcInsnNode(newHashCode)); // then replace it with pre-computed key LDC
246+
return true;
247+
}
248+
249+
private String invokeDecrypter(Class<?> decrypterclass, String name, String arg) throws Exception{
250+
try {
251+
Method decryptermethod = decrypterclass.getDeclaredMethod(name, String.class);
252+
253+
decryptermethod.setAccessible(true);
254+
return (String) decryptermethod.invoke(null, arg);
255+
256+
} catch (Exception e) {
257+
log("Could not invoke decrypter method: "+name+" of class "+decrypterclass.getName());
258+
throw e;
259+
}
260+
}
261+
42262
}

0 commit comments

Comments
 (0)