diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..5046ef1 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,6 @@ +# Ignore .git folder +.git* + +.gitignore +README.md +LICENSE diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..6ad309c --- /dev/null +++ b/Dockerfile @@ -0,0 +1,38 @@ +FROM gliderlabs/alpine:3.3 + +MAINTAINER blacktop, https://github.com/blacktop + +COPY . /go/src/github.com/maliceio/malice-yara +RUN apk-install python openssl file jansson +RUN apk-install -t build-deps go git mercurial autoconf automake file-dev flex gcc git jansson-dev libc-dev libtool make openssl-dev python-dev\ + && set -x \ + && cd /tmp/ \ + && git clone --recursive --branch v3.4.0 git://github.com/plusvic/yara \ + && cd /tmp/yara \ + && ./bootstrap.sh \ + && ./configure --enable-cuckoo \ + --enable-magic \ + --with-crypto \ + && make \ + && make install \ + && cd yara-python \ + && python setup.py build install \ + && && rm -rf /tmp/* + && echo "Building info Go binary..." \ + && cd /go/src/github.com/maliceio/malice-yara \ + && export GOPATH=/go \ + && go version \ + && go get \ + && go build -ldflags "-X main.Version=$(cat VERSION) -X main.BuildTime=$(date -u +%Y%m%d)" -o /bin/scan \ + && rm -rf /go \ + && rm -rf /tmp/* \ + && apk del --purge build-deps + +VOLUME ["/malware"] +VOLUME ["/rules"] + +WORKDIR /malware + +ENTRYPOINT ["/bin/scan"] + +CMD ["--help"] diff --git a/LICENSE b/LICENSE index 6de1fb5..35e8e67 100644 --- a/LICENSE +++ b/LICENSE @@ -20,3 +20,208 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +YARA - https://github.com/plusvic/yara/blob/master/COPYING + + +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md index ccc82d0..d15f8e6 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,90 @@ +![YARA-logo](https://raw.githubusercontent.com/maliceio/malice-yara/master/logo.png) # malice-yara + +[![License](http://img.shields.io/:license-mit-blue.svg)](http://doge.mit-license.org) +[![Docker Stars](https://img.shields.io/docker/stars/malice/yara.svg)][hub] +[![Docker Pulls](https://img.shields.io/docker/pulls/malice/yara.svg)][hub] +[![Image Size](https://img.shields.io/imagelayers/image-size/malice/yara/latest.svg)](https://imagelayers.io/?images=malice/yara:latest) +[![Image Layers](https://img.shields.io/imagelayers/layers/malice/yara/latest.svg)](https://imagelayers.io/?images=malice/yara:latest) + Malice Yara Plugin + +This repository contains a **Dockerfile** of **malice/yara** for [Docker](https://www.docker.io/)'s [trusted build](https://index.docker.io/u/malice/yara/) published to the public [DockerHub](https://index.docker.io/). + +> **WARNING:** Work in progress. Not ready yet. + +### Dependencies + +* [gliderlabs/alpine:3.3](https://index.docker.io/_/gliderlabs/alpine/) + + +### Installation + +1. Install [Docker](https://www.docker.io/). +2. Download [trusted build](https://hub.docker.com/r/malice/yara/) from public [DockerHub](https://hub.docker.com): `docker pull malice/yara` + +### Usage + + docker run --rm malice/yara FILE + +```bash +Usage: yara [OPTIONS] COMMAND [arg...] + +Malice yara Plugin + +Version: v0.1.0, BuildTime: 20160214 + +Author: + blacktop - + +Options: + --table, -t output as Markdown table + --post, -p POST results to Malice webhook [$MALICE_ENDPOINT] + --proxy, -x proxy settings for Malice webhook endpoint [$MALICE_PROXY] + --help, -h show help + --version, -v print the version + +Commands: + help Shows a list of commands or help for one command + +Run 'yara COMMAND --help' for more information on a command. +``` + +This will output to stdout and POST to malice results API webhook endpoint. + +### Sample Output JSON: +```json +{ + "yara": { + } +} +``` +### Sample Output STDOUT (Markdown Table): +--- +#### yara + +--- +### To Run on OSX + - Install [Homebrew](http://brew.sh) + +```bash +$ brew install caskroom/cask/brew-cask +$ brew cask install virtualbox +$ brew install docker +$ brew install docker-machine +$ docker-machine create --driver virtualbox malice +$ eval $(docker-machine env malice) +``` + +### Documentation + +### Issues + +Find a bug? Want more features? Find something missing in the documentation? Let me know! Please don't hesitate to [file an issue](https://github.com/maliceio/malice-av/issues/new) and I'll get right on it. + +### Credits + +### License +MIT Copyright (c) 2016 **blacktop** + +[hub]: https://hub.docker.com/r/malice/yara/ diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..b82608c --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +v0.1.0 diff --git a/logo.png b/logo.png new file mode 100644 index 0000000..8d57f97 Binary files /dev/null and b/logo.png differ diff --git a/yara.go b/yara.go new file mode 100644 index 0000000..ee4fbdf --- /dev/null +++ b/yara.go @@ -0,0 +1,336 @@ +package main + +import ( + "encoding/json" + "fmt" + "log" + "os" + "time" + + "github.com/codegangsta/cli" + "github.com/crackcomm/go-clitable" + "github.com/levigross/grequests" + "github.com/parnurzeal/gorequest" +) + +// Version stores the plugin's version +var Version string + +// BuildTime stores the plugin's build time +var BuildTime string + +// virustotal json object +type virustotal struct { + Results ResultsData `json:"virustotal"` +} + +// ResultsData json object +type ResultsData struct { + Scans map[string]Scan `json:"scans"` + Permalink string `json:"permalink"` + Resource string `json:"resource"` + ResponseCode int `json:"response_code"` + Total int `json:"total"` + Positives int `json:"positives"` + ScanID string `json:"scan_id"` + ScanDate string `json:"scan_date"` + VerboseMsg string `json:"verbose_msg"` + MD5 string `json:"md5"` + Sha1 string `json:"sha1"` + Sha256 string `json:"sha256"` +} + +// Scan is a VirusTotal AV scan JSON object +type Scan struct { + Detected bool `json:"detected"` + Version string `json:"version"` + Result string `json:"result"` + Update string `json:"update"` +} + +// ScanResults json object +type ScanResults struct { + Permalink string `json:"permalink"` + Resource string `json:"resource"` + ResponseCode int `json:"response_code"` + ScanID string `json:"scan_id"` + VerboseMsg string `json:"verbose_msg"` + MD5 string `json:"md5"` + Sha1 string `json:"sha1"` + Sha256 string `json:"sha256"` +} + +type bitly struct { + StatusCode int `json:"status_code"` + StatusTxt string `json:"status_txt"` + Data bitlyData `json:"data"` +} + +type bitlyData struct { + LongURL string `json:"long_url"` + URL string `json:"url"` + NewHash int `json:"new_hash"` + Hash string `json:"hash"` + GlobalHash string `json:"global_hash"` +} + +func getopt(name, dfault string) string { + value := os.Getenv(name) + if value == "" { + value = dfault + } + return value +} + +func assert(err error) { + if err != nil { + log.Fatal(err) + } +} + +func printStatus(resp gorequest.Response, body string, errs []error) { + fmt.Println(resp.Status) +} + +func printMarkDownTable(virustotal virustotal) { + fmt.Println("#### virustotal") + table := clitable.New([]string{"Ratio", "Link", "API", "Scanned"}) + table.AddRow(map[string]interface{}{ + "Ratio": getRatio(virustotal.Results.Positives, virustotal.Results.Total), + "Link": fmt.Sprintf("[link](%s)", virustotal.Results.Permalink), + "API": "Public", + // "API": virustotal.ApiType, + "Scanned": virustotal.Results.ScanDate, + }) + table.Markdown = true + table.Print() +} + +// scanFile uploads file to virustotal +func scanFile(path string, apikey string) string { + // fmt.Println("Uploading file to virustotal...") + fd, err := grequests.FileUploadFromDisk(path) + + if err != nil { + log.Println("Unable to open file: ", err) + } + + // This will upload the file as a multipart mime request + resp, err := grequests.Post("https://www.virustotal.com/vtapi/v2/file/scan", + &grequests.RequestOptions{ + Files: fd, + Params: map[string]string{ + "apikey": apikey, + // "notify_url": notify_url, + // "notify_changes_only": bool, + }, + }) + + if err != nil { + log.Println("Unable to make request", resp.Error) + } + + if resp.Ok != true { + log.Println("Request did not return OK") + } + + fmt.Println(resp.String()) + + var scanResults ScanResults + resp.JSON(&scanResults) + // fmt.Printf("%#v", scanResults) + + // TODO: wait for an hour!?!?!? or create a notify URL endpoint?!?!?! + ro := &grequests.RequestOptions{ + Params: map[string]string{ + "resource": scanResults.Sha256, + "scan_id": scanResults.ScanID, + "apikey": apikey, + "allinfo": "1", + }, + } + resp, err = grequests.Get("https://www.virustotal.com/vtapi/v2/file/report", ro) + + if err != nil { + log.Fatalln("Unable to make request: ", err) + } + + if resp.Ok != true { + log.Println("Request did not return OK") + } + + // fmt.Println(resp.String()) + return resp.String() +} + +// lookupHash retreieves the virustotal file report for the given hash +func lookupHash(hash string, apikey string) ResultsData { + // NOTE: https://godoc.org/github.com/levigross/grequests + // fmt.Println("Getting virustotal report...") + ro := &grequests.RequestOptions{ + Params: map[string]string{ + "resource": hash, + "apikey": apikey, + "allinfo": "1", + }, + } + resp, err := grequests.Get("https://www.virustotal.com/vtapi/v2/file/report", ro) + + if err != nil { + log.Fatalln("Unable to make request: ", err) + } + + if resp.StatusCode == 204 { + log.Fatalln("Used more than 4 queries per minute") + } + + if resp.Ok != true { + log.Println("Request did not return OK") + } + var vtResult ResultsData + // fmt.Println(resp.String()) + resp.JSON(&vtResult) + // fmt.Printf("%#v", vtResult) + t, _ := time.Parse("2006-01-02 15:04:05", vtResult.ScanDate) + vtResult.ScanDate = t.Format("Mon 2006Jan02 15:04:05") + + return vtResult +} + +func getRatio(positives int, total int) string { + ratio := 100.0 * float64(positives) / float64(total) + return fmt.Sprintf("%.f%%", ratio) +} + +func shortenPermalink(longURL string) string { + // NOTE: http://dev.bitly.com/api.html + // https://github.com/bitly/go-simplejson + var btl bitly + + ro := &grequests.RequestOptions{ + Params: map[string]string{ + "access_token": "23382325dd472aed14518ec5b8c8f4c2293e114a", + "longUrl": longURL, + }, + } + resp, err := grequests.Get("https://api-ssl.bitly.com/v3/shorten", ro) + + if err != nil { + log.Fatalln("Unable to make request: ", err) + } + + fmt.Println(resp.String()) + resp.JSON(&btl) + + return btl.Data.URL +} + +var appHelpTemplate = `Usage: {{.Name}} {{if .Flags}}[OPTIONS] {{end}}COMMAND [arg...] + +{{.Usage}} + +Version: {{.Version}}{{if or .Author .Email}} + +Author:{{if .Author}} + {{.Author}}{{if .Email}} - <{{.Email}}>{{end}}{{else}} + {{.Email}}{{end}}{{end}} +{{if .Flags}} +Options: + {{range .Flags}}{{.}} + {{end}}{{end}} +Commands: + {{range .Commands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}} + {{end}} +Run '{{.Name}} COMMAND --help' for more information on a command. +` + +func main() { + cli.AppHelpTemplate = appHelpTemplate + app := cli.NewApp() + app.Name = "virustotal" + app.Author = "blacktop" + app.Email = "https://github.com/blacktop" + app.Version = Version + ", BuildTime: " + BuildTime + app.Compiled, _ = time.Parse("20060102", BuildTime) + app.Usage = "Malice VirusTotal Plugin" + var apikey string + var table bool + app.Flags = []cli.Flag{ + cli.BoolFlag{ + Name: "post, p", + Usage: "POST results to Malice webhook", + EnvVar: "MALICE_ENDPOINT", + }, + cli.BoolFlag{ + Name: "proxy, x", + Usage: "proxy settings for Malice webhook endpoint", + EnvVar: "MALICE_PROXY", + }, + cli.BoolFlag{ + Name: "table, t", + Usage: "output as Markdown table", + Destination: &table, + }, + cli.StringFlag{ + Name: "api", + Value: "", + Usage: "VirusTotal API key", + EnvVar: "MALICE_VT_API", + Destination: &apikey, + }, + } + app.Commands = []cli.Command{ + { + Name: "scan", + Aliases: []string{"s"}, + Usage: "Upload binary to VirusTotal for scanning", + ArgsUsage: "FILE to upload to VirusTotal", + Action: func(c *cli.Context) { + // Check for valid apikey + if apikey == "" { + log.Fatal(fmt.Errorf("Please supply a valid VT_API key with the flag '--api'.")) + } + + if c.Args().Present() { + path := c.Args().First() + // Check that file exists + if _, err := os.Stat(path); os.IsNotExist(err) { + assert(err) + } + scanFile(path, apikey) + } else { + log.Fatal(fmt.Errorf("Please supply a file to upload to VirusTotal.")) + } + }, + }, + { + Name: "lookup", + Aliases: []string{"l"}, + Usage: "Get file hash scan report", + ArgsUsage: "MD5/SHA1/SHA256 hash of file", + Action: func(c *cli.Context) { + // Check for valid apikey + if apikey == "" { + log.Fatal(fmt.Errorf("Please supply a valid VT_API key with the flag '--api'.")) + } + + if c.Args().Present() { + vtReport := lookupHash(c.Args().First(), apikey) + vt := virustotal{Results: vtReport} + if table { + printMarkDownTable(vt) + } else { + vtJSON, err := json.Marshal(vt) + assert(err) + fmt.Println(string(vtJSON)) + } + } else { + log.Fatal(fmt.Errorf("Please supply a MD5/SHA1/SHA256 hash to query.")) + } + }, + }, + } + + err := app.Run(os.Args) + assert(err) +}