Browse Source

attck_map()

master
boB Rudis 2 years ago
parent
commit
71c2b1b087
No known key found for this signature in database GPG Key ID: 1D7529BE14E2BBA9
  1. 5
      DESCRIPTION
  2. 7
      NAMESPACE
  3. 71
      R/attck-map.R
  4. 3
      R/attckr-package.R
  5. 34
      README.Rmd
  6. 33
      README.md
  7. 40
      man/attck_map.Rd
  8. BIN
      man/figures/README-events-1.png

5
DESCRIPTION

@ -25,7 +25,10 @@ Suggests:
testthat,
covr
Depends:
R (>= 3.2.0)
R (>= 3.2.0),
grid,
gtable,
ggplot2
Imports:
jsonlite,
tibble,

7
NAMESPACE

@ -1,19 +1,26 @@
# Generated by roxygen2: do not edit by hand
export(attck_map)
export(fct_tactic)
export(read_events)
export(validate_tactics)
export(validate_technique_ids)
export(validate_techniques)
import(ggplot2)
import(grid)
import(gtable)
import(rmarkdown)
import(shiny)
import(stringi)
import(tibble)
importFrom(dplyr,arrange)
importFrom(dplyr,case_when)
importFrom(dplyr,count)
importFrom(dplyr,distinct)
importFrom(dplyr,filter)
importFrom(dplyr,group_by)
importFrom(dplyr,mutate)
importFrom(dplyr,n)
importFrom(dplyr,select)
importFrom(glue,glue)
importFrom(jsonlite,fromJSON)

71
R/attck-map.R

@ -0,0 +1,71 @@
#' Generate an ATT&CK heatmap
#'
#' @param xdf a data frame with `tactic`, `technique` and `value` columns.
#' If no `value` column exists, then the function will assume you
#' have passed in individual events and will perform a "count"
#' summarization before generating the heatmap.
#' @param input,output,matrix if both are not `NULL` then they should be
#' what [fct_tactic()] takes as parameters. Otherwise, the function
#' will assume that the `tactic` column is already an ordered factor.
#' @param tile_col,tile_size color/size for the tile borders;
#' defaults to "`white`" and `0.5`, respectively.
#' @param dark_lab,light_lab text colors for when they appear on top of a
#' dark or light tile
#' @param dark_value_threshold since you can supply your own fill scale
#' and may use a transformation (e.g. "`log10`") when doing so, you
#' can specify the cutoff value for when to use `dark_lab` vs `light_lab`.
#' If `NULL` then half of `max(value)` will be used.
#' @param ... passed on to the internal call to [ggplot2::geom_text()]
#' @return a ggplot2 plot object which you can add a fill scale to as well
#' as themeing.
#' @export
attck_map <- function(xdf, input = NULL, output = NULL, matrix = NULL,
tile_col = "white", tile_size = 0.5,
dark_lab = "white", light_lab = "black",
dark_value_threshold = NULL, ...) {
cn <- colnames(xdf)
if (!all(c("tactic", "technique") %in% cn)) {
stop("'xdf' needs both 'tactic' and 'technique' columns.", call.=FALSE)
}
if (!("value" %in% cn)) {
xdf <- dplyr::count(xdf, tactic, technique, name = "value")
}
if (is.null(input) && is.null(output)) {
if (!is.factor(xdf$tactic)) {
stop(
"No 'input'/'output' transformation specified but 'tactic' is not a factor.",
call.=FALSE
)
}
} else {
if (sum(c(!is.null(input), !is.null(output), !is.null(matrix))) != 3) {
stop("Must specify 'input', 'output', and 'matrix' if any one of them is not NULL", call.=FALSE)
}
xdf$tactic <- fct_tactic(xdf$tactic, input = input, output = output, matrix = matrix)
}
if (is.null(dark_value_threshold)) dark_value_threshold <- max(xdf$value)/2
xdf <- dplyr::arrange(xdf, value)
xdf$technique <- factor(gsub(" ", "\n", xdf$technique))
xdf <- dplyr::group_by(xdf, tactic)
xdf <- dplyr::mutate(xdf, ids = (n():1))
xdf <- dplyr::ungroup(xdf)
gg <- ggplot(xdf, aes(tactic, ids))
gg <- gg + geom_tile(aes(fill = value), color = tile_col, size = tile_size)
gg <- gg + geom_text(
aes(
label = technique,
color = I(ifelse(value <= dark_value_threshold, dark_lab, light_lab))
), ...
)
gg <- gg + scale_x_discrete(expand = c(0, 0), position = "top")
gg <- gg + scale_y_reverse(expand = c(0, 0))
gg
}

