#
# Cobalt Strike 3.0's Default Reports. *pHEAR*
#

# report descriptions, please!
describe("Activity Report", "This report shows a timeline of this engagement.");
describe("Hosts Report", "This report shows host information gathered during this engagement.");
describe("Social Engineering Report", "This report documents the social engineering portion of this engagement.");
describe("Indicators of Compromise", "This report documents Indicators of Compromise generated by Cobalt Strike during this engagement.");
describe("Sessions Report", "This report documents activity on a session-by-session basis.");
describe("Tactics, Techniques, and Procedures", "This report maps Cobalt Strike's actions to MITRE's Adversarial Tactics, Techniques, and Common Knowledge (ATT&CK) matrix.");

# define the hosts report
report "Hosts Report" {
	# Arguments:
	# $1 = a dictionary with document metadata
	# $2 = a dictionary with data structures aggregated 
	#      from your Cobalt Strike clients.

	# setup our document bookmarks [for the PDF output]
	local('$target');
	bookmark("Summary");
	foreach $target (agTargets($3)) {
		bookmark($target['address']);
	}

	# the first page is the cover page of our report.
	page "first" {
		# title heading
		h1($1['long']);

		# today's date/time in an italicized format
		ts();

		# a paragraph
		p($1['description']);
		
		# sub-heading
		h2("Summary");

		# key/value table
		kvtable(ohash(
			Hosts           => size(agTargets($3)),
			Services        => size(agServices($3)),
			Sessions        => size(agSessions($3))
		));
	}

	# this is the rest of the report
	page "rest" {
		local('$target @services @creds @sessions');
		foreach $target (agTargets($3)) {
			# figure out which sessions apply to our current target
			@sessions = agSessionsForHost($3, $target['address']);

			nobreak(lambda({
				local('%info');
				# display heading for this host
				h2_img(host_image($target['os'], $target['version'], iff(size(@sessions) > 0)), $target['address']);

				# summarize known info about this host
				%info = ohash();
				%info['Operating System'] = $target['os'] . " " . $target['version'];
				%info['Name']             = $target['name'] . "";
				%info['Note']             = $target['note'] . "";
				kvtable(%info);
			}, \$target, \@sessions));

			# let's do some services magic
			@services = agServicesForHost($3, $target['address']);
			if (size(@services) > 0) {
				h3("Services");
				table(@("port", "banner"), @("1.75in", "auto"), @services);
			}

			# let's do some creds magic next.
			@creds = agCredentialsForHost($3, $target['address']);
			if (size(@creds) > 0) {
				h3("Credentials");
				table(@("user", "realm", "password"), @("1.75in", "1.75in", "auto"), @creds);
			}

			# sessions next...
			if (size(@sessions) > 0) {
				h3("Sessions");
				map({ $1['opened'] = formatTime($1['opened']); }, @sessions);
				table(@("user", "process", "pid", "opened"), @("1.75in", "1.75in", "1in", "auto"), @sessions);
			}

			# add line break!
			br();
		}
	}
}

