服务粉丝

我们一直在努力
当前位置:首页 > 财经 >

JAVA安全|字节码篇:字节码操作库—javassist

日期: 来源:沃克学安全收集编辑:walker1995

0x00  前言

    原本打算把这部分内容放到基础篇章的,但随着我的学习,发现这一部分内容其实并不基础,于是又单独开了字节码篇章。这一篇章的主要内容为Javassist、ASM这两个字节码第三方库的使用,字节码文件的结构以及字节码相关技术,在内存马注入中应该也会有应用吧~

    本文为JAVA安全系列文章第二十二篇,学习javassist的基本使用

0x01  认识javassist

一、介绍

Java 字节码以二进制的形式存储在 .class 文件中,每一个 .class 文件包含一个 Java 类或接口。而Javaassist 就是一个用来处理 Java 字节码的类库。

下面是来自javassist官网的介绍,本人只是做了点小小的翻译:

Javassist是一个用于在 Java 中编辑字节码的类库;它使 Java 程序能够在运行时定义一个新类,并在 JVM 加载类文件时对其进行修改。

与其他字节码编辑器不同的是,Javassist 提供了两种级别的 API:源代码级别和字节码级别。

源代码级API使得用户不需要了解Java 字节码规范就可以编辑类文件。整个 API 仅使用 Java 语言的语法进行设计。用户甚至可以在源文本中插入指定的字节码,Javassist即时编译它。

字节码级 API 允许用户直接编辑类文件。

对于安全的学习,我们常使用的是源码级API,下面就来了解下源码级API中我们常使用的类和方法。

二、源码级API常见类和方法
1.Ctclass和ClassPool的关系

CtClass(compile-time class,编译时类)是一个class文件在代码中的抽象表现形式 。可以通过一个类的全限定名来获取一个CtClass对象,用来表示这个类文件。一个 CtClass对象可以处理一个 class 文件。

ClassPool是CtClass对象的容器,它根据需要读取类文件来构造CtClass对象,并且保存CtClass 对象方便以后使用。要修改类的定义,用户首先必须从ClassPool对象获取表示该类的CtClass对象的引用。ClassPool中的get()用于此目的。通常我们会使用ClassPool的静态方法getDefault()获取到一个ClassPool对象。

从实现的角度来讲,ClassPool是一个CtClass对象的哈希表,它使用类名作为键。ClassPool中的get()在哈希表中查找与指定键相关联的CtClass对象。如果没有找到这样的CtClass对象,get()将读取一个类文件来构造一个新的CtClass对象,该对象记录在哈希表中,然后作为get()的结果值返回。

需要注意的是 ClassPool 会在内存中维护所有被它创建过的 CtClass,当 CtClass 数量过多时,会占用大量的内存,API中给出的解决方案是 有意识的调用CtClassdetach()方法以释放内存

2.ClassPool常用方法

getDefault : 返回默认的ClassPool ,是单例模式的,一般通过该方法创建我们的ClassPool对象;

appendClassPath, insertClassPath : 将一个ClassPath加到类搜索路径的末尾位置 或 插入到起始位置。通常通过该方法写入额外的类搜索路径,以解决多个类加载器环境中找不到类的尴尬;

makeClass:创建一个新的类

toClass : 将修改后的CtClass加载至当前线程的上下文类加载器中,CtClass的toClass方法是通过调用本方法实现。需要注意的是一旦调用该方法,则无法继续修改已经被加载的class

get , getCtClass : 根据类路径名获取该类的CtClass对象,用于后续的编辑。

3.Ctclass常用方法

CtClass理解为编译时类,它的API与Class的API相似而又有不同:

addFieldaddMethodaddConstructor:添加字段、方法、构造器

getField(s)getMethod(s)getConstructor(s):获取非private的编译时字段、方法、构造器对象

getDeclaredField(s)getDeclaredMethod(s)getDeclaredConstructor(s):获取声明的编译时字段、方法、构造器,包括私有的

