├── .idea
├── go-malwarebazaar.iml
├── modules.xml
├── vcs.xml
└── workspace.xml
├── README.md
├── api
├── errors.go
├── responses.go
├── routes.go
└── types.go
├── bazaar.go
├── client
├── download.go
├── generic.go
├── metadata.go
└── query.go
├── go.mod
└── go.sum
/.idea/go-malwarebazaar.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/workspace.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 | true
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 🛒 go-malwarebazaar
2 | A Golang wrapper around MalwareBazaar's public API. The library supports several API endpoints, including querying and downloading. You can grab an API key for this great service [here](https://bazaar.abuse.ch/login/).
3 |
4 | ## Getting Started
5 | Make sure that your GOPATH is setup correctly, then pull the MalwareBazaar library from this GitHub repository.
6 | ```
7 | $ go get github.com/LloydLabs/go-malwarebazaar
8 | ```
9 |
10 | ## Exported Methods
11 | An outline of the exposed methods within this API wrapper are below.
12 | | Method | Description |
13 | |---------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
14 | | QueryTag | Query a tag on MalwareBazaar, with `tag` (string, e.g. `Trickbot`) as the parameter to query, and `limit` (int) as the limit of results to return |
15 | | QueryRecent | Query the most recent samples that have been submit to MalwareBazaar, with `amount` (int) as the limit of results to return |
16 | | QueryHash | Query an MD5, SHA-1 or SHA-256 hash, returns information such as YARA hits, further IOCs, sandbox information & more |
17 | | QueryYara | Query a MalwareBazaar defined YARA rule, e.g. `ach_Heodo_doc_20210105`, with a limit under `limit` (int) |
18 | | QuerySigature | Query a signature, e.g. `Heodo`, with `amount` (int) as the limit of results to return |
19 | | AddComment | Add a comment to a MalwareBazaar entry, with `hash` (string, SHA-256) as the entry, and `comment` (string) as the comment. |
20 | | DownloadFile | Download a file to disk from MalwareBazaar, with `hash` (string, SHA-256) as the entry, and `location` (string) as the destination. This wrapper will automatically decrypt and extract the sample from the archive. |
21 |
22 | ## Examples
23 | ```go
24 | // settings
25 | const Key := ""
26 |
27 | // initialise our client
28 | b, err := client.NewBazaar(Key)
29 | if err != nil {
30 | t.Errorf("failed to create bazaar")
31 | }
32 |
33 | // query trickbot tag, max 50
34 | x, err := b.QueryTag("TrickBot", 50)
35 | if err != nil {
36 | t.Errorf("failed to query tag %s\n", err.Error())
37 | }
38 |
39 | // print all 50 most recent trickbot hashes
40 | for _, hash := range x.Data {
41 | fmt.Printf("Recent Trickbot binary SHA-256: %s\nFirst seen: %s\n", hash.Sha1Hash, hash.FirstSeen)
42 | }
43 |
44 | // download a Qakbot sample, file to write to same as hash
45 | hash := "c5481c003005773954741e3fdd9cf0cc77d31fe59851321ddde80b41fe7ef0a3"
46 | err = b.DownloadFile(hash, hash)
47 | if err != nil {
48 | t.Errorf("failed to download sample %s\n", err.Error())
49 | }
50 | ```
51 |
--------------------------------------------------------------------------------
/api/errors.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | var ErrorTypes = map[string]string{
4 | "http_post_expected": "illegal SHA-256 provided",
5 | "no_api_key": "you did not provide an API key",
6 | "user_blacklisted": "your API key is blacklisted",
7 | "illegal_sha256_hash": "illegal SHA-256 provided",
8 | "no_sha256_hash": "no SHA-256 provided",
9 | "file_not_found": "this file does not exist in MalwareBazaar",
10 | "clamav_not_found": "the clamav signature you wanted to query is unknown to MalwareBazaar",
11 | "illegal_clamav": "the text you provided is not a valid ClamAV signature",
12 | "no_results": "your query yield no results",
13 | "illegal_yara_rule": "the text you provided is not a valid yara_rule",
14 | "no_yara_rule_provided": "you did not provide a yara_rule",
15 | "yara_not_found": "the yara_rule you wanted to query is unknown to MalwareBazaar",
16 | "illegal_issuer_cn": "the value you provided is not a valid issuer_cn",
17 | "no_issuer_cn": "you did not provide a issuer_cn",
18 | }
19 |
--------------------------------------------------------------------------------
/api/responses.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | type QueryResponse struct {
4 | QueryStatus string `json:"query_status"`
5 | Data []struct {
6 | Sha256Hash string `json:"sha256_hash"`
7 | Sha3384Hash string `json:"sha3_384_hash"`
8 | Sha1Hash string `json:"sha1_hash"`
9 | Md5Hash string `json:"md5_hash"`
10 | FirstSeen string `json:"first_seen"`
11 | LastSeen interface{} `json:"last_seen"`
12 | FileName string `json:"file_name"`
13 | FileSize int `json:"file_size"`
14 | FileTypeMime string `json:"file_type_mime"`
15 | FileType string `json:"file_type"`
16 | Reporter string `json:"reporter"`
17 | Anonymous int `json:"anonymous"`
18 | Signature string `json:"signature"`
19 | Imphash string `json:"imphash"`
20 | Tlsh string `json:"tlsh"`
21 | Ssdeep string `json:"ssdeep"`
22 | Tags []string `json:"tags"`
23 | CodeSign []interface{} `json:"code_sign"`
24 | Intelligence struct {
25 | Clamav []string `json:"clamav"`
26 | Downloads string `json:"downloads"`
27 | Uploads string `json:"uploads"`
28 | Mail interface{} `json:"mail"`
29 | } `json:"intelligence"`
30 | } `json:"data"`
31 | }
32 |
33 | type GenericResponse struct {
34 | QueryStatus string `json:"query_status"`
35 | }
36 |
--------------------------------------------------------------------------------
/api/routes.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | const (
4 | BaseUrl = "https://mb-api.abuse.ch/api/v1/"
5 | )
6 |
--------------------------------------------------------------------------------
/api/types.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | const (
4 | QueryTag = "get_taginfo"
5 | QueryLatest = "get_recent"
6 | QueryHash = "get_info"
7 | QueryYara = "get_yarainfo"
8 | DownloadFile = "get_file"
9 | AddComment = "add_comment"
10 | )
11 |
--------------------------------------------------------------------------------
/bazaar.go:
--------------------------------------------------------------------------------
1 | package go_malwarebazaar
2 |
3 | import (
4 | "github.com/LloydLabs/go-malwarebazaar/client"
5 | )
6 |
7 | func NewBazaar(key string) (instance *client.Bazaar, err error) {
8 | instance = new(client.Bazaar)
9 |
10 | instance.Client, err = client.NewClient(key)
11 | if err != nil {
12 | return instance, err
13 | }
14 |
15 | instance.Client.Key = key
16 |
17 | return instance, nil
18 | }
19 |
--------------------------------------------------------------------------------
/client/download.go:
--------------------------------------------------------------------------------
1 | package client
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "errors"
7 | "github.com/LloydLabs/go-malwarebazaar/api"
8 | "github.com/alexmullins/zip"
9 | "io/ioutil"
10 | "net/url"
11 | )
12 |
13 | const (
14 | ZipPassword = "infected"
15 | )
16 |
17 | func (b *Bazaar) DownloadFile(hash string) (out []byte, err error) {
18 | if len(hash) != 64 {
19 | return out, errors.New("the hash must be SHA-256")
20 | }
21 |
22 | data := url.Values{
23 | "query": {api.DownloadFile},
24 | "sha256_hash": {hash},
25 | }
26 |
27 | r, err := b.Client.Request(data)
28 | if err != nil {
29 | return out, err
30 | }
31 |
32 | hashData, err := ioutil.ReadAll(r.Body)
33 | if err != nil {
34 | return out, err
35 | }
36 |
37 | if r.Header.Get("Content-Type") == "application/json" {
38 | var resp api.GenericResponse
39 | if err = json.NewDecoder(r.Body).Decode(&resp); err == nil {
40 | if message, ok := api.ErrorTypes[resp.QueryStatus]; ok {
41 | return out, errors.New(message)
42 | }
43 |
44 | return out, errors.New("unknown error occurred")
45 | }
46 | }
47 |
48 | if r.Header.Get("Content-Type") != "application/zip" {
49 | return out, errors.New("invalid content-type returned, failed to recognise header")
50 | }
51 |
52 | reader := bytes.NewReader(hashData)
53 | archive, err := zip.NewReader(reader, int64(len(hashData)))
54 | if err != nil {
55 | return out, err
56 | }
57 |
58 | if len(archive.File) != 1 {
59 | return out, errors.New("the file does not exist within the archive")
60 | }
61 |
62 | if zipFile := archive.File[0]; zipFile.IsEncrypted() {
63 | zipFile.SetPassword(ZipPassword)
64 |
65 | f, err := zipFile.Open()
66 | if err != nil {
67 | return out, err
68 | }
69 |
70 | defer f.Close()
71 |
72 | out, err = ioutil.ReadAll(f)
73 | if err != nil {
74 | return out, err
75 | }
76 | }
77 |
78 | return out, nil
79 | }
80 |
--------------------------------------------------------------------------------
/client/generic.go:
--------------------------------------------------------------------------------
1 | package client
2 |
3 | import (
4 | "bytes"
5 | "github.com/LloydLabs/go-malwarebazaar/api"
6 | "net"
7 | "net/http"
8 | "net/url"
9 | "time"
10 | )
11 |
12 | type Bazaar struct {
13 | Client *GenericClient
14 | }
15 |
16 | type GenericClient struct {
17 | Client *http.Client
18 | Key string
19 | }
20 |
21 | func NewClient(key string) (*GenericClient, error) {
22 | c := new(GenericClient)
23 |
24 | c.Client = &http.Client{Transport: &http.Transport{
25 | DialContext: (&net.Dialer{
26 | Timeout: 5 * time.Second,
27 | }).DialContext,
28 | TLSHandshakeTimeout: 5 * time.Second,
29 | }}
30 |
31 | c.Key = key
32 |
33 | return c, nil
34 | }
35 |
36 | func (c *GenericClient) Request(data url.Values) (resp *http.Response, err error) {
37 | req, err := http.NewRequest(http.MethodPost, api.BaseUrl, bytes.NewBufferString(data.Encode()))
38 | if err != nil {
39 | return resp, err
40 | }
41 |
42 | req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
43 |
44 | if len(c.Key) > 0 {
45 | req.Header.Add("API-KEY", c.Key)
46 | }
47 |
48 | resp, err = c.Client.Do(req)
49 | if err != nil {
50 | return resp, err
51 | }
52 |
53 | return resp, nil
54 | }
55 |
--------------------------------------------------------------------------------
/client/metadata.go:
--------------------------------------------------------------------------------
1 | package client
2 |
3 | import (
4 | "encoding/json"
5 | "errors"
6 | "github.com/LloydLabs/go-malwarebazaar/api"
7 | "net/url"
8 | )
9 |
10 | func (b *Bazaar) AddComment(hash string, comment string) error {
11 | if len(hash) != 64 {
12 | return errors.New("the hash must be SHA-256")
13 | }
14 |
15 | data := url.Values{
16 | "query": {api.AddComment},
17 | "sha256_hash": {hash},
18 | "comment": {comment},
19 | }
20 |
21 | var resp api.GenericResponse
22 | r, err := b.Client.Request(data)
23 | if err != nil {
24 | return err
25 | }
26 |
27 | if err = json.NewDecoder(r.Body).Decode(&resp); err != nil {
28 | return errors.New("failed to unmarshal the response body")
29 | }
30 |
31 | if resp.QueryStatus != "success" {
32 | if message, ok := api.ErrorTypes[resp.QueryStatus]; ok {
33 | return errors.New(message)
34 | }
35 |
36 | return errors.New("unknown error occurred")
37 | }
38 |
39 | return nil
40 | }
41 |
--------------------------------------------------------------------------------
/client/query.go:
--------------------------------------------------------------------------------
1 | package client
2 |
3 | import (
4 | "encoding/json"
5 | "errors"
6 | "github.com/LloydLabs/go-malwarebazaar/api"
7 | "net/url"
8 | "strconv"
9 | )
10 |
11 | func (b *Bazaar) QueryGeneric(data url.Values) (api.QueryResponse, error) {
12 | var resp api.QueryResponse
13 | r, err := b.Client.Request(data)
14 | if err != nil {
15 | return resp, errors.New("failed to request the query")
16 | }
17 |
18 | if err = json.NewDecoder(r.Body).Decode(&resp); err != nil {
19 | return resp, errors.New("failed to unmarshal the response body")
20 | }
21 |
22 | if resp.QueryStatus != "ok" {
23 | return resp, errors.New("query endpoint failed with error")
24 | }
25 |
26 | return resp, nil
27 | }
28 |
29 | func (b *Bazaar) QueryTag(tag string, limit int) (api.QueryResponse, error) {
30 | if len(tag) == 0 || (limit <= 0 || limit > 1000) {
31 | return api.QueryResponse{}, errors.New("invalid query parameters")
32 | }
33 |
34 | data := url.Values{
35 | "query": {api.QueryTag},
36 | "tag": {tag},
37 | "limit": {strconv.Itoa(limit)},
38 | }
39 |
40 | return b.QueryGeneric(data)
41 | }
42 |
43 | func (b *Bazaar) QueryRecent(amount int) (api.QueryResponse, error) {
44 | data := url.Values{
45 | "query": {api.QueryLatest},
46 | "selector": {strconv.Itoa(amount)},
47 | }
48 |
49 | return b.QueryGeneric(data)
50 | }
51 |
52 | func (b *Bazaar) QueryHash(hash string) (api.QueryResponse, error) {
53 | if len(hash) != 64 || len(hash) != 32 || len(hash) != 128 {
54 | return api.QueryResponse{}, errors.New("the hash must be SHA-256, SHA-1, or MD5")
55 | }
56 |
57 | data := url.Values{
58 | "query": {api.QueryHash},
59 | "hash": {hash},
60 | }
61 |
62 | return b.QueryGeneric(data)
63 | }
64 |
65 | func (b *Bazaar) QueryYara(rule string, limit int) (api.QueryResponse, error) {
66 | if len(rule) == 0 {
67 | return api.QueryResponse{}, errors.New("provide a YARA rule to query")
68 | }
69 |
70 | if limit < 100 {
71 | limit = 100
72 | }
73 |
74 | data := url.Values{
75 | "query": {api.QueryYara},
76 | "yara_rule": {rule},
77 | "limit": {strconv.Itoa(limit)},
78 | }
79 |
80 | return b.QueryGeneric(data)
81 | }
82 |
83 | func (b *Bazaar) QuerySignature(signature string, limit int) (api.QueryResponse, error) {
84 | if len(signature) == 0 {
85 | return api.QueryResponse{}, errors.New("provide a signature to query")
86 | }
87 |
88 | if limit < 100 {
89 | limit = 100
90 | }
91 |
92 | data := url.Values{
93 | "query": {api.QueryYara},
94 | "signature": {signature},
95 | "limit": {strconv.Itoa(limit)},
96 | }
97 |
98 | return b.QueryGeneric(data)
99 | }
100 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/LloydLabs/go-malwarebazaar
2 |
3 | go 1.18
4 |
5 | require github.com/alexmullins/zip v0.0.0-20180717182244-4affb64b04d0
6 |
7 | require golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect
8 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/alexmullins/zip v0.0.0-20180717182244-4affb64b04d0 h1:BVts5dexXf4i+JX8tXlKT0aKoi38JwTXSe+3WUneX0k=
2 | github.com/alexmullins/zip v0.0.0-20180717182244-4affb64b04d0/go.mod h1:FDIQmoMNJJl5/k7upZEnGvgWVZfFeE6qHeN7iCMbCsA=
3 | golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY=
4 | golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
5 |
--------------------------------------------------------------------------------