一文深度学习java内存马

就是要打骨折http://mp.weixin.qq.com/s?__biz=MzkwNjY1Mzc0Nw==&mid=2247486065&idx=2&sn=b30ade8200e842743339d428f414475e&chksm=c0e4732df793fa3bf39a6eab17cc0ed0fca5f0e4c979ce64bd112762def9ee7cf0112a7e76af&scene=21#wechat_redirect

《Java代码审计》http://mp.weixin.qq.com/s?__biz=MzkwNjY1Mzc0Nw==&mid=2247484219&idx=1&sn=73564e316a4c9794019f15dd6b3ba9f6&chksm=c0e47a67f793f371e9f6a4fbc06e7929cb1480b7320fae34c32563307df3a28aca49d1a4addd&scene=21#wechat_redirect

《Web安全》http://mp.weixin.qq.com/s?__biz=MzkwNjY1Mzc0Nw==&mid=2247484238&idx=1&sn=ca66551c31e37b8d726f151265fc9211&chksm=c0e47a12f793f3049fefde6e9ebe9ec4e2c7626b8594511bd314783719c216bd9929962a71e6&scene=21#wechat_redirect

1. 前言

java内存马注入一般分为两种

  • 动态注册添加新的listener/filter/servlet/controller等等
  • agent注入修改已有class,插入恶意代码


在理解内存马注入前,有几个概念需要掌握的。

  • 类加载
  • 双亲委派问题以及context
  • 类反射

2. 基础

2.1. class对象

java中的对象可以分为两种对象:Class对象和实例对象

  1. 信息属性:从对象的作用看,Class对象保存每个类型运行时的类型信息,如类
    名、属性、方法、父类信息等等。在JVM中,一个类只对应一个Class对象
  2. 普适性:Class对象是java.lang.Class类的对象,和其他对象一样,我们可以获取
    并操作它的引用
  3. 运行时唯一性:每当JVM加载一个类就产生对应的Class对象,保存在堆区,类
    型和它的Class对象时一一对应的关系。一旦类被加载了到了内存中,那么不论通过哪种
    方式获得该类的Class对象,它们返回的都是指向同一个java堆地址上的Class对象引用。
    JVM不会创建两个相同类型的Class对象

Class类没有公共的构造方法,Class对象是在类加载的时候由Java虚拟机以及通过
调用类加载器中的 defineClass 方法自动构造的,因此不能显式地声明一个Class对象。

2.2.类加载

一个类被加载到内存并供我们使用需要经历如下三个阶段:

  • 加载,这是由类加载器(ClassLoader)执行 的。通过一个类的全限定名来获
    取其定义的二进制字节流(Class字节码),将这个字节流所代表的静态存储结
    构转化为方法去的运行时数据接口,根据字节码在java堆中生成一个代表这个类
    的java.lang.Class对象
  • 链接。在链接阶段将验证Class文件中的字节流包含的信息是否符合当前虚拟机
    的要求,为静态域分配存储空间并设置类变量的初始值(默认的零值),并且
    如果必需的话,将常量池中的符号引用转化为直接引用。
  • 初始化。到了此阶段,才真正开始执行类中定义的java程序代码。用于执行该
    类的静态初始器和静态初始块,如果该类有父类的话,则优先对其父类进行
    初始化

