TCP远程命令执行

news2024/9/21 19:52:39

目录

一.   命令集

二.   命令执行模块实现

三.   服务端模块实现

四.   服务端调用模块实现

五.   客户端模块实现

六.   效果展示


此篇教大家如何利用TCP进行远程命令执行。

一.   命令集

将值得信任的命令放进一个txt文件中,执行命令时,就去这个文件里面找,有就执行命令,没有就不执行。

ls -a -l
pwd
tree
whoami
who
uname -a
cat
touch

注意我们是用空格隔开的。

二.   命令执行模块实现

依然封装成类,将上述命令集写进类中。

class Command
{
public:
    Command(const string& cond_path)
    :_cond_path(cond_path)
    {}

    ~Command()
    {}
private:
    set<string> _safe_cmd;
    string _cond_path;
};

有着两个成员,首先是set类型,即命令集。其次是上述文件集的路径。

 初始化路径之后,我们再来将命令写进集合中。

const string sep=" ";

string PrefixCommand(const string& cmd)
{
    if(cmd.empty())
    {
        return string();
    }
    auto pos=cmd.find(sep);
    if(pos==string::npos)
    {
        return cmd;
    }
    else
    {
        return cmd.substr(0,pos);
    }
}

void LoadConf(const string& conf)
{
    ifstream in(conf);
    if(!in.is_open())
    {
        LOG(FATAL,"open %s error\n",conf);
        return;
    }
    string line;
    while(getline(in,line))
    {
        LOG(DEBUG,"load command [%s] success\n",line.c_str());
        _safe_cmd.insert(PrefixCommand(line));
    }

    in.close();
}

LoadConf函数即初始化集合函数,利用文件读取流打开文件,一行一行读取命令集,并且只取第一个空格前面的字符插入进集合中。下面会讲为什么。

这个操作可以在构造函数的时候实现,所以可以将这个函数加入到构造函数中。

Command(const string& cond_path)
:_cond_path(cond_path)
{
    LoadConf(_cond_path);
}

 此处肯定需要检测输入的命令是否在命令集中。我们只检查输入命令第一个空格前的字符是否相同即可。例如"ls -a -l"只需检测ls即可。为什么呢?

例如touch指令,后面肯定要加创建的文件名字,这样就需要连着文件名一起检查在不在命令集中,这显然是不合理的,文件名有无数个,怎么能列举完呢。所以只需检测第一个空格前面相不相同即可。

来看如何检查:

const string sep=" ";

string PrefixCommand(const string& cmd)
{
    if(cmd.empty())
    {
        return string();
    }
    auto pos=cmd.find(sep);
    if(pos==string::npos)
    {
        return cmd;
    }
    else
    {
        return cmd.substr(0,pos);
    }
}

bool SafeCheck(const string& cmd)
{
    string prefix=PrefixCommand(cmd);
    auto iter=_safe_cmd.find(prefix);
    if(iter==_safe_cmd.end())
    {
        return false;
    }
    return true;
}

string Excute(const string& cmd)
{
    string result;
    if(SafeCheck(cmd))
    {
        FILE* fp=popen(cmd.c_str(),"r");
        if(fp==nullptr)
        {
            return "failed";
        }

        char buffer[1024];
        while(fgets(buffer,sizeof(buffer),fp)!=NULL)
        {
            result+=buffer;
        }
        pclose(fp);
    }
    else
    {
        result="坏人\n";
    }

    return result;
}

Excute函数就是具体如何处理输入的正确指令,重点就是运用popen函数

FILE *popen(const char *command, const char *type);

popen() 函数通过创建一个管道,调用 fork 产生一个子进程,执行一个 shell 以运行命令来开启一个进程。 

参数说明:

command: 是一个指向以 NULL 结束的 shell 命令字符串的指针。命令将被传到 bin/sh 并使用 -c 标志,shell 将执行这个命令,比如sh -c ls

type: 只能是读或者写中的一种,得到的返回值(标准 I/O 流)也具有和 type 相应的只读或只写类型。如果 type 是 “r” 则文件指针连接到 command 的标准输出;如果 type 是 “w” 则文件指针连接到 command 的标准输入。

返回值:

