зеркало из https://github.com/mozilla/mig.git
[minor] vendor go-yara
This commit is contained in:
Родитель
5956901025
Коммит
d49a94b261
1
Makefile
1
Makefile
|
@ -195,6 +195,7 @@ go_vendor_dependencies:
|
|||
$(GOGETTER) golang.org/x/net/ipv4
|
||||
$(GOGETTER) golang.org/x/net/ipv6
|
||||
$(GOGETTER) gopkg.in/gcfg.v1
|
||||
$(GOGETTER) github.com/hillu/go-yara
|
||||
$(GOGETTER) github.com/cheggaaa/pb
|
||||
$(GOGETTER) github.com/stretchr/testify
|
||||
$(GOGETTER) github.com/fsnotify/fsnotify
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
language: go
|
||||
go:
|
||||
- 1.6.x
|
||||
- 1.7.x
|
||||
- 1.8.x
|
||||
- tip
|
||||
dist: xenial
|
||||
sudo: required
|
||||
before_install:
|
||||
- sudo apt-get update
|
||||
- sudo apt-get install bison flex automake autoconf libtool make gcc
|
||||
- wget --no-verbose -O- https://github.com/VirusTotal/yara/archive/v3.5.0.tar.gz | tar -C ${TRAVIS_BUILD_DIR} -xzf -
|
||||
- ( cd ${TRAVIS_BUILD_DIR}/yara-3.5.0 && ./bootstrap.sh && ./configure && make )
|
||||
- export CGO_CFLAGS=-I${TRAVIS_BUILD_DIR}/yara-3.5.0/libyara/include
|
||||
- export CGO_LDFLAGS=-L${TRAVIS_BUILD_DIR}/yara-3.5.0/libyara/.libs
|
||||
- export LD_LIBRARY_PATH=${TRAVIS_BUILD_DIR}/yara-3.5.0/libyara/.libs
|
|
@ -0,0 +1,23 @@
|
|||
Copyright (c) 2015, Hilko Bengen <bengen@hilluzination.de>
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,94 @@
|
|||
# go-yara
|
||||
|
||||
[![GoDoc](https://godoc.org/github.com/hillu/go-yara?status.svg)](https://godoc.org/github.com/hillu/go-yara)
|
||||
[![Travis](https://travis-ci.org/hillu/go-yara.svg?branch=master)](https://travis-ci.org/hillu/go-yara)
|
||||
|
||||
Go bindings for [YARA](http://plusvic.github.io/yara/), staying as
|
||||
close as sensible to the library's C-API while taking inspiration from
|
||||
the `yara-python` implementation.
|
||||
|
||||
YARA 3.4.0 or higher is required for full functionality. If you need
|
||||
to build with YARA 3.3.0, please build with the `yara3.3` build tag.
|
||||
(The `compat-yara-3.3` branch has been removed.)
|
||||
|
||||
## Installation
|
||||
|
||||
### Unix
|
||||
|
||||
On a Unix system with libyara properly installed, this should work,
|
||||
provided that `GOPATH` is set:
|
||||
|
||||
```
|
||||
go get github.com/hillu/go-yara
|
||||
go install github.com/hillu/go-yara
|
||||
```
|
||||
|
||||
Depending on what location libyara and its headers have been
|
||||
installed, proper `CFLAGS` and `LDFLAGS` may have to be added to
|
||||
`cgo.go` or be specified via environment variables (`CGO_CFLAGS` and
|
||||
`CGO_LDFLAGS`).
|
||||
|
||||
Linker errors buried in the CGO output such as
|
||||
|
||||
undefined reference to `yr_compiler_add_file'
|
||||
|
||||
probably mean that the linker is looking at an old version of the YARA
|
||||
library.
|
||||
|
||||
### Cross-building for Windows
|
||||
|
||||
YARA and go-yara can be cross-built on a Debian system as long as the
|
||||
Go compiler contains Windows runtime libraries with CGO support
|
||||
([cf.](https://github.com/hillu/golang-go-cross)).
|
||||
|
||||
The YARA library is built from the source tree with the MinGW compiler
|
||||
using the usual `./configure && make && make install`. Then go-yara is
|
||||
built and installed to `GOPATH` using `go install`. Some environment
|
||||
variables need to be passed to the `go` tool:
|
||||
|
||||
- `GOOS`, `GOARCH` indicate the cross compilation
|
||||
target.
|
||||
- `CGO_ENABLED` is set to 1 beacuse it defaults to 0 when
|
||||
cross-compiling.
|
||||
- `CC` has to specified because the `go` tool has no knowledge on what
|
||||
C compiler to use (it defaults to the system C compiler, usually
|
||||
gcc).
|
||||
- The C compiler in turn needs to know where to find headers and
|
||||
libraries, these locations are specified via the `CGO_CFLAGS` and
|
||||
`CGO_LDFLAGS` variables.
|
||||
|
||||
32bit:
|
||||
|
||||
```
|
||||
cd ${YARA_SRC}
|
||||
./configure --host=i686-w64-mingw32 --disable-magic --disable-cuckoo --without-crypto
|
||||
make
|
||||
make install prefix=./i686-w64-mingw32
|
||||
cd ${GO_YARA_SRC}
|
||||
GOOS=windows GOARCH=386 CGO_ENABLED=1 CC=i686-w64-mingw32-gcc \
|
||||
CGO_CFLAGS=-I${YARA_SRC}/i686-w64-mingw32/include \
|
||||
CGO_LDFLAGS=-L${YARA_SRC}/i686-w64-mingw32/lib \
|
||||
go install --ldflags '-extldflags "-static"' github.com/hillu/go-yara
|
||||
```
|
||||
|
||||
64bit:
|
||||
|
||||
```
|
||||
cd ${YARA_SRC}
|
||||
./configure --host=x86_64-w64-mingw32 --disable-magic --disable-cuckoo --without-crypto
|
||||
make
|
||||
make install prefix=./x86_64-w64-mingw32
|
||||
cd ${GO_YARA_SRC}
|
||||
GOOS=windows GOARCH=amd64 CGO_ENABLED=1 CC=x86_64-w64-mingw32-gcc \
|
||||
CGO_CFLAGS=-I${YARA_SRC}/x86_64-w64-mingw32/include \
|
||||
CGO_LDFLAGS=-L${YARA_SRC}/x86_64-w64-mingw32/lib \
|
||||
go install --ldflags '-extldflags "-static"' github.com/hillu/go-yara
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
BSD 2-clause, see LICENSE file in the source distribution.
|
||||
|
||||
## Author
|
||||
|
||||
Hilko Bengen <bengen@hilluzination.de>
|
|
@ -0,0 +1,56 @@
|
|||
package yara
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"sync"
|
||||
)
|
||||
|
||||
/*
|
||||
The closure type stores (pointers to) arbitrary data, returning a
|
||||
(usually small) uintptr. The uintptr value can be passed through C
|
||||
code to exported callback functions written in Go that can use it to
|
||||
access the data without violating the rules for passing pointers
|
||||
through C code.
|
||||
|
||||
Concurrent access to the stored data is protected through a
|
||||
sync.RWMutex.
|
||||
*/
|
||||
type closure struct {
|
||||
m map[uintptr]interface{}
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
func (c *closure) Put(elem interface{}) uintptr {
|
||||
c.Lock()
|
||||
if c.m == nil {
|
||||
c.m = make(map[uintptr]interface{})
|
||||
}
|
||||
defer c.Unlock()
|
||||
for i := uintptr(0); ; i++ {
|
||||
_, ok := c.m[i]
|
||||
if !ok {
|
||||
c.m[i] = elem
|
||||
return i
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *closure) Get(id uintptr) interface{} {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
if r, ok := c.m[id]; ok {
|
||||
return r
|
||||
}
|
||||
panic("get: element " + strconv.Itoa(int(id)) + " not found")
|
||||
}
|
||||
|
||||
func (c *closure) Delete(id uintptr) {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
if _, ok := c.m[id]; !ok {
|
||||
panic("delete: element " + strconv.Itoa(int(id)) + " not found")
|
||||
}
|
||||
delete(c.m, id)
|
||||
}
|
||||
|
||||
var callbackData closure
|
|
@ -0,0 +1,8 @@
|
|||
// Copyright © 2015 Hilko Bengen <bengen@hilluzination.de>. All rights reserved.
|
||||
// Use of this source code is governed by the license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package yara
|
||||
|
||||
// #cgo LDFLAGS: -lyara
|
||||
import "C"
|
|
@ -0,0 +1,215 @@
|
|||
// Copyright © 2015 Hilko Bengen <bengen@hilluzination.de>. All rights reserved.
|
||||
// Use of this source code is governed by the license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package yara
|
||||
|
||||
/*
|
||||
#ifdef _WIN32
|
||||
#define fdopen _fdopen
|
||||
#define dup _dup
|
||||
#endif
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <yara.h>
|
||||
|
||||
// This signature should be generated by cgo from the exported
|
||||
// function below
|
||||
void compilerCallback(int, char*, int, char*, void*);
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"runtime"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
//export compilerCallback
|
||||
func compilerCallback(errorLevel C.int, filename *C.char, linenumber C.int, message *C.char, userData unsafe.Pointer) {
|
||||
c := callbackData.Get(uintptr(userData)).(*Compiler)
|
||||
msg := CompilerMessage{
|
||||
Filename: C.GoString(filename),
|
||||
Line: int(linenumber),
|
||||
Text: C.GoString(message),
|
||||
}
|
||||
switch errorLevel {
|
||||
case C.YARA_ERROR_LEVEL_ERROR:
|
||||
c.Errors = append(c.Errors, msg)
|
||||
case C.YARA_ERROR_LEVEL_WARNING:
|
||||
c.Warnings = append(c.Warnings, msg)
|
||||
}
|
||||
}
|
||||
|
||||
// A Compiler encapsulates the YARA compiler that transforms rules
|
||||
// into YARA's internal, binary form which in turn is used for
|
||||
// scanning files or memory blocks.
|
||||
type Compiler struct {
|
||||
*compiler
|
||||
Errors []CompilerMessage
|
||||
Warnings []CompilerMessage
|
||||
}
|
||||
|
||||
type compiler struct {
|
||||
cptr *C.YR_COMPILER
|
||||
}
|
||||
|
||||
// A CompilerMessage contains an error or warning message produced
|
||||
// while compiling sets of rules using AddString or AddFile.
|
||||
type CompilerMessage struct {
|
||||
Filename string
|
||||
Line int
|
||||
Text string
|
||||
}
|
||||
|
||||
// NewCompiler creates a YARA compiler.
|
||||
func NewCompiler() (*Compiler, error) {
|
||||
var yrCompiler *C.YR_COMPILER
|
||||
if err := newError(C.yr_compiler_create(&yrCompiler)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c := &Compiler{compiler: &compiler{cptr: yrCompiler}}
|
||||
runtime.SetFinalizer(c.compiler, (*compiler).finalize)
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *compiler) finalize() {
|
||||
C.yr_compiler_destroy(c.cptr)
|
||||
runtime.SetFinalizer(c, nil)
|
||||
}
|
||||
|
||||
// Destroy destroys the YARA data structure representing a compiler.
|
||||
// Since a Finalizer for the underlying YR_COMPILER structure is
|
||||
// automatically set up on creation, it should not be necessary to
|
||||
// explicitly call this method.
|
||||
func (c *Compiler) Destroy() {
|
||||
if c.compiler != nil {
|
||||
c.compiler.finalize()
|
||||
c.compiler = nil
|
||||
}
|
||||
}
|
||||
|
||||
// AddFile compiles rules from a file. Rules are added to the
|
||||
// specified namespace.
|
||||
func (c *Compiler) AddFile(file *os.File, namespace string) (err error) {
|
||||
fd := C.dup(C.int(file.Fd()))
|
||||
fh, err := C.fdopen(fd, C.CString("r"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer C.fclose(fh)
|
||||
var ns *C.char
|
||||
if namespace != "" {
|
||||
ns = C.CString(namespace)
|
||||
defer C.free(unsafe.Pointer(ns))
|
||||
}
|
||||
filename := C.CString(file.Name())
|
||||
defer C.free(unsafe.Pointer(filename))
|
||||
id := callbackData.Put(c)
|
||||
defer callbackData.Delete(id)
|
||||
C.yr_compiler_set_callback(c.cptr, C.YR_COMPILER_CALLBACK_FUNC(C.compilerCallback), unsafe.Pointer(id))
|
||||
numErrors := int(C.yr_compiler_add_file(c.cptr, fh, ns, filename))
|
||||
if numErrors > 0 {
|
||||
var buf [1024]C.char
|
||||
msg := C.GoString(C.yr_compiler_get_error_message(
|
||||
c.cptr, (*C.char)(unsafe.Pointer(&buf[0])), 1024))
|
||||
err = errors.New(msg)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// AddString compiles rules from a string. Rules are added to the
|
||||
// specified namespace.
|
||||
func (c *Compiler) AddString(rules string, namespace string) (err error) {
|
||||
var ns *C.char
|
||||
if namespace != "" {
|
||||
ns = C.CString(namespace)
|
||||
defer C.free(unsafe.Pointer(ns))
|
||||
}
|
||||
crules := C.CString(rules)
|
||||
defer C.free(unsafe.Pointer(crules))
|
||||
id := callbackData.Put(c)
|
||||
defer callbackData.Delete(id)
|
||||
C.yr_compiler_set_callback(c.cptr, C.YR_COMPILER_CALLBACK_FUNC(C.compilerCallback), unsafe.Pointer(id))
|
||||
numErrors := int(C.yr_compiler_add_string(c.cptr, crules, ns))
|
||||
if numErrors > 0 {
|
||||
var buf [1024]C.char
|
||||
msg := C.GoString(C.yr_compiler_get_error_message(
|
||||
c.cptr, (*C.char)(unsafe.Pointer(&buf[0])), 1024))
|
||||
err = errors.New(msg)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DefineVariable defines a named variable for use by the compiler.
|
||||
// Boolean, int64, float64, and string types are supported.
|
||||
func (c *Compiler) DefineVariable(name string, value interface{}) (err error) {
|
||||
cname := C.CString(name)
|
||||
defer C.free(unsafe.Pointer(cname))
|
||||
switch value.(type) {
|
||||
case bool:
|
||||
var v int
|
||||
if value.(bool) {
|
||||
v = 1
|
||||
}
|
||||
err = newError(C.yr_compiler_define_boolean_variable(
|
||||
c.cptr, cname, C.int(v)))
|
||||
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
|
||||
value := toint64(value)
|
||||
err = newError(C.yr_compiler_define_integer_variable(
|
||||
c.cptr, cname, C.int64_t(value)))
|
||||
case float64:
|
||||
err = newError(C.yr_compiler_define_float_variable(
|
||||
c.cptr, cname, C.double(value.(float64))))
|
||||
case string:
|
||||
cvalue := C.CString(value.(string))
|
||||
defer C.free(unsafe.Pointer(cvalue))
|
||||
err = newError(C.yr_compiler_define_string_variable(
|
||||
c.cptr, cname, cvalue))
|
||||
default:
|
||||
err = errors.New("wrong value type passed to DefineVariable; bool, int64, float64, string are accepted")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetRules returns the compiled ruleset.
|
||||
func (c *Compiler) GetRules() (*Rules, error) {
|
||||
var yrRules *C.YR_RULES
|
||||
if err := newError(C.yr_compiler_get_rules(c.cptr, &yrRules)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r := &Rules{rules: &rules{cptr: yrRules}}
|
||||
runtime.SetFinalizer(r.rules, (*rules).finalize)
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// Compile compiles rules and an (optional) set of variables into a
|
||||
// Rules object in a single step.
|
||||
func Compile(rules string, variables map[string]interface{}) (r *Rules, err error) {
|
||||
var c *Compiler
|
||||
if c, err = NewCompiler(); err != nil {
|
||||
return
|
||||
}
|
||||
for k, v := range variables {
|
||||
if err = c.DefineVariable(k, v); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if err = c.AddString(rules, ""); err != nil {
|
||||
return
|
||||
}
|
||||
r, err = c.GetRules()
|
||||
return
|
||||
}
|
||||
|
||||
// MustCompile is like Compile but panics if the rules and optional
|
||||
// variables can't be compiled. Like regexp.MustCompile, it allows for
|
||||
// simple, safe initialization of global or test data.
|
||||
func MustCompile(rules string, variables map[string]interface{}) (r *Rules) {
|
||||
r, err := Compile(rules, variables)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
package yara
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestCompiler(t *testing.T) {
|
||||
c, _ := NewCompiler()
|
||||
if err := c.AddString(
|
||||
"rule test : tag1 { meta: author = \"Hilko Bengen\" strings: $a = \"abc\" fullword condition: $a }", "",
|
||||
); err != nil {
|
||||
t.Errorf("error: %s", err)
|
||||
}
|
||||
if err := c.AddString("xxx", ""); err == nil {
|
||||
t.Error("did not recognize error")
|
||||
} else {
|
||||
t.Logf("expected error: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPanic(t *testing.T) {
|
||||
defer func() {
|
||||
err := recover()
|
||||
if err == nil {
|
||||
t.Error("MustCompile with broken data did not panic")
|
||||
} else {
|
||||
t.Logf("Everything ok, MustCompile panicked: %v", err)
|
||||
}
|
||||
}()
|
||||
_ = MustCompile("asflkjkl", nil)
|
||||
}
|
||||
|
||||
func TestWarnings(t *testing.T) {
|
||||
c, _ := NewCompiler()
|
||||
c.AddString("rule foo { bar }", "")
|
||||
if len(c.Errors) == 0 {
|
||||
t.Error()
|
||||
}
|
||||
t.Logf("Recorded Errors=%#v, Warnings=%#v", c.Errors, c.Warnings)
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package yara
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Making a copy of Compiler struct should not cause a crash.
|
||||
func TestCompilerFinalizer(t *testing.T) {
|
||||
var c Compiler
|
||||
func() {
|
||||
fmt.Println("Create compiler")
|
||||
c1, _ := NewCompiler()
|
||||
c = *c1
|
||||
}()
|
||||
fmt.Println("Trigger GC")
|
||||
runtime.GC()
|
||||
fmt.Println("Trigger Gosched")
|
||||
runtime.Gosched()
|
||||
fmt.Println("Manually call destructure on copy")
|
||||
c.Destroy()
|
||||
t.Log("Did not crash due to yr_*_destroy() being called twice. Yay.")
|
||||
}
|
||||
|
||||
// Making a copy of Rules struct should not cause a crash.
|
||||
func TestRulesFinalizer(t *testing.T) {
|
||||
var r Rules
|
||||
func() {
|
||||
fmt.Println("Create rules")
|
||||
r1, _ := Compile("rule test { condition: true }", nil)
|
||||
r = *r1
|
||||
}()
|
||||
fmt.Println("Trigger GC")
|
||||
runtime.GC()
|
||||
fmt.Println("Trigger Gosched")
|
||||
runtime.Gosched()
|
||||
fmt.Println("Manually call destructure on copy")
|
||||
r.Destroy()
|
||||
t.Log("Did not crash due to yr_*_destroy() being called twice. Yay.")
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
// Copyright © 2015 Hilko Bengen <bengen@hilluzination.de>. All rights reserved.
|
||||
// Use of this source code is governed by the license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package yara
|
||||
|
||||
// #include <yara.h>
|
||||
import "C"
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func newError(code C.int) error {
|
||||
if code == 0 {
|
||||
return nil
|
||||
}
|
||||
if str, ok := errorStrings[code]; ok {
|
||||
return errors.New(str)
|
||||
}
|
||||
return fmt.Errorf("unknown error %d", code)
|
||||
}
|
||||
|
||||
// FIXME: This should be generated from yara/error.h
|
||||
var errorStrings = map[C.int]string{
|
||||
C.ERROR_INSUFICIENT_MEMORY: "insufficient memory",
|
||||
C.ERROR_COULD_NOT_ATTACH_TO_PROCESS: "could not attach to process",
|
||||
C.ERROR_COULD_NOT_OPEN_FILE: "could not open file",
|
||||
C.ERROR_COULD_NOT_MAP_FILE: "could not map file",
|
||||
C.ERROR_INVALID_FILE: "invalid file",
|
||||
C.ERROR_CORRUPT_FILE: "corrupt file",
|
||||
C.ERROR_UNSUPPORTED_FILE_VERSION: "unsupported file version",
|
||||
C.ERROR_INVALID_REGULAR_EXPRESSION: "invalid regular expression",
|
||||
C.ERROR_INVALID_HEX_STRING: "invalid hex string",
|
||||
C.ERROR_SYNTAX_ERROR: "syntax error",
|
||||
C.ERROR_LOOP_NESTING_LIMIT_EXCEEDED: "loop nesting limit exceeded",
|
||||
C.ERROR_DUPLICATED_LOOP_IDENTIFIER: "duplicated loop identifier",
|
||||
C.ERROR_DUPLICATED_IDENTIFIER: "duplicated identifier",
|
||||
C.ERROR_DUPLICATED_TAG_IDENTIFIER: "duplicated tag identifier",
|
||||
C.ERROR_DUPLICATED_META_IDENTIFIER: "duplicated meta identifier",
|
||||
C.ERROR_DUPLICATED_STRING_IDENTIFIER: "duplicated string identifier",
|
||||
C.ERROR_UNREFERENCED_STRING: "unreferenced string",
|
||||
C.ERROR_UNDEFINED_STRING: "undefined string",
|
||||
C.ERROR_UNDEFINED_IDENTIFIER: "undefined identifier",
|
||||
C.ERROR_MISPLACED_ANONYMOUS_STRING: "misplaced anonymous string",
|
||||
C.ERROR_INCLUDES_CIRCULAR_REFERENCE: "includes circular reference",
|
||||
C.ERROR_INCLUDE_DEPTH_EXCEEDED: "include depth exceeded",
|
||||
C.ERROR_WRONG_TYPE: "wrong type",
|
||||
C.ERROR_EXEC_STACK_OVERFLOW: "exec stack overflow",
|
||||
C.ERROR_SCAN_TIMEOUT: "scan timeout",
|
||||
C.ERROR_TOO_MANY_SCAN_THREADS: "too many scan threads",
|
||||
C.ERROR_CALLBACK_ERROR: "callback error",
|
||||
C.ERROR_INVALID_ARGUMENT: "invalid argument",
|
||||
C.ERROR_TOO_MANY_MATCHES: "too many matches",
|
||||
C.ERROR_INTERNAL_FATAL_ERROR: "internal fatal error",
|
||||
C.ERROR_NESTED_FOR_OF_LOOP: "nested for of loop",
|
||||
C.ERROR_INVALID_FIELD_NAME: "invalid field name",
|
||||
C.ERROR_UNKNOWN_MODULE: "unknown module",
|
||||
C.ERROR_NOT_A_STRUCTURE: "not a structure",
|
||||
C.ERROR_NOT_INDEXABLE: "not indexable",
|
||||
C.ERROR_NOT_A_FUNCTION: "not a function",
|
||||
C.ERROR_INVALID_FORMAT: "invalid format",
|
||||
C.ERROR_TOO_MANY_ARGUMENTS: "too many arguments",
|
||||
C.ERROR_WRONG_ARGUMENTS: "wrong arguments",
|
||||
C.ERROR_WRONG_RETURN_TYPE: "wrong return type",
|
||||
C.ERROR_DUPLICATED_STRUCTURE_MEMBER: "duplicated structure member",
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package yara
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
if r, err := Compile(`rule test : tag1 { meta: author = "Hilko Bengen" strings: $a = "abc" fullword condition: $a }`, nil); err != nil {
|
||||
os.Exit(1)
|
||||
} else if err = r.Save("testrules.yac"); err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
rc := m.Run()
|
||||
os.Remove("testrules.yac")
|
||||
os.Exit(rc)
|
||||
}
|
|
@ -0,0 +1,430 @@
|
|||
package yara
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// This file contains tests that were ported from yara/yara-python/tests.py
|
||||
|
||||
var pe32file = []byte{
|
||||
0x4d, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x40, 0x00, 0x00, 0x00, 0x50, 0x45, 0x00, 0x00, 0x4c, 0x01, 0x01, 0x00,
|
||||
0x5d, 0xbe, 0x45, 0x45, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0xe0, 0x00, 0x03, 0x01, 0x0b, 0x01, 0x08, 0x00, 0x04, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x01, 0x00, 0x00,
|
||||
0x60, 0x01, 0x00, 0x00, 0x64, 0x01, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00,
|
||||
0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x64, 0x01, 0x00, 0x00, 0x60, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x02, 0x00, 0x00, 0x04, 0x00, 0x00, 0x10, 0x00, 0x00, 0x10, 0x00, 0x00,
|
||||
0x00, 0x00, 0x10, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x2e, 0x74, 0x65, 0x78, 0x74, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
|
||||
0x60, 0x01, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x60, 0x01, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x20, 0x00, 0x00, 0x60, 0x6a, 0x2a, 0x58, 0xc3,
|
||||
}
|
||||
|
||||
var elf32file = []byte{
|
||||
0x7f, 0x45, 0x4c, 0x46, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||
0x60, 0x80, 0x04, 0x08, 0x34, 0x00, 0x00, 0x00, 0xa8, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x34, 0x00, 0x20, 0x00, 0x01, 0x00, 0x28, 0x00,
|
||||
0x04, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x80, 0x04, 0x08, 0x00, 0x80, 0x04, 0x08, 0x6c, 0x00, 0x00, 0x00,
|
||||
0x6c, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0xb8, 0x01, 0x00, 0x00, 0x00, 0xbb, 0x2a, 0x00, 0x00, 0x00, 0xcd, 0x80,
|
||||
0x00, 0x54, 0x68, 0x65, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x69, 0x64, 0x65,
|
||||
0x20, 0x41, 0x73, 0x73, 0x65, 0x6d, 0x62, 0x6c, 0x65, 0x72, 0x20, 0x32,
|
||||
0x2e, 0x30, 0x35, 0x2e, 0x30, 0x31, 0x00, 0x00, 0x2e, 0x73, 0x68, 0x73,
|
||||
0x74, 0x72, 0x74, 0x61, 0x62, 0x00, 0x2e, 0x74, 0x65, 0x78, 0x74, 0x00,
|
||||
0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||
0x06, 0x00, 0x00, 0x00, 0x60, 0x80, 0x04, 0x08, 0x60, 0x00, 0x00, 0x00,
|
||||
0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00,
|
||||
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x6c, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x8b, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
}
|
||||
|
||||
var elf64file = []byte{
|
||||
0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x3e, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||
0x80, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0xc8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x38, 0x00, 0x01, 0x00, 0x40, 0x00,
|
||||
0x04, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x8c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8c, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb8, 0x01, 0x00, 0x00,
|
||||
0x00, 0xbb, 0x2a, 0x00, 0x00, 0x00, 0xcd, 0x80, 0x00, 0x54, 0x68, 0x65,
|
||||
0x20, 0x4e, 0x65, 0x74, 0x77, 0x69, 0x64, 0x65, 0x20, 0x41, 0x73, 0x73,
|
||||
0x65, 0x6d, 0x62, 0x6c, 0x65, 0x72, 0x20, 0x32, 0x2e, 0x30, 0x35, 0x2e,
|
||||
0x30, 0x31, 0x00, 0x00, 0x2e, 0x73, 0x68, 0x73, 0x74, 0x72, 0x74, 0x61,
|
||||
0x62, 0x00, 0x2e, 0x74, 0x65, 0x78, 0x74, 0x00, 0x2e, 0x63, 0x6f, 0x6d,
|
||||
0x6d, 0x65, 0x6e, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x0b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||
0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xab, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
}
|
||||
|
||||
func TestBooleanOperators(t *testing.T) {
|
||||
assertTrueRules(t, []string{
|
||||
"rule test { condition: true }",
|
||||
"rule test { condition: true or false }",
|
||||
"rule test { condition: true and true }",
|
||||
"rule test { condition: 0x1 and 0x2}",
|
||||
}, []byte("dummy"))
|
||||
|
||||
assertFalseRules(t, []string{
|
||||
"rule test { condition: false }",
|
||||
"rule test { condition: true and false }",
|
||||
"rule test { condition: false or false }",
|
||||
}, []byte("dummy"))
|
||||
}
|
||||
|
||||
func TestComparisonOperators(t *testing.T) {
|
||||
assertTrueRules(t, []string{
|
||||
"rule test { condition: 2 > 1 }",
|
||||
"rule test { condition: 1 < 2 }",
|
||||
"rule test { condition: 2 >= 1 }",
|
||||
"rule test { condition: 1 <= 1 }",
|
||||
"rule test { condition: 1 == 1 }",
|
||||
"rule test { condition: 1.5 == 1.5}",
|
||||
"rule test { condition: 1.0 == 1}",
|
||||
"rule test { condition: 1.5 >= 1.0}",
|
||||
"rule test { condition: 1.5 >= 1}",
|
||||
"rule test { condition: 1.0 >= 1}",
|
||||
"rule test { condition: 0.5 < 1}",
|
||||
"rule test { condition: 0.5 <= 1}",
|
||||
"rule rest { condition: 1.0 <= 1}",
|
||||
"rule rest { condition: \"abc\" == \"abc\"}",
|
||||
"rule rest { condition: \"abc\" <= \"abc\"}",
|
||||
"rule rest { condition: \"abc\" >= \"abc\"}",
|
||||
"rule rest { condition: \"ab\" < \"abc\"}",
|
||||
"rule rest { condition: \"abc\" > \"ab\"}",
|
||||
"rule rest { condition: \"abc\" < \"abd\"}",
|
||||
"rule rest { condition: \"abd\" > \"abc\"}",
|
||||
}, []byte("dummy"))
|
||||
assertFalseRules(t, []string{
|
||||
"rule test { condition: 1 != 1}",
|
||||
"rule test { condition: 2 > 3}",
|
||||
"rule test { condition: 2 > 3}",
|
||||
"rule test { condition: 2.1 < 2}",
|
||||
"rule test { condition: \"abc\" != \"abc\"}",
|
||||
"rule test { condition: \"abc\" > \"abc\"}",
|
||||
"rule test { condition: \"abc\" < \"abc\"}",
|
||||
}, []byte("dummy"))
|
||||
}
|
||||
|
||||
func TestArithmeticOperators(t *testing.T) {
|
||||
assertTrueRules(t, []string{
|
||||
"rule test { condition: (1 + 1) * 2 == (9 - 1) \\ 2 }",
|
||||
"rule test { condition: 5 % 2 == 1 }",
|
||||
"rule test { condition: 1.5 + 1.5 == 3}",
|
||||
"rule test { condition: 3 \\ 2 == 1}",
|
||||
"rule test { condition: 3.0 \\ 2 == 1.5}",
|
||||
"rule test { condition: 1 + -1 == 0}",
|
||||
"rule test { condition: -1 + -1 == -2}",
|
||||
"rule test { condition: 4 --2 * 2 == 8}",
|
||||
"rule test { condition: -1.0 * 1 == -1.0}",
|
||||
"rule test { condition: 1-1 == 0}",
|
||||
"rule test { condition: -2.0-3.0 == -5}",
|
||||
"rule test { condition: --1 == 1}",
|
||||
"rule test { condition: 1--1 == 2}",
|
||||
"rule test { condition: -0x01 == -1}",
|
||||
}, []byte("dummy"))
|
||||
}
|
||||
|
||||
func TestBitwiseOperators(t *testing.T) {
|
||||
assertTrueRules(t, []string{
|
||||
"rule test { condition: 0x55 | 0xAA == 0xFF }",
|
||||
"rule test { condition: ~0xAA ^ 0x5A & 0xFF == (~0xAA) ^ (0x5A & 0xFF) }",
|
||||
"rule test { condition: ~0x55 & 0xFF == 0xAA }",
|
||||
"rule test { condition: 8 >> 2 == 2 }",
|
||||
"rule test { condition: 1 << 3 == 8 }",
|
||||
"rule test { condition: 1 | 3 ^ 3 == 1 | (3 ^ 3) }",
|
||||
}, []byte("dummy"))
|
||||
|
||||
assertFalseRules(t, []string{
|
||||
"rule test { condition: ~0xAA ^ 0x5A & 0xFF == 0x0F }",
|
||||
"rule test { condition: 1 | 3 ^ 3 == (1 | 3) ^ 3}",
|
||||
}, []byte("dummy"))
|
||||
}
|
||||
|
||||
func TestStrings(t *testing.T) {
|
||||
assertTrueRules(t, []string{
|
||||
"rule test { strings: $a = \"a\" condition: $a }",
|
||||
"rule test { strings: $a = \"ab\" condition: $a }",
|
||||
"rule test { strings: $a = \"abc\" condition: $a }",
|
||||
"rule test { strings: $a = \"xyz\" condition: $a }",
|
||||
"rule test { strings: $a = \"abc\" nocase fullword condition: $a }",
|
||||
"rule test { strings: $a = \"aBc\" nocase condition: $a }",
|
||||
"rule test { strings: $a = \"abc\" fullword condition: $a }",
|
||||
}, []byte("---- abc ---- xyz"))
|
||||
assertFalseRules(t, []string{
|
||||
"rule test { strings: $a = \"a\" fullword condition: $a }",
|
||||
"rule test { strings: $a = \"ab\" fullword condition: $a }",
|
||||
"rule test { strings: $a = \"abc\" wide fullword condition: $a }",
|
||||
}, []byte("---- abc ---- xyz"))
|
||||
assertTrueRules(t, []string{
|
||||
"rule test { strings: $a = \"a\" wide condition: $a }",
|
||||
"rule test { strings: $a = \"a\" wide ascii condition: $a }",
|
||||
"rule test { strings: $a = \"ab\" wide condition: $a }",
|
||||
"rule test { strings: $a = \"ab\" wide ascii condition: $a }",
|
||||
"rule test { strings: $a = \"abc\" wide condition: $a }",
|
||||
"rule test { strings: $a = \"abc\" wide nocase fullword condition: $a }",
|
||||
"rule test { strings: $a = \"aBc\" wide nocase condition: $a }",
|
||||
"rule test { strings: $a = \"aBc\" wide ascii nocase condition: $a }",
|
||||
"rule test { strings: $a = \"---xyz\" wide nocase condition: $a }",
|
||||
}, []byte("---- a\x00b\x00c\x00 -\x00-\x00-\x00-\x00x\x00y\x00z\x00"))
|
||||
assertTrueRules(t, []string{
|
||||
"rule test { strings: $a = \"abc\" fullword condition: $a }",
|
||||
}, []byte("abc"))
|
||||
assertFalseRules(t, []string{
|
||||
"rule test { strings: $a = \"abc\" fullword condition: $a }",
|
||||
}, []byte("xabcx"))
|
||||
assertFalseRules(t, []string{
|
||||
"rule test { strings: $a = \"abc\" fullword condition: $a }",
|
||||
}, []byte("xabc"))
|
||||
assertFalseRules(t, []string{
|
||||
"rule test { strings: $a = \"abc\" fullword condition: $a }",
|
||||
}, []byte("abcx"))
|
||||
assertFalseRules(t, []string{
|
||||
"rule test { strings: $a = \"abc\" ascii wide fullword condition: $a }",
|
||||
}, []byte("abcx"))
|
||||
assertTrueRules(t, []string{
|
||||
"rule test { strings: $a = \"abc\" ascii wide fullword condition: $a }",
|
||||
}, []byte("a\x00abc"))
|
||||
assertTrueRules(t, []string{
|
||||
"rule test { strings: $a = \"abc\" wide fullword condition: $a }",
|
||||
}, []byte("a\x00b\x00c\x00"))
|
||||
assertFalseRules(t, []string{
|
||||
"rule test { strings: $a = \"abc\" wide fullword condition: $a }",
|
||||
}, []byte("x\x00a\x00b\x00c\x00x\x00"))
|
||||
assertFalseRules(t, []string{
|
||||
"rule test { strings: $a = \"ab\" wide fullword condition: $a }",
|
||||
}, []byte("x\x00a\x00b\x00"))
|
||||
assertFalseRules(t, []string{
|
||||
"rule test { strings: $a = \"abc\" wide fullword condition: $a }",
|
||||
}, []byte("x\x00a\x00b\x00c\x00"))
|
||||
assertTrueRules(t, []string{
|
||||
"rule test { strings: $a = \"abc\" wide fullword condition: $a }",
|
||||
}, []byte("x\x01a\x00b\x00c\x00"))
|
||||
assertTrueRules(t, []string{"" +
|
||||
"rule test {\n" +
|
||||
" strings:\n" +
|
||||
" $a = \"abcdef\"\n" +
|
||||
" $b = \"cdef\"\n" +
|
||||
" $c = \"ef\"\n" +
|
||||
" condition:\n" +
|
||||
" all of them\n" +
|
||||
"}",
|
||||
}, []byte("abcdef"))
|
||||
}
|
||||
|
||||
func TestWildcardStrings(t *testing.T) {
|
||||
assertTrueRules(t, []string{"" +
|
||||
"rule test {\n" +
|
||||
" strings:\n" +
|
||||
" $s1 = \"abc\"\n" +
|
||||
" $s2 = \"xyz\"\n" +
|
||||
" condition:\n" +
|
||||
" for all of ($*) : ($)\n" +
|
||||
"}",
|
||||
}, []byte("---- abc ---- A\x00B\x00C\x00 ---- xyz"))
|
||||
}
|
||||
|
||||
func TestHexStrings(t *testing.T) {
|
||||
assertTrueRules(t, []string{
|
||||
"rule test { strings: $a = { 64 01 00 00 60 01 } condition: $a }",
|
||||
"rule test { strings: $a = { 64 0? 00 00 ?0 01 } condition: $a }",
|
||||
"rule test { strings: $a = { 64 01 [1-3] 60 01 } condition: $a }",
|
||||
"rule test { strings: $a = { 64 01 [1-3] (60|61) 01 } condition: $a }",
|
||||
"rule test { strings: $a = { 4D 5A [-] 6A 2A [-] 58 C3} condition: $a }",
|
||||
"rule test { strings: $a = { 4D 5A [300-] 6A 2A [-] 58 C3} condition: $a }",
|
||||
}, pe32file)
|
||||
assertFalseRules(t, []string{
|
||||
"rule test { strings: $a = { 4D 5A [0-300] 6A 2A } condition: $a }",
|
||||
}, pe32file)
|
||||
assertTrueRules(t, []string{
|
||||
"rule test { strings: $a = { 31 32 [-] 38 39 } condition: $a }",
|
||||
"rule test { strings: $a = { 31 32 [-] 33 34 [-] 38 39 } condition: $a }",
|
||||
"rule test { strings: $a = { 31 32 [1] 34 35 [2] 38 39 } condition: $a }",
|
||||
"rule test { strings: $a = { 31 32 [1-] 34 35 [1-] 38 39 } condition: $a }",
|
||||
"rule test { strings: $a = { 31 32 [0-3] 34 35 [1-] 38 39 } condition: $a }",
|
||||
}, []byte("123456789"))
|
||||
assertTrueRules(t, []string{
|
||||
"rule test { strings: $a = { 31 32 [-] 38 39 } condition: all of them }",
|
||||
}, []byte("123456789"))
|
||||
assertFalseRules(t, []string{
|
||||
"rule test { strings: $a = { 31 32 [-] 32 33 } condition: $a }",
|
||||
"rule test { strings: $a = { 35 36 [-] 31 32 } condition: $a }",
|
||||
"rule test { strings: $a = { 31 32 [2-] 34 35 } condition: $a }",
|
||||
"rule test { strings: $a = { 31 32 [0-3] 37 38 } condition: $a }",
|
||||
}, []byte("123456789"))
|
||||
|
||||
rules := makeRules(t, "rule test { strings: $a = { 61 [0-3] (62|63) } condition: $a }")
|
||||
matches, _ := rules.ScanMem([]byte("abbb"), 0, 0)
|
||||
if matches[0].Strings[0].Name != "$a" ||
|
||||
matches[0].Strings[0].Offset != 0 ||
|
||||
string(matches[0].Strings[0].Data) != "ab" {
|
||||
t.Error("wrong match")
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: TestCount
|
||||
// TODO: TestAt
|
||||
// TODO: TestOffset
|
||||
// TODO: TestOf
|
||||
// TODO: TestFor
|
||||
// TODO: TestRE
|
||||
|
||||
func TestEntrypoint(t *testing.T) {
|
||||
assertTrueRules(t, []string{
|
||||
"rule test { strings: $a = { 6a 2a 58 c3 } condition: $a at entrypoint }",
|
||||
}, pe32file)
|
||||
assertTrueRules(t, []string{
|
||||
"rule test { strings: $a = { b8 01 00 00 00 bb 2a } condition: $a at entrypoint }",
|
||||
}, elf32file)
|
||||
assertTrueRules(t, []string{
|
||||
"rule test { strings: $a = { b8 01 00 00 00 bb 2a } condition: $a at entrypoint }",
|
||||
}, elf64file)
|
||||
assertFalseRules(t, []string{
|
||||
"rule test { condition: entrypoint >= 0 }",
|
||||
}, []byte("dummy"))
|
||||
}
|
||||
|
||||
func TestFilesize(t *testing.T) {
|
||||
assertTrueRules(t, []string{
|
||||
fmt.Sprintf("rule test { condition: filesize == %d }", len(pe32file)),
|
||||
}, pe32file)
|
||||
}
|
||||
|
||||
// TODO: TestCompileFile
|
||||
// TODO: TestCompileFiles
|
||||
// TODO: TestIncludeFiles
|
||||
|
||||
type params map[string]interface{}
|
||||
|
||||
func TestExternals(t *testing.T) {
|
||||
for _, sample := range []struct {
|
||||
rule string
|
||||
params
|
||||
predicate bool
|
||||
}{
|
||||
{"rule test { condition: ext_int == 15 }", params{"ext_int": 15}, true},
|
||||
{"rule test { condition: ext_int == -15}", params{"ext_int": -15}, true},
|
||||
{"rule test { condition: ext_float == 3.14 }", params{"ext_float": 3.14}, true},
|
||||
{"rule test { condition: ext_float == -0.5 }", params{"ext_float": -0.5}, true},
|
||||
{"rule test { condition: ext_bool }", params{"ext_bool": true}, true},
|
||||
{"rule test { condition: ext_str }", params{"ext_str": ""}, false},
|
||||
{"rule test { condition: ext_str }", params{"ext_str": "foo"}, true},
|
||||
{"rule test { condition: ext_bool }", params{"ext_bool": false}, false},
|
||||
{"rule test { condition: ext_str contains \"ssi\" }", params{"ext_str": "mississippi"}, true},
|
||||
{"rule test { condition: ext_str matches /foo/ }", params{"ext_str": ""}, false},
|
||||
{"rule test { condition: ext_str matches /foo/ }", params{"ext_str": "FOO"}, false},
|
||||
{"rule test { condition: ext_str matches /foo/i }", params{"ext_str": "FOO"}, true},
|
||||
{"rule test { condition: ext_str matches /ssi(s|p)/ }", params{"ext_str": "mississippi"}, true},
|
||||
{"rule test { condition: ext_str matches /ppi$/ }", params{"ext_str": "mississippi"}, true},
|
||||
{"rule test { condition: ext_str matches /ssi$/ }", params{"ext_str": "mississippi"}, false},
|
||||
{"rule test { condition: ext_str matches /^miss/ }", params{"ext_str": "mississippi"}, true},
|
||||
{"rule test { condition: ext_str matches /^iss/ }", params{"ext_str": "mississippi"}, false},
|
||||
{"rule test { condition: ext_str matches /ssi$/ }", params{"ext_str": "mississippi"}, false},
|
||||
} {
|
||||
r, err := Compile(sample.rule, sample.params)
|
||||
if err != nil {
|
||||
t.Errorf("rule=%s params=%+v: Compile error: %s", sample.rule, sample.params, err)
|
||||
continue
|
||||
}
|
||||
m, _ := r.ScanMem([]byte("dummy"), 0, 0)
|
||||
if sample.predicate != (len(m) > 0) {
|
||||
t.Errorf("rule=%s params=%+v: expected %t, got %t",
|
||||
sample.rule, sample.params, sample.predicate, (len(m) > 0))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: TestCallback
|
||||
// TODO: TestCompare
|
||||
// TODO: TestComments
|
||||
|
||||
func TestModules(t *testing.T) {
|
||||
assertTrueRules(t, []string{
|
||||
"import \"tests\" rule test { condition: tests.constants.one + 1 == tests.constants.two }",
|
||||
"import \"tests\" rule test { condition: tests.constants.foo == \"foo\" }",
|
||||
"import \"tests\" rule test { condition: tests.struct_array[1].i == 1 }",
|
||||
"import \"tests\" rule test { condition: tests.struct_array[0].i == 1 or true}",
|
||||
"import \"tests\" rule test { condition: tests.integer_array[0] == 0}",
|
||||
"import \"tests\" rule test { condition: tests.integer_array[1] == 1}",
|
||||
"import \"tests\" rule test { condition: tests.string_array[0] == \"foo\"}",
|
||||
"import \"tests\" rule test { condition: tests.string_array[2] == \"baz\"}",
|
||||
"import \"tests\" rule test { condition: tests.string_dict[\"foo\"] == \"foo\"}",
|
||||
"import \"tests\" rule test { condition: tests.string_dict[\"bar\"] == \"bar\"}",
|
||||
"import \"tests\" rule test { condition: tests.isum(1,2) == 3}",
|
||||
"import \"tests\" rule test { condition: tests.isum(1,2,3) == 6}",
|
||||
"import \"tests\" rule test { condition: tests.fsum(1.0,2.0) == 3.0}",
|
||||
"import \"tests\" rule test { condition: tests.fsum(1.0,2.0,3.0) == 6.0}",
|
||||
"import \"tests\" rule test { condition: tests.length(\"dummy\") == 5}",
|
||||
}, []byte("dummy"))
|
||||
assertFalseRules(t, []string{
|
||||
"import \"tests\" rule test { condition: tests.struct_array[0].i == 1 }",
|
||||
"import \"tests\" rule test { condition: tests.isum(1,1) == 3}",
|
||||
"import \"tests\" rule test { condition: tests.fsum(1.0,1.0) == 3.0}",
|
||||
}, []byte("dummy"))
|
||||
}
|
||||
|
||||
func TestIntegerFunctions(t *testing.T) {
|
||||
assertTrueRules(t, []string{
|
||||
"rule test { condition: uint8(0) == 0xAA}",
|
||||
"rule test { condition: uint16(0) == 0xBBAA}",
|
||||
"rule test { condition: uint32(0) == 0xDDCCBBAA}",
|
||||
"rule test { condition: uint8be(0) == 0xAA}",
|
||||
"rule test { condition: uint16be(0) == 0xAABB}",
|
||||
"rule test { condition: uint32be(0) == 0xAABBCCDD}",
|
||||
}, []byte("\xAA\xBB\xCC\xDD"))
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
Copyright © 2015 Hilko Bengen <bengen@hilluzination.de>. All rights reserved.
|
||||
Use of this source code is governed by the license that can be
|
||||
found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#include <yara.h>
|
||||
#include "_cgo_export.h"
|
||||
|
||||
int rules_callback(int message, void *message_data, void *user_data) {
|
||||
if (message == CALLBACK_MSG_RULE_MATCHING) {
|
||||
YR_RULE* rule = (YR_RULE*) message_data;
|
||||
char* ns = rule->ns->name;
|
||||
if(ns == NULL) {
|
||||
ns = "";
|
||||
}
|
||||
newMatch(user_data, ns, (char*)rule->identifier);
|
||||
YR_META* meta;
|
||||
yr_rule_metas_foreach(rule, meta) {
|
||||
switch (meta->type) {
|
||||
case META_TYPE_INTEGER:
|
||||
addMetaInt(user_data, (char*)meta->identifier, meta->integer);
|
||||
break;
|
||||
case META_TYPE_STRING:
|
||||
addMetaString(user_data, (char*)meta->identifier, meta->string);
|
||||
break;
|
||||
case META_TYPE_BOOLEAN:
|
||||
addMetaBool(user_data, (char*)meta->identifier, meta->integer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
const char* tag_name;
|
||||
yr_rule_tags_foreach(rule, tag_name) {
|
||||
addTag(user_data, (char*)tag_name);
|
||||
}
|
||||
YR_STRING* string;
|
||||
YR_MATCH* m;
|
||||
yr_rule_strings_foreach(rule, string) {
|
||||
yr_string_matches_foreach(string, m) {
|
||||
#if YR_VERSION_HEX >= 0x030500
|
||||
/* YR_MATCH members have been renamed in YARA 3.5 */
|
||||
addString(user_data, string->identifier, m->offset, m->data, (int)m->data_length);
|
||||
#else
|
||||
addString(user_data, string->identifier, m->offset, m->data, (int)m->length);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
return CALLBACK_CONTINUE;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
/*
|
||||
Helper function that is merely used to cast fd from int to HANDLE.
|
||||
CGO treats HANDLE (void*) to an unsafe.Pointer. This confuses the
|
||||
go1.4 garbage collector, leading to runtime errors such as:
|
||||
|
||||
runtime: garbage collector found invalid heap pointer *(0x5b80ff14+0x4)=0xa0 s=nil
|
||||
*/
|
||||
int _yr_rules_scan_fd(
|
||||
YR_RULES* rules,
|
||||
int fd,
|
||||
int flags,
|
||||
YR_CALLBACK_FUNC callback,
|
||||
void* user_data,
|
||||
int timeout)
|
||||
{
|
||||
return yr_rules_scan_fd(rules, (YR_FILE_DESCRIPTOR)fd, flags, callback, user_data, timeout);
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,241 @@
|
|||
// Copyright © 2015 Hilko Bengen <bengen@hilluzination.de>. All rights reserved.
|
||||
// Use of this source code is governed by the license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// Package yara provides bindings to the YARA library.
|
||||
package yara
|
||||
|
||||
/*
|
||||
#include <yara.h>
|
||||
|
||||
int rules_callback(int message, void *message_data, void *user_data);
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"time"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Rules contains a compiled YARA ruleset.
|
||||
type Rules struct {
|
||||
*rules
|
||||
}
|
||||
|
||||
type rules struct {
|
||||
cptr *C.YR_RULES
|
||||
}
|
||||
|
||||
var dummy *[]MatchRule
|
||||
|
||||
// A MatchRule represents a rule successfully matched against a block
|
||||
// of data.
|
||||
type MatchRule struct {
|
||||
Rule string
|
||||
Namespace string
|
||||
Tags []string
|
||||
Meta map[string]interface{}
|
||||
Strings []MatchString
|
||||
}
|
||||
|
||||
// A MatchString represents a string declared and matched in a rule.
|
||||
type MatchString struct {
|
||||
Name string
|
||||
Offset uint64
|
||||
Data []byte
|
||||
}
|
||||
|
||||
func init() {
|
||||
_ = C.yr_initialize()
|
||||
}
|
||||
|
||||
//export newMatch
|
||||
func newMatch(userData unsafe.Pointer, namespace, identifier *C.char) {
|
||||
matches := callbackData.Get(uintptr(userData)).(*[]MatchRule)
|
||||
*matches = append(*matches, MatchRule{
|
||||
Rule: C.GoString(identifier),
|
||||
Namespace: C.GoString(namespace),
|
||||
Tags: []string{},
|
||||
Meta: map[string]interface{}{},
|
||||
Strings: []MatchString{},
|
||||
})
|
||||
}
|
||||
|
||||
//export addMetaInt
|
||||
func addMetaInt(userData unsafe.Pointer, identifier *C.char, value C.int) {
|
||||
matches := callbackData.Get(uintptr(userData)).(*[]MatchRule)
|
||||
i := len(*matches) - 1
|
||||
(*matches)[i].Meta[C.GoString(identifier)] = int32(value)
|
||||
}
|
||||
|
||||
//export addMetaString
|
||||
func addMetaString(userData unsafe.Pointer, identifier *C.char, value *C.char) {
|
||||
matches := callbackData.Get(uintptr(userData)).(*[]MatchRule)
|
||||
i := len(*matches) - 1
|
||||
(*matches)[i].Meta[C.GoString(identifier)] = C.GoString(value)
|
||||
}
|
||||
|
||||
//export addMetaBool
|
||||
func addMetaBool(userData unsafe.Pointer, identifier *C.char, value C.int) {
|
||||
matches := callbackData.Get(uintptr(userData)).(*[]MatchRule)
|
||||
i := len(*matches) - 1
|
||||
(*matches)[i].Meta[C.GoString(identifier)] = bool(value != 0)
|
||||
}
|
||||
|
||||
//export addTag
|
||||
func addTag(userData unsafe.Pointer, tag *C.char) {
|
||||
matches := callbackData.Get(uintptr(userData)).(*[]MatchRule)
|
||||
i := len(*matches) - 1
|
||||
(*matches)[i].Tags = append((*matches)[i].Tags, C.GoString(tag))
|
||||
}
|
||||
|
||||
//export addString
|
||||
func addString(userData unsafe.Pointer, identifier *C.char, offset C.uint64_t, data unsafe.Pointer, length C.int) {
|
||||
ms := MatchString{
|
||||
Name: C.GoString(identifier),
|
||||
Offset: uint64(offset),
|
||||
Data: make([]byte, int(length)),
|
||||
}
|
||||
|
||||
var tmpSlice []byte
|
||||
hdr := (*reflect.SliceHeader)(unsafe.Pointer(&tmpSlice))
|
||||
hdr.Data = uintptr(data)
|
||||
hdr.Len = int(length)
|
||||
copy(ms.Data, tmpSlice)
|
||||
|
||||
matches := callbackData.Get(uintptr(userData)).(*[]MatchRule)
|
||||
i := len(*matches) - 1
|
||||
(*matches)[i].Strings = append((*matches)[i].Strings, ms)
|
||||
}
|
||||
|
||||
// ScanFlags are used to tweak the behavior of Scan* functions.
|
||||
type ScanFlags int
|
||||
|
||||
const (
|
||||
// ScanFlagsFastMode avoids multiple matches of the same string
|
||||
// when not necessary.
|
||||
ScanFlagsFastMode = C.SCAN_FLAGS_FAST_MODE
|
||||
// ScanFlagsProcessMemory causes the scanned data to be
|
||||
// interpreted like live, in-prcess memory rather than an on-disk
|
||||
// file.
|
||||
ScanFlagsProcessMemory = C.SCAN_FLAGS_PROCESS_MEMORY
|
||||
)
|
||||
|
||||
// ScanMem scans an in-memory buffer using the ruleset.
|
||||
func (r *Rules) ScanMem(buf []byte, flags ScanFlags, timeout time.Duration) (matches []MatchRule, err error) {
|
||||
var ptr *C.uint8_t
|
||||
if len(buf) > 0 {
|
||||
ptr = (*C.uint8_t)(unsafe.Pointer(&(buf[0])))
|
||||
}
|
||||
id := callbackData.Put(&matches)
|
||||
defer callbackData.Delete(id)
|
||||
err = newError(C.yr_rules_scan_mem(
|
||||
r.cptr,
|
||||
ptr,
|
||||
C.size_t(len(buf)),
|
||||
C.int(flags),
|
||||
C.YR_CALLBACK_FUNC(C.rules_callback),
|
||||
unsafe.Pointer(id),
|
||||
C.int(timeout/time.Second)))
|
||||
return
|
||||
}
|
||||
|
||||
// ScanFile scans a file using the ruleset.
|
||||
func (r *Rules) ScanFile(filename string, flags ScanFlags, timeout time.Duration) (matches []MatchRule, err error) {
|
||||
cfilename := C.CString(filename)
|
||||
defer C.free(unsafe.Pointer(cfilename))
|
||||
id := callbackData.Put(&matches)
|
||||
defer callbackData.Delete(id)
|
||||
err = newError(C.yr_rules_scan_file(
|
||||
r.cptr,
|
||||
cfilename,
|
||||
C.int(flags),
|
||||
C.YR_CALLBACK_FUNC(C.rules_callback),
|
||||
unsafe.Pointer(id),
|
||||
C.int(timeout/time.Second)))
|
||||
return
|
||||
}
|
||||
|
||||
// ScanProc scans a live process using the ruleset.
|
||||
func (r *Rules) ScanProc(pid int, flags int, timeout time.Duration) (matches []MatchRule, err error) {
|
||||
id := callbackData.Put(&matches)
|
||||
defer callbackData.Delete(id)
|
||||
err = newError(C.yr_rules_scan_proc(
|
||||
r.cptr,
|
||||
C.int(pid),
|
||||
C.int(flags),
|
||||
C.YR_CALLBACK_FUNC(C.rules_callback),
|
||||
unsafe.Pointer(id),
|
||||
C.int(timeout/time.Second)))
|
||||
return
|
||||
}
|
||||
|
||||
// Save writes a compiled ruleset to filename.
|
||||
func (r *Rules) Save(filename string) (err error) {
|
||||
cfilename := C.CString(filename)
|
||||
defer C.free(unsafe.Pointer(cfilename))
|
||||
err = newError(C.yr_rules_save(r.cptr, cfilename))
|
||||
return
|
||||
}
|
||||
|
||||
// LoadRules retrieves a compiled ruleset from filename.
|
||||
func LoadRules(filename string) (*Rules, error) {
|
||||
r := &Rules{rules: &rules{}}
|
||||
cfilename := C.CString(filename)
|
||||
defer C.free(unsafe.Pointer(cfilename))
|
||||
if err := newError(C.yr_rules_load(cfilename,
|
||||
&(r.rules.cptr))); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
runtime.SetFinalizer(r.rules, (*rules).finalize)
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (r *rules) finalize() {
|
||||
C.yr_rules_destroy(r.cptr)
|
||||
runtime.SetFinalizer(r, nil)
|
||||
}
|
||||
|
||||
// Destroy destroys the YARA data structure representing a ruleset.
|
||||
// Since a Finalizer for the underlying YR_RULES structure is
|
||||
// automatically set up on creation, it should not be necessary to
|
||||
// explicitly call this method.
|
||||
func (r *Rules) Destroy() {
|
||||
if r.rules != nil {
|
||||
r.rules.finalize()
|
||||
r.rules = nil
|
||||
}
|
||||
}
|
||||
|
||||
// DefineVariable defines a named variable for use by the compiler.
|
||||
// Boolean, int64, float64, and string types are supported.
|
||||
func (r *Rules) DefineVariable(name string, value interface{}) (err error) {
|
||||
cname := C.CString(name)
|
||||
defer C.free(unsafe.Pointer(cname))
|
||||
switch value.(type) {
|
||||
case bool:
|
||||
var v int
|
||||
if value.(bool) {
|
||||
v = 1
|
||||
}
|
||||
err = newError(C.yr_rules_define_boolean_variable(
|
||||
r.cptr, cname, C.int(v)))
|
||||
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
|
||||
value := toint64(value)
|
||||
err = newError(C.yr_rules_define_integer_variable(
|
||||
r.cptr, cname, C.int64_t(value)))
|
||||
case float64:
|
||||
err = newError(C.yr_rules_define_float_variable(
|
||||
r.cptr, cname, C.double(value.(float64))))
|
||||
case string:
|
||||
cvalue := C.CString(value.(string))
|
||||
defer C.free(unsafe.Pointer(cvalue))
|
||||
err = newError(C.yr_rules_define_string_variable(
|
||||
r.cptr, cname, cvalue))
|
||||
default:
|
||||
err = errors.New("wrong value type passed to DefineVariable; bool, int64, float64, string are accepted")
|
||||
}
|
||||
return
|
||||
}
|
|
@ -0,0 +1,184 @@
|
|||
package yara
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/bzip2"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func makeRules(t *testing.T, rule string) *Rules {
|
||||
c, err := NewCompiler()
|
||||
if c == nil || err != nil {
|
||||
t.Fatal("NewCompiler():", err)
|
||||
}
|
||||
if err = c.AddString(rule, ""); err != nil {
|
||||
t.Fatal("AddString():", err)
|
||||
}
|
||||
r, err := c.GetRules()
|
||||
if err != nil {
|
||||
t.Fatal("GetRules:", err)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func TestSimpleMatch(t *testing.T) {
|
||||
r := makeRules(t,
|
||||
"rule test : tag1 { meta: author = \"Hilko Bengen\" strings: $a = \"abc\" fullword condition: $a }")
|
||||
m, err := r.ScanMem([]byte(" abc "), 0, 0)
|
||||
if err != nil {
|
||||
t.Errorf("ScanMem: %s", err)
|
||||
}
|
||||
t.Logf("Matches: %+v", m)
|
||||
}
|
||||
|
||||
func TestSimpleFileMatch(t *testing.T) {
|
||||
r, _ := Compile(
|
||||
"rule test : tag1 { meta: author = \"Hilko Bengen\" strings: $a = \"abc\" fullword condition: $a }",
|
||||
nil)
|
||||
tf, _ := ioutil.TempFile("", "TestSimpleFileMatch")
|
||||
defer os.Remove(tf.Name())
|
||||
tf.Write([]byte(" abc "))
|
||||
tf.Close()
|
||||
m, err := r.ScanFile(tf.Name(), 0, 0)
|
||||
if err != nil {
|
||||
t.Errorf("ScanFile(%s): %s", tf.Name(), err)
|
||||
}
|
||||
t.Logf("Matches: %+v", m)
|
||||
}
|
||||
|
||||
func TestSimpleFileDescriptorMatch(t *testing.T) {
|
||||
r, _ := Compile(
|
||||
"rule test : tag1 { meta: author = \"Hilko Bengen\" strings: $a = \"abc\" fullword condition: $a }",
|
||||
nil)
|
||||
tf, _ := ioutil.TempFile("", "TestSimpleFileMatch")
|
||||
defer os.Remove(tf.Name())
|
||||
tf.Write([]byte(" abc "))
|
||||
tf.Seek(0, os.SEEK_SET)
|
||||
m, err := r.ScanFileDescriptor(tf.Fd(), 0, 0)
|
||||
if err != nil {
|
||||
t.Errorf("ScanFile(%s): %s", tf.Name(), err)
|
||||
}
|
||||
t.Logf("Matches: %+v", m)
|
||||
}
|
||||
|
||||
func TestEmpty(t *testing.T) {
|
||||
r, _ := Compile("rule test { condition: true }", nil)
|
||||
r.ScanMem([]byte{}, 0, 0)
|
||||
t.Log("Scan of null-byte slice did not crash. Yay.")
|
||||
}
|
||||
|
||||
func assertTrueRules(t *testing.T, rules []string, data []byte) {
|
||||
for _, rule := range rules {
|
||||
r := makeRules(t, rule)
|
||||
if m, err := r.ScanMem(data, 0, 0); len(m) == 0 {
|
||||
t.Errorf("Rule < %s > did not match data < %v >", rule, data)
|
||||
} else if err != nil {
|
||||
t.Errorf("Error %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func assertFalseRules(t *testing.T, rules []string, data []byte) {
|
||||
for _, rule := range rules {
|
||||
r := makeRules(t, rule)
|
||||
if m, err := r.ScanMem(data, 0, 0); len(m) > 0 {
|
||||
t.Errorf("Rule < %s > matched data < %v >", rule, data)
|
||||
} else if err != nil {
|
||||
t.Errorf("Error %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoad(t *testing.T) {
|
||||
r, err := LoadRules("testrules.yac")
|
||||
if r == nil || err != nil {
|
||||
t.Fatalf("LoadRules: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReader(t *testing.T) {
|
||||
rd, err := os.Open("testrules.yac")
|
||||
if err != nil {
|
||||
t.Fatalf("os.Open: %s", err)
|
||||
}
|
||||
r, err := ReadRules(rd)
|
||||
if err != nil {
|
||||
t.Fatalf("ReadRules: %+v", err)
|
||||
}
|
||||
m, err := r.ScanMem([]byte(" abc "), 0, 0)
|
||||
if err != nil {
|
||||
t.Errorf("ScanMem: %s", err)
|
||||
}
|
||||
t.Logf("Matches: %+v", m)
|
||||
}
|
||||
|
||||
func TestWriter(t *testing.T) {
|
||||
rd, err := os.Open("testrules.yac")
|
||||
if err != nil {
|
||||
t.Fatalf("os.Open: %s", err)
|
||||
}
|
||||
compareBuf, _ := ioutil.ReadAll(rd)
|
||||
r, _ := Compile("rule test : tag1 { meta: author = \"Hilko Bengen\" strings: $a = \"abc\" fullword condition: $a }",
|
||||
nil)
|
||||
wr := bytes.Buffer{}
|
||||
if err := r.Write(&wr); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
newBuf := wr.Bytes()
|
||||
if len(compareBuf) != len(newBuf) {
|
||||
t.Errorf("len(compareBuf) = %d, len(newBuf) = %d", len(compareBuf), len(newBuf))
|
||||
}
|
||||
if bytes.Compare(compareBuf, newBuf) != 0 {
|
||||
t.Error("buffers are not equal")
|
||||
}
|
||||
}
|
||||
|
||||
// compress/bzip2 seems to return short reads which apparently leads
|
||||
// to YARA complaining about corrupt files. Tested with Go 1.4, 1.5.
|
||||
func TestReaderBZIP2(t *testing.T) {
|
||||
rulesBuf := bytes.NewBuffer(nil)
|
||||
for i := 0; i < 10000; i++ {
|
||||
fmt.Fprintf(rulesBuf, "rule test%d : tag%d { meta: author = \"Hilko Bengen\" strings: $a = \"abc\" fullword condition: $a }", i, i)
|
||||
}
|
||||
r, _ := Compile(string(rulesBuf.Bytes()), nil)
|
||||
cmd := exec.Command("bzip2", "-c")
|
||||
compressStream, _ := cmd.StdinPipe()
|
||||
buf := bytes.NewBuffer(nil)
|
||||
cmd.Stdout = buf
|
||||
if err := cmd.Start(); err != nil {
|
||||
t.Errorf("start bzip2 process: %s", err)
|
||||
return
|
||||
}
|
||||
if err := r.Write(compressStream); err != nil {
|
||||
t.Errorf("pipe to bzip2 process: %s", err)
|
||||
return
|
||||
}
|
||||
compressStream.Close()
|
||||
if err := cmd.Wait(); err != nil {
|
||||
t.Errorf("wait for bzip2 process: %s", err)
|
||||
return
|
||||
}
|
||||
if _, err := ReadRules(bzip2.NewReader(bytes.NewReader(buf.Bytes()))); err != nil {
|
||||
t.Errorf("read using compress/bzip2: %s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// See https://github.com/hillu/go-yara/issues/5
|
||||
func TestScanMemCgoPointer(t *testing.T) {
|
||||
r := makeRules(t,
|
||||
"rule test : tag1 { meta: author = \"Hilko Bengen\" strings: $a = \"abc\" fullword condition: $a }")
|
||||
buf := &bytes.Buffer{}
|
||||
buf.Write([]byte(" abc "))
|
||||
if err := func() (p interface{}) {
|
||||
defer func() { p = recover() }()
|
||||
r.ScanMem(buf.Bytes(), 0, 0)
|
||||
return nil
|
||||
}(); err != nil {
|
||||
t.Errorf("ScanMem panicked: %s", err)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
// Copyright © 2015-2016 Hilko Bengen <bengen@hilluzination.de>. All rights reserved.
|
||||
// Use of this source code is governed by the license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// This file contains functionality that require libyara 3.4 or higher
|
||||
|
||||
// +build !yara3.3
|
||||
|
||||
package yara
|
||||
|
||||
/*
|
||||
#include <yara.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
int _yr_rules_scan_fd(
|
||||
YR_RULES* rules,
|
||||
int fd,
|
||||
int flags,
|
||||
YR_CALLBACK_FUNC callback,
|
||||
void* user_data,
|
||||
int timeout);
|
||||
#else
|
||||
#define _yr_rules_scan_fd yr_rules_scan_fd
|
||||
#endif
|
||||
|
||||
size_t streamRead(void* ptr, size_t size, size_t nmemb, void* user_data);
|
||||
size_t streamWrite(void* ptr, size_t size, size_t nmemb, void* user_data);
|
||||
|
||||
int rules_callback(int message, void *message_data, void *user_data);
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"io"
|
||||
"runtime"
|
||||
"time"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// ScanFileDescriptor scans a file using the ruleset.
|
||||
func (r *Rules) ScanFileDescriptor(fd uintptr, flags ScanFlags, timeout time.Duration) (matches []MatchRule, err error) {
|
||||
id := callbackData.Put(&matches)
|
||||
defer callbackData.Delete(id)
|
||||
err = newError(C._yr_rules_scan_fd(
|
||||
r.cptr,
|
||||
C.int(fd),
|
||||
C.int(flags),
|
||||
C.YR_CALLBACK_FUNC(C.rules_callback),
|
||||
unsafe.Pointer(id),
|
||||
C.int(timeout/time.Second)))
|
||||
return
|
||||
}
|
||||
|
||||
// Write writes a compiled ruleset to an io.Writer.
|
||||
func (r *Rules) Write(wr io.Writer) (err error) {
|
||||
id := callbackData.Put(wr)
|
||||
defer callbackData.Delete(id)
|
||||
|
||||
stream := (*C.YR_STREAM)(C.malloc((C.sizeof_YR_STREAM)))
|
||||
defer C.free(unsafe.Pointer(stream))
|
||||
stream.user_data = unsafe.Pointer(id)
|
||||
stream.write = C.YR_STREAM_WRITE_FUNC(C.streamWrite)
|
||||
|
||||
err = newError(C.yr_rules_save_stream(r.cptr, stream))
|
||||
return
|
||||
}
|
||||
|
||||
// ReadRules retrieves a compiled ruleset from an io.Reader
|
||||
func ReadRules(rd io.Reader) (*Rules, error) {
|
||||
r := &Rules{rules: &rules{}}
|
||||
id := callbackData.Put(rd)
|
||||
defer callbackData.Delete(id)
|
||||
|
||||
stream := (*C.YR_STREAM)(C.malloc((C.sizeof_YR_STREAM)))
|
||||
defer C.free(unsafe.Pointer(stream))
|
||||
stream.user_data = unsafe.Pointer(id)
|
||||
stream.read = C.YR_STREAM_READ_FUNC(C.streamRead)
|
||||
|
||||
if err := newError(C.yr_rules_load_stream(stream,
|
||||
&(r.rules.cptr))); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
runtime.SetFinalizer(r.rules, (*rules).finalize)
|
||||
return r, nil
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
// Copyright © 2015 Hilko Bengen <bengen@hilluzination.de>. All rights reserved.
|
||||
// Use of this source code is governed by the license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package yara
|
||||
|
||||
import (
|
||||
"io"
|
||||
"reflect"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// #include <string.h>
|
||||
import "C"
|
||||
|
||||
//export streamRead
|
||||
func streamRead(ptr unsafe.Pointer, size, nmemb C.size_t, userData unsafe.Pointer) C.size_t {
|
||||
if size == 0 || nmemb == 0 {
|
||||
return nmemb
|
||||
}
|
||||
reader := callbackData.Get(uintptr(userData)).(io.Reader)
|
||||
buf := make([]byte, 0)
|
||||
hdr := (*reflect.SliceHeader)(unsafe.Pointer(&buf))
|
||||
hdr.Data = uintptr(ptr)
|
||||
hdr.Len = int(size * nmemb)
|
||||
hdr.Cap = hdr.Len
|
||||
s := int(size)
|
||||
for i := 0; i < int(nmemb); i++ {
|
||||
if sz, err := io.ReadFull(reader, buf[i*s:(i+1)*s]); sz < int(size) && err != nil {
|
||||
return C.size_t(i)
|
||||
}
|
||||
}
|
||||
return nmemb
|
||||
}
|
||||
|
||||
func writeFull(w io.Writer, buf []byte) (n int, err error) {
|
||||
var i int
|
||||
for n < len(buf) {
|
||||
i, err = w.Write(buf[n:])
|
||||
n += i
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
//export streamWrite
|
||||
func streamWrite(ptr unsafe.Pointer, size, nmemb C.size_t, userData unsafe.Pointer) C.size_t {
|
||||
if size == 0 || nmemb == 0 {
|
||||
return nmemb
|
||||
}
|
||||
writer := callbackData.Get(uintptr(userData)).(io.Writer)
|
||||
buf := make([]byte, 0)
|
||||
hdr := (*reflect.SliceHeader)(unsafe.Pointer(&buf))
|
||||
hdr.Data = uintptr(ptr)
|
||||
hdr.Len = int(size * nmemb)
|
||||
hdr.Cap = hdr.Len
|
||||
s := int(size)
|
||||
for i := 0; i < int(nmemb); i++ {
|
||||
if sz, err := writeFull(writer, buf[i*s:(i+1)*s]); sz < int(size) && err != nil {
|
||||
return C.size_t(i)
|
||||
}
|
||||
}
|
||||
return nmemb
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
package yara
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/user"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFileWalk(t *testing.T) {
|
||||
if os.ExpandEnv("${TEST_WALK}") == "" {
|
||||
t.Skip("Set TEST_WALK to enable scanning files from user's HOME with a dummy ruleset.\n" +
|
||||
"(Setting -test.timeout may be a good idea for this.)")
|
||||
}
|
||||
initialPath := os.ExpandEnv("${TEST_WALK_START}")
|
||||
if initialPath == "" {
|
||||
if u, err := user.Current(); err != nil {
|
||||
t.Skip("Could get user's homedir. You can use TEST_WALK_START " +
|
||||
"to set an initial path for filepath.Walk()")
|
||||
} else {
|
||||
initialPath = u.HomeDir
|
||||
}
|
||||
}
|
||||
r, err := Compile("rule test: tag1 tag2 tag3 { meta: foo = 1 bar = \"xxx\" quux = false condition: true }", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
wg := sync.WaitGroup{}
|
||||
for i := 0; i < 32; i++ {
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
filepath.Walk(initialPath, func(name string, info os.FileInfo, inErr error) error {
|
||||
fmt.Printf("[%02d] %s\n", i, name)
|
||||
if inErr != nil {
|
||||
fmt.Printf("[%02d] Walk to \"%s\": %s\n", i, name, inErr)
|
||||
return nil
|
||||
}
|
||||
if info.IsDir() && info.Mode()&os.ModeSymlink != 0 {
|
||||
fmt.Printf("[%02d] Walk to \"%s\": Skipping symlink\n", i, name)
|
||||
return filepath.SkipDir
|
||||
}
|
||||
if !info.Mode().IsRegular() || info.Size() >= 2000000 {
|
||||
return nil
|
||||
}
|
||||
if m, err := r.ScanFile(name, 0, 0); err == nil {
|
||||
fmt.Printf("[%02d] Scan \"%s\": %d\n", i, path.Base(name), len(m))
|
||||
} else {
|
||||
fmt.Printf("[%02d] Scan \"%s\": %s\n", i, path.Base(name), err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
wg.Done()
|
||||
}(i)
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
rule test : tag1 { meta: author = "Hilko Bengen" strings: $a = "abc" fullword condition: $a }
|
|
@ -0,0 +1,31 @@
|
|||
// Copyright © 2015 Hilko Bengen <bengen@hilluzination.de>. All rights reserved.
|
||||
// Use of this source code is governed by the license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package yara
|
||||
|
||||
func toint64(number interface{}) int64 {
|
||||
switch number.(type) {
|
||||
case int:
|
||||
return int64(number.(int))
|
||||
case int8:
|
||||
return int64(number.(int8))
|
||||
case int16:
|
||||
return int64(number.(int16))
|
||||
case int32:
|
||||
return int64(number.(int32))
|
||||
case int64:
|
||||
return int64(number.(int64))
|
||||
case uint:
|
||||
return int64(number.(uint))
|
||||
case uint8:
|
||||
return int64(number.(uint8))
|
||||
case uint16:
|
||||
return int64(number.(uint16))
|
||||
case uint32:
|
||||
return int64(number.(uint32))
|
||||
case uint64:
|
||||
return int64(number.(uint64))
|
||||
}
|
||||
panic("wrong number")
|
||||
}
|
Загрузка…
Ссылка в новой задаче