diff --git a/go.mod b/go.mod index b570991..ea63f2e 100644 --- a/go.mod +++ b/go.mod @@ -8,8 +8,14 @@ require ( ) require ( + bou.ke/monkey v1.0.2 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/edsrzf/mmap-go v1.1.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/saferwall/pe v1.4.4 // indirect + github.com/stretchr/objx v0.5.0 // indirect + github.com/stretchr/testify v1.8.4 // indirect go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352 // indirect golang.org/x/text v0.11.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index f31abb5..2f5441b 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,24 @@ +bou.ke/monkey v1.0.2 h1:kWcnsrCNUatbxncxR/ThdYqbytgOIArtYWqcQLQzKLI= +bou.ke/monkey v1.0.2/go.mod h1:OqickVX3tNx6t33n1xvtTtu85YN5s6cKwVug+oHMaIA= github.com/Binject/debug v0.0.0-20230508195519-26db73212a7a h1:4c0nc0krv8eh7gD809n+swLaCuFyHpxdrxwx0ZmHvBw= github.com/Binject/debug v0.0.0-20230508195519-26db73212a7a/go.mod h1:QzgxDLY/qdKlvnbnb65eqTedhvQPbaSP2NqIbcuKvsQ= +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/edsrzf/mmap-go v1.1.0 h1:6EUwBLQ/Mcr1EYLE4Tn1VdW1A4ckqCQWZBw8Hr0kjpQ= github.com/edsrzf/mmap-go v1.1.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q= +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/saferwall/pe v1.4.4 h1:Ml++7/2/Z1iKwV4zCsd1nIqTEAdUQKAetwbbcCarhOg= github.com/saferwall/pe v1.4.4/go.mod h1:SNzv3cdgk8SBI0UwHfyTcdjawfdnN+nbydnEL7GZ25s= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352 h1:CCriYyAfq1Br1aIYettdHZTy8mBTIPo7We18TuO/bak= go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= @@ -35,3 +50,7 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/api_test.go b/internal/api_test.go new file mode 100644 index 0000000..dd8f3bd --- /dev/null +++ b/internal/api_test.go @@ -0,0 +1,160 @@ +package pkg_test + +import ( + "fmt" + pkg "loldriverscan/internal" + "net/http" + "testing" + + "github.com/stretchr/testify/assert" +) + +// For testing purposes +type testlolDrivers struct { + md5Map map[string]string + sha1Map map[string]string + sha256Map map[string]string + + authentihashMd5Map map[string]string + authentihashSha1Map map[string]string + authentihashSha256Map map[string]string + + lolDriverMap map[string]pkg.LolDriver +} + +// For testing purposes +func mockfectchNormaliseData() ([]pkg.LolDriver, error) { + testdrivers := []pkg.LolDriver{ + { + ID: "driver-1", + Filename: "driver1.exe", + Path: "/sample/path/to/driver", + Status: "active", + Malicious: false, + MD5: "md5-hash", + Sha1: "sha1-hash", + Sha256: "sha256-hash", + CVEs: []string{"CVE-2021-1111", "CVE-2021-2222"}, + Authentihash: pkg.Authentihash{MD5: "auth-md5-1"}, + }, + } + return testdrivers, nil + +} + +// For testing purposes +func (tes *testlolDrivers) mockFindDriver(h pkg.Hashes, auth pkg.Authentihash) (pkg.LolDriver, error) { + id, ok := tes.authentihashMd5Map[auth.MD5] + if !ok { + id, ok = tes.authentihashSha1Map[auth.Sha1] + if !ok { + id, ok = tes.authentihashSha256Map[auth.Sha256] + if !ok { + // No matching driver found + return pkg.LolDriver{}, fmt.Errorf("no matching driver") + } + } + } + + // Found a matching driver ID, get the corresponding LolDriver object + lolDriver := tes.lolDriverMap[id] + + // Update the fields of the LolDriver object with the provided hashes + lolDriver.MD5 = h.Md5 + lolDriver.Sha1 = h.Sha1 + lolDriver.Sha256 = h.Sha256 + + // Update the fields of the Authentihash object + lolDriver.Authentihash.MD5 = auth.MD5 + lolDriver.Authentihash.Sha1 = auth.Sha1 + lolDriver.Authentihash.Sha256 = auth.Sha256 + + // Return the updated LolDriver object + return lolDriver, nil +} + +func TestDrivers(t *testing.T) { + lolDriversApiUrl := `https://www.loldrivers.io/api/drivers.json` + + // Check if the the response status returned is 200 OK + res, err := http.Get(lolDriversApiUrl) + if err != nil { + t.Fatalf("Unable to make driver requestd: %v", err) + } + + if res.StatusCode != http.StatusOK { + t.Errorf("Expected status code to be %v but got %v", http.StatusOK, res.StatusCode) + } +} + +func TestFetchApiNormaliseData(t *testing.T) { + sampledriver, err := mockfectchNormaliseData() // Mocking HTTP Get request + if err != nil { + t.Fail() // Will not occur + } + + assert.NotEmpty(t, sampledriver, "Driver list must not be empty") + assert.Equal(t, sampledriver[0].ID, "driver-1") +} + +func TestFindDriver(t *testing.T) { + + //For test + testlolDrivers := testlolDrivers{ + authentihashMd5Map: map[string]string{ + "authMd5": "testdriver-1", + "authMD5hash": "testdriver-7", + }, + authentihashSha1Map: map[string]string{"authSha1": "testdriver-2"}, + authentihashSha256Map: map[string]string{"authSha256": "testdriver-3"}, + sha1Map: map[string]string{"hashSha1": "testdriver-4"}, + sha256Map: map[string]string{"hashSha256": "testdriver-5"}, + md5Map: map[string]string{"hashMd5": "testdriver-6"}, + lolDriverMap: map[string]pkg.LolDriver{ + "testdriver-1": {ID: "testdriver-1", Authentihash: pkg.Authentihash{MD5: "authMD5hash"}}, + "testdriver-2": {ID: "testdriver-2", Authentihash: pkg.Authentihash{Sha1: "authSha1hash"}}, + "testdriver-3": {ID: "testdriver-3", Authentihash: pkg.Authentihash{Sha256: "authSha256"}}, + "testdriver-4": {ID: "testdriver-4"}, + "testdriver-5": {ID: "testdriver-5"}, + "testdriver-6": {ID: "testdriver-6"}, + }, + } + + // Case 1.1: + t.Run("No matching drivers found [INVALID HASH]", func(t *testing.T) { + _, err := testlolDrivers.mockFindDriver(pkg.Hashes{Md5: "testdriver-1"}, pkg.Authentihash{MD5: "authMD5HASH"}) + if err == nil { + t.Errorf("Expected error, got nil error") + } + }) + + // Case 1.2: + t.Run("No matching drivers found [INVALID DRIVER]", func(t *testing.T) { + _, err := testlolDrivers.mockFindDriver(pkg.Hashes{Md5: "testdriver-2"}, pkg.Authentihash{Sha256: "hashSha256"}) + if err == nil { + t.Errorf("Expected error, got nil error") + } + }) + + // Case 2: + t.Run("Valid drivers found", func(t *testing.T) { + foundDriver, err := testlolDrivers.mockFindDriver(pkg.Hashes{Md5: "testdriver-1"}, pkg.Authentihash{MD5: "authMd5"}) + if err != nil { + t.Errorf("Expected nil error, got error") + } + assert.Equal(t, foundDriver.Authentihash.MD5, "authMd5") + assert.Equal(t, foundDriver.MD5, "testdriver-1") + }) + +} + +func TestCreateVulnerableDriverFinder(t *testing.T) { + // Call the function + drivers, err := pkg.CreateVulnerableDriverFinder() + if err != nil { + t.Error("Expected nil error, got error") + } + + assert.NotEmpty(t, drivers, "Expected drivers to have values") + assert.NotNil(t, drivers, "Expected drivers to not be nil") +} diff --git a/internal/drivers_test.go b/internal/drivers_test.go new file mode 100644 index 0000000..e9f9ff5 --- /dev/null +++ b/internal/drivers_test.go @@ -0,0 +1,113 @@ +package pkg_test + +import ( + "errors" + "fmt" + pkg "loldriverscan/internal" + "testing" + + "bou.ke/monkey" + "github.com/stretchr/testify/assert" + "golang.org/x/sys/windows" + "golang.org/x/sys/windows/svc/mgr" +) + +func TestConnectToServiceManager(t *testing.T) { + mockWinHandle := windows.Handle(123) + + t.Run("ConnectToServiceManager without error", func(t *testing.T) { + // Monkey patching to mock implementation of windows.OpenSCmanager + patch := monkey.Patch(windows.OpenSCManager, func(host *uint16, database *uint16, access uint32) (windows.Handle, error) { + return mockWinHandle, nil + }) + + defer patch.Unpatch() + + mgr, err := pkg.ConnectToServiceManager() + if err != nil { + t.Error("Could not connect to service manager") + } + assert.NoError(t, err, "Unexpected error") + assert.NotNil(t, mgr, "Manager is nil") + assert.Equalf(t, mockWinHandle, mgr.Handle, "Unexpected handle value: Expected %v, got %v", mockWinHandle, mgr.Handle) + + }) + + t.Run("ConnectToServiceManager error", func(t *testing.T) { + mockwinError := windows.ERROR_ACCOUNT_RESTRICTION // Make it throws some error + patch := monkey.Patch(windows.OpenSCManager, func(host *uint16, database *uint16, access uint32) (windows.Handle, error) { + return mockWinHandle, mockwinError + }) + + defer patch.Unpatch() + + _, err := pkg.ConnectToServiceManager() + assert.Error(t, mockwinError, err) + assert.ErrorContainsf(t, err, "Account restrictions", "Expected error message %v, got %v", "Account restrictions", err) + + }) +} + +func TestListDriverServices(t *testing.T) { + + // Case 1: passing an invalid Mgr + t.Run("Invalid Mgr", func(t *testing.T) { + _, err := pkg.ListDriverServices(&mgr.Mgr{}) + assert.ErrorContains(t, err, "handle is invalid") + }) + + t.Run("Passing a valid service manager", func(t *testing.T) { + + patchListDriverService := monkey.Patch(pkg.ListDriverServices, func(m *mgr.Mgr) ([]string, error) { + if m.Handle == 123 { + return []string{"Sample service 1"}, nil + } else if m.Handle > 400 { + return []string{"Sample service 2"}, nil + } else { + return nil, fmt.Errorf("Invalid service manager handle") + } + + }) + defer patchListDriverService.Unpatch() + + res, err := pkg.ListDriverServices(&mgr.Mgr{Handle: 123}) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + assert.Containsf(t, res, "Sample service 1", "Expected service %v , got %v", "Sample service 1", res) + + }) + +} + +func TestOpenService(t *testing.T) { + OpenServicePatch := monkey.Patch(windows.OpenService, func(m windows.Handle, servicename *uint16, access uint32) (handle windows.Handle, err error) { + if m == 12345 { + return windows.Handle(12345), nil + } else { + return 0, fmt.Errorf("Invalid service Handle") + } + + }) + + defer OpenServicePatch.Unpatch() + + t.Run("Proper Service Openec", func(t *testing.T) { + mockOpenService, err := pkg.OpenService(&mgr.Mgr{Handle: 12345}, "Sample new service") + if err != nil { + t.Errorf("Unexpected error opening Handle %v", err) + } + + assert.Equalf(t, "Sample new service", mockOpenService.Name, "Expected service name %v but got %v:", "Sample new service", mockOpenService.Name) + assert.EqualValues(t, uintptr(12345), mockOpenService.Handle) + }) + + t.Run("Error new opening service", func(t *testing.T) { + mockOpenService, err := pkg.OpenService(&mgr.Mgr{Handle: 121}, "Sample new service 2") + if assert.Error(t, err) { + assert.Equal(t, errors.New("Invalid service Handle"), err) + } + assert.Nil(t, mockOpenService) + }) +} diff --git a/internal/helpers_test.go b/internal/helpers_test.go new file mode 100644 index 0000000..659afe0 --- /dev/null +++ b/internal/helpers_test.go @@ -0,0 +1,184 @@ +package pkg_test + +import ( + "crypto/md5" + "crypto/sha1" + "crypto/sha256" + "fmt" + "io/fs" + "io/ioutil" + pkg "loldriverscan/internal" + "os" + "strings" + "testing" + + "bou.ke/monkey" + "github.com/stretchr/testify/assert" +) + +func ioCopy(dst *strings.Builder, src *os.File) { + buf := make([]byte, 1024) + for { + n, err := src.Read(buf) + if n > 0 { + dst.Write(buf[:n]) + } + if err != nil { + break + } + } +} +func TestHeuristicNormalisePath(t *testing.T) { + + // Table test approach + testCases := []struct { + input string + expectedPath string + expectedErr error + }{ + { + input: `system32\example.dll`, + expectedPath: `C:\Windows\System32\example.dll`, + expectedErr: nil, + }, + { + input: `systemRoot\file.txt`, + expectedPath: `C:\Windows\file.txt`, + expectedErr: nil, + }, + { + input: `??\some\path\file.txt`, + expectedPath: `some\path\file.txt`, + expectedErr: nil, + }, + // Error with Heuristic normalisation of path + // { + // input: `system32\NonExistentFile.txt`, + // expectedPath: "", + // expectedErr: fmt.Errorf("normalised path C:\\Windows\\system32\\NonExistentFile.txt does not exist: The system cannot find the file specified."), + // }, + } + + patch := monkey.Patch(os.Stat, func(name string) (fs.FileInfo, error) { + return nil, nil + }) + defer patch.Unpatch() + + for _, test := range testCases { + normalisedPath, err := pkg.HeuristicNormalisePath(test.input) + if (err != nil && test.expectedErr == nil) || (err == nil && test.expectedErr != nil) || (err != nil && test.expectedErr != nil && err.Error() != test.expectedErr.Error()) { + t.Errorf("Unexpected error: got %v, expected %v", err, test.expectedErr) + } + + // Check if the normalized path matches the expected path + if !strings.EqualFold(normalisedPath, test.expectedPath) { + t.Errorf("Mismatched path: got %v, expected %v", normalisedPath, test.expectedPath) + } + + } +} + +func TestHashFile(t *testing.T) { + t.Run("Test Hash for existing files", func(t *testing.T) { + + // Make a temporary file + tmpFile, err := ioutil.TempFile("", "test_file.txt") + if err != nil { + t.Fatalf("Error creating temporary file: %v", err) + } + defer os.Remove(tmpFile.Name()) + + // Write some content into it + if _, err = tmpFile.WriteString("This is a test temp file"); err != nil { + t.Fatalf("Error writing to temp file: %v", err) + } + + defer tmpFile.Close() + + hash, err := pkg.HashFile(tmpFile.Name()) + if err != nil { + t.Fatalf("Unexpected error while hashing file: %v", err) + } + + expectedMd5hash := fmt.Sprintf("%x", md5.Sum([]byte("This is a test temp file"))) + expectedSha1hash := fmt.Sprintf("%x", sha1.Sum([]byte("This is a test temp file"))) + expectedSha256hash := fmt.Sprintf("%x", sha256.Sum256(([]byte("This is a test temp file")))) + + if hash.Md5 != expectedMd5hash { + t.Errorf("MD5 hash mismatch. Expected: %s, Got: %s", expectedMd5hash, hash.Md5) + } + if hash.Sha1 != expectedSha1hash { + t.Errorf("SHA1 hash mismatch. Expected: %s, Got: %s", expectedSha1hash, hash.Sha1) + } + if hash.Sha256 != expectedSha256hash { + t.Errorf("SHA256 hash mismatch. Expected: %s, Got: %s", expectedSha256hash, hash.Sha256) + } + }) + + t.Run("Hashing non existing files", func(t *testing.T) { + _, err := pkg.HashFile("testfile_2.txt") + if err == nil { + t.Error("Expected an error for non-existing file, but got nil.") + } + }) +} + +/* + * // PE parser handling - Get Auth hash + */ + +func TestPrintLolDrivers(t *testing.T) { + + // Test drivers + drivers := []pkg.LolDriver{ + { + Filename: "Driver1.sys", + Path: "C:\\Windows\\System32\\drivers\\", + Status: "Loaded", + Malicious: false, + MD5: "fcd6aa0a8c3f9dfc8efb5f49298a1109", + ID: "Driver1", + CVEs: []string{"CVE-2022-1234", "CVE-2022-5678"}, + Authentihash: pkg.Authentihash{ + Sha256: "abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234", + }, + }, + { + Filename: "MaliciousDriver.sys", + Path: "C:\\Malicious\\", + Status: "Loaded", + Malicious: true, + MD5: "jcnruadbaguvixhesoyam", + ID: "MaliciousDriver", + CVEs: []string{"CVE-2023-1111"}, + Authentihash: pkg.Authentihash{ + Sha256: "efgh5678efgh5678efgh5678efgh5678efgh5678efgh5678efgh5678efgh5678", + }, + }, + } + + // Capturing STDOUT + oldOutput := os.Stdout + r, w, _ := os.Pipe() + os.Stdout = w + + // Call the function being tested + pkg.PrintLolDrivers(drivers) + w.Close() + os.Stdout = oldOutput + + // Read the output from the buffer + var outputBuilder strings.Builder + ioCopy(&outputBuilder, r) + actualOutput := outputBuilder.String() + + for _, driver := range drivers { + assert.Contains(t, actualOutput, driver.Filename) + assert.Contains(t, actualOutput, driver.Path) + assert.Contains(t, actualOutput, driver.MD5) + assert.Contains(t, actualOutput, driver.ID) + assert.Contains(t, actualOutput, "fcd6aa0a8c3f9dfc8efb5f49298a1109") + assert.Contains(t, actualOutput, "jcnruadbaguvixhesoyam") + + } +}