如果调用 fork() 或 pipe() 失败,或者不能分配内存将返回NULL,否则返回一个读或者打开文件的指针。

 最后合起来就是命令指令模块:

const string sep=" ";

class Command
{
private:
    void LoadConf(const string& conf)
    {
        ifstream in(conf);
        if(!in.is_open())
        {
            LOG(FATAL,"open %s error\n",conf);
            return;
        }
        string line;
        while(getline(in,line))
        {
            LOG(DEBUG,"load command [%s] success\n",line.c_str());
            _safe_cmd.insert(PrefixCommand(line));
        }

        in.close();
    }
public:
    Command(const string& cond_path)
    :_cond_path(cond_path)
    {
        LoadConf(_cond_path);
    }

    string PrefixCommand(const string& cmd)
    {
        if(cmd.empty())
        {
            return string();
        }
        auto pos=cmd.find(sep);
        if(pos==string::npos)
        {
            return cmd;
        }
        else
        {
            return cmd.substr(0,pos);
        }
    }

    bool SafeCheck(const string& cmd)
    {
        string prefix=PrefixCommand(cmd);
        auto iter=_safe_cmd.find(prefix);
        if(iter==_safe_cmd.end())
        {
            return false;
        }
        return true;
    }

    string Excute(const string& cmd)
    {
        string result;
        if(SafeCheck(cmd))
        {
            FILE* fp=popen(cmd.c_str(),"r");
            if(fp==nullptr)
            {
                return "failed";
            }

            char buffer[1024];
            while(fgets(buffer,sizeof(buffer),fp)!=NULL)
            {
                result+=buffer;
            }
            pclose(fp);
        }
        else
        {
            result="坏人\n";
        }

        return result;
    }

    ~Command()
    {}
private:
    set<string> _safe_cmd;
    string _cond_path;
};

其中LOG函数是封装的日志功能:

#pragma once

//日志
#include<iostream>
#include<fstream>
#include<cstdio>
#include<string>
#include<ctime>
#include<unistd.h>
#include<sys/types.h>
#include<stdarg.h>
#include<pthread.h>
#include"LockGuard.hpp"

using namespace std;

bool gIsSave=false;
const string logname="log.txt";


void SaveFile(const string& filename,const string& message)
{
    ofstream out(filename,ios::app);
    if(!out.is_open())
    {
        return;
    }
    out<<message;
    out.close();
}

//1.日志是有等级的
enum Level
{
    DEBUG=0,
    INFO,
    WARNING,
    ERROR,
    FATAL
};


string LevelToString(int level)
{
    switch(level)
    {
        case DEBUG: return "Debug";break;
        case INFO: return "Info";break;
        case WARNING: return "Warning";break;
        case ERROR: return "Error";break;
        case FATAL: return "Fatal";break;
        default: return "Unknown";break;
    }
}

string GetTimeString()
{
    time_t curr_time=time(nullptr);
    struct tm* format_time=localtime(&curr_time);
    if(format_time==nullptr) return "None";
    char time_buffer[64];
    snprintf(time_buffer,sizeof(time_buffer),"%d-%d-%d %d:%d:%d",
    format_time->tm_year+1900,format_time->tm_mon+1,format_time->tm_mday,
    format_time->tm_hour,format_time->tm_min,format_time->tm_sec);

    return time_buffer;
}

pthread_mutex_t lock=PTHREAD_MUTEX_INITIALIZER;

//2.日志是由格式的
// 日志等级 时间 代码所在的文件名/行数 日志的内容...
void LogMessage(string filename,int line,bool issave,int level,const char* format,...)
{
    string levelstr=LevelToString(level);
    string timestr=GetTimeString();
    pid_t selfid=getpid();

    //可变参数部分处理
    char buffer[1024];
    va_list arg;
    va_start(arg,format);
    vsnprintf(buffer,sizeof(buffer),format,arg);
    va_end(arg);

    LockGuard lockguard(&lock);

    string message;
    message="["+timestr+"]"+"["+levelstr+"]"+"[pid: "
            +to_string(selfid)+"]"+"["+filename+"]"
            +"["+to_string(line)+"]"+buffer+"\n";
    if(!issave)
    {
        cout<<message;
    }
    else
    {
        SaveFile(logname,message);
    }
}

void Test(int num,...)
{
    va_list arg;
    va_start(arg,num);

    while(true)
    {
        int data=va_arg(arg,int);
        cout<<"data: "<<data<<endl;
        num--;
    }

    va_end(arg);//arg==NULL
}

//C99新特性 __VA_ARGS__
#define LOG(level,format,...) do {LogMessage(__FILE__,__LINE__,gIsSave,level,format,##__VA_ARGS__);} while(0)
#define EnableFile() do {gIsSave=true;} while(0)
#define EnableScreen() do {gIsSave=false;} while(0)

三.   服务端模块实现

具体的实现跟前面TCP服务端模块实现一样(点此查看)。此处我们采用多线程来实现,就不过多赘述。

#include<iostream>
#include<string>
#include<unistd.h>
#include<cstring>
#include<pthread.h>
#include<functional>
#include<sys/wait.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>

#include"Log.hpp"
#include"InetAddr.hpp"

const static int defaultsockfd=-1;
const static int gbacklog=10;

class TcpServer;

struct ThreadData
{
public:
    ThreadData(int fd,InetAddr addr,TcpServer* s)
    :sockfd(fd)
    ,clientaddr(addr)
    ,self(s)
    {}
public:
    int sockfd;
    InetAddr clientaddr;
    TcpServer* self;
};

enum
{
    SOCKET_ERROR = 1,
    BIND_ERROR,
    LISTEN_ERROR,
    USAGE_ERROR
};

using func_t=function<string(const string&)>;

class TcpServer
{
public:
    TcpServer(int port,func_t func)
    :_port(port)
    ,_listensock(defaultsockfd)
    ,_isrunning(false)
    ,_func(func)
    {}

    void InitServer()
    {
        //1.创建流式套接字
        _listensock=::socket(AF_INET,SOCK_STREAM,0);
        if(_listensock<0)
        {
            LOG(FATAL,"socket error\n");
            exit(SOCKET_ERROR);
        }
        LOG(DEBUG,"socket create success, sockfd is: %d\n",_listensock);

        //2.bind
        struct sockaddr_in local;
        memset(&local,0,sizeof(local));
        local.sin_family=AF_INET;
        local.sin_port=htons(_port);
        local.sin_addr.s_addr=INADDR_ANY;
        int n=::bind(_listensock,(struct sockaddr*)&local,sizeof(local));
        if(n<0)
        {
            LOG(FATAL,"bind error\n");
            exit(BIND_ERROR);
        }
        LOG(DEBUG,"bind success,sockfd is: %d\n",_listensock);

        //3.tcp是面向连接的,所以通信之前,必须先建立连接,服务器是被连接的
        //tcpserver启动,未来首先要一直等待客户的连接到来
        n=::listen(_listensock,gbacklog);
        if(n<0)
        {
            LOG(FATAL,"listen error\n");
            exit(LISTEN_ERROR);
        }
        LOG(DEBUG,"listen success,sockfd is: %d\n",_listensock);

    }

    void Service(int sockfd,InetAddr client)
    {
        LOG(DEBUG,"get a new link,info %s:%d,fd: %d\n",client.Ip().c_str(),client.Port(),sockfd);
        string clientaddr="["+client.Ip()+":"+to_string(client.Port())+"]# ";
        while(true)
        {
            char inbuffer[1024];
            ssize_t n=recv(sockfd,inbuffer,sizeof(inbuffer)-1,0);
            if(n>0)
            {
                inbuffer[n]=0;
                cout<<clientaddr<<inbuffer<<endl;

                string result=_func(inbuffer);

                send(sockfd,result.c_str(),result.size(),0);
            }
            else if(n==0)
            {
                //client退出&&关闭连接了
                LOG(INFO,"%s quit\n",clientaddr.c_str());
                break;
            }
            else
            {
                LOG(ERROR,"read error\n",clientaddr.c_str());
                break;
            }
        }

        ::close(sockfd);//不关闭会发生文件描述符泄露
    }

    static void* HandlerSock(void* args)//IO和业务解耦
    {
        pthread_detach(pthread_self());
        ThreadData* td=static_cast<ThreadData*>(args);
        td->self->Service(td->sockfd,td->clientaddr);
        delete td;
        return nullptr;
    }

