Add Shellbags table (#6949)

Hello, this largish PR adds shellbags support to osquery.
Shellbags is a complex (imo) windows Registry artifact that primarily keeps track of directories a user has browsed to (specifically directories accessed using Windows Explorer).  By parsing shellbags its possible to recreate what directories a user accessed
Shellbags are composed of shellitems, this PR also adds support to parsing several shellitems, finally shellbags also contain FAT timestamps that show when a directory was created, modified, accessed, FAT timestamp parsing is also included in this PR

Example query of what shellbags looks like
```
osquery> select * from shellbags;
+-----------------------------------------------+--------------+-------------------------------------------------------------------------------------+---------------+--------------+---------------+-----------+--------------+
| sid                                           | source       | path                                                                                | modified_time | created_time | accessed_time | mft_entry | mft_sequence |
+-----------------------------------------------+--------------+-------------------------------------------------------------------------------------+---------------+--------------+---------------+-----------+--------------+
| S-1-5-21-1079689790-2336414676-942872339-1001 | usrclass.dat | This PC\C:\Users\bob                                                                | 0             | 0            | 0             | 0         | 0            |
| S-1-5-21-1079689790-2336414676-942872339-1001 | usrclass.dat | This PC\C:\Users\bob\Downloads                                                      | 1571635108    | 1571620406   | 1571635108    | 3074      | 5            |
| S-1-5-21-1079689790-2336414676-942872339-1001 | usrclass.dat | This PC\C:\Users\bob\Projects                                                       | 0             | 0            | 0             | 0         | 0            |
| S-1-5-21-1079689790-2336414676-942872339-1001 | usrclass.dat | This PC\C:\Users\bob\Projects\osquery                                               | 0             | 0            | 0             | 0         | 0            |
| S-1-5-21-1079689790-2336414676-942872339-1001 | usrclass.dat | This PC\C:\Users\bob\Projects\osquery\build                                         | 0             | 0            | 0             | 0         | 0            |
| S-1-5-21-1079689790-2336414676-942872339-1001 | usrclass.dat | This PC\C:\Users\bob\Projects\osquery\build\osquery                                 | 0             | 0            | 0             | 0         | 0            |
| S-1-5-21-1079689790-2336414676-942872339-1001 | usrclass.dat | This PC\C:\Users\bob\Projects\osquery\build\osquery\RelWithDebInfo                  | 0             | 0            | 0             | 0         | 0            |
| S-1-5-21-1079689790-2336414676-942872339-1001 | usrclass.dat | This PC\C:\Users\bob\Projects\osquery\osquery                                       | 1578192498    | 1571701478   | 1578192498    | 495902    | 4            |
| S-1-5-21-1079689790-2336414676-942872339-1001 | usrclass.dat | This PC\C:\Users\bob\Projects\osquery\osquery\killswitch                            | 1578192406    | 1575859554   | 1578192406    | 707032    | 2            |
| S-1-5-21-1079689790-2336414676-942872339-1001 | usrclass.dat | This PC\C:\Users\bob\Projects\clamav-osquery                                        | 1572045050    | 1572045050   | 1572045050    | 221518    | 14           |
| S-1-5-21-1079689790-2336414676-942872339-1001 | usrclass.dat | This PC\C:\Users\bob\Projects\clamav-osquery\extension_clamav                       | 1572045050    | 1572045050   | 1572045050    | 432733    | 8            |
| S-1-5-21-1079689790-2336414676-942872339-1001 | usrclass.dat | This PC\C:\Users\bob\Projects\clamav-osquery\extension_clamav\src                   | 1572045050    | 1572045050   | 1572045050    | 432736    | 11           |
| S-1-5-21-1079689790-2336414676-942872339-1001 | usrclass.dat | This PC\C:\Users\bob\.osquery                                                       | 1571706262    | 1571706212   | 1571706262    | 575462    | 4            |
| S-1-5-21-1079689790-2336414676-942872339-1001 | usrclass.dat | This PC\C:\Users\bob\AppData                                                        | 1571623328    | 1571623318   | 1571623328    | 206482    | 7            |
| S-1-5-21-1079689790-2336414676-942872339-1001 | usrclass.dat | This PC\C:\Users\bob\AppData\Local                                                  | 1571701908    | 1571623318   | 1571701908    | 206502    | 8            |
| S-1-5-21-1079689790-2336414676-942872339-1001 | usrclass.dat | This PC\C:\Users\bob\AppData\Local\Microsoft                                        | 1593297370    | 1571623318   | 1593297370    | 206504    | 8            |
| S-1-5-21-1079689790-2336414676-942872339-1001 | usrclass.dat | This PC\C:\Users\bob\AppData\Local\Microsoft\Office                                 | 1593297370    | 1593295160   | 1593297370    | 52684     | 60           |
| S-1-5-21-1079689790-2336414676-942872339-1001 | usrclass.dat | This PC\C:\Users\bob\AppData\Local\Microsoft\Office\16.0                            | 1593297388    | 1593295160   | 1593297388    | 81742     | 11           |
| S-1-5-21-1079689790-2336414676-942872339-1001 | usrclass.dat | This PC\C:\Users\bob\AppData\Local\autopsy                                          | 1612935328    | 1612935328   | 1612935328    | 37041     | 7            |
| S-1-5-21-1079689790-2336414676-942872339-1001 | usrclass.dat | This PC\C:\Users\bob\AppData\Local\autopsy\Cache                                    | 1612935328    | 1612935328   | 1612935328    | 37104     | 8            |
| S-1-5-21-1079689790-2336414676-942872339-1001 | usrclass.dat | This PC\C:\Users\bob\super secret sensitive stuff                                   | 1613198066    | 1613198066   | 1613198066    | 101729    | 123          |
| S-1-5-21-1079689790-2336414676-942872339-1001 | usrclass.dat | This PC\C:\Users\bob\super secret sensitive stuff\secret data                       | 1613198092    | 1613198092   | 1613198092    | 383733    | 38           |
| S-1-5-21-1079689790-2336414676-942872339-1001 | usrclass.dat | This PC\C:\Users\bob\super secret sensitive stuff\secret data\dont look secret data | 1613198108    | 1613198108   | 1613198108    | 383736    | 15           |
+-----------------------------------------------+--------------+-------------------------------------------------------------------------------------+---------------+--------------+---------------+-----------+--------------+
```
Due to the complexity of shellbags currently this PR does not support (or only has partial support) for the following shellitems:
* optical disc
* variable (partial support)
* mtp (partial support)
* user property view data (partial support)

I was not able to generate shellbag data (or only some data) for the above shellitems in my windows vms (tested on two different Windows 10 systems, Windows 8.1, and Windows Server 2019), if osquery encounters any unknown shellbag data it will log a warning and mark the shellitem as "[UNKNOWN SHELL FORMAT]" when building directory paths.  
The main value of shellbags is reconstructing directories accessed as shown above, but this PR does include additional shellbag support such as FTP servers connected to via Windows Explorer, ZIP files opened, MTP devices (partial), and network shares browsed to via Windows Explorer
This PR is kind of large, let me know if there are any questions, suggestions for improvements, or issues, thanks!

Shellbags references:
[Shellitems](https://github.com/libyal/libfwsi/blob/main/documentation/Windows%20Shell%20Item%20format.asciidoc)
[Shellbags](https://www.magnetforensics.com/blog/forensic-analysis-of-windows-shellbags/)
[Property Stores](https://github.com/libyal/libfwps/blob/main/documentation/Windows%20Property%20Store%20format.asciidoc)
This commit is contained in:
puffyCid 2021-02-26 18:47:37 -05:00 committed by GitHub
parent b6b17f7629
commit 121f7e2589
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 1385 additions and 0 deletions

View File

@ -214,6 +214,7 @@ function(generateOsqueryTablesSystemSystemtable)
windows/scheduled_tasks.cpp
windows/services.cpp
windows/shared_resources.cpp
windows/shellbags.cpp
windows/shimcache.cpp
windows/smbios_tables.cpp
windows/startup_items.cpp

View File

@ -0,0 +1,411 @@
/**
* Copyright (c) 2014-present, The osquery authors
*
* This source code is licensed as defined by the LICENSE file found in the
* root directory of this source tree.
*
* SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only)
*/
#include <osquery/core/core.h>
#include <osquery/core/tables.h>
#include <osquery/logger/logger.h>
#include <osquery/tables/system/windows/registry.h>
#include <osquery/utils/conversions/join.h>
#include <osquery/utils/conversions/windows/windows_time.h>
#include <osquery/utils/windows/shellitem.h>
#include <string>
#include <vector>
namespace osquery {
namespace tables {
constexpr auto kShellBagPath =
"\\Local Settings\\Software\\Microsoft\\Windows\\Shell\\BagMRU";
constexpr auto kShellBagPathNtuser =
"\\Software\\Microsoft\\Windows\\Shell\\BagMRU";
std::string guidLookup(const std::string& guid) {
QueryData guid_data;
queryKey("HKEY_LOCAL_MACHINE\\SOFTWARE\\Classes\\CLSID\\{" + guid + "}",
guid_data);
for (const auto& rKey : guid_data) {
auto key_type = rKey.find("type");
auto key_path = rKey.find("path");
if (key_type == rKey.end() || key_path == rKey.end()) {
continue;
}
auto key_name = rKey.at("name");
if (key_name == "(Default)") {
if (rKey.at("data") == "") {
return "{" + guid + "}";
}
return rKey.at("data");
}
}
return "{" + guid + "}";
}
void parseShellData(const std::string& shell_data,
std::vector<std::string>& build_shellbag,
QueryData& results,
const std::string& sid,
const std::string& source) {
Row r;
r["sid"] = sid;
r["source"] = source;
std::string extension_sig = "";
// "0400EFBE", "2600EFBE", "2500EFBE" are the primary shell extensions needed
// to build directory paths
if (shell_data.find("0400EFBE") != std::string::npos) {
extension_sig = "0400EFBE";
} else if (shell_data.find("2600EFBE") != std::string::npos) {
extension_sig = "2600EFBE";
} else if (shell_data.find("2500EFBE") != std::string::npos) {
extension_sig = "2500EFBE";
}
std::string sig = shell_data.substr(4, 2);
ShellFileEntryData file_entry;
if (shell_data.length() > 200 && extension_sig == "" &&
(shell_data.substr(80, 2) == "2F" ||
shell_data.substr(76, 2) == "2F")) { // Zip contents
std::string path = zipContentItem(shell_data);
build_shellbag.push_back(path);
std::string full_path = osquery::join(build_shellbag, "\\");
r["path"] = full_path;
results.push_back(r);
return;
} else if (sig == "1F" &&
shell_data.find("31535053") == std::string::npos) { // Root Folder
std::string name;
std::string full_path;
if (shell_data.substr(8, 2) == "2F") { // User Property View Drive
name = propertyViewDrive(shell_data);
// osquery::join adds "\" to entries, remove drive "\"
name.pop_back();
build_shellbag.push_back(name);
full_path = osquery::join(build_shellbag, "\\");
full_path += "\\";
} else {
std::string root_name = rootFolderItem(shell_data);
name = guidLookup(root_name);
build_shellbag.push_back(name);
full_path = osquery::join(build_shellbag, "\\");
}
r["path"] = full_path;
results.push_back(r);
return;
} else if ((sig == "31" || sig == "30" || sig == "32" || sig == "35" ||
sig == "B1") &&
extension_sig == "0400EFBE") { // Directory/File Entry
file_entry = fileEntry(shell_data);
} else if ((sig == "2F" || sig == "23" || sig == "25" || sig == "29" ||
sig == "2A" || sig == "2E") &&
(extension_sig == "" || extension_sig == "2600EFBE" ||
extension_sig == "2500EFBE")) { // Drive Letter
if (shell_data.substr(6, 2) == "80" &&
(extension_sig == "2600EFBE" || extension_sig == "2500EFBE" ||
extension_sig == "")) { // Check if GUID exists
std::string guid_little = shell_data.substr(8, 32);
std::string guid_string = guidParse(guid_little);
std::string guid_name = guidLookup(guid_string);
build_shellbag.push_back(guid_name);
std::string full_path = osquery::join(build_shellbag, "\\");
r["path"] = full_path;
results.push_back(r);
return;
} else if (shell_data.find("5C007500730062002300") != std::string::npos &&
extension_sig == "") { // Check for \usb#
std::string name = mtpRoot(shell_data);
build_shellbag.push_back(name);
std::string full_path = osquery::join(build_shellbag, "\\");
r["path"] = full_path;
results.push_back(r);
return;
}
// Drive letter should have ":\"
if (shell_data.find("3A5C") == std::string::npos) {
build_shellbag.push_back("[UNKNOWN DRIVE NAME]");
std::string full_path = osquery::join(build_shellbag, "\\");
r["path"] = full_path;
results.push_back(r);
return;
}
std::string drive_name = driveLetterItem(shell_data);
// osquery::join adds "\" to entries, remove drive "\"
drive_name.pop_back();
build_shellbag.push_back(drive_name);
std::string full_path = osquery::join(build_shellbag, "\\");
r["path"] = full_path + "\\";
results.push_back(r);
return;
} else if (sig == "01") { // Control Panel Category
std::string panel = controlPanelCategoryItem(shell_data);
build_shellbag.push_back(panel);
std::string full_path = osquery::join(build_shellbag, "\\");
r["path"] = full_path;
results.push_back(r);
return;
} else if (sig == "71") { // Control Panel
std::string control_guid = controlPanelItem(shell_data);
std::string guid_name = guidLookup(control_guid);
build_shellbag.push_back(guid_name);
std::string full_path = osquery::join(build_shellbag, "\\");
r["path"] = full_path;
results.push_back(r);
return;
} else if (sig == "C3" || sig == "41" || sig == "42" || sig == "46" ||
sig == "47" || sig == "4C") { // Network share
std::string network_share = networkShareItem(shell_data);
build_shellbag.push_back(network_share);
std::string full_path = osquery::join(build_shellbag, "\\");
r["path"] = full_path;
results.push_back(r);
return;
} else if (sig == "61") { // FTP/URI
std::vector<std::string> ftp_data = ftpItem(shell_data);
long long unix_time = littleEndianToUnixTime(ftp_data[0]);
build_shellbag.push_back(ftp_data[1]);
std::string full_path = osquery::join(build_shellbag, "\\");
r["path"] = full_path;
r["accessed_time"] = INTEGER(unix_time);
results.push_back(r);
return;
} else if (sig == "74" && shell_data.find("43465346") !=
std::string::npos) { // User File View
file_entry = fileEntry(shell_data);
} else if (sig == "00") { // Variable shell item, can contain a variety of
// shell item formats
if (shell_data.find("EEBBFE23") != std::string::npos) {
std::string guid_string = variableGuid(shell_data);
std::string guid_name = guidLookup(guid_string);
build_shellbag.push_back(guid_name);
std::string full_path = osquery::join(build_shellbag, "\\");
r["path"] = full_path;
results.push_back(r);
return;
} else if (shell_data.substr(12, 8) == "05000000" ||
shell_data.substr(12, 8) == "05000300") {
std::string ftp_name = variableFtp(shell_data);
build_shellbag.push_back(ftp_name);
std::string full_path = osquery::join(build_shellbag, "\\");
r["path"] = full_path;
results.push_back(r);
return;
} else if (shell_data.find("31535053") != std::string::npos) {
// User Property View contains several signatures, data is likely
// associated with Explorer searches?
if ((shell_data.find("D5DFA323") != std::string::npos) ||
(shell_data.find("81191410") != std::string::npos) ||
(shell_data.find("EEBBFE23") != std::string::npos) ||
(shell_data.find("00EEBEBE") != std::string::npos)) {
build_shellbag.push_back("[VARIABLE USER PROPERTY VIEW]");
std::string full_path = osquery::join(build_shellbag, "\\");
r["path"] = full_path;
results.push_back(r);
return;
}
std::vector<size_t> wps_list;
// check if Windows Property Store version 1SPS is in shellbag data,
// there could be multiple
size_t wps = shell_data.find("31535053");
while (wps != std::string::npos) {
wps_list.push_back(wps);
wps = shell_data.find("31535053", wps + 1);
}
std::string property_name = propertyStore(shell_data, wps_list);
build_shellbag.push_back(property_name);
std::string full_path = osquery::join(build_shellbag, "\\");
r["path"] = full_path;
results.push_back(r);
return;
} else if (shell_data.find("0505203110") !=
std::string::npos) { // MTP Device
std::string name = mtpDevice(shell_data);
build_shellbag.push_back(name);
std::string full_path = osquery::join(build_shellbag, "\\");
r["path"] = full_path;
results.push_back(r);
return;
} else if (shell_data.find("06201907") != std::string::npos) { // MTP Folder
std::string name = mtpFolder(shell_data);
build_shellbag.push_back(name);
std::string full_path = osquery::join(build_shellbag, "\\");
r["path"] = full_path;
results.push_back(r);
return;
}
LOG(WARNING) << "Unknown variable format: " << shell_data;
build_shellbag.push_back("[UNKNOWN VARIABLE FORMAT]");
std::string full_path = osquery::join(build_shellbag, "\\");
r["path"] = full_path;
results.push_back(r);
return;
} else {
if (shell_data.find("31535053") != std::string::npos) {
if (shell_data.find("D5DFA323") !=
std::string::npos) { // User Property View
std::string property_guid = shell_data.substr(226, 32);
std::string guid_string = guidParse(property_guid);
std::string guid_name = guidLookup(guid_string);
build_shellbag.push_back(guid_name);
std::string full_path = osquery::join(build_shellbag, "\\");
r["path"] = full_path;
results.push_back(r);
return;
}
// User Property View may have additional other types of signatures, data
// is likely associated with Explorer searches?
if ((shell_data.find("81191410") != std::string::npos) ||
(shell_data.find("EEBBFE23") != std::string::npos) ||
(shell_data.find("BBAF933B") != std::string::npos) ||
(shell_data.find("00EEBEBE") != std::string::npos)) {
build_shellbag.push_back("[USER PROPERTY VIEW]");
std::string full_path = osquery::join(build_shellbag, "\\");
r["path"] = full_path;
results.push_back(r);
return;
}
std::vector<size_t> wps_list;
// check if Windows Property Store version 1SPS is in shellbag data, there
// could be multiple
size_t wps = shell_data.find("31535053");
while (wps != std::string::npos) {
wps_list.push_back(wps);
wps = shell_data.find("31535053", wps + 1);
}
std::string property_name = propertyStore(shell_data, wps_list);
build_shellbag.push_back(property_name);
std::string full_path = osquery::join(build_shellbag, "\\");
r["path"] = full_path;
results.push_back(r);
return;
} else {
LOG(WARNING) << "Unsupported Shellbag format: " << shell_data;
build_shellbag.push_back("[UNSUPPORTED FORMAT]");
std::string full_path = osquery::join(build_shellbag, "\\");
r["path"] = full_path;
results.push_back(r);
return;
}
}
if (file_entry.path == "[UNSUPPORTED SHELL EXTENSION]") {
build_shellbag.push_back(file_entry.path);
std::string full_path = osquery::join(build_shellbag, "\\");
r["path"] = full_path;
results.push_back(r);
return;
}
r["modified_time"] = INTEGER(file_entry.dos_modified);
r["created_time"] = INTEGER(file_entry.dos_created);
r["accessed_time"] = INTEGER(file_entry.dos_accessed);
build_shellbag.push_back(file_entry.path);
long long mft_entry = file_entry.mft_entry;
int mft_sequence = file_entry.mft_sequence;
std::string full_path = osquery::join(build_shellbag, "\\");
r["path"] = full_path;
r["mft_entry"] = BIGINT(mft_entry);
r["mft_sequence"] = INTEGER(mft_sequence);
results.push_back(r);
}
// Recursively loop through shellbag entries in the Registry
void parseShellbags(const std::string& path,
std::vector<std::string>& build_shellbag,
QueryData& results,
const std::string& sid,
const std::string& source) {
QueryData shellbag_data;
queryKey(path, shellbag_data);
for (const auto& rKey : shellbag_data) {
auto key_type = rKey.find("type");
auto key_path = rKey.find("path");
if (key_type == rKey.end() || key_path == rKey.end()) {
continue;
}
// For Shellbags Reg keys the last character is a number
if (!isdigit(key_path->second.back())) {
continue;
}
if (rKey.at("data") == "") {
continue;
}
parseShellData(rKey.at("data"), build_shellbag, results, sid, source);
parseShellbags(key_path->second, build_shellbag, results, sid, source);
if (build_shellbag.size() > 0) {
build_shellbag.pop_back();
}
}
}
void parseRegistry(const std::string& full_path,
const std::string& sid,
QueryData& results,
const std::string& source) {
QueryData shellbag_results;
queryKey(full_path, shellbag_results);
for (const auto& uKey : shellbag_results) {
auto key_type = uKey.find("type");
auto key_path = uKey.find("path");
if (key_type == uKey.end() || key_path == uKey.end()) {
continue;
}
if (!isdigit(key_path->second.back())) {
continue;
}
if (uKey.at("data") == "") {
continue;
}
std::vector<std::string> build_shellbag;
parseShellData(uKey.at("data"), build_shellbag, results, sid, source);
parseShellbags(key_path->second, build_shellbag, results, sid, source);
}
}
QueryData genShellbags(QueryContext& context) {
QueryData users;
QueryData results;
queryKey("HKEY_USERS", users);
for (const auto& rKey : users) {
auto key_type = rKey.find("type");
auto key_path = rKey.find("path");
if (key_type == rKey.end() || key_path == rKey.end()) {
continue;
}
std::string full_path = key_path->second + kShellBagPath;
size_t sid_start = full_path.find("S-");
if (sid_start == std::string::npos) {
continue;
}
size_t sid_end = full_path.find("_", sid_start);
if (sid_end == std::string::npos) {
sid_end = full_path.find("\\", sid_start);
}
std::string sid = full_path.substr(sid_start, sid_end - sid_start);
// Shellbags may exist in both SID_Classes (UsrClass.dat) and SID
// (NTUSER.dat) Keys but the paths are different
if (full_path.find("_Classes") == std::string::npos) {
full_path = key_path->second + kShellBagPathNtuser;
std::string source = "ntuser.dat";
parseRegistry(full_path, sid, results, source);
continue;
}
std::string source = "usrclass.dat";
parseRegistry(full_path, sid, results, source);
}
return results;
}
} // namespace tables
} // namespace osquery

View File

@ -45,6 +45,11 @@ function(generateOsqueryUtils)
darwin/plist.mm
)
endif()
if(DEFINED PLATFORM_WINDOWS)
list(APPEND source_files
windows/shellitem.cpp
)
endif()
add_osquery_library(osquery_utils EXCLUDE_FROM_ALL
${source_files}
@ -86,6 +91,14 @@ function(generateOsqueryUtils)
generateIncludeNamespace(osquery_utils "osquery/utils" "FULL_PATH" ${platform_public_header_files})
endif()
if(DEFINED PLATFORM_WINDOWS)
set(platform_public_header_files
windows/shellitem.h
)
generateIncludeNamespace(osquery_utils "osquery/utils" "FULL_PATH" ${platform_public_header_files})
endif()
set(attribute_public_header_files
attribute.h
)
@ -110,6 +123,7 @@ function(generateOsqueryUtilsUtilstestsTest)
list(APPEND source_files
tests/windows/env.cpp
tests/windows/filetime.cpp
tests/windows/shellitems.cpp
)
endif()

View File

@ -129,4 +129,13 @@ LONGLONG cimDatetimeToUnixtime(const std::string& src) {
return filetimeToUnixtime(timeStore);
}
std::string swapEndianess(const std::string& endian_string) {
std::string swap_string = endian_string;
std::reverse(swap_string.begin(), swap_string.end());
for (std::size_t i = 0; i < swap_string.length(); i += 2) {
std::swap(swap_string[i], swap_string[i + 1]);
}
return swap_string;
}
} // namespace osquery

View File

@ -52,4 +52,10 @@ LONGLONG cimDatetimeToUnixtime(const std::string& src);
*/
std::string bstrToString(const BSTR src);
/**
* @brief Windows helper function to swap endianess of a string
*
* @returns The swap endianess (little endian returns big endian, vice-versa)
*/
std::string swapEndianess(const std::string& endian_string);
} // namespace osquery

View File

@ -65,4 +65,11 @@ TEST_F(ConversionsTests, test_wstring_to_string_extended) {
EXPECT_EQ(narrowString, expected);
}
TEST_F(ConversionsTests, test_swapendianiess) {
std::string little_endian{"IJGHEFCDAB"};
auto swapendian = swapEndianess(little_endian);
std::string expected{"ABCDEFGHIJ"};
EXPECT_EQ(swapendian, expected);
}
} // namespace osquery

View File

@ -41,4 +41,11 @@ TEST_F(ConversionsTests, test_long_int_to_unixtime) {
EXPECT_EQ(converted, 1593277666);
}
TEST_F(ConversionsTests, test_fattime_to_unixtime) {
std::string fattime = "24450000";
auto converted = parseFatTime(fattime);
EXPECT_EQ(converted, 1409788800);
}
} // namespace osquery