setModifiers:设置(字段,方法,构造器等的)修饰符

等等,另外的一些常用API:

freeze : 冻结一个类,使其不可修改;

isFrozen : 判断一个类是否已被冻结;

prune : 删除类不必要的属性,以减少内存占用。调用该方法后,许多方法无法将无法正常使用,慎用;

defrost : 解冻一个类,使其可以被修改。如果事先知道一个类会被defrost, 则禁止调用 prune 方法;

detach : 将该class从ClassPool中删除;

writeFile : 根据CtClass生成 .class 文件;

toClass : 通过类加载器加载该CtClass。

4.CtMethod、CtNewMethod常用方法

对于一个类,相比于字段,我们更关心的是它的方法,相应的javassist中有CtMethod、CtNewMethod来修改/创建方法

(1)CtMethod常用方法

insertBefore : 在方法的起始位置插入代码;

insterAfter : 在方法的所有 return 语句前插入代码以确保语句能够被执行,除非遇到exception;

insertAt : 在指定的位置插入代码;

setBody : 将方法的内容设置为要写入的代码,当方法被 abstract修饰时,该修饰符被移除;

make : 创建一个新的方法。

(2)CtNewMethod常用方法

getter , setter:创建public的getter,setter方法

make:创建一个新的方法

上面列举的类都在javassist包下,其他类和方法不一一列举,关于方法的使用和传入参数参考官方API文档:

https://www.javassist.org/html/index.html

0x02  javassist的基本使用

上面列举了一些常见API,下面就实际敲代码来使用下。

不过,在实际敲代码前我们需要在maven中导入javassist包,通过mvn仓库,发现用得最多的好像是3.27.0-GA版本:

于是我环境中导入的是这个包,我的JDK版本是java1.8.0_111。

另外,敲代码前先附上一张从别处Copy过来的javassist使用流程图:

一、使用javassist创建一个class文件

下面的代码可以从无到有产生一个.class文件:

package javassist_;
/*
* @author   walker1995
* @version v1.0
*/

import javassist.*;

public class CreatePersonClass {
   public static void createPerson() throws Exception {
       //1.获取一个ClassPool对象
       ClassPool pool = ClassPool.getDefault();

       //2.使用ClassPool对象创建一个新类
       CtClass cc = pool.makeClass("javassist_.Person");

       //3.增加一个字段private String name,初始值为"walker"
       CtField field = new CtField(pool.get("java.lang.String"), "name", cc);
       field.setModifiers(Modifier.PRIVATE);
       cc.addField(field,CtField.Initializer.constant("walker"));

       //4.生成getter、setter方法
       cc.addMethod(CtNewMethod.getter("getName",field));
       cc.addMethod(CtNewMethod.setter("setName",field));

       //5.添加无参构造器
       CtConstructor cons = new CtConstructor(new CtClass[]{}, cc);
       cons.setBody("{name = \"walker\";}");
       cc.addConstructor(cons);

       //6.添加有参构造器
       cons = new CtConstructor(new CtClass[]{pool.get("java.lang.String")},cc);
       // $0 = this $1,$2,$3... 代表方法参数
       cons.setBody("{$0.name = $1;}");
       cc.addConstructor(cons);

       //7.创建一个名为personInfo的方法,无参数,无返回值,输出name值
       CtMethod method = new CtMethod(CtClass.voidType, "personInfo", new CtClass[]{}, cc);
       method.setModifiers(Modifier.PUBLIC);
       method.setBody("{System.out.println(name);}");
       cc.addMethod(method);

       //8.将创建的类编译为.class文件
       cc.writeFile("F:\\JAVA\\javassist_asm\\src\\main\\java");
  }

   public static void main(String[] args) {
       try {
           createPerson();
      } catch (Exception e) {
           e.printStackTrace();
      }
  }
}

setBody()中我们使用了一些符号,更多符号请看官方教程:

http://www.javassist.org/tutorial/tutorial2.html

