File Coverage

blib/lib/Malware.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             package Malware;
2              
3             =head1 NAME
4              
5             Malware - Perl extension for storing and manipulation malware and it's attributes
6              
7             =head1 SYNOPSIS
8              
9             See Constructor new()
10              
11             =head1 DESCRIPTION
12              
13             The idea of this module is to allow authors to parse different inputs that perform malware analysis and build objects that describe a piece of malware's behaviour. From here we can write detection or blocking rules based on that behavior.
14              
15             For now, the parsers will live within this class as they are directly connected to the API, this may change in the future.
16              
17             =cut
18              
19 1     1   59718 use 5.008007;
  1         4  
  1         42  
20 1     1   7 use strict;
  1         3  
  1         36  
21 1     1   7 use warnings;
  1         6  
  1         44  
22              
23 1     1   454 use Time::Timestamp;
  0            
  0            
24             use Text::Table;
25             use Class::ParmList qw(parse_parms);
26             our $VERSION = '1.02';
27              
28             =head1 OBJECT METHODS
29              
30             =head2 new()
31              
32             Object Constructor
33              
34             my $m = Malware->new(
35             -platform => 'win32',
36             -filesize => $size,
37             -filesizeUnits => $units,
38             -filename => $name,
39             -classification => $class,
40             -md5sum => $md5,
41             -source => $src,
42             -sourceLoc => $srcLoc,
43             -rawReport => $rawTextReport,
44             -dt_found => $dt, # [see Time::Timestamp]
45             -connections => $cons, # [see Net::Connection::Simple]
46             -securityIssues => $si,
47             -antiEmulation => $ae,
48             -backdoors => $bd, # [see Net::Protocol::Simple]
49             -malwareFiles => $mw # [hashref],
50             );
51              
52             By setting the -platform, the module will try to re-bless itself as that module (ie: Malware::Win32 for -platform => win32). This allows you to scale into specific OS's and their properties (registry for win32).
53              
54             See OBJECT ACCESSORS for the i/o of these properties
55              
56             =cut
57              
58             sub new {
59             my ($class,%parms) = @_;
60             my $self = {};
61             bless($self,$class);
62              
63             $self->init(%parms);
64             if(defined($parms{-platform})){
65             $class .= '::'.ucfirst($parms{-platform});
66             eval 'use '.$class;
67             if(!$@){
68             bless($self,$class);
69             eval { $self->init(%parms) };
70             } else { return ('Error loading: '.$parms{-platform}."\n".$@,undef); }
71             }
72             return $self;
73             }
74              
75             sub init {
76             my ($self,%parms) = @_;
77             $self->filesize( $parms{-filesize});
78             $self->filesizeUnits( $parms{-filesizeUnits});
79             $self->filename( $parms{-filename});
80             $self->classification( $parms{-classification});
81             $self->md5sum( $parms{-md5sum});
82             $self->source( $parms{-source});
83             $self->sourceLoc( $parms{-sourceLoc});
84             $self->rawReport( $parms{-rawReport});
85             $self->dt_found( $parms{-dt_found});
86             $self->connections( $parms{-connections});
87             $self->securityIssues( $parms{-securityIssues});
88             $self->antiEmulation( $parms{-antiEmulation});
89             $self->backdoors( $parms{-backdoors});
90             $self->malwareFiles( $parms{-malewareFiles});
91             }
92              
93             # METHODS
94              
95             =head2 blurb()
96              
97             Returns a blurb of the raw report in a nicer Text::Table'ish form. If you subclass (Malware::$platform) this module, you can tag into this method by creating a 'sub _blurb' function that returns a HASHREF in the form:
98              
99             sub _blurb {
100             my $self = shift;
101             my $hr = {};
102             $hr->{Registry} = $self->registry();
103             return $hr;
104             }
105              
106             =cut
107              
108             sub blurb {
109             my $self = shift;
110              
111             my $txtFilename = $self->filename();
112             $txtFilename = 'unknown' if(!$txtFilename);
113              
114             my $txtClassification = $self->classification();
115             $txtClassification = 'unknown' if(!$txtClassification);
116              
117             my $txtMd5sum = $self->md5sum();
118             $txtMd5sum = 'unknown' if(!$txtMd5sum);
119              
120             my $txtAntiEmulation = 'unknown';
121             if(defined($self->antiEmulation())){
122             $txtAntiEmulation = 'yes';
123             $txtAntiEmulation = 'no' if($self->antiEmulation() == 0);
124             }
125              
126             my $txtConnections = 'unknown';
127             if(my @a = @{$self->connections()}){
128             $txtConnections = '';
129             $txtConnections = Text::Table->new();
130             my @txt;
131             foreach my $c (@a){
132             next unless(ref($c) eq 'Net::Connection::Simple');
133             my $p = $c->protocols();
134             $p->{3}->{_dip} = 'unknown' if(!$p->{3}->{_dip});
135             $p->{4}->{_dport} = 'unknown' if(!$p->{4}->{_dport});
136             $p->{7}->{_dns} = 'unknown' if(!$p->{7}->{_dns});
137              
138             my $tz = Text::Table->new('Layer','Protocol','');
139             $tz->load(
140             [3,$p->{3}->protocol(),"DIP:\t".$p->{3}->{_dip}],
141             [4,$p->{4}->protocol(),"DPORT:\t".$p->{4}->{_dport}],
142             [7,$p->{7}->protocol(),"DNS:\t".$p->{7}->{_dns}],
143             ["\n",undef],
144             );
145             push(@txt,$tz);
146             }
147             $txtConnections->load(\@txt);
148             }
149              
150             my $txtMalwareFiles = 'unkown';
151             if(my @a = @{$self->malwareFiles()}){
152             $txtMalwareFiles = Text::Table->new('Filename',"\t".'Signature');
153             foreach my $hr (@a){
154             next unless(ref($hr) eq 'HASH');
155             foreach my $f (keys %$hr){
156             $txtMalwareFiles->load([$f,"\t".$hr->{$f}]);
157             }
158             }
159             }
160              
161             my $txtProcessInfo = 'unknown';
162             if(my @a = @{$self->processInfo()}){
163             $txtProcessInfo = Text::Table->new();
164             foreach my $x (@a){
165             $txtProcessInfo->load([$x]);
166             }
167             }
168              
169             my $txtFilesystem = 'unknown';
170             if(my @a = @{$self->filesystem()}){
171             $txtFilesystem = Text::Table->new();
172             foreach my $x (@a){
173             $txtFilesystem->load([$x]);
174             }
175             }
176              
177             my $txtDtFound = 'unknown';
178             $txtDtFound = $self->dt_found->tsIso() if($self->dt_found());
179              
180             my $txtSource = $self->source();
181             $txtSource = 'unknown' if(!$txtSource);
182              
183             my $txtFilesize = $self->filesize();
184             $txtFilesize = 'unknown' if(!$txtFilesize);
185              
186             my $txtFilesizeUnits = $self->filesizeUnits();
187             $txtFilesizeUnits = '' if(!$txtFilesizeUnits);
188              
189             my $t = Text::Table->new();
190             $t->load(
191             ['Filename:',$txtFilename],
192             ['Filesize:',$txtFilesize.' '.$txtFilesizeUnits],
193             ['MD5:',$txtMd5sum],
194             ['Classification:',$txtClassification],
195             ['source:',$txtSource],
196             ['Date Found:',$txtDtFound],
197             ['Anti-Emulation:',$txtAntiEmulation],
198             ["\n",undef],
199             ['Malware Files:',$txtMalwareFiles],
200             ["\n",undef],
201             ['Process Information:',$txtProcessInfo],
202             ["\n",undef],
203             ['Filesystem Changes:',$txtFilesystem],
204             ["\n",undef],
205             ['Connections:',$txtConnections],
206             );
207              
208             my $hr;
209             eval { $hr = $self->_blurb() };
210             return $t unless($hr);
211              
212             foreach my $key (keys %$hr){
213             my $txt = Text::Table->new();
214             foreach my $x (@{$hr->{$key}}){
215             $txt->load([$x]);
216             }
217             $t->load([$key.':',$txt]);
218             $t->load(["\n",undef]);
219             }
220             return $t;
221             }
222              
223             =head2 returnConnectionsByLayer_array()
224              
225             Diggs into the connections property and returns the type of connection you are looking for.
226              
227             Example: We want all the url connections the malware made
228              
229             my @http = $m->returnConnectionsByLayer(
230             -type => 'url',
231             -layer => 7,
232             -protocol => 'http')
233             );
234              
235             Example: We want all the irc connections the malware made via dns
236              
237             my @irc_dns = $m->returnConnectionsByLayer(
238             -type => 'dns',
239             -layer => 7,
240             -protocol => 'IRC')
241             );
242              
243             Example: We want all the irc connections the malware made via direct IP
244              
245             my @irc_dip = $m->returnConnectionsByLayer(
246             -type => 'dip',
247             -layer => 7,
248             -protocol => 'IRC')
249             );
250              
251              
252             All three params are required or it will return:
253              
254             ("Invalid parameters...",undef)
255              
256             On success it returns an @rray of strings
257              
258             =cut
259              
260             sub returnConnectionsByLayer_array {
261             my $self = shift;
262             return unless($self->connections());
263             my $parms = parse_parms({
264             -parms => \@_,
265             -required => [qw(-layer -protocol -type)],
266             });
267             return ("invalid parameters\n".Carp::longmess (Class::ParmList->error()),undef) unless(defined($parms));
268             my ($l,$p,$t) = $parms->get('-layer','-protocol','-type');
269              
270             my @a = @{$self->connections()};
271             my @return;
272             foreach my $c (@a){
273             my $proto = $c->protocols();
274             next unless(defined($proto->{$l}->{"_$t"}) && (($proto->{$l}->protocol() eq uc($p))));
275             push(@return,$proto->{$l}->{"_$t"});
276             }
277             return @return;
278             }
279              
280             # ACCESSORS/MODIFIERS
281              
282             =head1 ACCESSORS / MODIFIERS
283              
284             =head2 filesize()
285              
286             Sets and returns the filesize
287              
288             =cut
289              
290             sub filesize {
291             my ($self,$v) = @_;
292             $self->{_filesize} = $v if(defined($v));
293             return $self->{_filesize};
294             }
295              
296             =head2 filesizeUnits()
297              
298             Sets and returns the filesizeUnits
299              
300             =cut
301              
302             sub filesizeUnits {
303             my ($self,$v) = @_;
304             $self->{_filesizeUnits} = $v if(defined($v));
305             return $self->{_filesizeUnits};
306             }
307              
308             =head2 filename()
309              
310             Sets and returns the filename()
311              
312             =cut
313              
314             sub filename {
315             my ($self,$v) = @_;
316             $self->{_filename} = $v if(defined($v));
317             return $self->{_filename};
318             }
319              
320             =head2 classification()
321              
322             Sets and returns the malware classification
323              
324             =cut
325              
326             sub classification {
327             my ($self,$v) = @_;
328             $self->{_classification} = $v if(defined($v));
329             return $self->{_classification};
330             }
331              
332             =head2 md5sum()
333              
334             Sets and returns the md5sum
335              
336             =cut
337              
338             sub md5sum {
339             my ($self,$v) = @_;
340             $self->{_md5sum} = $v if(defined($v));
341             return $self->{_md5sum};
342             }
343              
344             =head2 dt_found()
345              
346             Sets and returns the date found, return is a Time::Timestamp object
347              
348             $m->dt_found($timestamp,$timezone);
349              
350             Timezone is optional, but the timing could get screwed up if you don't set it
351              
352             =cut
353              
354             sub dt_found {
355             my ($self,$v,$tz) = @_;
356             $self->{_dt_found} = Time::Timestamp->new(ts => $v, tz => $tz) if(defined($v));
357             return $self->{_dt_found};
358             }
359              
360             =head2 source()
361              
362             Sets and returns the malware report source (where did you get it?)
363              
364             =cut
365              
366             sub source {
367             my ($self,$v) = @_;
368             $self->{_source} = $v if(defined($v));
369             return $self->{_source};
370             }
371              
372              
373             =head2 sourceLoc()
374              
375             Sets and returns the source location (what medium did you get it from (email, website, etc...)
376              
377             =cut
378              
379              
380             sub sourceLoc {
381             my ($self,$v) = @_;
382             $self->{_sourceLoc} = $v if(defined($v));
383             return $self->{_sourceLoc};
384             }
385              
386             =head2 connections()
387              
388             Sets and returns the connection behaviour of the malware.
389              
390             my $c = Net::Connection::Simple->new(...);
391             $m->connections($c);
392              
393             my @cons = @{$m->connections()};
394              
395             Accepts: Net::Connection::Simple or returns ($errstr,undef)
396              
397             Returns: a ref to an array of Net::Connection::Simple objects
398              
399             =cut
400              
401             sub connections {
402             my ($self,$v) = @_;
403             if(defined($v)){
404             return ('Net::Connection::Simple required',undef) unless(ref($v) eq 'Net::Connection::Simple');
405             push(@{$self->{_connections}},$v);
406             }
407             return $self->{_connections};
408             }
409              
410             =head2 backdoors()
411              
412             Sets and returns the malwares backdoor behavior.
413              
414             my $p = Net::Protocol::Simple->new(...);
415             $m->backdoors($p);
416              
417             my @bds = @{$m->backdoors};
418              
419             Accepts: Net::Protocol::Simple or returns ($errstr,undef)
420              
421             Returns: a ref to an array of Net::Protocol::Simple objects
422              
423             =cut
424              
425             sub backdoors {
426             my ($self,$v) = @_;
427             if(defined($v)){
428             return ('expecting a Net::Protocol::Simple',undef) unless(ref($v) eq 'Net::Protocol::Simple');
429             push(@{$self->{_backdoors}},$v);
430             }
431             return $self->{_backdoors};
432             }
433              
434             =head2 processInfo()
435              
436             Sets and returns the processInfo behavior.
437              
438             Accepts: string
439              
440             Returns: a ref to an array of strings
441              
442             =cut
443              
444             sub processInfo {
445             my ($self,$v) = @_;
446             push(@{$self->{_processInfo}},$v) if(defined($v));
447             return $self->{_processInfo};
448             }
449              
450             =head2 filesystem()
451              
452             Sets and returns the filesystem behavior.
453              
454             Accepts: string
455              
456             Returns: a ref to an array of strings
457              
458             =cut
459              
460             sub filesystem {
461             my ($self,$v) = @_;
462             push(@{$self->{_filesystem}},$v) if(defined($v));
463             return $self->{_filesystem};
464             }
465              
466             =head2 rawReport()
467              
468             Sets and returns the raw report string
469              
470             Accepts: string
471              
472             Returns: string
473              
474             =cut
475              
476             sub rawReport {
477             my ($self,$v) = @_;
478             $self->{_rawReport} = $v if(defined($v));
479             return $self->{_rawReport};
480             }
481              
482             =head2 malwareFiles()
483              
484             Sets and returns a list of other files that are found to be created or associated with this malware
485              
486             Accepts: HASHREF or returns ($errstr,undef)
487              
488             $m->malwareFiles({
489             $f1 => $md5,
490             $f2 => $virus_sig,
491             });
492              
493             OR
494              
495             $m->malwareFiles({
496             $f1->{md5} = $md5,
497             $f1->{vsig} = $virus_sig,
498             $f1->{snortSig} = $snort_sig,
499             });
500              
501             Returns: HASHREF
502              
503             =cut
504              
505             sub malwareFiles {
506             my ($self,$v) = @_;
507             if(defined($v)){
508             return ('expecting a HASH',undef) unless(ref($v) eq 'HASH');
509             push(@{$self->{_malwareFiles}},$v);
510             }
511             return $self->{_malwareFiles};
512             }
513              
514             =head2 antiEmulation()
515              
516             Sets and returns the antiEmulation behavior.
517              
518             Accepts: int [undef|1|0]
519              
520             Returns: whatever you put in
521              
522             **Note: the blurb will translate [undef] as unknown
523              
524             =cut
525              
526             sub antiEmulation {
527             my ($self,$v) = @_;
528             $self->{_antiEmulation} = $v if(defined($v));
529             return $self->{_antiEmulation};
530             }
531              
532             =head2 securityIssues()
533              
534             Sets and returns other security issues that are caused.
535              
536             Accepts: string
537              
538             Returns: a ref to an array of strings
539              
540             =cut
541              
542             sub securityIssues {
543             my ($self,$v) = @_;
544             push(@{$self->{_securityIssues}},$v) if(defined($v));
545             return $self->{_securityIssues};
546             }
547             1;
548             __END__