Skip to main content

maec/objects/
malware_family.rs

1//! MAEC Malware Family object implementation
2//!
3//! A Malware Family is a set of malware instances that are related by common
4//! authorship and/or lineage.
5
6use serde::{Deserialize, Serialize};
7
8use crate::common::{ExternalReference, MaecObject};
9use crate::error::{MaecError, Result};
10use crate::objects::types::{FieldData, Name};
11use crate::Capability;
12use chrono::{DateTime, Utc};
13
14/// MAEC Malware Family
15///
16/// A set of malware instances that are related by common authorship and/or lineage.
17/// Malware Families are often named and may have components such as strings that
18/// are common across all members of the family.
19///
20/// # Examples
21///
22/// ```
23/// use maec::{MalwareFamily, Name};
24///
25/// let family = MalwareFamily::builder()
26///     .name(Name::new("WannaCry"))
27///     .description("Ransomware family first seen in May 2017")
28///     .build()
29///     .unwrap();
30///
31/// assert_eq!(family.name.value, "WannaCry");
32/// ```
33#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
34#[serde(rename_all = "snake_case")]
35pub struct MalwareFamily {
36    /// Common MAEC properties
37    #[serde(flatten)]
38    pub common: crate::common::CommonProperties,
39
40    /// Name of the malware family
41    pub name: Name,
42
43    /// Alternative names/aliases
44    #[serde(default, skip_serializing_if = "Vec::is_empty")]
45    pub aliases: Vec<Name>,
46
47    /// Labels describing the family (e.g., "worm", "ransomware")
48    #[serde(default, skip_serializing_if = "Vec::is_empty")]
49    pub labels: Vec<String>,
50
51    /// Textual description
52    #[serde(skip_serializing_if = "Option::is_none")]
53    pub description: Option<String>,
54
55    /// Field data (delivery vectors, timestamps)
56    #[serde(skip_serializing_if = "Option::is_none")]
57    pub field_data: Option<FieldData>,
58
59    /// Strings common to all family members
60    #[serde(default, skip_serializing_if = "Vec::is_empty")]
61    pub common_strings: Vec<String>,
62
63    /// Capabilities common to all family members
64    #[serde(default, skip_serializing_if = "Vec::is_empty")]
65    pub common_capabilities: Vec<Capability>,
66
67    /// References to common code artifacts (STIX artifact IDs)
68    #[serde(default, skip_serializing_if = "Vec::is_empty")]
69    pub common_code_refs: Vec<String>,
70
71    /// References to common behavior IDs
72    #[serde(default, skip_serializing_if = "Vec::is_empty")]
73    pub common_behavior_refs: Vec<String>,
74
75    /// External references (ATT&CK, research papers, etc.)
76    #[serde(default, skip_serializing_if = "Vec::is_empty")]
77    pub references: Vec<ExternalReference>,
78}
79
80impl MalwareFamily {
81    /// Creates a new MalwareFamily builder
82    pub fn builder() -> MalwareFamilyBuilder {
83        MalwareFamilyBuilder::default()
84    }
85
86    /// Creates a minimal MalwareFamily with just a name
87    pub fn new(name: impl Into<Name>) -> Self {
88        Self {
89            common: crate::common::CommonProperties::new("malware-family", None),
90            name: name.into(),
91            aliases: vec![],
92            labels: vec![],
93            description: None,
94            field_data: None,
95            common_strings: vec![],
96            common_capabilities: vec![],
97            common_code_refs: vec![],
98            common_behavior_refs: vec![],
99            references: vec![],
100        }
101    }
102
103    /// Validates the MalwareFamily structure
104    pub fn validate(&self) -> Result<()> {
105        if self.common.r#type != "malware-family" {
106            return Err(MaecError::ValidationError(format!(
107                "type must be 'malware-family', got '{}'",
108                self.common.r#type
109            )));
110        }
111
112        if !crate::common::is_valid_maec_id(&self.common.id) {
113            return Err(MaecError::InvalidId(self.common.id.clone()));
114        }
115
116        Ok(())
117    }
118}
119
120impl MaecObject for MalwareFamily {
121    fn id(&self) -> &str {
122        &self.common.id
123    }
124
125    fn type_(&self) -> &str {
126        &self.common.r#type
127    }
128
129    fn created(&self) -> DateTime<Utc> {
130        self.common.created
131    }
132}
133
134/// Builder for MalwareFamily objects
135#[derive(Debug, Default)]
136pub struct MalwareFamilyBuilder {
137    id: Option<String>,
138    name: Option<Name>,
139    aliases: Vec<Name>,
140    labels: Vec<String>,
141    description: Option<String>,
142    field_data: Option<FieldData>,
143    common_strings: Vec<String>,
144    common_capabilities: Vec<Capability>,
145    common_code_refs: Vec<String>,
146    common_behavior_refs: Vec<String>,
147    references: Vec<ExternalReference>,
148}
149
150impl MalwareFamilyBuilder {
151    /// Sets a custom ID
152    pub fn id(mut self, id: impl Into<String>) -> Self {
153        self.id = Some(id.into());
154        self
155    }
156
157    /// Sets the family name (required)
158    pub fn name(mut self, name: impl Into<Name>) -> Self {
159        self.name = Some(name.into());
160        self
161    }
162
163    /// Adds an alias
164    pub fn add_alias(mut self, alias: impl Into<Name>) -> Self {
165        self.aliases.push(alias.into());
166        self
167    }
168
169    /// Sets all aliases at once
170    pub fn aliases(mut self, aliases: Vec<Name>) -> Self {
171        self.aliases = aliases;
172        self
173    }
174
175    /// Adds a label
176    pub fn add_label(mut self, label: impl Into<String>) -> Self {
177        self.labels.push(label.into());
178        self
179    }
180
181    /// Sets all labels at once
182    pub fn labels(mut self, labels: Vec<String>) -> Self {
183        self.labels = labels;
184        self
185    }
186
187    /// Sets the description
188    pub fn description(mut self, desc: impl Into<String>) -> Self {
189        self.description = Some(desc.into());
190        self
191    }
192
193    /// Sets field data
194    pub fn field_data(mut self, field_data: FieldData) -> Self {
195        self.field_data = Some(field_data);
196        self
197    }
198
199    /// Adds a common string
200    pub fn add_common_string(mut self, s: impl Into<String>) -> Self {
201        self.common_strings.push(s.into());
202        self
203    }
204
205    /// Sets all common strings
206    pub fn common_strings(mut self, strings: Vec<String>) -> Self {
207        self.common_strings = strings;
208        self
209    }
210
211    /// Adds a common capability
212    pub fn add_capability(mut self, capability: Capability) -> Self {
213        self.common_capabilities.push(capability);
214        self
215    }
216
217    /// Sets all common capabilities
218    pub fn common_capabilities(mut self, capabilities: Vec<Capability>) -> Self {
219        self.common_capabilities = capabilities;
220        self
221    }
222
223    /// Adds a common code reference
224    pub fn add_common_code_ref(mut self, ref_id: impl Into<String>) -> Self {
225        self.common_code_refs.push(ref_id.into());
226        self
227    }
228
229    /// Adds a common behavior reference
230    pub fn add_common_behavior_ref(mut self, ref_id: impl Into<String>) -> Self {
231        self.common_behavior_refs.push(ref_id.into());
232        self
233    }
234
235    /// Adds an external reference
236    pub fn add_reference(mut self, reference: ExternalReference) -> Self {
237        self.references.push(reference);
238        self
239    }
240
241    /// Sets all external references
242    pub fn references(mut self, references: Vec<ExternalReference>) -> Self {
243        self.references = references;
244        self
245    }
246
247    /// Builds the MalwareFamily
248    pub fn build(self) -> Result<MalwareFamily> {
249        let name = self.name.ok_or(MaecError::MissingField("name"))?;
250
251        let mut common = crate::common::CommonProperties::new("malware-family", None);
252        if let Some(id) = self.id {
253            common.id = id;
254        }
255
256        let family = MalwareFamily {
257            common,
258            name,
259            aliases: self.aliases,
260            labels: self.labels,
261            description: self.description,
262            field_data: self.field_data,
263            common_strings: self.common_strings,
264            common_capabilities: self.common_capabilities,
265            common_code_refs: self.common_code_refs,
266            common_behavior_refs: self.common_behavior_refs,
267            references: self.references,
268        };
269
270        family.validate()?;
271        Ok(family)
272    }
273}
274
275#[cfg(test)]
276mod tests {
277    use super::*;
278
279    #[test]
280    fn test_malware_family_new() {
281        let family = MalwareFamily::new("WannaCry");
282        assert_eq!(family.name.value, "WannaCry");
283        assert_eq!(family.common.r#type, "malware-family");
284        assert!(family.common.id.starts_with("malware-family--"));
285    }
286
287    #[test]
288    fn test_malware_family_builder() {
289        let family = MalwareFamily::builder()
290            .name(Name::new("Emotet"))
291            .description("Banking trojan and malware loader")
292            .add_label("trojan")
293            .add_label("banking")
294            .add_alias(Name::new("Geodo"))
295            .build()
296            .unwrap();
297
298        assert_eq!(family.name.value, "Emotet");
299        assert_eq!(family.labels.len(), 2);
300        assert_eq!(family.aliases.len(), 1);
301        assert!(family.description.is_some());
302    }
303
304    #[test]
305    fn test_malware_family_validation() {
306        let family = MalwareFamily::new("Test");
307        assert!(family.validate().is_ok());
308    }
309
310    #[test]
311    fn test_malware_family_serialize() {
312        let family = MalwareFamily::builder()
313            .name(Name::new("TestMalware"))
314            .build()
315            .unwrap();
316
317        let json = serde_json::to_string(&family).unwrap();
318        assert!(json.contains("\"type\":\"malware-family\""));
319        assert!(json.contains("TestMalware"));
320
321        let deserialized: MalwareFamily = serde_json::from_str(&json).unwrap();
322        assert_eq!(family, deserialized);
323    }
324}