浅薄研究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...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- maven镜像 (69)
- undefined reference to (60)
- zip格式 (63)
- oracle over (62)
- date_format函数用法 (67)
- 在线代理服务器 (60)
- shell 字符串比较 (74)
- x509证书 (61)
- localhost (65)
- java.awt.headless (66)
- syn_sent (64)
- settings.xml (59)
- 弹出窗口 (56)
- applicationcontextaware (72)
- my.cnf (73)
- httpsession (62)
- pkcs7 (62)
- session cookie (63)
- java 生成uuid (58)
- could not initialize class (58)
- beanpropertyrowmapper (58)
- word空格下划线不显示 (73)
- jar文件 (60)
- jsp内置对象 (58)
- makefile编写规则 (58)