3
R/attckr-package.R

@ -15,7 +15,8 @@
#' @author Bob Rudis (bob@@rud.is)
#' @import tibble ggplot2 shiny rmarkdown stringi
#' @importFrom glue glue
#' @importFrom dplyr filter distinct mutate select case_when
#' @importFrom dplyr filter distinct mutate select case_when group_by arrange count n
#' @importFrom readr read_csv
#' @importFrom jsonlite fromJSON
#' @import ggplot2 grid gtable
"_PACKAGE"

34
README.Rmd

@ -25,10 +25,8 @@ hrbrpkghelpr::describe_ingredients()
The following datasets are included:
```{r datasets, echo=FALSE, results="asis"}
d <- as.data.frame(data(package = "attckr")$results[,c("Item", "Title")], stringsAsFactors=FALSE)
d$Item <- sprintf("`%s`", d$Item)
cat(sprintf("- %s: %s", d$Item, d$Title), sep="\n")
```{r datasets, results='asis', echo=FALSE, cache=FALSE}
hrbrpkghelpr::describe_datasets()
```
## Installation
@ -41,6 +39,7 @@ hrbrpkghelpr::install_block()
```{r lib-ex}
library(attckr)
library(hrbrthemes)
library(tidyverse)
# current version
@ -55,24 +54,17 @@ tidy_attack
```{r events, message=TRUE, fig.width=11, fig.height=6}
events <- read_events(system.file("extdat/sample-incidents.csv.gz", package = "attckr"))
count(head(events, 30), tactic, technique) %>%
mutate(tactic = fct_tactic(tactic, "pretty", "nl")) %>%
left_join(
filter(tidy_attack, matrix == "mitre-attack") %>%
distinct(id, technique),
c("technique" = "id")
) %>%
complete(tactic, technique.y) %>%
mutate(technique.y = factor(technique.y, rev(sort(unique(technique.y))))) %>%
ggplot(aes(tactic, technique.y)) +
geom_tile(aes(fill = n), color = "#2b2b2b", size = 0.125) +
scale_x_discrete(expand = c(0, 0), position = "top") +
scale_fill_viridis_c(direction = -1, na.value = "white") +
labs(
x = NULL, y = NULL
attck_map(
events, "pretty", "nl", "enterprise",
dark_value_threshold = 1,
size = 3, family = font_rc, lineheight = 0.875
) +
scale_fill_distiller(
palette = "Spectral", na.value = "white", label = scales::comma, breaks = 1:3
) +
theme_minimal() +
theme(panel.grid=element_blank())
labs(x = NULL, y = NULL, fill = NULL) +
theme_ipsum_rc(grid="") +
theme(axis.text.y = element_blank())
```
## attckr Metrics

33
README.md

@ -32,6 +32,7 @@ CTI Corpus.
The following functions are implemented:
- `attck_map`: Generate an ATT\&CK heatmap
- `enterprise_attack`: Enterprise Attack Taxonomy v2.0
- `fct_tactic`: Make an ordered Tactics factor with optional better
labelling
@ -81,6 +82,7 @@ NOTE: To use the ‘remotes’ install options you will need to have the
``` r
library(attckr)
library(hrbrthemes)
library(tidyverse)
# current version
@ -123,24 +125,17 @@ events <- read_events(system.file("extdat/sample-incidents.csv.gz", package = "a
## You appear to be using Tactic ids.
## You appear to be using Techinque ids.
count(head(events, 30), tactic, technique) %>%
mutate(tactic = fct_tactic(tactic, "pretty", "nl")) %>%
left_join(
filter(tidy_attack, matrix == "mitre-attack") %>%
distinct(id, technique),
c("technique" = "id")
) %>%
complete(tactic, technique.y) %>%
mutate(technique.y = factor(technique.y, rev(sort(unique(technique.y))))) %>%
ggplot(aes(tactic, technique.y)) +
geom_tile(aes(fill = n), color = "#2b2b2b", size = 0.125) +
scale_x_discrete(expand = c(0, 0), position = "top") +
scale_fill_viridis_c(direction = -1, na.value = "white") +
labs(
x = NULL, y = NULL
attck_map(
events, "pretty", "nl", "enterprise",
dark_value_threshold = 1,
size = 3, family = font_rc, lineheight = 0.875
) +
scale_fill_distiller(
palette = "Spectral", na.value = "white", label = scales::comma, breaks = 1:3
) +
theme_minimal() +
theme(panel.grid=element_blank())
labs(x = NULL, y = NULL, fill = NULL) +
theme_ipsum_rc(grid="") +
theme(axis.text.y = element_blank())
```
<img src="man/figures/README-events-1.png" width="1056" />
@ -149,8 +144,8 @@ count(head(events, 30), tactic, technique) %>%
| Lang | \# Files | (%) | LoC | (%) | Blank lines | (%) | \# Lines | (%) |
| :--- | -------: | ---: | --: | ---: | ----------: | ---: | -------: | ---: |
| R | 10 | 0.91 | 203 | 0.86 | 57 | 0.74 | 144 | 0.81 |
| Rmd | 1 | 0.09 | 32 | 0.14 | 20 | 0.26 | 34 | 0.19 |
| R | 11 | 0.92 | 245 | 0.91 | 65 | 0.76 | 166 | 0.83 |
| Rmd | 1 | 0.08 | 24 | 0.09 | 20 | 0.24 | 34 | 0.17 |
## Code of Conduct

40
man/attck_map.Rd

@ -0,0 +1,40 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/attck-map.R
\name{attck_map}
\alias{attck_map}
\title{Generate an ATT&CK heatmap}
\usage{
attck_map(xdf, input = NULL, output = NULL, matrix = NULL,
tile_col = "white", tile_size = 0.5, dark_lab = "white",
light_lab = "black", dark_value_threshold = NULL, ...)
}
\arguments{
\item{xdf}{a data frame with \code{tactic}, \code{technique} and \code{value} columns.
If no \code{value} column exists, then the function will assume you
have passed in individual events and will perform a "count"
summarization before generating the heatmap.}
\item{input, output, matrix}{if both are not \code{NULL} then they should be
what \code{\link[=fct_tactic]{fct_tactic()}} takes as parameters. Otherwise, the function
will assume that the \code{tactic} column is already an ordered factor.}
\item{tile_col, tile_size}{color/size for the tile borders;
defaults to "\code{white}" and \code{0.5}, respectively.}
\item{dark_lab, light_lab}{text colors for when they appear on top of a
dark or light tile}
\item{dark_value_threshold}{since you can supply your own fill scale
and may use a transformation (e.g. "\code{log10}") when doing so, you
can specify the cutoff value for when to use \code{dark_lab} vs \code{light_lab}.
If \code{NULL} then half of \code{max(value)} will be used.}
\item{...}{passed on to the internal call to \code{\link[ggplot2:geom_text]{ggplot2::geom_text()}}}
}
\value{
a ggplot2 plot object which you can add a fill scale to as well
as themeing.
}
\description{
Generate an ATT&CK heatmap
}

BIN
man/figures/README-events-1.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 79 KiB

Loading…
Cancel
Save