所有的类都是在对其第一次使用时,动态加载到JVM中的(懒加载
触发类加载的方式

  • Class.forName("类的全限定名")
  • new 类构造方法

除了上述方式,还可以通过网络加载字节码方式来调用外部类

  • oadclass:判断是否已加载,使用双亲委派模型,请求父加载器,都为空,使用
    findclass
  • findclass:根据名称或位置加载.class字节码,然后使用defineClass
  • defineclass:解析定义.class字节流,返回class对象
  • loadClass 的作用是从已加载的类缓存、父加载器等位置寻找类(这里实际上是
    双亲委派机制),在前面没有找到的情况下,执行 findClass
  • findClass 的作用是根据基础URL指定的方式来加载类的字节码,就像上一节中
    说到的,可能会在本地文件系统、jar包或远程http服务器上读取字节码,然后交
    给 defineClass
  • defineClass 的作用是处理前面传入的字节码,将其处理成真正的Java类

所以可见,真正核心的部分其实是 defineClass ,他决定了如何将一段字节流转变成
一个Java类,Java默认的 ClassLoader#defineClass 是一个native方法,逻辑在JVM的C语言
代码中

classloader推荐

jxxload_help.PathVFSJavaLoader#loadClassFromBytes
org.python.core.BytecodeLoader1#loadClassFromBytes
sun.org.mozilla.javascript.internal.DefiningClassLoader#defineCl
ass
java.security.SecureClassLoader#defineClass(java.lang.String,
byte[], int, int, java.security.CodeSource)
org.mozilla.classfile.DefiningClassLoader#defineClass
org.mozilla.javascript.DefiningClassLoader
com.sun.org.apache.bcel.internal.util.ClassLoader
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl

类装载器是用来把类(class)装载进 JVM 的。JVM 规范定义了两种类型的类装载器:
启动类装载器(bootstrap)和用户自定义装载器(user-defined class loader)。 JVM在运行时会
产生3个类加载器组成的初始化加载器层次结构 ,如下图所示:

//1、获取系统类的加载器
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
System.out.println(classLoader);
//2. 获取系统类加载器的父类加载器(扩展类加载器,可以获取).
classLoader = classLoader.getParent();
System.out.println(classLoader);
//3. 获取扩展类加载器的父类加载器(引导类加载器,不可获取).
classLoader = classLoader.getParent();
System.out.println(classLoader);

2.3. 类反射

其实就是获取Class对象,然后调用该对象进行一系列操作

  • Class: 是一个类; 一个描述类的类
  • 封装了描述方法的 Method
  • 描述字段的 Filed
  • 描述构造器的 Constructor 等属性

如何得到 Class 对象

  1. Person.class
  2. person.getClass()
  3. Class.forName("com.atguigu.javase.Person")

关于 Method

如何获取 Method:

  1. getDeclaredMethods: 得到 Method 的数组
  2. getDeclaredMethod(String methondName, Class ...
    parameterTypes) 可以拿到反射类中的公共方法、私有方法、保
    护方法、默认访问,但不获得父类方法
  3. getMethod(String methondName, Class ... parameterTypes)可以
    拿到反射类及其父类中的所有公共方法, 但无法获取私有方法

如何调用 Method

  1. 如果方法时 private 修饰的, 需要先调用 Method 的
    setAccessible(true), 使其变为可访问
  2. method.invoke(obj, Object ... args)

关于 Field

如何获取 Field: getField(String fieldName)

如何获取 Field 的值

  1. setAccessible(true)
  2. field.get(Object obj)

如何设置 Field 的值

  1. field.set(Obejct obj, Object val)

如果属性用 final 修饰的,需要获取Field的mofilers属性,将FINAL约
束去掉,则可修改

ips:
数组反射


声明数组对象
Array.newInstance(int.class, 3);
数组class

Class intArray = Class.forName("[I");
Class byteArray = Class.forName("[B");
Class stringArrayClass = Class.forName("[Ljava.lang.String;");

