diff --git a/docs/Module Developer's Guide.md b/docs/Module Developer's Guide.md index 663df2b2e..881f70d9b 100644 --- a/docs/Module Developer's Guide.md +++ b/docs/Module Developer's Guide.md @@ -12,7 +12,6 @@ For illustrative purposes we are going to create a `text` module that allows creating YARA rules for plain-text files, based on the number of lines and words -- [Module Developer's Guide](#module-developers-guide) - [Defining the module's structure](#defining-the-modules-structure) - [Proto2 vs Proto3](#proto2-vs-proto3) - [Tweaking the module's YAML output](#tweaking-the-modules-yaml-output) @@ -65,6 +64,7 @@ option (yara.module_options) = { name : "text" root_message: "text.Text" rust_module: "text" + cargo_feature: "text-module" }; message Text { @@ -121,6 +121,7 @@ option (yara.module_options) = { name : "text" root_message: "text.Text" rust_module: "text" + cargo_feature: "text-module" }; ``` @@ -131,16 +132,24 @@ file, but one describing a module. In fact, you can put any `.proto` file in the files is describing a YARA module. Only files containing a `yara.module_options` section will define a module. -Options `name` and `root_message` are required, while `rust_module` is optional. -The `name` option defines the module's name. This is the name that will be used -for importing the module in a YARA rule, in this case our module will be imported -with `import "text"`. The `root_message` option indicates which is the module's -root structure, it must contain the name of some structure (a.k.a message) defined -in the `.proto` file. In our case the value for `root_message` is `"text.Text"` -because we have defined our module's structure in a message named `Text`, which -is under package `text`. In general the value in this field will have the form -`package.Message`, except if the `package` statement is missing, in which case -it would be the name of the message alone (i.e: `Text`). +Options `name` and `root_message` are required, while `rust_module` and +`cargo_feature` are optional. The `name` option defines the module's name. This +is the name that will be used for importing the module in a YARA rule, in this +case our module will be imported with `import "text"`. + +The `cargo_feature` option indicates the name of the feature that controls whether +the module is built or not. If this option is not specified the module is always +built, but if you specify a feature name, this feature name must also be included +in the `Cargo.toml` file, and the module will be built only when this `cargo` +feature is enabled. + +The `root_message` option a very important option indicating which is the module's +root structure, it must contain the name of some structure (a.k.a. message) +defined in the `.proto` file. In our case the value for `root_message` is +`"text.Text"` because we have defined our module's structure in a message named +`Text`, which is under package `text`. In general the value in this field will +have the form `package.Message`, except if the `package` statement is missing, +in which case it would be the name of the message alone (i.e: `Text`). The `root_message` field is required because your `.proto` file can define multiple messages, and YARA needs to know which of them is considered the root diff --git a/lib/build.rs b/lib/build.rs index cabd1f909..32e4c3857 100644 --- a/lib/build.rs +++ b/lib/build.rs @@ -121,7 +121,7 @@ fn main() { yara_module_options.get(&proto_file.options) { modules.push(( - module_options.name, + module_options.name.unwrap(), proto_file .name .unwrap() @@ -129,7 +129,8 @@ fn main() { .unwrap() .to_string(), module_options.rust_module, - module_options.root_message, + module_options.cargo_feature, + module_options.root_message.unwrap(), )); } } @@ -164,37 +165,44 @@ fn main() { let name = m.0; let proto_mod = m.1; let rust_mod = m.2; - let root_message = m.3; - - if !rust_mod.is_empty() { - write!( - modules_rs, - r#" -#[cfg(feature = "{name}-module")] -mod {rust_mod};"#, - ) - .unwrap(); - } + let cargo_feature = m.3; + let root_message = m.4; // If the YARA module has an associated Rust module, this module must // have a function named "main". If the YARA module doesn't have an // associated YARA module, the main function is set to None. - let main_fn = if !rust_mod.is_empty() { + let main_fn = if let Some(rust_mod) = &rust_mod { format!("Some({}::__main__ as MainFn)", rust_mod) } else { "None".to_string() }; - let rust_mod_name = if !rust_mod.is_empty() { + let rust_mod_name = if let Some(rust_mod) = &rust_mod { format!(r#"Some("{}")"#, rust_mod) } else { "None".to_string() }; + let cfg_feature = if let Some(cargo_feature) = &cargo_feature { + format!(r#"#[cfg(feature = "{cargo_feature}")]"#) + } else { + "".to_string() + }; + + if let Some(rust_mod) = &rust_mod { + write!( + modules_rs, + r#" +{cfg_feature} +mod {rust_mod};"#, + ) + .unwrap(); + } + write!( add_modules_rs, r#" -#[cfg(feature = "{name}-module")] +{cfg_feature} add_module!(modules, "{name}", {proto_mod}, "{root_message}", {rust_mod_name}, {main_fn});"#, ) .unwrap(); diff --git a/lib/src/modules/protos/console.proto b/lib/src/modules/protos/console.proto index 05ce59fc7..40d615584 100644 --- a/lib/src/modules/protos/console.proto +++ b/lib/src/modules/protos/console.proto @@ -7,6 +7,7 @@ option (yara.module_options) = { name : "console" root_message: "console.Console" rust_module: "console" + cargo_feature: "console-module" }; message Console { diff --git a/lib/src/modules/protos/dotnet.proto b/lib/src/modules/protos/dotnet.proto index bb5239e2d..6eb817ce2 100644 --- a/lib/src/modules/protos/dotnet.proto +++ b/lib/src/modules/protos/dotnet.proto @@ -7,6 +7,7 @@ option (yara.module_options) = { name : "dotnet" root_message: "dotnet.Dotnet" rust_module: "dotnet" + cargo_feature: "dotnet-module" }; message Dotnet { diff --git a/lib/src/modules/protos/elf.proto b/lib/src/modules/protos/elf.proto index b99e5bb27..006f88cb2 100644 --- a/lib/src/modules/protos/elf.proto +++ b/lib/src/modules/protos/elf.proto @@ -7,6 +7,7 @@ option (yara.module_options) = { name : "elf" root_message: "elf.ELF" rust_module: "elf" + cargo_feature: "elf-module" }; message ELF { diff --git a/lib/src/modules/protos/hash.proto b/lib/src/modules/protos/hash.proto index 488f2a871..888b435a7 100644 --- a/lib/src/modules/protos/hash.proto +++ b/lib/src/modules/protos/hash.proto @@ -7,6 +7,7 @@ option (yara.module_options) = { name : "hash" root_message: "hash.Hash" rust_module: "hash" + cargo_feature: "hash-module" }; message Hash { diff --git a/lib/src/modules/protos/lnk.proto b/lib/src/modules/protos/lnk.proto index 049d2f149..df4305bff 100644 --- a/lib/src/modules/protos/lnk.proto +++ b/lib/src/modules/protos/lnk.proto @@ -7,6 +7,7 @@ option (yara.module_options) = { name : "lnk" root_message: "lnk.Lnk" rust_module: "lnk" + cargo_feature: "lnk-module" }; enum FileAttributes { diff --git a/lib/src/modules/protos/macho.proto b/lib/src/modules/protos/macho.proto index 8dfd00475..886784349 100644 --- a/lib/src/modules/protos/macho.proto +++ b/lib/src/modules/protos/macho.proto @@ -8,6 +8,7 @@ option (yara.module_options) = { name : "macho" root_message: "macho.Macho" rust_module: "macho" + cargo_feature: "macho-module" }; message Dylib { diff --git a/lib/src/modules/protos/math.proto b/lib/src/modules/protos/math.proto index edd5ed3bb..7a74645da 100644 --- a/lib/src/modules/protos/math.proto +++ b/lib/src/modules/protos/math.proto @@ -7,6 +7,7 @@ option (yara.module_options) = { name : "math" root_message: "math.Math" rust_module: "math" + cargo_feature: "math-module" }; message Math { diff --git a/lib/src/modules/protos/pe.proto b/lib/src/modules/protos/pe.proto index 37204d27b..2da3b5e58 100644 --- a/lib/src/modules/protos/pe.proto +++ b/lib/src/modules/protos/pe.proto @@ -9,6 +9,7 @@ option (yara.module_options) = { name : "pe" root_message: "pe.PE" rust_module: "pe" + cargo_feature: "pe-module" }; message PE { diff --git a/lib/src/modules/protos/string.proto b/lib/src/modules/protos/string.proto index 4d5a34b3a..286b44410 100644 --- a/lib/src/modules/protos/string.proto +++ b/lib/src/modules/protos/string.proto @@ -7,6 +7,7 @@ option (yara.module_options) = { name : "string" root_message: "string.String" rust_module: "string" + cargo_feature: "string-module" }; message String { diff --git a/lib/src/modules/protos/test_proto2.proto b/lib/src/modules/protos/test_proto2.proto index 668970bd2..434daaf98 100644 --- a/lib/src/modules/protos/test_proto2.proto +++ b/lib/src/modules/protos/test_proto2.proto @@ -45,6 +45,10 @@ option (yara.module_options) = { // in the data structure defined by this proto file, and don't need to have // any associated code. rust_module: "test_proto2" + + // The name of the feature that controls whether this module is compiled or + // not. A feature with this name must be added to the Cargo.toml file. + cargo_feature: "test_proto2-module" }; /// Top-level structure for this module. diff --git a/lib/src/modules/protos/test_proto3.proto b/lib/src/modules/protos/test_proto3.proto index d6235e3d5..1163073c6 100644 --- a/lib/src/modules/protos/test_proto3.proto +++ b/lib/src/modules/protos/test_proto3.proto @@ -45,6 +45,10 @@ option (yara.module_options) = { // in the data structure defined by this proto file, and don't need to have // any associated code. rust_module: "test_proto3" + + // The name of the feature that controls whether this module is compiled or + // not. A feature with this name must be added to the Cargo.toml file. + cargo_feature: "test_proto3-module" }; /// Top-level structure for this module. diff --git a/lib/src/modules/protos/text.proto b/lib/src/modules/protos/text.proto index e66eaf29e..b0e38fc48 100644 --- a/lib/src/modules/protos/text.proto +++ b/lib/src/modules/protos/text.proto @@ -15,6 +15,9 @@ option (yara.module_options) = { // The Rust module implementing this YARA module is named `text`. It can // be found in `src/modules/text.rs`. rust_module: "text" + // The feature that controls whether this module is compiled or not is named + // `text-module`. + cargo_feature: "text-module" }; // This is the module's root structure. diff --git a/lib/src/modules/protos/time.proto b/lib/src/modules/protos/time.proto index 0056c55fe..79351d4cd 100644 --- a/lib/src/modules/protos/time.proto +++ b/lib/src/modules/protos/time.proto @@ -7,6 +7,7 @@ option (yara.module_options) = { name : "time" root_message: "time.Time" rust_module: "time" + cargo_feature: "time-module" }; message Time { diff --git a/lib/src/types/structure.rs b/lib/src/types/structure.rs index f9fecd54c..e16e123e1 100644 --- a/lib/src/types/structure.rs +++ b/lib/src/types/structure.rs @@ -412,7 +412,7 @@ impl Struct { if let Some(options) = module_options.get(&file_descriptor.proto().options) { - options.root_message == msg_descriptor.full_name() + options.root_message.unwrap() == msg_descriptor.full_name() } else { false } @@ -438,11 +438,7 @@ impl Struct { if let Some(options) = enum_options.get(&enum_descriptor.proto().options) { - if options.name.is_empty() { - enum_descriptor.name().to_owned() - } else { - options.name.clone() - } + options.name.unwrap_or_else(|| enum_descriptor.name().to_owned()) } else { enum_descriptor.name().to_owned() } @@ -484,7 +480,7 @@ impl Struct { if let Some(options) = enum_options.get(&enum_descriptor.proto().options) { - options.inline + options.inline.unwrap_or(false) } else { false } @@ -529,7 +525,7 @@ impl Struct { if let Some(options) = enum_value.get(&enum_value_descriptor.proto().options) { - options.i64 + options.i64.unwrap_or_else(|| enum_value_descriptor.value() as i64) } else { enum_value_descriptor.value() as i64 } @@ -552,11 +548,7 @@ impl Struct { if let Some(options) = field_options.get(&field_descriptor.proto().options) { - if options.name.is_empty() { - field_descriptor.name().to_owned() - } else { - options.name.clone() - } + options.name.unwrap_or_else(|| field_descriptor.name().to_owned()) } else { field_descriptor.name().to_owned() } @@ -575,7 +567,7 @@ impl Struct { if let Some(options) = field_options.get(&field_descriptor.proto().options) { - options.ignore + options.ignore.unwrap_or(false) } else { false } diff --git a/mod.cfg b/mod.cfg new file mode 100644 index 000000000..1f1e27969 --- /dev/null +++ b/mod.cfg @@ -0,0 +1,20 @@ +{ + "includes": [ + "../../atlas/vt-protos/protos/tools", + "../../atlas/vt-protos/protos" + ], + "inputs": [ + "../../atlas/vt-protos/protos/titan.proto", + "../../atlas/vt-protos/protos/filetypes.proto", + "../../atlas/vt-protos/protos/sandbox.proto", + "../../atlas/vt-protos/protos/vtnet.proto", + "../../atlas/vt-protos/protos/submitter.proto", + "../../atlas/vt-protos/protos/analysis.proto", + "../../atlas/vt-protos/protos/tools/net_analysis.proto", + "../../atlas/vt-protos/protos/tools/snort.proto", + "../../atlas/vt-protos/protos/tools/suricata.proto", + "../../atlas/vt-protos/protos/tools/tshark.proto", + "../../atlas/vt-protos/protos/sigma.proto", + "../../atlas/vt-protos/protos/relationships.proto" + ] +} \ No newline at end of file diff --git a/proto/src/yara.proto b/proto/src/yara.proto index 669a2e300..dbf731326 100644 --- a/proto/src/yara.proto +++ b/proto/src/yara.proto @@ -1,52 +1,53 @@ // Protocol buffer that specifies the options that can be used in other protos // for controlling the generation of YARA modules. -syntax = "proto3"; +syntax = "proto2"; package yara; import "google/protobuf/descriptor.proto"; message ModuleOptions { - string name = 1; - string root_message = 2; - string rust_module = 3; + required string name = 1; + required string root_message = 2; + optional string rust_module = 3; + optional string cargo_feature = 4; } message FieldOptions { - string name = 1; - bool ignore = 2; + optional string name = 1; + optional bool ignore = 2; } message MessageOptions { - string name = 1; + optional string name = 1; } message EnumOptions { - string name = 1; - bool inline = 2; + optional string name = 1; + optional bool inline = 2; } message EnumValueOptions { - int64 i64 = 1; + optional int64 i64 = 1; } extend google.protobuf.FileOptions { - ModuleOptions module_options = 51503; + optional ModuleOptions module_options = 51503; } extend google.protobuf.FieldOptions { - FieldOptions field_options = 51504; + optional FieldOptions field_options = 51504; } extend google.protobuf.MessageOptions { - MessageOptions message_options = 51505; + optional MessageOptions message_options = 51505; } extend google.protobuf.EnumOptions { - EnumOptions enum_options = 51506; + optional EnumOptions enum_options = 51506; } extend google.protobuf.EnumValueOptions { - EnumValueOptions enum_value = 51507; + optional EnumValueOptions enum_value = 51507; } \ No newline at end of file