View File

@ -11,6 +11,8 @@
#include <osquery/utils/conversions/tryto.h>
#include <osquery/utils/conversions/windows/windows_time.h>
#include <string>
#include <time.h>
namespace osquery {
LONGLONG filetimeToUnixtime(const FILETIME& ft) {
@ -56,4 +58,40 @@ LONGLONG littleEndianToUnixTime(const std::string& time_data) {
return last_time;
}
LONGLONG parseFatTime(const std::string& fat_data) {
if (fat_data.length() != 8) {
LOG(WARNING)
<< "Incorrect FAT timestamp format, expecting string length 8, got: "
<< fat_data;
return 0ll;
}
std::string fat_date_data = fat_data.substr(0, 4);
std::string fat_time_data = fat_data.substr(4, 4);
auto fat_date = std::stoi(fat_date_data.substr(2, 2), nullptr, 16) << 8;
fat_date |= std::stoi(fat_date_data.substr(0, 2), nullptr, 16);
// Year is stored as number of years after 1980. Ex: 2020 is stored as 40
int fat_year = ((fat_date & 0xfe00) >> 9) + 1980;
int fat_month = (fat_date & 0x1e0) >> 5;
int fat_day = fat_date & 0x1f;
auto fat_time = std::stoi(fat_time_data.substr(2, 2), nullptr, 16) << 8;
fat_time |= std::stoi(fat_time_data.substr(0, 2), nullptr, 16);
int fat_sec = (fat_time & 0x1f) * 2;
int fat_min = (fat_time & 0x7e0) >> 5;
int fat_hour = (fat_time & 0xf800) >> 11;
struct tm fat_timestamp = {0};
fat_timestamp.tm_year = fat_year - 1900;
fat_timestamp.tm_mon = fat_month - 1;
fat_timestamp.tm_mday = fat_day;
fat_timestamp.tm_hour = fat_hour;
fat_timestamp.tm_min = fat_min;
fat_timestamp.tm_sec = fat_sec;
time_t epoch = _mkgmtime(&fat_timestamp);
return epoch;
}
} // namespace osquery

View File

@ -37,4 +37,12 @@ LONGLONG longIntToUnixtime(LARGE_INTEGER& ft);
*/
LONGLONG littleEndianToUnixTime(const std::string& time_data);
/**
* @brief Windows helper function for parsing and converting FAT time to Unix
* epoch.
*
* @returns The unix epoch timestamp representation of FAT time
*/
LONGLONG parseFatTime(const std::string& dos_data);
} // namespace osquery

View File

@ -0,0 +1,223 @@
/**
* Copyright (c) 2014-present, The osquery authors
*
* This source code is licensed as defined by the LICENSE file found in the
* root directory of this source tree.
*
* SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only)
*/
#include <gtest/gtest.h>
#include <osquery/utils/windows/shellitem.h>
#include <string>
namespace osquery {
class ShellitemTests : public testing::Test {};
TEST_F(ShellitemTests, test_shellitem_fileentry) {
std::string data =
"56003100000000000000000010006F73717565727900400009000400EFBE000000000000"
"00002E00000000000000000000000000000000000000000000000000000000006F007300"
"71007500650072007900000016000000";
auto file_entry = fileEntry(data);
ASSERT_TRUE(file_entry.path == "osquery");
ASSERT_TRUE(file_entry.mft_entry == 0LL);
ASSERT_TRUE(file_entry.dos_created == 0LL);
ASSERT_TRUE(file_entry.dos_modified == 0LL);
ASSERT_TRUE(file_entry.dos_accessed == 0LL);
ASSERT_TRUE(file_entry.mft_sequence == 0LL);
}
TEST_F(ShellitemTests, test_shellitem_ftpserver) {
std::string data =
"560061034C00030100000400000012122B8B7BFBD601FFFFFFFF00000000000000000000"
"000015000000140000007370656564746573742E74656C65322E6E657400040000000000"
"00000400000000000000667470000000";
std::vector<std::string> ftp_data = ftpItem(data);
ASSERT_TRUE(ftp_data[1] == "speedtest.tele2.net");
ASSERT_TRUE(ftp_data[0] == "12122B8B7BFBD601");
}
TEST_F(ShellitemTests, test_shellitem_zipcontent) {
std::string data =
"86007E0043003A0000000000000000000000000000000000000000000000000010000000"
"4E002F0041000000000000000000000000000000010000000000000004E3CB1700000000"
"0100000000000000FFFF000011000000000000004C004900450046002D0030002E003100"
"30002E0031002D00770069006E0036003400000000006D6265720000";
auto name = zipContentItem(data);
ASSERT_TRUE(name == "LIEF-0.10.1-win64");
}
TEST_F(ShellitemTests, test_shellitem_mtpdevice) {
std::string data =
"9C050000960505203110030000001A0020000080AF38060000000000000000000000B602"
"00001800000019000000150000000700000049006E007400650072006E0061006C002000"
"7300680061007200650064002000730074006F0072006100670065000000530049004400"
"2D007B00310030003000300031002C002C00320036003700320030003800320039003400"
"340030007D000000470065006E0065007200690063002000680069006500720061007200"
"630068006900630061006C0000007B00450046003200310030003700440035002D004100"
"3500320041002D0034003200340033002D0041003200360042002D003600320044003400"
"310037003600440037003600300033007D0000007B003400410044003200430038003500"
"45002D0035004500320044002D0034003500450035002D0038003800360034002D003400"
"460032003200390045003300430036004300460030007D0000007B003100410033003300"
"46003700450034002D0041004600310033002D0034003800460035002D00390039003400"
"45002D003700370033003600390044004600450030003400410033007D0000007B003900"
"32003600310042003000330043002D0033004400370038002D0034003500310039002D00"
"38003500450033002D003000320043003500450031004600350030004200420039007D00"
"00007B00360038003000410044004600350032002D0039003500300041002D0034003000"
"340031002D0039004200340031002D003600350045003300390033003600340038003100"
"350035007D0000007B00320038004400380044003300310045002D003200340039004300"
"2D0034003500340045002D0041004100420043002D003300340038003800330031003600"
"380045003600330034007D0000007B00320037004500320045003300390032002D004100"
"3100310031002D0034003800450030002D0041004200300043002D004500310037003700"
"300035004100300035004600380035007D0000000D00000003D5150C17D0CE4790167B3F"
"978721CC0F0000007A05A301D674804EBEA7DC4C212CE50A020000001300000003000000"
"7A05A301D674804EBEA7DC4C212CE50A030000001F0000002A000000470065006E006500"
"7200690063002000680069006500720061007200630068006900630061006C0000007A05"
"A301D674804EBEA7DC4C212CE50A0B00000013000000000000007A05A301D674804EBEA7"
"DC4C212CE50A04000000150000000080AF38060000007A05A301D674804EBEA7DC4C212C"
"E50A05000000150000000070F58B050000007A05A301D674804EBEA7DC4C212CE50A0600"
"00001500000000000040000000007A05A301D674804EBEA7DC4C212CE50A070000001F00"
"00003000000049006E007400650072006E0061006C002000730068006100720065006400"
"2000730074006F00720061006700650000000D496BEFD85C7A43AFFCDA8B60EE4A3C0500"
"00001F000000320000005300490044002D007B00310030003000300031002C002C003200"
"36003700320030003800320039003400340030007D0000000D496BEFD85C7A43AFFCDA8B"
"60EE4A3C040000001F0000003000000049006E007400650072006E0061006C0020007300"
"680061007200650064002000730074006F00720061006700650000007A05A301D674804E"
"BEA7DC4C212CE50A080000001F0000000200000000000D496BEFD85C7A43AFFCDA8B60EE"
"4A3C0600000048000000000001306CAE044898BAC57B46965FE70D496BEFD85C7A43AFFC"
"DA8B60EE4A3C1A0000000B00000000000D496BEFD85C7A43AFFCDA8B60EE4A3C07000000"
"480000006001ED99FF17444C9D981D7A6F941921932D058FCAABC54FA5ACB01DF4DBE598"
"0200000048000000BC5BF023DE152A4CA55BA9AF5CE412EF0D496BEFD85C7A43AFFCDA8B"
"60EE4A3C170000001F0000000E000000730031003000300030003100000000000000";
auto name = mtpDevice(data);
ASSERT_TRUE(name == "Internal shared storage");
}
TEST_F(ShellitemTests, test_shellitem_rootentry) {
std::string data =
"3A001F44471A0359723FA74489C55595FE6B30EE260001002600EFBE100000002A4B9884"
"B387D50168891281D387D501BF5E6881D387D50114000000";
auto name = rootFolderItem(data);
ASSERT_TRUE(name == "59031A47-3F72-44A7-89C5-5595FE6B30EE");
}
TEST_F(ShellitemTests, test_shellitem_driveletterentry) {
std::string data = "19002F433A5C000000000000000000000000000000000000000000";
auto name = driveLetterItem(data);
ASSERT_TRUE(name == "C:\\");
}
TEST_F(ShellitemTests, test_shellitem_mtpfolder) {
std::string data =
"0E030000080306201907FB000000020020000000000000000000000000000000000080FD"
"D223D4F9D40192E3E22711A1E048AB0CE17705A05F85400200000D0000000D0000002700"
"0000730075007000650072007400750078006B0061007200740000007300750070006500"
"72007400750078006B0061007200740000007B0030003000300032004600410042003200"
"2D0030003000300031002D0030003000300031002D0030003000300030002D0030003000"
"30003000300030003000300030003000300030007D0000000D00000003D5150C17D0CE47"
"90167B3F978721CC0C0000000D496BEFD85C7A43AFFCDA8B60EE4A3C020000001F000000"
"0E0000006F00320046004100420032000000ABFDD4FB7D987747B3F9726185A9312B0200"
"00001F0000000200000000000D496BEFD85C7A43AFFCDA8B60EE4A3C1300000007000000"
"A2A702F34B47E5400D496BEFD85C7A43AFFCDA8B60EE4A3C060000004800000000000130"
"6CAE044898BAC57B46965FE70D496BEFD85C7A43AFFCDA8B60EE4A3C0700000048000000"
"92E3E22711A1E048AB0CE17705A05F850D496BEFD85C7A43AFFCDA8B60EE4A3C04000000"
"1F0000001A000000730075007000650072007400750078006B0061007200740000000D49"
"6BEFD85C7A43AFFCDA8B60EE4A3C170000001F0000000E00000073003100300030003000"
"310000000D496BEFD85C7A43AFFCDA8B60EE4A3C050000001F0000004E0000007B003000"
"30003000320046004100420032002D0030003000300031002D0030003000300031002D00"
"30003000300030002D003000300030003000300030003000300030003000300030007D00"
"00000D496BEFD85C7A43AFFCDA8B60EE4A3C1A0000000B000000FFFF5850544DCE4F7845"
"95C88698A9BC0F4903DC00001200000000005850544DCE4F784595C88698A9BC0F494EDC"
"00001F000000200000003200300031003700310031003200370054003000360035003300"
"3300330000000D496BEFD85C7A43AFFCDA8B60EE4A3C0C0000001F0000001A0000007300"
"75007000650072007400750078006B00610072007400000000000000";
auto name = mtpFolder(data);
ASSERT_TRUE(name == "supertuxkart");
}
TEST_F(ShellitemTests, test_shellitem_mtproot) {
std::string data =
"6E012E004801062031080300000000000000030000006E00000001000000090000005200"
"000000004E00650078007500730020003500580000005C005C003F005C00750073006200"
"23007600690064005F00310038006400310026007000690064005F003400650065003100"
"230030003200350061006300650063003600320064003400380063003200340038002300"
"7B00360061006300320037003800370038002D0061003600660061002D00340031003500"
"35002D0062006100380035002D0066003900380066003400390031006400340066003300"
"33007D0000000D00000003D5150C17D0CE4790167B3F978721CC020000009A97D42643E6"
"26469E2B736DC0C92FDC0C0000001F000000120000004E00650078007500730020003500"
"58000000932D058FCAABC54FA5ACB01DF4DBE59802000000480000006B46EA08A4E33643"
"A1F3A44D2B5C438C0000741A595E96DFD3488D671733BCEE28BA3C6D783575B0B94988DD"
"029876E11C010000";
auto name = mtpRoot(data);
ASSERT_TRUE(name == "Nexus 5X");
}
TEST_F(ShellitemTests, test_shellitem_controlpanelcategoryitem) {
std::string data = "0C0001008421DE39050000000000";
auto name = controlPanelCategoryItem(data);
ASSERT_TRUE(name == "System and Security");
}
TEST_F(ShellitemTests, test_shellitem_controlpanelitem) {
std::string data =
"1E007180000000000000000000006ABE817B2BCE7646A29EEB907A5126C50000";
auto name = controlPanelItem(data);
ASSERT_TRUE(name == "7B81BE6A-CE2B-4676-A29E-EB907A5126C5");
}
TEST_F(ShellitemTests, test_shellitem_variableftp) {
std::string data =
"3E0000000000050003001000000000700A00000000000018389483FBD601550700000000"
"000075706C6F61640000750070006C006F0061006400000000000000";
auto name = variableFtp(data);
ASSERT_TRUE(name == "upload");
}
TEST_F(ShellitemTests, test_shellitem_variableguid) {
std::string data =
"200000001A00EEBBFE23000010003ACCBFB42CDB4C42B0297FE99A87C64100000000";
auto name = variableGuid(data);
ASSERT_TRUE(name == "B4BFCC3A-DB2C-424C-B029-7FE99A87C641");
}
TEST_F(ShellitemTests, test_shellitem_propertyviewdrive) {
std::string data =
"55001F002F0010B7A6F519002F443A5C0000000000000000000000000000000000000000"
"0000000000000000000000000000000000741A595E96DFD3488D671733BCEE28BA772CFB"
"F52F0E164AA3813E560C68BC830000";
auto name = propertyViewDrive(data);
ASSERT_TRUE(name == "D:\\");
}
TEST_F(ShellitemTests, test_shellitem_propertystore) {
std::string data =
"100100000A01BBAF933BFC000400000000002D000000315350537343E50ABE43AD4F85E4"
"69DC8633986E110000000B000000000B000000FFFF000000000000450000003153505330"
"F125B7EF471A10A5F102608C9EEBAC290000000A000000001F0000000C00000076006D00"
"77006100720065002D0068006F00730074000000000000005900000031535053A66A6328"
"3D95D211B5D600C04FD918D03D0000001F000000001F0000001600000056004D00770061"
"00720065002000530068006100720065006400200046006F006C00640065007200730000"
"00000000002D000000315350533AA4BDDEB337834391E74498DA2995AB11000000030000"
"00001300000000000000000000000000000000000000";
std::vector<size_t> wps_list;
size_t wps = data.find("31535053");
while (wps != std::string::npos) {
wps_list.push_back(wps);
wps = data.find("31535053", wps + 1);
}
auto name = propertyStore(data, wps_list);
ASSERT_TRUE(name == "vmware-host");
}
TEST_F(ShellitemTests, test_shellitem_networkshare) {
std::string data =
"3A00C301815C5C766D776172652D686F73745C53686172656420466F6C6465727300564D"
"776172652053686172656420466F6C64657273003F000000";
auto name = networkShareItem(data);
ASSERT_TRUE(name == "\\\\vmware-host\\Shared Folders");
}
} // namespace osquery

View File

@ -0,0 +1,442 @@
/**
* Copyright (c) 2014-present, The osquery authors
*
* This source code is licensed as defined by the LICENSE file found in the
* root directory of this source tree.
*
* SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only)
*/
#include <osquery/logger/logger.h>
#include <osquery/utils/conversions/windows/strings.h>
#include <osquery/utils/conversions/windows/windows_time.h>
#include <boost/algorithm/hex.hpp>
#include <boost/algorithm/string.hpp>
#include <string>
#include <vector>
struct ShellFileEntryData {
std::string path;
long long dos_created;
long long dos_accessed;
long long dos_modified;
int version;
std::string extension_sig;
std::string identifier;
long long mft_entry;
int mft_sequence;
int string_size;
};
const std::string kNetworkShareIds[6] = {"41", "42", "46", "47", "4C", "C3"};
// Property set GUIDs associated with name entries
const std::string kPropertySets[15] = {"000214A1-0000-0000-C000-000000000046",
"01A3057A-74D6-4E80-BEA7-DC4C212CE50A",
"46588AE2-4CBC-4338-BBFC-139326986DCE",
"4D545058-4FCE-4578-95C8-8698A9BC0F49",
"56A3372E-CE9C-11D2-9F0E-006097C686F6",
"6444048F-4C8B-11D1-8B70-080036B11A03",
"64440490-4C8B-11D1-8B70-080036B11A03",
"64440491-4C8B-11D1-8B70-080036B11A03",
"64440492-4C8B-11D1-8B70-080036B11A03",
"8F052D93-ABCA-4FC5-A5AC-B01DF4DBE598",
"B725F130-47EF-101A-A5F1-02608C9EEBAC",
"D5CDD502-2E9C-101B-9397-08002B2CF9AE",
"D5CDD505-2E9C-101B-9397-08002B2CF9AE",
"EF6B490D-5CD8-437A-AFFC-DA8B60EE4A3C",
"F29F85E0-4FF9-1068-AB91-08002B27B3D9"};
namespace osquery {
std::string guidParse(const std::string& guid_little) {
std::vector<std::string> guids;
guids.push_back(guid_little.substr(0, 8));
guids.push_back(guid_little.substr(8, 4));
guids.push_back(guid_little.substr(12, 4));
std::string guid_4 = guid_little.substr(16, 4);
std::string guid_5 = guid_little.substr(20, 12);
// The first 16 GUID characters are in litte endian format
for (auto& guid : guids) {
std::reverse(guid.begin(), guid.end());
for (std::size_t i = 0; i < guid.length(); i += 2) {
std::swap(guid[i], guid[i + 1]);
}
}
std::string guid_string =
guids[0] + "-" + guids[1] + "-" + guids[2] + "-" + guid_4 + "-" + guid_5;
return guid_string;
}
ShellFileEntryData fileEntry(const std::string& shell_data) {
size_t offset;
std::string extension_sig;
size_t entry_offset = 0;
// Find "0400EFBE" offset
if (shell_data.find("0400EFBE") != std::string::npos) {
offset = shell_data.find("0400EFBE");
extension_sig = shell_data.substr(offset, 8);
entry_offset = offset - 8;
}
ShellFileEntryData file_entry;
if (entry_offset <= 0) {
LOG(WARNING)
<< "Could not find supported file entry extension in shell data: "
<< shell_data;
file_entry.path = "[UNSUPPORTED SHELL EXTENSION]";
return file_entry;
}
std::string version = shell_data.substr(entry_offset + 4, 4);
version = swapEndianess(version);
file_entry.version = std::stoi(version, nullptr, 16);
if (file_entry.version < 7) {
LOG(WARNING) << "Shellitem format unsupported. Expecting version 7 or "
"higher: "
<< shell_data;
file_entry.path = "[UNSUPPORTED SHELL EXTENSION]";
return file_entry;
}
file_entry.extension_sig = shell_data.substr(entry_offset + 8, 8);
// Shell data may contain Users Files folder signature, modified time is at
// offset 0x18
std::string timestamp = "";
if (shell_data.find("43465346") != std::string::npos) {
timestamp = shell_data.substr(36, 8);
file_entry.dos_modified =
(timestamp == "00000000") ? 0LL : parseFatTime(timestamp);
} else {
timestamp = shell_data.substr(16, 8);
file_entry.dos_modified =
(timestamp == "00000000") ? 0LL : parseFatTime(timestamp);
}
timestamp = shell_data.substr(entry_offset + 16, 8);
file_entry.dos_created =
(timestamp == "00000000") ? 0LL : parseFatTime(timestamp);
timestamp = shell_data.substr(entry_offset + 24, 8);
file_entry.dos_accessed =
(timestamp == "00000000") ? 0LL : parseFatTime(timestamp);
file_entry.identifier = shell_data.substr(entry_offset + 32, 4);
std::string ntfs_data = shell_data.substr(entry_offset + 40, 16);
std::string mft_entry = ntfs_data.substr(0, 12);
mft_entry = swapEndianess(mft_entry);
if (mft_entry == "000000000000") {
file_entry.mft_entry = 0LL;
} else {
file_entry.mft_entry = std::stoll(mft_entry, nullptr, 16);
}
std::string mft_sequence = ntfs_data.substr(12, 4);
mft_sequence = swapEndianess(mft_sequence);
if (mft_sequence == "0000") {
file_entry.mft_sequence = 0;
} else {
file_entry.mft_sequence = std::stoi(mft_sequence, nullptr, 16);
}
std::string string_size = shell_data.substr(entry_offset + 72, 4);
string_size = swapEndianess(string_size);
file_entry.string_size = std::stoi(string_size, nullptr, 16);
std::string entry_name = shell_data.substr(entry_offset + 92);
// path name ends with 0000 (end of string)
size_t name_end = entry_name.find("0000");
std::string shell_name = entry_name.substr(0, name_end);
// Path is in unicode, extra 00
boost::erase_all(shell_name, "00");
// verify the the hex string length is even. This fixes issues with 10 base
// hex values Example 70006900700000... (pip)
if (shell_name.length() % 2 != 0) {
shell_name += "0";
}
std::string name;
// Convert hex path to readable string
try {
name = boost::algorithm::unhex(shell_name);
} catch (const boost::algorithm::hex_decode_error& /* e */) {
LOG(WARNING) << "Failed to decode ShellItem path hex values to string: "
<< shell_name;
file_entry.path = "[UNSUPPORTED SHELL EXTENSION]";
return file_entry;
}
file_entry.path = name;
return file_entry;
}
// returns property store name or GUID/id if name not found
std::string propertyStore(const std::string& shell_data,
const std::vector<size_t>& wps_list) {
std::string guid_string;
for (const auto& offsets : wps_list) {
std::string guid_little = shell_data.substr(offsets + 8, 32);
guid_string = guidParse(guid_little);
// If GUID property set is found get the property set name
for (const auto& property_list : kPropertySets) {
if (guid_string != property_list) {
continue;
}
std::string name_size = shell_data.substr(offsets + 48, 8);
name_size = swapEndianess(name_size);
int size = std::stoi(name_size, nullptr, 16);
std::string string_hex = shell_data.substr(offsets + 74, (size + 1) * 4);
boost::erase_all(string_hex, "00");
std::string name;
// Convert hex path to readable string
try {
name = boost::algorithm::unhex(string_hex);
} catch (const boost::algorithm::hex_decode_error& /* e */) {
LOG(WARNING)
<< "Failed to decode Windows Property List hex values to string: "
<< shell_data;
return guid_string;
}
return name;
}
}
return guid_string;
}
std::string networkShareItem(const std::string& shell_data) {
for (const auto& net_id : kNetworkShareIds) {
if (net_id == shell_data.substr(4, 2)) {
// Network path ends with "00"
std::string network_path =
shell_data.substr(10, shell_data.find("00", 10) - 10);
std::string name;
try {
name = boost::algorithm::unhex(network_path);
} catch (const boost::algorithm::hex_decode_error& /* e */) {
LOG(WARNING) << "Failed to decode ShellItem path hex values to string: "
<< shell_data;
return "[UNKNOWN NETWORK SHELL ITEM]";
}
return name;
}
}
return "[UNKNOWN NETWORK SHELL ITEM]";
}
std::string zipContentItem(const std::string& shell_data) {
std::string path_size_string = shell_data.substr(168, 4);
path_size_string = swapEndianess(path_size_string);
int path_size = std::stoi(path_size_string, nullptr, 16);
std::string path = shell_data.substr(184, path_size * 4);
// Path is in unicode, extra 00
boost::erase_all(path, "00");
try {
path = boost::algorithm::unhex(path);
} catch (const boost::algorithm::hex_decode_error& /* e */) {
LOG(WARNING) << "Failed to decode ShellItem path hex values to string: "
<< path;
return "[ZIP PATH DECODE ERROR]";
}
// Zip folders can go down a max of two directories
std::string second_path_size_string = shell_data.substr(176, 4);
second_path_size_string = swapEndianess(second_path_size_string);
int second_path_size = std::stoi(second_path_size_string, nullptr, 16);
if (second_path_size != 0) {
path += "/";
std::string second_path =
shell_data.substr((184 + (path_size * 4) + 4), second_path_size * 4);
boost::erase_all(second_path, "00");
try {
second_path = boost::algorithm::unhex(second_path);
path += second_path;
} catch (const boost::algorithm::hex_decode_error& /* e */) {
LOG(WARNING) << "Failed to decode ShellItem path hex values to string: "
<< second_path;
path += "[ZIP PATH DECODE ERROR]";
return path;
}
}
return path;
}
std::string rootFolderItem(const std::string& shell_data) {
std::string guid_little = shell_data.substr(8, 32);
std::string guid_string = guidParse(guid_little);
return guid_string;
}
std::string driveLetterItem(const std::string& shell_data) {
std::string volume;
try {
volume = boost::algorithm::unhex(shell_data.substr(6, 6));
} catch (const boost::algorithm::hex_decode_error& /* e */) {
LOG(WARNING) << "Failed to decode Shellbag hex values to string: "
<< shell_data;
return "[UNKNOWN DRIVE VOLUME]";
}
return volume;
}
std::string controlPanelCategoryItem(const std::string& shell_data) {
std::string panel_id = shell_data.substr(16, 2);
if (panel_id == "00") {
return "All Control Panel Items";
} else if (panel_id == "01") {
return "Appearence and Personalization";
} else if (panel_id == "02") {
return "Hardware and Sound";
} else if (panel_id == "03") {
return "Network and Internet";
} else if (panel_id == "04") {
return "Sound, Speech, and Audio Devices";
} else if (panel_id == "05") {
return "System and Security";
} else if (panel_id == "06") {
return "Clock, Language, and Region";
} else if (panel_id == "07") {
return "Ease of Access";
} else if (panel_id == "08") {
return "Programs";
} else if (panel_id == "09") {
return "User Accounts";
} else if (panel_id == "10") {
return "Security Center";
} else if (panel_id == "11") {
return "Mobile PC";
} else {
LOG(WARNING) << "Unknown panel category: " << shell_data;
return "[UNKNOWN PANEL CATEGORY]";
}
}
std::string controlPanelItem(const std::string& shell_data) {
std::string guid_little = shell_data.substr(28, 32);
std::string guid_string = guidParse(guid_little);
return guid_string;
}
std::vector<std::string> ftpItem(const std::string& shell_data) {
std::vector<std::string> ftp_data;
std::string access_time =
shell_data.substr(28, 16); // shell data contains connection time
ftp_data.push_back(access_time);
// find end of string
size_t offset = shell_data.find("00", 92);
size_t hostname_size = offset - 92;
std::string ftp_hostname = shell_data.substr(92, hostname_size);
std::string name;
try {
name = boost::algorithm::unhex(ftp_hostname);
} catch (const boost::algorithm::hex_decode_error& /* e */) {
LOG(WARNING) << "Failed to decode ShellItem path hex values to string: "
<< shell_data;
ftp_data.push_back("[UNKNOWN NAME]");
}
ftp_data.push_back(name);
return ftp_data;
}
std::string propertyViewDrive(const std::string& shell_data) {
std::string drive_hex = shell_data.substr(26, 6);
std::string name;
try {
name = boost::algorithm::unhex(drive_hex);
} catch (const boost::algorithm::hex_decode_error& /* e */) {
LOG(WARNING) << "Failed to decode ShellItem path hex values to string: "
<< shell_data;
return "[UNKNOWN USER PROPERTY DRIVE NAME]";
}
return name;
}
std::string variableFtp(const std::string& shell_data) {
// Short FTP name starts at string offset 76
if (shell_data.length() < 76) {
LOG(WARNING) << "FTP Variable name smaller than 76 chars: " << shell_data;
return "[UNKNOWN VARIABLE FTP NAME]";
}
std::string name_start = shell_data.substr(76);
// Short name should end with 0000
size_t offset = name_start.find("0000");
if (offset == std::string::npos) {
LOG(WARNING) << "Could not identify Variable FTP name: " << shell_data;
return "[UNKNOWN VARIABLE FTP NAME]";
}
std::string long_name = name_start.substr(offset);
boost::erase_all(long_name, "00");
// Check to make sure name is even, fixes issues with 10 base characters
// Ex: p is 70
if (long_name.length() % 2 != 0) {
long_name += "0";
}
std::string name;
try {
name = boost::algorithm::unhex(long_name);
} catch (const boost::algorithm::hex_decode_error& /* e */) {
LOG(WARNING) << "Failed to decode ShellItem path hex values to string: "
<< shell_data;
return "[UNKNOWN VARIABLE FTP NAME]";
}
return name;
}
std::string variableGuid(const std::string& shell_data) {
std::string guid_little = shell_data.substr(28, 32);
std::string guid_string = guidParse(guid_little);
return guid_string;
}
std::string mtpFolder(const std::string& shell_data) {
std::string name_size = shell_data.substr(124, 8);
name_size = swapEndianess(name_size);
int size = std::stoi(name_size, nullptr, 16);
std::string path_name = shell_data.substr(148, size * 4);
boost::erase_all(path_name, "00");
std::string name;
try {
name = boost::algorithm::unhex(path_name);
} catch (const boost::algorithm::hex_decode_error& /* e */) {
LOG(WARNING) << "Failed to decode ShellItem path hex values to string: "
<< shell_data;
return "[UNKNOWN MTP FOLDER NAME]";
}
return name;
}
std::string mtpDevice(const std::string& shell_data) {
std::string name_size = shell_data.substr(76, 8);
name_size = swapEndianess(name_size);
int size = std::stoi(name_size, nullptr, 16);
std::string path_name = shell_data.substr(108, size * 4);
boost::erase_all(path_name, "00");
std::string name;
try {
name = boost::algorithm::unhex(path_name);
} catch (const boost::algorithm::hex_decode_error& /* e */) {
LOG(WARNING) << "Failed to decode ShellItem path hex values to string: "
<< shell_data;
return "[UNKNOWN MTP DEVICE NAME]";
}
return name;
}
std::string mtpRoot(const std::string& shell_data) {
size_t name_end = shell_data.find("000000", 80);
std::string path_name = shell_data.substr(80, name_end - 80);
boost::erase_all(path_name, "00");
std::string name;
try {
name = boost::algorithm::unhex(path_name);
} catch (const boost::algorithm::hex_decode_error& /* e */) {
LOG(WARNING) << "Failed to decode ShellItem path hex values to string: "
<< shell_data;
return "[UNKNOWN MTP ROOT NAME]";
}
return name;
}
} // namespace osquery

