关山难越,谁悲失路之人;萍水相逢,尽是他乡之客。
百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程教程 > 技术文章 > 正文

XMind原来是这样被破解的...

guanshanw 2023-09-29 21:52 19 浏览 0 评论

有没有想过,XMind是如何被破解的?那么今天我们就来看看javaassit这项技术,其实在你接触的很多其他工具中这个工具早就被广泛使用了

javaassit

我们知道,java是一门面向对象的编程语言,更是一门面向切面的编程语言,正是这个特性,让Java更加地灵活。

可能你写过基于Spring AOP的代码,其原理都是基于JDK动态代理或者CGLIB来实现,其局限性在于我们只能以方法作为连接点,来实现基于方法执行过程的代理。

你可还知道更厉害的代理工具:AspectJ、javaassit,这些都是基于字节码,属于更底层,但是功能更强大的代理。

知识点

  • ASM

通过指令修改class字节码,主要基于ClassReader结合JVM指令集直接操作字节码,Cglib即是通过该技术实现。

  • JavaAssit

基于org.javassist:javassist类库提供的CtPool工具类对字节码进行修改

  • Instrumentation

JVM提供的一个可以修改已加载类的类库,通过编写java代码即可完成对字节码的修改

  • JavaAgent

JVM加载类之前与JVM运行时,基于JavaAssit、Instrumentation实现字节码修改并加载到JVM

应用场景

  • IDE的调试功能,例如 Eclipse、IntelliJ IDEA
  • 热部署功能,例如 JRebel、XRebel、spring-loaded
  • 线上诊断工具,例如 Btrace、Greys,还有阿里的 Arthas
  • 性能分析工具,例如 Visual VM、JConsole、TProfiler等
  • 全链路性能检测工具,例如 Skywalking、Pinpoint等

示例

下面我们基于javaagent以及运行时Attach的模式看下javaassit如何实现目标类的代理的:

基于javaagent

  1. 编写代理类

方法签名固定,方法名为premain,参数分别对应args(不是数组)以及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);
    }

}
  1. 打包代理类

这里我们借助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>
  1. 编写测试类

目的很简单,每隔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);
    }
}
  1. 配置代理

如何让我们编写的代理生效,这里提供两种方法:

  • 当你使用IDEA启动时,可以在Config Configurations中通过配置VM OPTION,添加如下内容:

-javaagent:/your_jar_path/agent.jar=param=value

  • 当你使用java命令启动时:

java -javaagent:/path/agent.jar=param=value -jar xxx.jar

  1. 测试

执行测试类main方法,你可以看到,在打印时间前后,分别会打印“开始执行方法:print”,“结束执行方法:print”,这也是我们代理类实现的功能。

>>> 开始执行方法:print
14:46:09.457 [main] INFO com.sucl.blog.javaassit.Target - 打印内容:Fri Mar 10 14:46:09 CST 2023
>>> 结束执行方法:print

基于Attach

  1. 编写代理类

方法签名固定,方法名为attachmain,参数分别对应args(不是数组)以及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"));
    }

}
  1. 打包代理类

同样借助插件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>
  1. 编写测试类

测试类完全一样,由于启动代理织入的方式不一样,因此分为两个类

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);
        }
    }

}
  1. 执行代理

如何将编写的代码(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执行的pid
        VirtualMachine virtualMachine = VirtualMachine.attach(pid);
        virtualMachine.loadAgent(JAR_PATH.substring(1));
        virtualMachine.detach();
    }
}
  1. 测试
  • a. 先执行测试代码(AttachAgentMain.java),此时每间隔3秒会打印当前时间。
  • b. 执行代理织入方法(AttachAgentTests#attachAgent)
  • c. 观察测试代码输出结果,你会会发现此时每次打印时间前后都会有“开始执行方法:print”,“结束执行方法: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(\">>> 开始执行方法:%s\");}",ctMethod.getName()));
                    ctMethod.insertAfter(String.format("{System.out.println(\">>> 结束执行方法:%s\");}",ctMethod.getName()));
                }
                return ctClass.toBytecode();
            } catch (NotFoundException | CannotCompileException | IOException e) {
                System.out.println(String.format("+++++ 代理出错:%s",e.getMessage()));
                e.printStackTrace();
            }
            return classfileBuffer;
        }
    }
}

通过上面的例子可以看到,两种方式的比对如下:

对比JavaAgentAttachAgent/META-INF/MANIFEST.MFPremain-ClassAgent-Class代理类方法名称premainattachmain代理入口VM配置:-javaagentJVM attach进程ID代理时机JVM加载字节码时程序运行时作用Java桌面程序Web应用

原理

代理可以发送在编译时,类加载时或者是运行时。

这里你要清楚,java程序的入口是main方法,不管是普通程序(比如桌面应用、可执行jar)或是Web应用(在Web容器中运行的基于Servlet的应用)

