有沒有想過,XMind是如何被破解的?那么今天我們就來看看javaassit這項技術,其實在你接觸的很多其他工具中這個工具早就被廣泛使用了
javaassit
我們知道,java是一門面向對象的編程語言,更是一門面向切面的編程語言,正是這個特性,讓Java更加地靈活。
可能你寫過基于Spring AOP的代碼,其原理都是基于JDK動態(tài)代理或者CGLIB來實現(xiàn),其局限性在于我們只能以方法作為連接點,來實現(xiàn)基于方法執(zhí)行過程的代理。
你可還知道更厲害的代理工具:AspectJ、javaassit,這些都是基于字節(jié)碼,屬于更底層,但是功能更強大的代理。
知識點
- ASM
通過指令修改class字節(jié)碼,主要基于ClassReader結合JVM指令集直接操作字節(jié)碼,Cglib即是通過該技術實現(xiàn)。
- JavaAssit
基于org.javassist:javassist類庫提供的CtPool工具類對字節(jié)碼進行修改
- Instrumentation
JVM提供的一個可以修改已加載類的類庫,通過編寫java代碼即可完成對字節(jié)碼的修改
- JavaAgent
JVM加載類之前與JVM運行時,基于JavaAssit、Instrumentation實現(xiàn)字節(jié)碼修改并加載到JVM
應用場景
- IDE的調試功能,例如 Eclipse、IntelliJ IDEA
- 熱部署功能,例如 JRebel、XRebel、spring-loaded
- 線上診斷工具,例如 Btrace、Greys,還有阿里的 Arthas
- 性能分析工具,例如 Visual VM、JConsole、TProfiler等
- 全鏈路性能檢測工具,例如 Skywalking、Pinpoint等
示例
下面我們基于javaagent以及運行時Attach的模式看下javaassit如何實現(xiàn)目標類的代理的:
基于javaagent
- 編寫代理類
方法簽名固定,方法名為 premain ,參數(shù)分別對應args(不是數(shù)組)以及Instrumentation
public class JavaAgent {
private static final String TARGET_CLASS_NAME = "com.sucl.blog.javaassit.Target";
public static void premain(String args, Instrumentation instrumentation){
AgentHelper.create(TARGET_CLASS_NAME).proxy(args, instrumentation);
}
}
- 打包代理類
這里我們借助maven插件 maven-shade-plugin ,主要是為了打包時修改/META-INF/MANIFEST.MF文件,需要加上Premain-Class這項
< plugin >
< groupId >org.apache.maven.plugins< /groupId >
< artifactId >maven-shade-plugin< /artifactId >
< version >2.3< /version >
< executions >
< execution >
< phase >package< /phase >
< goals >
< goal >shade< /goal >
< /goals >
< configuration >
< transformers >
< transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer" >
< manifestEntries >
< Premain-Class >com.sucl.blog.agent.JavaAgent< /Premain-Class >
< Agent-Class >com.sucl.blog.agent.AttachAgent< /Agent-Class >
< Can-Redefine-Classes >true< /Can-Redefine-Classes >
< Can-Retransform-Classes >true< /Can-Retransform-Classes >
< /manifestEntries >
< /transformer >
< /transformers >
< /configuration >
< /execution >
< /executions >
< /plugin >
- 編寫測試類
目的很簡單,每隔3秒打印當前時間
public class JavaAgentMain {
public static void main(String[] args) throws InterruptedException {
Target target = new Target();
while (true) {
target.print(new Date());
TimeUnit.SECONDS.sleep(3);
}
}
}
@Slf4j
class Target {
public void print(Object obj) {
log.info("打印內容:{}", obj);
}
}
- 配置代理
如何讓我們編寫的代理生效,這里提供兩種方法:
- 當你使用IDEA啟動時,可以在Config Configurations中通過配置VM OPTION,添加如下內容:
-javaagent:/your_jar_path/agent.jar=param=value
- 當你使用java命令啟動時:
java -javaagent:/path/agent.jar=param=value -jar xxx.jar
- 測試
執(zhí)行測試類main方法,你可以看到,在打印時間前后,分別會打印“開始執(zhí)行方法:print”,“結束執(zhí)行方法:print”,這也是我們代理類實現(xiàn)的功能。
>> > 開始執(zhí)行方法:print
14:46:09.457 [main] INFO com.sucl.blog.javaassit.Target - 打印內容:Fri Mar 10 14:46:09 CST 2023
>> > 結束執(zhí)行方法:print
基于Attach
- 編寫代理類
方法簽名固定,方法名為 attachmain ,參數(shù)分別對應args(不是數(shù)組)以及Instrumentation; 和上面的相比唯一的不同是方法名稱。
public class AttachAgent {
private static final String TARGET_CLASS_NAME = "com.sucl.blog.javaassit.Target";
public static void agentmain(String args, Instrumentation instrumentation){
System.out.println(String.format(" >> > agentmain starting, args: %s",args));
AgentHelper.create(TARGET_CLASS_NAME).proxy(args, instrumentation);
System.out.println(String.format(" >> > agentmain finished"));
}
}
- 打包代理類
同樣借助插件 maven-shade-plugin ,主要是為了打包時修改/META-INF/MANIFEST.MF文件,需要加上Agent-Class這項
< !-- 省略 ...-- >
< Agent-Class >com.sucl.blog.agent.AttachAgent< /Agent-Class >
< !-- 省略 ...-- >
注意,這里我們使用了ClassPool、CtClass、CtMethod相關的類,記得在pom.xml中引入對應的依賴
< dependency >
< groupId >org.javassist< /groupId >
< artifactId >javassist< /artifactId >
< /dependency >
- 編寫測試類
測試類完全一樣,由于啟動代理織入的方式不一樣,因此分為兩個類
public class AttachAgentMain {
public static void main(String[] args) throws InterruptedException {
Target target = new Target();
while (true) {
target.print(new Date());
TimeUnit.SECONDS.sleep(3);
}
}
}
- 執(zhí)行代理
如何將編寫的代碼(AttachAgent)織入到目標類完成對目標類(Target)方法的代理?
這里我們需要用到jdk中的tool.jar,你可以在測試模塊中添加下面的依賴:
< dependency >
< groupId >com.sun< /groupId >
< artifactId >tools< /artifactId >
< version >1.8< /version >
< scope >system< /scope >
< systemPath >${java.home}/../lib/tools.jar< /systemPath >
< /dependency >
如何在運行時進行代理織入:
public class AttachAgentTests {
private static String JAR_PATH = AttachAgentTests.class.getClassLoader().getResource("").getPath().replace("test-classes/","")+"agent.jar";
@Test
public void attachAgent() throws Exception {
String pid = findPid(KEY); // 通過jps命令找到AttachAgentMain執(zhí)行的pid
VirtualMachine virtualMachine = VirtualMachine.attach(pid);
virtualMachine.loadAgent(JAR_PATH.substring(1));
virtualMachine.detach();
}
}
- 測試
- a. 先執(zhí)行測試代碼(AttachAgentMain.java),此時每間隔3秒會打印當前時間。
- b. 執(zhí)行代理織入方法(AttachAgentTests#attachAgent)
- c. 觀察測試代碼輸出結果,你會會發(fā)現(xiàn)此時每次打印時間前后都會有“開始執(zhí)行方法:print”,“結束執(zhí)行方法:print”
AgentHelper
public class AgentHelper {
private String targetClassName;
private AgentHelper(String targetClassName) {
this.targetClassName = targetClassName;
}
public static AgentHelper create(String targetClassName){
AgentHelper agentHelper = new AgentHelper(targetClassName);
return agentHelper;
}
public void proxy(String args, Instrumentation instrumentation){
Class targetClass = obtainTargetClass(instrumentation);
try {
instrumentation.addTransformer(new SimpleTransformer(targetClassName), true);
instrumentation.retransformClasses(targetClass); //
} catch (Exception e) {
System.out.println(String.format(" >> > agentmain failure, error: %s: %s", e.getClass().getName(),e.getLocalizedMessage()));
e.printStackTrace();
}
}
private Class obtainTargetClass(Instrumentation instrumentation) {
Class targetClass = null;
for (Class loadedClass : instrumentation.getAllLoadedClasses()) {
if(targetClassName.equals(loadedClass.getName())){
targetClass = loadedClass;
}
}
if(targetClass == null){
try {
// 無法加載
targetClass = Class.forName(targetClassName);
} catch (ClassNotFoundException e) {
System.out.println(String.format(" >> > Class [%s] not found", targetClassName));
}
}
return targetClass;
}
public static class SimpleTransformer implements ClassFileTransformer {
private String targetClassName;
public SimpleTransformer(String targetClassName) {
this.targetClassName = targetClassName;
}
@Override
public byte[] transform(ClassLoader loader, String className, Class< ? > classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
if(!className.equals(targetClassName.replaceAll(".","/"))){
return null;
}
ClassPool classPool = ClassPool.getDefault();
System.out.println(String.format("+++++ 代理類名:%s", className));
try {
CtClass ctClass = classPool.get(className.replace("/","."));
CtMethod[] ctMethods = ctClass.getDeclaredMethods();
for (CtMethod ctMethod : ctMethods) { // 所有類方法
ctMethod.insertBefore(String.format("{System.out.println(" >> > 開始執(zhí)行方法:%s");}",ctMethod.getName()));
ctMethod.insertAfter(String.format("{System.out.println(" >> > 結束執(zhí)行方法:%s");}",ctMethod.getName()));
}
return ctClass.toBytecode();
} catch (NotFoundException | CannotCompileException | IOException e) {
System.out.println(String.format("+++++ 代理出錯:%s",e.getMessage()));
e.printStackTrace();
}
return classfileBuffer;
}
}
}
通過上面的例子可以看到,兩種方式的比對如下:
對比 | JavaAgent | AttachAgent |
---|---|---|
/META-INF/MANIFEST.MF | Premain-Class | Agent-Class |
代理類方法名稱 | premain | attachmain |
代理入口 | VM配置:-javaagent | JVM attach進程ID |
代理時機 | JVM加載字節(jié)碼時 | 程序運行時 |
作用 | Java桌面程序 | Web應用 |
原理
代理可以發(fā)送在編譯時,類加載時或者是運行時。
這里你要清楚, java程序的入口是main方法 ,不管是普通程序(比如桌面應用、可執(zhí)行jar)或是Web應用(在Web容器中運行的基于Servlet的應用)
以javaagent為例,是在執(zhí)行main方法前對已經(jīng)加載到JVM的類進行修改,從而實現(xiàn)對目標類的代理,這里的修改是在字節(jié)碼層面的,當然我們可以基于ASM工具庫來實現(xiàn),但是門檻太高。
基于Instrumentation可以與編寫java代碼一樣,實現(xiàn)修改字節(jié)碼來
ClassPool:保存CtClass的池子,通過classPool.get(類全路徑名)來獲取CtClass CtClass:編譯時類信息,它是一個class文件在代碼中的抽象表現(xiàn)形式 CtMethod:對應類中的方法 CtField:對應類中的屬性、變量
XMind
還記得XMind8的破解之法嗎?
是不是需要在XMind.ini文件中插入這樣一段:-javaagent:.../XMindCrack.jar 要是你打開這個jar,你會看到這樣的內容:
首先你需要知道其原理,是通過/plugins/net.xmind.verify.jar中提供的方法LicenseVerifier#doCheckLicenseKeyBlacklisted來進行身份校驗
我們是不是只用修改License的校驗方法 doCheckLicenseKeyBlacklisted ,忽略其校驗過程并直接返回true就完事了?當然截圖中就是這樣做的,如果你想看懂那幾行代碼,可能你先要去學習ASM相關的知識。
InsnList insnList = methodNode.instructions;
insnList.clear();
insnList.add((AbstractInsnNode)new InsnNode(4));
insnList.add((AbstractInsnNode)new InsnNode(172));
methodNode.exceptions.clear();
methodNode.visitEnd();
以上代碼其實就是講方法體清除,并寫入“return true”
結束語
通過示例了解javaassit如何實現(xiàn)代對目標類的代理。是不是覺得java應用程序都能被修改,那不是太不安全了?所以,你覺得呢...
-
JAVA
+關注
關注
20文章
2982瀏覽量
106398 -
編程語言
+關注
關注
10文章
1952瀏覽量
35556 -
代碼
+關注
關注
30文章
4872瀏覽量
69913 -
代理
+關注
關注
1文章
44瀏覽量
11279
發(fā)布評論請先 登錄
相關推薦
JDK動態(tài)代理的原理
求教labview是否實現(xiàn)代碼編程? 多謝
適配器模式和代理模式的區(qū)別
實現(xiàn)代碼自動生成的步驟
基于聚類融合的多目標跟蹤算法
java動態(tài)代理機制詳解的類和接口描述
基于聚類的多目標遺傳算法在類職責分配中的應用

空間鄰近的點目標聚類實現(xiàn)方法

評論