Skip to content

Commit

Permalink
rework internals (hashmaps > loops), smaller optimizations
Browse files Browse the repository at this point in the history
  • Loading branch information
rtfmkiesel committed Feb 17, 2024
1 parent ff6fdca commit 9f3274d
Show file tree
Hide file tree
Showing 12 changed files with 329 additions and 385 deletions.
42 changes: 18 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,41 +3,35 @@

The first *blazingly fast* client for [LOLDrivers](https://github.com/magicsword-io/LOLDrivers) (Living Off The Land Drivers) by [MagicSword](https://www.magicsword.io/). Scan your computer for known vulnerable and known malicious Windows drivers.


![](demo.gif)


## Usage
```
LOLDrivers-client.exe [OPTIONS]
Options:
-m, --mode Operating Mode {online, local, internal}
online = Download the newest driver set (default)
local = Use a local drivers.json file (requires '-f')
internal = Use the built-in driver set (can be outdated)
-f, --driver-file File path to 'drivers.json', when running in local mode
-d, --scan-dir Directory to scan for drivers (default: Windows driver folders)
Files which cannot be opened or read will be silently ignored
-l, --scan-limit Size limit for files to scan in MB (default: 10)
Be aware, higher values greatly increase runtime & CPU usage
-s, --silent Will only output found files for easy parsing (default: false)
-j, --json Format output as JSON (default: false)
-w, --workers Number of "threads" to spawn (default: 20)
-h, --help Shows this text
Usage:
loldrivers-client.exe [flags]
Flags:
OPERATING MODE:
-m, -mode string Operating Mode {online, local, internal} (default "online")
-f, -driver-file string File path to 'drivers.json', when mode == local
SCAN OPTIONS:
-d, -scan-dir string Directory to scan for drivers (default: Windows driver folders)
-l, -scan-size int Size limit for files to scan in MB (default 10)
-w, -workers int Number of checksum "threads" to spawn (default 20)
-s, -surpress-errors Do not show file read errors when calculating checksums
OUTPUT OPTIONS:
-g, -grepable Will only output found files for easy parsing
-j, -json Format output as JSON
```

## Installation
### Binaries
Download the prebuilt binaries [here](https://github.com/rtfmkiesel/loldrivers-client/releases).

## Build from source
```bash
```
git clone https://github.com/rtfmkiesel/loldrivers-client
cd loldrivers-client
go mod tidy
Expand Down
59 changes: 36 additions & 23 deletions cmd/loldrivers-client/loldrivers-client.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,54 +3,67 @@
package main

import (
"encoding/json"
"sync"
"time"

"github.com/fatih/color"
"github.com/rtfmkiesel/loldrivers-client/pkg/checksums"
"github.com/rtfmkiesel/loldrivers-client/pkg/logger"
"github.com/rtfmkiesel/loldrivers-client/pkg/loldrivers"
"github.com/rtfmkiesel/loldrivers-client/pkg/options"
"github.com/rtfmkiesel/loldrivers-client/pkg/result"
)

func main() {
// Parse the command line options
opt, err := options.Parse()
if err != nil {
logger.Fatal(err)
}

// Load the drivers and their hashes
if err = loldrivers.LoadDrivers(opt.Mode, opt.LocalDriversPath); err != nil {
if err = loldrivers.LoadDrivers(opt.Mode, opt.ModeLocalFilePath); err != nil {
logger.Fatal(err)
}

// Set up the checksum runners
chanFiles := make(chan string)
chanResults := make(chan result.Result)
wgRunner := new(sync.WaitGroup)
for i := 0; i <= opt.Workers; i++ {
go checksums.CalcRunner(wgRunner, chanFiles, chanResults)
wgRunner.Add(1)
chanFilepaths := make(chan string)
chanResults := make(chan *checksums.Result)
wgChecksums := new(sync.WaitGroup)
for i := 0; i <= opt.ScanWorkers; i++ {
go checksums.Runner(wgChecksums, chanFilepaths, chanResults, opt.ScanShowErrors)
wgChecksums.Add(1)
}

// Set up the one output runner
wgResults := new(sync.WaitGroup)
go result.OutputRunner(wgResults, chanResults, opt.OutputMode)
wgResults.Add(1)
wgOutput := new(sync.WaitGroup)
go func() {
defer wgOutput.Done()
for result := range chanResults {
switch opt.OutputMode {
case "grep":
logger.PlainStdout("%s", result.Filepath)
case "json":
jsonOutput, err := json.Marshal(result)
if err != nil {
logger.Error(err)
continue
}
logger.PlainStdout("%s", string(jsonOutput))
default:
logger.Custom("VUL", color.FgRed, "%s (https://loldrivers.io/drivers/%s)", result.Filepath, result.Driver.ID)
}
}
}()
wgOutput.Add(1)

// Get all files from subfolders and send them to the checksum runners via a channel
for _, path := range opt.ScanDirectories {
if err := checksums.FileWalker(path, opt.ScanSizeLimit, chanFiles); err != nil {
logger.Fatal(err)
if err := checksums.DirectoryWalker(path, opt.ScanSizeLimit, chanFilepaths); err != nil {
logger.Error(err)
continue
}
}

close(chanFiles)
wgRunner.Wait()

close(chanFilepaths)
wgChecksums.Wait()
close(chanResults)
wgResults.Wait()
wgOutput.Wait()

logger.Logf("[+] Done, took %s", time.Since(opt.StartTime))
logger.Info("Finished in %s", time.Since(opt.StartTime))
}
Binary file modified demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
26 changes: 16 additions & 10 deletions pkg/checksums/checksums.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
package checksums

import (
"crypto/md5"
"crypto/sha1"
"crypto/md5" //#nosec G501
"crypto/sha1" //#nosec G505
"crypto/sha256"
"encoding/hex"
"io"
"os"
)

// calcSHA256() will return the SHA256 checksum of filePath
// Calculate the SHA2 / SHA256 of a file at filePath
//
// Returns the hexadecimal string of the checksum
func calcSHA256(filePath string) (string, error) {
file, err := os.Open(filePath)
file, err := os.Open(filePath) //#nosec G304
if err != nil {
return "", err
}
Expand All @@ -26,15 +28,17 @@ func calcSHA256(filePath string) (string, error) {
return hex.EncodeToString(checksum), nil
}

// calcSHA1() will return the SHA1 checksum of filePath
// Calculate the SHA1 / SHA128 of a file at filePath
//
// Returns the hexadecimal string of the checksum
func calcSHA1(filePath string) (string, error) {
file, err := os.Open(filePath)
file, err := os.Open(filePath) //#nosec G304
if err != nil {
return "", err
}
defer file.Close()

hash := sha1.New()
hash := sha1.New() //#nosec G401
if _, err := io.Copy(hash, file); err != nil {
return "", err
}
Expand All @@ -43,15 +47,17 @@ func calcSHA1(filePath string) (string, error) {
return hex.EncodeToString(checksum), nil
}

// calcMD5() will return the MD5 checksum of filePath
// Calculate the MD5 of a file at filePath
//
// Returns the hexadecimal string of the checksum
func calcMD5(filePath string) (string, error) {
file, err := os.Open(filePath)
file, err := os.Open(filePath) //#nosec G304
if err != nil {
return "", err
}
defer file.Close()

hash := md5.New()
hash := md5.New() //#nosec G401
if _, err := io.Copy(hash, file); err != nil {
return "", err
}
Expand Down
14 changes: 6 additions & 8 deletions pkg/checksums/filewalker.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,11 @@ import (
"github.com/rtfmkiesel/loldrivers-client/pkg/logger"
)

// checksums.FileWalker() will recursively send files from path, who are smaller than sizeLimit, to outputChannel
func FileWalker(path string, sizeLimit int64, outputChannel chan<- string) (err error) {
logger.Logf("[*] Searching for files in %s", path)
// Will recursively walk and send filepaths from root, who are smaller than sizeLimit to filepaths
func DirectoryWalker(root string, sizeLimit int, filepaths chan<- string) (err error) {
logger.Info("Checking %s", root)

// Walk over every file in a given folder
err = filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
err = filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
if err != nil {
// Ignore a file if we get "Access is denied" error
if os.IsPermission(err) {
Expand All @@ -38,12 +37,11 @@ func FileWalker(path string, sizeLimit int64, outputChannel chan<- string) (err
}

// Skip files larger than the specified size limit
if info.Size() > sizeLimit*1024*1024 {
if info.Size() > int64(sizeLimit)*1024*1024 {
return nil
}

// Send to the channel
outputChannel <- path
filepaths <- path
return nil
})

Expand Down
83 changes: 48 additions & 35 deletions pkg/checksums/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,55 +5,68 @@ import (

"github.com/rtfmkiesel/loldrivers-client/pkg/logger"
"github.com/rtfmkiesel/loldrivers-client/pkg/loldrivers"
"github.com/rtfmkiesel/loldrivers-client/pkg/result"
)

// checksums.CalcRunner() is used as a go func for calculating and comparing file checksums
// from a chanJobs. If a calculated checksum matches a loaded checksum a result.Result will be sent to chanResults
func CalcRunner(wg *sync.WaitGroup, chanJobs <-chan string, chanResults chan<- result.Result) {
type Result struct {
Filepath string `json:"Filepath"`
Checksum string `json:"Checksum"`
Driver *loldrivers.Driver `json:"LOLDrivers-Entry"`
}

// Is used as a go func for calculating and comparing file checksums
func Runner(wg *sync.WaitGroup, filepaths <-chan string, results chan<- *Result, silenceErrors bool) {
defer wg.Done()

for job := range chanJobs {
// SHA256
sha256, err := calcSHA256(job)
if err != nil {
logger.Error(err)
} else if driver := loldrivers.MatchHash(sha256); driver != nil {
chanResults <- result.Result{
Filepath: job,
Checksum: sha256,
Driver: *driver,
}
for filepath := range filepaths {
// The order of the hash checks was choosen based on the amount of hashes upon writing this bit
// SHA1 > SHA2 > MD5

sha1, err := calcSHA1(filepath)
if err != nil && !silenceErrors {
logger.Error(err)
continue
}
} else {
matched, driver := loldrivers.MatchHash(sha1)
if matched {
results <- &Result{
Filepath: filepath,
Checksum: sha1,
Driver: driver,
}

// SHA1
sha1, err := calcSHA1(job)
if err != nil {
logger.Error(err)
} else if driver := loldrivers.MatchHash(sha1); driver != nil {
chanResults <- result.Result{
Filepath: job,
Checksum: sha1,
Driver: *driver,
continue // No need to check others as there was a match
}
}

sha256, err := calcSHA256(filepath)
if err != nil && !silenceErrors {
logger.Error(err)
continue
} else {
matched, driver := loldrivers.MatchHash(sha256)
if matched {
results <- &Result{
Filepath: filepath,
Checksum: sha256,
Driver: driver,
}

continue // No need to check others as there was a match
}
}

// MD5
md5, err := calcMD5(job)
if err != nil {
md5, err := calcMD5(filepath)
if err != nil && !silenceErrors {
logger.Error(err)
} else if driver := loldrivers.MatchHash(md5); driver != nil {
chanResults <- result.Result{
Filepath: job,
Checksum: md5,
Driver: *driver,
} else {
matched, driver := loldrivers.MatchHash(md5)
if matched {
results <- &Result{
Filepath: filepath,
Checksum: md5,
Driver: driver,
}
}

continue
}
}
}
Loading

0 comments on commit 9f3274d

Please sign in to comment.