diff --git a/osquery/core/tables.cpp b/osquery/core/tables.cpp index 69c12bdfb19..4e6f2d048d1 100644 --- a/osquery/core/tables.cpp +++ b/osquery/core/tables.cpp @@ -561,14 +561,20 @@ Status QueryContext::expandConstraints( ConstraintOperator op, std::set& output, std::function& output)> predicate) { - for (const auto& constraint : constraints[column].getAll(op)) { + std::set& output)> predicate) const { + auto constraint_it = constraints.find(column); + + if (constraint_it == constraints.end()) { + return Status::success(); + } + + for (const auto& constraint : constraint_it->second.getAll(op)) { auto status = predicate(constraint, output); if (!status) { return status; } } - return Status(0); + return Status::success(); } Status deserializeQueryContextJSON(const JSON& json_helper, diff --git a/osquery/core/tables.h b/osquery/core/tables.h index 2323cece3c5..723533d8a85 100644 --- a/osquery/core/tables.h +++ b/osquery/core/tables.h @@ -567,7 +567,7 @@ struct QueryContext { ConstraintOperator op, std::set& output, std::function& output)> predicate); + std::set& output)> predicate) const; /// Check if the given column is used by the query bool isColumnUsed(const std::string& colName) const; diff --git a/osquery/tables/utility/file.cpp b/osquery/tables/utility/file.cpp index 047695ab637..5901b6e8bbd 100644 --- a/osquery/tables/utility/file.cpp +++ b/osquery/tables/utility/file.cpp @@ -9,6 +9,13 @@ #if !defined(WIN32) #include +#else +#include + +#include +#include + +#include #endif #include @@ -16,6 +23,7 @@ #include #include #include +#include #include #include @@ -25,8 +33,161 @@ namespace osquery { namespace tables { -#if !defined(WIN32) +namespace { +#ifdef WIN32 + +/* These are the number of bytes to read from the ShellLinkHeader structure + at the start of a ShellLink file for the "HeaderSize" field */ +constexpr std::uint32_t kShellLinkHeaderSizeFieldSize = 4; + +// This is the expected value of the "HeaderSize" field +constexpr std::uint32_t kShellLinkHeaderSizeExpectedValue = 0x4C; + +struct LnkData { + std::string target_path; + std::string target_type; + std::string target_location; + std::string start_in; + std::string run; + std::string comment; +}; + +std::string showCmdToString(int show_cmd) { + switch (show_cmd) { + case SW_SHOWNORMAL: { + return "Normal window"; + } + case SW_SHOWMAXIMIZED: { + return "Maximized"; + } + case SW_SHOWMINIMIZED: { + return "Minimized"; + } + default: { + return "Unknown"; + } + } +} + +boost::optional parseLnkData(const fs::path& link) { + IShellLink* shell_link; + auto hres = CoCreateInstance(CLSID_ShellLink, + nullptr, + CLSCTX_INPROC_SERVER, + IID_IShellLink, + reinterpret_cast(&shell_link)); + if (FAILED(hres)) { + TLOG << "Failed to create an instance of a shell link, error: " + << shell_link; + return boost::none; + } + + auto shell_link_release = + scope_guard::create([shell_link]() { shell_link->Release(); }); + + IPersistFile* file; + hres = shell_link->QueryInterface(IID_IPersistFile, + reinterpret_cast(&file)); + + if (FAILED(hres)) { + TLOG << "Failed to create an instance of a shell link, error: " + << shell_link; + return boost::none; + } + + auto file_link_release = scope_guard::create([file]() { file->Release(); }); + + hres = file->Load(link.c_str(), STGM_READ); + + if (FAILED(hres)) { + // Not a shell link + return boost::none; + } + + /* Empty files are still able to be loaded via the ShellLink COM interface, + but they are not ShellLink files, so verify that the file + contains a header of a certain size */ + std::string header_size_field_bytes; + auto status = + readFile(link, header_size_field_bytes, kShellLinkHeaderSizeFieldSize); + + if (!status.ok() || + header_size_field_bytes.size() != kShellLinkHeaderSizeFieldSize) { + return boost::none; + } + + std::uint32_t header_size_field_value; + std::memcpy(&header_size_field_value, + header_size_field_bytes.data(), + kShellLinkHeaderSizeFieldSize); + + if (header_size_field_value != kShellLinkHeaderSizeExpectedValue) { + return boost::none; + } + constexpr auto max_chars = INFOTIPSIZE > MAX_PATH ? INFOTIPSIZE : MAX_PATH; + WCHAR buffer[max_chars + 1]{}; + + WIN32_FIND_DATA target_data{}; + hres = shell_link->GetPath(&buffer[0], max_chars, &target_data, 0); + + if (FAILED(hres)) { + return boost::none; + } + + SHFILEINFO file_info{}; + auto res = SHGetFileInfoW(buffer, + target_data.dwFileAttributes, + &file_info, + sizeof(file_info), + SHGFI_TYPENAME | SHGFI_USEFILEATTRIBUTES); + + if (res == 0) { + return boost::none; + } + + LnkData link_data; + + link_data.target_path = wstringToString(buffer); + + auto type_name_length = + wcsnlen(file_info.szTypeName, ARRAYSIZE(file_info.szTypeName)); + link_data.target_type = + wstringToString(std::wstring(file_info.szTypeName, type_name_length)); + + fs::path target_path = link_data.target_path; + link_data.target_location = target_path.parent_path().filename().string(); + + std::memset(buffer, 0, sizeof(buffer)); + hres = shell_link->GetWorkingDirectory(buffer, max_chars); + + if (FAILED(hres)) { + return boost::none; + } + + link_data.start_in = wstringToString(buffer); + + std::memset(buffer, 0, sizeof(buffer)); + hres = shell_link->GetDescription(buffer, max_chars); + + if (FAILED(hres)) { + return boost::none; + } + + link_data.comment = wstringToString(buffer); + + int show_cmd = 0; + hres = shell_link->GetShowCmd(&show_cmd); + + if (FAILED(hres)) { + return boost::none; + } + + link_data.run = showCmdToString(show_cmd); + + return link_data; +} +#else const std::map kTypeNames{ {fs::regular_file, "regular"}, {fs::directory_file, "directory"}, @@ -38,23 +199,181 @@ const std::map kTypeNames{ {fs::type_unknown, "unknown"}, {fs::status_error, "error"}, }; - #endif -void genFileInfo(const fs::path& path, - const fs::path& parent, - const std::string& pattern, - QueryData& results) { +std::set getPathsFromConstraints(const QueryContext& context) { + auto constraint_it = context.constraints.find("path"); + + if (constraint_it == context.constraints.end()) { + return {}; + } + + auto paths = constraint_it->second.getAll(EQUALS); + context.expandConstraints( + "path", + LIKE, + paths, + ([&](const std::string& pattern, std::set& out) { + std::vector patterns; + auto status = + resolveFilePattern(pattern, patterns, GLOB_ALL | GLOB_NO_CANON); + if (status.ok()) { + for (const auto& resolved : patterns) { + out.insert(resolved); + } + } + return status; + })); + + return paths; +} + +std::set getDirsFromConstraints(const QueryContext& context) { + auto constraint_it = context.constraints.find("directory"); + + if (constraint_it == context.constraints.end()) { + return {}; + } + + auto directories = constraint_it->second.getAll(EQUALS); + context.expandConstraints( + "directory", + LIKE, + directories, + ([&](const std::string& pattern, std::set& out) { + std::vector patterns; + auto status = + resolveFilePattern(pattern, patterns, GLOB_FOLDERS | GLOB_NO_CANON); + if (status.ok()) { + for (const auto& resolved : patterns) { + out.insert(resolved); + } + } + return status; + })); + + return directories; +} + +} // namespace + +#ifdef WIN32 +void genFileInfoWindows(const fs::path& path, + const fs::path& parent, + const std::string& pattern, + bool get_shortcut_data, + QueryData& results) { // Must provide the path, filename, directory separate from boost path->string // helpers to match any explicit (query-parsed) predicate constraints. - Row r; r["path"] = path.string(); r["filename"] = path.filename().string(); r["directory"] = parent.string(); r["symlink"] = "0"; -#if !defined(WIN32) + WINDOWS_STAT file_stat; + + auto rtn = platformStat(path, &file_stat); + if (!rtn.ok()) { + VLOG(1) << "PlatformStat failed with " << rtn.getMessage(); + return; + } + + r["symlink"] = INTEGER(file_stat.symlink); + r["inode"] = BIGINT(file_stat.inode); + r["uid"] = BIGINT(file_stat.uid); + r["gid"] = BIGINT(file_stat.gid); + r["mode"] = SQL_TEXT(file_stat.mode); + r["device"] = BIGINT(file_stat.device); + r["size"] = BIGINT(file_stat.size); + r["block_size"] = INTEGER(file_stat.block_size); + r["hard_links"] = INTEGER(file_stat.hard_links); + r["atime"] = BIGINT(file_stat.atime); + r["mtime"] = BIGINT(file_stat.mtime); + r["ctime"] = BIGINT(file_stat.ctime); + r["btime"] = BIGINT(file_stat.btime); + r["type"] = SQL_TEXT(file_stat.type); + r["attributes"] = SQL_TEXT(file_stat.attributes); + r["file_id"] = SQL_TEXT(file_stat.file_id); + r["volume_serial"] = SQL_TEXT(file_stat.volume_serial); + r["product_version"] = SQL_TEXT(file_stat.product_version); + r["file_version"] = SQL_TEXT(file_stat.file_version); + r["original_filename"] = SQL_TEXT(file_stat.original_filename); + + if (get_shortcut_data) { + auto opt_link_data = parseLnkData(path); + + if (opt_link_data.has_value()) { + const auto& link_data = *opt_link_data; + r["shortcut_target_path"] = link_data.target_path; + r["shortcut_target_type"] = link_data.target_type; + r["shortcut_target_location"] = link_data.target_location; + r["shortcut_start_in"] = link_data.start_in; + r["shortcut_run"] = link_data.run; + r["shortcut_comment"] = link_data.comment; + } + } + + results.push_back(r); +} + +QueryData genFileWindows(QueryContext& context, Logger& logger) { + QueryData results; + + // Resolve file paths for EQUALS and LIKE operations. + auto paths = getPathsFromConstraints(context); + + // Only get shortcut data if actually requested + bool get_shortcut_data = context.isAnyColumnUsed({"shortcut_target_path", + "shortcut_target_type", + "shortcut_target_location", + "shortcut_start_in", + "shortcut_run", + "shortcut_comment"}); + + // Iterate through each of the resolved/supplied paths. + for (const auto& path_string : paths) { + fs::path path = path_string; + genFileInfoWindows( + path, path.parent_path(), "", get_shortcut_data, results); + } + + // Resolve directories for EQUALS and LIKE operations. + auto directories = getDirsFromConstraints(context); + + // Now loop through constraints using the directory column constraint. + for (const auto& directory_string : directories) { + if (!isReadable(directory_string) || !isDirectory(directory_string)) { + continue; + } + + try { + // Iterate over the directory and generate info for each regular file. + fs::directory_iterator begin(directory_string), end; + for (; begin != end; ++begin) { + genFileInfoWindows(begin->path(), directory_string, "", false, results); + } + } catch (const fs::filesystem_error& /* e */) { + continue; + } + } + + return results; +} + +#else + +void genFileInfoPosix(const fs::path& path, + const fs::path& parent, + const std::string& pattern, + QueryData& results) { + // Must provide the path, filename, directory separate from boost path->string + // helpers to match any explicit (query-parsed) predicate constraints. + Row r; + r["path"] = path.string(); + r["filename"] = path.filename().string(); + r["directory"] = parent.string(); + r["symlink"] = "0"; struct stat file_stat; @@ -113,86 +432,23 @@ void genFileInfo(const fs::path& path, r["bsd_flags"] = bsd_file_flags_description; #endif -#else - - WINDOWS_STAT file_stat; - - auto rtn = platformStat(path, &file_stat); - if (!rtn.ok()) { - VLOG(1) << "PlatformStat failed with " << rtn.getMessage(); - return; - } - - r["symlink"] = INTEGER(file_stat.symlink); - r["inode"] = BIGINT(file_stat.inode); - r["uid"] = BIGINT(file_stat.uid); - r["gid"] = BIGINT(file_stat.gid); - r["mode"] = SQL_TEXT(file_stat.mode); - r["device"] = BIGINT(file_stat.device); - r["size"] = BIGINT(file_stat.size); - r["block_size"] = INTEGER(file_stat.block_size); - r["hard_links"] = INTEGER(file_stat.hard_links); - r["atime"] = BIGINT(file_stat.atime); - r["mtime"] = BIGINT(file_stat.mtime); - r["ctime"] = BIGINT(file_stat.ctime); - r["btime"] = BIGINT(file_stat.btime); - r["type"] = SQL_TEXT(file_stat.type); - r["attributes"] = SQL_TEXT(file_stat.attributes); - r["file_id"] = SQL_TEXT(file_stat.file_id); - r["volume_serial"] = SQL_TEXT(file_stat.volume_serial); - r["product_version"] = SQL_TEXT(file_stat.product_version); - r["file_version"] = SQL_TEXT(file_stat.file_version); - r["original_filename"] = SQL_TEXT(file_stat.original_filename); - -#endif - results.push_back(r); } -QueryData genFileImpl(QueryContext& context, Logger& logger) { +QueryData genFilePosix(QueryContext& context, Logger& logger) { QueryData results; // Resolve file paths for EQUALS and LIKE operations. - auto paths = context.constraints["path"].getAll(EQUALS); - context.expandConstraints( - "path", - LIKE, - paths, - ([&](const std::string& pattern, std::set& out) { - std::vector patterns; - auto status = - resolveFilePattern(pattern, patterns, GLOB_ALL | GLOB_NO_CANON); - if (status.ok()) { - for (const auto& resolved : patterns) { - out.insert(resolved); - } - } - return status; - })); + auto paths = getPathsFromConstraints(context); // Iterate through each of the resolved/supplied paths. for (const auto& path_string : paths) { fs::path path = path_string; - genFileInfo(path, path.parent_path(), "", results); + genFileInfoPosix(path, path.parent_path(), "", results); } // Resolve directories for EQUALS and LIKE operations. - auto directories = context.constraints["directory"].getAll(EQUALS); - context.expandConstraints( - "directory", - LIKE, - directories, - ([&](const std::string& pattern, std::set& out) { - std::vector patterns; - auto status = - resolveFilePattern(pattern, patterns, GLOB_FOLDERS | GLOB_NO_CANON); - if (status.ok()) { - for (const auto& resolved : patterns) { - out.insert(resolved); - } - } - return status; - })); + auto directories = getDirsFromConstraints(context); // Now loop through constraints using the directory column constraint. for (const auto& directory_string : directories) { @@ -204,7 +460,7 @@ QueryData genFileImpl(QueryContext& context, Logger& logger) { // Iterate over the directory and generate info for each regular file. fs::directory_iterator begin(directory_string), end; for (; begin != end; ++begin) { - genFileInfo(begin->path(), directory_string, "", results); + genFileInfoPosix(begin->path(), directory_string, "", results); } } catch (const fs::filesystem_error& /* e */) { continue; @@ -213,6 +469,15 @@ QueryData genFileImpl(QueryContext& context, Logger& logger) { return results; } +#endif + +QueryData genFileImpl(QueryContext& context, Logger& logger) { +#ifdef WIN32 + return genFileWindows(context, logger); +#else + return genFilePosix(context, logger); +#endif +} QueryData genFile(QueryContext& context) { if (hasNamespaceConstraint(context)) { diff --git a/osquery/utils/CMakeLists.txt b/osquery/utils/CMakeLists.txt index 5d799b89810..29c6f1f2944 100644 --- a/osquery/utils/CMakeLists.txt +++ b/osquery/utils/CMakeLists.txt @@ -53,7 +53,6 @@ function(generateOsqueryUtils) list(APPEND source_files windows/shellitem.cpp windows/lzxpress.cpp - windows/shelllnk.cpp ) endif() @@ -101,7 +100,6 @@ function(generateOsqueryUtils) set(platform_public_header_files windows/shellitem.h windows/lzxpress.h - windows/shelllnk.h ) generateIncludeNamespace(osquery_utils "osquery/utils" "FULL_PATH" ${platform_public_header_files}) @@ -153,7 +151,6 @@ function(generateOsqueryUtilsUtilstestsTest) tests/windows/env.cpp tests/windows/filetime.cpp tests/windows/shellitems.cpp - tests/windows/shelllnk.cpp ) endif() diff --git a/osquery/utils/tests/windows/shelllnk.cpp b/osquery/utils/tests/windows/shelllnk.cpp deleted file mode 100644 index 0050d7c1d61..00000000000 --- a/osquery/utils/tests/windows/shelllnk.cpp +++ /dev/null @@ -1,176 +0,0 @@ -/** - * 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 -#include - -#include - -namespace osquery { -class ShelllnkTests : public testing::Test {}; - -TEST_F(ShelllnkTests, test_shelllnk_header) { - std::string data = - "4C0000000114020000000000C0000000000000469B00200020000000B08E934EEF12D701" - "B08E934EEF12D701B08E934EEF12D7010000000000000000010000000000000000000000" - "00000000110314001F50E04FD020EA3A6910A2D808002B30309D19002F433A5C00000000" - "0000000000000000000000000000007800310000000000554FC0BC110055736572730064" - "0009000400EFBE734EAC24554FC0BC2E0000001E9201000000060000000000000000003A" - "00000000003B3FF40055007300650072007300000040007300680065006C006C00330032" - "002E0064006C006C002C002D0032003100380031003300000014004A003100000000004D" - "5257341000626F6200380009000400EFBE554F3D104D5257342E00000087260300000005" - "00000000000000000000000000000097AA840062006F006200000012007E003100000000" - "006552392911004465736B746F7000680009000400EFBE554FAD09655239292E00000065" - "2603000000070000000000000000003E00000000006B716A004400650073006B0074006F" - "007000000040007300680065006C006C00330032002E0064006C006C002C002D00320031" - "00370036003900000016004E003100000000006752D30910006E61766900003A00090004" - "00EFBE6752D3096752D3092E0000006FAF00000000990000000000000000000000000000" - "00CA220B006E00610076006900000014004A003100000000006752D70910006865790038" - "0009000400EFBE6752D7096752D7092E00000071AF000000007E00000000000000000000" - "000000000043EE84006800650079000000120054003100000000006752DA0910006C6973" - "74656E00003E0009000400EFBE6752DA096752DA092E00000074AF000000007000000000" - "000000000000000000000022D14D006C0069007300740065006E00000016004A00310000" - "0000006752E00910006C6E6B00380009000400EFBE6752E0096752E0092E00000076AF00" - "0000006D000000000000000000000000000000A249F0006C006E006B00000012006C0032" - "00000000006752E3092000534156455F487E312E5458540000500009000400EFBE6752E3" - "096752E3092E0000007DAF000000007200000000000000000000000000000050BA440073" - "006100760065005F0068007900720075006C0065002E0074007800740000001C00000067" - "0000001C000000010000001C0000002D000000000000006600000011000000030000006F" - "129DD41000000000433A5C55736572735C626F625C4465736B746F705C6E6176695C6865" - "795C6C697374656E5C6C6E6B5C736176655F687972756C652E74787400003A002E002E00" - "5C002E002E005C002E002E005C002E002E005C002E002E005C004400650073006B007400" - "6F0070005C006E006100760069005C006800650079005C006C0069007300740065006E00" - "5C006C006E006B005C0073006100760065005F0068007900720075006C0065002E007400" - "78007400280043003A005C00550073006500720073005C0062006F0062005C0044006500" - "73006B0074006F0070005C006E006100760069005C006800650079005C006C0069007300" - "740065006E005C006C006E006B0060000000030000A058000000000000006465736B746F" - "702D6569733933386E0068458D3E11E418498F7897CD6CB340C5AC94C210D67EEB11A0F6" - "0800276EB45E68458D3E11E418498F7897CD6CB340C5AC94C210D67EEB11A0F60800276E" - "B45E45000000090000A03900000031535053B1166D44AD8D7048A748402EA43D788C1D00" - "0000680000000048000000902F5408000000000000501F00000000000000000000000000" - "000000"; - auto header_data = parseShortcutHeader(data); - ASSERT_TRUE(header_data.modified_time == 1615079705); - ASSERT_TRUE(header_data.creation_time == 1615079705); - ASSERT_TRUE(header_data.access_time == 1615079705); - ASSERT_TRUE(header_data.file_size == 0); -} - -TEST_F(ShelllnkTests, test_shelllnk_target_info) { - std::string data = - "110314001F50E04FD020EA3A6910A2D808002B30309D19002F433A5C0000000000000000" - "00000000000000000000007800310000000000554FC0BC11005573657273006400090004" - "00EFBE734EAC24554FC0BC2E0000001E9201000000060000000000000000003A00000000" - "003B3FF40055007300650072007300000040007300680065006C006C00330032002E0064" - "006C006C002C002D0032003100380031003300000014004A003100000000004D52573410" - "00626F6200380009000400EFBE554F3D104D5257342E0000008726030000000500000000" - "000000000000000000000097AA840062006F006200000012007E00310000000000655239" - "2911004465736B746F7000680009000400EFBE554FAD09655239292E0000006526030000" - "00070000000000000000003E00000000006B716A004400650073006B0074006F00700000" - "0040007300680065006C006C00330032002E0064006C006C002C002D0032003100370036" - "003900000016004E003100000000006752D30910006E61766900003A0009000400EFBE67" - "52D3096752D3092E0000006FAF0000000099000000000000000000000000000000CA220B" - "006E00610076006900000014004A003100000000006752D7091000686579003800090004" - "00EFBE6752D7096752D7092E00000071AF000000007E0000000000000000000000000000" - "0043EE84006800650079000000120054003100000000006752DA0910006C697374656E00" - "003E0009000400EFBE6752DA096752DA092E00000074AF00000000700000000000000000" - "0000000000000022D14D006C0069007300740065006E00000016004A0031000000000067" - "52E00910006C6E6B00380009000400EFBE6752E0096752E0092E00000076AF000000006D" - "000000000000000000000000000000A249F0006C006E006B00000012006C003200000000" - "006752E3092000534156455F487E312E5458540000500009000400EFBE6752E3096752E3" - "092E0000007DAF000000007200000000000000000000000000000050BA44007300610076" - "0065005F0068007900720075006C0065002E0074007800740000001C000000670000001C" - "000000010000001C0000002D000000000000006600000011000000030000006F129DD410" - "00000000433A5C55736572735C626F625C4465736B746F705C6E6176695C6865795C6C69" - "7374656E5C6C6E6B5C736176655F687972756C652E74787400003A002E002E005C002E00" - "2E005C002E002E005C002E002E005C002E002E005C004400650073006B0074006F007000" - "5C006E006100760069005C006800650079005C006C0069007300740065006E005C006C00" - "6E006B005C0073006100760065005F0068007900720075006C0065002E00740078007400" - "280043003A005C00550073006500720073005C0062006F0062005C004400650073006B00" - "74006F0070005C006E006100760069005C006800650079005C006C006900730074006500" - "6E005C006C006E006B0060000000030000A058000000000000006465736B746F702D6569" - "733933386E0068458D3E11E418498F7897CD6CB340C5AC94C210D67EEB11A0F60800276E" - "B45E68458D3E11E418498F7897CD6CB340C5AC94C210D67EEB11A0F60800276EB45E4500" - "0000090000A03900000031535053B1166D44AD8D7048A748402EA43D788C1D0000006800" - "00000048000000902F5408000000000000501F00000000000000000000000000000000"; - - auto target_info = parseTargetInfo(data); - ASSERT_TRUE( - target_info.path == - "C:\\Users\\bob\\Desktop\\navi\\hey\\listen\\lnk\\save_hyrule.txt"); - ASSERT_TRUE(target_info.mft_entry == 44925); - ASSERT_TRUE(target_info.mft_sequence == 114); -} - -TEST_F(ShelllnkTests, test_shelllnk_location_info) { - std::string data = - "0000670000001C000000010000001C0000002D0000000000000066000000110000000300" - "00006F129DD41000000000433A5C55736572735C626F625C4465736B746F705C6E617669" - "5C6865795C6C697374656E5C6C6E6B5C736176655F687972756C652E74787400003A002E" - "002E005C002E002E005C002E002E005C002E002E005C002E002E005C004400650073006B" - "0074006F0070005C006E006100760069005C006800650079005C006C0069007300740065" - "006E005C006C006E006B005C0073006100760065005F0068007900720075006C0065002E" - "00740078007400280043003A005C00550073006500720073005C0062006F0062005C0044" - "00650073006B0074006F0070005C006E006100760069005C006800650079005C006C0069" - "007300740065006E005C006C006E006B0060000000030000A05800000000000000646573" - "6B746F702D6569733933386E0068458D3E11E418498F7897CD6CB340C5AC94C210D67EEB" - "11A0F60800276EB45E68458D3E11E418498F7897CD6CB340C5AC94C210D67EEB11A0F608" - "00276EB45E45000000090000A03900000031535053B1166D44AD8D7048A748402EA43D78" - "8C1D000000680000000048000000902F5408000000000000501F00000000000000000000" - "000000000000"; - auto location_info = parseLocationData(data); - ASSERT_TRUE( - location_info.local_path == - "C:\\Users\\bob\\Desktop\\navi\\hey\\listen\\lnk\\save_hyrule.txt"); - ASSERT_TRUE(location_info.type == "Fixed storage media (harddisk)"); - ASSERT_TRUE(location_info.serial == "D49D126F"); -} - -TEST_F(ShelllnkTests, test_shelllnk_data_string) { - std::string data = - "3A002E002E005C002E002E005C002E002E005C002E002E005C002E002E005C0044006500" - "73006B0074006F0070005C006E006100760069005C006800650079005C006C0069007300" - "740065006E005C006C006E006B005C0073006100760065005F0068007900720075006C00" - "65002E00740078007400280043003A005C00550073006500720073005C0062006F006200" - "5C004400650073006B0074006F0070005C006E006100760069005C006800650079005C00" - "6C0069007300740065006E005C006C006E006B0060000000030000A05800000000000000" - "6465736B746F702D6569733933386E0068458D3E11E418498F7897CD6CB340C5AC94C210" - "D67EEB11A0F60800276EB45E68458D3E11E418498F7897CD6CB340C5AC94C210D67EEB11" - "A0F60800276EB45E45000000090000A03900000031535053B1166D44AD8D7048A748402E" - "A43D788C1D000000680000000048000000902F5408000000000000501F00000000000000" - "000000000000000000"; - bool unicode = true; - bool has_name = false; - bool has_relative_path = true; - bool has_working_dir = true; - bool has_icon_location = false; - bool has_args = false; - auto data_string = parseDataString(data, - unicode, - has_name, - has_relative_path, - has_working_dir, - has_icon_location, - has_args); - ASSERT_TRUE( - data_string.relative_path == - "..\\..\\..\\..\\..\\Desktop\\navi\\hey\\listen\\lnk\\save_hyrule.txt"); -} - -TEST_F(ShelllnkTests, test_shelllnk_extra_data_tracker) { - std::string data = - "60000000030000A058000000000000006465736B746F702D6569733933386E0068458D3E" - "11E418498F7897CD6CB340C5AC94C210D67EEB11A0F60800276EB45E68458D3E11E41849" - "8F7897CD6CB340C5AC94C210D67EEB11A0F60800276EB45E45000000090000A039000000" - "31535053B1166D44AD8D7048A748402EA43D788C1D000000680000000048000000902F54" - "08000000000000501F00000000000000000000000000000000"; - auto extra_data = parseExtraDataTracker(data); - ASSERT_TRUE(extra_data.hostname == "desktop-eis938n"); -} -} // namespace osquery \ No newline at end of file diff --git a/osquery/utils/windows/shelllnk.cpp b/osquery/utils/windows/shelllnk.cpp deleted file mode 100644 index fd987129fd6..00000000000 --- a/osquery/utils/windows/shelllnk.cpp +++ /dev/null @@ -1,514 +0,0 @@ -/** - * 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 -#include -#include -#include -#include -#include -#include - -#include - -namespace osquery { - -LinkFlags parseShortcutFlags(const std::string& flags) { - std::string flags_swap = swapEndianess(flags); - int flags_int = tryTo(flags_swap, 16).takeOr(0); - LinkFlags lnk_flags; - lnk_flags.has_target_id_list = (bool)(flags_int & 0x1); - lnk_flags.has_link_info = (bool)(flags_int & 0x2); - lnk_flags.has_name = (bool)(flags_int & 0x4); - lnk_flags.has_relative_path = (bool)(flags_int & 0x8); - lnk_flags.has_working_dir = (bool)(flags_int & 0x10); - lnk_flags.has_arguments = (bool)(flags_int & 0x20); - lnk_flags.has_icon_location = (bool)(flags_int & 0x40); - lnk_flags.is_unicode = (bool)(flags_int & 0x80); - lnk_flags.no_link_info = (bool)(flags_int & 0x100); - lnk_flags.has_exp_string = (bool)(flags_int & 0x200); - lnk_flags.separate_process = (bool)(flags_int & 0x400); - lnk_flags.unused = (bool)(flags_int & 0x800); - lnk_flags.has_darwin_id = (bool)(flags_int & 0x1000); - lnk_flags.run_as_user = (bool)(flags_int & 0x2000); - lnk_flags.has_icon = (bool)(flags_int & 0x4000); - lnk_flags.pid_alias = (bool)(flags_int & 0x5000); - lnk_flags.unused2 = (bool)(flags_int & 0x10000); - lnk_flags.shim_layer = (bool)(flags_int & 0x20000); - lnk_flags.no_link_track = (bool)(flags_int & 0x40000); - lnk_flags.target_metadata = (bool)(flags_int & 0x80000); - lnk_flags.disable_link_path = (bool)(flags_int & 0x100000); - lnk_flags.disable_folder_tracking = (bool)(flags_int & 0x200000); - lnk_flags.disable_folder_alias = (bool)(flags_int & 0x400000); - lnk_flags.link_to_link = (bool)(flags_int & 0x800000); - lnk_flags.unalias_on_save = (bool)(flags_int & 0x1000000); - lnk_flags.environment_path = (bool)(flags_int & 0x2000000); - lnk_flags.local_id_for_unc_target = (bool)(flags_int & 0x4000000); - return lnk_flags; -} - -LinkFileHeader parseShortcutHeader(const std::string& header) { - LinkFileHeader lnk_header; - - if (header.size() < 132) { - LOG(WARNING) << "Header size smaller than expected: " << header; - lnk_header.header = ""; - return lnk_header; - } - lnk_header.header = header.substr(0, 8); - lnk_header.guid = header.substr(8, 32); - std::string header_flags = header.substr(40, 8); - lnk_header.flags = parseShortcutFlags(header_flags); - lnk_header.file_attribute = header.substr(48, 8); - std::string target_timestamp = header.substr(56, 16); - if (target_timestamp == "0000000000000000") { - lnk_header.creation_time = 0LL; - } else { - lnk_header.creation_time = littleEndianToUnixTime(target_timestamp); - } - target_timestamp = header.substr(72, 16); - if (target_timestamp == "0000000000000000") { - lnk_header.access_time = 0LL; - } else { - lnk_header.access_time = littleEndianToUnixTime(target_timestamp); - } - target_timestamp = header.substr(88, 16); - if (target_timestamp == "0000000000000000") { - lnk_header.modified_time = 0LL; - } else { - lnk_header.modified_time = littleEndianToUnixTime(target_timestamp); - } - std::string file_size_str = swapEndianess(header.substr(104, 8)); - - lnk_header.file_size = - tryTo(file_size_str, 16).takeOr(0ull); - lnk_header.icon_index = header.substr(112, 8); - lnk_header.window_value = header.substr(120, 8); - lnk_header.hot_key = header.substr(128, 4); - return lnk_header; -} - -TargetInfo parseTargetInfo(const std::string& target_info) { - // Skip the first two bytes - std::string data = target_info.substr(4); - TargetInfo target_lnk; - std::vector build_path; - - ShellFileEntryData file_entry; - file_entry.mft_entry = -1LL; - file_entry.mft_sequence = -1; - // Loop through all the shellitems - while (true) { - std::string str_item_size = data.substr(0, 4); - str_item_size = swapEndianess(str_item_size); - int item_size = tryTo(str_item_size, 16).takeOr(0) * 2; - // Empty target item sizes will cause infinte loop, sometimes at the end of - // the item there may be extra zeros - if (item_size == 0) { - break; - } - std::string sig = data.substr(4, 2); - std::string item_string = data.substr(0, item_size); - if (sig == "1F") { - if (item_string.substr(8, 2) == "2F") { // User Property View Drive - std::string name = propertyViewDrive(item_string); - // osquery::join adds "\" to entries, remove drive "\" - name.pop_back(); - build_path.push_back(name); - data.erase(0, item_size); - continue; - } - if (item_string.find("31535053") != std::string::npos) { - if (item_string.find("D5DFA323") != - std::string::npos) { // User Property View - std::string property_guid = item_string.substr(226, 32); - std::string guid_string = guidParse(property_guid); - target_lnk.property_guid = guid_string; - data.erase(0, item_size); - continue; - } - LOG(WARNING) << "Unknown user property data: " << item_string; - data.erase(0, item_size); - continue; - } - target_lnk.root_folder = rootFolderItem(item_string); - data.erase(0, item_size); - continue; - } else if (sig == "31" || sig == "30" || sig == "32" || sig == "35" || - sig == "B1") { - file_entry = fileEntry(item_string); - build_path.push_back(file_entry.path); - data.erase(0, item_size); - continue; - } else if (sig == "01") { // Control Panel Category - std::string panel = controlPanelCategoryItem(item_string); - target_lnk.control_panel_category = panel; - data.erase(0, item_size); - continue; - } else if (sig == "71") { // Control Panel - std::string control_guid = controlPanelItem(item_string); - target_lnk.control_panel = control_guid; - data.erase(0, item_size); - continue; - } else if (sig == "2F" || sig == "23" || sig == "25" || sig == "29" || - sig == "2A" || sig == "2E") { // Driver letter - if (item_string.substr(6, 2) == "80" || - (item_string.find("2600EFBE") != std::string::npos || - item_string.find("2500EFBE") != - std::string::npos)) { // Check if a GUID exists - std::string guid_little = item_string.substr(8, 32); - std::string guid_string = guidParse(guid_little); - - build_path.push_back('{' + guid_string + '}'); - data.erase(0, item_size); - continue; - } - std::string drive = driveLetterItem(item_string); - drive.pop_back(); - build_path.push_back(drive); - data.erase(0, item_size); - continue; - } else if ((sig == "74") && // User File View - item_string.find("43465346") != std::string::npos) { - file_entry = fileEntry(item_string); - build_path.push_back(file_entry.path); - data.erase(0, item_size); - continue; - } else if ((sig == "61")) { // FTP/URI entry - std::vector ftp_data = ftpItem(item_string); - build_path.push_back(ftp_data[1]); - data.erase(0, item_size); - continue; - } else if (sig == "00") { // Variable shell item, can contain a variety of - // shell item formats - if (item_string.find("EEBBFE23") != std::string::npos) { - std::string guid_string = variableGuid(item_string); - build_path.push_back('{' + guid_string + '}'); - data.erase(0, item_size); - continue; - } else if (item_string.substr(12, 8) == "05000000" || - item_string.substr(12, 8) == "05000300") { - std::string ftp_name = variableFtp(item_string); - build_path.push_back(ftp_name); - data.erase(0, item_size); - continue; - } - } - break; - } - target_lnk.path = osquery::join(build_path, "\\"); - target_lnk.mft_entry = file_entry.mft_entry; - target_lnk.mft_sequence = file_entry.mft_sequence; - target_lnk.data = data; - return target_lnk; -} - -std::string getLocationType(std::string& type) { - std::string location_type = ""; - if (type == "00001a00") { - location_type = "WNNC_NET_AVID"; - } else if (type == "00001B00") { - location_type = "WNNC_NET_DOCUSPACE"; - } else if (type == "00001C00") { - location_type = "WNNC_NET_MANGOSOFT"; - } else if (type == "00001D00") { - location_type = "WNNC_NET_SERNET"; - } else if (type == "00001E00") { - location_type = "WNNC_NET_RIVERFRONT1"; - } else if (type == "00001F00") { - location_type = "WNNC_NET_RIVERFRONT2"; - } else if (type == "00002000") { - location_type = "WNNC_NET_DECORB"; - } else if (type == "00002100") { - location_type = "WNNC_NET_PROTSTOR"; - } else if (type == "00002200") { - location_type = "WNNC_NET_FJ_REDIR"; - } else if (type == "00002300") { - location_type = "WNNC_NET_DISTINCT"; - } else if (type == "00002400") { - location_type = "WNNC_NET_TWINS"; - } else if (type == "00002500") { - location_type = "WNNC_NET_RDR2SAMPLE"; - } else if (type == "00002600") { - location_type = "WNNC_NET_CSC"; - } else if (type == "00002700") { - location_type = "WNNC_NET_3IN1"; - } else if (type == "00002900") { - location_type = "WNNC_NET_EXTENDNET"; - } else if (type == "00002A00") { - location_type = "WNNC_NET_STAC"; - } else if (type == "00002B00") { - location_type = "WNNC_NET_FOXBAT"; - } else if (type == "00002C00") { - location_type = "WNNC_NET_YAHOO"; - } else if (type == "00002D00") { - location_type = "WNNC_NET_EXIFS"; - } else if (type == "00002E00") { - location_type = "WNNC_NET_DAV"; - } else if (type == "00002F00") { - location_type = "WNNC_NET_KNOWARE"; - } else if (type == "00003000") { - location_type = "WNNC_NET_OBJECT_DIRE"; - } else if (type == "00003100") { - location_type = "WNNC_NET_MASFAX"; - } else if (type == "00003200") { - location_type = "WNNC_NET_HOB_NFS"; - } else if (type == "00003300") { - location_type = "WNNC_NET_SHIVA"; - } else if (type == "00003400") { - location_type = "WNNC_NET_IBMAL"; - } else if (type == "00003500") { - location_type = "WNNC_NET_LOCK"; - } else if (type == "00003600") { - location_type = "WNNC_NET_TERMSRV"; - } else if (type == "00003700") { - location_type = "WNNC_NET_SRT"; - } else if (type == "00003800") { - location_type = "WNNC_NET_QUINCY"; - } else if (type == "00003900") { - location_type = "WNNC_NET_OPENAFS"; - } else if (type == "00003A00") { - location_type = "WNNC_NET_AVID1"; - } else if (type == "00003B00") { - location_type = "WNNC_NET_DFS"; - } else if (type == "00003C00") { - location_type = "WNNC_NET_KWNP"; - } else if (type == "00003D00") { - location_type = "WNNC_NET_ZENWORKS"; - } else if (type == "00003E00") { - location_type = "WNNC_NET_DRIVEONWEB"; - } else if (type == "00003F00") { - location_type = "WNNC_NET_VMWARE"; - } else if (type == "00004000") { - location_type = "WNNC_NET_RSFX"; - } else if (type == "00004100") { - location_type = "WNNC_NET_MFILES"; - } else if (type == "00004200") { - location_type = "WNNC_NET_MS_NFS"; - } else if (type == "00004300") { - location_type = "WNNC_NET_GOOGLE"; - } else { - LOG(WARNING) << "Unknown network type: " << type; - } - return location_type; -} - -LocationInfo parseLocationData(const std::string& location_data) { - LocationInfo location_info; - std::string data = location_data; - // Previous lnk data may have extra zeros (due to Unicode null termination?) - if (data.substr(0, 4) == "0000") { - data = data.substr(4); - } - std::string str_location_size = data.substr(0, 8); - str_location_size = swapEndianess(str_location_size); - int location_size = tryTo(str_location_size, 16).takeOr(0) * 2; - - std::string location_type = data.substr(16, 8); - location_type = swapEndianess(location_type); - - if (location_type == "00000001") { - std::string volume_offset = data.substr(24, 8); - volume_offset = swapEndianess(volume_offset); - int offset = tryTo(volume_offset, 16).takeOr(0); - std::string type = data.substr((offset * 2) + 8, 8); - if (type == "03000000") { - location_info.type = "Fixed storage media (harddisk)"; - } else if (type == "00000000") { - location_info.type = "Unknown"; - } else if (type == "01000000") { - location_info.type = "No root directory"; - } else if (type == "04000000") { - location_info.type = "Remote storage"; - } else if (type == "05000000") { - location_info.type = "Optical disc (CD-ROM, DVD, BD)"; - } else if (type == "06000000") { - location_info.type = "RAM drive"; - } else if (type == "02000000") { - location_info.type = "Removable storage media (floppy, usb)"; - } else { - LOG(WARNING) << "Unknown volume type: " << type; - data.erase(0, location_size); - location_info.data = data; - return location_info; - } - std::string local_path_offset = data.substr(32, 8); - local_path_offset = swapEndianess(local_path_offset); - int path_offset = tryTo(local_path_offset, 16).takeOr(0) * 2; - size_t local_path_size = data.find("00", path_offset); - // Size should be even but if values end in base 10 it will be odd - if (local_path_size % 2 != 0) { - local_path_size++; - } - std::string local_path = - data.substr(path_offset, local_path_size - path_offset); - try { - location_info.local_path = boost::algorithm::unhex(local_path); - } catch (const boost::algorithm::hex_decode_error& /* e */) { - LOG(WARNING) << "Failed to decode local path hex values to string: " - << local_path; - } - std::string serial = data.substr((offset * 2) + 16, 8); - location_info.serial = swapEndianess(serial); - } else if (location_type == "00000002") { - std::string network_offset = data.substr(40, 8); - network_offset = swapEndianess(network_offset); - int offset = tryTo(network_offset, 16).takeOr(0); - std::string type = data.substr((offset * 2) + 32, 8); - std::string location_type = getLocationType(type); - if (location_type.empty()) { - data.erase(0, location_size); - location_info.data = data; - return location_info; - } - location_info.type = location_type; - std::string common_path_offset = data.substr(48, 8); - common_path_offset = swapEndianess(common_path_offset); - int path_offset = tryTo(common_path_offset, 16).takeOr(0) * 2; - size_t common_path_size = data.find("00", path_offset); - // Size should be even but if values end in base 10 it will be odd - if (common_path_size % 2 != 0) { - common_path_size++; - } - std::string common_path = - data.substr(path_offset, common_path_size - path_offset); - try { - location_info.common_path = boost::algorithm::unhex(common_path); - } catch (const boost::algorithm::hex_decode_error& /* e */) { - LOG(WARNING) << "Failed to decode common path hex values to string: " - << common_path; - } - std::string share_name_offset = data.substr((offset * 2) + 16, 8); - share_name_offset = swapEndianess(share_name_offset); - int share_offset = tryTo(share_name_offset, 16).takeOr(0); - size_t share_name_size = data.find("00", (offset * 2) + share_offset * 2); - std::string share_name = - data.substr((offset * 2) + share_offset * 2, - share_name_size - ((offset * 2) + share_offset * 2)); - try { - location_info.share_name = boost::algorithm::unhex(share_name); - } catch (const boost::algorithm::hex_decode_error& /* e */) { - LOG(WARNING) << "Failed to decode share name hex values to string: " - << share_name; - } - } else { - LOG(WARNING) << "Unknown location type: " << location_type; - } - data.erase(0, location_size); - location_info.data = data; - return location_info; -} - -std::string parseLnkData(const std::string& data_string, - const bool unicode, - const int& size) { - int data_size = size; - if (unicode) { - data_size = data_size * 4; - } - std::string data_str_type = data_string.substr(4, data_size); - if (unicode) { - boost::erase_all(data_str_type, "00"); - } - - try { - std::string lnk_data = boost::algorithm::unhex(data_str_type); - return lnk_data; - } catch (const boost::algorithm::hex_decode_error& /* e */) { - LOG(WARNING) - << "Failed to decode shortcut data string hex values to string: " - << data_str_type; - return ""; - } -} - -DataStringInfo parseDataString(const std::string& data, - const bool unicode, - const bool description, - const bool relative_path, - const bool working_path, - const bool icon_location, - const bool command_args) { - std::string data_string = data; - DataStringInfo data_info; - std::string str_data_size = data_string.substr(0, 4); - // Previous lnk data may have extra zeros (due to Unicode null termination?) - if (str_data_size == "0000") { - data_string.erase(0, 4); - str_data_size = data_string.substr(0, 4); - } - str_data_size = swapEndianess(str_data_size); - int data_size = tryTo(str_data_size, 16).takeOr(0); - - auto dataStringParse = [&data_size, &data_string](std::string& target, - bool unicode) { - target = parseLnkData(data_string, unicode, data_size); - if (unicode) { - data_size = data_size * 4; - } - data_string.erase(0, data_size + 4); - auto str_data_size = data_string.substr(0, 4); - str_data_size = swapEndianess(str_data_size); - data_size = tryTo(str_data_size, 16).takeOr(0); - }; - - // Data strings go in the following order: description, relative path, working - // path, command args, icon location. Some may not exist - if (description) { - dataStringParse(data_info.description, unicode); - } - if (relative_path) { - dataStringParse(data_info.relative_path, unicode); - } - if (working_path) { - dataStringParse(data_info.working_path, unicode); - } - if (command_args) { - dataStringParse(data_info.arguments, unicode); - } - if (icon_location) { - dataStringParse(data_info.icon_location, unicode); - } - data_info.data = data_string; - return data_info; -} - -ExtraDataTracker parseExtraDataTracker(const std::string& data) { - ExtraDataTracker data_tracker; - // Check for tracker database, contains hostname. It may not exist. - if ((data.find("60000000") == std::string::npos) || - (data.find("030000A0") == std::string::npos)) { - data_tracker.hostname = ""; - return data_tracker; - } - size_t extra_offset = data.find("030000A0") + 24; - std::string hostname = data.substr(extra_offset, 32); - boost::erase_all(hostname, "00"); - // Size should be even but if values end in base 10 it will be odd - if (hostname.size() % 2 != 0) { - hostname = hostname + "0"; - } - - try { - data_tracker.hostname = boost::algorithm::unhex(hostname); - } catch (const boost::algorithm::hex_decode_error& /* e */) { - LOG(WARNING) << "Failed to decode extra data tracker hex values to string: " - << hostname; - } - std::string guid = data.substr(extra_offset + 32, 32); - data_tracker.droid_volume = guidParse(guid); - guid = data.substr(extra_offset + 64, 32); - data_tracker.droid_file = guidParse(guid); - guid = data.substr(extra_offset + 96, 32); - data_tracker.birth_droid_volume = guidParse(guid); - guid = data.substr(extra_offset + 128, 32); - data_tracker.birth_droid_file = guidParse(guid); - return data_tracker; -} -} // namespace osquery \ No newline at end of file diff --git a/osquery/utils/windows/shelllnk.h b/osquery/utils/windows/shelllnk.h deleted file mode 100644 index d9a527a7372..00000000000 --- a/osquery/utils/windows/shelllnk.h +++ /dev/null @@ -1,141 +0,0 @@ -/** - * 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 - -#include -#include - -namespace osquery { - -struct LinkFlags { - bool has_target_id_list; - bool has_link_info; - bool has_name; - bool has_relative_path; - bool has_working_dir; - bool has_arguments; - bool has_icon_location; - bool is_unicode; - bool no_link_info; - bool has_exp_string; - bool separate_process; - bool unused; - bool has_darwin_id; - bool run_as_user; - bool has_icon; - bool pid_alias; - bool unused2; - bool shim_layer; - bool no_link_track; - bool target_metadata; - bool disable_link_path; - bool disable_folder_tracking; - bool disable_folder_alias; - bool link_to_link; - bool unalias_on_save; - bool environment_path; - bool local_id_for_unc_target; - bool error; -}; - -struct LinkFileHeader { - std::string header; - std::string guid; - LinkFlags flags; - std::string file_attribute; - long long creation_time; - long long access_time; - long long modified_time; - long long file_size; - std::string icon_index; - std::string window_value; - std::string hot_key; -}; - -struct TargetInfo { - std::string root_folder; - std::string control_panel; - std::string control_panel_category; - std::string path; - long long mft_entry; - int mft_sequence; - std::string data; - std::string property_guid; -}; - -struct LocationInfo { - std::string type; - std::string serial; - std::string data; - std::string local_path; - std::string common_path; - std::string share_name; -}; - -struct DataStringInfo { - std::string description; - std::string relative_path; - std::string working_path; - std::string arguments; - std::string icon_location; - std::string data; -}; - -struct ExtraDataTracker { - std::string hostname; - std::string droid_volume; - std::string droid_file; - std::string birth_droid_volume; - std::string birth_droid_file; -}; - -/** - * @brief Windows helper function for parsing shortcut header data - * - * @returns The shortcut header structure - */ -LinkFileHeader parseShortcutHeader(const std::string& header); - -/** - * @brief Windows helper function for parsing shortcut target data - * - * @returns The shortcut target structure - */ -TargetInfo parseTargetInfo(const std::string& target_info); - -/** - * @brief Windows helper function for parsing shortcut location data - * - * @returns The shortcut location structure - */ -LocationInfo parseLocationData(const std::string& location_data); - -/** - * @brief Windows helper function for parsing shortcut data string data - * - * @returns The shortcut data string structure - */ -DataStringInfo parseDataString(const std::string& data, - const bool unicode, - const bool description, - const bool relative_path, - const bool working_path, - const bool icon_location, - const bool command_args); - -/** - * @brief Windows helper function for parsing shortcut extra data tracker - * - * @returns The shortcut extra data tracker structure - */ -ExtraDataTracker parseExtraDataTracker(const std::string& data); -} // namespace osquery diff --git a/specs/utility/file.table b/specs/utility/file.table index 32fb900b6c3..ec56507cba3 100644 --- a/specs/utility/file.table +++ b/specs/utility/file.table @@ -26,6 +26,12 @@ extended_schema(WINDOWS, [ Column("file_version", TEXT, "File version"), Column("product_version", TEXT, "File product version"), Column("original_filename", TEXT, "(Executable files only) Original filename"), + Column("shortcut_target_path", TEXT, "Full path to the file the shortcut points to"), + Column("shortcut_target_type", TEXT, "Display name for the target type"), + Column("shortcut_target_location", TEXT, "Folder name where the shortcut target resides"), + Column("shortcut_start_in", TEXT, "Full path to the working directory to use when executing the shortcut target"), + Column("shortcut_run", TEXT, "Window mode the target of the shortcut should be run in"), + Column("shortcut_comment", TEXT, "Comment on the shortcut"), ]) extended_schema(DARWIN, [ Column("bsd_flags", TEXT, "The BSD file flags (chflags). Possible values: NODUMP, UF_IMMUTABLE, UF_APPEND, OPAQUE, HIDDEN, ARCHIVED, SF_IMMUTABLE, SF_APPEND") diff --git a/tests/integration/tables/file.cpp b/tests/integration/tables/file.cpp index 1ffa7800f54..29f1c15f4b0 100644 --- a/tests/integration/tables/file.cpp +++ b/tests/integration/tables/file.cpp @@ -10,6 +10,15 @@ // Sanity check integration test for file // Spec file: specs/utility/file.table +#ifdef WIN32 +#include + +#include + +#include +#include +#endif + #include #include @@ -23,13 +32,52 @@ namespace { const std::vector kFileNameList{ // In order to test MBCS support, here's a japanese word - // that *should* mean "dictionary" - "辞書.txt" + // that means "dictionary" + "辞書.txt", "file-table-test.txt", }; +#ifdef WIN32 +void createShellLink(boost::filesystem::path link_path, + boost::filesystem::path file_path) { + HRESULT hres; + IShellLink* shell_link = nullptr; + + hres = CoCreateInstance(CLSID_ShellLink, + NULL, + CLSCTX_INPROC_SERVER, + IID_IShellLink, + (LPVOID*)&shell_link); + + ASSERT_TRUE(SUCCEEDED(hres)); + + IPersistFile* file = nullptr; + + shell_link->SetPath(file_path.wstring().data()); + shell_link->SetDescription(L"Test shortcut"); + shell_link->SetWorkingDirectory(file_path.parent_path().wstring().data()); + + hres = shell_link->QueryInterface(IID_IPersistFile, + reinterpret_cast(&file)); + + if (FAILED(hres)) { + shell_link->Release(); + FAIL(); + } + + hres = file->Save(link_path.wstring().data(), TRUE); + + if (FAILED(hres)) { + file->Release(); + shell_link->Release(); + FAIL(); + } + + file->Release(); + shell_link->Release(); } +#endif class FileTests : public testing::Test { public: @@ -53,6 +101,10 @@ class FileTests : public testing::Test { fout.open(filepath.string(), std::ios::out); fout << "test"; } + +#ifdef WIN32 + createShellLink(filepath.replace_extension(".lnk"), filepath); +#endif } } @@ -80,13 +132,22 @@ boost::optional getRowIndexForFileName( return std::distance(data.begin(), it); } +} // namespace TEST_F(FileTests, test_sanity) { std::string path_constraint = (directory / boost::filesystem::path("%.txt")).string(); - QueryData data = execute_query("select * from file where path like \"" + - path_constraint + "\""); - EXPECT_EQ(data.size(), kFileNameList.size()); + std::string link_constraint = + (directory / boost::filesystem::path("%.lnk")).string(); + QueryData data = + execute_query("select * from file where path like \"" + path_constraint + + "\" OR path like \"" + link_constraint + "\""); + + if (isPlatform(PlatformType::TYPE_WINDOWS)) { + EXPECT_EQ(data.size(), kFileNameList.size() * 2); + } else { + EXPECT_EQ(data.size(), kFileNameList.size()); + } ValidationMap row_map = {{"path", FileOnDisk}, {"directory", DirectoryOnDisk}, @@ -112,6 +173,12 @@ TEST_F(FileTests, test_sanity) { row_map["product_version"] = NormalType; row_map["file_version"] = NormalType; row_map["original_filename"] = NormalType; + row_map["shortcut_target_path"] = NormalType; + row_map["shortcut_target_type"] = NormalType; + row_map["shortcut_target_location"] = NormalType; + row_map["shortcut_start_in"] = NormalType; + row_map["shortcut_run"] = NormalType; + row_map["shortcut_comment"] = NormalType; #endif #ifdef __APPLE__ @@ -138,6 +205,20 @@ TEST_F(FileTests, test_sanity) { ASSERT_EQ(row.at("path"), expected_path); ASSERT_EQ(row.at("directory"), directory.string()); ASSERT_EQ(row.at("filename"), test_file_name); + + if (isPlatform(PlatformType::TYPE_WINDOWS)) { + auto link_path = boost::filesystem::path(expected_path); + + if (row.at("path").rfind(".lnk") != std::string::npos) { + EXPECT_EQ(row.at("shortcut_target_path"), + link_path.replace_extension(".lnk").string()); + EXPECT_EQ(row.at("shortcut_target_type"), "File"); + EXPECT_EQ(row.at("shortcut_target_location"), test_file_name); + EXPECT_EQ(row.at("shortcut_target_start_in"), directory.string()); + EXPECT_EQ(row.at("shortcut_target_run"), "Normal window"); + EXPECT_EQ(row.at("shortcut_target_comment"), "Test shortcut"); + } + } } validate_rows(data, row_map);