百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术资源 > 正文

浅薄研究intellij的javaagent

lipiwang 2024-11-22 17:21 4 浏览 0 评论

这两天学习了一下jvm的字节码,学习过程中,正好想起来java程序员们赖以生存的IDE,大名鼎鼎的Intellij,国内的小伙伴们应该对这个也不陌生,尤其是需要用它的商业版本的时候,刚开始学习的同学们会用各种方法找破解版[奸笑],而其中有一种方法就是在启动的时候添加-javaagent参数加载一个jar包。于是,就翻出来看看这个神奇的东西是如何实现的。。。

一、盲猜,他可能的实现方式

从-javaagent来加载jar包就可以绕过注册码认证,第一想到的就是对Intellij字节码的增强(修改),来绕过验证。比如代码可能是下面这样:

private Boolean verify(String verificationCode) {
  if(verificationCode check通过) {
    return true;
  }
  return false;
}

然后-javaagent加载的jar包通过对这个verify方法进行增强,修改其中的class文件,达到绕过验证的目的。这是我最初的想法,也是最直接的想法。随着这个思路,开始扒这个agent的源代码。

二、寻找源码

这个jar包的名字叫ja-netfilter.jar,于是乎,一个最大的同性交友网站(github.com)就派上用场了。

于是乎打开,搜索,果然找到了源码,但是国内的网络对于github不太友好(贼慢),于是乎又来到了另一个同性交友网站(gitee),果然已经有人从github上转载了源码,于是乎pull下来。这下终于能在本地开始研究其中的逻辑了[偷笑]。


三、从入口阅读源码,理解其中的逻辑

终于进入主题了,这里列出ja-netfilter的部分源码:

<archive>
                                <manifest>
                                    <addClasspath>true</addClasspath>
                                </manifest>
                                <manifestEntries>
                                    <Built-By>neo</Built-By>
                                    <Premain-Class>com.janetfilter.core.Launcher</Premain-Class>
                                    <Agent-Class>com.janetfilter.core.Launcher</Agent-Class>
                                    <Main-Class>com.janetfilter.core.Launcher</Main-Class>
                                    <Can-Redefine-Classes>true</Can-Redefine-Classes>
                                    <Can-Retransform-Classes>true</Can-Retransform-Classes>
                                    <Can-Set-Native-Method-Prefix>true</Can-Set-Native-Method-Prefix>
                                </manifestEntries>
                            </archive>

项目是用我们熟悉的maven来管理依赖和打包发布的,因为javaagent是个jar包,maven打包时需要用到针对于jar包打包的插件,这段配置能看出来,这个jar包可以自己启动(有Main-Class配置),可以用-javaagent启动(有Premain-Class),也可以通过Attach-API动态修改class(有Agent-Class),三个class配置都指向同一个类com.janetfilter.core.Launcher,于是这个就是我们要找的程序入口了。

既然我们的用法是-javaagent,于是选择premain方法作为入口来研究,如下为premain的主要片段:

private static void premain(String args, Instrumentation inst, boolean attachMode) {
        if (loaded) {
            DebugInfo.warn("You have multiple `ja-netfilter` as javaagent.");
            return;
        }

        printUsage();

        URI jarURI;
        try {
            loaded = true;
            jarURI = WhereIsUtils.getJarURI();
        } catch (Throwable e) {
            DebugInfo.error("Can not locate `ja-netfilter` jar file.", e);
            return;
        }

        File agentFile = new File(jarURI.getPath());
        try {
            inst.appendToBootstrapClassLoaderSearch(new JarFile(agentFile));
        } catch (Throwable e) {
            DebugInfo.error("Can not access `ja-netfilter` jar file.", e);
            return;
        }

        Initializer.init(new Environment(inst, agentFile, args, attachMode)); // for some custom UrlLoaders
    }

我们找到inst这个对象,使用字节码替换的关键就是在Instrumentation对象里对ClassFileTransformer对象进行注册,这样就可以在类加载时期对我们需要的class字节码进行增强。

inst.appendToBootstrapClassLoaderSearch(new JarFile(agentFile));

这里是使用了appendToBootstrapClassLoaderSearch这个方法,查了一下,这个方法:

jarfile – The JAR file to be searched when the bootstrap class loader unsuccessfully searches for a class.

当bootstrap的类加载器没有能够加载某个类的时候会在这个jar文件中寻找类加载器。

于是,焦点就来到了agentFile这个文件,这个是个什么文件?

URI jarURI;
        try {
            loaded = true;
            jarURI = WhereIsUtils.getJarURI();
        } catch (Throwable e) {
            DebugInfo.error("Can not locate `ja-netfilter` jar file.", e);
            return;
        }

        File agentFile = new File(jarURI.getPath());

