Shimcache Table (#6463)

This commit is contained in:
puffyCid 2020-07-12 21:54:37 -04:00 committed by GitHub
parent e8ef7b56bd
commit 336e6b075f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 286 additions and 18 deletions

View File

@ -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

View 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

View File

@ -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 {

View File

@ -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

View File

@ -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"
)

View 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;",
])

View File

@ -285,6 +285,7 @@ function(generateTestsIntegrationTablesTestsTest)
scheduled_tasks.cpp
services.cpp
shared_resources.cpp
shimcache.cpp
startup_items.cpp
userassist.cpp
video_info.cpp

View 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