osquery-1/osquery/tables/other/yara_utils.cpp
Mike Arpaia a140333441 [fix #1390] query pack re-org
This commit contains the features specified in #1390 as well as a
refactoring of the general osquery configuration code.

The API for the config plugins hasn't changed, although now there's a
`genPack` method that config plugins can implement. If a plugin doesn't
implement `genPack`, then the map<string, string> format cannot be used.
The default config plugin, the filesystem plugin, now implements
`genPack`, so existing query packs code will continue to work as it
always has.

Now many other config plugins can implement custom pack handling for
what makes sense in their context. `genPacks` is not a pure virtual, so
it doesn't have to be implemented in your plugin if you don't want to
use it. Also, more importantly, all config plugins can use the standard
inline pack format if they want to use query packs. Which is awesome.

For more information, refer to #1390, the documentation and the doxygen
comments included with this pull requests, as well as the following
example config which is now supported, regardless of what config plugin
you're using:

```json
{
  "options": {
    "enable_monitor": "true"
  },
  "packs": {
    "core_os_monitoring": {
        "version": "1.4.5",
        "discovery": [
          "select pid from processes where name like '%osqueryd%';"
        ],
        "queries": {
          "kernel_modules": {
              "query": "SELECT name, size FROM kernel_modules;",
              "interval": 600
          },
          "system_controls": {
              "query": "SELECT * FROM system_controls;",
              "interval": 600,
              "snapshot": true,
          },
          "usb_devices": {
              "query": "SELECT * FROM usb_devices;",
              "interval": 600
          }
        }
    },
    "osquery_internal_info": {
        "version": "1.4.5",
        "discovery": [
          "select pid from processes where name like '%osqueryd%';"
        ],
        "queries": {
          "info": {
              "query": "select i.*, p.resident_size, p.user_time, p.system_time, time.minutes as counter from osquery_info i, processes p, time where p.pid = i.pid;",
              "interval": 60,
              "snapshot": true
          },
          "registry": {
              "query": "SELECT * FROM osquery_registry;",
              "interval": 600,
              "snapshot": true
          },
          "schedule": {
              "query": "select name, interval, executions, output_size, wall_time, (user_time/executions) as avg_user_time, (system_time/executions) as avg_system_time, average_memory from osquery_schedule;",
              "interval": 60,
              "snapshot": true
          }
        }
    }
  }
}
```

The `osquery_packs` table was modified to remove the superfluous
columns which could already have been found in `osquery_schedule`. Two
more columns were added in their place, representing stats about pack's
discovery query execution history.

Notably, the internal API for the `osquery::Config` class has changed
rather dramatically as apart of the refactoring. We think this is an
improvement. While strictly adhering to the osquery config plugin
interface will have avoided any compatibility errors, advanced users may
notice compilation errors if they access config data directly. All
internal users of the config have obviously been updated. Yet another
reason to merge your code into mainline; we update it for you when we
refactor!
2015-09-02 10:56:26 -07:00

285 lines
8.6 KiB
C++

/*
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#include <map>
#include <string>
#include <osquery/config.h>
#include <osquery/logger.h>
#include "osquery/tables/other/yara_utils.h"
namespace osquery {
/**
* The callback used when there are compilation problems in the rules.
*/
void YARACompilerCallback(int error_level,
const char* file_name,
int line_number,
const char* message,
void* user_data) {
if (error_level == YARA_ERROR_LEVEL_ERROR) {
VLOG(1) << file_name << "(" << line_number << "): error: " << message;
} else {
VLOG(1) << file_name << "(" << line_number << "): warning: " << message;
}
}
/**
* Compile a single rule file and load it into rule pointer.
*/
Status compileSingleFile(const std::string& file, YR_RULES** rules) {
YR_COMPILER *compiler = nullptr;
int result = yr_compiler_create(&compiler);
if (result != ERROR_SUCCESS) {
VLOG(1) << "Could not create compiler: " + std::to_string(result);
return Status(1, "Could not create compiler: " + std::to_string(result));
}
yr_compiler_set_callback(compiler, YARACompilerCallback, nullptr);
bool compiled = false;
YR_RULES *tmp_rules;
VLOG(1) << "Loading " << file;
// First attempt to load the file, in case it is saved (pre-compiled)
// rules.
//
// If you want to use saved rule files you must have them all in a single
// file. This is easy to accomplish with yarac(1).
result = yr_rules_load(file.c_str(), &tmp_rules);
if (result != ERROR_SUCCESS && result != ERROR_INVALID_FILE) {
yr_compiler_destroy(compiler);
return Status(1, "Error loading YARA rules: " + std::to_string(result));
} else if (result == ERROR_SUCCESS) {
*rules = tmp_rules;
} else {
compiled = true;
// Try to compile the rules.
FILE *rule_file = fopen(file.c_str(), "r");
if (rule_file == nullptr) {
yr_compiler_destroy(compiler);
return Status(1, "Could not open file: " + file);
}
int errors =
yr_compiler_add_file(compiler, rule_file, nullptr, file.c_str());
fclose(rule_file);
rule_file = nullptr;
if (errors > 0) {
yr_compiler_destroy(compiler);
// Errors printed via callback.
return Status(1, "Compilation errors");
}
}
if (compiled) {
// All the rules for this category have been compiled, save them in the map.
result = yr_compiler_get_rules(compiler, *(&rules));
if (result != ERROR_SUCCESS) {
yr_compiler_destroy(compiler);
return Status(1, "Insufficient memory to get YARA rules");
}
}
if (compiler != nullptr) {
yr_compiler_destroy(compiler);
compiler = nullptr;
}
return Status(0, "OK");
}
/**
* Given a vector of strings, attempt to compile them and store the result
* in the map under the given category.
*/
Status handleRuleFiles(const std::string &category,
const pt::ptree &rule_files,
std::map<std::string, YR_RULES *> &rules) {
YR_COMPILER *compiler = nullptr;
int result = yr_compiler_create(&compiler);
if (result != ERROR_SUCCESS) {
VLOG(1) << "Could not create compiler: error " + std::to_string(result);
return Status(1, "YARA compile error " + std::to_string(result));
}
yr_compiler_set_callback(compiler, YARACompilerCallback, nullptr);
bool compiled = false;
for (const auto& item : rule_files) {
YR_RULES *tmp_rules = nullptr;
auto rule = item.second.get("", "");
if (rule[0] != '/') {
rule = std::string("/etc/osquery/yara/") + rule;
}
// First attempt to load the file, in case it is saved (pre-compiled)
// rules. Sadly there is no way to load multiple compiled rules in
// succession. This means that:
//
// saved1, saved2
// results in saved2 being the only file used.
//
// Also, mixing source and saved rules results in the saved rules being
// overridden by the combination of the source rules once compiled, e.g.:
//
// file1, saved1
// result in file1 being the only file used.
//
// If you want to use saved rule files you must have them all in a single
// file. This is easy to accomplish with yarac(1).
result = yr_rules_load(rule.c_str(), &tmp_rules);
if (result != ERROR_SUCCESS && result != ERROR_INVALID_FILE) {
yr_compiler_destroy(compiler);
return Status(1, "YARA load error " + std::to_string(result));
} else if (result == ERROR_SUCCESS) {
// If there are already rules there, destroy them and put new ones in.
if (rules.count(category) > 0) {
yr_rules_destroy(rules[category]);
}
rules[category] = tmp_rules;
} else {
compiled = true;
// Try to compile the rules.
FILE *rule_file = fopen(rule.c_str(), "r");
if (rule_file == nullptr) {
yr_compiler_destroy(compiler);
return Status(1, "Could not open file: " + rule);
}
int errors =
yr_compiler_add_file(compiler, rule_file, nullptr, rule.c_str());
fclose(rule_file);
rule_file = nullptr;
if (errors > 0) {
yr_compiler_destroy(compiler);
// Errors printed via callback.
return Status(1, "Compilation errors");
}
}
}
if (compiled) {
// All the rules for this category have been compiled, save them in the map.
result = yr_compiler_get_rules(compiler, &rules[category]);
if (result != ERROR_SUCCESS) {
yr_compiler_destroy(compiler);
return Status(1, "Insufficient memory to get YARA rules");
}
}
if (compiler != nullptr) {
yr_compiler_destroy(compiler);
compiler = nullptr;
}
return Status(0, "OK");
}
/**
* This is the YARA callback. Used to store matching rules in the row which is
* passed in as user_data.
*/
int YARACallback(int message, void *message_data, void *user_data) {
if (message == CALLBACK_MSG_RULE_MATCHING) {
Row *r = (Row *) user_data;
YR_RULE *rule = (YR_RULE *) message_data;
if ((*r)["matches"].length() > 0) {
(*r)["matches"] += "," + std::string(rule->identifier);
} else {
(*r)["matches"] = std::string(rule->identifier);
}
YR_STRING *string = nullptr;
yr_rule_strings_foreach(rule, string) {
YR_MATCH *match = nullptr;
yr_string_matches_foreach(string, match) {
if ((*r)["strings"].length() > 0) {
(*r)["strings"] += "," + std::string(string->identifier);
} else {
(*r)["strings"] = std::string(string->identifier);
}
std::stringstream ss;
ss << std::hex << (match->base + match->offset);
(*r)["strings"] += ":" + ss.str();
}
}
const char *tag = nullptr;
yr_rule_tags_foreach(rule, tag) {
if ((*r)["tags"].length() > 0) {
(*r)["tags"] += "," + std::string(tag);
} else {
(*r)["tags"] = std::string(tag);
}
}
(*r)["count"] = INTEGER(std::stoi((*r)["count"]) + 1);
}
return CALLBACK_CONTINUE;
}
Status YARAConfigParserPlugin::setUp() {
int result = yr_initialize();
if (result != ERROR_SUCCESS) {
LOG(WARNING) << "Unable to initialize YARA (" << result << ")";
return Status(1, "Unable to initialize YARA");
}
return Status(0, "OK");
}
Status YARAConfigParserPlugin::update(
const std::map<std::string, pt::ptree> &config) {
// The YARA config parser requested the "yara" top-level key in the config.
const auto& yara_config = config.at("yara");
// Look for a "signatures" key with the group/file content.
if (yara_config.count("signatures") > 0) {
const auto& signatures = yara_config.get_child("signatures");
data_.add_child("signatures", signatures);
for (const auto& element : signatures) {
VLOG(1) << "Compiling YARA signature group: " << element.first;
auto status = handleRuleFiles(element.first, element.second, rules_);
if (!status.ok()) {
VLOG(1) << "YARA rule compile error: " << status.getMessage();
return status;
}
}
}
// The "file_paths" set maps the rule groups to the "file_paths" top level
// configuration key. That similar key keeps the groups of file paths.
if (yara_config.count("file_paths") > 0) {
const auto& file_paths = yara_config.get_child("file_paths");
data_.add_child("file_paths", file_paths);
}
return Status(0, "OK");
}
/// Call the simple YARA ConfigParserPlugin "yara".
REGISTER(YARAConfigParserPlugin, "config_parser", "yara");
}