通过源码可以分析出,这个agentFile就是ja-netfilter工程打包成的jar文件。OK似乎这里并没有涉及到在Instrumentation对象注册的动作。继续往下看,下面还有一步:

Initializer.init(new Environment(inst, agentFile, args, attachMode)); // for some custom UrlLoaders

这里有个init方法,并且参数传入了inst对象,ummm。。。打开看看:关键代码如下

public class Initializer {
    public static void init(Environment environment) {
        DebugInfo.useFile(environment.getLogsDir());
        DebugInfo.info(environment.toString());

        Dispatcher dispatcher = new Dispatcher(environment);
        new PluginManager(dispatcher, environment).loadPlugins();

        Instrumentation inst = environment.getInstrumentation();
        inst.addTransformer(dispatcher, true);
        inst.setNativeMethodPrefix(dispatcher, environment.getNativePrefix());

终于看到Instrumentation对象里注册ClassFileTransformer的代码了。

inst.addTransformer(dispatcher, true);

那对于字节码增强的细节就来到了Dispatcher这个类。

Dispatcher的关键代码如下:

public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classFileBuffer) throws IllegalClassFormatException {
        if (null == className) {
            return classFileBuffer;
        }

        List<MyTransformer> transformers = transformerMap.get(className);
        List<MyTransformer> globalTransformers = null == transformers ? this.manageTransformers : this.globalTransformers;

        int order = 0;

        try {
            for (MyTransformer transformer : globalTransformers) {
                transformer.before(loader, classBeingRedefined, protectionDomain, className, classFileBuffer);
            }

            for (MyTransformer transformer : globalTransformers) {
                classFileBuffer = transformer.preTransform(loader, classBeingRedefined, protectionDomain, className, classFileBuffer, order++);
            }

            if (null != transformers) {
                for (MyTransformer transformer : transformers) {
                    classFileBuffer = transformer.transform(loader, classBeingRedefined, protectionDomain, className, classFileBuffer, order++);
                }
            }

            for (MyTransformer transformer : globalTransformers) {
                classFileBuffer = transformer.postTransform(loader, classBeingRedefined, protectionDomain, className, classFileBuffer, order++);
            }

            for (MyTransformer transformer : globalTransformers) {
                transformer.after(loader, classBeingRedefined, protectionDomain, className, classFileBuffer);
            }
        } catch (Throwable e) {
            DebugInfo.error("Transform class failed: " + className, e);
        }

        return classFileBuffer;
    }

原来关键在这里,MyTransformer是一个自定义接口,里面的方法签名主要是加强了对于字节码入参classFileBuffer的修改流程控制。MyTransformer的实现类是通过加载与ja-netfilter.jar包的同级目录plugins/下的jar来获取的。plugins目录下有啥呢?

于是我打开了这个目录:

dns.jar,hideme.jar,power.jar,url.jar

好家伙,原来核心是在这4个plugin的jar包?

于是解压反编译发现这几个都是标准的对ja-netfilter框架的扩展plugin,果不其然,在同性交友网站上分别都找到了这几个包的源代码。分别来看看这几个plugin的readme:

dns:

A plugin for theja-netfilter, it can block dns resolution.

hideme:

A plugin for theja-netfilter, it can prevent detection against javaagent.

power:

A plugin for theja-netfilter, it is a dragon slayer for asymmetric encryption.

url:

A plugin for theja-netfilter, it can block http requests.

dns, url光看解释大概也能猜到是干嘛的,这个hideme用来隐藏自己,让加了-javaagent的jvm不知道运行了agent?(好神奇。。。),不过最神奇的是这个power,它是一个非对称加密的屠龙者?(牛逼吹得好大)。不过这4个插件就能破解大名鼎鼎的intellij的话,核心一定就是在power这个插件上了。继续深入看看power的说明:

Config file: power.conf
; for replace arguments
[Args]
EQUAL,y,z->fakeY,fakeZ

; for replace result
[Result]
EQUAL,x,y,z->fakeResult

power的配置文件为power.conf,整个power分成2个部分,一个是对入参的拦截Args,一个是对返回值的拦截Result,如果入参是2个,值为y, z,他将把值替换为fakeY, fakeZ。如果返回值是x,y,z,那么替换为fakeResult。

这么神奇的么?如何做到的?来扒扒源码。

先来看看Args部分的源码:(打开ArgsTransformer类)

@Override
    public String getHookClassName() {
        return "java/math/BigInteger";
    }

好吧,原来是对BigInteger类的字节码进行了增强(修改)

再看看增强的细节:

@Override
    public byte[] transform(String className, byte[] classBytes, int order) throws Exception {
        ClassReader reader = new ClassReader(classBytes);
        ClassNode node = new ClassNode(ASM5);
        reader.accept(node, 0);

        for (MethodNode mn : node.methods) {
            if ("oddModPow".equals(mn.name) && "(Ljava/math/BigInteger;Ljava/math/BigInteger;)Ljava/math/BigInteger;".equals(mn.desc)) {
                InsnList list = new InsnList();
                list.add(new VarInsnNode(ALOAD, 0));
                list.add(new VarInsnNode(ALOAD, 1));
                list.add(new VarInsnNode(ALOAD, 2));
                list.add(new MethodInsnNode(INVOKESTATIC, "com/janetfilter/plugins/power/ArgsFilter", "testFilter", "(Ljava/math/BigInteger;Ljava/math/BigInteger;Ljava/math/BigInteger;)[Ljava/math/BigInteger;", false));
                list.add(new VarInsnNode(ASTORE, 3));
                list.add(new InsnNode(ACONST_NULL));
                list.add(new VarInsnNode(ALOAD, 3));

                LabelNode label0 = new LabelNode();
                list.add(new JumpInsnNode(IF_ACMPEQ, label0));
                list.add(new VarInsnNode(ALOAD, 3));
                list.add(new InsnNode(ICONST_0));
                list.add(new InsnNode(AALOAD));
                list.add(new VarInsnNode(ASTORE, 1));
                list.add(new VarInsnNode(ALOAD, 3));
                list.add(new InsnNode(ICONST_1));
                list.add(new InsnNode(AALOAD));
                list.add(new VarInsnNode(ASTORE, 2));
                list.add(label0);

                mn.instructions.insert(list);
            }
        }

        ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
        node.accept(writer);

        return writer.toByteArray();
    }

看到这里,对于ASM比较熟悉的人就比较清晰了,这是对BigInteger的oddModPow方法进行了修改。然后一系列的骚操作就是对寄存器和栈的流程控制。

那一些列的栈和寄存器的操作用人话来解释就是:

BigInteger[] tuple = ArgsFilter.testFilter(this, arg0, arg1);
if (tuple != null) {
    arg0 = tuple[0];
    arg1 = tuple[1];
}

通过调用ArgsFilter.testFilter方法得到的返回值,来替换原来的入参。在testFilter的内部实现里其实就是读取power.conf配置文件,然后解析之后的替换值。说白了就是当程序执行BigInteger.oddModPow方法的时候进行拦截,如果参数值和配置文件相同,则参数被替换为配置文件里的fake值。

有了对入参的拦截,再来看看对返回值的拦截:(打开ResultTransformer类)

@Override
    public String getHookClassName() {
        return "java/math/BigInteger";
    }

好吧,也是对BigInteger类的拦截。

然后是字节码替换关键代码:

@Override
    public byte[] transform(String className, byte[] classBytes, int order) throws Exception {
        ClassReader reader = new ClassReader(classBytes);
        ClassNode node = new ClassNode(ASM5);
        reader.accept(node, 0);

        for (MethodNode mn : node.methods) {
            if ("oddModPow".equals(mn.name) && "(Ljava/math/BigInteger;Ljava/math/BigInteger;)Ljava/math/BigInteger;".equals(mn.desc)) {
                InsnList list = new InsnList();
                list.add(new VarInsnNode(ALOAD, 0));
                list.add(new VarInsnNode(ALOAD, 1));
                list.add(new VarInsnNode(ALOAD, 2));
                list.add(new MethodInsnNode(INVOKESTATIC, "com/janetfilter/plugins/power/ResultFilter", "testFilter", "(Ljava/math/BigInteger;Ljava/math/BigInteger;Ljava/math/BigInteger;)Ljava/math/BigInteger;", false));
                list.add(new VarInsnNode(ASTORE, 3));
                list.add(new InsnNode(ACONST_NULL));
                list.add(new VarInsnNode(ALOAD, 3));

                LabelNode label0 = new LabelNode();
                list.add(new JumpInsnNode(IF_ACMPEQ, label0));
                list.add(new VarInsnNode(ALOAD, 3));
                list.add(new InsnNode(ARETURN));
                list.add(label0);

                mn.instructions.insert(list);
            }
        }

        ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
        node.accept(writer);

        return writer.toByteArray();
    }

说人话就是:

BigInteger result = ResultFilter.testFilter(this, arg0, arg1);
if (result != null) {
    return result;
}

意思和参数替换差不多,返回值如果匹配配置文件,则返回配置文件里的fake值。

OK,到这里,整个破解Intellij的javaagent的逻辑基本梳理完了,主要是针对BigInteger的oddModPow方法进行入参和返回值的拦截和替换来完成的。至于这个power插件为啥牛逼轰轰的在readme文件里说自己是非对称加密的屠龙者,这个得研究一下是否java的非对称加密一定要用到BigInteger的oddModPow方法。。。(感觉是故意搞的很玄幻),至于其他的插件比如dns,url都是对远程访问JetBrain的一些拦截,如果不用这些插件,直接断网应该也可以。。。


思考

为啥写这个javaagent的人知道Intellij是用非对称加密算法来验证签名的?

为啥这个人精确知道BigInteger的oddModPow方法入参需要匹配哪些值,并且需要返回哪些值?(RSA算法必用到BigInteger么?内部开发人员知道需要匹配哪些值?)


能看到这里的人应该知道我在说啥,也一定是一个有钻研精神的开发人员[呲牙],但是这篇文章还是重点在探究字节码技术,而非鼓励大家去破解。。。[呲牙]。所以支持正版是一个开发人员的基本素质

相关推荐

ubuntu单机安装open-falcon极度详细操作

备注:以下操作均由本人实际操作并得到验证,喜欢的同学可尝试操作安装。步骤一1.1环境准备(使用系统:ubuntu18.04)1.1.1安装redisubuntu下安装(参考借鉴:https://...

Linux搭建promtail、loki、grafana轻量日志监控系统

一:简介日志监控告警系统,较为主流的是ELK(Elasticsearch、Logstash和Kibana核心套件构成),虽然优点是功能丰富,允许复杂的操作。但是,这些方案往往规模复杂,资源占用高,...

一文搞懂,WAF阻止恶意攻击的8种方法

WAF(Web应用程序防火墙)是应用程序和互联网流量之间的第一道防线,它监视和过滤Internet流量以阻止不良流量和恶意请求,WAF是确保Web服务的可用性和完整性的重要安全解决方案。它...

14配置appvolume(ios14.6配置文件)

使用AppVolumes应用程序功能,您可以管理应用程序的整个生命周期,包括打包、更新和停用应用程序。您还可以自定义应用程序分配,以向最终用户提供应用程序的特定版本14.1安装appvolume...

目前流行的缺陷管理工具(缺陷管理方式存在的优缺点)

摘自:https://blog.csdn.net/jasonteststudy/article/details/7090127?utm_medium=distribute.pc_relevant.no...

开源数字货币交易所开发学习笔记(2)——SpringCloud

前言码云(Gitee)上开源数字货币交易所源码CoinExchange的整体架构用了SpringCloud,对于经验丰富的Java程序员来说,可能很简单,但是对于我这种入门级程序员,还是有学习的必要的...

开发JAX-RPC Web Services for WebSphere(下)

在开发JAX-RPCWebServicesforWebSphere(上)一文中,小编为大家介绍了如何创建一个Web服务项目、如何创建一个服务类和Web服务,以及部署项目等内容。接下来小编将为大...

CXF学习笔记1(cxf client)

webservice是发布服务的简单并实用的一种技术了,个人学习了CXF这个框架,也比较简单,发布了一些笔记,希望对笔友收藏并有些作用哦1.什么是webServicewebService让一个程序可...

分布式RPC最全详解(图文全面总结)

分布式通信RPC是非常重要的分布式系统组件,大厂经常考察的Dubbo等RPC框架,下面我就全面来详解分布式通信RPC@mikechen本篇已收于mikechen原创超30万字《阿里架构师进阶专题合集》...

Oracle WebLogic远程命令执行0day漏洞(CVE-2019-2725补丁绕过)预警

概述近日,奇安信天眼与安服团队通过数据监控发现,野外出现OracleWebLogic远程命令执行漏洞最新利用代码,此攻击利用绕过了厂商今年4月底所发布的最新安全补丁(CVE-2019-2725)。由...

Spring IoC Container 原理解析(spring中ioc三种实现原理)

IoC、DI基础概念关于IoC和DI大家都不陌生,我们直接上martinfowler的原文,里面已经有DI的例子和spring的使用示例《InversionofControlContainer...

Arthas线上服务器问题排查(arthas部署)

1Arthas(阿尔萨斯)能为你做什么?这个类从哪个jar包加载的?为什么会报各种类相关的Exception?我改的代码为什么没有执行到?难道是我没commit?分支搞错了?遇到问题无法在...

工具篇之IDEA功能插件HTTP_CLENT(idea2021插件)

工具描述:Java开发人员通用的开发者工具IDEA集成了HTTPClient功能,之后可以无需单独安装使用PostMan用来模拟http请求。创建方式:1)简易模式Tools->HTTPCl...

RPC、Web Service等几种远程监控通信方式对比

几种远程监控通信方式的介绍一.RPCRPC使用C/S方式,采用http协议,发送请求到服务器,等待服务器返回结果。这个请求包括一个参数集和一个文本集,通常形成“classname.meth...

《github精选系列》——SpringBoot 全家桶

1简单总结1SpringBoot全家桶简介2项目简介3子项目列表4环境5运行6后续计划7问题反馈gitee地址:https://gitee.com/yidao620/springbo...

取消回复欢迎 发表评论: