snort:预处理器开发HelloWorld

snort:预处理器开发HelloWorld文章目录参考 1 预处理器回顾 2 README PLUGINS3 spp template c 参考 Snort 预处理插件 HelloWorld 程序开发 Snort 预处理器介绍 详细 本专栏所有相关博文使用的 snort 版本均为 2 9 151 预处理器回顾预处理器在 Snort 应用规则前处理接收到的数据预处理器对每一个数据包只执行一次被捕获的数据包首先经过预处理器 然后经过探测引擎根

参考

本专栏所有相关博文使用的snort版本均为 2.9.15.1

0.开胃菜

1. 预处理器回顾

  • 预处理器在Snort应用规则前处理接收到的数据
  • 预处理器对每一个数据包只执行一次
  • 被捕获的数据包首先经过预处理器,然后经过探测引擎根据规则处理。
  • 输入插件和预处理器是同一概念
  • 当Snort接收到数据包的时候,主探测引擎并不能对它们进行处理和应用规则,比如,数据包有可能是分片的,需要重新组装,预处理器就是做这样的工作,使数据能够被探测引擎处理,另外,一些预处理器还可以做一些其它工作,比如探测包中的一些明显错误。
  • 你可以编写自己的预处理器,察看Snort源代码doc目录中的README.PLUGINS文件,你可以获得相关的资料,也可以在templates目录中查看源代码示例。

2. README.PLUGINS

Plugin Info 12/5/99 Martin Roesch Overview: Snort version 1.5 introduces a major new concept, plugins. There are two types of plugin currently available in Snort: detection plugins and preprocessors. Detection plugins check a single aspect of a packet for a value defined within a rule and determine if the packet data meets their acceptance criteria. For example, the tcp flags detection plugin checks the flags section of TCP packets for matches with flag combinations defined in a particular rule. Detection plugins may be called multiple times per packet with different arguments. Preprocessors are only called a single time per packet and may perform highly complex functions like TCP stream reassembly, IP defragmentation, or HTTP request normalization. They can directly manipulate packet data and even call the detection engine directly with their modified data. They can perform less complex tasks like statistics gathering or threshold monitoring as well. Adding New Plugins to Snort as a User: Right now, adding a new plugin to Snort is straightforward but requires you to edit two files by hand. The plugin should consist of two files, "sp_something.c"/"sp_something.h" for detection plugins, and "spp_something.c"/"spp_something.h" for preprocessors. For detection plugins, there are two steps to integrating it with Snort: 1) Edit plugbase.h and insert the line #include "sp_something.h" into the file with the other "#include" statements. Save and close the file. 2) Edit the plugbase.c file and in the InitPlugins() function, add the name of the setup function to the list with the other Setup functions. Save and close the file. 3) Edit the Makefile.am and add the names of the two files to the list of names on the "snort_SOURCES" line. Save and exit the file. Run "automake". Someday, there will be a nice little program that will do all this work for you! Writing New Snort Plugins as a Developer: This process is also pretty straight forward, and the best place to look for information on doing these things is at the files in the "templates" directory. The sp_* files are setup for detection plugins, and the spp_* files refer to the preprocessor files. The main thing to remember once you've got it written is to put the proper includes and function calls into plugbase[.c|.h]. I should probably flesh out this document more, but I think that the best info is in the template files. If you have any questions or comments, don't hesitate to send me an e-mail! 

机翻来自百度

插件信息 1999512日 马丁·罗斯奇 概述: Snort版本1.5引入了一个重要的新概念,即插件。有两种类型 Snort中当前可用插件的数量:检测插件和预处理器。 检测插件检查数据包的一个方面是否有在 一种规则,用于确定包数据是否满足其接受标准。为了 例如,tcp标志检测插件检查tcp数据包的标志部分 用于与特定规则中定义的标志组合匹配。检测 每个包可以使用不同的参数多次调用插件。 预处理器在每个包中只调用一次,并且可以执行很高的性能 复杂功能,如TCP流重组、IP碎片整理或HTTP 请求规范化。它们可以直接操作包数据,甚至调用 检测引擎直接用他们修改过的数据。他们能做的更少 复杂的任务,例如统计数据收集或阈值监视。 以用户身份向Snort添加新插件: 现在,向Snort添加一个新插件很简单,但是需要您 手工编辑两个文件。插件应该由两个文件组成, “sp_something.c”或“sp_something.h”用于检测插件,以及 “spp_something.c”或“spp_something.h”表示预处理器。对于检测插件, 将它与Snort集成有两个步骤: 1) 编辑plugbase.h并插入行 #包括“sp_something.h” 与其他“#include”语句一起放入文件。保存并关闭 文件。 2) 编辑plugbase.c文件,在InitPlugins()函数中,添加 列表中的设置函数和其他设置函数。保存和 关闭文件。 3) 编辑Makefile.am并将两个文件的名称添加到 “snort_SOURCES”行中的名称。保存并退出文件。跑步 “汽车制造”。 总有一天,会有一个很好的小程序来帮你完成所有的工作! 作为开发人员编写新的Snort插件: 这个过程也是非常直接的,而且是最好的地方 有关执行这些操作的信息位于“templates”目录中的文件中。 sp U*文件是为检测插件设置的,spp U*文件是指 预处理器文件。一旦你写好了,最重要的是要记住 是将适当的include和函数调用放入plugbase[.c |.h]。我 也许应该更详细地说明这个文件,但是我认为最好的信息 在模板文件中。如果你有任何问题或意见,不要 不好意思给我发电子邮件! 

3. spp_template.c

/* $Id$ */ /* Snort Preprocessor Plugin Source File Template */ /* spp_template * * Purpose: * * Preprocessors perform some function *once* for *each* packet. This is * different from detection plugins, which are accessed depending on the * standard rules. When adding a plugin to the system, be sure to * add the "Setup" function to the InitPreprocessors() function call in * plugbase.c! * 注意:上面是Snort源文件中spp_template.c (预处理器模板)中的注释, * 这个注释没有及时更新 * 原文中的InitPreprocessors( ) 函数已经被替换成了 RegistPreprocessors( ) * * Arguments: * * This is the list of arguments that the plugin can take at the * "preprocessor" line in the rules file * * Effect: * * What the preprocessor does. Check out some of the default ones * (e.g. spp_frag2) for a good example of this description. * * Comments: * * Any comments? * */ #include <sys/types.h> #include <stdlib.h> #include <ctype.h> #include <rpc/types.h> /* * If you're going to issue any alerts from this preproc you * should include generators.h and event_wrapper.h */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "generators.h" #include "event_wrapper.h" #include "util.h" #include "plugbase.h" #include "parser.h" /* * put in other inculdes as necessary */ /* * your preprocessor header file goes here if necessary, don't forget * to include the header file in plugbase.h too! */ #include "spp_template.h" /* * define any needed data structs for things like configuration */ typedef struct _TemplateData { 
    /* Your struct members here */ } TemplateData; /* * If you need to instantiate the preprocessor's * data structure, do it here */ TemplateData SomeData; /* * function prototypes go here */ static void TemplateInit(u_char *); static void ParseTemplateArgs(char *); static void PreprocFunction(Packet *); static void PreprocCleanExitFunction(int, void *); static void PreprocRestartFunction(int, void *); /* * Function: SetupTemplate() * * Purpose: Registers the preprocessor keyword and initialization * function into the preprocessor list. This is the function that * gets called from InitPreprocessors() in plugbase.c. * * Arguments: None. * * Returns: void function * */ void SetupTemplate() { 
    /* * link the preprocessor keyword to the init function in * the preproc list */ RegisterPreprocessor("keyword", TemplateInit); DebugMessage(DEBUG_PLUGIN,"Preprocessor: Template is setup...\n"); } /* * Function: TemplateInit(u_char *) * * Purpose: Calls the argument parsing function, performs final setup on data * structs, links the preproc function into the function list. * * Arguments: args => ptr to argument string * * Returns: void function * */ static void TemplateInit(u_char *args) { 
    DebugMessage(DEBUG_PLUGIN,"Preprocessor: Template Initialized\n"); /* * parse the argument list from the rules file */ ParseTemplateArgs(args); /* * perform any other initialization functions that are required here */ /* * Set the preprocessor function into the function list */ AddFuncToPreprocList(PreprocFunction); AddFuncToCleanExitList(PreprocCleanExitFunction, NULL); AddFuncToRestartList(PreprocRestartFunction, NULL); } /* * Function: ParseTemplateArgs(char *) * * Purpose: Process the preprocessor arguments from the rules file and * initialize the preprocessor's data struct. This function doesn't * have to exist if it makes sense to parse the args in the init * function. * * Arguments: args => argument list * * Returns: void function * */ static void ParseTemplateArgs(char *args) { 
    /* your parsing function goes here, check out the other spp files for examples */ } /* * Function: PreprocFunction(Packet *) * * Purpose: Perform the preprocessor's intended function. This can be * simple (statistics collection) or complex (IP defragmentation) * as you like. Try not to destroy the performance of the whole * system by trying to do too much.... * * Arguments: p => pointer to the current packet data struct * * Returns: void function * */ static void PreprocFunction(Packet *p) { 
    /* your preproc function goes here.... */ /* * if you need to issue an alert from your preprocessor, check out * event_wrapper.h, there are some useful helper functions there */ } /* * Function: PreprocCleanExitFunction(int, void *) * * Purpose: This function gets called when Snort is exiting, if there's * any cleanup that needs to be performed (e.g. closing files) * it should be done here. * * Arguments: signal => the code of the signal that was issued to Snort * data => any arguments or data structs linked to this * functioin when it was registered, may be * needed to properly exit * * Returns: void function */ static void PreprocCleanExitFunction(int signal, void *data) { 
    /* clean exit code goes here */ } /* * Function: PreprocRestartFunction(int, void *) * * Purpose: This function gets called when Snort is restarting on a SIGHUP, * if there's any initialization or cleanup that needs to happen * it should be done here. * * Arguments: signal => the code of the signal that was issued to Snort * data => any arguments or data structs linked to this * functioin when it was registered, may be * needed to properly exit * * Returns: void function */ static void PreprocRestartFunction(int signal, void *foo) { 
    /* restart code goes here */ } 

4. spp_template.h

/* $Id$ */ /* Snort Preprocessor Plugin Header File Template */ /* This file gets included in plugbase.h when it is integrated into the rest * of the program. */ #ifndef __SPP_TEMPLATE_H__ #define __SPP_TEMPLATE_H__ /* * list of function prototypes to export for this preprocessor */ void SetupTemplate(); #endif /* __SPP_TEMPLATE_H__ */ 

5. 预处理器的Hello World

5.1 复制模板

进入snort-2.9.15.1/

cp templates/spp_template.h src/preprocessors/spp_helloworld.h 
cp templates/spp_template.c src/preprocessors/spp_helloworld.c 

5.2 包含新文件

vim src/plugbase.c 

在64行,插入

#include "preprocessors/spp_helloworld.h" 

在这里插入图片描述
703行插入

SetupHelloWorld(); 

稍后会在spp_helloworld.c编写这个函数并在spp_helloworld.h中声明

在这里插入图片描述
保存退出

5.3 修改spp_helloworld.h

vim src/preprocessors/spp_helloworld.h 

#ifndef改掉
13行声明上文修改的那个函数

void SetupHelloWorld(); 

在这里插入图片描述

保存退出

5.4 修改spp_helloworld.c

我这个写的挺丑的,应该把声明放一块,函数放一块…下次一定改

先总体看一下,此文件应有如下几个函数及其声明,从上到下:

  • 空函数:HelloWorldReloadFunction。snort reload时调用的函数,可不写函数体
  • 声明:HelloWorldInit
  • 函数:SetupHelloWorld。主程序启动,运行plugbase.c时,调用此函数。此函数内调用RegisterPreprocessor进行预处理器注册,以及调用HelloWorldInit进行预处理器的初始化
  • 函数:HelloWorldMain。预处理器的主函数,即每收到一个符合条件的包都会调用此函数
  • 函数:HelloWorldInit。预处理器的初始化函数,此函数内调用AddFuncToPreprocList,将预处理器的主函数(功能函数)HelloWorldMain添加到对应链表中。在新版snort中,需要有一句session_api->enable_preproc_all_ports(sc,PP_HELLOWORLD_RT, 0x01);
    否则预处理器主函数不能正常工作。
vim src/preprocessors/spp_helloworld.c 

58行,修改为

#include "spp_helloworld.h" 

在这里插入图片描述
然后加上

#include "session_api.h" #include "decode.h" 
// 添加函数到预处理器链表时用的 #define PROTO_MASK 0x0001 

5.4.1 空函数 HelloWorldReloadFunction

// 重加载的函数,可不写函数体 void HelloWorldReloadFunction(){ 
   } 

5.4.2 声明 HelloWorldInit

// 声明 static void HelloWorldInit(); 

5.4.3 函数 主配置函数 SetupHelloWorld

修改函数SetupTemplate()SetupHelloWorld()

// 主配置函数 void SetupHelloWorld() { 
    /* * link the preprocessor keyword to the init function in * the preproc list */ // RegisterPreprocessor("keyword", TemplateInit); // DebugMessage(DEBUG_PLUGIN,"Preprocessor: Template is setup...\n"); #ifndef SNORT_RELOAD // 预处理器名称, 用于初始化预处理器的函数 RegisterPreprocessor("HelloWorld", HelloWorldInit); printf("*wuming-register* HelloWorld start\n"); #else // ReloadFunction可以暂时不写 RegisterPreprocessor("HelloWorld", HelloWorldInit, HelloWorldReloadFunction,NULL,NULL, NULL); printf("*wuming-register* HelloWorld reload\n"); #endif printf("*wuming-setup done*\n"); DEBUG_WRAP(DebugMessage(DEBUG_PLUGIN,"Preprocessor: HelloSnort is setup...\n");); } 

5.4.4 函数 HelloWorldMain

static void PreprocFunction(Packet *p) 
// 实际执行的主函数 static void HelloWorldMain(Packet *p){ 
    printf("*wuming-main* Hello World's main function*\n"); } 

5.4.5 函数 HelloWorldInit

修改 TemplateInit()如下

