diff --git a/.Rbuildignore b/.Rbuildignore index eaf2f47..4bbc677 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -14,3 +14,4 @@ ^notes$ ^\.gitlab-ci\.yml$ ^data-raw$ +^tools$ \ No newline at end of file diff --git a/DESCRIPTION b/DESCRIPTION index 1942d77..7e5490a 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,26 +1,37 @@ Package: attckr Type: Package -Title: attckr title goes here otherwise CRAN checks fail +Title: Analyze Adversary Tactics and Techniques Using the MITRE ATT&CK CTI Corpus Version: 0.1.0 -Date: 2019-05-28 +Date: 2019-07-28 Authors@R: c( person("Bob", "Rudis", email = "bob@rud.is", role = c("aut", "cre"), comment = c(ORCID = "0000-0001-5670-2640")) ) Maintainer: Bob Rudis -Description: A good description goes here otherwise CRAN checks fail. +Description: MITRE ATT&CK is a globally-accessible knowledge base of + adversary tactics and techniques based on real-world observations. + The ATT&CK knowledge base is used as a foundation for the development + of specific threat models and methodologies in the private sector, + in government, and in the cybersecurity product and service community. + Tools are provided to analyze adversary tactics and techniques, + build incident metrics, and identify high level program gaps + using the MITRE ATT&CK CTI Corpus. URL: https://gitlab.com/hrbrmstr/attckr BugReports: https://gitlab.com/hrbrmstr/attckr/issues Encoding: UTF-8 -License: AGPL +License: Apache License 2.0 | file LICENSE +LazyData: true Suggests: testthat, covr Depends: R (>= 3.2.0) Imports: - httr, jsonlite, - tibble + tibble, + ggplot2, + shiny, + stringi, + rmarkdown Roxygen: list(markdown = TRUE) RoxygenNote: 6.1.1 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..df0bb94 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/NAMESPACE b/NAMESPACE index 7982dc0..978c6eb 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,5 +1,12 @@ # Generated by roxygen2: do not edit by hand -import(httr) +export(fct_tactic) +export(validate_tactics) +export(validate_technique_ids) +export(validate_techniques) +import(ggplot2) +import(rmarkdown) +import(shiny) +import(stringi) import(tibble) importFrom(jsonlite,fromJSON) diff --git a/R/attckr-package.R b/R/attckr-package.R index 05846b6..8a5f16e 100644 --- a/R/attckr-package.R +++ b/R/attckr-package.R @@ -1,13 +1,18 @@ -#' ... +#' Analyze Adversary Tactics and Techniques Using the MITRE ATT&CK CTI Corpus #' -#' - URL: -#' - BugReports: +#' MITRE ATT&CK™ is a globally-accessible knowledge base of +#' adversary tactics and techniques based on real-world observations. +#' The ATT&CK knowledge base is used as a foundation for the development +#' of specific threat models and methodologies in the private sector, +#' in government, and in the cybersecurity product and service community. +#' Tools are provided to analyze adversary tactics and techniques, +#' build incident metrics, and identify high level program gaps +#' using the MITRE ATT&CK CTI Corpus. #' #' @md #' @name attckr #' @docType package #' @author Bob Rudis (bob@@rud.is) -#' @import httr -#' @import tibble +#' @import tibble ggplot2 shiny rmarkdown stringi #' @importFrom jsonlite fromJSON -NULL +"_PACKAGE" \ No newline at end of file diff --git a/R/data-docs.R b/R/data-docs.R new file mode 100644 index 0000000..796393c --- /dev/null +++ b/R/data-docs.R @@ -0,0 +1,19 @@ +#' @title Enterprise Attack Taxonomy +#' @name enterprise_attack +#' @docType data +NULL + +#' @title Mobile Attack Taxonomy +#' @name mobile_attack +#' @docType data +NULL + +#' @title Pre-Attack Taxonomy +#' @name pre_attack +#' @docType data +NULL + +#' @title Combined ATT&CK Matricies Tactics, Techniques and Technique detail +#' @name tidy_attack +#' @docType data +NULL diff --git a/R/fct-tactic.R b/R/fct-tactic.R new file mode 100644 index 0000000..12c846c --- /dev/null +++ b/R/fct-tactic.R @@ -0,0 +1,52 @@ +.tactics_f <- list() + +.tactics_f[["mitre-attack"]] <- list( + id = c( + "initial-access", "execution", "persistence", "privilege-escalation", + "defense-evasion", "credential-access", "discovery", "lateral-movement", + "collection", "command-and-control", "exfiltration", "impact" + ), + pretty = c( + "Initial Access", "Execution", "Persistence", "Privilege Escalation", + "Defense Evasion", "Credential Access", "Discovery", "Lateral Movement", + "Collection", "Command And Control", "Exfiltration", "Impact" + ), + nl = c( + "Initial\nAccess", "Execution", "Persistence", "Privilege\nEscalation", + "Defense\nEvasion", "Credential\nAccess", "Discovery", "Lateral\nMovement", + "Collection", "Command\nAnd\nControl", "Exfiltration", "Impact" + ) +) + +#' Make an ordered Tactics factor with optional better labelling +#' +#' +#' @param tactics a character vector +#' @param input what is in `tactics`? +#' @param output what do you want the factor label to be? +#' @param matrix which matrix? +#' @export +fct_tactic <- function(tactics, + input = c("id", "pretty", "nl"), + output = c("pretty", "nl", "id"), + matrix = c("enterprise", "mobile", "pre")) { + + input <- match.arg(input[1], c("id", "pretty", "nl")) + output <- match.arg(output[1], c("id", "pretty", "nl")) + matrix <- match.arg(matrix[1], c("enterprise", "mobile", "pre")) + + switch( + matrix, + enterprise = "mitre-attack", + mobile = "mitre-mobile-attack", + pre = "mitre-pre-attack" + ) -> tax + + input <- .tactics_f[[tax]][[input]] + output <- .tactics_f[[tax]][[output]] + + factor(x = tactics, levels = input, labels = output, ordered = TRUE) + +} + + diff --git a/R/read-events.R b/R/read-events.R new file mode 100644 index 0000000..1b25bbb --- /dev/null +++ b/R/read-events.R @@ -0,0 +1,6 @@ +#' Read in ATT&CK events from a file +read_events <- function(path) { + +} + + diff --git a/R/utils.R b/R/utils.R new file mode 100644 index 0000000..3f75914 --- /dev/null +++ b/R/utils.R @@ -0,0 +1 @@ +normalize_identifier <- function(x) { gsub("[[:space:]]+", "-", trimws(tolower(x))) } \ No newline at end of file diff --git a/R/validate-tactics.R b/R/validate-tactics.R new file mode 100644 index 0000000..757a094 --- /dev/null +++ b/R/validate-tactics.R @@ -0,0 +1,56 @@ +#' Validate Tactics strings against MITRE authoritative source +#' +#' @param tactics a character vector of tactic strings to validate. This will be +#' converted to lower-case, left/right spaces will be trimmed and +#' internal spaces will be converted to a single `-` +#' @param matrix which matrix to use when validating? +#' @param na_rm remove NA's before comparing? +#' @return `TRUE` if all tactics validate, otherwise `FALSE` with messages +#' identifying the invalid tactics. +#' @export +#' @examples +#' validate_tactics("persistence") +#' validate_tactics(c("persistence", "Persistence", "Persistance")) +validate_tactics <- function(tactics, matrix = c("enterprise", "mobile", "pre"), + na_rm = TRUE) { + + matrix <- match.arg(matrix[1], c("enterprise", "mobile", "pre")) + + switch( + matrix, + enterprise = "mitre-attack", + mobile = "mitre-mobile-attack", + pre = "mitre-pre-attack" + ) -> tax + + tax <- unique(tidy_attack[tidy_attack$matrix == tax, "tactic", drop=TRUE]) + + if (na_rm) { + no_na <- na.exclude(tactics) + where_nas <- attr(no_na, "na.action", exact = TRUE) + if (length(where_nas)) message("Removed ", length(where_nas), " NA values.\n") + tac <- as.character(no_na) + } + + o_tac <- tactics + + tac <- normalize_identifier(tactics) + + bad <- o_tac[which(!(tac %in% tax))] + + if (length(bad)) { + warning( + "Tactics not in the ", matrix, " MITRE ATT&CK matrix found\n", + paste0(sprintf('- "%s"', sort(unique(bad))), collapse = "\n"), + call. = FALSE + ) + invisible(sort(unique(bad))) + } else { + message( + "All tactics were found in the ", matrix, " MITRE ATT&CK matrix" + ) + invisible(TRUE) + } + +} + diff --git a/R/validate-tech-ids.R b/R/validate-tech-ids.R new file mode 100644 index 0000000..54e53c9 --- /dev/null +++ b/R/validate-tech-ids.R @@ -0,0 +1,47 @@ +#' Validate Technique IDs +#' +#' @param technique_ids a character vector of technique ids to validate +#' @param matrix which matrix to validate against (not all IDs are associated +#' with techniques in every matrix) +#' @param na_rm remove NA's before comparing? +#' @export +validate_technique_ids <- function(technique_ids, + matrix = c("enterprise", "mobile", "pre"), + na_rm = TRUE) { + + matrix <- match.arg(matrix[1], c("enterprise", "mobile", "pre")) + + switch( + matrix, + enterprise = "mitre-attack", + mobile = "mitre-mobile-attack", + pre = "mitre-pre-attack" + ) -> tax + + if (na_rm) { + no_na <- na.exclude(technique_ids) + where_nas <- attr(no_na, "na.action", exact = TRUE) + if (length(where_nas)) message("Removed ", length(where_nas), " NA values.\n") + tec <- as.character(no_na) + } + + mtec <- unique(tidy_attack[tidy_attack$matrix == tax, "id", drop=TRUE]) + + bad <- unique(tec[which(!(tec %in% mtec))]) + + if (length(bad)) { + warning( + "Technique ids not in the ", matrix, " MITRE ATT&CK matrix found\n", + paste0(sprintf('- "%s"', sort(unique(bad))), collapse = "\n"), + call. = FALSE + ) + invisible(sort(unique(bad))) + } else { + message( + "All techniques were found in the ", matrix, " MITRE ATT&CK matrix" + ) + invisible(TRUE) + } + + +} \ No newline at end of file diff --git a/R/validate-techniques.R b/R/validate-techniques.R new file mode 100644 index 0000000..efa621a --- /dev/null +++ b/R/validate-techniques.R @@ -0,0 +1,83 @@ +#' Validate Techniques strings against MITRE authoritative source +#' +#' @param tactics a character vector of tactic strings to validate. +#' @param matrix which matrix to use when validating? +#' @param ignore_case if `TRUE` case will not be taken into account when +#' comparing strings. Default is `FALSE`. +#' @param na_rm remove NA's before comparing? +#' @return `TRUE` if all tactics validate, otherwise `FALSE` with messages +#' identifying the invalid tactics. +#' @export +#' @examples +#' validate_techniques("persistence") +#' validate_techniques(c("persistence", "Persistence", "Persistance")) +validate_techniques <- function(techniques, matrix = c("enterprise", "mobile", "pre"), + ignore_case = FALSE, na_rm = TRUE) { + + matrix <- match.arg(matrix[1], c("enterprise", "mobile", "pre")) + + switch( + matrix, + enterprise = "mitre-attack", + mobile = "mitre-mobile-attack", + pre = "mitre-pre-attack" + ) -> tax + + if (na_rm) { + no_na <- na.exclude(techniques) + where_nas <- attr(no_na, "na.action", exact = TRUE) + if (length(where_nas)) message("Removed ", length(where_nas), " NA values.\n") + tec <- as.character(no_na) + } + + mtec <- unique(tidy_attack[tidy_attack$matrix == tax, "technique", drop=TRUE]) + o_tec <- tec + + if (ignore_case) { + o_tec <- tolower(tec) + mtec <- tolower(mtec) + } + + bad <- unique(o_tec[which(!(tec %in% mtec))]) + + if (length(bad)) { + + bad <- sort(bad) + suggest <- sapply(bad, agrep, x = mtec, ignore.case = ignore_case) + suggest <- ifelse(lengths(suggest) == 0, "No suggestions found", suggest) + + do.call( + rbind.data.frame, + lapply(names(suggest), function(x) { + data.frame( + input = x, + alts = as.character(suggest[[x]]), + stringsAsFactors = FALSE + ) %>% as_tibble() + }) + ) -> suggest + + can_suggest <- which(suggest[["alts"]] != "No suggestions found") + + suggest[can_suggest, "alts"] <- sprintf('Perhaps: "%s"', + mtec[as.integer(suggest[can_suggest, "alts", drop=TRUE])]) + + warning( + "Techniques not in the ", matrix, " MITRE ATT&CK matrix found:\n", + paste0(sprintf('- "%s (%s)"', suggest$input, suggest$alts), collapse = "\n"), + call. = FALSE + ) + + invisible( + tibble( + input = suggest$input, + alts = suggest$alts + ) + ) + + } else { + message("All techniques were found in the ", matrix, " MITRE ATT&CK Framework.") + invisible(TRUE) + } + +} \ No newline at end of file diff --git a/README.Rmd b/README.Rmd index 10bd515..8bb22eb 100644 --- a/README.Rmd +++ b/README.Rmd @@ -1,31 +1,30 @@ --- output: rmarkdown::github_document -editor_options: - chunk_output_type: inline --- ```{r pkg-knitr-opts, include=FALSE} -knitr$opts_chunk$set(collapse=TRUE, fig.retina=2, message=FALSE, warning=FALSE) -options(width=120) +hrbrpkghelpr::global_opts() ``` -[![Travis-CI Build Status](https://travis-ci.org/hrbrmstr/attckr.svg?branch=master)](https://travis-ci.org/hrbrmstr/attckr) -[![Coverage Status](https://codecov.io/gh/hrbrmstr/attckr/branch/master/graph/badge.svg)](https://codecov.io/gh/hrbrmstr/attckr) -[![CRAN_Status_Badge](http://www.r-pkg.org/badges/version/attckr)](https://cran.r-project.org/package=attckr) - -# attckr +```{r badges, results='asis', echo=FALSE, cache=FALSE} +hrbrpkghelpr::stinking_badges() +``` -## Description +```{r description, results='asis', echo=FALSE, cache=FALSE} +hrbrpkghelpr::yank_title_and_description() +``` ## What's Inside The Tin The following functions are implemented: +```{r ingredients, results='asis', echo=FALSE, cache=FALSE} +hrbrpkghelpr::describe_ingredients() +``` + ## Installation -```{r install-ex, eval=FALSE} -devtools::install_git("https://gitlab.com/hrbrmstr/attckr.git") -# or -devtools::install_github("hrbrmstr/attckr") +```{r install-ex, results='asis', echo=FALSE, cache=FALSE} +hrbrpkghelpr::install_block() ``` ## Usage @@ -46,5 +45,4 @@ cloc::cloc_pkg_md() ## Code of Conduct -Please note that this project is released with a [Contributor Code of Conduct](CONDUCT.md). -By participating in this project you agree to abide by its terms. +Please note that this project is released with a Contributor Code of Conduct. By participating in this project you agree to abide by its terms. diff --git a/README.md b/README.md index 8907abe..c5166c8 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,85 @@ + +[![Project Status: Active – The project has reached a stable, usable +state and is being actively +developed.](https://www.repostatus.org/badges/latest/active.svg)](https://www.repostatus.org/#active) +[![Signed +by](https://img.shields.io/badge/Keybase-Verified-brightgreen.svg)](https://keybase.io/hrbrmstr) +![Signed commit +%](https://img.shields.io/badge/Signed_Commits-66.7%25-lightgrey.svg) +[![Linux build +Status](https://travis-ci.org/hrbrmstr/attckr.svg?branch=master)](https://travis-ci.org/hrbrmstr/attckr) +![Minimal R +Version](https://img.shields.io/badge/R%3E%3D-3.2.0-blue.svg) +![License](https://img.shields.io/badge/License-Apache-blue.svg) + # attckr +Analyze Adversary Tactics and Techniques Using the MITRE ATT\&CK CTI +Corpus + +## Description + +MITRE ATT\&CK is a globally-accessible knowledge base of adversary +tactics and techniques based on real-world observations. The ATT\&CK +knowledge base is used as a foundation for the development of specific +threat models and methodologies in the private sector, in government, +and in the cybersecurity product and service community. Tools are +provided to analyze adversary tactics and techniques, build incident +metrics, and identify high level program gaps using the MITRE ATT\&CK +CTI Corpus. + +## What’s Inside The Tin + +The following functions are implemented: + + - `enterprise_attack`: Enterprise Attack Taxonomy + - `fct_tactic`: Make an ordered Tactics factor with optional better + labelling + - `mobile_attack`: Mobile Attack Taxonomy + - `pre_attack`: Pre-Attack Taxonomy + - `read_events`: Read in ATT\&CK events from a file + - `tidy_attack`: Combined ATT\&CK Matricies Tactics, Techniques and + Technique detail + - `validate_tactics`: Validate Tactics strings against MITRE + authoritative source + - `validate_technique_ids`: Validate Technique IDs + - `validate_techniques`: Validate Techniques strings against MITRE + authoritative source + +## Installation + +``` r +remotes::install_git("https://git.rud.is/hrbrmstr/attckr.git") +# or +remotes::install_git("https://git.sr.ht/~hrbrmstr/attckr") +# or +remotes::install_gitlab("hrbrmstr/attckr") +# or +remotes::install_bitbucket("hrbrmstr/attckr") +``` + +NOTE: To use the ‘remotes’ install options you will need to have the +[{remotes} package](https://github.com/r-lib/remotes) installed. + +## Usage + +``` r +library(attckr) + +# current version +packageVersion("attckr") +## [1] '0.1.0' +``` + +## attckr Metrics + +| Lang | \# Files | (%) | LoC | (%) | Blank lines | (%) | \# Lines | (%) | +| :--- | -------: | ---: | --: | ---: | ----------: | ---: | -------: | ---: | +| R | 10 | 0.91 | 168 | 0.95 | 50 | 0.77 | 72 | 0.74 | +| Rmd | 1 | 0.09 | 8 | 0.05 | 15 | 0.23 | 25 | 0.26 | + +## Code of Conduct + +Please note that this project is released with a Contributor Code of +Conduct. By participating in this project you agree to abide by its +terms. diff --git a/data-raw/enterprise-attack.json.xz b/data-raw/enterprise-attack.json.xz index 0d1d66f..5ce4376 100644 Binary files a/data-raw/enterprise-attack.json.xz and b/data-raw/enterprise-attack.json.xz differ diff --git a/data-raw/sync-attack.R b/data-raw/sync-attack.R deleted file mode 100644 index 45994b5..0000000 --- a/data-raw/sync-attack.R +++ /dev/null @@ -1,94 +0,0 @@ -## code to prepare `enterprise_attack` dataset goes here -library(tidyverse) - -unlink( - sprintf("%s.xz", here::here( - "data-raw", - c( - "enterprise-attack.json", - "mobile-attack.json", - "pre-attack.json" - ) - )) -) - -download.file( - url = c( - "https://github.com/mitre/cti/raw/master/enterprise-attack/enterprise-attack.json", - "https://github.com/mitre/cti/raw/master/mobile-attack/mobile-attack.json", - "https://github.com/mitre/cti/raw/master/pre-attack/pre-attack.json" - ), - destfile = c( - here::here( - "data-raw", - c( - "enterprise-attack.json", - "mobile-attack.json", - "pre-attack.json" - ) - ) - ), - method = "libcurl" -) - -walk( - here::here( - "data-raw", - c( - "enterprise-attack.json", - "mobile-attack.json", - "pre-attack.json" - ) - ), - ~system2("xz", args = .x) -) - -jsonlite::fromJSON( - here::here("data-raw/enterprise-attack.json.xz") -) -> enterprise_attack - -enterprise_attack[["objects"]] <- tibble::as_tibble(enterprise_attack[["objects"]]) - -usethis::use_data(enterprise_attack, overwrite = TRUE, compress = "xz") - -jsonlite::fromJSON( - here::here("data-raw/mobile-attack.json.xz") -) -> mobile_attack - -mobile_attack[["objects"]] <- tibble::as_tibble(mobile_attack[["objects"]]) - -usethis::use_data(mobile_attack, overwrite = TRUE, compress = "xz") - -jsonlite::fromJSON( - here::here("data-raw/pre-attack.json.xz") -) -> pre_attack - -pre_attack[["objects"]] <- tibble::as_tibble(pre_attack[["objects"]]) - -usethis::use_data(pre_attack, overwrite = TRUE, compress = "xz") - -bind_rows( - tibble( - technique = enterprise_attack$objects$name, - description = enterprise_attack$objects$x_mitre_detection, - tactic = enterprise_attack$objects$kill_chain_phases - ) %>% - filter(lengths(tactic) > 0) %>% - unnest(), - tibble( - technique = mobile_attack$objects$name, - description = mobile_attack$objects$x_mitre_detection, - tactic = mobile_attack$objects$kill_chain_phases - ) %>% - filter(lengths(tactic) > 0) %>% - unnest(), - tibble( - technique = pre_attack$objects$name, - description = pre_attack$objects$description, - tactic = pre_attack$objects$kill_chain_phases - ) %>% - filter(lengths(tactic) > 0) %>% - unnest() -) -> tidy_attack - -usethis::use_data(tidy_attack, overwrite = TRUE, compress = "xz") diff --git a/data/enterprise_attack.rda b/data/enterprise_attack.rda index f5e0219..3aad444 100644 Binary files a/data/enterprise_attack.rda and b/data/enterprise_attack.rda differ diff --git a/data/mobile_attack.rda b/data/mobile_attack.rda index f18635b..d4d3891 100644 Binary files a/data/mobile_attack.rda and b/data/mobile_attack.rda differ diff --git a/data/pre_attack.rda b/data/pre_attack.rda index 7d87d1e..e0406b6 100644 Binary files a/data/pre_attack.rda and b/data/pre_attack.rda differ diff --git a/data/tidy_attack.rda b/data/tidy_attack.rda index 9fb30e2..9f1164b 100644 Binary files a/data/tidy_attack.rda and b/data/tidy_attack.rda differ diff --git a/man/attckr.Rd b/man/attckr.Rd index c0cd8d7..8226e9c 100644 --- a/man/attckr.Rd +++ b/man/attckr.Rd @@ -4,12 +4,24 @@ \name{attckr} \alias{attckr} \alias{attckr-package} -\title{...} +\title{Analyze Adversary Tactics and Techniques Using the MITRE ATT&CK CTI Corpus} \description{ +MITRE ATT&CK™ is a globally-accessible knowledge base of +adversary tactics and techniques based on real-world observations. +The ATT&CK knowledge base is used as a foundation for the development +of specific threat models and methodologies in the private sector, +in government, and in the cybersecurity product and service community. +Tools are provided to analyze adversary tactics and techniques, +build incident metrics, and identify high level program gaps +using the MITRE ATT&CK CTI Corpus. +} +\seealso{ +Useful links: \itemize{ -\item URL: \url{https://gitlab.com/hrbrmstr/attckr} -\item BugReports: \url{https://gitlab.com/hrbrmstr/attckr/issues} + \item \url{https://gitlab.com/hrbrmstr/attckr} + \item Report bugs at \url{https://gitlab.com/hrbrmstr/attckr/issues} } + } \author{ Bob Rudis (bob@rud.is) diff --git a/man/enterprise_attack.Rd b/man/enterprise_attack.Rd new file mode 100644 index 0000000..4c87b05 --- /dev/null +++ b/man/enterprise_attack.Rd @@ -0,0 +1,9 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/data-docs.R +\docType{data} +\name{enterprise_attack} +\alias{enterprise_attack} +\title{Enterprise Attack Taxonomy} +\description{ +Enterprise Attack Taxonomy +} diff --git a/man/fct_tactic.Rd b/man/fct_tactic.Rd new file mode 100644 index 0000000..b86d7c0 --- /dev/null +++ b/man/fct_tactic.Rd @@ -0,0 +1,22 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/fct-tactic.R +\name{fct_tactic} +\alias{fct_tactic} +\title{Make an ordered Tactics factor with optional better labelling} +\usage{ +fct_tactic(tactics, input = c("id", "pretty", "nl"), + output = c("pretty", "nl", "id"), matrix = c("enterprise", "mobile", + "pre")) +} +\arguments{ +\item{tactics}{a character vector} + +\item{input}{what is in \code{tactics}?} + +\item{output}{what do you want the factor label to be?} + +\item{matrix}{which matrix?} +} +\description{ +Make an ordered Tactics factor with optional better labelling +} diff --git a/man/mobile_attack.Rd b/man/mobile_attack.Rd new file mode 100644 index 0000000..946b895 --- /dev/null +++ b/man/mobile_attack.Rd @@ -0,0 +1,9 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/data-docs.R +\docType{data} +\name{mobile_attack} +\alias{mobile_attack} +\title{Mobile Attack Taxonomy} +\description{ +Mobile Attack Taxonomy +} diff --git a/man/pre_attack.Rd b/man/pre_attack.Rd new file mode 100644 index 0000000..ed88de8 --- /dev/null +++ b/man/pre_attack.Rd @@ -0,0 +1,9 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/data-docs.R +\docType{data} +\name{pre_attack} +\alias{pre_attack} +\title{Pre-Attack Taxonomy} +\description{ +Pre-Attack Taxonomy +} diff --git a/man/read_events.Rd b/man/read_events.Rd new file mode 100644 index 0000000..7ee7f30 --- /dev/null +++ b/man/read_events.Rd @@ -0,0 +1,11 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/read-events.R +\name{read_events} +\alias{read_events} +\title{Read in ATT&CK events from a file} +\usage{ +read_events(path) +} +\description{ +Read in ATT&CK events from a file +} diff --git a/man/tidy_attack.Rd b/man/tidy_attack.Rd new file mode 100644 index 0000000..e611154 --- /dev/null +++ b/man/tidy_attack.Rd @@ -0,0 +1,9 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/data-docs.R +\docType{data} +\name{tidy_attack} +\alias{tidy_attack} +\title{Combined ATT&CK Matricies Tactics, Techniques and Technique detail} +\description{ +Combined ATT&CK Matricies Tactics, Techniques and Technique detail +} diff --git a/man/validate_tactics.Rd b/man/validate_tactics.Rd new file mode 100644 index 0000000..18582f4 --- /dev/null +++ b/man/validate_tactics.Rd @@ -0,0 +1,29 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/validate-tactics.R +\name{validate_tactics} +\alias{validate_tactics} +\title{Validate Tactics strings against MITRE authoritative source} +\usage{ +validate_tactics(tactics, matrix = c("enterprise", "mobile", "pre"), + na_rm = TRUE) +} +\arguments{ +\item{tactics}{a character vector of tactic strings to validate. This will be +converted to lower-case, left/right spaces will be trimmed and +internal spaces will be converted to a single \code{-}} + +\item{matrix}{which matrix to use when validating?} + +\item{na_rm}{remove NA's before comparing?} +} +\value{ +\code{TRUE} if all tactics validate, otherwise \code{FALSE} with messages +identifying the invalid tactics. +} +\description{ +Validate Tactics strings against MITRE authoritative source +} +\examples{ +validate_tactics("persistence") +validate_tactics(c("persistence", "Persistence", "Persistance")) +} diff --git a/man/validate_technique_ids.Rd b/man/validate_technique_ids.Rd new file mode 100644 index 0000000..f0131e7 --- /dev/null +++ b/man/validate_technique_ids.Rd @@ -0,0 +1,20 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/validate-tech-ids.R +\name{validate_technique_ids} +\alias{validate_technique_ids} +\title{Validate Technique IDs} +\usage{ +validate_technique_ids(technique_ids, matrix = c("enterprise", "mobile", + "pre"), na_rm = TRUE) +} +\arguments{ +\item{technique_ids}{a character vector of technique ids to validate} + +\item{matrix}{which matrix to validate against (not all IDs are associated +with techniques in every matrix)} + +\item{na_rm}{remove NA's before comparing?} +} +\description{ +Validate Technique IDs +} diff --git a/man/validate_techniques.Rd b/man/validate_techniques.Rd new file mode 100644 index 0000000..5a8ebdc --- /dev/null +++ b/man/validate_techniques.Rd @@ -0,0 +1,30 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/validate-techniques.R +\name{validate_techniques} +\alias{validate_techniques} +\title{Validate Techniques strings against MITRE authoritative source} +\usage{ +validate_techniques(techniques, matrix = c("enterprise", "mobile", + "pre"), ignore_case = FALSE, na_rm = TRUE) +} +\arguments{ +\item{matrix}{which matrix to use when validating?} + +\item{ignore_case}{if \code{TRUE} case will not be taken into account when +comparing strings. Default is \code{FALSE}.} + +\item{na_rm}{remove NA's before comparing?} + +\item{tactics}{a character vector of tactic strings to validate.} +} +\value{ +\code{TRUE} if all tactics validate, otherwise \code{FALSE} with messages +identifying the invalid tactics. +} +\description{ +Validate Techniques strings against MITRE authoritative source +} +\examples{ +validate_techniques("persistence") +validate_techniques(c("persistence", "Persistence", "Persistance")) +} diff --git a/tools/fill-mat.R b/tools/fill-mat.R new file mode 100644 index 0000000..3cb23ab --- /dev/null +++ b/tools/fill-mat.R @@ -0,0 +1,21 @@ +filter(tidy_attack, matrix == "mitre-attack") %>% + distinct(tactic, technique) %>% + mutate(tactic = fct_tactic("id", "nl")) %>% + group_by(tactic) %>% + mutate(ypos = 1:n()) %>% + ggplot(aes(tactic, ypos)) + + geom_tile(color = "#b2b2b2", fill = "white") + + geom_text(aes(label = technique), size = 2) + + scale_x_discrete(position = "top") + + scale_y_reverse(expand = c(0,0)) + + labs( + x = NULL, y = NULL + ) + + theme_minimal() + + theme(panel.grid = element_blank()) + + theme(axis.text.y = element_blank()) + + + +filter(tidy_attack, matrix == "mitre-attack") %>% + distinct(tactic, technique) diff --git a/tools/update-framework.R b/tools/update-framework.R new file mode 100644 index 0000000..ed50993 --- /dev/null +++ b/tools/update-framework.R @@ -0,0 +1,129 @@ +## code to prepare `enterprise_attack` dataset goes here + +library(tidyverse) + +unlink( + sprintf("%s.xz", here::here( + "data-raw", + c( + "enterprise-attack.json", + "mobile-attack.json", + "pre-attack.json" + ) + )) +) + +download.file( + url = c( + "https://github.com/mitre/cti/raw/master/enterprise-attack/enterprise-attack.json", + "https://github.com/mitre/cti/raw/master/mobile-attack/mobile-attack.json", + "https://github.com/mitre/cti/raw/master/pre-attack/pre-attack.json" + ), + destfile = c( + here::here( + "data-raw", + c( + "enterprise-attack.json", + "mobile-attack.json", + "pre-attack.json" + ) + ) + ), + method = "libcurl" +) + +walk( + here::here( + "data-raw", + c( + "enterprise-attack.json", + "mobile-attack.json", + "pre-attack.json" + ) + ), + ~system2("xz", args = .x) +) + +jsonlite::fromJSON( + here::here("data-raw/enterprise-attack.json.xz") +) -> enterprise_attack + +enterprise_attack[["objects"]] <- tibble::as_tibble(enterprise_attack[["objects"]]) + +jsonlite::fromJSON( + here::here("data-raw/mobile-attack.json.xz") +) -> mobile_attack + +mobile_attack[["objects"]] <- tibble::as_tibble(mobile_attack[["objects"]]) + +jsonlite::fromJSON( + here::here("data-raw/pre-attack.json.xz") +) -> pre_attack + +pre_attack[["objects"]] <- tibble::as_tibble(pre_attack[["objects"]]) + +bind_rows( + map_df(1:nrow(enterprise_attack$objects), ~{ + if (is.na(enterprise_attack$objects$name[[.x]])) return(NULL) + if (is.null(enterprise_attack$objects$kill_chain_phases[[.x]])) return(NULL) + tibble( + technique = enterprise_attack$objects$name[[.x]], + description = enterprise_attack$objects$description[.x], + id = discard(enterprise_attack$objects$external_references[[.x]]$external_id, is.na) %||% NA_character_, + phs = enterprise_attack$objects$kill_chain_phases[.x] + ) %>% + unnest() + }), + map_df(1:nrow(mobile_attack$objects), ~{ + if (is.na(mobile_attack$objects$name[[.x]])) return(NULL) + if (is.null(mobile_attack$objects$kill_chain_phases[[.x]])) return(NULL) + tibble( + technique = mobile_attack$objects$name[[.x]], + description = mobile_attack$objects$description[.x], + id = discard(mobile_attack$objects$external_references[[.x]]$external_id, is.na) %||% NA_character_, + phs = mobile_attack$objects$kill_chain_phases[.x] + ) %>% + unnest() + }), + map_df(1:nrow(pre_attack$objects), ~{ + if (is.na(pre_attack$objects$name[[.x]])) return(NULL) + if (is.null(pre_attack$objects$kill_chain_phases[[.x]])) return(NULL) + tibble( + technique = pre_attack$objects$name[[.x]], + description = pre_attack$objects$description[.x], + id = discard(pre_attack$objects$external_references[[.x]]$external_id, is.na) %||% NA_character_, + phs = pre_attack$objects$kill_chain_phases[.x] + ) %>% + unnest() + }) +) %>% + rename(tactic = phase_name, matrix = kill_chain_name) %>% + distinct() -> tidy_attack + +usethis::use_data( + enterprise_attack, mobile_attack, pre_attack, tidy_attack, + internal = FALSE, + overwrite = TRUE, + compress = "xz" +) + +bind_rows(enterprise_attack$objects$kill_chain_phases) %>% + distinct(phase_name) + + + + + + + + + + + + + + + + + +