pax_global_header00006660000000000000000000000064140463143550014517gustar00rootroot0000000000000052 comment=6724dc5e1a27cc1cf77cf3b11764e482fa21254e go-yara-4.1.0/000077500000000000000000000000001404631435500130605ustar00rootroot00000000000000go-yara-4.1.0/.travis.yml000066400000000000000000000013541404631435500151740ustar00rootroot00000000000000language: go go: - 1.9.x - 1.10.x - 1.11.x - 1.12.x - 1.13.x - 1.14.x - tip dist: bionic addons: apt: packages: - bison - flex - automake - autoconf - libtool - make - gcc - pkg-config before_install: - YARA_VERSION=4.1.0 - wget --no-verbose -O- https://github.com/VirusTotal/yara/archive/v${YARA_VERSION}.tar.gz | tar -C ${HOME} -xzf - - ( cd ${HOME}/yara-${YARA_VERSION} && ./bootstrap.sh ) - ( mkdir -p ${HOME}/yara-build && cd ${HOME}/yara-build && ${HOME}/yara-${YARA_VERSION}/configure --prefix=${HOME}/prefix ) - make -C ${HOME}/yara-build install - find ${HOME}/prefix - export PKG_CONFIG_PATH=${HOME}/prefix/lib/pkgconfig - export LD_LIBRARY_PATH=${HOME}/prefix/lib go-yara-4.1.0/LICENSE000066400000000000000000000024551404631435500140730ustar00rootroot00000000000000Copyright (c) 2015-2020 Hilko Bengen 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. go-yara-4.1.0/README.cross-building.md000066400000000000000000000104621404631435500172650ustar00rootroot00000000000000# Cross-building _go-yara_ _go-yara_ can be cross-built for a different CPU architecture/operating system platform, provided a C cross-compiler for the target platform is available to be used by the _cgo_ tool. After the _yara_ library has been built using the proper C cross-compiler through the usual `configure / make / make install` steps, _go-yara_ can be built and installed. The following environment variables need to be set when running `go build` or `go install`: - `GOOS`, `GOARCH` indicate the cross compilation target. - `CGO_ENABLED` has to be set to 1 beacuse it defaults to 0 when cross-compiling. - `CC` may have to be set to point to the C cross compiler. (It defaults to the system C compiler, usually gcc). - `PKG_CONFIG_PATH` may have to be set to point to the _pkg-config_ directory where the `yara.pc` file has been installed. (Alternatively, the `yara_no_pkg_config` build tag can be used together with `CGO_CFLAGS` and `CGO_LDFLAGS` environment variables.) Since the MinGW environments provided by [MSYS2](https://msys2.org/) are technically cross-building environments, similar steps have to be taken, see below. ## Example: Building an entirely static Linux binary (musl-libc) Because binaries that are linked statically with GNU libc are not entirely portable [musl libc](https://www.musl-libc.org/) can be used instead. (Sadly, it does not seem to be possible to cross-build for different architctures using the standard `musl-gcc.specs` file.) On a Debian-based system, install `musl-tools`. Build _libyara_ and [`_examples/simple-yara`](_examples/simple-yara): ``` shell ( cd ${YARA_BUILD_LINUX_MUSL} && \ ${YARA_SRC}/configure CC=musl-gcc --prefix=${PREFIX_LINUX_MUSL}) make -C ${YARA_BUILD_LINUX_MUSL} install GOOS=linux GOARCH=amd64 CGO_ENABLED=1 \ CC=musl-gcc \ PKG_CONFIG_PATH=${PREFIX_LINUX_MUSL}/lib/pkgconfig \ go build -ldflags '-extldflags "-static"' -tags yara_static -o simple-yara-musl ./_examples/simple-yara ``` ## Example: Cross-building for Windows On a Debian-based system, install the MinGW C compiler `gcc-mingw-w64-i686`, `gcc-mingw-w64-x86-64` for Win32, Win64, respectively. Build _libyara_ and [`_examples/simple-yara`](_examples/simple-yara) for Win32: ``` shell ( cd ${YARA_BUILD_WIN32} && \ ${YARA_SRC}/configure --host=i686-w64-mingw32 --prefix=${PREFIX_WIN32} ) make -C ${YARA_BUILD_WIN32} install GOOS=windows GOARCH=386 CGO_ENABLED=1 \ CC=i686-w64-mingw32-gcc \ PKG_CONFIG_PATH=${PREFIX_WIN32}/lib/pkgconfig \ go build -ldflags '-extldflags "-static"' -tags yara_static -o simple-yara-w32.exe ./_examples/simple-yara ``` Build _libyara_ and [`_examples/simple-yara`](_examples/simple-yara) for Win64: ``` shell ( cd ${YARA_BUILD_WIN64} && \ ${YARA_SRC}/configure --host=x86_64-w64-mingw32 --prefix=${PREFIX_WIN64} ) make -C ${YARA_BUILD_WIN64} install GOOS=windows GOARCH=amd64 CGO_ENABLED=1 \ CC=x86_64-w64-mingw32-gcc \ PKG_CONFIG_PATH=${PREFIX_WIN64}/lib/pkgconfig \ go build -ldflags '-extldflags "-static"' -tags yara_static -o simple-yara-w64.exe ./_examples/simple-yara ``` ## Example: Building on Windows, using MSYS2 This example assumes that [MSYS2](https://msys2.org/) has been installed to `C:\msys64`. The following packages have to be installed: - autoconf - automake - libtool - mingw-w64-{x86_64,i686}-gcc - mingw-w64-{x86_64,i686}-make - mingw-w64-{x86_64,i686}-pkgconf While MSYS2 contains usable Go compilers, the [official distribution](https://golang.org/dl) is used here. Build _libyara_ and [`_examples/simple-yara`](_examples/simple-yara) for Win32: - At the MinGW 32 prompt: ``` cd ${YARA_BUILD_WIN32} && ${YARA_SRC}/configure make -C ${YARA_BUILD_WIN32} install ``` - At the CMD or Powershell prompt: ``` set PATH=%PATH%;C:\msys64\mingw32\bin set CGO_ENABLED=1 set GOARCH=386 go build -ldflags "-extldflags=-static" -tags yara_static -o simple-yara-w32.exe .\_examples\simple-yara ``` Build _libyara_ and [`_examples/simple-yara`](_examples/simple-yara) for Win64: - At the MinGW 64 prompt: ``` cd ${YARA_BUILD_WIN64} && ${YARA_SRC}/configure make -C ${YARA_BUILD_WIN64} install ``` - At the CMD or Powershell prompt: ``` set PATH=%PATH%;C:\msys64\mingw64\bin set CGO_ENABLED=1 set GOARCH=amd64 go build -ldflags "-extldflags=-static" -tags yara_static -o simple-yara-w64.exe .\_examples\simple-yara ``` go-yara-4.1.0/README.md000066400000000000000000000054101404631435500143370ustar00rootroot00000000000000![Logo](/goyara-logo.png) # go-yara [![PkgGoDev](https://pkg.go.dev/badge/github.com/hillu/go-yara/v4)](https://pkg.go.dev/github.com/hillu/go-yara/v4) [![Travis](https://travis-ci.org/hillu/go-yara.svg?branch=master)](https://travis-ci.org/hillu/go-yara) [![Go Report Card](https://goreportcard.com/badge/github.com/hillu/go-yara)](https://goreportcard.com/report/github.com/hillu/go-yara) Go bindings for [YARA](https://virustotal.github.io/yara/), staying as close as sensible to the library's C-API while taking inspiration from the `yara-python` implementation. ## Build/Installation On Unix-like systems, _libyara_ version 4.1, corresponding header files, and _pkg-config_ must be installed. Adding _go-yara_ v4 to a project with Go Modules enabled, simply add the proper dependency… ``` go import "github.com/hillu/go-yara/v4" ``` …and rebuild your package. If _libyara_ has been installed to a custom location, the `PKG_CONFIG_PATH` environment variable can be used to point _pkg-config_ at the right `yara.pc` file. For anything more complicated, refer to the "Build Tags" section below. Instructions for cross-building _go-yara_ for different operating systems or architectures can be found in [README.cross-building.md](README.cross-building.md). To build _go-yara_ on Windows, a GCC-based build environment is required, preferably one that includes _pkg-config_. The 32-bit and 64-bit MinGW environments provided by the [MSYS2](https://msys2.org/) provide such an environment. ## Build Tags ### Static builds The build tag `yara_static` can be used to tell the Go toolchain to run _pkg-config_ with the `--static` switch. This is not enough for a static build; the appropriate linker flags (e.g. `-extldflags "-static"`) still need to be passed to the _go_ tool. ### Building without _pkg-config_ The build tag `yara_no_pkg_config` can be used to tell the Go toolchain not to use _pkg-config_'s output. In this case, any compiler or linker flags have to be set via the `CGO_CFLAGS` and `CGO_LDFLAGS` environment variables, e.g.: ``` export CGO_CFLAGS="-I${YARA_SRC}/libyara/include" export CGO_LDFLAGS="-L${YARA_SRC}/libyara/.libs -lyara" go install -tags yara_no_pkg_config github.com/hillu/go-yara ``` ## YARA 4.1.x vs. earlier versions This version of _go-yara_ can only be used with YARA 4.1 or later. Version of _go-yara_ compatible with YARA 4.0.x are available via the `v4.0.x` branch or tagged `v4.0.*` releases. Versions of _go-yara_ compatible with YARA 3.11 are available via the `v3.x` branch or tagged `v3.*` releases. Versions of _go-yara_ compatible with earlier 3.x versions of YARA are available via the `v1.x` branch or tagged `v1.*` releases. ## License BSD 2-clause, see LICENSE file in the source distribution. ## Author Hilko Bengen <> go-yara-4.1.0/_examples/000077500000000000000000000000001404631435500150355ustar00rootroot00000000000000go-yara-4.1.0/_examples/simple-yara/000077500000000000000000000000001404631435500172605ustar00rootroot00000000000000go-yara-4.1.0/_examples/simple-yara/flags.go000066400000000000000000000025701404631435500207070ustar00rootroot00000000000000package main import ( "errors" "fmt" "strconv" "strings" ) type rule struct{ namespace, filename string } type rules []rule func (r *rules) Set(arg string) error { if len(arg) == 0 { return errors.New("empty rule specification") } a := strings.SplitN(arg, ":", 2) switch len(a) { case 1: *r = append(*r, rule{filename: a[0]}) case 2: *r = append(*r, rule{namespace: a[0], filename: a[1]}) } return nil } func (r *rules) String() string { var s string for _, rule := range *r { if len(s) > 0 { s += " " } if rule.namespace != "" { s += rule.namespace + ":" } s += rule.filename } return s } type variables map[string]interface{} func (v *variables) Set(s string) error { if *v == nil { *v = make(variables) } kv := strings.SplitN(s, "=", 2) if len(kv) != 2 { return errors.New("expected key=value") } if _, ok := (*v)[kv[0]]; ok { return fmt.Errorf("duplicate identifier '%s'", kv[0]) } if kv[1] == "true" { (*v)[kv[0]] = true } else if kv[1] == "false" { (*v)[kv[0]] = false } else if i, err := strconv.Atoi(kv[1]); err == nil { (*v)[kv[0]] = i } else if f, err := strconv.ParseFloat(kv[1], 64); err == nil { (*v)[kv[0]] = f } else { (*v)[kv[0]] = kv[1] } return nil } func (v *variables) String() string { var s string for k, v := range *v { if s != "" { s += " " } s += fmt.Sprintf("%s=%s", k, v) } return s } go-yara-4.1.0/_examples/simple-yara/simple-yara.go000066400000000000000000000063521404631435500220400ustar00rootroot00000000000000package main import ( "github.com/hillu/go-yara/v4" "bytes" "flag" "fmt" "log" "os" "path/filepath" "strconv" "sync" ) func printMatches(item string, m []yara.MatchRule, err error) { if err != nil { log.Printf("%s: error: %s", item, err) return } if len(m) == 0 { log.Printf("%s: no matches", item) return } buf := &bytes.Buffer{} fmt.Fprintf(buf, "%s: [", item) for i, match := range m { if i > 0 { fmt.Fprint(buf, ", ") } fmt.Fprintf(buf, "%s:%s", match.Namespace, match.Rule) } fmt.Fprint(buf, "]") log.Print(buf.String()) } func main() { var ( rules rules vars variables processScan bool pids []int threads int ) flag.BoolVar(&processScan, "processes", false, "scan processes instead of files") flag.Var(&rules, "rule", "add rules in source form: [namespace:]filename") flag.Var(&vars, "define", "define variable referenced n ruleset") flag.IntVar(&threads, "threads", 1, "use specified number of threads") flag.Parse() if len(rules) == 0 { flag.Usage() log.Fatal("no rules specified") } args := flag.Args() if len(args) == 0 { flag.Usage() log.Fatal("no files or processes specified") } if processScan { for _, arg := range args { if pid, err := strconv.Atoi(arg); err != nil { log.Fatalf("Could not parse %s ad number", arg) } else { pids = append(pids, pid) } } } c, err := yara.NewCompiler() if err != nil { log.Fatalf("Failed to initialize YARA compiler: %s", err) } for id, value := range vars { if err := c.DefineVariable(id, value); err != nil { log.Fatal("failed to define variable '%s': %s", id, err) } } for _, rule := range rules { f, err := os.Open(rule.filename) if err != nil { log.Fatalf("Could not open rule file %s: %s", rule.filename, err) } err = c.AddFile(f, rule.namespace) f.Close() if err != nil { log.Fatalf("Could not parse rule file %s: %s", rule.filename, err) } } r, err := c.GetRules() if err != nil { log.Fatalf("Failed to compile rules: %s", err) } wg := sync.WaitGroup{} wg.Add(threads) if processScan { c := make(chan int, threads) for i := 0; i < threads; i++ { s, _ := yara.NewScanner(r) go func(c chan int, tid int) { for pid := range c { var m yara.MatchRules log.Printf("<%02d> Scanning process %d...", tid, pid) err := s.SetCallback(&m).ScanProc(pid) printMatches(fmt.Sprintf(" Scanning file %s... ", tid, filename) err := s.SetCallback(&m).ScanFile(filename) printMatches(filename, m, err) } wg.Done() }(c, i) } for _, path := range args { if err := filepath.Walk(path, func(path string, info os.FileInfo, err error) error { if info.Mode().IsRegular() { c <- path } else if info.Mode().IsDir() { return nil } else { log.Printf("Sipping %s", path) } return nil }); err != nil { log.Printf("walk: %s: %s", path, err) } } close(c) } wg.Wait() } go-yara-4.1.0/cbpool.go000066400000000000000000000051101404631435500146620ustar00rootroot00000000000000// Copyright © 2015-2020 Hilko Bengen // All rights reserved. // // Use of this source code is governed by the license that can be // found in the LICENSE file. package yara import ( "reflect" "runtime" "sync" "unsafe" ) // #include import "C" // cbPoolPool implements a key/value store for data that is safe // to pass as callback data through CGO functions. // // The keys are pointers which do not directly reference the stored // values, therefore any "Go pointer to Go pointer" errors are avoided. type cbPool struct { indices []int objects []interface{} m sync.RWMutex } // MakePool creates a Pool that can hold n elements. func makecbPool(n int) *cbPool { p := &cbPool{ indices: make([]int, 0), objects: make([]interface{}, n), } hdr := (*reflect.SliceHeader)(unsafe.Pointer(&p.indices)) hdr.Data = uintptr(C.calloc(C.size_t(n), C.size_t(unsafe.Sizeof(int(0))))) hdr.Len = n runtime.SetFinalizer(p, (*cbPool).Finalize) return p } // Put adds an element to the cbPool, returning a stable pointer // suitable for passing through CGO. It panics if the pool is full. func (p *cbPool) Put(obj interface{}) unsafe.Pointer { p.m.Lock() defer p.m.Unlock() for id, val := range p.indices { if val != 0 { continue } p.indices[id] = id + 1 p.objects[id] = obj return unsafe.Pointer(&p.indices[id]) } panic("cbPool storage exhausted") } func (p *cbPool) checkPointer(ptr unsafe.Pointer) { if uintptr(ptr) < uintptr(unsafe.Pointer(&p.indices[0])) || uintptr(unsafe.Pointer(&p.indices[len(p.indices)-1])) < uintptr(ptr) { panic("Attempt to access pool using invalid pointer") } } // Get accesses an element stored in the cbPool, using a pointer // previously returned by Put. It panics if the pointer is invalid or // if it references an empty slot. func (p *cbPool) Get(ptr unsafe.Pointer) interface{} { p.m.RLock() defer p.m.RUnlock() p.checkPointer(ptr) id := *(*int)(ptr) - 1 if id == -1 { panic("Attempt to get nonexistent value from pool") } return p.objects[id] } // Delete removes an element from the cbPool, using a pointer previously // returned by Put. It panics if the pointer is invalid or if it // references an empty slot. func (p *cbPool) Delete(ptr unsafe.Pointer) { p.m.Lock() defer p.m.Unlock() p.checkPointer(ptr) id := *(*int)(ptr) - 1 if id == -1 { panic("Attempt to delete nonexistent value from pool") } p.indices[id] = 0 p.objects[id] = nil return } func (p *cbPool) Finalize() { p.m.Lock() defer p.m.Unlock() if p.indices != nil { C.free(unsafe.Pointer(&p.indices[0])) p.indices = nil } } go-yara-4.1.0/cbpool_test.go000066400000000000000000000024601404631435500157260ustar00rootroot00000000000000// Copyright © 2015-2020 Hilko Bengen // All rights reserved. // // Use of this source code is governed by the license that can be // found in the LICENSE file. package yara import ( "testing" ) func TestBasic(t *testing.T) { pool := makecbPool(32) p1 := pool.Put("asdf") p2 := pool.Put("ghjk") s1, ok := pool.Get(p1).(string) if !ok || s1 != "asdf" { t.Errorf("s1: expected 'asdf', got '%v'", s1) } pool.Delete(p1) i := func() interface{} { defer func() { if x := recover(); x != nil { t.Logf("Get: Got expected panic: %v", x) } }() x := pool.Get(p1) t.Error("Get: No panic was triggered.") return x }() if s1, ok := i.(string); ok || s1 == "asdf" { t.Errorf("s1: expected nil, got '%v'", s1) } s2, ok := pool.Get(p2).(string) if !ok || s2 != "ghjk" { t.Errorf("s1: expected 'hjkl', got '%v'", s1) } pool.Delete(p2) func() { defer func() { if x := recover(); x != nil { t.Logf("Delete: Got expected panic: %v", x) } }() pool.Delete(p2) t.Error("Delete: No panic was triggered.") }() // Fill pool for i := 0; i < 32; i++ { pool.Put(i) } func() { defer func() { if x := recover(); x != nil { t.Logf("full pool: Got expected panic: %v", x) } }() pool.Put(100) t.Error("full pool: No panic was triggered.") }() } go-yara-4.1.0/cgo.go000066400000000000000000000007601404631435500141620ustar00rootroot00000000000000// Copyright © 2015-2020 Hilko Bengen // All rights reserved. // // Use of this source code is governed by the license that can be // found in the LICENSE file. package yara // #cgo !yara_no_pkg_config,!yara_static pkg-config: yara // #cgo !yara_no_pkg_config,yara_static pkg-config: --static yara // #cgo yara_no_pkg_config LDFLAGS: -lyara /* #include #if YR_VERSION_HEX < 0x040100 #error YARA version 4.1 required #endif */ import "C" go-yara-4.1.0/compiler.go000066400000000000000000000207541404631435500152310ustar00rootroot00000000000000// Copyright © 2015-2020 Hilko Bengen // 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 #include #include void compilerCallback(int, char*, int, YR_RULE*, char*, void*); char* includeCallback(char*, char*, char*, void*); void freeCallback(char*, void*); */ import "C" import ( "errors" "os" "reflect" "runtime" "unsafe" ) //export compilerCallback func compilerCallback(errorLevel C.int, filename *C.char, linenumber C.int, rule *C.YR_RULE, message *C.char, userData unsafe.Pointer) { c := callbackData.Get(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. // // Since this type contains a C pointer to a YR_COMPILER structure // that may be automatically freed, it should not be copied. type Compiler struct { Errors []CompilerMessage Warnings []CompilerMessage // used for include callback callbackData unsafe.Pointer 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{cptr: yrCompiler} runtime.SetFinalizer(c, (*Compiler).Destroy) return c, nil } // Destroy destroys the YARA data structure representing a compiler. // // It should not be necessary to call this method directly. func (c *Compiler) Destroy() { if c.cptr != nil { C.yr_compiler_destroy(c.cptr) c.cptr = nil } runtime.SetFinalizer(c, nil) } func (c *Compiler) setCallbackData(ptr unsafe.Pointer) { if c.callbackData != nil { callbackData.Delete(c.callbackData) } c.callbackData = ptr } // AddFile compiles rules from a file. Rules are added to the // specified namespace. // // If this function returns an error, the Compiler object will become // unusable. func (c *Compiler) AddFile(file *os.File, namespace string) (err error) { if c.cptr.errors != 0 { return errors.New("Compiler cannot be used after parse error") } 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), id) numErrors := int(C.yr_compiler_add_fd(c.cptr, (C.YR_FILE_DESCRIPTOR)(file.Fd()), 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) } runtime.KeepAlive(c) return } // AddString compiles rules from a string. Rules are added to the // specified namespace. // // If this function returns an error, the Compiler object will become // unusable. func (c *Compiler) AddString(rules string, namespace string) (err error) { if c.cptr.errors != 0 { return errors.New("Compiler cannot be used after parse 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), 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) } runtime.KeepAlive(c) return } // DefineVariable defines a named variable for use by the compiler. // Boolean, int64, float64, and string types are supported. func (c *Compiler) DefineVariable(identifier string, value interface{}) (err error) { cid := C.CString(identifier) defer C.free(unsafe.Pointer(cid)) switch value.(type) { case bool: var v int if value.(bool) { v = 1 } err = newError(C.yr_compiler_define_boolean_variable( c.cptr, cid, 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, cid, C.int64_t(value))) case float64: err = newError(C.yr_compiler_define_float_variable( c.cptr, cid, 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, cid, cvalue)) default: err = errors.New("wrong value type passed to DefineVariable; bool, int64, float64, string are accepted") } runtime.KeepAlive(c) return } // GetRules returns the compiled ruleset. func (c *Compiler) GetRules() (*Rules, error) { if c.cptr.errors != 0 { return nil, errors.New("Compiler cannot be used after parse error") } var yrRules *C.YR_RULES if err := newError(C.yr_compiler_get_rules(c.cptr, &yrRules)); err != nil { return nil, err } r := &Rules{cptr: yrRules} runtime.SetFinalizer(r, (*Rules).Destroy) runtime.KeepAlive(c) return r, nil } //export includeCallback func includeCallback(name, filename, namespace *C.char, userData unsafe.Pointer) *C.char { callbackFunc := callbackData.Get(userData).(CompilerIncludeFunc) if buf := callbackFunc( C.GoString(name), C.GoString(filename), C.GoString(namespace), ); buf != nil { ptr := C.calloc(1, C.size_t(len(buf)+1)) if ptr == nil { return nil } outbuf := make([]byte, 0) hdr := (*reflect.SliceHeader)(unsafe.Pointer(&outbuf)) hdr.Data, hdr.Len = uintptr(ptr), len(buf)+1 copy(outbuf, buf) return (*C.char)(ptr) } return nil } //export freeCallback func freeCallback(callbackResultPtr *C.char, userData unsafe.Pointer) { if callbackResultPtr != nil { C.free(unsafe.Pointer(callbackResultPtr)) } return } // CompilerIncludeFunc is used with Compiler.SetIncludeCallback. // Arguments are: name for the rule file to be included, filename for // the file that contains the include statement, namespace for the rule // namespace. The function returns a byte slice containing the // contents of the included file. It must return a nil return value on // error. // // See also: yr_compiler_set_include_callback in the YARA C API // documentation. type CompilerIncludeFunc func(name, filename, namespace string) []byte // SetIncludeCallback registers an include function that is called // (through Go glue code) by the YARA compiler for every include // statement. func (c *Compiler) SetIncludeCallback(cb CompilerIncludeFunc) { if cb == nil { c.DisableIncludes() return } id := callbackData.Put(cb) c.setCallbackData(id) C.yr_compiler_set_include_callback( c.cptr, C.YR_COMPILER_INCLUDE_CALLBACK_FUNC(C.includeCallback), C.YR_COMPILER_INCLUDE_FREE_FUNC(C.freeCallback), id, ) runtime.KeepAlive(c) return } // DisableIncludes disables all include statements in the compiler. // See yr_compiler_set_include_callbacks. func (c *Compiler) DisableIncludes() { C.yr_compiler_set_include_callback(c.cptr, nil, nil, nil) c.setCallbackData(nil) runtime.KeepAlive(c) return } // 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 } defer c.Destroy() 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 } go-yara-4.1.0/compiler_test.go000066400000000000000000000040031404631435500162550ustar00rootroot00000000000000// Copyright © 2015-2020 Hilko Bengen // All rights reserved. // // Use of this source code is governed by the license that can be // found in the LICENSE file. 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) } func setupCompiler(t *testing.T) *Compiler { c, err := NewCompiler() if err != nil { t.Fatal(err) } c.SetIncludeCallback(func(name, rulefile, namespace string) []byte { t.Logf(`Processing include "%s" (from ns="%s", file="%s")`, name, namespace, rulefile) if name == "existing" { return []byte(`rule ext { condition: true }`) } return nil }) return c } func TestCompilerIncludeCallback(t *testing.T) { c := setupCompiler(t) var err error if err = c.AddString(`include "existing"`, ""); err != nil { t.Fatalf(`Failed to include "existing" rule "file": %s`, err) } if err = c.AddString(`rule int { condition: ext }`, ""); err != nil { t.Fatalf(`Failed to define rule referring to included rule: %s`, err) } c = setupCompiler(t) if err = c.AddString(`include "non-existing"`, ""); err != nil { t.Logf("Compiler returned error on attempt to include non-existing rule: %s", err) } else { t.Fatal(`Compiler did not return error on non-existing include rule`) } } go-yara-4.1.0/config.go000066400000000000000000000021051404631435500146520ustar00rootroot00000000000000// Copyright © 2015-2020 Hilko Bengen // All rights reserved. // // Use of this source code is governed by the license that can be // found in the LICENSE file. package yara // #include import "C" import "unsafe" type ConfigName uint32 const ( ConfigStackSize ConfigName = C.YR_CONFIG_STACK_SIZE ConfigMaxMatchData = C.YR_CONFIG_MAX_MATCH_DATA ConfigMaxStringsPerRule = C.YR_CONFIG_MAX_STRINGS_PER_RULE ) // SetConfiguration sets a global YARA configuration option. func SetConfiguration(name ConfigName, src interface{}) error { i, ok := src.(int) if !ok { return newError(C.ERROR_INTERNAL_FATAL_ERROR) } u := C.uint32_t(i) return newError( C.yr_set_configuration(C.YR_CONFIG_NAME(name), unsafe.Pointer(&u))) } // GetConfiguration gets a global YARA configuration option. func GetConfiguration(name ConfigName) (interface{}, error) { var u C.uint32_t if err := newError(C.yr_get_configuration( C.YR_CONFIG_NAME(name), unsafe.Pointer(&u)), ); err != nil { return nil, err } return int(u), nil } go-yara-4.1.0/error.go000066400000000000000000000105501404631435500145410ustar00rootroot00000000000000// Copyright © 2015-2020 Hilko Bengen // All rights reserved. // // Use of this source code is governed by the license that can be // found in the LICENSE file. package yara // #include import "C" import "strconv" // Error encapsulates the C API error codes. type Error int func (e Error) Error() string { if str, ok := errorStrings[int(e)]; ok { return str } return "unknown YARA error " + strconv.Itoa(int(e)) } func newError(code C.int) error { if code != 0 { return Error(code) } return nil } // FIXME: This should be generated from yara/error.h var errorStrings = map[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", C.ERROR_EMPTY_STRING: "empty string", C.ERROR_DIVISION_BY_ZERO: "division by zero", C.ERROR_REGULAR_EXPRESSION_TOO_LARGE: "regular expression too large", C.ERROR_TOO_MANY_RE_FIBERS: "too many regular expression fibers", C.ERROR_COULD_NOT_READ_PROCESS_MEMORY: "could not read process memory", C.ERROR_INVALID_EXTERNAL_VARIABLE_TYPE: "invalid external variable type", C.ERROR_REGULAR_EXPRESSION_TOO_COMPLEX: "regular expression too complex", C.ERROR_INVALID_MODULE_NAME: "invalid module name", C.ERROR_TOO_MANY_STRINGS: "too many strings", C.ERROR_INTEGER_OVERFLOW: "integer overflow", C.ERROR_CALLBACK_REQUIRED: "callback required", C.ERROR_INVALID_OPERAND: "invalid operand", C.ERROR_COULD_NOT_READ_FILE: "could not read file", C.ERROR_DUPLICATED_EXTERNAL_VARIABLE: "duplicated external variable", C.ERROR_INVALID_MODULE_DATA: "invalid module data", C.ERROR_WRITING_FILE: "error writing file", C.ERROR_INVALID_MODIFIER: "invalid modifier", C.ERROR_DUPLICATED_MODIFIER: "duplicated modifier", } go-yara-4.1.0/example_mem_blocks_test.go000066400000000000000000000043471404631435500203040ustar00rootroot00000000000000// Copyright © 2015-2020 Hilko Bengen // All rights reserved. // // Use of this source code is governed by the license that can be // found in the LICENSE file. package yara_test import ( "bytes" "fmt" "io" "github.com/hillu/go-yara/v4" ) type Iterator struct { blocksize int rs io.ReadSeeker offset int64 length int } func (s *Iterator) read(buf []byte) { s.rs.Seek(s.offset, io.SeekStart) s.rs.Read(buf) } func (s *Iterator) First() *yara.MemoryBlock { s.offset = 0 return &yara.MemoryBlock{ Base: uint64(s.offset), Size: uint64(s.length), FetchData: s.read, } } func (s *Iterator) Next() *yara.MemoryBlock { s.offset += int64(s.length) end, _ := s.rs.Seek(0, io.SeekEnd) s.length = int(end - s.offset) if s.length <= 0 { return nil } if s.length > s.blocksize { s.length = s.blocksize } return &yara.MemoryBlock{ Base: uint64(s.offset), Size: uint64(s.length), FetchData: s.read, } } func (s *Iterator) Filesize() uint64 { end, _ := s.rs.Seek(0, io.SeekEnd) return uint64(end) } func Example_ScanMemBlocks() { // Set up a []byte-backed io.ReadSeeker buf := make([]byte, 65536) buf[0] = 0x7f buf[60000] = 0x7f copy(buf[10000:], []byte("abc")) copy(buf[20000:], []byte("def")) copy(buf[1022:], []byte("ghij")) it := &Iterator{blocksize: 1024, rs: bytes.NewReader(buf)} rs := yara.MustCompile(` rule A { strings: $abc = "abc" $def = "def" condition: uint8(0) == 0x7f and uint8(60000) == 0x7f and $abc at 10000 and $def at 20000 } // we do not expect rule B to be matched since the relevant value // crosses the block boundary at 1024. (However, it does match when // setting a blocksize that does not cause this value to be split.) rule B { strings: $ghij = "ghij" condition: $ghij at 1022 or uint32be(1022) == 0x6768696a } `, nil) var mrs yara.MatchRules err := rs.ScanMemBlocks(it, 0, 0, &mrs) if err != nil { fmt.Printf("error: %+v\n", err) return } for _, rule := range mrs { fmt.Printf("match: %s\n strings:\n", rule.Rule) for _, ms := range rule.Strings { fmt.Printf(" - %s at %d\n", ms.Name, ms.Base+ms.Offset) } } // Output: // match: A // strings: // - $abc at 10000 // - $def at 20000 } go-yara-4.1.0/go.mod000066400000000000000000000000431404631435500141630ustar00rootroot00000000000000module github.com/hillu/go-yara/v4 go-yara-4.1.0/goyara-logo.png000066400000000000000000000207071404631435500160140ustar00rootroot00000000000000PNG  IHDR=iVtEXtSoftwareAdobe ImageReadyqe<!iIDATx PU׵o0 JuPSHM TiHuXt3k+&_1ӾWvIi'F}L $H FB\['}>>5\s7^kܺu%W?Ƌc8}-#$GM"3L^dEB?J9t_g<#DC>.!zb''~(`0ADJ W;01joD RD~w'8W ,l,t$ա0Awzb Xd3WJ'/܋H!25àIVEa#jPH#` |٩u qTޱ$:+4 <3 5"hRqSO,}p.V0g;I?1UﺽaGi:T)f؇gi D;ux=lUkv;PNB|xb" z.(N"e? <,YT8utt5>Y0hN6 )su^$!$h0} 5 bF@9*%k`HyN}Z1Wio ILx9 !Fk^wyvn"C0]G<-0p汞+rCZl\c,]Xv0# h3BG4 mb=yĔxP.O:ƱϏ~XL#j4wR^PlmܢfA4x~yn WmXю`_ 'o @|Aߡ=cjaaL݉OE}}}kkKooϠ 2 `0p/?Gkx^qpif+slCCvӉ6H&]߽~ tz&Eb `4[]^gJk羴H֡`%S2m^n:zP}w#tz`4;$ƕԾJq9 ѓ.c/ldv2, o"~Q_Pk}͛7k'`غu+C`+c-Z]^U?eee4Hz|䦗f(A~3-UTvڕa"naM}B@JӋ^i&'J9AYpYbZ2$@u${Һ#W ֋:I =OlF JϑF:԰|Qc@q80Э xN(!YE$kM㌂_WMIP++kCwh) vG|xIeuzI۶p7ye_0:_MʡRSF4xGõm}<&]TT ΢Ip$my[3u $xc=WlN&]TR T:Hd+#⇒w .iD ?"a8qux*Nv"i:>~a :w۷o'K"$8i7eh*m6MCƉTTQvZ]uF,!;sD}׿~iii#6ommuyWR`έf\u`4/?d͜+Zm ,E˕ 1Z *s?oܸ|CCT99PwySPP@VLy)j_z UUU6˛7m {e$;HBh'G.^xʕ~46T&z7лtYT; O.677wt\ϼT A}|̆!dZ WU5oݺ*+O^?$@/#P0LY?qF _tQQ1yf e0T46 X1ȩnk׮\Y b!mVIl>裀P)+!kn4[n0 Mp\w61I$;+׼⪪Kns _$OaX"bLsB,Y|4Y>,[  ݪ`,׷v<2(]44Y6 o~ܣ`FC,YOU-*Uzs!d*2JTPtum&-!4 *vZ*:- 'IwiPSc Hww#oDe,FoPGW*˭fΜ3#ڻe"I4*XTXbrg /`X7Ae$B .a&*OuZٛ.KIZZڜ9sΥ&h{%XB^ưA,Zhx)粬m6 þ.+YTO~USS[YYIPՓ0o&'ܴ̀ibЋi&ޜ`E@dժb駷{oL~DMȓi1<~fg礦{LN5hV&qc0DuplatK\Ɨ= s6 I[3'|M1J, *gfB -! H DS> (3ImG[ ,sbixWJ&[Ȫ\d4[x(..BxC2 夔7~8xؐ; U,g/#ZR_]%9¦@e^w-$$Ӗ5چ/x:S5CX0A#KhQ>pezJ)>U,bBn4.ZZ~IGS/$slzJ`, i%zΕ"va42aDxJ@駷nkUzHIB`!@6pC??D)M{kvݨt Hp6m2ͦ11jr3L"xk'M U]a˗/7ds=•!,IRCyBwI" J}4ԍsc1G0sG<¿Φ)=W)#&[:7%ndWyI 6FAx2,?xGp9*x(((iNz ƨ̰,n/be#̀vyCd)Iy u]YMY!Vwt֋CA/.J,|P+5a/Kk+m^)0Uj١w'/Ac,kI+=*+++NT1'iKՒ.`YXwH|}n݉P"M<ᮀ4,L艏:HHz|_WwhF^|Txgkl7Bo⪍; (;mݦT.M 7~篝J6QRRlmm!?}t9RzoQ` TLy'hnRp #f@$7 *NvFiݷ՚5!S;q_N2%66vxxƍ2\ql@b^E&45vaaG5 r[# ,T\#8DԊ$'谼1d FZhV'\0B$29 k₍}ܨ24VAx"{G7ȑ5%[1i4f{#\XDtɊG1hD7~G^Ͻ?ץes?77~mzAw:πrB4=BK`R_$4έL#0P]oJ|Gԟ>p c鳾xOtC!|#DEepQs=MTC{z'r d$z54>-M|gh,]) #; -;^/T_+ g<ë>k:.tuAiISU%`MeK4zvH$0 %sZ8i A9„%zGDl CN:F2R vY0M8Ȑ. }.,<Q7g a4zp.|uW6'] GȜKre0h<6rBQbz @~ ``@Y<'_VHKE7u+Sf&L!Jr`nt:!.浝}x %gWOm\ g?EhL~0(]ϟ:3QoZsp:9sf__/Bj[WC0X8KkhHA *JW䦗f_جUiR݉EEŅnJ>!U|<(9d&J$ dkf%eUe*LWwuu*^w%͟?moo ẊE+ h dS&e*&*tpOkz A EYY4 phsXHJaaaj w7N^0/0kaPtnRAjқv ˛oArܱ ёxNL1Ip .,՛|s ?(qB^3ztIUyN ӥIX[E2 `h:!',Yڣ7αGZ]ǧn$c0f&|8+*K^1 gcKW F瓧}TG/kf%R8sMZ#1AQ1Fxv&9W5kը"oxe.Vd(3:zBDFaAǕw$Hkòn0UZATڀ⇵5g CƔɊ" &p߮7\VVVYY M9~ظekmcSuW4ӭh$WUȋ/3~z+ȹ ) \pPdHD "c3Ρ Eѧ_7x ,pWcyƫBpUٟ24q3G4X׶3PkK'ͅ} =3voIpgGJϾ]b i\Ӌڻa -!u6Un06?֢uhkt0-ssh٩\`pښ F$A35qL0Vnx;h)M|a. 4" g bPy}4Њ:' u&g)-bI&8 [RHe,VGƁ[M8٩"0`.:F~|'NC`6l FਈufL/`c^1b㇮cԏo-&B\w8$)ACsd~̿ LUp('7INT| ~&>!<"!s)9R%n xF0Υc go-yara-4.1.0/main.go000066400000000000000000000020151404631435500143310ustar00rootroot00000000000000// Copyright © 2015-2020 Hilko Bengen // 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 */ import "C" func init() { if err := initialize(); err != nil { panic(err) } } // Prepares the library to be used. func initialize() error { return newError(C.yr_initialize()) } // Finalize releases all the resources allocated by the YARA library. // It should be called by the program when it no longer needs YARA, // e.g. just before the program exits. It is not strictly necessary to // call Finalize because the allocated memory will be freed on program // exit; however, explicitly-freed resources will not show up as a // leak in memory profiling tools. // // A good practice is calling Finalize as a deferred function in the // program's main function: // defer yara.Finalize() func Finalize() error { return newError(C.yr_finalize()) } go-yara-4.1.0/main_test.go000066400000000000000000000015221404631435500153720ustar00rootroot00000000000000// Copyright © 2015-2020 Hilko Bengen // 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/ioutil" "log" "os" "testing" ) var compiledTestRulesPath string func TestMain(m *testing.M) { r, err := Compile(`rule test : tag1 { meta: author = "Hilko Bengen" strings: $a = "abc" fullword condition: $a }`, nil) if err != nil { log.Fatalf("Compile: %v", err) } f, err := ioutil.TempFile("", "testrules.yac") if err != nil { log.Fatalf("ioutil.TempFile: %v", err) } compiledTestRulesPath = f.Name() if err := r.Save(compiledTestRulesPath); err != nil { os.Remove(compiledTestRulesPath) log.Fatalf("Save(%q): %v", compiledTestRulesPath, err) } rc := m.Run() os.Remove(compiledTestRulesPath) os.Exit(rc) } go-yara-4.1.0/mem_blocks.go000066400000000000000000000125411404631435500155250ustar00rootroot00000000000000// Copyright © 2015-2020 Hilko Bengen // All rights reserved. // // Use of this source code is governed by the license that can be // found in the LICENSE file. package yara /* #include int scanCallbackFunc(YR_SCAN_CONTEXT*, int, void*, void*); uint8_t* memoryBlockFetch(YR_MEMORY_BLOCK*); uint8_t* memoryBlockFetchNull(YR_MEMORY_BLOCK*); YR_MEMORY_BLOCK* memoryBlockIteratorFirst(YR_MEMORY_BLOCK_ITERATOR*); YR_MEMORY_BLOCK* memoryBlockIteratorNext(YR_MEMORY_BLOCK_ITERATOR*); uint64_t memoryBlockIteratorFilesize(YR_MEMORY_BLOCK_ITERATOR*); */ import "C" import ( "reflect" "unsafe" ) // MemoryBlockIterator is a Go representation of YARA's // YR_MEMORY_BLOCK_ITERATOR mechanism that is used within // yr_rules_mem_scan_blobs. type MemoryBlockIterator interface { First() *MemoryBlock Next() *MemoryBlock } type MemoryBlockIteratorWithFilesize interface { MemoryBlockIterator Filesize() uint64 } type memoryBlockIteratorContainer struct { MemoryBlockIterator // MemoryBlock holds return values of the First and Next methods // as it is moved back to libyara. *MemoryBlock // cblock is passed to memoryBlockFetch. Its data lives in malloc // memory. cblock *C.YR_MEMORY_BLOCK // buf is used by (MemoryBlock).FetchData() to pass data back to // YARA. Its backing array lives in malloc memory and will only be // resized using the realloc method. buf []byte } func makeMemoryBlockIteratorContainer(mbi MemoryBlockIterator) (c *memoryBlockIteratorContainer) { c = &memoryBlockIteratorContainer{ MemoryBlockIterator: mbi, cblock: (*C.YR_MEMORY_BLOCK)(C.calloc(1, C.size_t(unsafe.Sizeof(C.YR_MEMORY_BLOCK{})))), buf: make([]byte, 0, 0), } hdr := (*reflect.SliceHeader)(unsafe.Pointer(&c.buf)) hdr.Data = 0 return } // The caller is responsible to remove cmbi.context from callbackData // pool. func makeCMemoryBlockIterator(c *memoryBlockIteratorContainer) (cmbi *C.YR_MEMORY_BLOCK_ITERATOR) { cmbi = &C.YR_MEMORY_BLOCK_ITERATOR{ context: callbackData.Put(c), first: C.YR_MEMORY_BLOCK_ITERATOR_FUNC(C.memoryBlockIteratorFirst), next: C.YR_MEMORY_BLOCK_ITERATOR_FUNC(C.memoryBlockIteratorNext), } if _, implementsFilesize := c.MemoryBlockIterator.(MemoryBlockIteratorWithFilesize); implementsFilesize { cmbi.file_size = C.YR_MEMORY_BLOCK_ITERATOR_SIZE_FUNC(C.memoryBlockIteratorFilesize) } return cmbi } func (c *memoryBlockIteratorContainer) realloc(size int) { if len(c.buf) < size { hdr := (*reflect.SliceHeader)(unsafe.Pointer(&c.buf)) hdr.Data = uintptr(C.realloc(unsafe.Pointer(hdr.Data), C.size_t(size))) hdr.Len = size hdr.Cap = hdr.Len } } func (c *memoryBlockIteratorContainer) free() { hdr := (*reflect.SliceHeader)(unsafe.Pointer(&c.buf)) if hdr.Cap > 0 { C.free(unsafe.Pointer(hdr.Data)) c.buf = nil } C.free(unsafe.Pointer(c.cblock)) } // MemoryBlock is returned by the MemoryBlockIterator's First and Next methods type MemoryBlock struct { // Base contains the base address of the current block Base uint64 // Size contains the size of the current block Size uint64 // FetchData is used to read size bytes into a byte slice FetchData func([]byte) } // memoryBlockFetch is used as YR_MEMORY_BLOCK.fetch_data. // It is called from YARA code. // //export memoryBlockFetch func memoryBlockFetch(cblock *C.YR_MEMORY_BLOCK) *C.uint8_t { c := callbackData.Get(cblock.context).(*memoryBlockIteratorContainer) c.realloc(int(cblock.size)) c.MemoryBlock.FetchData(c.buf) return (*C.uint8_t)(unsafe.Pointer(&c.buf[0])) } // memoryBlockFetchNull is used as YR_MEMORY_BLOCK.fetch_data for empty blocks. // It is called from YARA code. // //export memoryBlockFetchNull func memoryBlockFetchNull(*C.YR_MEMORY_BLOCK) *C.uint8_t { return nil } // memoryBlockIteratorCommon turns a MemoryBlock into a YR_MEMORY_BLOCK // structure that is used by YARA internally. func memoryBlockIteratorCommon(cmbi *C.YR_MEMORY_BLOCK_ITERATOR, c *memoryBlockIteratorContainer) (cblock *C.YR_MEMORY_BLOCK) { if c.MemoryBlock == nil { return } cblock = c.cblock cblock.base = C.uint64_t(c.MemoryBlock.Base) cblock.size = C.size_t(c.MemoryBlock.Size) cblock.fetch_data = C.YR_MEMORY_BLOCK_FETCH_DATA_FUNC(C.memoryBlockFetchNull) if c.MemoryBlock.Size == 0 { return } cblock.context = cmbi.context cblock.fetch_data = C.YR_MEMORY_BLOCK_FETCH_DATA_FUNC(C.memoryBlockFetch) return } // memoryBlockIteratorFirst is used as YR_MEMORY_BLOCK_ITERATOR.first. // It is called from YARA code. // //export memoryBlockIteratorFirst func memoryBlockIteratorFirst(cmbi *C.YR_MEMORY_BLOCK_ITERATOR) *C.YR_MEMORY_BLOCK { c := callbackData.Get(cmbi.context).(*memoryBlockIteratorContainer) c.MemoryBlock = c.MemoryBlockIterator.First() return memoryBlockIteratorCommon(cmbi, c) } // memoryBlockIteratorNext is used as YR_MEMORY_BLOCK_ITERATOR.next. // It is called from YARA code. // //export memoryBlockIteratorNext func memoryBlockIteratorNext(cmbi *C.YR_MEMORY_BLOCK_ITERATOR) *C.YR_MEMORY_BLOCK { c := callbackData.Get(cmbi.context).(*memoryBlockIteratorContainer) c.MemoryBlock = c.MemoryBlockIterator.Next() return memoryBlockIteratorCommon(cmbi, c) } //export memoryBlockIteratorFilesize func memoryBlockIteratorFilesize(cmbi *C.YR_MEMORY_BLOCK_ITERATOR) C.uint64_t { c := callbackData.Get(cmbi.context).(*memoryBlockIteratorContainer) return C.uint64_t(c.MemoryBlockIterator.(MemoryBlockIteratorWithFilesize).Filesize()) } go-yara-4.1.0/mem_blocks_test.go000066400000000000000000000037401404631435500165650ustar00rootroot00000000000000// Copyright © 2015-2020 Hilko Bengen // All rights reserved. // // Use of this source code is governed by the license that can be // found in the LICENSE file. package yara import ( "testing" ) type block struct { base uint64 data []byte } type testIter struct { data []block current int } func (it *testIter) First() *MemoryBlock { it.current = 0 return it.Next() } func (it *testIter) Next() *MemoryBlock { if it.current >= len(it.data) { return nil } data := it.data[it.current].data base := it.data[it.current].base it.current += 1 return &MemoryBlock{ Base: base, Size: uint64(len(data)), FetchData: func(buf []byte) { copy(buf, data) }, } } type testIterWithFilesize struct { testIter filesize uint64 } func (it *testIterWithFilesize) Filesize() uint64 { return it.filesize } func TestIterator(t *testing.T) { rs := MustCompile(` rule t1 { condition: true } //rule a { strings: $a = "aaaa" condition: all of them } //rule b { strings: $b = "bbbb" condition: all of them } rule t2 { strings: $a = "aaaa" $b = "bbbb" condition: $a at 0 and $b at 32 } rule t3 { condition: filesize < 20 } rule t4 { condition: filesize >= 20 } `, nil) var mrs MatchRules if err := rs.ScanMemBlocks(&testIter{}, 0, 0, &mrs); err != nil { t.Errorf("simple iterator scan (no data): %v", err) } else { t.Logf("simple iterator scan (no data): %v", mrs) } mrs = nil if err := rs.ScanMemBlocks(&testIter{ data: []block{{0, nil}}, }, 0, 0, &mrs); err != nil { t.Errorf("simple iterator scan (empty block): %v", err) } else { t.Logf("simple iterator scan (empty block): %+v", mrs) } mrs = nil if err := rs.ScanMemBlocks(&testIterWithFilesize{ testIter: testIter{ data: []block{ {0, []byte("aaaaaaaaaaaaaaaa")}, {32, []byte("bbbbbbbbbbbbbbbb")}, }, }, filesize: 64, }, 0, 0, &mrs); err != nil { t.Errorf("simple iterator scan (aaa..bbbb): %v", err) } else { t.Logf("simple iterator scan (aaa..bbb): %+v", mrs) } } go-yara-4.1.0/object.go000066400000000000000000000004271404631435500146600ustar00rootroot00000000000000// Copyright © 2015-2020 Hilko Bengen // All rights reserved. // // Use of this source code is governed by the license that can be // found in the LICENSE file. package yara /* #include */ import "C" type Object struct{ cptr *C.YR_OBJECT } go-yara-4.1.0/ported_test.go000066400000000000000000000474661404631435500157640ustar00rootroot00000000000000package 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 }") var matches MatchRules rules.ScanMem([]byte("abbb"), 0, 0, &matches) 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 } var m MatchRules r.ScanMem([]byte("dummy"), 0, 0, &m) 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")) } go-yara-4.1.0/rule.go000066400000000000000000000156421404631435500143660ustar00rootroot00000000000000// Copyright © 2015-2020 Hilko Bengen // All rights reserved. // // Use of this source code is governed by the license that can be // found in the LICENSE file. package yara /* #include // rule_identifier is a union accessor function. // (CGO does not represent them properly to Go code.) static const char* rule_identifier(YR_RULE* r) { return r->identifier; } // rule_namespace is a union accessor function. // (CGO does not represent them properly to Go code.) static const char* rule_namespace(YR_RULE* r) { return r->ns->name; } // rule_tags returns pointers to the tag names associated with a rule, // using YARA's own implementation. static void rule_tags(YR_RULE* r, const char *tags[], int *n) { const char *tag; int i = 0; yr_rule_tags_foreach(r, tag) { if (i < *n) tags[i] = tag; i++; }; *n = i; return; } // rule_tags returns pointers to the meta variables associated with a // rule, using YARA's own implementation. static void rule_metas(YR_RULE* r, const YR_META *metas[], int *n) { const YR_META *meta; int i = 0; yr_rule_metas_foreach(r, meta) { if (i < *n) metas[i] = meta; i++; }; *n = i; return; } // meta_get is a union accessor function. // (CGO does not represent them properly to Go code.) static void meta_get(YR_META *m, const char** identifier, char** string) { *identifier = m->identifier; *string = (char*)m->string; return; } // rule_strings returns pointers to the matching strings associated // with a rule, using YARA's macro-based implementation. static void rule_strings(YR_RULE* r, const YR_STRING *strings[], int *n) { const YR_STRING *string; int i = 0; yr_rule_strings_foreach(r, string) { if (i < *n) strings[i] = string; i++; } *n = i; return; } // string_identifier is a union accessor function. // (CGO does not represent them properly to Go code.) static const char* string_identifier(YR_STRING* s) { return s->identifier; } // string_matches returns pointers to the string match objects // associated with a string, using YARA's macro-based implementation. static void string_matches(YR_SCAN_CONTEXT *ctx, YR_STRING* s, const YR_MATCH *matches[], int *n) { const YR_MATCH *match; int i = 0; yr_string_matches_foreach(ctx, s, match) { if (i < *n) matches[i] = match; i++; }; *n = i; return; } // get_rules returns pointers to the RULE objects for a ruleset, using // YARA's macro-based implementation. static void get_rules(YR_RULES *ruleset, const YR_RULE *rules[], int *n) { const YR_RULE *rule; int i = 0; yr_rules_foreach(ruleset, rule) { if (i < *n) rules[i] = rule; i++; } *n = i; return; } */ import "C" import "unsafe" // Rule represents a single rule as part of a ruleset. type Rule struct { cptr *C.YR_RULE // Save underlying YR_RULES from being discarded through GC rules *Rules } // Identifier returns the rule's name. func (r *Rule) Identifier() string { return C.GoString(C.rule_identifier(r.cptr)) } // Namespace returns the rule's namespace. func (r *Rule) Namespace() string { return C.GoString(C.rule_namespace(r.cptr)) } // Tags returns the rule's tags. func (r *Rule) Tags() (tags []string) { var size C.int C.rule_tags(r.cptr, nil, &size) if size == 0 { return } tagptrs := make([]*C.char, int(size)) C.rule_tags(r.cptr, &tagptrs[0], &size) for _, t := range tagptrs { tags = append(tags, C.GoString(t)) } return } // Meta represents a rule meta variable. Value can be of type string, // int, boolean, or nil. type Meta struct { Identifier string Value interface{} } // Metas returns the rule's meta variables as a list of Meta // objects. func (r *Rule) Metas() (metas []Meta) { var size C.int C.rule_metas(r.cptr, nil, &size) if size == 0 { return } mptrs := make([]*C.YR_META, int(size)) C.rule_metas(r.cptr, &mptrs[0], &size) for _, cptr := range mptrs { var cid, cstr *C.char C.meta_get(cptr, &cid, &cstr) id := C.GoString(cid) var val interface{} switch cptr._type { case C.META_TYPE_STRING: val = C.GoString(cstr) case C.META_TYPE_INTEGER: val = int(cptr.integer) case C.META_TYPE_BOOLEAN: val = (cptr.integer != 0) } metas = append(metas, Meta{id, val}) } return } // IsPrivate returns true if the rule is marked as private. func (r *Rule) IsPrivate() bool { return r.cptr.flags&C.RULE_FLAGS_PRIVATE != 0 } // IsGlobal returns true if the rule is marked as global. func (r *Rule) IsGlobal() bool { return r.cptr.flags&C.RULE_FLAGS_GLOBAL != 0 } // String represents a string as part of a rule. type String struct { cptr *C.YR_STRING // Save underlying YR_RULES from being discarded through GC rules *Rules } // Strings returns the rule's strings. func (r *Rule) Strings() (strs []String) { var size C.int C.rule_strings(r.cptr, nil, &size) if size == 0 { return } ptrs := make([]*C.YR_STRING, int(size)) C.rule_strings(r.cptr, &ptrs[0], &size) for _, ptr := range ptrs { strs = append(strs, String{ptr, r.rules}) } return } // Identifier returns the string's name. func (s *String) Identifier() string { return C.GoString(C.string_identifier(s.cptr)) } // Match represents a string match. type Match struct { cptr *C.YR_MATCH // Save underlying YR_RULES from being discarded through GC rules *Rules } // Matches returns all matches that have been recorded for the string. func (s *String) Matches(sc *ScanContext) (matches []Match) { if sc == nil || sc.cptr == nil { return } var size C.int C.string_matches(sc.cptr, s.cptr, nil, &size) ptrs := make([]*C.YR_MATCH, int(size)) if size == 0 { return } C.string_matches(sc.cptr, s.cptr, &ptrs[0], &size) for _, ptr := range ptrs { matches = append(matches, Match{ptr, s.rules}) } return } // Base returns the base offset of the memory block in which the // string match occurred. func (m *Match) Base() int64 { return int64(m.cptr.base) } // Offset returns the offset at which the string match occurred. func (m *Match) Offset() int64 { return int64(m.cptr.offset) } // Data returns the blob of data associated with the string match. func (m *Match) Data() []byte { return C.GoBytes(unsafe.Pointer(m.cptr.data), C.int(m.cptr.data_length)) } func (r *Rule) getMatchStrings(sc *ScanContext) (matchstrings []MatchString) { for _, s := range r.Strings() { for _, m := range s.Matches(sc) { matchstrings = append(matchstrings, MatchString{ Name: s.Identifier(), Base: uint64(m.Base()), Offset: uint64(m.Offset()), Data: m.Data(), }) } } return } // Enable enables a single rule. func (r *Rule) Enable() { C.yr_rule_enable(r.cptr) } // Disable disables a single rule. func (r *Rule) Disable() { C.yr_rule_disable(r.cptr) } // GetRules returns a slice of rule objects that are part of the // ruleset. func (r *Rules) GetRules() (rules []Rule) { var size C.int C.get_rules(r.cptr, nil, &size) if size == 0 { return } ptrs := make([]*C.YR_RULE, int(size)) C.get_rules(r.cptr, &ptrs[0], &size) for _, ptr := range ptrs { rules = append(rules, Rule{ptr, r}) } return } go-yara-4.1.0/rules.go000066400000000000000000000204141404631435500145420ustar00rootroot00000000000000// Copyright © 2015-2020 Hilko Bengen // All rights reserved. // // Use of this source code is governed by the license that can be // found in the LICENSE file. package yara /* #include #ifdef _WIN32 #include // 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)(intptr_t)fd, flags, callback, user_data, 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 scanCallbackFunc(YR_SCAN_CONTEXT*, int, void*, void*); */ import "C" import ( "errors" "io" "runtime" "time" "unsafe" ) // Rules contains a compiled YARA ruleset. // // Since this type contains a C pointer to a YR_RULES structure that // may be automatically freed, it should not be copied. type Rules struct{ cptr *C.YR_RULES } // A MatchRule represents a rule successfully matched against a block // of data. type MatchRule struct { Rule string Namespace string Tags []string Metas []Meta Strings []MatchString } // A MatchString represents a string declared and matched in a rule. type MatchString struct { Name string Base uint64 Offset uint64 Data []byte } // 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 ) func (sf ScanFlags) withReportFlags(sc ScanCallback) (i C.int) { i = C.int(sf) | C.SCAN_FLAGS_REPORT_RULES_MATCHING if _, ok := sc.(ScanCallbackNoMatch); ok { i |= C.SCAN_FLAGS_REPORT_RULES_NOT_MATCHING } return } // ScanMem scans an in-memory buffer using the ruleset. // For every event emitted by libyara, the corresponding method on the // ScanCallback object is called. func (r *Rules) ScanMem(buf []byte, flags ScanFlags, timeout time.Duration, cb ScanCallback) (err error) { var ptr *C.uint8_t if len(buf) > 0 { ptr = (*C.uint8_t)(unsafe.Pointer(&(buf[0]))) } id := callbackData.Put(makeScanCallbackContainer(cb, r)) defer callbackData.Delete(id) err = newError(C.yr_rules_scan_mem( r.cptr, ptr, C.size_t(len(buf)), flags.withReportFlags(cb), C.YR_CALLBACK_FUNC(C.scanCallbackFunc), id, C.int(timeout/time.Second))) runtime.KeepAlive(r) return } // ScanFile scans a file using the ruleset. For every // event emitted by libyara, the corresponding method on the // ScanCallback object is called. func (r *Rules) ScanFile(filename string, flags ScanFlags, timeout time.Duration, cb ScanCallback) (err error) { cfilename := C.CString(filename) defer C.free(unsafe.Pointer(cfilename)) id := callbackData.Put(makeScanCallbackContainer(cb, r)) defer callbackData.Delete(id) err = newError(C.yr_rules_scan_file( r.cptr, cfilename, flags.withReportFlags(cb), C.YR_CALLBACK_FUNC(C.scanCallbackFunc), id, C.int(timeout/time.Second))) runtime.KeepAlive(r) return } // ScanFileDescriptor scans a file using the ruleset. For every event // emitted by libyara, the corresponding method on the ScanCallback // object is called. func (r *Rules) ScanFileDescriptor(fd uintptr, flags ScanFlags, timeout time.Duration, cb ScanCallback) (err error) { id := callbackData.Put(makeScanCallbackContainer(cb, r)) defer callbackData.Delete(id) err = newError(C._yr_rules_scan_fd( r.cptr, C.int(fd), flags.withReportFlags(cb), C.YR_CALLBACK_FUNC(C.scanCallbackFunc), id, C.int(timeout/time.Second))) runtime.KeepAlive(r) return } // ScanProc scans a live process using the ruleset. For // every event emitted by libyara, the corresponding method on the // ScanCallback object is called. func (r *Rules) ScanProc(pid int, flags ScanFlags, timeout time.Duration, cb ScanCallback) (err error) { id := callbackData.Put(makeScanCallbackContainer(cb, r)) defer callbackData.Delete(id) err = newError(C.yr_rules_scan_proc( r.cptr, C.int(pid), flags.withReportFlags(cb), C.YR_CALLBACK_FUNC(C.scanCallbackFunc), id, C.int(timeout/time.Second))) runtime.KeepAlive(r) return } // ScanMemBlocks scans over a MemoryBlockIterator using the ruleset. // For every event emitted by libyara, the corresponding method on the // ScanCallback object is called. func (r *Rules) ScanMemBlocks(mbi MemoryBlockIterator, flags ScanFlags, timeout time.Duration, cb ScanCallback) (err error) { c := makeMemoryBlockIteratorContainer(mbi) defer c.free() cmbi := makeCMemoryBlockIterator(c) defer callbackData.Delete(cmbi.context) id := callbackData.Put(makeScanCallbackContainer(cb, r)) defer callbackData.Delete(id) err = newError(C.yr_rules_scan_mem_blocks( r.cptr, cmbi, flags.withReportFlags(cb), C.YR_CALLBACK_FUNC(C.scanCallbackFunc), id, C.int(timeout/time.Second))) runtime.KeepAlive(r) 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)) runtime.KeepAlive(r) return } // LoadRules retrieves a compiled ruleset from filename. func LoadRules(filename string) (*Rules, error) { cfilename := C.CString(filename) defer C.free(unsafe.Pointer(cfilename)) r := &Rules{} if err := newError(C.yr_rules_load(cfilename, &(r.cptr))); err != nil { return nil, err } runtime.SetFinalizer(r, (*Rules).Destroy) return r, nil } // 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{ write: C.YR_STREAM_WRITE_FUNC(C.streamWrite), // The complaint from go vet about possible misuse of // unsafe.Pointer is wrong: user_data will be interpreted as // an uintptr on the other side of the callback user_data: id, } err = newError(C.yr_rules_save_stream(r.cptr, &stream)) runtime.KeepAlive(r) return } // ReadRules retrieves a compiled ruleset from an io.Reader. func ReadRules(rd io.Reader) (*Rules, error) { id := callbackData.Put(rd) defer callbackData.Delete(id) stream := C.YR_STREAM{ read: C.YR_STREAM_READ_FUNC(C.streamRead), // The complaint from go vet about possible misuse of // unsafe.Pointer is wrong, see above. user_data: id, } r := &Rules{} if err := newError(C.yr_rules_load_stream(&stream, &(r.cptr))); err != nil { return nil, err } runtime.SetFinalizer(r, (*Rules).Destroy) return r, nil } // Destroy destroys the YARA data structure representing a ruleset. // // It should not be necessary to call this method directly. func (r *Rules) Destroy() { if r.cptr != nil { C.yr_rules_destroy(r.cptr) r.cptr = nil } runtime.SetFinalizer(r, nil) } // DefineVariable defines a named variable for use by the compiler. // Boolean, int64, float64, and string types are supported. func (r *Rules) DefineVariable(identifier string, value interface{}) (err error) { cid := C.CString(identifier) defer C.free(unsafe.Pointer(cid)) switch value.(type) { case bool: var v int if value.(bool) { v = 1 } err = newError(C.yr_rules_define_boolean_variable( r.cptr, cid, 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, cid, C.int64_t(value))) case float64: err = newError(C.yr_rules_define_float_variable( r.cptr, cid, 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, cid, cvalue)) default: err = errors.New("wrong value type passed to DefineVariable; bool, int64, float64, string are accepted") } runtime.KeepAlive(r) return } go-yara-4.1.0/rules_callback.go000066400000000000000000000121521404631435500163560ustar00rootroot00000000000000// Copyright © 2015-2020 Hilko Bengen // All rights reserved. // // Use of this source code is governed by the license that can be // found in the LICENSE file. package yara /* #include #include */ import "C" import ( "reflect" "runtime" "unsafe" ) // ScanContext contains the data passed to the ScanCallback methods. // // Since this type contains a C pointer to a YR_SCAN_CONTEXT structure // that may be automatically freed, it should not be copied. type ScanContext struct { cptr *C.YR_SCAN_CONTEXT } // ScanCallback is a placeholder for different interfaces that may be // implemented by the callback object that is passed to the // (*Rules).ScanXxxx and (*Scanner).ScanXxxx methods. // // The RuleMatching method corresponds to YARA's // CALLBACK_MSG_RULE_MATCHING message. type ScanCallback interface { RuleMatching(*ScanContext, *Rule) (bool, error) } // ScanCallbackNoMatch is used to record rules that did not match // during a scan. The RuleNotMatching method corresponds to YARA's // CALLBACK_MSG_RULE_NOT_MATCHING mssage. type ScanCallbackNoMatch interface { RuleNotMatching(*ScanContext, *Rule) (bool, error) } // ScanCallbackFinished is used to signal that a scan has finished. // The ScanFinished method corresponds to YARA's // CALLBACK_MSG_SCAN_FINISHED message. type ScanCallbackFinished interface { ScanFinished(*ScanContext) (bool, error) } // ScanCallbackModuleImport is used to provide data to a YARA module. // The ImportModule method corresponds to YARA's // CALLBACK_MSG_IMPORT_MODULE message. type ScanCallbackModuleImport interface { ImportModule(*ScanContext, string) ([]byte, bool, error) } // ScanCallbackModuleImportFinished can be used to free resources that // have been used in the ScanCallbackModuleImport implementation. The // ModuleImported method corresponds to YARA's // CALLBACK_MSG_MODULE_IMPORTED message. type ScanCallbackModuleImportFinished interface { ModuleImported(*ScanContext, *Object) (bool, error) } // scanCallbackContainer is used by to pass a ScanCallback (and // associated data) between ScanXxx methods and scanCallbackFunc(). It // stores the public callback interface and a list of malloc()'d C // pointers. type scanCallbackContainer struct { ScanCallback rules *Rules cdata []unsafe.Pointer } // makeScanCallbackContainer sets up a scanCallbackContainer with a // finalizer method that that frees any stored C pointers when the // container is garbage-collected. func makeScanCallbackContainer(sc ScanCallback, r *Rules) *scanCallbackContainer { c := &scanCallbackContainer{sc, r, nil} runtime.SetFinalizer(c, (*scanCallbackContainer).finalize) return c } // addCPointer adds a C pointer that can later be freed using free(). func (c *scanCallbackContainer) addCPointer(p unsafe.Pointer) { c.cdata = append(c.cdata, p) } // finalize frees stored C pointers func (c *scanCallbackContainer) finalize() { for _, p := range c.cdata { C.free(p) } c.cdata = nil runtime.SetFinalizer(c, nil) } //export scanCallbackFunc func scanCallbackFunc(ctx *C.YR_SCAN_CONTEXT, message C.int, messageData, userData unsafe.Pointer) C.int { cbc, ok := callbackData.Get(userData).(*scanCallbackContainer) s := &ScanContext{cptr: ctx} if !ok { return C.CALLBACK_ERROR } if cbc.ScanCallback == nil { return C.CALLBACK_CONTINUE } var abort bool var err error switch message { case C.CALLBACK_MSG_RULE_MATCHING: abort, err = cbc.ScanCallback.RuleMatching(s, &Rule{(*C.YR_RULE)(messageData), cbc.rules}) case C.CALLBACK_MSG_RULE_NOT_MATCHING: if c, ok := cbc.ScanCallback.(ScanCallbackNoMatch); ok { abort, err = c.RuleNotMatching(s, &Rule{(*C.YR_RULE)(messageData), cbc.rules}) } case C.CALLBACK_MSG_SCAN_FINISHED: if c, ok := cbc.ScanCallback.(ScanCallbackFinished); ok { abort, err = c.ScanFinished(s) } case C.CALLBACK_MSG_IMPORT_MODULE: if c, ok := cbc.ScanCallback.(ScanCallbackModuleImport); ok { mi := (*C.YR_MODULE_IMPORT)(messageData) var buf []byte if buf, abort, err = c.ImportModule(s, C.GoString(mi.module_name)); len(buf) == 0 { break } cbuf := C.calloc(1, C.size_t(len(buf))) outbuf := make([]byte, 0) hdr := (*reflect.SliceHeader)(unsafe.Pointer(&outbuf)) hdr.Data, hdr.Len = uintptr(cbuf), len(buf) copy(outbuf, buf) mi.module_data, mi.module_data_size = unsafe.Pointer(&outbuf[0]), C.size_t(len(outbuf)) cbc.addCPointer(cbuf) } case C.CALLBACK_MSG_MODULE_IMPORTED: if c, ok := cbc.ScanCallback.(ScanCallbackModuleImportFinished); ok { abort, err = c.ModuleImported(s, &Object{(*C.YR_OBJECT)(messageData)}) } } if err != nil { return C.CALLBACK_ERROR } if abort { return C.CALLBACK_ABORT } return C.CALLBACK_CONTINUE } // MatchRules is used to collect matches that are returned by the // simple (*Rules).Scan* methods. type MatchRules []MatchRule // RuleMatching implements the ScanCallbackMatch interface for // MatchRules. func (mr *MatchRules) RuleMatching(sc *ScanContext, r *Rule) (abort bool, err error) { *mr = append(*mr, MatchRule{ Rule: r.Identifier(), Namespace: r.Namespace(), Tags: r.Tags(), Metas: r.Metas(), Strings: r.getMatchStrings(sc), }) return } go-yara-4.1.0/rules_test.go000066400000000000000000000220021404631435500155740ustar00rootroot00000000000000// Copyright © 2015-2020 Hilko Bengen // All rights reserved. // // Use of this source code is governed by the license that can be // found in the LICENSE file. package yara import ( "bytes" "compress/bzip2" "fmt" "io/ioutil" "os" "os/exec" "reflect" "runtime" "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 }") var m MatchRules err := r.ScanMem([]byte(" abc "), 0, 0, &m) 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() var m MatchRules err := r.ScanFile(tf.Name(), 0, 0, &m) 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) var m MatchRules err := r.ScanFileDescriptor(tf.Fd(), 0, 0, &m) if err != nil { t.Errorf("ScanFileDescriptor(%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, nil) 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) var m MatchRules if err := r.ScanMem(data, 0, 0, &m); 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) var m MatchRules if err := r.ScanMem(data, 0, 0, &m); 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(compiledTestRulesPath) if r == nil || err != nil { t.Fatalf("LoadRules: %s", err) } } func TestReader(t *testing.T) { rd, err := os.Open(compiledTestRulesPath) if err != nil { t.Fatalf("os.Open: %s", err) } r, err := ReadRules(rd) if err != nil { t.Fatalf("ReadRules: %+v", err) } var m MatchRules if err := r.ScanMem([]byte(" abc "), 0, 0, &m); err != nil { t.Errorf("ScanMem: %s", err) } t.Logf("Matches: %+v", m) } func TestWriter(t *testing.T) { rd, err := os.Open(compiledTestRulesPath) 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") } } // in Go 1.8 this code does not work in go-yara 1.0.2 // go 1.8/debian stretch panics // go 1.8/darwin produces stack overflow func TestWriterBuffer(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) buf := new(bytes.Buffer) if err := r.Write(buf); err != nil { t.Fatalf("write to bytes.Buffer: %s", err) } } // 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, err := Compile(string(rulesBuf.Bytes()), nil) if err != nil { t.Fatalf("compile text for bzip2 rule compression: %s", err) } cmd := exec.Command("bzip2", "-c") compressStream, _ := cmd.StdinPipe() buf := bytes.NewBuffer(nil) cmd.Stdout = buf if err := cmd.Start(); err != nil { t.Fatalf("start bzip2 process: %s", err) } if err := r.Write(compressStream); err != nil { t.Fatalf("pipe to bzip2 process: %s", err) } compressStream.Close() if err := cmd.Wait(); err != nil { t.Fatalf("wait for bzip2 process: %s", err) } if _, err := ReadRules(bzip2.NewReader(bytes.NewReader(buf.Bytes()))); err != nil { t.Fatalf("read using compress/bzip2: %s", err) } } // 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, nil) return nil }(); err != nil { t.Errorf("ScanMem panicked: %s", err) } } type rule struct { identifier string tags []string metas []Meta private, global bool } func TestRule(t *testing.T) { rs := makeRules(t, ` rule t1 : tag1 { meta: author = "Author One" strings: $a = "abc" fullword condition: $a } rule t2 : tag2 x y { meta: author = "Author Two" strings: $b = "def" condition: $b } rule t3 : tag3 x y z { meta: author = "Author Three" strings: $c = "ghi" condition: $c } rule t4 { strings: $d = "qwe" condition: $d } private rule t5 { condition: false } global rule t6 { condition: false }`) expected := []rule{ rule{"t1", []string{"tag1"}, []Meta{{"author", "Author One"}}, false, false}, rule{"t2", []string{"tag2", "x", "y"}, []Meta{{"author", "Author Two"}}, false, false}, rule{"t3", []string{"tag3", "x", "y", "z"}, []Meta{{"author", "Author Three"}}, false, false}, rule{"t4", nil, nil, false, false}, rule{"t5", nil, nil, true, false}, rule{"t6", nil, nil, false, true}, } var got []rule for _, r := range rs.GetRules() { got = append(got, rule{identifier: r.Identifier(), metas: r.Metas(), tags: r.Tags(), private: r.IsPrivate(), global: r.IsGlobal()}) } if !reflect.DeepEqual(got, expected) { t.Errorf("Got %+v , expected %+v", got, expected) } } type testCallback struct { t *testing.T finished bool modules map[string]struct{} matched map[string]struct{} notMatched map[string]struct{} } func newTestCallback(t *testing.T) *testCallback { return &testCallback{ t, false, make(map[string]struct{}), make(map[string]struct{}), make(map[string]struct{}), } } func (c *testCallback) RuleMatching(_ *ScanContext, r *Rule) (bool, error) { c.t.Logf("RuleMatching callback called: rule=%s", r.Identifier()) c.matched[r.Identifier()] = struct{}{} return false, nil } func (c *testCallback) RuleNotMatching(_ *ScanContext, r *Rule) (bool, error) { c.t.Logf("RuleNotMatching callback called: rule=%s", r.Identifier()) c.notMatched[r.Identifier()] = struct{}{} return false, nil } func (c *testCallback) ScanFinished(*ScanContext) (bool, error) { c.t.Log("ScanFinished callback called") c.finished = true return false, nil } func (c *testCallback) ImportModule(_ *ScanContext, s string) ([]byte, bool, error) { c.t.Logf("ImportModule callback called: module=%s", s) c.modules[s] = struct{}{} if s == "tests" { return []byte("callback-data-for-tests-module"), false, nil } return nil, false, nil } func (c *testCallback) ModuleImported(*ScanContext, *Object) (bool, error) { c.t.Log("ModuleImported callback called") return false, nil } func TestImportDataCallback(t *testing.T) { cb := newTestCallback(t) r := makeRules(t, ` import "tests" import "pe" rule t1 { condition: true } rule t2 { condition: false } rule t3 { condition: tests.module_data == "callback-data-for-tests-module" }`) if err := r.ScanMem([]byte(""), 0, 0, cb); err != nil { t.Error(err) } for _, module := range []string{"tests", "pe"} { if _, ok := cb.modules[module]; !ok { t.Errorf("ImportModule was not called for %s", module) } } for _, rule := range []string{"t1", "t3"} { if _, ok := cb.matched["t1"]; !ok { t.Errorf("RuleMatching was not called for %s", rule) } } if _, ok := cb.notMatched["t2"]; !ok { t.Errorf("RuleNotMatching was not called for %s", "t2") } if !cb.finished { t.Errorf("ScanFinished was not called") } runtime.GC() } go-yara-4.1.0/scanner.go000066400000000000000000000176111404631435500150460ustar00rootroot00000000000000// Copyright © 2015-2020 Hilko Bengen // All rights reserved. // // Use of this source code is governed by the license that can be // found in the LICENSE file. package yara /* #include #ifdef _WIN32 #include int _yr_scanner_scan_fd( YR_SCANNER* scanner, int fd) { return yr_scanner_scan_fd(scanner, (YR_FILE_DESCRIPTOR)(intptr_t)fd); } #else #define _yr_scanner_scan_fd yr_scanner_scan_fd #endif int scanCallbackFunc(YR_SCAN_CONTEXT*, int, void*, void*); */ import "C" import ( "errors" "runtime" "time" "unsafe" ) // Scanner contains a YARA scanner (YR_SCANNER). The main difference // to Rules (YR_RULES) is that it is possible to set variables in a // thread-safe manner (cf. // https://github.com/VirusTotal/yara/issues/350). // // Since this type contains a C pointer to a YR_SCANNER structure that // may be automatically freed, it should not be copied. type Scanner struct { cptr *C.YR_SCANNER // The Scanner struct has to hold a pointer to the rules // it wraps, as otherwise it may be be garbage collected. rules *Rules // Current callback object, set by SetCallback Callback ScanCallback // Scan flags are set just before scanning. flags ScanFlags } // NewScanner creates a YARA scanner. func NewScanner(r *Rules) (*Scanner, error) { var yrScanner *C.YR_SCANNER if err := newError(C.yr_scanner_create(r.cptr, &yrScanner)); err != nil { return nil, err } s := &Scanner{cptr: yrScanner, rules: r} runtime.SetFinalizer(s, (*Scanner).Destroy) return s, nil } // Destroy destroys the YARA data structure representing a scanner. // // It should not be necessary to call this method directly. func (s *Scanner) Destroy() { if s.cptr != nil { C.yr_scanner_destroy(s.cptr) s.cptr = nil } runtime.SetFinalizer(s, nil) } // DefineVariable defines a named variable for use by the scanner. // Boolean, int64, float64, and string types are supported. func (s *Scanner) DefineVariable(identifier string, value interface{}) (err error) { cid := C.CString(identifier) defer C.free(unsafe.Pointer(cid)) switch value.(type) { case bool: var v int if value.(bool) { v = 1 } err = newError(C.yr_scanner_define_boolean_variable( s.cptr, cid, C.int(v))) case int, int8, int16, int32, int64, uint, uint8, uint32, uint64: value := toint64(value) err = newError(C.yr_scanner_define_integer_variable( s.cptr, cid, C.int64_t(value))) case float64: err = newError(C.yr_scanner_define_float_variable( s.cptr, cid, C.double(value.(float64)))) case string: cvalue := C.CString(value.(string)) defer C.free(unsafe.Pointer(cvalue)) err = newError(C.yr_scanner_define_string_variable( s.cptr, cid, cvalue)) default: err = errors.New("wrong value type passed to DefineVariable; bool, int64, float64, string are accepted") } runtime.KeepAlive(s) return } // SetFlags sets flags for the scanner. func (s *Scanner) SetFlags(flags ScanFlags) *Scanner { s.flags = flags return s } // SetTimeout sets a timeout for the scanner. func (s *Scanner) SetTimeout(timeout time.Duration) *Scanner { C.yr_scanner_set_timeout(s.cptr, C.int(timeout/time.Second)) return s } // SetCallback sets a callback object for the scanner. For every event // emitted by libyara during subsequent scan, the appropriate method // on the ScanCallback object is called. // // For the common case where only a list of matched rules is relevant, // setting a callback object is not necessary. func (s *Scanner) SetCallback(cb ScanCallback) *Scanner { s.Callback = cb return s } // putCallbackData stores the scanner's callback object in // callbackData, returning a pointer. If no callback object has been // set, it is initialized with the pointer to an empty ScanRules // object. The object must be removed from callbackData by the calling // ScanXxxx function. func (s *Scanner) putCallbackData() unsafe.Pointer { if _, ok := s.Callback.(ScanCallback); !ok { s.Callback = &MatchRules{} } ptr := callbackData.Put(makeScanCallbackContainer(s.Callback, s.rules)) C.yr_scanner_set_callback(s.cptr, C.YR_CALLBACK_FUNC(C.scanCallbackFunc), ptr) return ptr } // ScanMem scans an in-memory buffer using the scanner. // // If no callback object has been set for the scanner using // SetCAllback, it is initialized with an empty MatchRules object. func (s *Scanner) ScanMem(buf []byte) (err error) { var ptr *C.uint8_t if len(buf) > 0 { ptr = (*C.uint8_t)(unsafe.Pointer(&(buf[0]))) } cbPtr := s.putCallbackData() defer callbackData.Delete(cbPtr) C.yr_scanner_set_flags(s.cptr, s.flags.withReportFlags(s.Callback)) err = newError(C.yr_scanner_scan_mem( s.cptr, ptr, C.size_t(len(buf)))) runtime.KeepAlive(s) return } // ScanFile scans a file using the scanner. // // If no callback object has been set for the scanner using // SetCAllback, it is initialized with an empty MatchRules object. func (s *Scanner) ScanFile(filename string) (err error) { cfilename := C.CString(filename) defer C.free(unsafe.Pointer(cfilename)) cbPtr := s.putCallbackData() defer callbackData.Delete(cbPtr) C.yr_scanner_set_flags(s.cptr, s.flags.withReportFlags(s.Callback)) err = newError(C.yr_scanner_scan_file( s.cptr, cfilename, )) runtime.KeepAlive(s) return } // ScanFileDescriptor scans a file using the scanner. // // If no callback object has been set for the scanner using // SetCAllback, it is initialized with an empty MatchRules object. func (s *Scanner) ScanFileDescriptor(fd uintptr) (err error) { cbPtr := s.putCallbackData() defer callbackData.Delete(cbPtr) C.yr_scanner_set_flags(s.cptr, s.flags.withReportFlags(s.Callback)) err = newError(C._yr_scanner_scan_fd( s.cptr, C.int(fd), )) runtime.KeepAlive(s) return } // ScanProc scans a live process using the scanner. // // If no callback object has been set for the scanner using // SetCAllback, it is initialized with an empty MatchRules object. func (s *Scanner) ScanProc(pid int) (err error) { cbPtr := s.putCallbackData() defer callbackData.Delete(cbPtr) C.yr_scanner_set_flags(s.cptr, s.flags.withReportFlags(s.Callback)) err = newError(C.yr_scanner_scan_proc( s.cptr, C.int(pid), )) runtime.KeepAlive(s) return } // ScahMemBlocks scans over a MemoryBlockIterator using the scanner. // // If no callback object has been set for the scanner using // SetCAllback, it is initialized with an empty MatchRules object. func (s *Scanner) ScanMemBlocks(mbi MemoryBlockIterator) (err error) { c := makeMemoryBlockIteratorContainer(mbi) defer c.free() cmbi := makeCMemoryBlockIterator(c) defer callbackData.Delete(cmbi.context) cbPtr := s.putCallbackData() defer callbackData.Delete(cbPtr) C.yr_scanner_set_flags(s.cptr, s.flags.withReportFlags(s.Callback)) err = newError(C.yr_scanner_scan_mem_blocks( s.cptr, cmbi, )) runtime.KeepAlive(s) return } // GetLastErrorRule returns the Rule which caused the last error. // // The result is nil, if scanner returned no rule func (s *Scanner) GetLastErrorRule() (r *Rule) { ptr := C.yr_scanner_last_error_rule(s.cptr) if ptr != nil { r = &Rule{ptr, s.rules} } runtime.KeepAlive(s) return } // GetLastErrorString returns the String which caused the last error. // // The result is nil, if scanner returned no string func (s *Scanner) GetLastErrorString() (r *String) { ptr := C.yr_scanner_last_error_string(s.cptr) if ptr != nil { r = &String{ptr, s.rules} } runtime.KeepAlive(s) return } type RuleProfilingInfo struct { Rule Cost uint64 } // GetProfilingInfo retrieves profiling information from the Scanner. func (s *Scanner) GetProfilingInfo() (rpis []RuleProfilingInfo) { for rpi := C.yr_scanner_get_profiling_info(s.cptr); rpi.rule != nil; rpi = (*C.YR_RULE_PROFILING_INFO)(unsafe.Pointer(uintptr(unsafe.Pointer(rpi)) + unsafe.Sizeof(*rpi))) { rpis = append(rpis, RuleProfilingInfo{Rule{rpi.rule, s.rules}, uint64(rpi.cost)}) } return } // ResetProfilingInfo resets the Scanner's profiling information func (s *Scanner) ResetProfilingInfo() { C.yr_scanner_reset_profiling_info(s.cptr) } go-yara-4.1.0/scanner_test.go000066400000000000000000000122451404631435500161030ustar00rootroot00000000000000// Copyright © 2015-2020 Hilko Bengen // All rights reserved. // // Use of this source code is governed by the license that can be // found in the LICENSE file. package yara import ( "errors" "io/ioutil" "os" "runtime" "testing" ) func makeScanner(t *testing.T, rule string) *Scanner { 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) } s, err := NewScanner(r) if err != nil { t.Fatal("NewScanner:", err) } return s } func TestScannerSimpleMatch(t *testing.T) { s := makeScanner(t, "rule test : tag1 { meta: author = \"Matt Blewitt\" strings: $a = \"abc\" fullword condition: $a }") var m MatchRules if err := s.SetCallback(&m).ScanMem([]byte(" abc ")); err != nil { t.Errorf("ScanMem: %s", err) } else if len(m) != 1 { t.Errorf("ScanMem: wanted 1 match, got %d", len(m)) } t.Logf("Matches: %+v", m) } func TestScannerSimpleFileMatch(t *testing.T) { s := makeScanner(t, "rule test : tag1 { meta: author = \"Matt Blewitt\" strings: $a = \"abc\" fullword condition: $a }") tf, _ := ioutil.TempFile("", "TestScannerSimpleFileMatch") defer os.Remove(tf.Name()) tf.Write([]byte(" abc ")) tf.Close() var m MatchRules if err := s.SetCallback(&m).ScanFile(tf.Name()); err != nil { t.Errorf("ScanFile(%s): %s", tf.Name(), err) } else if len(m) != 1 { t.Errorf("ScanFile: wanted 1 match, got %d", len(m)) } t.Logf("Matches: %+v", m) } func TestScannerSimpleFileDescriptorMatch(t *testing.T) { s := makeScanner(t, "rule test : tag1 { meta: author = \"Matt Blewitt\" strings: $a = \"abc\" fullword condition: $a }") tf, _ := ioutil.TempFile("", "TestScannerSimpleFileDescriptorMatch") defer os.Remove(tf.Name()) tf.Write([]byte(" abc ")) tf.Seek(0, os.SEEK_SET) var m MatchRules if err := s.SetCallback(&m).ScanFileDescriptor(tf.Fd()); err != nil { t.Errorf("ScanFileDescriptor(%v): %s", tf.Fd(), err) } else if len(m) != 1 { t.Errorf("ScanFileDescriptor: wanted 1 match, got %d", len(m)) } t.Logf("Matches: %+v", m) } func TestScannerEmptyCallback(t *testing.T) { s := makeScanner(t, "rule test : tag1 { meta: author = \"Matt Blewitt\" strings: $a = \"abc\" fullword condition: $a }") if err := s.ScanMem([]byte(" abc ")); err != nil { t.Errorf("ScanMem: %s", err) } if m, ok := s.Callback.(*MatchRules); !ok { t.Error("no *MatchRules set") } else if len(*m) != 1 { t.Errorf("length of MatchRules: %d (expected 1)", len(*m)) } } // TestScannerIndependence tests that two scanners can // execute with different external variables and the same ruleset func TestScannerIndependence(t *testing.T) { rulesStr := ` rule test { condition: bool_var and int_var == 1 and str_var == "foo" } ` c, err := NewCompiler() if c == nil || err != nil { t.Fatal("NewCompiler():", err) } c.DefineVariable("bool_var", false) c.DefineVariable("int_var", 0) c.DefineVariable("str_var", "") if err = c.AddString(rulesStr, ""); err != nil { t.Fatal("AddString():", err) } r, err := c.GetRules() if err != nil { t.Fatal("GetRules:", err) } s1, err := NewScanner(r) if err != nil { t.Fatal("NewScanner:", err) } s2, err := NewScanner(r) if err != nil { t.Fatal("NewScanner:", err) } s1.DefineVariable("bool_var", true) s1.DefineVariable("int_var", 1) s1.DefineVariable("str_var", "foo") s2.DefineVariable("bool_var", false) s2.DefineVariable("int_var", 2) s2.DefineVariable("str_var", "bar") var m1, m2 MatchRules if err := s1.SetCallback(&m1).ScanMem([]byte("")); err != nil { t.Fatal(err) } if err := s2.SetCallback(&m2).ScanMem([]byte("")); err != nil { t.Fatal(err) } if !(len(m1) > 0) { t.Errorf("wanted >0 matches, got %d", len(m1)) } if len(m2) != 0 { t.Errorf("wanted 0 matches, got %d", len(m2)) } t.Logf("Matches 1: %+v", m1) t.Logf("Matches 2: %+v", m2) } func TestScannerImportDataCallback(t *testing.T) { cb := newTestCallback(t) s := makeScanner(t, ` import "tests" import "pe" rule t1 { condition: true } rule t2 { condition: false } rule t3 { condition: tests.module_data == "callback-data-for-tests-module" }`) if err := s.SetCallback(cb).ScanMem([]byte("")); err != nil { t.Error(err) } for _, module := range []string{"tests", "pe"} { if _, ok := cb.modules[module]; !ok { t.Errorf("ImportModule was not called for %s", module) } } for _, rule := range []string{"t1", "t3"} { if _, ok := cb.matched["t1"]; !ok { t.Errorf("RuleMatching was not called for %s", rule) } } if _, ok := cb.notMatched["t2"]; !ok { t.Errorf("RuleNotMatching was not called for %s", "t2") } if !cb.finished { t.Errorf("ScanFinished was not called") } runtime.GC() } type failingScanCallback struct{} func (*failingScanCallback) RuleMatching(*ScanContext, *Rule) (bool, error) { return true, errors.New("go away") } func TestScannerError(t *testing.T) { s := makeScanner(t, ` rule test { condition: true } `) var err error if err = s.SetCallback(&failingScanCallback{}).ScanMem([]byte{0, 0, 0, 0}); err == nil { t.Fatal("ScanMem: did not fail") } t.Logf("ScanMem: got expected error, %s", err) } go-yara-4.1.0/stream.go000066400000000000000000000031121404631435500146770ustar00rootroot00000000000000// Copyright © 2015-2020 Hilko Bengen // 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 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(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 } // writeFull does its best to write all of buf to w. See io.ReadFull. 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(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 } go-yara-4.1.0/stress_test.go000066400000000000000000000071101404631435500157700ustar00rootroot00000000000000// Copyright © 2015-2020 Hilko Bengen // All rights reserved. // // Use of this source code is governed by the license that can be // found in the LICENSE file. 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 } // r.DefineVariable("filename", info.Name()) // r.DefineVariable("filepath", name) var m MatchRules if err := r.ScanFile(name, 0, 0, &m); 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() } func TestScannerFileWalk(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 } `, map[string]interface{}{ "filename": "", "filepath": "", }) if err != nil { t.Fatal(err) } wg := sync.WaitGroup{} for i := 0; i < 32; i++ { wg.Add(1) go func(i int) { s, err := NewScanner(r) if err != nil { t.Fatal(err) } 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 } s.DefineVariable("filepath", name) s.DefineVariable("filename", info.Name()) var m MatchRules if err := s.SetCallback(&m).ScanFile(name); 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() } go-yara-4.1.0/testdata/000077500000000000000000000000001404631435500146715ustar00rootroot00000000000000go-yara-4.1.0/testdata/rules.yar000066400000000000000000000001361404631435500165400ustar00rootroot00000000000000rule test : tag1 { meta: author = "Hilko Bengen" strings: $a = "abc" fullword condition: $a } go-yara-4.1.0/util.go000066400000000000000000000014021404631435500143610ustar00rootroot00000000000000// Copyright © 2015-2020 Hilko Bengen // All rights reserved. // // Use of this source code is governed by the license that can be // found in the LICENSE file. package yara var callbackData = makecbPool(256) 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") }