Skip to content

Commit

Permalink
Add code structure to parse Sigma and YARA rules
Browse files Browse the repository at this point in the history
Added functionality to parse Sigma and YARA rules and extract rule names and tags from them.
  • Loading branch information
mtnmunuklu committed Mar 17, 2024
1 parent 14d9c30 commit 31c8197
Show file tree
Hide file tree
Showing 16 changed files with 1,152 additions and 0 deletions.
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,11 @@

# Go workspace file
go.work

# VS Code specific files
.vscode/
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets
16 changes: 16 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
module github.com/mtnmunuklu/analyze-tags

go 1.20

require (
github.com/VirusTotal/gyp v0.9.0
github.com/stretchr/testify v1.9.0
gopkg.in/yaml.v3 v3.0.1
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/golang/protobuf v1.5.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
google.golang.org/protobuf v1.27.1 // indirect
)
32 changes: 32 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
github.com/VirusTotal/gyp v0.9.0 h1:jhOBl93jfStmAcKLa/EcTmdPng5bn5kvJJZqQqJ5R4g=
github.com/VirusTotal/gyp v0.9.0/go.mod h1:nmcW15dQ1657PmMcG9X/EZmp6rTQsyo9g8r6Cz1/AHc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
235 changes: 235 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
package main

import (
"encoding/base64"
"encoding/json"
"flag"
"fmt"
"os"
"path/filepath"
"strings"

"github.com/mtnmunuklu/analyze-tags/sigma"
"github.com/mtnmunuklu/analyze-tags/yara"
)

var (
filePath string
fileContent string
showHelp bool
outputJSON bool
outputPath string
version bool
useSigma bool
useYara bool
)

// Set up the command-line flags
func init() {
flag.StringVar(&filePath, "filepath", "", "Name or path of the file or directory to read")
flag.StringVar(&fileContent, "filecontent", "", "Base64-encoded content of the file or directory to read")
flag.BoolVar(&showHelp, "help", false, "Show usage")
flag.BoolVar(&outputJSON, "json", false, "Output results in JSON format")
flag.StringVar(&outputPath, "output", "", "Output directory for writing files")
flag.BoolVar(&version, "version", false, "Show version information")
flag.BoolVar(&useSigma, "sigma", false, "Use Sigma rules")
flag.BoolVar(&useYara, "yara", false, "Use Yara rules")
flag.Parse()

// If the version flag is provided, print version information and exit
if version {
fmt.Println("Analyze Tags version 1.0.0")
os.Exit(1)
}

// If the help flag is provided, print usage information and exit
if showHelp {
printUsage()
os.Exit(1)
}

// Check if both filecontent and configcontent are provided
if filePath == "" && fileContent == "" {
fmt.Println("Please provide either file paths or file contents.")
printUsage()
os.Exit(1)
}
}

func printUsage() {
fmt.Println("Usage: analyze-tags -sigma/-yara -filepath <path> [flags]")
fmt.Println("Flags:")
flag.PrintDefaults()
fmt.Println("Example:")
fmt.Println(" analyze-tags -sigma/-yara -filepath /path/to/file")
}

func formatJSONResult(identifier string, tags []string) []byte {
// Define a struct type named JSONResult to represent the JSON output fields.
type JSONResult struct {
Name string `json:"Name"`
Tags []string `json:"Tags"`
}

// Create an instance of the JSONResult struct.
jsonResult := JSONResult{
Name: identifier,
Tags: tags,
}

// Marshal the JSONResult struct into JSON data.
jsonData, err := json.MarshalIndent(jsonResult, "", " ")
if err != nil {
fmt.Println("Error encoding JSON:", err)
return nil
}

return jsonData
}

func main() {
// Ensure either Sigma or Yara flag is provided
if !useSigma && !useYara {
fmt.Println("Please provide either --sigma or --yara flag to specify the type of rules.")
printUsage()
os.Exit(1)
}

// Read the contents of the file(s) specified by the filepath flag or filecontent flag
fileContents := make(map[string][]byte)

// Check if file paths are provided
if filePath != "" {
// Check if the filepath is a directory
fileInfo, err := os.Stat(filePath)
if err != nil {
fmt.Println("Error getting file/directory info:", err)
return
}

if fileInfo.IsDir() {
// filePath is a directory, so walk the directory to read all the files inside it
filepath.Walk(filePath, func(path string, info os.FileInfo, err error) error {
if err != nil {
fmt.Println("Error accessing file:", err)
return nil
}
if !info.IsDir() {
// read file content
content, err := os.ReadFile(path)
if err != nil {
fmt.Println("Error reading file:", err)
return nil
}
fileContents[path] = content
}
return nil
})
} else {
// filePath is a file, so read its contents
fileContents[filePath], err = os.ReadFile(filePath)
if err != nil {
fmt.Println("Error reading file:", err)
return
}
}
} else if fileContent != "" {
// Check if the filecontent is a directory
lines := strings.Split(fileContent, "\n")
if len(lines) > 1 {
// fileContent is a directory, so read all lines as separate files
for _, line := range lines {
// decode base64 content
decodedContent, err := base64.StdEncoding.DecodeString(line)
if err != nil {
fmt.Println("Error decoding base64 content:", err)
return
}
fileContents[line] = decodedContent
}
} else {
// fileContent is a file, so read its content
// decode base64 content
decodedContent, err := base64.StdEncoding.DecodeString(fileContent)
if err != nil {
fmt.Println("Error decoding base64 content:", err)
return
}
fileContents["filecontent"] = decodedContent
}
}

// Loop over each file and parse its contents as a Sigma rule
for _, fileContent := range fileContents {
if useSigma {
sigmaRule, err := sigma.ParseRule(fileContent)
if err != nil {
fmt.Println("Error parsing rule:", err)
continue
}

var output string

// Print the results of the query
if outputJSON {
jsonResult := formatJSONResult(sigmaRule.Title, sigmaRule.Tags)
output = string(jsonResult)
} else {
output = "Name: " + sigmaRule.Title + " Tags: " + strings.Join(sigmaRule.Tags, " ")
}

// Check if outputPath is provided
if outputPath != "" {
// Create the output file path using the Name field from the rule
outputFilePath := filepath.Join(outputPath, fmt.Sprintf("%s.json", sigmaRule.Title))

// Write the output string to the output file
err := os.WriteFile(outputFilePath, []byte(output), 0644)
if err != nil {
fmt.Println("Error writing output to file:", err)
continue
}

fmt.Printf("Output for rule '%s' written to file: %s\n", sigmaRule.Title, outputFilePath)
} else {
fmt.Printf("%s", output)
}
} else if useYara {
yaraRuleSet, err := yara.ParseByte(fileContent)
if err != nil {
fmt.Println("Error parsing rule:", err)
continue
}

for _, yaraRule := range yaraRuleSet.Rules {

var output string

// Print the results of the query
if outputJSON {
jsonResult := formatJSONResult(yaraRule.Identifier, yaraRule.Tags)
output = string(jsonResult)
} else {
output = "Name: " + yaraRule.Identifier + " Tags: " + strings.Join(yaraRule.Tags, " ")
}

// Check if outputPath is provided
if outputPath != "" {
// Create the output file path using the Name field from the rule
outputFilePath := filepath.Join(outputPath, fmt.Sprintf("%s.json", yaraRule.Identifier))

// Write the output string to the output file
err := os.WriteFile(outputFilePath, []byte(output), 0644)
if err != nil {
fmt.Println("Error writing output to file:", err)
continue
}

fmt.Printf("Output for rule '%s' written to file: %s\n", yaraRule.Identifier, outputFilePath)
} else {
fmt.Printf("%s", output)
}
}
}
}
}
14 changes: 14 additions & 0 deletions sigma/data/output/Chafer Activity.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"Name": "Chafer Activity",
"Tags": [
"attack.persistence",
"attack.g0049",
"attack.t1053.005",
"attack.s0111",
"attack.t1543.003",
"attack.defense_evasion",
"attack.t1112",
"attack.command_and_control",
"attack.t1071.004"
]
}
46 changes: 46 additions & 0 deletions sigma/data/rules/proc_creation_win_apt_chafer_mar18.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
title: Chafer Activity
id: ce6e34ca-966d-41c9-8d93-5b06c8b97a06
#related:
# - id: 53ba33fd-3a50-4468-a5ef-c583635cfa92
# type: derived
description: Detects Chafer activity attributed to OilRig as reported in Nyotron report in March 2018
status: test
references:
- https://nyotron.com/nyotron-discovers-next-generation-oilrig-attacks/
tags:
- attack.persistence
- attack.g0049
- attack.t1053.005
- attack.s0111
- attack.t1543.003
- attack.defense_evasion
- attack.t1112
- attack.command_and_control
- attack.t1071.004
date: 2018/03/23
modified: 2021/09/19
author: Florian Roth, Markus Neis, Jonhnathan Ribeiro, Daniil Yugoslavskiy, oscd.community
logsource:
category: process_creation
product: windows
detection:
selection_process0:
CommandLine|contains: '\Service.exe'
CommandLine|endswith:
- 'i'
- 'u'
selection_process1:
- CommandLine|endswith: '\microsoft\Taskbar\autoit3.exe'
- CommandLine|startswith: 'C:\wsc.exe'
selection_process2:
Image|contains: '\Windows\Temp\DB\'
Image|endswith: '.exe'
selection_process3:
CommandLine|contains|all:
- '\nslookup.exe'
- '-q=TXT'
ParentImage|contains: '\Autoit'
condition: 1 of selection*
falsepositives:
- Unknown
level: high
31 changes: 31 additions & 0 deletions sigma/data/rules/proxy_apt40.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Copied from https://github.com/Neo23x0/sigma/blob/0603264a09fc61b0eaeac7620aab566da61ca9b8/rules/proxy/proxy_apt40.yml
# under license https://github.com/Neo23x0/sigma/blob/master/LICENSE.Detection.Rules.md
title: APT40 Dropbox Tool User Agent
id: 5ba715b6-71b7-44fd-8245-f66893e81b3d
status: experimental
description: Detects suspicious user agent string of APT40 Dropbox tool
references:
- Internal research from Florian Roth
author: Thomas Patzke
date: 2019/11/12
modified: 2020/09/02
tags:
- attack.command_and_control
- attack.t1071.001
- attack.t1043 # an old one
- attack.exfiltration
- attack.t1567.002
- attack.t1048 # an old one
logsource:
category: proxy
detection:
selection:
c-useragent: 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.143 Safari/537.36'
r-dns: 'api.dropbox.com'
condition: selection
fields:
- c-ip
- c-uri
falsepositives:
- Old browsers
level: high
Loading

0 comments on commit 31c8197

Please sign in to comment.