# define the social engineering report
report "Social Engineering Report" {
	local('%campaigns @phishes @emails @tokens %appinfo');

	# the first page is the cover page of our report.
	page "first" {
		# title heading
		h1($1['long']);

		# today's date/time in an italicized format
		ts();

		# a paragraph
		p($1['description']);
		
		# sub-heading
		h2("Summary");

		# key/value table
		local('%info');
		%info = ohash();
		if (size(agCampaigns($3)) == 0) {
			%info["Total Campaigns"]      = "none";
			%info["Emails Sent"]          = "none";
			%info["Phished Users"]        = "none";
			%info["Click-through Rate"]   = "0%";
			%info["Total Clicks"]         = "n/a";
		}
		else {
			local('$ctr');
			$ctr = double( size(agCountWebHitsByToken($3)) ) / size(agTokens($3));

			%info["Total Campaigns"]      = size(agCampaigns($3));
			%info["Emails Sent"]          = size(agSentEmails($3));
			%info["Phished Users"]        = size(agEmailAddresses($3));
			%info["Click-through Rate"]   = int ( $ctr * 100 ) . '%';
			%info["Total Clicks"]         = size(agWebHitsWithTokens($3));
		}

		kvtable(%info);
	}

	page "rest" {
		if (size(agCampaigns($3)) == 0) {
			p("No campaigns were run.");
		}
		else {
			h1("Campaigns");
			bookmark("Campaigns");

			local('$index $campaign $phish');
			foreach $index => $campaign ( values(agCampaigns($3)) ) {
				h2("Campaign " . ($index + 1));

				kvtable(ohash(
					Subject     => $campaign['subject'],
					URL         => $campaign['url'],
					Attachment  => $campaign['attachment']
				));

				@specific = agSentEmailsForCampaign($3, $campaign['cid']);
				foreach $phish (@specific) {
					$phish['To']     = $phish['email'];
					$phish['Date']   = formatTime($phish['when']); 
					$phish['Visits'] = size(agWebHitsForToken($3, $phish['token']));
				}

				table(@("To", "Date", "Visits"), @("auto", "2in", "1in"), @specific);
			}
		}
	}

	page "rest" {
		if (size(agEmailAddresses($3)) == 0) {
			p("No user information to report.");
			return;
		}
		else {
			h1("Users");
			bookmark("Users");

			local('$email $apps @hits');
			foreach $email (agEmailAddresses($3)) {
				if (size(agWebHitsForEmail($3, $email)) == 0) {
					continue;
				}

				$apps = agApplicationsForEmailAddress($3, $email);
				if (size($apps) > 0) {
					h2_img(host_image($apps[0]['os'], $apps[0]['osver'], false), $email);
					bookmark("Users", $email);
					kvtable(%(
						External  => $apps[0]['external'],
						Internal  => $apps[0]['internal'],
						Campaigns => size(agSentEmailsForEmailAddress($3, $email))
					));
				}
				else {
					h2_img(host_image("unknown", 0.0, false), $email);
					bookmark("Users", $email);
					kvtable(%(
						External  => "unknown",
						Internal  => "unknown",
						Campaigns => size(agSentEmailsForEmailAddress($3, $email))
					));
				}

				if (size($apps) > 0) {
					h3("Applications");
					table(@("application", "version"), @("auto", "3in"), $apps);
				}

				# update each row, somewhat.
				@hits = agWebHitsForEmail($3, $email);
				map({ 
					$1['date']     = formatTime($1['when']); 
					$1['activity'] = $1['data'];
				}, @hits);

				if (size(@hits) > 0) {
					h3("Visits");
					table(@("date", "activity"), @("1.5in", "auto"), @hits);
				}
			}
		}
	}
}

report "Sessions Report" {
	# the first page is the cover page of our report.
	page "first" {
		# title heading
		h1($1['long']);

		# today's date/time in an italicized format
		ts();

		# a paragraph
		p($1['description']);
		
		# sub-heading
		h2("Summary");
		bookmark("Summary");

		# key/value table
		kvtable(ohash(
			Commands        => size(agInputs($3)),
			Indicators      => size(agIndicators($3)),
			Sessions        => size(agSessions($3))
		));
	}

	# this is the rest of the report
	page "rest" {
		local('$session $target %info');
		foreach $target (sort({ return $1['name'] cmp $2['name']; }, agTargets($3))) {
			if (size(agSessionsForHost($3, $target['address'])) == 0) {
				continue;
			}

			foreach $session (agSessionsForHost($3, $target['address'])) {
				local('$host $user $pid');
				$host = iff($target['name'], $target['name'], $session['internal']);
				$user = $session['user'];
				$pid  = $session['pid'];

				# bookmark this session
				bookmark($host, "$user @ $pid");

				# we're in a high integrity context
				if (charAt($session['user'], -1) eq '*') {
					h2_img(host_image($target['os'], $target['version'], true), $host, "$user @ $pid");
				}
				# we're not in a high integrity context
				else {
					h2_img(host_image($target['os'], $target['version'], false), $host, "$user @ $pid");
				}
				%info = ohash();
				%info["User"]    = $session['user'];
				%info["Process"] = $session['process'];
				%info["PID"]     = $session['pid'];
				%info["Opened"]  = formatTime($session['opened']);
				kvtable(%info);

				h3("Communication Path");

				table(@("hosts", "port", "protocol"), @("auto", "1in", "1in"), agCommunicationPathForSession($3, $session['id']));

				if (size(agFileIndicatorsForSession($3, $session['id'])) > 0) {
					h3("File Hashes");
					table(@("date", "hash", "name"), @("1.25in", "3in", "auto"), map({
						$1['date'] = formatTime($1['when']);
						return $1;
					}, agFileIndicatorsForSession($3, $session['id'])));
				}

				
				if (size(agOtherIndicatorsForSession($3, $session['id'])) > 0) {
					h3("Other Indicators");
					table(@("date", "type", "target", "name"), @("1.25in", "1in", "2in", "auto"), map({
						$1['date'] = formatTime($1['when']);
						return $1;
					}, agOtherIndicatorsForSession($3, $session['id'])));
				}

				if (size(agTasksAndCheckinsForSession($3, $session['id'])) > 0) {
					h3("Activity");
					table(@("date", "activity"), @("1.25in", "auto"), map({
						$1['date']     = formatTime($1['when']);
						$1['activity'] = $1['data'];
						return $1;
					}, agTasksAndCheckinsForSession($3, $session['id'])));
				}
			}

			# add two line breaks!
			br();
		}
	}
}