运行上面的代码会在指定位置产生.class文件,内容如下:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package javassist_;

public class Person {
   private String name = "walker";

   public String getName() {
       return this.name;
  }

   public void setName(String var1) {
       this.name = var1;
  }

   public Person() {
       this.name = "walker";
  }

   public Person(String var1) {
       this.name = var1;
  }

   public void personInfo() {
       System.out.println(this.name);
  }
}
二、调用生成的类对象
1.读取.class文件的方式反射调用

我们希望创建的class文件能够在别的类中被调用,代码如下:

package javassist_;
/*
* @author   walker1995
* @version v1.0
*/

import javassist.ClassPool;
import javassist.CtClass;
import java.lang.reflect.Method;

public class InvokePerson {
   public static void main(String[] args) throws Exception {
       ClassPool pool = ClassPool.getDefault();
       //通过.class文件获取CtClass对象,需要添加类路径
       pool.appendClassPath("F:\\JAVA\\javassist_asm\\src\\main\\java\\");

       //1.这里通过get获取Person的CtClass对象
       CtClass cc = pool.get("javassist_.Person");

       //2.使用toClass加载Person的CtClass对象并实例化
       Object person = cc.toClass().newInstance();

       //3.通过反射获取setter方法对象并调用
       Method setName = person.getClass().getMethod("setName", String.class);
       setName.invoke(person, "沃克");

       //4.通过反射获取personInfo方法对象并调用
       Method info = person.getClass().getMethod("personInfo");
       info.invoke(person);
  }
}
2.通过接口的方式调用

有时在我们的工程中其实并没有类对象,使用反射的方式比较麻烦,并且开销也很大。

那么如果类对象可以抽象为一些方法的合集,就可以考虑为该类生成一个接口类。这样在newInstance()的时候我们就可以强转为接口,可以将反射的那一套省略掉了。

比如上面的Person类,我们新建一个PersonI接口类:

package javassist_;
/*
* @author   walker1995
* @version v1.0
*/

public interface PersonI {

   void setName(String name);

   String getName();

   void personInfo();

}

然后通过接口方式调用:

package javassist_;
/*
* @author   walker1995
* @version v1.0
*/

import javassist.ClassPool;
import javassist.CtClass;

public class InvokePersonI {
   public static void main(String[] args) throws Exception {
       ClassPool pool = ClassPool.getDefault();
       pool.appendClassPath("F:\\JAVA\\javassist_asm\\src\\main\\java\\");

       CtClass ctClass = pool.get("javassist_.Person");

       //获取接口
       CtClass ctInterface = pool.get("javassist_.PersonI");

       //使代码生成的类实现接口PersonI
       ctClass.setInterfaces(new CtClass[]{ctInterface});

       //通过接口直接调用,强转
       PersonI person = (PersonI)ctClass.toClass().newInstance();
       System.out.println("当前name为:" + person.getName());
       person.setName("Walker 沃克");
       person.personInfo();
  }
}

可以清晰看到,使用这种方式比反射更轻松。

三、修改现有的类对象

开发中遇到较多的使用场景应该是修改已有的类。比如常见的日志切面,权限切面。

目前有一个如下类:

package javassist_;
/*
* @author   walker1995
* @version v1.0
*/

public class PersonService {
   public void personFly(){
       System.out.println("oh my god,I can fly!");
  }
}

我们希望在原本的personFly前后执行其他代码,且还想增加一个joinFriend的方法,修改代码如下:

package javassist_;
/*
* @author   walker1995
* @version v1.0
*/

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.Modifier;

import java.lang.reflect.Method;