View File

@ -0,0 +1,157 @@
/**
* Copyright (c) 2014-present, The osquery authors
*
* This source code is licensed as defined by the LICENSE file found in the
* root directory of this source tree.
*
* SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only)
*/
#pragma once
#include <osquery/utils/system/system.h>
#include <string>
#include <vector>
struct ShellFileEntryData {
std::string path;
long long dos_created;
long long dos_accessed;
long long dos_modified;
int version;
std::string extension_sig;
std::string identifier;
long long mft_entry;
int mft_sequence;
int string_size;
};
namespace osquery {
/**
* @brief Windows helper function for parsing file entry shell items
* epoch.
*
* @returns The file entry data structure
*/
ShellFileEntryData fileEntry(const std::string& shell_data);
/**
* @brief Windows helper function for parsing Windows Property lists
* epoch.
*
* @returns The Windows Property List GUID name or GUID value
*/
std::string propertyStore(const std::string& shell_data,
const std::vector<size_t>& wps_list);
/**
* @brief Windows helper function for parsing netshare shell items
* epoch.
*
* @returns The network share name
*/
std::string networkShareItem(const std::string& shell_data);
/**
* @brief Windows helper function for parsing zip content shell items
* epoch.
*
* @returns The zip content name
*/
std::string zipContentItem(const std::string& shell_data);
/**
* @brief Windows helper function for parsing root folder shell items
* epoch.
*
* @returns The root folder name
*/
std::string rootFolderItem(const std::string& shell_data);
/**
* @brief Windows helper function for parsing drive letter shell items
* epoch.
*
* @returns The drive name
*/
std::string driveLetterItem(const std::string& shell_data);
/**
* @brief Windows helper function for parsing conrol panel category shell items
* epoch.
*
* @returns The control panel category name
*/
std::string controlPanelCategoryItem(const std::string& shell_data);
/**
* @brief Windows helper function for parsing conrol panel shell items
* epoch.
*
* @returns The control panel name
*/
std::string controlPanelItem(const std::string& shell_data);
/**
* @brief Windows helper function for parsing ftp shell items
* epoch.
*
* @returns The ftp hostname
*/
std::vector<std::string> ftpItem(const std::string& shell_data);
/**
* @brief Windows helper function for parsing little endian guid data
* epoch.
*
* @returns GUID string in the proper order
*/
std::string guidParse(const std::string& guid_little);
/**
* @brief Windows helper function for parsing user property drive data
* epoch.
*
* @returns The drive name
*/
std::string propertyViewDrive(const std::string& shell_data);
/**
* @brief Windows helper function for parsing user variable GUID data
* epoch.
*
* @returns The GUID name or GUID
*/
std::string variableGuid(const std::string& shell_data);
/**
* @brief Windows helper function for parsing variable FTP data
* epoch.
*
* @returns The ftp string
*/
std::string variableFtp(const std::string& shell_data);
/**
* @brief Windows helper function for parsing MTP device data
* epoch.
*
* @returns The MTP device name
*/
std::string mtpDevice(const std::string& shell_data);
/**
* @brief Windows helper function for parsing MTP folder name data
* epoch.
*
* @returns The MTP folder name
*/
std::string mtpFolder(const std::string& shell_data);
/**
* @brief Windows helper function for parsing MTP root name data
* epoch.
*
* @returns The MTP root name
*/
std::string mtpRoot(const std::string& shell_data);
} // namespace osquery