// 上面的字符串可以通过打印来观察
System.out.println(LinkOption[].class);
// 输出
class [Ljava.nio.file.LinkOption;

// 取巧
Class theClass = getClass(theClassName);
Class stringArrayClass = Array.newInstance(theClass,
0).getClass();

获取数组值

Array.get(obj, index);

2.4. 双亲委派

双亲委派的作用:

  1. 为了保证相同的class文件,在使用的时候,是相同的对象,jvm设计的时候,采
    用了双亲委派的方式来加载类,防止相同类的重复加载。一般来说应用启动的
    时候会有统一的AppClassLoader来加载项目里的class
  2. 保证启动类加载器优先加载,防止JDK的class被篡改

打破双亲委派:ClassLoader#loadClass方法就是以双亲委派逻辑编写的,只要继承
ClassLoader重写loadClass去掉双亲委派的代码就可以打破双亲委派。也可以通过
defineclass绕过loadclass。

tomcat类加载器需要破坏双亲委派机制

tomcat是个web容器,要解决以下问题

  1. 一个web容器可能要部署两个或者多个应用程序,不同的应用程序,可能会依赖
    同一个第三方类库的不同版本,因此要保证每一个应用程序的类库都是独立、相互隔离
  2. 部署在同一个web容器中的相同类库的相同版本可以共享,否则,会有重复的类
    库被加载进JVM
  3. web容器也有自己的类库,不能和应用程序的类库混淆,需要相互隔离
  4. web容器支持jsp文件修改后不用重启,jsp文件也是要编译成.class文件的,支持
    HotSwap功能
  • 架构图

tomcat自己定义的类加载器

  1. CommonClassLoader:tomcat最基本的类加载器,加载路径中的class可以被tomcat和
    各个webapp访问
  2. CatalinaClassLoader:tomcat私有的类加载器,webapp不能访问其加载路径下的
    class,即对webapp不可见
  3. SharedClassLoader:各个webapp共享的类加载器,对tomcat不可见
  4. WebappClassLoader:webapp私有的类加载器,只对当前webapp可见
  5. JspClassLoader
  • 每一个web应用程序对应一个WebappClassLoader,每一个jsp文件对应一个
    JspClassLoader,所以这两个类加载器有多个实例

Tomcat 中有 4 类容器组件,从上至下依次是:

  1. Engine,实现类为 org.apache.catalina.core.StandardEngine
  2. Host,实现类为 org.apache.catalina.core.StandardHost
  3. Context,实现类为 org.apache.catalina.core.StandardContext
  4. Wrapper,实现类为 org.apache.catalina.core.StandardWrapper
     

“从上至下” 的意思是,它们之间是存在父子关系的

  • Engine:最顶层容器组件,其下可以包含多个 Host
  • Host:一个 Host 代表一个虚拟主机,其下可以包含多个 Context
  • Context:一个 Context 代表一个 Web 应用,其下可以包含多个 Wrapper
  • Wrapper:一个 Wrapper 代表一个 Servlet
    srping

3. 动态注册方式注入

关于各个协议和组件的内存马的构造思路其实都大同小异

  1. 分析涉及处理请求的对象,阅读它的源码看看是否能获取请求内容,同时能否
    控制响应内容
  2. 然后分析该对象是如何被注册到内存当中的,最后我们只要模拟下这个过程即
  3. 一般的流程就是request->context->addListener/addFilter

我怎么拿到当前请求的request对象,request对象里一般会有context,context里面一
般都有一些注册组件的方法或者变量存储

需要注意问题

  1. 如果不是web相关类加载器,可能出现类加载报类找不到的问题,这是因为双亲
    委派隔离
  2. 如果用当前上下文的类加载,则相同类名只能加载一次

要解决上面两个问题,就是基于当前上下文的类加载去new一个新的类加载器。
如下用Mlet就是一种方法

new javax.management.loading.MLet(new java.net.URL[0],
conreq.getClass().getClassLoader())

参考代码

java.lang.reflect.Method defineClassMethod =
ClassLoader.class.getDeclaredMethod("defineClass", new Class[]
{byte[].class, int.class, int.class});
defineClassMethod.setAccessible(true);
Class cc = (Class) defineClassMethod.invoke(new
javax.management.loading.MLet(new java.net.URL[0],
conreq.getClass().getClassLoader()), new Object[]{classBytes,
new Integer(0), new Integer(classBytes.length)});

在研究中间件之前,最好去了解下该中间件的发展历史,有哪些版本,因为不同版
本的代码肯定会有不同层度的改动,你的内存马是否适配每个版本是个问题

上一篇:并发


下一篇:报错解决:opene3d draw_geometries: incompatible function arguments.-1. 报错信息