package Malware;

=head1 NAME

Malware - Perl extension for storing and manipulation malware and it's attributes

=head1 SYNOPSIS

See Constructor new() 

=head1 DESCRIPTION

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.

For now, the parsers will live within this class as they are directly connected to the API, this may change in the future.

=cut

use 5.008007;
use strict;
use warnings;

use Time::Timestamp;
use Text::Table;
use Class::ParmList qw(parse_parms);
our $VERSION = '1.00';

=head1 OBJECT METHODS

=head2 new()

Object Constructor

  my $m = Malware->new(
  	-platform => 'win32',
  	-filesize => $size,
  	-filesizeUnits => $units,
  	-filename => $name,
  	-classification => $class,
  	-md5sum => $md5,
  	-source => $src,
  	-sourceLoc => $srcLoc,
  	-rawReport => $rawTextReport,
  	-dt_found => $dt, # [see Time::Timestamp]
  	-connections => $cons, # [see Net::Connection::Simple]
  	-securityIssues => $si,
  	-antiEmulation => $ae,
  	-backdoors => $bd, # [see Net::Protocol::Simple]
  	-malwareFiles => $mw # [hashref],
  );

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).

See OBJECT ACCESSORS for the i/o of these properties

=cut

sub new {
	my ($class,%parms) = @_;
	my $self = {};
	bless($self,$class);

	$self->init(%parms);
	if(defined($parms{-platform})){
		$class .= '::'.ucfirst($parms{-platform});
		eval 'use '.$class;
		if(!$@){
			bless($self,$class);
			eval { $self->init(%parms) };
		} else { return ('Error loading: '.$parms{-platform}."\n".$@,undef); }
	}
	return $self;
}

sub init {
	my ($self,%parms) = @_;
	$self->filesize(	$parms{-filesize});
	$self->filesizeUnits(	$parms{-filesizeUnits});
	$self->filename(	$parms{-filename});
	$self->classification(	$parms{-classification});
	$self->md5sum(		$parms{-md5sum});
	$self->source(		$parms{-source});
	$self->sourceLoc(	$parms{-sourceLoc});
	$self->rawReport(	$parms{-rawReport});
	$self->dt_found(	$parms{-dt_found});
	$self->connections(	$parms{-connections});
	$self->securityIssues(	$parms{-securityIssues});
	$self->antiEmulation(	$parms{-antiEmulation});
	$self->backdoors(	$parms{-backdoors});
	$self->malwareFiles(	$parms{-malewareFiles});
}

# METHODS

=head2 blurb()

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:

  sub _blurb {
  	my $self = shift;
  	my $hr = {};
  	$hr->{Registry} = $self->registry();
  	return $hr;
  }

=cut

sub blurb {
	my $self = shift;

	my $txtFilename = $self->filename();
	$txtFilename = 'unknown' if(!$txtFilename);

	my $txtClassification = $self->classification();
	$txtClassification = 'unknown' if(!$txtClassification);

	my $txtMd5sum = $self->md5sum();
	$txtMd5sum = 'unknown' if(!$txtMd5sum);

	my $txtAntiEmulation = 'unknown';
	if(defined($self->antiEmulation())){
		$txtAntiEmulation = 'yes';
		$txtAntiEmulation = 'no' if($self->antiEmulation() == 0);
	}

	my $txtConnections = 'unknown';
	if(my @a = @{$self->connections()}){
		$txtConnections = '';
		$txtConnections = Text::Table->new();
		my @txt;
		foreach my $c (@a){
			next unless(ref($c) eq 'Net::Connection::Simple');
			my $p = $c->protocols();
			$p->{3}->{_dip} = 'unknown' if(!$p->{3}->{_dip});
			$p->{4}->{_dport} = 'unknown' if(!$p->{4}->{_dport});
			$p->{7}->{_dns} = 'unknown' if(!$p->{7}->{_dns});

			my $tz = Text::Table->new('Layer','Protocol','');
			$tz->load(
				[3,$p->{3}->protocol(),"DIP:\t".$p->{3}->{_dip}],
				[4,$p->{4}->protocol(),"DPORT:\t".$p->{4}->{_dport}],
				[7,$p->{7}->protocol(),"DNS:\t".$p->{7}->{_dns}],
				["\n",undef],
			);
			push(@txt,$tz);
		}
		$txtConnections->load(\@txt);
	}

	my $txtMalwareFiles = 'unkown';
	if(my @a = @{$self->malwareFiles()}){
		$txtMalwareFiles = Text::Table->new('Filename',"\t".'Signature');
		foreach my $hr (@a){
			next unless(ref($hr) eq 'HASH');
			foreach my $f (keys %$hr){
				$txtMalwareFiles->load([$f,"\t".$hr->{$f}]);
			}
		}
	}

	my $txtProcessInfo = 'unknown';
	if(my @a = @{$self->processInfo()}){
		$txtProcessInfo = Text::Table->new();
		foreach my $x (@a){
			$txtProcessInfo->load([$x]);
		}
	}

	my $txtFilesystem = 'unknown';
	if(my @a = @{$self->filesystem()}){
		$txtFilesystem = Text::Table->new();
		foreach my $x (@a){
			$txtFilesystem->load([$x]);
		}
	}

	my $txtDtFound = 'unknown';
	$txtDtFound = $self->dt_found->tsIso() if($self->dt_found());

	my $txtSource = $self->source();
	$txtSource = 'unknown' if(!$txtSource);

	my $txtFilesize = $self->filesize();
	$txtFilesize = 'unknown' if(!$txtFilesize);

	my $txtFilesizeUnits = $self->filesizeUnits();
	$txtFilesizeUnits = '' if(!$txtFilesizeUnits);

	my $t = Text::Table->new();
	$t->load(
		['Filename:',$txtFilename],
		['Filesize:',$txtFilesize.' '.$txtFilesizeUnits],
		['MD5:',$txtMd5sum],
		['Classification:',$txtClassification],
		['source:',$txtSource],
		['Date Found:',$txtDtFound],
		['Anti-Emulation:',$txtAntiEmulation],
		["\n",undef],
		['Malware Files:',$txtMalwareFiles],
		["\n",undef],
		['Process Information:',$txtProcessInfo],
		["\n",undef],
		['Filesystem Changes:',$txtFilesystem],
		["\n",undef],
		['Connections:',$txtConnections],
	);

	my $hr;
	eval { $hr = $self->_blurb() };
	return $t unless($hr);

	foreach my $key (keys %$hr){
		my $txt = Text::Table->new();
		foreach my $x (@{$hr->{$key}}){
			$txt->load([$x]);
		}
		$t->load([$key.':',$txt]);
		$t->load(["\n",undef]);
	}
	return $t;
}