public class ModifyPersonService {
   public static void main(String[] args) throws Exception {
       ClassPool classPool = ClassPool.getDefault();
       //此处通过.java文件类文件获取CtClass对象,并不需要添加类路径
//       classPool.appendClassPath("F:\\JAVA\\javassist_asm\\src\\main\\java\\");

       CtClass ctClass = classPool.get("javassist_.PersonService");

       //修改personFly方法
       CtMethod personFly = ctClass.getDeclaredMethod("personFly");
       personFly.insertBefore("System.out.println(\"起飞之前准备降落伞\");");
       personFly.insertAfter("System.out.println(\"成功落地...\");");

       //新增joinFriend方法
       CtMethod joinFriend = new CtMethod(CtClass.voidType, "joinFriend", new CtClass[]{}, ctClass);
       joinFriend.setModifiers(Modifier.PUBLIC);
       joinFriend.setBody("{System.out.println(\"I want to be your friend\");}");
       ctClass.addMethod(joinFriend);

       //实例化PersonService并反射调用personFly和joinFriend方法
       Object personService = ctClass.toClass().newInstance();
       Method personFlyMethod = personService.getClass().getMethod("personFly", new Class[]{});
       personFlyMethod.invoke(personService);
       System.out.println();

       Method joinFriendMethod = personService.getClass().getMethod("joinFriend", new Class[]{});
       joinFriendMethod.invoke(personService);
  }
}

此处需要注意的是:insertBefore() 和 setBody()中的语句,如果是单行语句可以直接用双引号,如果是多行语句需要用{}括起来。javassist只接受单个语句或用大括号括起来的语句块。

0x03  总结

本文主要了解了javassist源码级API的一些常用类和方法,并通过实际敲代码来学习了其基本使用。建议各位读者在理解了javassist的使用逻辑后都动手敲敲代码。

关于javassist后续学习的建议的是:

API使用详情参官方提供的在线API文档:https://www.javassist.org/html/,

关于javassist的设计逻辑和更多使用,参考官方教程:https://www.javassist.org/tutorial/tutorial.html

对于这类东西的学习,还是多看看官方文档。一方面,国内文章都来源于官方文档,只不过一手文章的作者加入了自己的一些理解,其他也都是相互抄,本文也是在看了一部分官方文档和国内文章来写的,所以本文不知道是第几手文章了(狗头)

另一方面,如果想搞安全研究一类的工作,阅读英文文档会是一个必不可少的内容,我英文也很菜,多看多学吧。


参考:

https://www.javassist.org/html/

https://www.javassist.org/tutorial/tutorial.html

https://juejin.cn/post/7078681608206680094

Java安全系列文集

第0篇:JAVA安全|即将开启:java安全系列文章

第1篇:JAVA安全|基础篇:认识java反序列化

第2篇:JAVA安全|基础篇:实战java原生反序列化

第3篇:JAVA安全|基础篇:反射机制之快速入门

第4篇:JAVA安全|基础篇:反射机制之Class类

第5篇:JAVA安全|基础篇:反射机制之类加载

第6篇:JAVA安全|基础篇:反射机制之常见ReflectionAPI使用

第7篇:JAVA安全|Gadget篇:URLDNS链

第8篇:JAVA安全|Gadget篇:TransformedMap CC1链

第9篇:JAVA安全|基础篇:反射的应用—动态代理

第10篇:JAVA安全|Gadget篇:LazyMap CC1链

第11篇:JAVA安全|Gadget篇:无JDK版本限制的CC6链

第12篇:JAVA安全|基础篇:动态字节码加载(一)

第13篇:JAVA安全|基础篇:动态字节码加载(二)

第14篇:JAVA安全|Gadget篇:CC3链及其通杀改造

第15篇:JAVA安全|Gadget篇:CC依赖下为shiro反序列化利用而生的CCK1 CC11链

第16篇:JAVA安全|Gadget篇:CC5 CC7链

第17篇:JAVA安全|Gadget篇:CC2  CC4链—Commons-Collections4.0下的特有链

第18篇:JAVA安全|Gadget篇:CC8 CC9链

第19篇:JAVA安全|Gadget篇:Ysoserial CB1链

第20篇:JAVA安全|Gadget篇:shiro无依赖利用与常见shiro利用工具对比浅析

