Files
atomic-red-team-gs/bin/generate-atomic-docs.rb
T

195 lines
7.1 KiB
Ruby
Raw Normal View History

2018-05-10 13:26:34 -06:00
#! /usr/bin/env ruby
$LOAD_PATH << "#{File.dirname(File.dirname(__FILE__))}/atomic_red_team" unless $LOAD_PATH.include? "#{File.dirname(File.dirname(__FILE__))}/atomic_red_team"
2018-05-10 13:26:34 -06:00
require 'erb'
2018-05-11 07:29:16 +02:00
require 'fileutils'
require 'json'
require 'atomic_red_team'
2018-05-11 05:38:02 +02:00
class AtomicRedTeamDocs
ATTACK_API = Attack.new
ATOMIC_RED_TEAM = AtomicRedTeam.new
ATOMIC_RED_TEAM_DIR = "#{File.dirname(File.dirname(__FILE__))}/atomic_red_team"
2018-05-11 05:38:02 +02:00
2018-05-11 05:59:03 +02:00
#
# Generates all the documentation used by Atomic Red Team
#
2018-05-11 05:38:02 +02:00
def generate_all_the_docs!
oks = []
fails = []
ATOMIC_RED_TEAM.atomic_tests.each do |atomic_yaml|
begin
print "Generating docs for #{atomic_yaml['atomic_yaml_path']}"
2018-05-11 05:59:03 +02:00
generate_technique_docs! atomic_yaml, atomic_yaml['atomic_yaml_path'].gsub(/.yaml/, '.md')
2018-05-11 07:29:16 +02:00
# generate_technique_execution_docs! atomic_yaml, "#{File.dirname(File.dirname(__FILE__))}/atomic-red-team-execution/#{atomic_yaml['attack_technique'].downcase}.html"
2018-05-11 05:38:02 +02:00
oks << atomic_yaml['atomic_yaml_path']
puts "OK"
rescue => ex
fails << atomic_yaml['atomic_yaml_path']
puts "FAIL\n#{ex}\n#{ex.backtrace.join("\n")}"
2018-05-11 04:09:32 +02:00
end
end
puts
puts "Generated docs for #{oks.count} techniques, #{fails.count} failures"
generate_attack_matrix! 'All', "#{File.dirname(File.dirname(__FILE__))}/atomics/matrix.md"
generate_attack_matrix! 'Windows', "#{File.dirname(File.dirname(__FILE__))}/atomics/windows-matrix.md", only_platform: /windows/
generate_attack_matrix! 'macOS', "#{File.dirname(File.dirname(__FILE__))}/atomics/macos-matrix.md", only_platform: /macos/
generate_attack_matrix! 'Linux', "#{File.dirname(File.dirname(__FILE__))}/atomics/linux-matrix.md", only_platform: /^(?!windows|macos).*$/
generate_index! 'All', "#{File.dirname(File.dirname(__FILE__))}/atomics/index.md"
generate_index! 'Windows', "#{File.dirname(File.dirname(__FILE__))}/atomics/windows-index.md", only_platform: /windows/
generate_index! 'macOS', "#{File.dirname(File.dirname(__FILE__))}/atomics/macos-index.md", only_platform: /macos/
generate_index! 'Linux', "#{File.dirname(File.dirname(__FILE__))}/atomics/linux-index.md", only_platform: /^(?!windows|macos).*$/
generate_yaml_index! "#{File.dirname(File.dirname(__FILE__))}/atomics/index.yaml"
generate_navigator_layer! "#{File.dirname(File.dirname(__FILE__))}/atomics/art_navigator_layer.json"
2018-05-11 05:38:02 +02:00
return oks, fails
2018-05-10 14:10:12 -06:00
end
2018-05-10 13:26:34 -06:00
2018-05-11 05:59:03 +02:00
#
# Generates Markdown documentation for a specific technique from its YAML source
#
def generate_technique_docs!(atomic_yaml, output_doc_path)
2018-05-11 05:38:02 +02:00
technique = ATTACK_API.technique_info(atomic_yaml.fetch('attack_technique'))
technique['identifier'] = atomic_yaml.fetch('attack_technique').upcase
2018-05-10 13:26:34 -06:00
template = ERB.new File.read("#{ATOMIC_RED_TEAM_DIR}/atomic_doc_template.md.erb"), nil, "-"
2018-05-11 05:38:02 +02:00
generated_doc = template.result(binding)
2018-05-10 13:26:34 -06:00
2018-05-11 05:38:02 +02:00
print " => #{output_doc_path} => "
File.write output_doc_path, generated_doc
2018-05-11 04:09:32 +02:00
end
2018-05-11 07:29:16 +02:00
#
# Generates Markdown documentation for a specific technique from its YAML source
#
def generate_technique_execution_docs!(atomic_yaml, output_doc_path)
FileUtils.mkdir_p File.dirname(output_doc_path)
technique = ATTACK_API.technique_info(atomic_yaml.fetch('attack_technique'))
technique['identifier'] = atomic_yaml.fetch('attack_technique').upcase
template = ERB.new File.read("#{ATOMIC_RED_TEAM_DIR}/atomic_execution_template.html.erb"), nil, "-"
2018-05-11 07:29:16 +02:00
generated_doc = template.result(binding)
print " => #{output_doc_path} => "
File.write output_doc_path, generated_doc
end
2018-05-11 05:59:03 +02:00
#
# Generates a Markdown ATT&CK documentation matrix for all techniques
#
def generate_attack_matrix!(title_prefix, output_doc_path, only_platform: /.*/)
result = ''
result += "# #{title_prefix} Atomic Tests by ATT&CK Tactic & Technique\n"
result += "| #{ATTACK_API.ordered_tactics.join(' | ')} |\n"
2018-05-11 05:38:02 +02:00
result += "|#{'-----|' * ATTACK_API.ordered_tactics.count}\n"
2018-05-11 04:09:32 +02:00
ATTACK_API.ordered_tactic_to_technique_matrix(only_platform: only_platform).each do |row_of_techniques|
row_values = row_of_techniques.collect do |technique|
2018-05-11 05:38:02 +02:00
if technique
ATOMIC_RED_TEAM.github_link_to_technique(technique, include_identifier: false, link_new_to_contrib: false)
2018-05-11 05:38:02 +02:00
end
end
result += "| #{row_values.join(' | ')} |\n"
end
2018-05-11 05:59:03 +02:00
File.write output_doc_path, result
puts "Generated ATT&CK matrix at #{output_doc_path}"
2018-05-11 04:09:32 +02:00
end
2018-05-11 05:59:03 +02:00
#
# Generates a master Markdown index of ATT&CK Tactic -> Technique -> Atomic Tests
#
def generate_index!(title_prefix, output_doc_path, only_platform: /.*/)
2018-05-11 05:38:02 +02:00
result = ''
result += "# #{title_prefix} Atomic Tests by ATT&CK Tactic & Technique\n"
2018-05-11 04:09:32 +02:00
ATTACK_API.techniques_by_tactic(only_platform: only_platform).each do |tactic, techniques|
2018-05-11 05:38:02 +02:00
result += "# #{tactic}\n"
techniques.each do |technique|
result += "- #{ATOMIC_RED_TEAM.github_link_to_technique(technique, include_identifier: true, link_new_to_contrib: true)}\n"
2018-05-11 05:38:02 +02:00
ATOMIC_RED_TEAM.atomic_tests_for_technique(technique).each_with_index do |atomic_test, i|
next unless atomic_test['supported_platforms'].any? {|platform| platform.downcase =~ only_platform}
result += " - Atomic Test ##{i+1}: #{atomic_test['name']} [#{atomic_test['supported_platforms'].join(', ')}]\n"
2018-05-11 05:38:02 +02:00
end
end
result += "\n"
end
2018-05-11 04:09:32 +02:00
2018-05-11 05:59:03 +02:00
File.write output_doc_path, result
puts "Generated Atomic Red Team index at #{output_doc_path}"
2018-05-10 13:26:34 -06:00
end
#
# Generates a master YAML index of ATT&CK Tactic -> Technique -> Atomic Tests
#
def generate_yaml_index!(output_doc_path)
result = {}
ATTACK_API.techniques_by_tactic.each do |tactic, techniques|
result[tactic] = techniques.collect do |technique|
[
technique['identifier'],
{
'technique' => technique,
'atomic_tests' => ATOMIC_RED_TEAM.atomic_tests_for_technique(technique)
}
]
end.to_h
end
File.write output_doc_path, JSON.parse(result.to_json).to_yaml # shenanigans to eliminate YAML aliases
puts "Generated Atomic Red Team YAML index at #{output_doc_path}"
end
#
# Generates a MITRE ATT&CK Navigator Layer based on contributed techniques
#
def generate_navigator_layer!(output_layer_path)
techniques = []
ATOMIC_RED_TEAM.atomic_tests.each do |atomic_yaml|
begin
technique = {
"techniqueID" => atomic_yaml['attack_technique'],
"score" => 100,
"enabled" => true
}
techniques.push(technique)
end
layer = {
"version" => "2.1",
"name" => "Atomic Red Team",
"description" => "Atomic Red Team MITRE ATT&CK Navigator Layer",
"domain" => "mitre-enterprise",
"gradient" => {
"colors" => ["#ce232e","#ce232e"],
"minValue" => 0,
"maxValue" => 100
},
"techniques" => techniques
}
File.write output_layer_path,layer.to_json
end
puts "Generated Atomic Red Team ATT&CK Navigator Layer at #{output_layer_path}"
end
2018-05-10 13:26:34 -06:00
end
2018-05-11 05:59:03 +02:00
#
# MAIN
#
2018-05-11 05:38:02 +02:00
oks, fails = AtomicRedTeamDocs.new.generate_all_the_docs!
2018-05-10 13:26:34 -06:00
exit fails.count