=head2 returnConnectionsByLayer_array()

Diggs into the connections property and returns the type of connection you are looking for.

Example: We want all the url connections the malware made

  my @http = $m->returnConnectionsByLayer(
  	-type => 'url',
  	-layer => 7,
  	-protocol => 'http')
  );

Example: We want all the irc connections the malware made via dns

  my @irc_dns = $m->returnConnectionsByLayer(
  	-type => 'dns',
  	-layer => 7,
  	-protocol => 'IRC')
  );

Example: We want all the irc connections the malware made via direct IP

  my @irc_dip = $m->returnConnectionsByLayer(
  	-type => 'dip',
  	-layer => 7,
  	-protocol => 'IRC')
  );


All three params are required or it will return:

  ("Invalid parameters...",undef)

On success it returns an @rray of strings

=cut

sub returnConnectionsByLayer_array {
	my $self = shift;
	return unless($self->connections());
	my $parms = parse_parms({
		-parms => \@_,
		-required => [qw(-layer -protocol -type)],
	});
	return ("invalid parameters\n".Carp::longmess (Class::ParmList->error()),undef) unless(defined($parms));
	my ($l,$p,$t) = $parms->get('-layer','-protocol','-type');

	my @a = @{$self->connections()};
	my @return;
	foreach my $c (@a){
		my $proto = $c->protocols();
		next unless(defined($proto->{$l}->{"_$t"}) && (($proto->{$l}->protocol() eq uc($p))));
		push(@return,$proto->{$l}->{"_$t"});
	}
	return @return;
}

# ACCESSORS/MODIFIERS

=head1 ACCESSORS / MODIFIERS

=head2 filesize()

Sets and returns the filesize

=cut

sub filesize {
	my ($self,$v) = @_;
	$self->{_filesize} = $v if(defined($v));
	return $self->{_filesize};
}

=head2 filesizeUnits()

Sets and returns the filesizeUnits

=cut

sub filesizeUnits {
	my ($self,$v) = @_;
	$self->{_filesizeUnits} = $v if(defined($v));
	return $self->{_filesizeUnits};
}

=head2 filename()

Sets and returns the filename()

=cut

sub filename {
	my ($self,$v) = @_;
	$self->{_filename} = $v if(defined($v));
	return $self->{_filename};
}

=head2 classification()

Sets and returns the malware classification

=cut

sub classification {
	my ($self,$v) = @_;
	$self->{_classification} = $v if(defined($v));
	return $self->{_classification};
}

=head2 md5sum()

Sets and returns the md5sum

=cut

sub md5sum {
	my ($self,$v) = @_;
	$self->{_md5sum} = $v if(defined($v));
	return $self->{_md5sum};
}

=head2 dt_found()