report "Activity Report" {
	# change the orientation of this document to landscape
	landscape();

	# the first page is the cover page of our report.
	page "first-center" {
		# title heading
		h1($1['long']);

		# today's date/time in an italicized format
		ts();

		# a paragraph
		p($1['description']);
	}

	# this is the rest of the report
	page "rest" {
		# walk through all of the archived events and put them into a table. 
		table(@("date", "host", "user", "pid", "activity"), @("1.25in", "1.25in", "1.25in", "0.75in", "auto"), filter(lambda({
			local('$bid $session %temp');
			$bid = $1['bid'];

			# create a new row.
			%temp = %();
			%temp['date'] = formatTime($1['when']);

			# add session info (if it exists) to the row.
			if ($bid ne "" && $bid in %sessions) {
				$session = %sessions[$bid];
				%temp['host']    = iff($session['computer'], $session['computer'], $session['internal']);
				%temp['user']    = $session['user'];
				%temp['pid']     = $session['pid'];
				%temp['process'] = $session['process'];
			}

			# let's modify the presentation of some of this information
			if ($1['type'] eq "sendmail_start") {
				%temp['activity'] = "Started phishing campaign: " . $1['subject'];
			}
			else if ($1['type'] eq "sendmail_post") {
				%temp['activity'] = "Email to " . agTokenToEmail($aggr, $1['token']) . ": " . $1['status'];
			}
			else if ($1['type'] eq "webhit") {
				if ('token' in $1) {
					%temp['activity'] = $1['data'] . ' (' . agTokenToEmail($aggr, $1['token']) . ')';
				}
				else {
					%temp['activity'] = $1['data'];
				}
			}
			else if ($1['type'] eq "beacon_initial") {
				if (%temp['process'] ne "") {
					%temp['activity'] = "[" . %temp['process'] . "] initial beacon";
				}
				else {
					%temp['activity'] = "initial beacon";
				}
			}
			else {
				%temp['activity'] = $1['data'];
			}

			# exclude indicators from this report
			if ($1['type'] ne "input" && $1['type'] ne "indicator") {
				return %temp;
			}
		}, %sessions => agSessionsById($3), $aggr => $3), agArchives($3)));
	}
}

# capture indicators of compromise
report "Indicators of Compromise" {
	# the first page is the cover page of our report.
	page "first" {
		# title heading
		h1($1['long']);

		# today's date/time in an italicized format
		ts();

		# a paragraph
		p($1['description']);
		
		# sub-heading
		h2("Summary");
		bookmark("Summary");

		local('%info');
		%info = ohash();
		%info["Hashes"]          = size(agFileIndicators($3));
		%info["Domains"]         = size(agC2Domains($3));
		%info["Payloads"]        = size(agC2Samples($3));

		# key/value table
		kvtable(%info);
	}

	# show our payload samples
	local('$count $sample $string');
	foreach $count => $sample (agC2Samples($3)) {
		page "rest" {
			h1($sample['name'], $sample['name']);
			bookmark($sample['name']);

			p("This payload was observed in conjunction with this actor's activities.");

			h2("Portable Executable Information");
			kvtable(agPEForSample($sample));

			# add some simulated analyst notes.
			if (agPENotesForSample($sample)) {
				p(agPENotesForSample($sample));
			}

			if (size($sample['callbacks']) > 0) {
				h2("Contacted Hosts");
				table(@("Host", "Port", "Protocols"), @("auto", "1in", "2in"), agC2ForSample($sample));
			}

			h2("HTTP Traffic");
			output(lambda({
				local('$client $server');
				($client, $server) = values($sample, @("client", "server"));

				color($client, "DarkRed");
				color($server, "DarkBlue");
			}, \$sample));

			# show our strings only if there are some to show
			if ($sample['strings'] ne "") {
				h2("Extracted Strings");
				foreach $string (split("\n", $sample['strings'])) {
					p($string);
				}
			}
		}
	}

	if (size(agFileIndicators($3)) > 0) {
		page "rest" {
			h1("File Hashes");
			p("The following file hashes were observed in conjunction with this actor's activities.");
			br();
			bookmark("File Hashes");

			table(@("MD5 Hash", "File Size"), @("5in", "auto"), map({
				$1['MD5 Hash']  = $1['hash'];
				$1['File Size'] = $1['size'];
				return $1;
			}, sort({ return $1['hash'] cmp $2['hash']; }, agFileIndicators($3))));
		}
	}

	if (size(agC2Domains($3)) > 0) {
		page "rest" {
			h1("Domains and IP Addresses", "Domains");
			p("The following domains and IP addresses were attributed to this actor.");
			br();
			bookmark("Domains");

			foreach $host (agC2Domains($3)) {
				p($host);
			}
		}
	}

	if (size(agTacticsUsed($3)) > 0) {
		page "rest" {
			h1("MITRE ATT&CK\u2122 Techniques", "Tactics");
			p("The following tactics and techniques were used by this actor.");
			br();
			bookmark("Tactics");

			local('$tactic @tactics');
			@tactics = sort({ return attack_name($1) cmp attack_name($2); }, agTacticsUsed($3));

			foreach $tactic (@tactics) {
				p(attack_name($tactic) . " ( $+ $tactic $+ )");
			}
		}
	}
}