    void Loop()
    {
        _isrunning=true;
        //4.不能直接接收数据,应该先获取连接
        while(true)
        {
            struct sockaddr_in peer;
            socklen_t len=sizeof(peer);
            int sockfd=::accept(_listensock,(struct sockaddr*)&peer,&len);
            if(sockfd<0)
            {
                LOG(WARNING,"accept error\n");
                continue;
            }

            //采用多线程
            //此处不能像多进程一样关闭文件描述符,因为多线程文件描述符表是共享的
            pthread_t t;
            ThreadData* td=new ThreadData(sockfd,InetAddr(peer),this);
            pthread_create(&t,nullptr,HandlerSock,td);//将线程分离
        }
        _isrunning=false;
    }

    ~TcpServer()
    {
        if(_listensock>defaultsockfd)
        {
            ::close(_listensock);
        }
    }
private:
    uint16_t _port;
    int _listensock;
    bool _isrunning;

    func_t _func;
};

其中InetAddr.hpp文件是我们封装的。

#include<iostream>
#include<string>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>

using namespace std;

class InetAddr
{
private:
    void GetAddress(string* ip,uint16_t* port)
    {
        *port=ntohs(_addr.sin_port);//网络字节序转为主机字节序
        *ip=inet_ntoa(_addr.sin_addr);//将网络字节序IP转为点分式十进制IP
    }
public:
    InetAddr(const struct sockaddr_in &addr)
    :_addr(addr)
    {
        GetAddress(&_ip,&_port);
    }

    string Ip()
    {
        return _ip;
    }

    uint16_t Port()
    {
        return _port;
    }

    ~InetAddr()
    {}
private:
    struct sockaddr_in _addr;
    string _ip;
    uint16_t _port;
};

四.   服务端调用模块实现

只需创建出服务端类的对象,依次调用InitServer和Loop函数即可。并创建出执行命令类对象。

#include"CommandExcute.hpp"
#include"TcpServer.hpp"
#include<memory>

using namespace std;

void Usage(string proc)
{
    cout<<"Usage:\n\t"<<proc<<" local_port\n"<<endl;
}

// ./tcpserver port
int main(int argc,char *argv[])
{
    if(argc!=2)
    {
        Usage(argv[0]);
        return 1;
    }
    EnableScreen();
    uint16_t port=stoi(argv[1]);
    Command cmd("./safe.txt");

    func_t cmdExec=bind(&Command::Excute,&cmd,placeholders::_1);
    unique_ptr<TcpServer> tsvr=make_unique<TcpServer>(port,cmdExec);
    tsvr->InitServer();
    tsvr->Loop();


    return 0;
}

五.   客户端模块实现

客户端还是没有变化。不懂得可看此处(点此查看)。

#include<iostream>
#include<string>
#include<unistd.h>
#include<cstring>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>

#include"Log.hpp"

using namespace std;

void Usage(string proc)
{
    cout<<"Usage:\n\t"<<proc<<" serverip serverport\n"<<endl;
}


// ./tcpclient serverip serverport
int main(int argc,char *argv[])
{
    if(argc!=3)
    {
        Usage(argv[0]);
        exit(1);
    }
    string serverip=argv[1];
    uint16_t serverport=stoi(argv[2]);

    int sockfd=::socket(AF_INET,SOCK_STREAM,0);
    if(sockfd<0)
    {
        cerr<<"socket error\n"<<endl;
        exit(2);
    }

    //与udpclient一样,不需显式bind

    //构建目标主机的socket信息
    struct sockaddr_in server;
    memset(&server,0,sizeof(server));
    server.sin_family=AF_INET;
    server.sin_port=htons(serverport);
    server.sin_addr.s_addr=inet_addr(serverip.c_str());

    int n=connect(sockfd,(struct sockaddr*)&server,sizeof(server));
    if(n<0)
    {
        cerr<<"connect error\n"<<endl;
        exit(3);
    }

    while(true)
    {
        cout<<"Please Enter#";
        string outstring;
        getline(cin,outstring);

        ssize_t s=send(sockfd,outstring.c_str(),outstring.size(),0);//write
        if(s>0)
        {
            char inbuffer[1024];
            ssize_t m=recv(sockfd,inbuffer,sizeof(inbuffer)-1,0);
            if(m>0)
            {
                inbuffer[m]=0;
                cout<<inbuffer<<endl;
            }
            else
            {
                break;
            }
        }
        else
        {
            break;
        }
    }
}

