├── .gitignore ├── LICENSE ├── build.go ├── cgo.c ├── cgo.h ├── exported.go ├── rules ├── dummy.yar ├── metadata.yar ├── precompiled ├── recursion.yar └── tags.yar ├── yara.go └── yara_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | vendor/yara-3.3.0/compile 3 | vendor/yara-3.3.0/libyara/modules/.dirstamp 4 | vendor/yara-3.3.0/libyara/yara.pc 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (C) 2015 Thomas Kastner 4 | Copyright (c) 2015 Cumulo Information System Security GmbH 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /build.go: -------------------------------------------------------------------------------- 1 | package yara 2 | 3 | // #cgo pkg-config: yara 4 | import "C" 5 | -------------------------------------------------------------------------------- /cgo.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "cgo.h" 5 | #include "_cgo_export.h" 6 | 7 | int callback(int msg, void* msg_data, void* user_data) { 8 | if (msg != CALLBACK_MSG_RULE_MATCHING) { 9 | return CALLBACK_CONTINUE; 10 | } 11 | 12 | return goCallback(user_data, (YR_RULE *) msg_data); 13 | } 14 | 15 | void translate_rule(void* go, YR_RULE* c) { 16 | goRuleSetIdentifier(go, (char *)c->identifier); 17 | 18 | const char* tag; 19 | yr_rule_tags_foreach(c, tag) { 20 | goRuleAddTag(go, (char *)tag); 21 | } 22 | 23 | YR_META* meta; 24 | yr_rule_metas_foreach(c, meta) { 25 | switch (meta->type) { 26 | case META_TYPE_NULL: 27 | // ignore NULL type metadata 28 | break; 29 | 30 | case META_TYPE_INTEGER: 31 | goMetadataAddNumber(go, (char *)meta->identifier, meta->integer); 32 | break; 33 | 34 | case META_TYPE_BOOLEAN: 35 | goMetadataAddBool(go, (char *)meta->identifier, meta->integer); 36 | break; 37 | 38 | case META_TYPE_STRING: 39 | goMetadataAddString(go, (char *)meta->identifier, (char *)meta->string); 40 | break; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /cgo.h: -------------------------------------------------------------------------------- 1 | #ifndef _CGO_YARA 2 | #define _CGO_YARA 3 | int callback(int msg, void* msg_data, void* user_data); 4 | void translate_rule(void* go, YR_RULE* c); 5 | 6 | size_t goStreamRead(void *ptr, size_t size, size_t count, void *prw); 7 | size_t goStreamWrite(void *ptr, size_t size, size_t count, void *prw); 8 | #endif 9 | -------------------------------------------------------------------------------- /exported.go: -------------------------------------------------------------------------------- 1 | package yara 2 | 3 | /* 4 | #include 5 | #include 6 | #include 7 | #include "cgo.h" 8 | */ 9 | import "C" 10 | 11 | import ( 12 | "io" 13 | "unsafe" 14 | ) 15 | 16 | //export goStreamRead 17 | func goStreamRead(ptr unsafe.Pointer, size C.size_t, count C.size_t, prw unsafe.Pointer) C.size_t { 18 | r := *(*io.Reader)(prw) 19 | 20 | buffer := make([]byte, int(size)) 21 | n, err := io.ReadAtLeast(r, buffer, int(size)) 22 | if err != nil { 23 | return 0 24 | } 25 | 26 | C.memcpy(ptr, (unsafe.Pointer)(&buffer[0]), C.size_t(n)) 27 | return 1 28 | } 29 | 30 | //export goStreamWrite 31 | func goStreamWrite(ptr unsafe.Pointer, size C.size_t, count C.size_t, prw unsafe.Pointer) C.size_t { 32 | w := *(*io.Writer)(prw) 33 | 34 | buffer := make([]byte, int(size)) 35 | C.memcpy((unsafe.Pointer)(&buffer[0]), ptr, size) 36 | 37 | _, err := w.Write(buffer) 38 | if err != nil { 39 | return 0 40 | } 41 | 42 | return 1 43 | } 44 | 45 | //export goCallback 46 | func goCallback(p unsafe.Pointer, data *C.YR_RULE) C.int { 47 | rule := NewRule() 48 | C.translate_rule(unsafe.Pointer(rule), data) 49 | 50 | f := (*(*Callback)(unsafe.Pointer(&p))) 51 | return C.int(f(rule)) 52 | } 53 | 54 | //export goRuleSetIdentifier 55 | func goRuleSetIdentifier(ptr unsafe.Pointer, identifier *C.char) { 56 | rule := (*Rule)(ptr) 57 | rule.Identifier = C.GoString(identifier) 58 | } 59 | 60 | //export goRuleAddTag 61 | func goRuleAddTag(ptr unsafe.Pointer, tag *C.char) { 62 | rule := (*Rule)(ptr) 63 | rule.Tags = append(rule.Tags, C.GoString(tag)) 64 | } 65 | 66 | //export goMetadataAddString 67 | func goMetadataAddString(ptr unsafe.Pointer, pkey *C.char, pvalue *C.char) { 68 | rule := (*Rule)(ptr) 69 | key := C.GoString(pkey) 70 | value := C.GoString(pvalue) 71 | rule.Metadata[key] = value 72 | } 73 | 74 | //export goMetadataAddNumber 75 | func goMetadataAddNumber(ptr unsafe.Pointer, pkey *C.char, value C.int) { 76 | rule := (*Rule)(ptr) 77 | key := C.GoString(pkey) 78 | rule.Metadata[key] = int(value) 79 | } 80 | 81 | //export goMetadataAddBool 82 | func goMetadataAddBool(ptr unsafe.Pointer, pkey *C.char, value C.int) { 83 | rule := (*Rule)(ptr) 84 | key := C.GoString(pkey) 85 | rule.Metadata[key] = (int(value) != 0) 86 | } 87 | -------------------------------------------------------------------------------- /rules/dummy.yar: -------------------------------------------------------------------------------- 1 | rule dummy 2 | { 3 | condition: 4 | false 5 | } 6 | -------------------------------------------------------------------------------- /rules/metadata.yar: -------------------------------------------------------------------------------- 1 | rule metadata { 2 | meta: 3 | string = "abcdef" 4 | t = true 5 | f = false 6 | one = 1 7 | two = 2 8 | 9 | strings: 10 | $ = "metadata" 11 | 12 | condition: 13 | any of them 14 | } 15 | -------------------------------------------------------------------------------- /rules/precompiled: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cumulodev/yara/64e35bd4c6d0117527abfb5414e16ed9f8d73d15/rules/precompiled -------------------------------------------------------------------------------- /rules/recursion.yar: -------------------------------------------------------------------------------- 1 | rule First 2 | { 3 | strings: 4 | $ = "rule" 5 | 6 | condition: 7 | 1 of them 8 | } 9 | 10 | rule Second 11 | { 12 | strings: 13 | $ = "condition" 14 | 15 | condition: 16 | 1 of them 17 | } 18 | -------------------------------------------------------------------------------- /rules/tags.yar: -------------------------------------------------------------------------------- 1 | rule tags : lol rofl 2 | { 3 | strings: 4 | $ = "tags" 5 | 6 | condition: 7 | any of them 8 | } 9 | -------------------------------------------------------------------------------- /yara.go: -------------------------------------------------------------------------------- 1 | package yara 2 | 3 | // #include 4 | // #include 5 | // #include "cgo.h" 6 | import "C" 7 | 8 | import ( 9 | "fmt" 10 | "io" 11 | "log" 12 | "unsafe" 13 | ) 14 | 15 | const RequiredVersion = "3.4.0" 16 | 17 | var callback = (C.YR_CALLBACK_FUNC)(unsafe.Pointer(C.callback)) 18 | 19 | // Callback is a callback that gets called during a scan with a matching rule. 20 | type Callback func(*Rule) CallbackStatus 21 | 22 | // CallbackStatus is flag to indicate to libyara if it should continue or abort a scanning 23 | // process. 24 | type CallbackStatus int 25 | 26 | const ( 27 | // Coninue tells libyara to continue the scanning. 28 | Continue = CallbackStatus(C.CALLBACK_CONTINUE) 29 | 30 | // Abort tells libyara to abort the scanning. 31 | Abort = CallbackStatus(C.CALLBACK_ABORT) 32 | 33 | // Fail tells libyara that an error occured and it should abort the scanning. 34 | Fail = CallbackStatus(C.CALLBACK_ERROR) 35 | ) 36 | 37 | func init() { 38 | code := C.yr_initialize() 39 | if code != C.ERROR_SUCCESS { 40 | log.Fatalf("failed to initialize libyara!") 41 | } 42 | } 43 | 44 | func Finalize() error { 45 | code := C.yr_finalize() 46 | if code != C.ERROR_SUCCESS { 47 | return Error(code) 48 | } 49 | 50 | return nil 51 | } 52 | 53 | type Compiler struct { 54 | handle *C.YR_COMPILER 55 | } 56 | 57 | func NewCompiler() (*Compiler, error) { 58 | var handle *C.YR_COMPILER 59 | code := C.yr_compiler_create(&handle) 60 | if code != C.ERROR_SUCCESS { 61 | return nil, Error(code) 62 | } 63 | 64 | return &Compiler{handle}, nil 65 | } 66 | 67 | func (c *Compiler) Destroy() { 68 | C.yr_compiler_destroy(c.handle) 69 | } 70 | 71 | func (c *Compiler) AddFile(ns, path string) error { 72 | cpath := C.CString(path) 73 | cmode := C.CString("r") 74 | cns := C.CString(ns) 75 | 76 | defer C.free(unsafe.Pointer(cpath)) 77 | defer C.free(unsafe.Pointer(cmode)) 78 | defer C.free(unsafe.Pointer(cns)) 79 | 80 | fd := C.fopen(cpath, cmode) 81 | if fd == nil { 82 | return fmt.Errorf("libyara: failed to open %q", path) 83 | } 84 | 85 | defer C.fclose(fd) 86 | 87 | errors := C.yr_compiler_add_file(c.handle, fd, nil, cpath) 88 | if errors > 0 { 89 | return fmt.Errorf("libyara: failed to compile %q", path) 90 | } 91 | 92 | return nil 93 | } 94 | 95 | func (c *Compiler) AddString(ns, rule string) error { 96 | cns := C.CString(ns) 97 | crule := C.CString(rule) 98 | errors := C.yr_compiler_add_string(c.handle, crule, cns) 99 | C.free(unsafe.Pointer(crule)) 100 | C.free(unsafe.Pointer(cns)) 101 | 102 | if errors > 0 { 103 | return fmt.Errorf("libyara: failed to compile rule") 104 | } 105 | 106 | return nil 107 | } 108 | 109 | func (c *Compiler) Rules() (*Rules, error) { 110 | var handle *C.YR_RULES 111 | code := C.yr_compiler_get_rules(c.handle, &handle) 112 | if code != C.ERROR_SUCCESS { 113 | return nil, Error(code) 114 | } 115 | 116 | return &Rules{ 117 | handle: handle, 118 | }, nil 119 | } 120 | 121 | type Rules struct { 122 | handle *C.YR_RULES 123 | } 124 | 125 | func Load(r io.Reader) (*Rules, error) { 126 | var handle *C.YR_RULES 127 | code := C.yr_rules_load_stream(readStream(r), &handle) 128 | if code != C.ERROR_SUCCESS { 129 | return nil, Error(code) 130 | } 131 | 132 | return &Rules{handle}, nil 133 | } 134 | 135 | func LoadFromFile(path string) (*Rules, error) { 136 | var handle *C.YR_RULES 137 | 138 | cpath := C.CString(path) 139 | code := C.yr_rules_load(cpath, &handle) 140 | C.free(unsafe.Pointer(cpath)) 141 | 142 | if code != C.ERROR_SUCCESS { 143 | return nil, Error(code) 144 | } 145 | 146 | return &Rules{ 147 | handle: handle, 148 | }, nil 149 | } 150 | 151 | func (r *Rules) Destroy() { 152 | C.yr_rules_destroy(r.handle) 153 | } 154 | 155 | func (r *Rules) Write(w io.Writer) error { 156 | code := C.yr_rules_save_stream(r.handle, writeStream(w)) 157 | if code != C.ERROR_SUCCESS { 158 | return Error(code) 159 | } 160 | 161 | return nil 162 | } 163 | 164 | func (r *Rules) Save(path string) error { 165 | cpath := C.CString(path) 166 | code := C.yr_rules_save(r.handle, cpath) 167 | C.free(unsafe.Pointer(cpath)) 168 | 169 | if code != C.ERROR_SUCCESS { 170 | return Error(code) 171 | } 172 | 173 | return nil 174 | } 175 | 176 | func (r *Rules) ScanMemory(buffer []byte, fn Callback) error { 177 | data := (*C.uint8_t)(unsafe.Pointer(&buffer[0])) 178 | size := C.size_t(len(buffer)) 179 | 180 | code := C.yr_rules_scan_mem(r.handle, data, size, 0, callback, *(*unsafe.Pointer)(unsafe.Pointer(&fn)), 0) 181 | 182 | if code != C.ERROR_SUCCESS { 183 | return Error(code) 184 | } 185 | 186 | return nil 187 | } 188 | 189 | func (r *Rules) ScanFile(path string, fn Callback) error { 190 | cpath := C.CString(path) 191 | code := C.yr_rules_scan_file(r.handle, cpath, 0, callback, *(*unsafe.Pointer)(unsafe.Pointer(&fn)), 0) 192 | C.free(unsafe.Pointer(cpath)) 193 | 194 | if code != C.ERROR_SUCCESS { 195 | return Error(code) 196 | } 197 | 198 | return nil 199 | } 200 | 201 | type Rule struct { 202 | Identifier string 203 | Tags []string 204 | Metadata map[string]interface{} 205 | } 206 | 207 | func NewRule() *Rule { 208 | return &Rule{ 209 | Metadata: make(map[string]interface{}), 210 | } 211 | } 212 | 213 | var errorcodes = map[Error]string{ 214 | C.ERROR_COULD_NOT_OPEN_FILE: "File could not be opened", 215 | C.ERROR_COULD_NOT_MAP_FILE: "File could not be mapped into memory", 216 | C.ERROR_INVALID_FILE: "File is not a valid rules file", 217 | C.ERROR_CORRUPT_FILE: "Rules file is corrupt", 218 | C.ERROR_UNSUPPORTED_FILE_VERSION: "File was generated by a different YARA and can’t be loaded by this version", 219 | C.ERROR_TOO_MANY_SCAN_THREADS: "Too many threads trying to use the same YR_RULES object simultaneosly", 220 | C.ERROR_SCAN_TIMEOUT: "Scan timed out", 221 | C.ERROR_CALLBACK_ERROR: "Callback returned an error", 222 | C.ERROR_TOO_MANY_MATCHES: "Too many matches for some string in your rules", 223 | } 224 | 225 | // Error is an libyara error. 226 | type Error int 227 | 228 | // Error returns an human readable repsentation of the libyara error. 229 | func (e Error) Error() string { 230 | if msg, ok := errorcodes[e]; ok { 231 | return fmt.Sprintf("libyara: %s (%d)", msg, e) 232 | } else { 233 | return fmt.Sprintf("libyara: Unknown error code (%d)", e) 234 | } 235 | } 236 | 237 | func readStream(r io.Reader) *C.YR_STREAM { 238 | stream := new(C.YR_STREAM) 239 | stream.user_data = unsafe.Pointer(&r) 240 | stream.read = (C.YR_STREAM_READ_FUNC)(C.goStreamRead) 241 | return stream 242 | } 243 | 244 | func writeStream(w io.Writer) *C.YR_STREAM { 245 | stream := new(C.YR_STREAM) 246 | stream.user_data = unsafe.Pointer(&w) 247 | stream.write = (C.YR_STREAM_WRITE_FUNC)(C.goStreamWrite) 248 | return stream 249 | } 250 | -------------------------------------------------------------------------------- /yara_test.go: -------------------------------------------------------------------------------- 1 | package yara 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestNewCompiler(t *testing.T) { 9 | c, err := NewCompiler() 10 | assertEq(t, nil, err) 11 | 12 | c.Destroy() 13 | } 14 | 15 | func TestCompilerAddFile(t *testing.T) { 16 | c, err := NewCompiler() 17 | assertEq(t, nil, err) 18 | 19 | err = c.AddFile("", "rules/dummy.yar") 20 | assertEq(t, nil, err) 21 | 22 | c.Destroy() 23 | } 24 | 25 | func TestScanMem(t *testing.T) { 26 | c, err := NewCompiler() 27 | assertEq(t, nil, err) 28 | 29 | err = c.AddFile("", "rules/recursion.yar") 30 | assertEq(t, nil, err) 31 | 32 | engine, err := c.Rules() 33 | assertEq(t, nil, err) 34 | 35 | c.Destroy() 36 | 37 | name := "" 38 | err = engine.ScanMemory([]byte("rule and condition"), func(rule *Rule) CallbackStatus { 39 | name = rule.Identifier 40 | return Abort 41 | }) 42 | 43 | assertEq(t, "First", name) 44 | assertEq(t, nil, err) 45 | } 46 | 47 | func TestScanFile(t *testing.T) { 48 | c, err := NewCompiler() 49 | assertEq(t, nil, err) 50 | 51 | err = c.AddFile("", "rules/recursion.yar") 52 | assertEq(t, nil, err) 53 | 54 | engine, err := c.Rules() 55 | assertEq(t, nil, err) 56 | 57 | c.Destroy() 58 | 59 | name := "" 60 | err = engine.ScanFile("rules/recursion.yar", func(rule *Rule) CallbackStatus { 61 | name = rule.Identifier 62 | return Abort 63 | }) 64 | 65 | assertEq(t, "First", name) 66 | assertEq(t, nil, err) 67 | } 68 | 69 | func TestScanContinue(t *testing.T) { 70 | c, err := NewCompiler() 71 | assertEq(t, nil, err) 72 | 73 | err = c.AddFile("", "rules/recursion.yar") 74 | assertEq(t, nil, err) 75 | 76 | engine, err := c.Rules() 77 | assertEq(t, nil, err) 78 | 79 | c.Destroy() 80 | 81 | name := "" 82 | err = engine.ScanFile("rules/recursion.yar", func(rule *Rule) CallbackStatus { 83 | name = rule.Identifier 84 | return Continue 85 | }) 86 | 87 | assertEq(t, "Second", name) 88 | assertEq(t, nil, err) 89 | } 90 | 91 | func TestLoadRule(t *testing.T) { 92 | _, err := LoadFromFile("rules/precompiled") 93 | assertEq(t, nil, err) 94 | } 95 | 96 | func TestSaveRule(t *testing.T) { 97 | engine, err := LoadFromFile("rules/precompiled") 98 | assertEq(t, nil, err) 99 | 100 | err = engine.Save("rules/precompiled") 101 | assertEq(t, nil, err) 102 | } 103 | 104 | func assertEq(t *testing.T, expected interface{}, actual interface{}) { 105 | if !reflect.DeepEqual(expected, actual) { 106 | t.Fatalf("Assertion error.\n\tExpected: %v\n\tActual: %v", expected, actual) 107 | } 108 | } 109 | 110 | func TestMetadata(t *testing.T) { 111 | c, err := NewCompiler() 112 | assertEq(t, nil, err) 113 | 114 | err = c.AddFile("", "rules/metadata.yar") 115 | assertEq(t, nil, err) 116 | 117 | engine, err := c.Rules() 118 | assertEq(t, nil, err) 119 | 120 | c.Destroy() 121 | err = engine.ScanFile("rules/metadata.yar", func(rule *Rule) CallbackStatus { 122 | assertEq(t, "abcdef", rule.Metadata["string"]) 123 | assertEq(t, true, rule.Metadata["t"]) 124 | assertEq(t, false, rule.Metadata["f"]) 125 | assertEq(t, 1, rule.Metadata["one"]) 126 | assertEq(t, 2, rule.Metadata["two"]) 127 | return Abort 128 | }) 129 | 130 | assertEq(t, nil, err) 131 | } 132 | 133 | func TestTags(t *testing.T) { 134 | c, err := NewCompiler() 135 | assertEq(t, nil, err) 136 | 137 | err = c.AddFile("", "rules/tags.yar") 138 | assertEq(t, nil, err) 139 | 140 | engine, err := c.Rules() 141 | assertEq(t, nil, err) 142 | 143 | c.Destroy() 144 | err = engine.ScanFile("rules/tags.yar", func(rule *Rule) CallbackStatus { 145 | assertEq(t, []string{"lol", "rofl"}, rule.Tags) 146 | return Abort 147 | }) 148 | 149 | assertEq(t, nil, err) 150 | } 151 | 152 | func TestStackGrowth(t *testing.T) { 153 | c, err := NewCompiler() 154 | assertEq(t, nil, err) 155 | 156 | err = c.AddFile("", "rules/recursion.yar") 157 | assertEq(t, nil, err) 158 | 159 | engine, err := c.Rules() 160 | assertEq(t, nil, err) 161 | 162 | c.Destroy() 163 | 164 | n := 50 165 | ch := make(chan int) 166 | 167 | for i := 0; i < n; i++ { 168 | go func() { 169 | name := "foo" 170 | 171 | // use some stack memory to trigger split stack check 172 | var buf [4096]byte 173 | use(buf[:]) 174 | 175 | err := engine.ScanFile("rules/recursion.yar", func(rule *Rule) CallbackStatus { 176 | name = rule.Identifier 177 | return Abort 178 | }) 179 | 180 | assertEq(t, "First", name) 181 | assertEq(t, nil, err) 182 | ch <- 1 183 | }() 184 | } 185 | 186 | for i := 0; i < n; i++ { 187 | <-ch 188 | } 189 | } 190 | 191 | var Used byte 192 | 193 | func use(buf []byte) { 194 | for _, c := range buf { 195 | Used += c 196 | } 197 | } 198 | --------------------------------------------------------------------------------