第21篇:JAVA安全|Gadget篇:JDK原生链—JDK7u21


如果喜欢小编的文章,记得多多转发,点赞+关注支持一下哦~,您的点赞和支持是我最大的动力~

相关阅读

  • 对学校系统开展的一次完整渗透

  • 声明:该公众号大部分文章来自作者日常学习笔记,也有部分文章是经过作者授权和其他公众号白名单转载,未经授权,严禁转载,如需转载,联系开白。请勿利用文章内的相关技术从事非法测试
  • Spring环境下有关内存马的回显总结

  • 0x01 前言在学习各种内存马的过程中,关注到了观星实验室的一篇文章,较为全面的列举了在Spring环境下的有关内存马的实现技巧这里我们深入进行学习学习一下0x02 正文前景回顾网
  • 投资高手们都是如何炼成的?

  • 投资进化论之一1/7钟表与眼睛成功的专业投资者素有“学院派”和“草根派”之分,前者名校毕业,从大公募基金或券商研究所入职,研而优则投,一般都是理论功底和专业知识浓厚;后者,股
  • Spring 新特性,正式“抛弃”Feign了

  • 近期,Spring 6 的第一个 GA 版本发布了,其中带来了一个新的特性——HTTP Interface。这个新特性,可以让开发者将 HTTP 服务,定义成一个包含特定注解标记的方法的 Java 接口,然后
  • 详解 Java 泛型,写得太好了!

  • -正文泛型—— 一种可以接收数据类型的数据类型,本文将通俗讲解Java泛型的优点、方法及相关细节。一、泛型的引入我们都知道,继承是面向对象的三大特性之一,比如在我们向集合中
  • 一文深入理解 Java 的四种引用类型

  • 这是 JsonChao 的第 318 期分享前言Java Reference 类型是与虚拟机垃圾回收机制密切相关的知识点,同时也是面试重要考点之一。一般认为 Java 有四种 Reference(强引用 & 软引

热门文章

  • “复活”半年后 京东拍拍二手杀入公益事业

  • 京东拍拍二手“复活”半年后,杀入公益事业,试图让企业捐的赠品、家庭闲置品变成实实在在的“爱心”。 把“闲置品”变爱心 6月12日,“益心一益·守护梦想每一步”2018年四

最新文章

  • JAVA安全|字节码篇:字节码操作库—javassist

  • 0x00 前言 原本打算把这部分内容放到基础篇章的,但随着我的学习,发现这一部分内容其实并不基础,于是又单独开了字节码篇章。这一篇章的主要内容为Javassist、ASM这两个字节
  • 对学校系统开展的一次完整渗透

  • 声明:该公众号大部分文章来自作者日常学习笔记,也有部分文章是经过作者授权和其他公众号白名单转载,未经授权,严禁转载,如需转载,联系开白。请勿利用文章内的相关技术从事非法测试
  • 实战 | 记一次挖矿应急响应案例

  • 声明:该公众号大部分文章来自作者日常学习笔记,也有部分文章是经过作者授权和其他公众号白名单转载,未经授权,严禁转载,如需转载,联系开白。请勿利用文章内的相关技术从事非法测试
  • 实战 | 记一次从登录框到内网横向

  • 声明:该公众号大部分文章来自作者日常学习笔记,也有部分文章是经过作者授权和其他公众号白名单转载,未经授权,严禁转载,如需转载,联系开白。请勿利用文章内的相关技术从事非法测试
  • GitHub监控和信息收集工具

  • 声明:该公众号大部分文章来自作者日常学习笔记,也有部分文章是经过作者授权和其他公众号白名单转载,未经授权,严禁转载,如需转载,联系开白。请勿利用文章内的相关技术从事非法测试
  • Go语言内网资产探测工具

  • 声明:该公众号大部分文章来自作者日常学习笔记,也有部分文章是经过作者授权和其他公众号白名单转载,未经授权,严禁转载,如需转载,联系开白。请勿利用文章内的相关技术从事非法测试