# define the TTPs report
report "Tactics, Techniques, and Procedures" {
	local('%sessions @uses @tactics $tactic');

	# the first page is the cover page of our report.
	page "first" {
		# title heading
		h1($1['long']);
	
		# today's date/time in an italicized format
		ts();
	
		# a paragraph [could be the default...
		p($1['description']);
	}

	# grab all of our session info (do this once)
	%sessions = agSessionsById($3);

	# sort our tactics in alpha-order, by name
	@tactics = sort({ return lc(attack_name($1)) cmp lc(attack_name($2)); }, attack_tactics());

	# loop through each tactic
	foreach $tactic (@tactics) {
		# grab archived messages associated with each tactic
		@uses = agArchivesByTactic($3, $tactic);

		# if there's more than one message, present it on a new page
		if (size(@uses) > 0) {
			page "rest" {
				print_tactic($tactic, @uses, %sessions);
			}
		}
	}

	# tack on MITRE's license and disclaimer in compliance with:
	# https://attack.mitre.org/wiki/enterprise:Terms_of_Use
	page "rest" {
		bookmark("License");
		h1("LICENSE", "License");

		p("The MITRE Corporation (MITRE) hereby grants you a non-exclusive, royalty-free license to use Adversarial Tactics, Techniques and Common Knowledge (ATT&CK\u2122) for research, development, and commercial purposes. Any copy you make for such purposes is authorized provided that you reproduce MITRE's copyright designation and this license in any such copy.");

		br();

		p("\"(c) 2017 The MITRE Corporation. This work is reproduced and distributed with the permission of The MITRE Corporation.\"");

		h2("DISCLAIMERS");

		p("MITRE does not claim ATT&CK enumerates all possibilities for the types of actions and behaviors documented as part of its adversary model and framework of techniques. Using the information contained within ATT&CK to address or cover full categories of techniques will not guarantee full defensive coverage as there may be undisclosed techniques or variations on existing techniques not documented by ATT&CK.");

		br();

		p("ALL DOCUMENTS AND THE INFORMATION CONTAINED THEREIN ARE PROVIDED ON AN \"AS IS\" BASIS AND THE CONTRIBUTOR, THE ORGANIZATION HE/SHE REPRESENTS OR IS SPONSORED BY (IF ANY), THE MITRE CORPORATION, ITS BOARD OF TRUSTEES, OFFICERS, AGENTS, AND EMPLOYEES, DISCLAIM ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION THEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.");
	}
}

sub print_tactic {
	local('$tactic @uses %sessions $name $desc $mitigate $detect @content');
	($tactic, @uses, %sessions) = @_;

	# heading for this tactic
	$name     = attack_name($tactic);
	$bookmark = "$name ( $+ $tactic $+ )";

	h2($name, $bookmark);
	bookmark($bookmark);

	# description
	p_formatted(attack_describe($tactic));

	# print all events from Cobalt Strike related to this tactic
	h3("Related Events");

	# format our uses into an array. Each row is a dictionary mapping columns to content
	@content = map(lambda({
		local('$bid $session %temp');
		$bid = $1['bid'];

		# create a new row.
		%temp = %();
		%temp['date'] = formatTime($1['when']);

		# add session info (if it exists) to the row.
		if ($bid ne "" && $bid in %sessions) {
			$session      = %sessions[$bid];
			%temp['host'] = iff($session['computer'] ne "", $session['computer'], $session['internal']);
			%temp['pid']  = $session['pid'];
		}
		
		%temp['activity'] = $1['data'];

		return %temp;
	}, \%sessions), @uses);

	# present our related events in a table
	table(@("date", "host", "pid", "activity"), @("1.25in", "1.25in", "0.75in", "auto"), @content); 

	# mitigation steps
	if (attack_mitigate($tactic) ne "") {
		h3("Mitigation");
		p_formatted(attack_mitigate($tactic));
	}

	# detect steps
	if (attack_detect($tactic) ne "") {
		h3("Detection Methods");
		p_formatted(attack_detect($tactic));
	}

	# token references
	h3("Reference");

	link("Tactic: $tactic", attack_url($tactic));
}
