From 352e3ff7f8cfd887d713b8e7616cddbd94cdd296 Mon Sep 17 00:00:00 2001 From: Rich5 Date: Sun, 15 Jul 2018 17:12:46 -0400 Subject: [PATCH] Windows file ops (#4613) --- osquery/core/windows/process_ops.cpp | 52 ++--- osquery/filesystem/fileops.h | 43 +++- osquery/filesystem/windows/fileops.cpp | 271 +++++++++++++++++++++++++ osquery/tables/utility/file.cpp | 61 ++++-- specs/utility/file.table | 5 + 5 files changed, 382 insertions(+), 50 deletions(-) diff --git a/osquery/core/windows/process_ops.cpp b/osquery/core/windows/process_ops.cpp index 67c88890f01..8beb3824e29 100644 --- a/osquery/core/windows/process_ops.cpp +++ b/osquery/core/windows/process_ops.cpp @@ -24,47 +24,30 @@ std::string psidToString(PSID sid) { } int getUidFromSid(PSID sid) { - auto eUse = SidTypeUnknown; - unsigned long unameSize = 0; - unsigned long domNameSize = 1; - - // LookupAccountSid first gets the size of the username buff required. - LookupAccountSidW( - nullptr, sid, nullptr, &unameSize, nullptr, &domNameSize, &eUse); - std::vector uname(unameSize); - std::vector domName(domNameSize); - auto ret = LookupAccountSidW(nullptr, - sid, - uname.data(), - &unameSize, - domName.data(), - &domNameSize, - &eUse); - - if (ret == 0) { - return -1; - } - // USER_INFO_3 struct contains the RID (uid) of our user - unsigned long userInfoLevel = 3; - unsigned char* userBuff = nullptr; unsigned long uid = -1; - ret = NetUserGetInfo(nullptr, uname.data(), userInfoLevel, &userBuff); - if (ret != NERR_Success && ret != NERR_UserNotFound) { + LPTSTR sidString; + if (ConvertSidToStringSid(sid, &sidString) == 0) { + VLOG(1) << "getUidFromSid failed ConvertSidToStringSid error " + + std::to_string(GetLastError()); + LocalFree(sidString); return uid; } + auto toks = osquery::split(sidString, "-"); - // SID belongs to a domain user, so we return the relative identifier (RID) - if (ret == NERR_UserNotFound) { - LPTSTR sidString; - ConvertSidToStringSid(sid, &sidString); - auto toks = osquery::split(sidString, "-"); - safeStrtoul(toks.at(toks.size() - 1), 10, uid); + if (toks.size() < 1) { LocalFree(sidString); - } else if (ret == NERR_Success) { - uid = LPUSER_INFO_3(userBuff)->usri3_user_id; + return uid; } - NetApiBufferFree(userBuff); + auto ret = safeStrtoul(toks.at(toks.size() - 1), 10, uid); + + if (!ret.ok()) { + LocalFree(sidString); + VLOG(1) << "getUidFromSid failed with safeStrtoul failed to parse PSID"; + return uid; + } + + LocalFree(sidString); return uid; } @@ -101,6 +84,7 @@ int getGidFromSid(PSID sid) { auto toks = osquery::split(sidString, "-"); safeStrtoul(toks.at(toks.size() - 1), 10, gid); LocalFree(sidString); + } else if (ret == NERR_Success) { gid = LPUSER_INFO_3(userBuff)->usri3_primary_group_id; } diff --git a/osquery/filesystem/fileops.h b/osquery/filesystem/fileops.h index 134198d18b2..8f9ea56437d 100644 --- a/osquery/filesystem/fileops.h +++ b/osquery/filesystem/fileops.h @@ -15,7 +15,8 @@ #include #ifdef WIN32 - +#include +#include #include #else #include @@ -61,6 +62,38 @@ using PlatformTimeType = FILETIME; #define S_IXOTH (S_IXGRP >> 3) #define S_IRWXO (S_IRWXG >> 3) +const std::map kDriveLetters{ + {0, "A:\\"}, {1, "B:\\"}, {2, "C:\\"}, {3, "D:\\"}, {4, "E:\\"}, + {5, "F:\\"}, {6, "G:\\"}, {7, "H:\\"}, {8, "I:\\"}, {9, "J:\\"}, + {10, "K:\\"}, {11, "L:\\"}, {12, "M:\\"}, {13, "N:\\"}, {14, "O:\\"}, + {15, "P:\\"}, {16, "Q:\\"}, {17, "R:\\"}, {18, "S:\\"}, {19, "T:\\"}, + {20, "U:\\"}, {21, "V:\\"}, {22, "W:\\"}, {23, "X:\\"}, {24, "Y:\\"}, + {25, "Z:\\"}, +}; + +typedef struct win_stat { + std::string path; + std::string filename; + int symlink; + std::string file_id; + LONGLONG inode; + unsigned long uid; + unsigned long gid; + std::string mode; + LONGLONG device; + LONGLONG size; + int block_size; + LONGLONG atime; + LONGLONG mtime; + LONGLONG ctime; + LONGLONG btime; + int hard_links; + std::string type; + std::string attributes; + std::string volume_serial; + +} WINDOWS_STAT; + #else using PlatformHandle = int; @@ -72,6 +105,8 @@ typedef struct { PlatformTimeType times[2]; } PlatformTime; /// Constant for an invalid handle. const PlatformHandle kInvalidHandle = (PlatformHandle)-1; +std::string lastErrorMessage(unsigned long); + /** * @brief File access modes for PlatformFile. * @@ -110,6 +145,12 @@ enum SeekMode { PF_SEEK_BEGIN = 0, PF_SEEK_CURRENT, PF_SEEK_END }; /// Takes a Windows FILETIME object and returns seconds since epoch LONGLONG filetimeToUnixtime(const FILETIME& ft); +LONGLONG longIntToUnixtime(LARGE_INTEGER& ft); + +std::string getFileAttribStr(unsigned long); + +Status platformStat(const boost::filesystem::path&, WINDOWS_STAT*); + /** * @brief Stores information about the last Windows async request * diff --git a/osquery/filesystem/windows/fileops.cpp b/osquery/filesystem/windows/fileops.cpp index c9d64ee73a8..a3947b18284 100644 --- a/osquery/filesystem/windows/fileops.cpp +++ b/osquery/filesystem/windows/fileops.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -25,6 +26,7 @@ #include #include "osquery/core/process.h" +#include "osquery/core/windows/process_ops.h" #include "osquery/filesystem/fileops.h" #define min(a, b) (((a) < (b)) ? (a) : (b)) @@ -35,6 +37,9 @@ namespace errc = boost::system::errc; namespace osquery { +int getUidFromSid(PSID sid); +int getGidFromSid(PSID sid); + /* * Avoid having the same right being used in multiple CHMOD_* macros. Doing so * will cause issues when requesting certain permissions in the presence of deny @@ -1469,6 +1474,272 @@ LONGLONG filetimeToUnixtime(const FILETIME& ft) { return date.QuadPart / 10000000; } +LONGLONG longIntToUnixtime(LARGE_INTEGER& ft) { + ULARGE_INTEGER ull; + ull.LowPart = ft.LowPart; + ull.HighPart = ft.HighPart; + return ull.QuadPart / 10000000ULL - 11644473600ULL; +} + +std::string getFileAttribStr(unsigned long file_attributes) { + std::string attribs; + + if (file_attributes & FILE_ATTRIBUTE_ARCHIVE) { + // Archive file attribute + attribs.push_back('A'); + } + if (file_attributes & FILE_ATTRIBUTE_COMPRESSED) { + // Compressed (Not included in attrib.exe output) + attribs.push_back('C'); + } + if (file_attributes & FILE_ATTRIBUTE_ENCRYPTED) { + // Encrypted (Not included in attrib.exe output) + attribs.push_back('E'); + } + if (file_attributes & FILE_ATTRIBUTE_REPARSE_POINT) { + // Hidden file attribute + attribs.push_back('L'); + } + if (file_attributes & FILE_ATTRIBUTE_HIDDEN) { + // Hidden file attribute + attribs.push_back('H'); + } + if (file_attributes & FILE_ATTRIBUTE_INTEGRITY_STREAM) { + // + attribs.push_back('V'); + } + if (file_attributes & FILE_ATTRIBUTE_NORMAL) { + // Normal (Not included in attrib.exe output) + attribs.push_back('N'); + } + if (file_attributes & FILE_ATTRIBUTE_NOT_CONTENT_INDEXED) { + // Not content indexed file attribute + attribs.push_back('I'); + } + if (file_attributes & FILE_ATTRIBUTE_NO_SCRUB_DATA) { + // No scrub file attribute + attribs.push_back('X'); + } + if (file_attributes & FILE_ATTRIBUTE_OFFLINE) { + // Offline attribute + attribs.push_back('O'); + } + if (file_attributes & FILE_ATTRIBUTE_READONLY) { + // Read-only file attribute + attribs.push_back('R'); + } + if (file_attributes & FILE_ATTRIBUTE_SYSTEM) { + // System file attribute + attribs.push_back('S'); + } + if (file_attributes & FILE_ATTRIBUTE_TEMPORARY) { + // Temporary file attribute (Not included in attrib.exe output) + attribs.push_back('T'); + } + + return attribs; +} + +std::string lastErrorMessage(unsigned long error_code) { + LPTSTR msg_buffer = nullptr; + + FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + error_code, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR)&msg_buffer, + 0, + NULL); + + if (msg_buffer != NULL) { + auto error_message = std::string(msg_buffer); + LocalFree(msg_buffer); + msg_buffer = nullptr; + + return error_message; + } + + VLOG(1) << "FormatMessage failed for code (" << std::to_string(error_code) + << ")"; + return std::string("Error code" + std::to_string(error_code) + "not found"); +} + +Status platformStat(const fs::path& path, WINDOWS_STAT* wfile_stat) { + auto FLAGS_AND_ATTRIBUTES = FILE_ATTRIBUTE_ARCHIVE | + FILE_ATTRIBUTE_ENCRYPTED | FILE_ATTRIBUTE_HIDDEN | + FILE_ATTRIBUTE_NORMAL | FILE_ATTRIBUTE_OFFLINE | + FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM | + FILE_ATTRIBUTE_TEMPORARY; + + if (fs::is_directory(path)) { + FLAGS_AND_ATTRIBUTES |= FILE_FLAG_BACKUP_SEMANTICS; + } + + // Get the handle of the file object. + auto file_handle = CreateFile(path.string().c_str(), + GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, + nullptr, + OPEN_EXISTING, + FLAGS_AND_ATTRIBUTES, + nullptr); + + // Check GetLastError for CreateFile error code. + if (file_handle == INVALID_HANDLE_VALUE) { + CloseHandle(file_handle); + return Status(-1, + "CreateFile failed for " + path.string() + " with " + + std::to_string(GetLastError())); + } + + // Get the owner SID of the file. + PSID sid_owner = nullptr; + PSID gid_owner = nullptr; + PSECURITY_DESCRIPTOR security_descriptor = nullptr; + auto ret = + GetSecurityInfo(file_handle, + SE_FILE_OBJECT, + OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION, + &sid_owner, + &gid_owner, + NULL, + NULL, + &security_descriptor); + + // Check GetLastError for GetSecurityInfo error condition. + if (ret != ERROR_SUCCESS) { + CloseHandle(file_handle); + return Status(-1, + "GetSecurityInfo failed for " + path.string() + " with " + + std::to_string(GetLastError())); + } + + FILE_BASIC_INFO basic_info; + BY_HANDLE_FILE_INFORMATION file_info; + + if (GetFileInformationByHandle(file_handle, &file_info) == 0) { + CloseHandle(file_handle); + return Status(-1, + "GetFileInformationByHandle failed for " + path.string() + + " with " + std::to_string(GetLastError())); + } + + auto file_index = + (static_cast(file_info.nFileIndexHigh) << 32) | + static_cast(file_info.nFileIndexLow); + + std::stringstream stream; + stream << "0x" << std::setfill('0') + << std::setw(sizeof(unsigned long long) * 2) << std::hex << file_index; + std::string file_id(stream.str()); + + // Windows has fileid's that are displayed in hex using: + // fsutil file queryfileid + wfile_stat->file_id = file_id; + + // inode is the decimal equivalent of fileid + wfile_stat->inode = file_index; + + wfile_stat->uid = getUidFromSid(sid_owner); + + wfile_stat->gid = getUidFromSid(gid_owner); + + LocalFree(security_descriptor); + + // Permission bits don't make sense for Windows. Use ntfs_acl_permissions + // table + wfile_stat->mode = "-1"; + + wfile_stat->symlink = 0; + + auto file_type = GetFileType(file_handle); + // Try to assign a human readable file type + switch (file_type) { + case FILE_TYPE_CHAR: { + wfile_stat->type = "character"; + break; + } + case FILE_TYPE_DISK: { + if ((file_info.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE) || + (file_info.dwFileAttributes & FILE_ATTRIBUTE_NORMAL)) { + wfile_stat->type = "regular"; + } else if (file_info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + wfile_stat->type = "directory"; + } else if (file_info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) { + wfile_stat->type = "symbolic"; + wfile_stat->symlink = 1; + } else { + // This is the type returned from GetFileType -> FILE_TYPE_DISK + wfile_stat->type = "disk"; + } + break; + } + case FILE_TYPE_PIPE: { + // If GetNamedPipeInfo fails we assume it's a socket + (GetNamedPipeInfo(file_handle, 0, 0, 0, 0)) ? wfile_stat->type = "pipe" + : wfile_stat->type = "socket"; + break; + } + default: { wfile_stat->type = "unknown"; } + } + + wfile_stat->attributes = getFileAttribStr(file_info.dwFileAttributes); + + std::stringstream volume_serial; + volume_serial << std::hex << std::setfill('0') << std::setw(4) + << HIWORD(file_info.dwVolumeSerialNumber) << "-" << std::setw(4) + << LOWORD(file_info.dwVolumeSerialNumber); + + wfile_stat->device = file_info.dwVolumeSerialNumber; + wfile_stat->volume_serial = volume_serial.str(); + + LARGE_INTEGER li = {0}; + (GetFileSizeEx(file_handle, &li) == 0) ? wfile_stat->size = -1 + : wfile_stat->size = li.QuadPart; + + const char* drive_letter = nullptr; + auto drive_letter_index = PathGetDriveNumber(path.string().c_str()); + + if (drive_letter_index != -1 && kDriveLetters.count(drive_letter_index)) { + drive_letter = kDriveLetters.at(drive_letter_index).c_str(); + + unsigned long sect_per_cluster; + unsigned long bytes_per_sect; + unsigned long free_clusters; + unsigned long total_clusters; + + if (GetDiskFreeSpace(drive_letter, + §_per_cluster, + &bytes_per_sect, + &free_clusters, + &total_clusters) != 0) { + wfile_stat->block_size = bytes_per_sect; + } else { + wfile_stat->block_size = -1; + } + + } else { + wfile_stat->block_size = -1; + } + + wfile_stat->hard_links = file_info.nNumberOfLinks; + wfile_stat->atime = filetimeToUnixtime(file_info.ftLastAccessTime); + wfile_stat->mtime = filetimeToUnixtime(file_info.ftLastWriteTime); + wfile_stat->btime = filetimeToUnixtime(file_info.ftCreationTime); + + // Change time is not available in GetFileInformationByHandle + ret = GetFileInformationByHandleEx( + file_handle, FileBasicInfo, &basic_info, sizeof(basic_info)); + + (!ret) ? wfile_stat->ctime = -1 + : wfile_stat->ctime = longIntToUnixtime(basic_info.ChangeTime); + + CloseHandle(file_handle); + + return Status(); +} + fs::path getSystemRoot() { std::vector winDirectory(MAX_PATH + 1); ZeroMemory(winDirectory.data(), MAX_PATH + 1); diff --git a/osquery/tables/utility/file.cpp b/osquery/tables/utility/file.cpp index 442a3752a68..7e821731d00 100644 --- a/osquery/tables/utility/file.cpp +++ b/osquery/tables/utility/file.cpp @@ -8,19 +8,24 @@ * You may select, at your option, one of the above-listed licenses. */ +#if !defined(WIN32) #include - -#include +#endif #include #include #include +#include "osquery/filesystem/fileops.h" + namespace fs = boost::filesystem; namespace osquery { + namespace tables { +#if !defined(WIN32) + const std::map kTypeNames{ {fs::regular_file, "regular"}, {fs::directory_file, "directory"}, @@ -33,6 +38,8 @@ const std::map kTypeNames{ {fs::status_error, "error"}, }; +#endif + void genFileInfo(const fs::path& path, const fs::path& parent, const std::string& pattern, @@ -46,25 +53,22 @@ void genFileInfo(const fs::path& path, r["directory"] = parent.string(); r["symlink"] = "0"; - struct stat file_stat; #if !defined(WIN32) + + struct stat file_stat; + // On POSIX systems, first check the link state. struct stat link_stat; if (lstat(path.string().c_str(), &link_stat) < 0) { // Path was not real, had too may links, or could not be accessed. return; } - if (S_ISLNK(link_stat.st_mode)) { + if ((link_stat.st_mode & S_IFLNK) != 0) { r["symlink"] = "1"; } -#endif if (stat(path.string().c_str(), &file_stat)) { -#if !defined(WIN32) file_stat = link_stat; -#else - return; -#endif } r["inode"] = BIGINT(file_stat.st_ino); @@ -73,17 +77,14 @@ void genFileInfo(const fs::path& path, r["mode"] = lsperms(file_stat.st_mode); r["device"] = BIGINT(file_stat.st_rdev); r["size"] = BIGINT(file_stat.st_size); - -#if !defined(WIN32) r["block_size"] = INTEGER(file_stat.st_blksize); r["hard_links"] = INTEGER(file_stat.st_nlink); -#endif - // Times r["atime"] = BIGINT(file_stat.st_atime); r["mtime"] = BIGINT(file_stat.st_mtime); r["ctime"] = BIGINT(file_stat.st_ctime); -#if defined(__linux__) || defined(WIN32) + +#if defined(__linux__) // No 'birth' or create time in Linux or Windows. r["btime"] = "0"; #else @@ -99,6 +100,36 @@ void genFileInfo(const fs::path& path, r["type"] = "unknown"; } +#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"] = 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"] = TEXT(file_stat.type); + r["attributes"] = TEXT(file_stat.attributes); + r["file_id"] = TEXT(file_stat.file_id); + r["volume_serial"] = TEXT(file_stat.volume_serial); + +#endif + results.push_back(r); } @@ -167,4 +198,4 @@ QueryData genFile(QueryContext& context) { return results; } } -} +} // namespace osquery diff --git a/specs/utility/file.table b/specs/utility/file.table index 60d38291689..04f6f7a1bfe 100644 --- a/specs/utility/file.table +++ b/specs/utility/file.table @@ -19,6 +19,11 @@ schema([ Column("symlink", INTEGER, "1 if the path is a symlink, otherwise 0"), Column("type", TEXT, "File status"), ]) +extended_schema(WINDOWS, [ + Column("attributes", TEXT, "File attrib string. See: https://ss64.com/nt/attrib.html"), + Column("volume_serial", TEXT, "Volume serial number"), + Column("file_id", TEXT, "file ID"), +]) attributes(utility=True) implementation("utility/file@genFile") examples([