六.   效果展示

可以看见只要在命令集中的命令都能执行。


总结:

好了,到这里今天的知识就讲完了,大家有错误一点要在评论指出,我怕我一人搁这瞎bb,没人告诉我错误就寄了。

祝大家越来越好,不用关注我(疯狂暗示)

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

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

相关文章

英语每日一段 195

Promising economic indicators won’t instantly reverse the lingering impact of hard times for millions of families, workplace culture expert Jessica Kriegel said. “Perception and reality are sometimes aligned and sometimes not,” Kriegel told Newsweek. “…

这才是程序猿梦想的终端,赶快动手搞起来

文章目录 目标资源列表安装iTerm2安装oh-my-zsh安装颜色主题查找配置文件将配置内容复制到本地设置iTerm2 安装NERD FONTS下载字体安装设置iTerm2 安装PowerLevel10k修改.zshrc重新加载配置 安装插件下载[语法高亮](#syntaxhighlighting)下载[命令提示](#autosuggestions)配置插…

git的使用和gdb工具

1.git的使用 首先现在gitee上新建一个仓库 然后复制克隆链接到本地仓库 在本地仓库中&#xff0c;我们可以用git status查看仓库状态 我们要提交代码就是要三步 git add 文件名 git commit -m "写提交的日志" git push 将代码上传到远端仓库 然后你就完成一次提…

AIOT人工智能物联网六大场景

AIOT&#xff08;人工智能物联网&#xff09;融合了人工智能技术和物联网技术&#xff0c;实现了设备之间的智能互联和数据交互&#xff0c;在多个场景中都有广泛的应用。以下是一些主要的AIOT场景&#xff1a; 一、智能交通 智能汽车&#xff1a; 自动驾驶是AIOT在智能交通领…

tolower/toupper 函数讲解

目录 1.函数介绍 2.示例如下&#xff1a; 方源一把抓住VS2022&#xff0c;又是顷刻炼化&#xff01;&#xff1f; 1.函数介绍 C语言中提供了两种函数用于字符大小的转换 tolower可以将大写字符转小写字符&#xff0c;toupper可以将小写字符转大写字符 tolower函数与touppe…

设计并用Java实现一个简易的规则引擎

文章目录 前言正文一、代码结构二、核心代码2.1 上下文数据接口 ContextData.java2.2 规则接口 Rule.java2.3 规则引擎接口 RuleEngine.java2.4 规则引擎上下文类 RuleEngineContext.java2.5 规则引擎默认实现类 DefaultRuleEngine.java2.6 执行时出错监听器接口 ExecuteOnErro…

[mysql]SQL语言的规则和规范

规则 是什么呢&#xff0c;规则就是我们最基本&#xff0c;每时每刻都要遵守的比如人行道靠右&#xff0c;不能逆行&#xff0c; 规范 呢就是锦上添花&#xff0c;如果你不这么做&#xff0c;是不那么道德&#xff0c;不那么好的&#xff0c;就像小学生见到老师要问好&#…

【秋招笔试题】浇水

题解&#xff1a;离散化之后差分数组&#xff0c;注意左闭右闭区间的处理&#xff0c;将点和线段都抽象成点 #include <iostream> #include <vector> #include <set> #include <algorithm>using namespace std;const int MAXN 3000000 5; const int…

基于Spring Boot的火车订票管理系统

你好呀&#xff0c;我是计算机学姐码农小野&#xff01;如果有相关需求&#xff0c;可以私信联系我。 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;JAVA语言 Spring Boot框架 工具&#xff1a;IDEA/Eclipse、Navicat、Tomcat 系统展示 首页 管理…

方正畅享新闻采编系统 binary.do SQL注入漏洞分析复现

漏洞简介 binary.do接口的TableName参数对传入的数据没有充足的校验&#xff0c;导致该接口存在SQL注入漏洞&#xff0c;未授权的攻击者可获取数据库敏感信息。资产测绘搜索语句 hunter&#xff1a;web.body"/newsedit/newsedit/" 路由与鉴权分析 通过分析web.xml配置…

ubuntu 安装python3 教程

本篇教程,主要介绍如何在Ubuntu上安装python3教程。 1、查看是否有python 在安装前,首先看看自己系统上,是否存在python环境,可能有些系统,默认就安装过python,如果已经有python了,可以直接跳过安装教程。 2、安装步骤 apt update && apt install -y python3 p…

【知识分享】MQTT实战-使用mosquitto客户端连接emqx服务器

一、简介 MQTT&#xff08;Message Queuing Telemetry Transport&#xff09;是一种轻量级的、基于发布/订阅模式的通信协议&#xff0c;旨在实现物联网设备之间的低带宽、高延迟的通信。MQTT协议设计简洁&#xff0c;使用TCP/IP协议进行通信&#xff0c;适用于各种网络环境&am…

OpenGL——着色器画一个点

一、 绘制 在窗口中间画一个像素点&#xff1a; #include <GL/glew.h> #include <GLFW/glfw3.h> #include <iostream>using namespace std;#define numVAOs 1GLuint renderingProgram; GLuint vao[numVAOs];GLuint createShaderProgram () {const char *v…

基于canal的Redis缓存双写

canal地址&#xff1a;alibaba/canal: 阿里巴巴 MySQL binlog 增量订阅&消费组件 (github.com)https://github.com/alibaba/canal 1. 准备 1.1 MySQL 查看主机二进制日志 show master status 查看binlog是否开启 show variables like log_bin 授权canal连接MySQL账号 …

有限自动机例题

答案&#xff1a;A 解析&#xff1a; 从图中可以看出从1出发&#xff0c;有一个a的闭环&#xff0c;可以多次重复a&#xff0c;因此选项A不正确 选项B&#xff0c;如果有b&#xff0c;必然经过a回去&#xff0c;不可能出现连续的b 选项C&#xff0c;可以从图中看出&#xf…

前端学习-day14

文章目录 01-媒体查询02-媒体查询-书写顺序03-媒体查询04-媒体查询-link引入06-Bootstrap-使用07-Bootstrap-栅格系统08-Bootstrap-按钮样式09-Bootstrap-表格样式10-bootstrap组件11-bootstrap字体图标alloyTeam项目index.htmlindex.less 01-媒体查询 <!DOCTYPE html> …

数学建模算法汇总(全网最全,含matlab案例代码)

数学建模常用的算法分类 全国大学生数学建模竞赛中&#xff0c;常见的算法模型有以下30种&#xff1a; 最小二乘法数值分析方法图论算法线性规划整数规划动态规划贪心算法分支定界法蒙特卡洛方法随机游走算法遗传算法粒子群算法神经网络算法人工智能算法模糊数学时间序列分析马…

一文梳理RAG(检索增强生成)的现状与挑战

一 RAG简介 大模型相较于过去的语言模型具备更加强大的能力&#xff0c;但在实际应用中&#xff0c;例如在准确性、知识更新速度和答案透明度方面&#xff0c;仍存在不少问题&#xff0c;比如典型的幻觉现象。因此&#xff0c;检索增强生成 (Retrieval-Augmented Generation, …

Learn ComputeShader 09 Night version lenses

这次将要制作一个类似夜视仪的效果 第一步就是要降低图像的分辨率&#xff0c; 这只需要将id.xy除上一个数字然后再乘上这个数字 可以根据下图理解&#xff0c;很明显通过这个操作在多个像素显示了相同的颜色&#xff0c;并且很多像素颜色被丢失了&#xff0c;自然就会有降低分…

Open-Sora代码详细解读(1):解读DiT结构

Diffusion Models专栏文章汇总&#xff1a;入门与实战 前言&#xff1a;目前开源的DiT视频生成模型不是很多&#xff0c;Open-Sora是开发者生态最好的一个&#xff0c;涵盖了DiT、时空DiT、3D VAE、Rectified Flow、因果卷积等Diffusion视频生成的经典知识点。本篇博客从Open-S…