View File

@ -300,6 +300,7 @@ function(generateNativeTables)
"windows/dns_cache.table:windows"
"windows/hvci_status.table:windows"
"windows/shimcache.table:windows"
"windows/shellbags.table:windows"
"yara/yara_events.table:linux,macos"
"yara/yara.table:linux,macos,freebsd,windows"
)

View File

@ -0,0 +1,16 @@
table_name("shellbags")
description("Shows directories accessed via Windows Explorer.")
schema([
Column("sid", TEXT, "User SID"),
Column("source", TEXT, "Shellbags source Registry file"),
Column("path", TEXT, "Directory name."),
Column("modified_time", INTEGER, "Directory Modified time."),
Column("created_time", INTEGER, "Directory Created time."),
Column("accessed_time", INTEGER, "Directory Accessed time."),
Column("mft_entry", BIGINT, "Directory master file table entry."),
Column("mft_sequence", INTEGER, "Directory master file table sequence."),
])
implementation("shellbags@genShellbags")
examples([
"select * from shellbags;",
])

View File

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

View File

@ -0,0 +1,44 @@
/**
* Copyright (c) 2014-present, The osquery authors
*
* This source code is licensed as defined by the LICENSE file found in the
* root directory of this source tree.
*
* SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only)
*/
#include <osquery/tests/integration/tables/helper.h>
namespace osquery {
namespace table_tests {
class ShellbagsTest : public testing::Test {
protected:
void SetUp() override {
setUpEnvironment();
}
};
TEST_F(ShellbagsTest, test_sanity) {
QueryData const rows = execute_query("select * from shellbags");
if (!rows.empty()) {
QueryData const specific_query_rows =
execute_query("select * from shellbags where path like '%This PC%'");
ASSERT_GT(rows.size(), 0ul);
ASSERT_GT(specific_query_rows.size(), 0ul);
ValidationMap row_map = {
{"sid", NonEmptyString},
{"source", NonEmptyString},
{"path", NonEmptyString},
{"modified_time", NormalType},
{"created_time", NormalType},
{"accessed_time", NormalType},
{"mft_entry", NormalType},
{"mft_sequence", NormalType},
};
validate_rows(rows, row_map);
validate_rows(specific_query_rows, row_map);
}
}
} // namespace table_tests
} // namespace osquery