Sets and returns the date found, return is a Time::Timestamp object

  $m->dt_found($timestamp,$timezone);

Timezone is optional, but the timing could get screwed up if you don't set it

=cut

sub dt_found {
	my ($self,$v,$tz) = @_;
	$self->{_dt_found} = Time::Timestamp->new(ts => $v, tz => $tz) if(defined($v));
	return $self->{_dt_found};
}

=head2 source()

Sets and returns the malware report source (where did you get it?)

=cut

sub source {
	my ($self,$v) = @_;
	$self->{_source} = $v if(defined($v));
	return $self->{_source};
}


=head2 sourceLoc()

Sets and returns the source location (what medium did you get it from (email, website, etc...)

=cut


sub sourceLoc {
	my ($self,$v) = @_;
	$self->{_sourceLoc} = $v if(defined($v));
	return $self->{_sourceLoc};
}

=head2 connections()

Sets and returns the connection behaviour of the malware.

  my $c = Net::Connection::Simple->new(...);
  $m->connections($c);

  my @cons = @{$m->connections()};

Accepts: Net::Connection::Simple or returns ($errstr,undef)

Returns: a ref to an array of Net::Connection::Simple objects

=cut

sub connections {
	my ($self,$v) = @_;
	if(defined($v)){
		return ('Net::Connection::Simple required',undef) unless(ref($v) eq 'Net::Connection::Simple');
		push(@{$self->{_connections}},$v);
	}
	return $self->{_connections};
}

=head2 backdoors()

Sets and returns the malwares backdoor behavior.

  my $p = Net::Protocol::Simple->new(...);
  $m->backdoors($p);

  my @bds = @{$m->backdoors};

Accepts: Net::Protocol::Simple or returns ($errstr,undef)

Returns: a ref to an array of Net::Protocol::Simple objects

=cut

sub backdoors {
	my ($self,$v) = @_;
	if(defined($v)){
		return ('expecting a Net::Protocol::Simple',undef) unless(ref($v) eq 'Net::Protocol::Simple');
		push(@{$self->{_backdoors}},$v);
	}
	return $self->{_backdoors};
}

=head2 processInfo()

Sets and returns the processInfo behavior.

Accepts: string

Returns: a ref to an array of strings

=cut

sub processInfo {
	my ($self,$v) = @_;
	push(@{$self->{_processInfo}},$v) if(defined($v));
	return $self->{_processInfo};
}

=head2 filesystem()

Sets and returns the filesystem behavior.

Accepts: string

Returns: a ref to an array of strings

=cut

sub filesystem {
	my ($self,$v) = @_;
	push(@{$self->{_filesystem}},$v) if(defined($v));
	return $self->{_filesystem};
}

=head2 rawReport()

Sets and returns the raw report string

Accepts: string

Returns: string

=cut

sub rawReport {
	my ($self,$v) = @_;
	$self->{_rawReport} = $v if(defined($v));
	return $self->{_rawReport};
}

=head2 malwareFiles()

Sets and returns a list of other files that are found to be created or associated with this malware

Accepts: HASHREF or returns ($errstr,undef)

  $m->malwareFiles({
  	$f1 => $md5,
  	$f2 => $virus_sig,
  });

OR

  $m->malwareFiles({
  	$f1->{md5} = $md5,
  	$f1->{vsig} = $virus_sig,
  	$f1->{snortSig} = $snort_sig,
  });

Returns: HASHREF

=cut

sub malwareFiles {
	my ($self,$v) = @_;
	if(defined($v)){
		return ('expecting a HASH',undef) unless(ref($v) eq 'HASH');
		push(@{$self->{_malwareFiles}},$v);
	}
	return $self->{_malwareFiles};
}

=head2 antiEmulation()

Sets and returns the antiEmulation behavior.

Accepts: int [undef|1|0]

Returns: whatever you put in

**Note: the blurb will translate [undef] as unknown

=cut

sub antiEmulation {
	my ($self,$v) = @_;
	$self->{_antiEmulation} = $v if(defined($v));
	return $self->{_antiEmulation};
}

=head2 securityIssues()

Sets and returns other security issues that are caused.

Accepts: string

Returns: a ref to an array of strings

=cut

sub securityIssues {
	my ($self,$v) = @_;
	push(@{$self->{_securityIssues}},$v) if(defined($v));
	return $self->{_securityIssues};
}
1;
__END__

=head1 SEE ALSO

Time::Timestamp,Net::Connection::Simple,Net::Protocol::Simple

=head1 AUTHOR

Wes Young, E<lt>saxguard9-cpan@yahoo.comE<gt>

=head1 COPYRIGHT AND LICENSE

Copyright (C) 2006 by Wes Young

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself, either Perl version 5.8.7 or,
at your option, any later version of Perl 5 you may have available.


=cut