static void HelloWorldInit(struct _SnortConfig *sc,u_char *args) { 
    // DebugMessage(DEBUG_PLUGIN,"Preprocessor: Template Initialized\n"); /* * parse the argument list from the rules file */ // ParseTemplateArgs(args); /* * perform any other initialization functions that are required here */ /* * Set the preprocessor function into the function list */ DEBUG_WRAP(DebugMessage(DEBUG_PLUGIN,"Preprocessor: HelloSnortInit Initialized\n");); // printf("HelloSnortInit ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^is setup"); // 参数分别为:结构体指针 预处理器实际执行的函数 处理器优先级 预定义常数 自定义解码类型标志 AddFuncToPreprocList(sc, HelloWorldMain, 0x01, PP_HELLOWORLD_RT, PROTO_MASK); session_api->enable_preproc_all_ports(sc,PP_HELLOWORLD_RT, 0x01); printf("*wuming-init done*\n"); // 不需要总结输出的话这行可以删掉 // 预处理器名称, //RegisterPreprocStats("HelloWorld", ProfinetPrintStats); // AddFuncToCleanExitList(PreprocCleanExitFunction, NULL); // AddFuncToRestartList(PreprocRestartFunction, NULL); } 

保存退出

5.5 修改preprocids.h

spp_helloworld.c中预定义常数PP_HELLOWORLD_RT需要添加到 src/preprocids.h

vim src/preprocids.h 

增加一行,然后PP_MAX增加1

在这里插入图片描述

5.6 MakeFile.am

vim src/preprocessors/Makefile.am 

把预处理器的两个文件添加到libspp_a_SOURCES

在这里插入图片描述

5.7 snort.conf

vim your_path/snort.conf 

270行附近 step5,添加5.4中定义的预处理器名称HelloWorld
在这里插入图片描述

5.8 automake && make && make install

automake && make && make install 

在这里插入图片描述
没有报错的话就可以启动了,有报错的话就回去DEBUG
一定要注意对应的函数名、常量名是否正确,否则会出现各种奇怪的BUG

5.9 启动snort

我使用-A参数直接打印到控制台

snort -A console -i ens33 -c your_path/snort.conf 

在这里插入图片描述

6. 完整的 spp_helloworld.c

再次说明一下,写的很丑,其实原来的模板中都有注释的,最好还是按对应的位置来写。

/* $Id$ */ /* Snort Preprocessor Plugin Source File Template */ /* spp_template * * Purpose: * * Preprocessors perform some function *once* for *each* packet. This is * different from detection plugins, which are accessed depending on the * standard rules. When adding a plugin to the system, be sure to * add the "Setup" function to the InitPreprocessors() function call in * plugbase.c! * * Arguments: * * This is the list of arguments that the plugin can take at the * "preprocessor" line in the rules file * * Effect: * * What the preprocessor does. Check out some of the default ones * (e.g. spp_frag2) for a good example of this description. * * Comments: * * Any comments? * */ #include <sys/types.h> #include <stdlib.h> #include <ctype.h> #include <rpc/types.h> /* * If you're going to issue any alerts from this preproc you * should include generators.h and event_wrapper.h */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "generators.h" #include "event_wrapper.h" #include "util.h" #include "plugbase.h" #include "parser.h" #include "session_api.h" #include "decode.h" /* * put in other inculdes as necessary */ /* * your preprocessor header file goes here if necessary, don't forget * to include the header file in plugbase.h too! */ #include "spp_helloworld.h" // 添加函数到预处理器链表时用的 #define PROTO_MASK 0x0001 /* * define any needed data structs for things like configuration */ typedef struct _TemplateData { 
    /* Your struct members here */ } TemplateData; /* * If you need to instantiate the preprocessor's * data structure, do it here */ TemplateData SomeData; /* * function prototypes go here */ static void TemplateInit(u_char *); static void ParseTemplateArgs(char *); static void PreprocFunction(Packet *); static void PreprocCleanExitFunction(int, void *); static void PreprocRestartFunction(int, void *); /* * Function: SetupTemplate() * * Purpose: Registers the preprocessor keyword and initialization * function into the preprocessor list. This is the function that * gets called from InitPreprocessors() in plugbase.c. * * Arguments: None. * * Returns: void function * */ // 重加载的函数,可不写 void HelloWorldReloadFunction(){ 
   } // 声明 static void HelloWorldInit(); // 此函数用于调用下面的初始化函数 HelloWorldRTInit() void SetupHelloWorld() { 
    /* * link the preprocessor keyword to the init function in * the preproc list */ // RegisterPreprocessor("keyword", TemplateInit); // DebugMessage(DEBUG_PLUGIN,"Preprocessor: Template is setup...\n"); #ifndef SNORT_RELOAD // 预处理器名称, 初始化预处理器的函数 RegisterPreprocessor("HelloWorld", HelloWorldInit); printf("*wuming-register* HelloWorld start\n"); #else // ReloadFunction可以暂时不写 RegisterPreprocessor("HelloWorld", HelloWorldInit, HelloWorldReloadFunction,NULL,NULL, NULL); printf("*wuming-register* HelloWorld reload\n"); #endif printf("*wuming-setup done*\n"); DEBUG_WRAP(DebugMessage(DEBUG_PLUGIN,"Preprocessor: HelloSnort is setup...\n");); } // 实际执行的主函数 static void HelloWorldMain(Packet *p){ 
    printf("*wuming-main* Hello World's main function*\n"); } /* * Function: TemplateInit(u_char *) * * Purpose: Calls the argument parsing function, performs final setup on data * structs, links the preproc function into the function list. * * Arguments: args => ptr to argument string * * Returns: void function * */ // 初始化预处理器 static void HelloWorldInit(struct _SnortConfig *sc,u_char *args) { 
    // DebugMessage(DEBUG_PLUGIN,"Preprocessor: Template Initialized\n"); /* * parse the argument list from the rules file */ // ParseTemplateArgs(args); /* * perform any other initialization functions that are required here */ /* * Set the preprocessor function into the function list */ DEBUG_WRAP(DebugMessage(DEBUG_PLUGIN,"Preprocessor: HelloSnortInit Initialized\n");); // 参数分别为:结构体指针 预处理器实际执行的函数 处理器优先级 预定义常数 自定义解码类型标志 AddFuncToPreprocList(sc, HelloWorldMain, 0x01, PP_HELLOWORLD_RT, PROTO_MASK); session_api->enable_preproc_all_ports(sc,PP_HELLOWORLD_RT, 0x01); printf("*wuming-init done*\n"); // 不需要总结输出的话这行可以删掉 // 预处理器名称, //RegisterPreprocStats("HelloWorld", ProfinetPrintStats); // session_api->enable_preproc_all_ports(sc, PP_HELLOWORLD_RT, PROTO_BIT_IP); // AddFuncToCleanExitList(PreprocCleanExitFunction, NULL); // AddFuncToRestartList(PreprocRestartFunction, NULL); } /* * Function: ParseTemplateArgs(char *) * * Purpose: Process the preprocessor arguments from the rules file and * initialize the preprocessor's data struct. This function doesn't * have to exist if it makes sense to parse the args in the init * function. * * Arguments: args => argument list * * Returns: void function * */ static void ParseTemplateArgs(char *args) { 
    /* your parsing function goes here, check out the other spp files for examples */ } /* * Function: PreprocFunction(Packet *) * * Purpose: Perform the preprocessor's intended function. This can be * simple (statistics collection) or complex (IP defragmentation) * as you like. Try not to destroy the performance of the whole * system by trying to do too much.... * * Arguments: p => pointer to the current packet data struct * * Returns: void function * */ static void PreprocFunction(Packet *p) { 
    /* your preproc function goes here.... */ /* * if you need to issue an alert from your preprocessor, check out * event_wrapper.h, there are some useful helper functions there */ } /* * Function: PreprocCleanExitFunction(int, void *) * * Purpose: This function gets called when Snort is exiting, if there's * any cleanup that needs to be performed (e.g. closing files) * it should be done here. * * Arguments: signal => the code of the signal that was issued to Snort * data => any arguments or data structs linked to this * functioin when it was registered, may be * needed to properly exit * * Returns: void function */ static void PreprocCleanExitFunction(int signal, void *data) { 
    /* clean exit code goes here */ } /* * Function: PreprocRestartFunction(int, void *) * * Purpose: This function gets called when Snort is restarting on a SIGHUP, * if there's any initialization or cleanup that needs to happen * it should be done here. * * Arguments: signal => the code of the signal that was issued to Snort * data => any arguments or data structs linked to this * functioin when it was registered, may be * needed to properly exit * * Returns: void function */ static void PreprocRestartFunction(int signal, void *foo) { 
    /* restart code goes here */ } 
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/233419.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 各种云服务器性能优秀强大,各家云服务器性能对比

    各种云服务器性能优秀强大,各家云服务器性能对比各家云服务器性能对比内容精选换一换外部镜像文件在从原平台导出前,没有按照“Windows操作系统的镜像文件限制”的要求完成初始化操作,推荐您使用弹性云服务器完成相关配置。流程如图1所示。云服务器的正常运行依赖于XENGuestOSdriver(PVdriver)和KVMGuestOSdriver(UVPVMTools),未安装会对云服务器运行时的性能产生影使用弹性云服务器或者外部…

    2022年5月23日
    39
  • java注释的作用是什么_java为什么要写注释

    java注释的作用是什么_java为什么要写注释1、什么是注释,有什么用?注释是对java源代码的解释说明。注释可以帮程序员更好的理解程序。2、注释信息只保存在java源文件当中,java源文件编译生成的字节码class文件,这个class文件中是没有这些注释信息的。3、在实际的开发中,一般项目组都要求积极的编写注释。这也是一个java软件工程师的基本素养。4、注释不是写的越多…

    2025年7月5日
    1
  • 在flask中使用jsonify和json.dumps的区别

    在flask中使用jsonify和json.dumps的区别flask提供了jsonify函数供用户处理返回的序列化json数据,而python自带的json库中也有dumps方法可以序列化json对象,那么在flask的视图函数中return它们会有什么不同之处呢?想必开始很多人和我一样搞不清楚,只知道既然框架提供了方法就用,肯定不会错。但作为开发人员,我们需要弄清楚开发过程中各种实现方式的特点和区别,这样在我们面对不同的需求时才能做出相对合理的选择,而

    2022年5月24日
    33
  • dedecms 图集标签{dede:productimagelist} {dede:field name=’imgurls’}&nbs

    dedecms 图集标签{dede:productimagelist} {dede:field name=’imgurls’}&nbs

    2021年9月22日
    44
  • ksh简介「建议收藏」

    ksh简介「建议收藏」–Start什么是Shell如果把Linux比作一个蛋,那么Shell就是蛋壳,我们需要通过Shell来使用系统。Shell的种类最早的Shell是BourneSh

    2022年8月2日
    11
  • win10和linux双系统安装步骤(详细!)

    win10和linux双系统安装步骤(详细!)Windows10安装ubuntu双系统教程ubuntu分区方案

    2022年7月24日
    19

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号