mirror of
https://github.com/valitydev/osquery-1.git
synced 2024-11-06 09:35:20 +00:00
Shimcache Table (#6463)
This commit is contained in:
parent
e8ef7b56bd
commit
336e6b075f
@ -194,6 +194,7 @@ function(generateOsqueryTablesSystemSystemtable)
|
||||
windows/scheduled_tasks.cpp
|
||||
windows/services.cpp
|
||||
windows/shared_resources.cpp
|
||||
windows/shimcache.cpp
|
||||
windows/smbios_tables.cpp
|
||||
windows/startup_items.cpp
|
||||
windows/system_info.cpp
|
||||
|
213
osquery/tables/system/windows/shimcache.cpp
Normal file
213
osquery/tables/system/windows/shimcache.cpp
Normal file
@ -0,0 +1,213 @@
|
||||
/**
|
||||
* Copyright (c) 2014-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed in accordance with the terms specified in
|
||||
* the LICENSE file found in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#include <osquery/core.h>
|
||||
#include <osquery/logger.h>
|
||||
#include <osquery/tables.h>
|
||||
#include <osquery/tables/system/windows/registry.h>
|
||||
#include <osquery/tables/system/windows/userassist.h>
|
||||
#include <osquery/utils/conversions/tryto.h>
|
||||
|
||||
#include <boost/algorithm/hex.hpp>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <boost/optional.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
|
||||
const int kWin8 = 256;
|
||||
const int kWin10PreCreator = 96;
|
||||
const int kWin10Creator = 104;
|
||||
const std::string kWin8Start = "80";
|
||||
const std::string kWin10Start = "30";
|
||||
const std::string kWin10CreatorStart = "34";
|
||||
const std::string kWin8110ShimcacheDelimiter = "31307473";
|
||||
|
||||
// Shimcache can be in multiple ControlSets (ControlSet001, ControlSet002, etc)
|
||||
const std::string kShimcacheControlset =
|
||||
"HKEY_LOCAL_MACHINE\\SYSTEM\\%ControlSet%\\Control\\Session "
|
||||
"Manager\\AppCompatCache";
|
||||
|
||||
struct ShimcacheData {
|
||||
std::string path;
|
||||
long long last_modified;
|
||||
boost::optional<bool> execution_flag;
|
||||
};
|
||||
|
||||
namespace osquery {
|
||||
namespace tables {
|
||||
|
||||
auto parseShimcacheData(const std::string& token,
|
||||
const boost::optional<bool>& execution_flag_exists) {
|
||||
ShimcacheData shimcache;
|
||||
std::string path_length = token.substr(16, 4);
|
||||
|
||||
// swap endianess
|
||||
std::reverse(path_length.begin(), path_length.end());
|
||||
|
||||
for (std::size_t i = 0; i < path_length.length(); i += 2) {
|
||||
std::swap(path_length[i], path_length[i + 1]);
|
||||
}
|
||||
|
||||
// Convert string to size_t for file path length
|
||||
size_t shimcache_file_path = tryTo<std::size_t>(path_length, 16).takeOr(0_sz);
|
||||
|
||||
// Registry data is in Unicode (extra 0x00)
|
||||
std::string path = token.substr(20, shimcache_file_path * 2);
|
||||
boost::erase_all(path, "00");
|
||||
|
||||
// Windows Store entries have extra data, the extra data includes tabs in the
|
||||
// entry (Unicode value 09). Convert to spaces due to table formatting
|
||||
// issues
|
||||
boost::replace_all(path, "09", "20");
|
||||
std::string string_path;
|
||||
|
||||
// Convert hex path to readable string
|
||||
try {
|
||||
string_path = boost::algorithm::unhex(path);
|
||||
} catch (const boost::algorithm::hex_decode_error& /* e */) {
|
||||
LOG(WARNING) << "Failed to decode Shimcache hex values to string";
|
||||
shimcache.last_modified = 0LL;
|
||||
return shimcache;
|
||||
}
|
||||
|
||||
shimcache.path = string_path;
|
||||
size_t shimcache_modified_start = 0;
|
||||
size_t execution_flag_start = 0;
|
||||
|
||||
// If execution flag exists set where the flag starts in the substring
|
||||
if (execution_flag_exists == true) {
|
||||
shimcache_modified_start = 40;
|
||||
execution_flag_start = 24;
|
||||
} else {
|
||||
shimcache_modified_start = 20;
|
||||
}
|
||||
std::string shimcache_time =
|
||||
token.substr(shimcache_modified_start + shimcache_file_path * 2, 16);
|
||||
|
||||
// Check to make sure the shimcache entry is greater than zero
|
||||
if (!string_path.empty()) {
|
||||
// Get last modified time of the file
|
||||
shimcache.last_modified = littleEndianToUnixTime(shimcache_time);
|
||||
} else {
|
||||
shimcache.last_modified = 0LL;
|
||||
}
|
||||
|
||||
if (execution_flag_exists == true) {
|
||||
int shimcache_flag =
|
||||
tryTo<int>(
|
||||
token.substr(execution_flag_start + shimcache_file_path * 2, 2), 16)
|
||||
.takeOr(0);
|
||||
// Perform Bitwise AND to determine TRUE or FALSE
|
||||
if (shimcache_flag & 2) {
|
||||
shimcache.execution_flag = true;
|
||||
} else {
|
||||
shimcache.execution_flag = false;
|
||||
}
|
||||
}
|
||||
return shimcache;
|
||||
}
|
||||
|
||||
void parseEntry(const Row& aKey, size_t& index, QueryData& results) {
|
||||
boost::optional<bool> execution_flag_exists;
|
||||
std::string delimter;
|
||||
std::string data = aKey.at("data");
|
||||
|
||||
// Check if Registry data starts with any of supported WIN_START
|
||||
// values and if the Shimcache delimiter exists at the specific
|
||||
// substring
|
||||
if ((boost::starts_with(data, kWin8Start)) &&
|
||||
(data.substr(kWin8, 8) == kWin8110ShimcacheDelimiter)) {
|
||||
execution_flag_exists = true;
|
||||
delimter = kWin8110ShimcacheDelimiter;
|
||||
} else if (boost::starts_with(data, kWin10Start) &&
|
||||
(data.substr(kWin10PreCreator, 8) == kWin8110ShimcacheDelimiter)) {
|
||||
delimter = kWin8110ShimcacheDelimiter;
|
||||
} else if (boost::starts_with(data, kWin10CreatorStart) &&
|
||||
(data.substr(kWin10Creator, 8) == kWin8110ShimcacheDelimiter)) {
|
||||
delimter = kWin8110ShimcacheDelimiter;
|
||||
} else {
|
||||
LOG(WARNING) << "Unknown or unsupported shimcache data: "
|
||||
<< data.substr(256, 8);
|
||||
return;
|
||||
}
|
||||
|
||||
bool first_run = true;
|
||||
size_t pos = 0;
|
||||
std::string token;
|
||||
|
||||
auto createRow = [&results, &index](const ShimcacheData& shimcache) {
|
||||
Row r;
|
||||
r["entry"] = INTEGER(index);
|
||||
r["path"] = TEXT(shimcache.path);
|
||||
r["modified_time"] = INTEGER(shimcache.last_modified);
|
||||
if (shimcache.execution_flag.is_initialized()) {
|
||||
if (shimcache.execution_flag.get()) {
|
||||
r["execution_flag"] = INTEGER(1);
|
||||
} else {
|
||||
r["execution_flag"] = INTEGER(0);
|
||||
}
|
||||
} else {
|
||||
r["execution_flag"] = INTEGER(-1);
|
||||
}
|
||||
results.push_back(std::move(r));
|
||||
};
|
||||
|
||||
// Find all entries base on shimcache data delimter
|
||||
while ((pos = data.find(delimter)) != std::string::npos) {
|
||||
token = data.substr(0, pos);
|
||||
|
||||
// Skip all the data before the first delimter match
|
||||
if (token.length() > 20) {
|
||||
if (first_run) {
|
||||
first_run = false;
|
||||
data.erase(0, pos + delimter.length());
|
||||
continue;
|
||||
}
|
||||
createRow(parseShimcacheData(token, execution_flag_exists));
|
||||
index++;
|
||||
} else {
|
||||
LOG(ERROR) << "Shimcache entry does not meet length requirements: "
|
||||
<< token;
|
||||
}
|
||||
data.erase(0, pos + delimter.length());
|
||||
}
|
||||
|
||||
// Get last appcopmat entry
|
||||
token = data.substr(0, pos);
|
||||
createRow(parseShimcacheData(token, execution_flag_exists));
|
||||
}
|
||||
|
||||
QueryData genShimcache(QueryContext& context) {
|
||||
QueryData results;
|
||||
std::set<std::string> shimcacheResults;
|
||||
|
||||
expandRegistryGlobs(kShimcacheControlset, shimcacheResults);
|
||||
for (const auto& rKey : shimcacheResults) {
|
||||
auto entry = rKey.find("Control\\Session Manager\\AppCompatCache");
|
||||
if (entry == std::string::npos) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QueryData entryResults;
|
||||
size_t index = 1;
|
||||
queryKey(rKey, entryResults);
|
||||
for (const auto& aKey : entryResults) {
|
||||
if (aKey.at("name") != "AppCompatCache") {
|
||||
continue;
|
||||
}
|
||||
parseEntry(aKey, index, results);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
} // namespace tables
|
||||
} // namespace osquery
|
@ -21,7 +21,7 @@ namespace osquery {
|
||||
namespace tables {
|
||||
|
||||
constexpr auto kFullRegPath =
|
||||
"\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\UserAssist";
|
||||
"\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\UserAssist";
|
||||
|
||||
// Decode ROT13 sub key value
|
||||
/**
|
||||
@ -50,27 +50,28 @@ std::string rotDecode(std::string& value_key_reg) {
|
||||
return decoded_value_key;
|
||||
}
|
||||
|
||||
// Get last exeution time
|
||||
auto lastExecute(std::string& time_data) {
|
||||
// Convert little endian Windows FILETIME to unix timestamp
|
||||
long long littleEndianToUnixTime(const std::string& time_data) {
|
||||
// If timestamp is zero dont convert to UNIX Time
|
||||
if (time_data == "0000000000000000") {
|
||||
return 1LL;
|
||||
return 0LL;
|
||||
} else {
|
||||
std::string time_string = time_data;
|
||||
// swap endianess
|
||||
std::reverse(time_data.begin(), time_data.end());
|
||||
std::reverse(time_string.begin(), time_string.end());
|
||||
|
||||
for (std::size_t i = 0; i < time_data.length(); i += 2) {
|
||||
char temp = time_data[i];
|
||||
time_data[i] = time_data[i + 1];
|
||||
time_data[i + 1] = temp;
|
||||
for (std::size_t i = 0; i < time_string.length(); i += 2) {
|
||||
char temp = time_string[i];
|
||||
time_string[i] = time_string[i + 1];
|
||||
time_string[i + 1] = temp;
|
||||
}
|
||||
|
||||
// Convert string to long long
|
||||
unsigned long long last_run =
|
||||
tryTo<unsigned long long>(time_data, 16).takeOr(0ull);
|
||||
tryTo<unsigned long long>(time_string, 16).takeOr(0ull);
|
||||
if (last_run == 0ull) {
|
||||
LOG(WARNING) << "Failed to convert string to long long: " << time_data;
|
||||
return 1LL;
|
||||
LOG(WARNING) << "Failed to convert string to long long: " << time_string;
|
||||
return 0LL;
|
||||
}
|
||||
|
||||
FILETIME file_time;
|
||||
@ -84,7 +85,7 @@ auto lastExecute(std::string& time_data) {
|
||||
}
|
||||
|
||||
// Get execution count
|
||||
int executionNum(const std::string& assist_data) {
|
||||
std::size_t executionNum(const std::string& assist_data) {
|
||||
if (assist_data.length() <= 16) {
|
||||
LOG(WARNING) << "Userassist execution count format is incorrect";
|
||||
return -1;
|
||||
@ -159,18 +160,18 @@ QueryData genUserAssist(QueryContext& context) {
|
||||
results.push_back(r);
|
||||
} else {
|
||||
std::string assist_data = aKey.at("data");
|
||||
auto time_str = 1LL;
|
||||
auto time_str = 0LL;
|
||||
if (assist_data.length() <= 136) {
|
||||
LOG(WARNING)
|
||||
<< "Userassist last execute Timestamp format is incorrect";
|
||||
} else {
|
||||
std::string time_data = assist_data.substr(120, 16);
|
||||
time_str = lastExecute(time_data);
|
||||
time_str = littleEndianToUnixTime(time_data);
|
||||
}
|
||||
|
||||
r["path"] = decoded_value_key;
|
||||
|
||||
if (time_str == 1LL) {
|
||||
if (time_str == 0LL) {
|
||||
r["count"] = "";
|
||||
r["last_execution_time"] = "";
|
||||
} else {
|
||||
|
@ -16,7 +16,8 @@ namespace tables {
|
||||
// Decode ROT13 data
|
||||
std::string rotDecode(std::string& value_key_reg);
|
||||
|
||||
// Get Epoch time from Windows FILETIME
|
||||
auto lastExecute(std::string& time_data);
|
||||
// Get Epoch time from Windows FILETIME in little endian format
|
||||
// Windows Registry sometimes stores FILETIME in little endian format
|
||||
long long littleEndianToUnixTime(const std::string& time_data);
|
||||
} // namespace tables
|
||||
} // namespace osquery
|
||||
|
@ -288,6 +288,7 @@ function(generateNativeTables)
|
||||
"windows/connectivity.table:windows"
|
||||
"windows/userassist.table:windows"
|
||||
"windows/hvci_status.table:windows"
|
||||
"windows/shimcache.table:windows"
|
||||
"yara/yara_events.table:linux,macos"
|
||||
"yara/yara.table:linux,macos,freebsd"
|
||||
)
|
||||
|
12
specs/windows/shimcache.table
Normal file
12
specs/windows/shimcache.table
Normal file
@ -0,0 +1,12 @@
|
||||
table_name("shimcache")
|
||||
description("Application Compatibility Cache, contains artifacts of execution.")
|
||||
schema([
|
||||
Column("entry", INTEGER, "Execution order."),
|
||||
Column("path", TEXT, "This is the path to the executed file."),
|
||||
Column("modified_time", INTEGER, "File Modified time."),
|
||||
Column("execution_flag", INTEGER, "Boolean Execution flag, 1 for execution, 0 for no execution, -1 for missing (this flag does not exist on Windows 10 and higher)."),
|
||||
])
|
||||
implementation("shimcache@genShimcache")
|
||||
examples([
|
||||
"select * from shimcache;",
|
||||
])
|
@ -285,6 +285,7 @@ function(generateTestsIntegrationTablesTestsTest)
|
||||
scheduled_tasks.cpp
|
||||
services.cpp
|
||||
shared_resources.cpp
|
||||
shimcache.cpp
|
||||
startup_items.cpp
|
||||
userassist.cpp
|
||||
video_info.cpp
|
||||
|
38
tests/integration/tables/shimcache.cpp
Normal file
38
tests/integration/tables/shimcache.cpp
Normal file
@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Copyright (c) 2014-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed in accordance with the terms specified in
|
||||
* the LICENSE file found in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#include <osquery/tests/integration/tables/helper.h>
|
||||
|
||||
namespace osquery {
|
||||
namespace table_tests {
|
||||
class ShimcacheTest : public testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
setUpEnvironment();
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(ShimcacheTest, test_sanity) {
|
||||
QueryData const rows = execute_query("select * from shimcache");
|
||||
QueryData const specific_query_rows =
|
||||
execute_query("select * from shimcache where path like '%.exe'");
|
||||
|
||||
ASSERT_GT(rows.size(), 0ul);
|
||||
ASSERT_GT(specific_query_rows.size(), 0ul);
|
||||
|
||||
ValidationMap row_map = {
|
||||
{"entry", NonEmptyString},
|
||||
{"path", NormalType},
|
||||
{"modified_time", NormalType},
|
||||
{"execution_flag", NormalType},
|
||||
};
|
||||
validate_rows(rows, row_map);
|
||||
validate_rows(specific_query_rows, row_map);
|
||||
}
|
||||
} // namespace table_tests
|
||||
} // namespace osquery
|
Loading…
Reference in New Issue
Block a user