Skip to content

Commit

Permalink
Adding the 0.1 of vlinpeas
Browse files Browse the repository at this point in the history
  • Loading branch information
Max authored and Max committed Jan 24, 2023
0 parents commit d24e8d0
Show file tree
Hide file tree
Showing 8 changed files with 344 additions and 0 deletions.
9 changes: 9 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

[*.v]
indent_style = tab
indent_size = 4
7 changes: 7 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
* text=auto eol=lf
*.bat eol=crlf

**/*.v linguist-language=V
**/*.vv linguist-language=V
**/*.vsh linguist-language=V
**/v.mod linguist-language=V
24 changes: 24 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Binaries for programs and plugins
main
vlinpeas
*.exe
*.exe~
*.so
*.dylib
*.dll

# Ignore binary output folders
bin/

# Ignore common editor/system specific metadata
.DS_Store
.idea/
.vscode/
*.iml

# ENV
.env

# vweb and database
*.db
*.js
102 changes: 102 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# What is this?
This is a wrapper for linpeas json files. The intent of which is to allow for more programability for linpeas output.

I am primarily developing in a linux environment. So it might not work quite as well for Windows based environments

# Technical Limitations
## Winpeas Uncertainty
I can confirm this will work in both MacOS and Linux using macpeas and linpeas respectively

I am uncertain how effective this will be with winpeas. I use nix environments at home so I do not know how well this will work when running winpeas and parsing its output. Theoretically it should work as I am only parsing the output unless there is something with the underlying implementation of V right now that makes it difficult to run on Windows.

## JSON Parsing Capabilities
The current V documentation organizes json as structs where each field in a struct is the json key.

The issue with this is that I will need to explicitly declare every JSON field name. That can be a long and tedious process, especially when something follows a defined structure in JSON.

The linpeas JSON output seems to follow the following structure:
```
{
'Scan1': {
sections: {
},
lines: [
{
},
],
'infos': []
},
'Scan2': {
sections: {
'Scan': {
sections: {},
lines: [],
infos: [
'string'
]
}
},
lines: [
{
'raw_text': 'raw text with shell color encoding text',
'colors': {
'DETECTED_COLOR': [
'optional text in dark grey, blue, green, redyellow,red. This will not be here if there is nothing detected',
],
},
'clean_text': 'text without shell encoding text'
},
],
'infos': [
'URL string'
]
},
}
```

So all in all, it is not the most complex structure. It is very repetative. The issue though is that the Scan key will have a different name every time.

There is currently an experimental json library in the core vlib (json2) but it is experimental and is prone to error.

Honestly knowing how V works and how the structure of a JSON file for linpeas is set up, I really probably should just use the abstract syntax tree. But that would require learning. Gross.

I also had a shower thought that really all it could be is a map[string]map[string][]string where
1. The first map is the section name
1. The second map is the color
1. The strings array are all the findings of the color
The only problem with this shower thought is if the scan format changes, it isn't exactly the most extensible for other formats.

# Install
1. vpm
> v install trinitok.vlinpeas
# Use
## Prerequisite
1. Run linpeas and send the output to a file
1. https://github.com/carlospolop/PEASS-ng
1. Use the `peass2json.py` program to turn linpeas into json
## Usage
```
import trinitok.vlinpeas
peass2json_output := 'path_to_peass2json.py_output.text'
// Parse the output
linpeas_out := linpeas_analyzer.decode_linpeas_json(peass2json_output)
// Retrieve all REDYELLOW text (95% PE vectors)
crit_findings := linpeas_out.retrieve_critical_findings()
// Retrieve all BLUE text (because why not?)
blue_findings := linpeas_out.
```
# Testing
You can definitely try running the tests. They will fail because I did not include a local file from the linpeas output. You will likely want to add a peass2json output file and change the name in the `analyzer_test.v`

# TODO
1. Return more than just the first instance of the linpeas keywords
1. Rewrite the peass2json in V
1. Allow for more ways to analyze output of lin/mac/win-peas
1. Optimize parser to not be so jank (slightly dependent on V making advancements on json parsing)
46 changes: 46 additions & 0 deletions linpeas_analyzer/linpeas_analyzer.v
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
module linpeas_analyzer

struct LinpeasRetrievedFindings {
name string
clean_text string
findings []string
}

// Retrieve all the text from linpeas findings that were
// marked as redyellow output in the terminal
pub fn (linpeas_findings LinpeasFindings) retrieve_critical_findings() []LinpeasRetrievedFindings {
scans := linpeas_findings.scans
crit_findings :=
retrieve_linpeas_finding_lines_with_color('REDYELLOW', scans)
return crit_findings
}

// Retrieve all lines of text from linpeas findings that match the color_id passed.
// Must match a valid color from Linpeas
pub fn (linpeas_findings LinpeasFindings) retrieve_findings_with_color_id(color_id string) []LinpeasRetrievedFindings {
scans := linpeas_findings.scans
crit_findings :=
retrieve_linpeas_finding_lines_with_color(color_id, scans)
return crit_findings
}

// Uses depth first search in order to obtain all the color_name texts from LinpeasLinesNode
fn retrieve_linpeas_finding_lines_with_color(color_name string, scan_nodes []LinpeasScanNode) []LinpeasRetrievedFindings {
mut ret_arr := []LinpeasRetrievedFindings
for scan in scan_nodes {
sections := scan.sections
if sections.sub_scan.len > 0 {
ret_arr << retrieve_linpeas_finding_lines_with_color(color_name, sections.sub_scan)
}
lines := scan.lines
if color_name in lines.colors.keys() {
ret_arr << LinpeasRetrievedFindings {
name: scan.name
clean_text: lines.clean_text
findings: lines.colors[color_name]
}
}
}

return ret_arr
}
137 changes: 137 additions & 0 deletions linpeas_analyzer/linpeas_parser.v
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
module linpeas_analyzer

import os
import x.json2

struct LinpeasFindings {
scans []LinpeasScanNode
}

struct LinpeasScanNode {
name string
sections LinpeasSectionNode
lines LinpeasLineNode
infos []string
}

struct LinpeasSectionNode {
sub_scan []LinpeasScanNode
}

struct LinpeasLineNode {
colors map[string][]string
clean_text string
}

// fn init() {
// println('probably want to install this here')
// os.execute_or_panic('curl -L https://github.com/carlospolop/PEASS-ng/releases/latest/download/linpeas.sh | sh > linpeas.out')
// }

pub fn decode_linpeas_json(filename string) LinpeasFindings {
buf := os.read_file(filename) or {
println('error reading linpeas.json file')
panic(err)
}

test_out := json2.raw_decode(buf) or {
println('issue trying to decode linpeas.json file')
unsafe { buf.free() }
panic(err)
}
unsafe { buf.free() }
product := test_out.as_map()
data := product as map[string]json2.Any
linpeas_out := parse_scan_nodes(data)

return LinpeasFindings {
scans: linpeas_out
}
}

fn parse_json_str_to_map(json_str string) map[string]json2.Any {
test_basic_info_values := json2.raw_decode(json_str) or {
println('error trying to decode values for json string: ${json_str}')
panic(err)
}
product_sections := test_basic_info_values.as_map()
data_sections := product_sections as map[string]json2.Any

return data_sections
}

fn parse_scan_nodes(scan_node map[string]json2.Any) []LinpeasScanNode {
mut ret_arr := []LinpeasScanNode {}
for scan_key in scan_node.keys() {
// convert to json str
scan_children_str := scan_node[scan_key].str()
mapped_scan_node_children := parse_json_str_to_map(scan_children_str)
new_scan_node := LinpeasScanNode {
name: scan_key
sections: process_sections(mapped_scan_node_children['sections'])
lines: process_lines(mapped_scan_node_children['lines'])
infos: process_infos(mapped_scan_node_children['infos'])
}
ret_arr << new_scan_node
}

return ret_arr
}

fn process_sections(scan_section json2.Any) LinpeasSectionNode {
mapped_section_json := scan_section.as_map()
if mapped_section_json.len == 0 {
return LinpeasSectionNode {}
}
else {
return LinpeasSectionNode {
sub_scan: parse_scan_nodes(mapped_section_json)
}
}
}

fn process_lines_colors(mapped_value_colors map[string]json2.Any) map[string][]string {
mut ret_map := map[string][]string
// for each string key in the colors take the color map key, then for the array loop over that and convert each value to string
for key in mapped_value_colors.keys() {
colors_arr := mapped_value_colors[key].arr()
mut transformed_colors_arr := []string
// loop over each json2.Any value and convert each individually to string
for i := 0; i < colors_arr.len; i ++ {
orig_item := colors_arr[i]
transformed_colors_arr << orig_item.str()
}
ret_map[key] << transformed_colors_arr
}

return ret_map
}

fn process_lines(scan_lines json2.Any) LinpeasLineNode {
lines := scan_lines.as_map()
mut ret_map := map[string][]string
mut ret_clean_text := ''
// reeeee super inefficient
// for each value in lines
for value in lines.values() {
// []json2.Any cannot convert to []string so I have to go over every array and every value in the array and convert them to an array of strings
mapped_values := value.as_map()
mapped_value_colors := mapped_values['colors'].as_map()
ret_clean_text = mapped_values['clean_text'].str()
// get the colors
ret_map = process_lines_colors(mapped_value_colors)
}
return LinpeasLineNode {
colors: ret_map
clean_text: ret_clean_text
}
}

fn process_infos(scan_infos json2.Any) [] string {
scan_infos_arr := scan_infos.arr()
mut ret_arr := []string
for value in scan_infos_arr {
ret_arr << scan_infos_arr.str()
}
return ret_arr
}
12 changes: 12 additions & 0 deletions test/analyzer_test.v
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module tests

import linpeas_analyzer

fn test_parse_and_crit_findings() {
linpeas_out_file := 'linpeas.json'

linpeas_out := linpeas_analyzer.decode_linpeas_json(linpeas_out_file)
crit_findings := linpeas_out.retrieve_critical_findings()

println('here are the critical linpeas findings: ${crit_findings}')
}
7 changes: 7 additions & 0 deletions v.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Module {
name: 'vlinpeas'
description: 'Linpeas JSON output parser in V Lang'
version: '0.1'
license: 'MIT'
dependencies: []
}

0 comments on commit d24e8d0

Please sign in to comment.