破解过程大致如下// 反编译插件
// 修改注册逻辑代码
// 字节码写入
// 重新打包
// 替换jar包
破解方法
- 下载官方插件
- 将安装好的插件jar拷贝到桌面
- Mac插件保存目录:
/Users/username/Library/Application Support/WebStorm2017.2/Iedis/lib/
- 通过反射和javassist将混淆的字符串还原。
- 最终找到注册相关的类,通过javassist修改字节码文件并更新到原jar中,替换jar即可
工具准备
- 反编译工具
- cfr
- procyon
- Luyten
- Fernfolower
- BytecodeViewer
- JD-gui
- 查找文本中的相应字符串工具(Linux可以用自带的grep)
- javassist(字节码修改工具)
示例:Iedis破解思路
Iedis是IDEA上的一个收费redis插件,java编写的,既然是java写的,收费这些自然是很容易绕过的。由于Java太容易被反编译,作者还是做了些代码混淆和字符串加密。
利用JD-GUI反编译出来的代码片段
可以看出类名和字符串都被混淆和加密了,从代码上很难去定位和分析他的注册流程。
package com.seventh7.widget.iedis.config;import com.intellij.icons.AllIcons.General;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.ui.Messages;class iextends e
{private static final String[] ib;private static final String[] jb;i(n paramn){super(a(20539, 52876), a(20539, 52876), AllIcons.General.Remove, a(20537, 55341), paramn);}void a(AnActionEvent paramAnActionEvent, P paramP){String str1 = String.format(a(20538, 25199), new Object[] { paramP.f() });String str2 = a(20536, 20012);try{if (Messages.showOkCancelDialog(a(), str1, str2, Messages.getQuestionIcon()) == 0) {com.seventh7.widget.iedis.d.e.a().a(a(), paramP.b());}}catch (RuntimeException localRuntimeException){throw d(localRuntimeException);}}
破解的2个思路
- 还原代码中的所有加密字符串,根据字符串的内定位到相关的代码,利用javassist修改class文件,将文件替换掉原来的文件
- 逆向出他的认证算法,然后做个注册机之类的。iedis是采用服务器认证的,每次启动都要去服务器查询激活,所以注册机不适合。但是我们可以本地架设一个认证服务。
架设认证服务器还是比较简单的,下面还是主要研究一下第一种思路。
还原字符串
从那些混淆的代码去定位软件的运行逻辑很难下手,但是我们可以换个思路,将软件运行过程中字符串都打印出来,这样我们基本上就可以得到一份软件的运行日志,对java程序进行运行时插入语句看似很麻烦,其实JVM默认就支持javaagent,写个javaagent即可达到效果,javaagent的使用可以参考《javaagent-的使用》
- 编写javaagent程序
/*** 给iedis的加密字符串函数 插入打印代码*/
public class IedisTransformer implements ClassFileTransformer {private final static String IDEA_LIB="/Applications/IntelliJ IDEA.app/Contents/lib/*";private final static String IDEIS_LIB="/Users/liaojiacan/Library/Application Support/IntelliJIdea2017.2/Iedis/lib/*";public IedisTransformer() {try {ClassPool.getDefault().appendClassPath(IDEA_LIB);ClassPool.getDefault().appendClassPath(IDEIS_LIB);} catch (NotFoundException e) {e.printStackTrace();}}@Overridepublic byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {if(className.startsWith("com/seventh7/widget/iedis")){try {CtClass clazz = ClassPool.getDefault().makeClass(new ByteArrayInputStream(classfileBuffer));CtMethod[] methods = clazz.getDeclaredMethods();CtClass string = ClassPool.getDefault().getCtClass(String.class.getName());for(CtMethod method :methods){if(method.getLongName().startsWith("com.seventh7.widget.iedis.a.p.f")){System.out.println("Inject :: SUCCESS!");method.insertBefore("if(true){return true;} ");continue;}if(method.getReturnType().equals(string)){String name = method.getLongName();System.out.println("transform the iedis method:"+name);method.insertAfter("System.out.println(\"--------------------\");" +" System.out.println(\""+name+"\"); " +" System.out.println(java.util.Arrays.toString($args)); " +" System.out.println(\"return:\"+$_);");}}return clazz.toBytecode();} catch (IOException e) {e.printStackTrace();} catch (NotFoundException e) {e.printStackTrace();} catch (CannotCompileException e) {e.printStackTrace();}}return null;}
}
- 修改System.out,把所有的print打印到我们指定的文件中 /tmp/system.out
-
public class Main {public static void premain(String agentOps, Instrumentation inst) {PrintStream out = null;try {out = new PrintStream("/tmp/system.out");} catch (FileNotFoundException e) {e.printStackTrace();}System.setOut(out);System.setErr(out);if ("iedis".equals(agentOps)){inst.addTransformer(new IedisTransformer());}else if("injectPrint".equals(agentOps)) {inst.addTransformer(new InjectPrintTransformer());}else {inst.addTransformer(new SimpleTransformer());}}public static void main(String[] args) {System.out.println(helloWorld());}public static String helloWorld(){return "This is a javaagent!";} }
- 配置idea启动配置,加入我们的javaagent
#修改idea.vmoptions文件加入下面一行配置
-javaagent:/Users/liaojiacan/Workspace/tools/decomplie/javaagent/javaagent-1.0-SNAPSHOT.jar=iedis
-Xms128m
-Xmx750m
-XX:ReservedCodeCacheSize=240m
-XX:+UseCompressedOops
-Dfile.encoding=UTF-8
-XX:+UseConcMarkSweepGC
-XX:SoftRefLRUPolicyMSPerMB=50
-ea
-Dsun.io.useCanonCaches=false
-Djava.net.preferIPv4Stack=true
-XX:+HeapDumpOnOutOfMemoryError
-XX:-OmitStackTraceInFastThrow
-Xverify:none-XX:ErrorFile=$USER_HOME/java_error_in_idea_%p.log
-XX:HeapDumpPath=$USER_HOME/java_error_in_idea.hprof
-Xbootclasspath/a:../lib/boot.jar
启动Idea 后我们可以在/tmp/system.out中可以看到这些关键的日志
--------------------
com.seventh7.widget.iedis.L.a(java.lang.String)
return:{"trailing":true,"daysLeft":9,"popup":true,"activated":false}
--------------------
com.seventh7.widget.iedis.B.a()
return:186b474e0ffffffb70ffffff96680ffffffc0240ffffff89456b0fffffffa320ffffffa70ffffff92
--------------------
com.seventh7.widget.iedis.x.a(byte[])
return:MTg2YjQ3NGUwZmZmZmZmYjcwZmZmZmZmOTY2ODBmZmZmZmZjMDI0MGZmZmZmZjg5NDU2YjBmZmZm
ZmZmYTMyMGZmZmZmZmE3MGZmZmZmZjkyOjI=
--------------------
com.seventh7.widget.iedis.L.a(java.lang.String)
return:{"trailing":true,"daysLeft":9,"popup":true,"activated":false}
--------------------com.seventh7.widget.iedis.L.a(java.lang.String)
[https://www.codesmagic.com/q2?t=MTg2YjQ3NGUwZmZmZmZmYjcwZmZmZmZmOTY2ODBmZmZmZmZjMDI0MGZmZmZmZjg5NDU2YjBmZmZmZmZmYTMyMGZmZmZmZmE3MGZmZmZmZjkyOjI=]
return:{"trailing":true,"daysLeft":9,"popup":true,"activated":false}
--------------------
com.seventh7.widget.iedis.a.p.b(int,int)
[-13938, -6118]
return:trailing
--------------------
com.seventh7.widget.iedis.a.p.b(int,int)
[-13937, -25088]
return:daysLeft
--------------------
com.seventh7.widget.iedis.a.p.b(int,int)
[-13939, 7216]
return:popup
从上面的日志可以看出一些关键点:
-
https://www.codesmagic.com/q2?t= 是注册的服务器
-
com.seventh7.widget.iedis.a.o 这个类是很关键的类
-
认证服务器返回的认证结果为
{“trailing”:true,“daysLeft”:9,“popup”:true,“activated”:false}
查看反编译的代码,可以看出这个类是一个抽象类,他的唯一子类是com.seventh7.widget.iedis.a.p,根据外面获取到的运行日志,大概可以推断出 f这个方法是认证的方法。
package com.seventh7.widget.iedis.a;import com.seventh7.widget.iedis.b.d.a;
import java.util.Map;
import java.io.IOException;
import com.seventh7.widget.iedis.L;class p extends o
{private static final String[] kb;private static final String[] lb;//基本上可以推断出 这个就是认证的方法,最直接的方法就是直接return true@Overrideprotected boolean f() throws IOException {//this.d() 是调用https://www.codesmagic.com/q2去注册的//Map的返回值{"trailing":true,"daysLeft":9,"popup":true,"activated":false}final Map d = this.d();//trailingfinal boolean booleanValue = L.a(d, b(-13938, -6118));//this.d()执行后的异常信息。final a[] b = av.b();//daysLeftfinal int b2 = L.b(d, b(-13937, -25088));//popupfinal boolean booleanValue2 = L.a(d, b(-13939, 7216));boolean booleanValue3 = false;Label_0104: {Label_0074: {boolean b3;try {b3 = (booleanValue3 = booleanValue);if (b != null) {break Label_0104;}if (b3) {break Label_0074;}break Label_0074;}catch (IOException ex) {throw b(ex);}try {if (b3) {this.a(b2, booleanValue2);return false;}}catch (IOException ex2) {throw b(ex2);}}//activedbooleanValue3 = L.a(d, b(-13940, 8507));}final boolean b4 = booleanValue3;//如果已经过了试用,就检测激活Label_0122: {boolean b5;try {final boolean b6;b5 = (b6 = b4);// b = av.b()if (b != null) {return b6;}if (!b5) {break Label_0122;}return true;}catch (IOException ex3) {throw b(ex3);}try {if (!b5) {this.c();return false;}}catch (IOException ex4) {throw b(ex4);}}return true;}private static IOException b(final IOException ex) {return ex;}}
从上面的分享结果可以看出,有两种破解思路
-
方法一 修改 com.seventh7.widget.iedis.a.p.f 永远return true
-
方法二 搭建一个认证服务器,本地替换host,认证服务器返回的结果为
|
方法一的实现
public class IedisCracker {private final static String IDEA_LIB="/Applications/IntelliJ IDEA.app/Contents/lib/*";private final static String IDEIS_LIB="/Users/liaojiacan/Library/Application Support/IntelliJIdea2017.2/Iedis/lib/*";public static void main(String[] args) {try {ClassPool.getDefault().appendClassPath(IDEA_LIB);ClassPool.getDefault().appendClassPath(IDEIS_LIB);CtClass clazz = ClassPool.getDefault().getCtClass("com.seventh7.widget.iedis.a.p");CtMethod[] mds = clazz.getDeclaredMethods();for(CtMethod method : mds){if(method.getLongName().startsWith("com.seventh7.widget.iedis.a.p.f")){System.out.println("Inject :: SUCCESS!");try {method.insertBefore("if(true){return true;} ");} catch (CannotCompileException e) {e.printStackTrace();}continue;}}clazz.writeFile("/tmp/p.class");} catch (NotFoundException e) {e.printStackTrace();} catch (CannotCompileException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}}
}
示例二:
Javassist实现的破解IDEA MybatisPlugin修改字节码工具,仅供学习用途。
https://github.com/An0nymous0/MybatisPlugin-Crack-Javassist
ps:
javaagent 的使用:
javaagent 是类似一个JVM的插件,利用JVM提供的Instrumentation API实现获取或者修改加载到JVM中的类字节码。
编写一个javagent的jar的方式如下:
1.实现一个ClassFileTransformer
public class SimpleTransformer implements ClassFileTransformer {@Overridepublic byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {System.out.println(className);System.out.println(protectionDomain.toString());return new byte[0];}
}
2.实现一个Premain-Class
public class Main {public static void premain(String agentOps, Instrumentation inst) {inst.addTransformer(new SimpleTransformer());}public static void main(String[] args) {System.out.println("This is a javaagent!");}
}
3.MANIFEST.MF配置
Manifest-Version: 1.0
Premain-Class: com.github.liaojiacan.Main
Can-Redefine-Classes: true
Can-Retransform-Classes: true
Can-Set-Native-Method-Prefix: true
4.运行命令
|
代码和assembly的打包配置可以参考
github
参考:https://www.awei.org/2017/11/19/idea-iedis-plugin-2-41-po-jie-fang-fa/