以javaagent为例,是在执行main方法前对已经加载到JVM的类进行修改,从而实现对目标类的代理,这里的修改是在字节码层面的,当然我们可以基于ASM工具库来实现,但是门槛太高。

基于Instrumentation可以与编写java代码一样,实现修改字节码来

ClassPool:保存CtClass的池子,通过classPool.get(类全路径名)来获取CtClass CtClass:编译时类信息,它是一个class文件在代码中的抽象表现形式 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如何实现代对目标类的代理。是不是觉得java应用程序都能被修改,那不是太不安全了?所以,你觉得呢...

原文链接;https://mp.weixin.qq.com/s/4sLA3WVJ3sRup0yU4q0Tdw

相关推荐

七条简单命令让您玩转Git
七条简单命令让您玩转Git

凭借着出色的协作能力、快速部署效果与代码构建辅助作用,Git已经得到越来越多企业用户的青睐。除了用于开发商业及消费级应用之外,众多科学及政府机构也开始尝试使用这...

2023-10-07 12:14 guanshanw

基本完整的关于Git分支branch的操作
基本完整的关于Git分支branch的操作

Git使用背景项目中要用到dev或者其他分支开发完代码,需要将该分支合并到master的需求操作步骤下面以dev名称为lex为分支名为例来操作一遍客户端操作:...

2023-10-07 12:14 guanshanw

Git 进阶(合并与变基)
Git 进阶(合并与变基)

在Git中整合来自不同分支的修改主要有两种方法:合并(merge)以及变基(rebase)合并(merge)merge流程图merge的原理是找到这两个分...

2023-10-07 12:13 guanshanw

Git学习笔记 003 Git进阶功能 part5 合并(第一部分)

合并(merge)是很常用的操作。尤其是一个庞大的很多人参与开发的企业级应用。一般会设定一个主分支,和多个副分支。在副分支开发完成后,合并到主分支中。始终保持主分支是一个完整的,稳定的最新状态的分支。...

非标题党,三张图帮你理解git merge和git rebase的区别
非标题党,三张图帮你理解git merge和git rebase的区别

初始场景:基于正常的开发分支修改几个小bug,然后在合并到开发分支上。gitmergegitcheckoutfeaturegitmergeho...

2023-10-07 12:13 guanshanw

git 初次使用(01)
git 初次使用(01)

先从github上克隆代码下来:使用vscode克隆代码如下图,填写上github仓库地址:vscode有时候克隆代码速度比较慢,可以用命令行方式克隆gitc...

2023-10-07 12:12 guanshanw

Git 远程操作

4.Git远程操作命令说明gitremote远程版本库操作gitfetch从远程获取版本库gitpull下载远程代码并合并gitpush上传远程代码并合并4.1远程版本库操作gitre...

Git常用命令-总结
Git常用命令-总结

创建git用户$gitconfig--globaluser.name"YourName"$gitconfig--globaluser.em...

2023-10-07 12:12 guanshanw

git中删除从别人clone下来项目的git信息,并修改为自己的分支

如果你从别人的Git存储库中克隆了一个项目,并想要删除与该存储库相关的Git信息,并将其修改为你自己的分支,则可以执行以下步骤:使用gitclone命令克隆存储库:gitclone<u...

git系列-回滚和放弃本地修改

回滚历史提交就是reset的功能。这种情况是已经提交远程仓库,需要回滚到之前的提交。gitreset--hardcommitId//注:强制提交后,当前版本后面的提交版本将会删掉!gi...

GIT使用小技巧大全
GIT使用小技巧大全

在大型软件工程的开发过程中,版本控制是无法绕过去的;目前来说,最火的版本控制软件就是GIT了。早两年SVN比较火,不过被大神linus喷了几次后,就日落西山了,...

2023-10-07 12:11 guanshanw

git相关命令-上
git相关命令-上

这些命令都是看了文档后,个人觉得比较有用的一些,展示给大家。回到远程仓库的状态抛弃本地所有的修改,回到远程仓库的状态。gitfetch--all&...

2023-10-07 12:10 guanshanw

Git命令行接口:掌握Git的必备技能
Git命令行接口:掌握Git的必备技能

Git是一款强大的分布式版本控制工具,它支持命令行界面操作。熟练掌握Git命令行接口,是开发者使用Git的必备技能之一。在这篇文章中,我们将介绍Git命令行接口...

2023-10-07 12:10 guanshanw

Git命令详解
Git命令详解

相信各位小伙伴们应该都对git有一些了解,毕竟作为代码管理的神器,就算不是IT行业的小伙伴肯定也或多或少的听说过一些。今天就来和小伙伴们分享一下自己总结的常用命...

2023-10-07 12:10 guanshanw

工作7年收集到的git命令
工作7年收集到的git命令

概念git中的术语解释:仓库也叫版本库(repository)stage:暂存区,add后会存到暂存区,commit后提交到版本库git安装linux...

2023-10-07 12:10 guanshanw

取消回复欢迎 发表评论: