Skip to content

Commit 2f3ee9a

Browse files
author
xorhex
committed
Corrected the triage connection - Added api auth to MalwareBazaar - Additional checks added for HybridAnalysis, VsShare, and VirusExchange - Updated test cases
1 parent 02c69e5 commit 2f3ee9a

8 files changed

Lines changed: 197 additions & 93 deletions

File tree

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ Use mlget to query multiple sources for a given malware hash and download it. T
1717

1818
MIT License
1919

20-
Copyright (c) 2024 @xorhex
20+
Copyright (c) 2025 @xorhex
2121

2222
Permission is hereby granted, free of charge, to any person obtaining a copy
2323
of this software and associated documentation files (the "Software"), to deal
@@ -36,3 +36,4 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
3636
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
3737
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
3838
SOFTWARE.
39+

config.go

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ func (malrepo MalwareRepoType) QueryAndDownload(repos []RepositoryConfigEntry, h
6464
fmt.Printf(" [*] %s: %s\n", mcr.Type, mcr.Host)
6565
switch malrepo {
6666
case MalwareBazaar:
67-
found, filename = malwareBazaar(mcr.Host, hash, doNotExtract, "infected")
67+
found, filename = malwareBazaar(mcr.Host, mcr.Api, hash, doNotExtract, "infected")
6868
checkedRepo = MalwareBazaar
6969
case MWDB:
7070
found, filename = mwdb(mcr.Host, mcr.Api, hash)
@@ -79,7 +79,7 @@ func (malrepo MalwareRepoType) QueryAndDownload(repos []RepositoryConfigEntry, h
7979
found, filename = inquestlabs(mcr.Host, mcr.Api, hash)
8080
checkedRepo = InQuest
8181
case HybridAnalysis:
82-
found, filename = hybridAnlysis(mcr.Host, mcr.Api, hash, doNotExtract)
82+
found, filename = hybridAnalysis(mcr.Host, mcr.Api, hash)
8383
checkedRepo = HybridAnalysis
8484
case Polyswarm:
8585
found, filename = polyswarm(mcr.Host, mcr.Api, hash)
@@ -126,8 +126,7 @@ func (malrepo MalwareRepoType) QueryAndDownload(repos []RepositoryConfigEntry, h
126126
found, filename = mwdb(mcr.Host, mcr.Api, hash)
127127
checkedRepo = UploadMWDB
128128
}
129-
// So some repos we can't download from but we want to know that it exists at that service
130-
// At the moment, this is just Any.Run but suspect more will be added as time goes on
129+
131130
if found {
132131
return found, filename, checkedRepo
133132
}
@@ -139,10 +138,6 @@ func (malrepo MalwareRepoType) VerifyRepoParams(repo RepositoryConfigEntry) bool
139138
switch malrepo {
140139
case NotSupported:
141140
return false
142-
case MalwareBazaar:
143-
if repo.Host != "" {
144-
return true
145-
}
146141
case ObjectiveSee:
147142
if repo.Host != "" {
148143
return true
@@ -181,7 +176,7 @@ func (malrepo MalwareRepoType) CreateEntry() (RepositoryConfigEntry, error) {
181176
case MWDB:
182177
default_url = "https://mwdb.cert.pl/api"
183178
case CapeSandbox:
184-
default_url = "https://www.capesandbox.com/apiv2"
179+
default_url = ""
185180
case JoeSandbox:
186181
default_url = "https://jbxcloud.joesecurity.org/api/v2"
187182
case InQuest:
@@ -193,7 +188,7 @@ func (malrepo MalwareRepoType) CreateEntry() (RepositoryConfigEntry, error) {
193188
case VirusTotal:
194189
default_url = "https://www.virustotal.com/api/v3"
195190
case Polyswarm:
196-
default_url = "https://api.polyswarm.network/v2"
191+
default_url = "https://api.polyswarm.network/v3"
197192
case ObjectiveSee:
198193
default_url = "https://objective-see.com/malware.json"
199194
case UnpacMe:
@@ -237,7 +232,7 @@ func (malrepo MalwareRepoType) CreateEntry() (RepositoryConfigEntry, error) {
237232
fmt.Println("Invalid option entered")
238233
}
239234
}
240-
if malrepo != MalwareBazaar && malrepo != ObjectiveSee {
235+
if malrepo != ObjectiveSee {
241236
fmt.Println("Enter API Key:")
242237
fmt.Print(">> ")
243238
fmt.Scanln(&api)

download.go

Lines changed: 42 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"os"
1616
"regexp"
1717
"strings"
18+
"syscall"
1819

1920
"github.com/yeka/zip"
2021
)
@@ -580,7 +581,7 @@ func polyswarm(uri string, api string, hash Hash) (bool, string) {
580581
}
581582

582583
func polyswarmDownload(uri string, api string, hash Hash) (bool, string) {
583-
query := "/download/" + url.PathEscape(hash.HashType.String()) + "/" + url.PathEscape(hash.Hash)
584+
query := "/consumer/download/" + url.PathEscape(hash.HashType.String()) + "/" + url.PathEscape(hash.Hash)
584585

585586
_, error := url.ParseQuery(query)
586587
if error != nil {
@@ -620,7 +621,7 @@ func polyswarmDownload(uri string, api string, hash Hash) (bool, string) {
620621
}
621622
}
622623

623-
func hybridAnlysis(uri string, api string, hash Hash, doNotExtract bool) (bool, string) {
624+
func hybridAnalysis(uri string, api string, hash Hash) (bool, string) {
624625
if api == "" {
625626
fmt.Println(" [!] !! Missing Key !!")
626627
return false, ""
@@ -672,12 +673,12 @@ func hybridAnlysis(uri string, api string, hash Hash, doNotExtract bool) (bool,
672673
}
673674

674675
if hash.HashType == sha256 {
675-
return hybridAnlysisDownload(uri, api, hash, doNotExtract)
676+
return hybridAnalysisDownload(uri, api, hash)
676677
}
677678
return false, ""
678679
}
679680

680-
func hybridAnlysisDownload(uri string, api string, hash Hash, extract bool) (bool, string) {
681+
func hybridAnalysisDownload(uri string, api string, hash Hash) (bool, string) {
681682
request, error := http.NewRequest("GET", uri+"/overview/"+url.PathEscape(hash.Hash)+"/sample", nil)
682683

683684
request.Header.Set("accept", "application/gzip")
@@ -699,6 +700,9 @@ func hybridAnlysisDownload(uri string, api string, hash Hash, extract bool) (boo
699700
if response.StatusCode == http.StatusForbidden {
700701
fmt.Printf(" [!] Not authorized. Check the URL and APIKey in the config.\nCould also be that the sample is not allowed to be downloaded.\n")
701702
return false, ""
703+
} else if response.StatusCode == http.StatusNotFound {
704+
fmt.Printf(" [!] Hash not found")
705+
return false, ""
702706
} else if response.StatusCode != http.StatusOK {
703707
return false, ""
704708
}
@@ -812,52 +816,18 @@ func traigeDownload(uri string, api string, sampleId string, hash Hash) (bool, s
812816
fmt.Println(error)
813817
return false, ""
814818
}
815-
// Triage will download an archive file that contians the hash in question sometimes versus the actual sample being requested
816-
hashMatch, _ := hash.ValidateFile(hash.Hash)
819+
// Triage will download the sample directly - no password protected zip file.
820+
hashMatch, dhash := hash.ValidateFile(hash.Hash)
817821
if !hashMatch {
818-
files, err := extractPwdZip(hash.Hash, "", false, hash)
819-
if err != nil {
820-
fmt.Println(error)
821-
return false, ""
822-
}
822+
fmt.Printf(" [!] Sample ID %s (%s)\n contains the file in question, further processing of the sample is needed to get the hash requested.\n", sampleId, dhash)
823+
//ok := YesNoPrompt(fmt.Sprintf(" [?] Keep the file %s or delete it and continue looking for sample?", dhash), false)
824+
//if ok {
825+
return true, hash.Hash
826+
// } else {
827+
//return false, ""
828+
//}
823829

824-
found := false
825-
826-
fmt.Printf(" [-] The downloaded file appears to be a zip file in which the requested file should be located.\n")
827-
for _, f := range files {
828-
fmt.Printf(" [-] Checking file: %s\n", f.Name)
829-
hashMatch, _ = hash.ValidateFile(f.Name)
830-
if !hashMatch {
831-
err = os.Remove(f.Name)
832-
if err != nil {
833-
fmt.Println(" [!] Error when deleting file: ", f.Name)
834-
fmt.Println(err)
835-
}
836-
} else {
837-
fmt.Printf(" [+] %s is hash %s\n", f.Name, hash.Hash)
838-
err = os.Rename(f.Name, hash.Hash)
839-
if err != nil {
840-
fmt.Println(" [!] Error when renaming file: ", f.Name)
841-
fmt.Println(err)
842-
} else {
843-
found = true
844-
}
845-
}
846-
}
847-
if !found {
848-
fmt.Printf(" [!] Hash %s not found\n", hash.Hash)
849-
err = os.Remove(hash.Hash)
850-
if err != nil {
851-
fmt.Println(" [!] Error when deleting file: ", hash.Hash)
852-
fmt.Println(err)
853-
}
854-
return false, ""
855-
} else {
856-
fmt.Printf(" [+] Found %s\n", hash.Hash)
857-
return true, hash.Hash
858-
}
859830
} else {
860-
fmt.Printf(" [+] Downloaded %s\n", hash.Hash)
861831
return true, hash.Hash
862832
}
863833
}
@@ -905,7 +875,7 @@ func malshareDownload(uri string, api string, hash Hash) (bool, string) {
905875
}
906876
}
907877

908-
func malwareBazaar(uri string, hash Hash, doNotExtract bool, password string) (bool, string) {
878+
func malwareBazaar(uri string, api string, hash Hash, doNotExtract bool, password string) (bool, string) {
909879
if hash.HashType != sha256 {
910880
fmt.Printf(" [-] Looking up sha256 hash for %s\n", hash.Hash)
911881

@@ -949,19 +919,26 @@ func malwareBazaar(uri string, hash Hash, doNotExtract bool, password string) (b
949919
}
950920

951921
if hash.HashType == sha256 {
952-
return malwareBazaarDownload(uri, hash, doNotExtract, password)
922+
return malwareBazaarDownload(uri, api, hash, doNotExtract, password)
953923
}
954924
return false, ""
955925
}
956926

957-
func malwareBazaarDownload(uri string, hash Hash, doNotExtract bool, password string) (bool, string) {
927+
func malwareBazaarDownload(uri string, api string, hash Hash, doNotExtract bool, password string) (bool, string) {
958928
query := "query=get_file&sha256_hash=" + hash.Hash
959-
values, err := url.ParseQuery(query)
960-
if err != nil {
961-
fmt.Println(err)
929+
values, error := url.ParseQuery(query)
930+
if error != nil {
931+
fmt.Println(error)
962932
return false, ""
963933
}
964934

935+
request, error := http.NewRequest("POST", uri, nil)
936+
if error != nil {
937+
fmt.Println(error)
938+
return false, ""
939+
}
940+
941+
request.Header.Set("Auth-Key", api)
965942
client := &http.Client{}
966943

967944
response, err := client.PostForm(uri, values)
@@ -976,14 +953,13 @@ func malwareBazaarDownload(uri string, hash Hash, doNotExtract bool, password st
976953
if response.StatusCode == http.StatusMethodNotAllowed {
977954
if !strings.HasSuffix(uri, "/") {
978955
fmt.Printf(" [!] Trying again with a trailing slash: %s/\n", uri)
979-
return malwareBazaarDownload(uri+"/", hash, doNotExtract, password)
956+
return malwareBazaarDownload(uri+"/", api, hash, doNotExtract, password)
980957
} else {
981958
fmt.Printf(" [!] Normally the response code: %s means that the provided URL %s needs a trailing slash (to avoid the redirect), but this already has a trailing slash.\nPlease file a bug report at https://github.com/xorhex/mlget/issues\n", response.Status, uri)
982959
}
983960
} else {
984961
fmt.Printf(" [!] %s\n", response.Status)
985962
}
986-
return false, ""
987963
}
988964

989965
err = writeToFile(response.Body, hash.Hash+".zip")
@@ -1108,6 +1084,9 @@ func vxsharedownload(uri string, api string, hash Hash, doNotExtract bool, passw
11081084

11091085
if response.StatusCode == 404 {
11101086
return false, ""
1087+
} else if response.StatusCode == http.StatusInternalServerError {
1088+
fmt.Printf(" [!] Internal service error. Skipping.\n")
1089+
return false, ""
11111090
} else if response.StatusCode == 204 {
11121091
fmt.Printf(" [!] Request rate limit exceeded. You are making more requests than are allowed or have exceeded your quota.\n")
11131092
return false, ""
@@ -1361,8 +1340,13 @@ func assemblyline(uri string, user string, api string, ignoretlserrors bool, has
13611340
client := &http.Client{Transport: tr}
13621341
response, error := client.Do(request)
13631342
if error != nil {
1364-
fmt.Println(error)
1365-
return false, ""
1343+
if errors.Is(error, syscall.ECONNREFUSED) {
1344+
fmt.Println(" [!] Connection Refused. Is the service online?")
1345+
return false, ""
1346+
} else {
1347+
fmt.Println(error)
1348+
return false, ""
1349+
}
13661350
}
13671351
defer response.Body.Close()
13681352

@@ -1519,6 +1503,7 @@ func virusexchangeDownload(uri string, hash Hash) (bool, string) {
15191503
defer response.Body.Close()
15201504

15211505
if response.StatusCode == http.StatusNotFound {
1506+
fmt.Printf(" [!] Invalid download link returned by the API.\n")
15221507
return false, ""
15231508
} else if response.StatusCode == http.StatusForbidden {
15241509
fmt.Printf(" [!] Not authorized for some reason.\n")

go.mod

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,14 @@ module xorhex.com/mlget
33
go 1.21.3
44

55
require (
6-
github.com/kr/pretty v0.1.0
7-
github.com/spf13/pflag v1.0.5
6+
github.com/spf13/pflag v1.0.6
87
github.com/yeka/zip v0.0.0-20180914125537-d046722c6feb
8+
golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2
9+
gopkg.in/yaml.v2 v2.4.0
10+
)
11+
12+
require (
13+
github.com/kr/pretty v0.3.1 // indirect
914
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect
10-
golang.org/x/exp v0.0.0-20221217163422-3c43f8badb15
1115
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
12-
gopkg.in/yaml.v2 v2.4.0
1316
)

hashes.go

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package main
22

33
import (
4+
"bufio"
45
"crypto"
56
"errors"
67
"fmt"
@@ -160,22 +161,12 @@ func (h Hash) Validate(bytes []byte) (bool, string) {
160161
}
161162

162163
func deleteInvalidFile(filename string) {
163-
if !alwaysDeleteInvalidFile {
164-
var delete_file string
165-
fmt.Printf(" [?] Delete invalid file? [A/Y/n] Always delete/Yes, this time/No, not this time\n")
166-
fmt.Scanln(&delete_file)
167-
if strings.ToUpper(delete_file) == "Y" || delete_file == "" || strings.ToUpper(delete_file) == "A" {
168-
os.Remove(filename)
169-
fmt.Printf(" [!] Deleted invalid file\n")
170-
if strings.ToUpper(delete_file) == "A" {
171-
alwaysDeleteInvalidFile = true
172-
}
173-
} else {
174-
fmt.Printf(" [!] Keeping invalid file\n")
175-
}
176-
} else {
164+
ok := YesNoAlwaysDeleteInvalidFilePrompt(" [?] Delete invalid file?", true)
165+
if ok {
177166
os.Remove(filename)
178167
fmt.Printf(" [!] Deleted invalid file\n")
168+
} else {
169+
fmt.Printf(" [!] Keeping invalid file\n")
179170
}
180171
}
181172

@@ -220,3 +211,37 @@ func extractHashes(text string) ([]string, error) {
220211

221212
return hashes, nil
222213
}
214+
215+
func YesNoAlwaysDeleteInvalidFilePrompt(label string, def bool) bool {
216+
if alwaysDeleteInvalidFile {
217+
return true
218+
}
219+
220+
choices := "a - always /Y - Yes /n - no"
221+
if !def {
222+
choices = "a - always /y - yes /N - No"
223+
}
224+
225+
r := bufio.NewReader(os.Stdin)
226+
var s string
227+
228+
for {
229+
fmt.Fprintf(os.Stderr, "%s (%s) ", label, choices)
230+
s, _ = r.ReadString('\n')
231+
s = strings.TrimSpace(s)
232+
if s == "" {
233+
return def
234+
}
235+
s = strings.ToLower(s)
236+
if s == "y" || s == "yes" || s == "Y" {
237+
return true
238+
}
239+
if s == "n" || s == "no" || s == "N" {
240+
return false
241+
}
242+
if s == "a" || s == "always" || s == "A" {
243+
alwaysDeleteInvalidFile = true
244+
return true
245+
}
246+
}
247+
}

mlget-test-config/samples.yaml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,13 @@ test 19:
5454
hash: b78e786091f017510b44137961f3074fe7d5f950
5555
test 20:
5656
name: TestTriageV2
57-
hash: 5eaaf8ac2d358c2d7065884b7994638fee3987f02474e54467f14b010a18d028
57+
hash: 0d8d46ec44e737e6ef6cd7df8edf95d83807e84be825ef76089307b399a6bcbb
5858
test 21:
5959
name: TestVirusExchange
60-
hash: ad5156e1e0285de9348d9b2c4649d9f6f39bac89567f833b1a8ba3d26468fc84
60+
hash: 5d11b9be5daa65fe010cc7900d5d5eead7f62a7885e862a5971a005856ae9878
61+
test 22:
62+
name: TestHybridAnalysisNotFound
63+
hash: fc17c021f18ec73d1544ad46dde6a1f1949f126bf3e75f97e241f982e2b07c86
64+
test 23:
65+
name: TestVirusExchangeV2
66+
hash: 0cacdb88b24bd34b9d8ef600b06814f76206e60e70f975c8e4bdaa1ab7cebb80

0 commit comments

Comments
 (0)