0x01 Java反编译工具
Java反编译提供了在线的反汇编方式,同时也给出几款反编译工具作为参考,罗列如下:
1. Procyon
https://bitbucket.org/mstrobel/procyon/wiki/Java%20Decompiler
2. CFR
http://www.benf.org/other/cfr/
3. JD
http://jd.benow.ca/
4. Fernflower
https://github.com/fesh0r/fernflower
5. JAD(非开源)
http://www.javadecompilers.com/jad
0x02 反编译BurpLoader
在Mac为了方便,我直接使用Jd-gui来载入Burploader.jar,但是很显然的能看到已经经过了混淆了。转换成字节码打开,如下:
// Original Bytecode: // // 0: bipush 12 // 2: anewarray Ljava/lang/String; // 5: dup // 6: iconst_0 // 7: ldc “\u0002\u001bW\tY\u0002\” // 9: jsr 216 // 12: aastore // 13: dup // 14: iconst_1 // 15: ldc “2C\fBN\u0016Y\nV\u001c\u0007FER\u000f\b” // 17: jsr 216 // 20: aastore // 21: dup // 22: iconst_2 // 23: ldc “\u0004^\u0017A@5_\u0004C\u001a$^\u0017A” // 25: jsr 216 // 28: aastore // 29: dup // 30: iconst_3 // 31: ldc “*e\f\\\f\u0013X” // 33: jsr 216 // 36: aastore // 37: dup // 38: iconst_4 // 39: ldc “\u000bJ\f_” // 41: jsr 216 // 44: aastore // 45: dup // 46: iconst_5 // 47: ldc “II\u0010C\u001e” // 49: jsr 216
根据乌云Drops的资料来看,混淆的算法依旧没变是Zelix KlassMaster(http://www.zelix.com/klassmaster/index.html),这个提问中同时提到了一些其他的 混淆器(http://stackoverflow.com/questions/13098606/decompiler-bytecode-and- obfuscators)。在参考链接Defcon的议题中也提到了这个混淆器的特征。我们这里用DirtyJoe(http://dirty- joe.com/)来帮反混出ZKM加密的字符串信息,使用其的Py脚本(http://dirty-joe.com/help /python_scripting.php)。
打开DirtyJoe载入Burploader.class(先解压Burploader.jar),然后宣导Method的标签栏,中间有两项没有名字 的函数clinit,往下找可以找到ZKM的加密Key.然后在Constant Pool中找到加密过的常量的值,右键选择Run Python Script,载入ZKM的解密脚本,并修正Py代码中的Key值,则可以解出加密串的内容,如图所示:
依次解出加密串,并尝试还原程序流程。我们来对比一下目前能下载到且没有混淆过的BurpLoader的代码(http://pan.baidu.com/share/link?shareid=442575&uk=2466540631),删减部分常量:
package larry.lau; import burp.StartBurp; import java.lang.reflect.Field; import java.util.prefs.Preferences; import javax.swing.JOptionPane; public class BurpLoader { public static final String readme1 = "------------------------------------------"; public static final String readme2 = " x "; public static final String readme3 = " x "; public static final String readme4 = " x "; public static final String readme5 = " x "; public static final String readme6 = " x"; public static final String readme7 = " larry_lau@163.com "; public static final String readme0 = "------------------------------------------"; private static final String[] strData = { "a", "b", "c", "d", "e", "f", "g", "h" }; private static final String[] clzzData = { "burp.ecc", "burp.voc", "burp.jfc", "burp.gtc", "burp.zi", "burp.q4c", "burp.pid", "burp.y0b" }; private static final String[] fieldData = { "b", "b", "c", "c", "c", "b", "c", "c" }; private static final String tip = "This program can not be used for commercial purposes!"; private static final String errortip = "This program can only run with burpsuite_pro_v1.5.01.jar"; private static final String[] keys = { "license1", "uG4NTkffOhFN/on7RT1nbw==" }; private static final String[] vals = { "like base64 string", "like base64 string" }; public static void main(String[] args) { try { int ret = JOptionPane.showOptionDialog(null, "This program can not be used for commercial purposes!", "BurpLoader by larry_lau@163.com", 0, 2, null, new String[] { "I Accept", "I Decline" }, null); if (ret == 0) { for (int i = 0; i < clzzData.length; i++) { Class<?> clzz = Class.forName(clzzData[i]); Field field = clzz.getDeclaredField(fieldData[i]); field.setAccessible(true); field.set(null, strData[i]); } Preferences prefs = Preferences.userNodeForPackage(StartBurp.class); for (int i = 0; i < keys.length; i++) { String v = prefs.get(keys[i], null); if (!vals[i].equals(v)) { prefs.put(keys[i], vals[i]); } } StartBurp.main(args); } } catch (Exception e) { JOptionPane.showMessageDialog(null, "This program can only run with burpsuite_pro_v1.5.01.jar", "BurpLoader by larry_lau@163.com", 0); } } }
再来对比我们混淆过的,且解出了字符串的版本(这里是1.6.37的Burp)。
0x03 BurpLoader源码分析
经过对比,整理出差不多的反汇编后的代码:
// // Decompiled by Procyon v0.5.30 // package larry.lau; import java.lang.reflect.Field; import java.io.InputStream; import java.security.CodeSource; import java.awt.HeadlessException; import java.awt.GraphicsEnvironment; import java.util.prefs.Preferences; import java.awt.Component; import javax.swing.JOptionPane; import java.security.MessageDigest; import javax.swing.UIManager; public class BurpLoader { public static final String readme0 = "Burp Suite is an integrated platform for performing security testing of web applications. "; public static final String readme1 = "If you like it, please try Free edition or <b>buy</b> Pro edition."; public static final String readme2 = "This loader is Free and CAN NOT be used for Commercial purposes!"; public static final String readme3 = "If you bought it somewhere else, you should take action against the seller."; public static final String readme4 = "No exploiting and no malware in my code. Shaby is boring!"; public static final String readme5 = "Usage:"; public static final String readme6 = "1. you need a burpsuite_pro jar file."; public static final String readme7 = "2. add burpsuite_pro jar into classpath then run burploader"; public static final String readme8 = "<ul><li>java -jar BurpLoader.jar</li>"; public static final String readme9 = "<li>java -cp BurpLoader.jar;burpsuite_pro.jar larry.lau.BurpLoader</li>"; public static final String readme10 = "<li>java -cp BurpLoader.jar:burpsuite_pro.jar larry.lau.BurpLoader</li></ul>"; public static final String readme11 = "3. To Support headless mode, add -Djava.awt.headless=true into jvm arguments."; public static final String readme12 = "4. Any suggestions, let me know."; private static final String[] a; //stringdata 对应解密出密文的那一大堆 private static final String[] b = {"llc" , "frb" , "dfd" ,"zud" ,"bdf", "uxh", "a7b" , "u6b" }; //classname数组 private static final String[] c = {}; //fileddata数组 private static final String[] d = {"license1", "uG4NTkffOhFN/on7RT1nbw=="}; // keys数组 private static final String[] e = {"pQwzPmoS+nV4oph/ti7SdybjoUB9IWHt0BGBVS1kycSvYAn2zh0uJq9gCfbOHS4mrpBSDMCrw+Aw6t2wCDaKXMJoXejEZIvQifD7ev/ieLttU9OZmtQxiQvOezD/tIy96RrqBxRJQqO1M+cHUq0emgxViBsoFVklsQmr6ayz0rTcwz9sXYvE9N4LvQ7thuRKMaO49TJR+9sjImBql1kBGTOHsJCETT5Mh62lIzXUYnpKhnWi1IjmSYXhhQnHSrTJtPMnN4lc0W36TPXlqWE7KmcGSWrjNiEWMVJ8ez1jIk2J5kSD3dV3gokc2tAm3MeslXO39AhD3RCbXV4SzJNb9A==","CONSTANT_String : 6Oxo0eZXXJNgBSjf5x9U7CXRqLnEIQaqNhLHKcSKbabe//W7jgdzbDMQor2PE44WeyvBtJFh848jEZys6bvlyLN70lJi5wqkoXe+BtrTnpS3Y9+9ygkcaUleLj7/UPie18gUNblikWyctTG/IKCPKGPZSe4JuPclh3HH9FAcd4shrJmrcAhJYCTYOFO3bqxs0kIhcZKUBUJ/DG//UIFceegfGVijiBeM9K4xbR+HfxTIg49Pqa1JTNcoWqjeq1xewqd8Ovqt+J9Zrsn7XC1zy8XyK6U65vHBA6HY/h/2Li7sxatMzfGX8m8L3hiAx7eOKBBtFJj/8VnuVggQZodIPna6xhrBBIcA1YLrQ3EXxXDLlO5yVM/S+B7oFgYMgMd4"}; // values数组 public static boolean f; public static boolean g; private static final String[] z = { "d0287d85d288c2af116e0d60878a8a24" , "This program can only run with burpsuite_pro_v1.6.37.jar", "burp.StartBurp", "LNimbus" , "main", "/burp", "md5", "BurpLoader by larry_lau", "larry.lau.javax.swing.plaf.nimbus.NimbusLookAndFeel", "burpsuite_pro_v1.6.37.jar was corrupted!" , "burp." , "%02x"}; static {} //error public static void main(final String[] array) { final boolean g = BurpLoader.g; try { try { final String s = BurpLoader.z[8]; Class.forName(s); //实例化一个字符串为名称的类 http://blog.csdn.net/kaiwii/article/details/7405761 UIManager.installLookAndFeel(BurpLoader.z[3], s); //Swing用来设置外观与风格的相关函数 } catch (ClassNotFoundException ex8) {} final Class<?> forName = Class.forName(BurpLoader.z[2]); //找不到那个外观类就实例化 burp.StartBurp 既BurpSuite的启动类 //用md5验证了啥东西 try { MessageDigest messageDigest = null; Label_0086_Outer: while (true) { final CodeSource codeSource = forName.getProtectionDomain().getCodeSource(); //取CodeSource,一般配合getLocation使用取Jar包中的路径 final MessageDigest instance = MessageDigest.getInstance(BurpLoader.z[6]); //动态获取加密算法,这里是md5 final InputStream openStream = codeSource.getLocation().openStream(); //OpenStream()打开流以读取当前生成提供程序对象的虚拟路径。 final byte[] array2 = new byte[1024]; int read = 0; while (true) { while (true) { Label_0094: { try { if (!g) { break Label_0094; } } catch (ClassNotFoundException ex) { throw ex; } messageDigest.update(array2, 0, read); //// // array2是输入字符串转换得到的字节数组 } if ((read = openStream.read(array2)) > 0) { continue Label_0086_Outer; } break; } openStream.close(); messageDigest = instance; if (!g) { break; } continue; } } StringBuffer sb = null; Label_0178_Outer: while (true) { final byte[] digest = messageDigest.digest(); //生成md5 sb = new StringBuffer(); int n = 0; while (true) { while (true) { Label_0181: { try { if (!g) { break Label_0181; } //sb.append(String.format(BurpLoader.z[11], digest[n] & 0xFF)); 优化 sb.append(String.format("%02x", digest[n] & 0xFF)) //准备输出md5? } catch (ClassNotFoundException ex2) { throw ex2; } ++n; } if (n < digest.length) { continue Label_0178_Outer; } break; } if (!g) { break; } continue; } } int equals = 0; /* 用于确认版本 ~>>> md5 burpsuite_pro_v1.6.37.jar MD5 (burpsuite_pro_v1.6.37.jar) = d0287d85d288c2af116e0d60878a8a24 */ Label_0245: { Label_0219: { int n2; try { n2 = (equals = (BurpLoader.z[0].equals(sb.toString()) ? 1 : 0)); // 比较md5的值 if (g) { break Label_0245; } if (n2 == 0) { break Label_0219; } break Label_0219; } catch (ClassNotFoundException ex3) { throw ex3; } try { if (n2 == 0) { JOptionPane.showMessageDialog(null, BurpLoader.z[9], BurpLoader.z[7], 0); //弹出提示版本不符或者被修改 System.exit(-1); } } catch (ClassNotFoundException ex4) { throw ex4; } } equals = 0; } /* 利用反射机制修改BurpSuite类相关信息,对于未混淆版本的Burploader和解密出的字符串可以重构前面的定义 */ int n3 = equals; //0 while (true) { Label_0316: { if (!g) { break Label_0316; } //final Field declaredField = Class.forName(BurpLoader.z[10] + BurpLoader.b[n3]).getDeclaredField(BurpLoader.c[n3]); 优化 //通过反射机制获取类相关的属性或去修改它 http://my.oschina.net/swords/blog/117357 //从 burp.xxx 的类中取变量,可以用jd-gui反编译去看,取对应类中的第一个Strinig变量 final Field declaredField = Class.forName("burp.b_classarray").getDeclaredField(BurpLoader.c[n3]); declaredField.setAccessible(true); declaredField.set(null, BurpLoader.a[n3]); //插入stringdata ++n3; } if (n3 < BurpLoader.b.length) { continue; } break; } Preferences preferences = null; Label_0352_Outer: while (true) { //保存用户偏好,Windows是注册表,Linux是Home目录下的文件 final Preferences node = Preferences.userRoot().node(BurpLoader.z[5]); //node("/burp") int n4 = 0; while (true) { while (true) { Label_0398: { try { if (!g) { break Label_0398; } } catch (ClassNotFoundException ex5) { throw ex5; } //比较一些偏好设定,这里其实是key和values的比较 /* String v = prefs.get(keys[i], null); if (!vals[i].equals(v)) { prefs.put(keys[i], vals[i]); } */ if (!BurpLoader.e[n4].equals(preferences.get(BurpLoader.d[n4], null))) { node.put(BurpLoader.d[n4], BurpLoader.e[n4]); } ++n4; } if (n4 < BurpLoader.d.length) { continue Label_0352_Outer; } break; } preferences = node; if (!g) { break; } continue; } } preferences.flush(); //反射调用 StartBurp.main(args); forName.getDeclaredMethod(BurpLoader.z[4], String[].class).invoke(null, array); // z4 = main } catch (Throwable t) { t.printStackTrace(); int headless = GraphicsEnvironment.isHeadless() ? 1 : 0; //测试当前是否为图形环境 Label_0504: { int n5 = 0; Label_0503: { try { n5 = headless; if (g) { break Label_0504; } if (n5 != 0) { break Label_0503; } } catch (ClassNotFoundException ex6) { throw ex6; } try { GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice(); //取默认图形环境 } catch (HeadlessException ex9) { headless = 1; } catch (InternalError internalError) { headless = 1; } } try { if (n5 != 0) { System.err.println(BurpLoader.z[1]); if (!g) { return; } } } catch (ClassNotFoundException ex7) { throw ex7; } } //JOptionPane.showMessageDialog(null, BurpLoader.z[1], BurpLoader.z[7], 0); 弹出提示框 JOptionPane.showMessageDialog(null, "This program can only run with burpsuite_pro_v1.6.37.jar", "Powered by XXX", 0); } } catch (ClassNotFoundException ex10) { System.exit(-1); } } }
从以上代码可以看出,BurpLoader的新版本破解BurpSuite的方式依旧没有发生改变,还是通过反射机制修改相关Burp类的成员变量 值来固定检测环境,同时写入Key来实现的。只不过比之前的版本,多了通过md5等方式来判断要启动的Burp主体是否是对应的版本。
0x04 使坏
原谅用了这么一个2B的小标题,Raphael Mudge牛曾在自己的Blog上写过(http://blog.cobaltstrike.com/2013/09/05/how-to-crack- cobalt-strike-and-backdoor-it/)如何破解自己的产品并加后门。事实上在BurpLoader中加上一点dirty code也是很容易的,然后再各大论坛或者某F传播出去也是能收获一批灰阔的,显而易见的例子是(http://it.rising.com.cn /dongtai/18192.html),堪比二十世纪出头灰阔圈子中各大XX联盟和XX技术小组了。最后感谢乌云Drops带来的好文章,多学习多实 践,有机会也去感受一把逆向Burp。