Add quicklook_cache to Darwin (#2099)

This commit is contained in:
Teddy Reed 2016-05-13 23:49:10 -07:00
parent 77273f6500
commit 9c01d4a6e3
3 changed files with 160 additions and 1 deletions

View File

@ -0,0 +1,140 @@
/*
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#include <boost/property_tree/ptree.hpp>
#include <osquery/filesystem.h>
#include <osquery/logger.h>
#include <osquery/tables.h>
#include "osquery/core/conversions.h"
#include "osquery/sql/sqlite_util.h"
namespace pt = boost::property_tree;
namespace osquery {
namespace tables {
/**
* @brief The Apple reference date offset.
*
* Some Apple epoch dates use 1/1/2001 UTC as the beginning of time.
* Since *most* things in osquery are UNIX epoch, append the 1970's offset.
*/
const size_t kReferenceDateOffset = 978307200;
/// Basic GLOB pattern for discovering caches.
const std::string kQuicklookPattern =
"/private/var/folders/%/%/%/com.apple.QuickLook.thumbnailcache/"
"index.sqlite";
void genQuicklookRow(sqlite3_stmt* stmt, Row& r) {
for (int i = 0; i < sqlite3_column_count(stmt); i++) {
auto column_name = std::string(sqlite3_column_name(stmt, i));
auto column_type = sqlite3_column_type(stmt, i);
if (column_type == SQLITE_TEXT) {
auto value = sqlite3_column_text(stmt, i);
if (value != nullptr) {
r[column_name] = std::string((const char*)value);
}
} else if (column_type == SQLITE_INTEGER) {
// Handle INTEGER columns explicitly to handle the date-value offset.
auto value = sqlite3_column_int(stmt, i);
if (column_name == "last_hit_date") {
value += kReferenceDateOffset;
}
r[column_name] = INTEGER(value);
} else if (column_type == SQLITE_BLOB) {
// Handle BLOB values explicitly to avoid the default char* termination
// for binary-plist data.
auto getField = [](const pt::ptree& tree, const std::string& field) {
if (field == "mtime" && tree.count(field) > 0) {
// Apply a special case for embedded date-value fields.
return INTEGER(tree.get<size_t>(field) + kReferenceDateOffset);
}
return (tree.count(field) > 0) ? tree.get<std::string>(field) : "";
};
if (column_name == "version") {
pt::ptree tree;
auto version = std::string((const char*)sqlite3_column_blob(stmt, i),
sqlite3_column_bytes(stmt, i));
if (parsePlistContent(version, tree)) {
r["mtime"] = getField(tree, "date");
r["size"] = getField(tree, "size");
r["label"] = getField(tree, "gen");
}
}
}
}
// Transform the folder/file_name into an aggregate path.
r["path"] = std::move(r["folder"]) + "/" + std::move(r["file_name"]);
r.erase("folder");
r.erase("file_name");
// Transform the encoded fs_id.
auto details = osquery::split(r["fs_id"], "=.");
if (details.size() == 4) {
r["volume_id"] = details[2];
r["inode"] = details[3];
}
}
QueryData genQuicklookCache(QueryContext& context) {
QueryData results;
// There may be several quick look caches.
// Apply a GLOB search since the folder is randomized.
std::vector<std::string> databases;
if (!resolveFilePattern(kQuicklookPattern, databases)) {
return results;
}
for (const auto& index : databases) {
sqlite3* db = nullptr;
auto rc = sqlite3_open_v2(
("file://" + index).c_str(), &db,
(SQLITE_OPEN_READONLY | SQLITE_OPEN_PRIVATECACHE | SQLITE_OPEN_NOMUTEX),
nullptr);
if (rc != SQLITE_OK || db == nullptr) {
VLOG(1) << "Cannot open " << index << " read only";
if (db != nullptr) {
free(db);
}
continue;
}
// QueryData file_results;
std::string query =
"SELECT f.*, last_hit_date, hit_count, icon_mode FROM (SELECT rowid, * "
"FROM files) f, (SELECT *, max(last_hit_date) AS last_hit_date FROM "
"thumbnails GROUP BY file_id) t WHERE t.file_id = rowid;";
sqlite3_stmt* stmt = nullptr;
rc = sqlite3_prepare_v2(db, query.c_str(), -1, &stmt, nullptr);
while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) {
Row r;
genQuicklookRow(stmt, r);
// For each row added to the results from this database, add the path to
// the database, then move into the table's result set.
r["cache_path"] = index;
results.push_back(r);
}
// Clean up.
sqlite3_finalize(stmt);
free(db);
}
return results;
}
}
}

View File

@ -0,0 +1,19 @@
table_name("quicklook_cache")
description("Files and thumbnails within OS X's Quicklook Cache")
schema([
Column("path", TEXT, "Path of file"),
Column("rowid", INTEGER, "Quicklook file rowid key"),
Column("fs_id", TEXT, "Quicklook file fs_id key"),
Column("volume_id", INTEGER, "Parsed volume ID from fs_id"),
Column("inode", INTEGER, "Parsed file ID (inode) from fs_id"),
Column("mtime", INTEGER, "Parsed version date field"),
Column("size", INTEGER, "Parsed version size field"),
Column("label", TEXT, "Parsed version 'gen' field"),
Column("last_hit_date", INTEGER,
"Apple date format for last thumbnail cache hit"),
Column("hit_count", TEXT, "Number of cache hits on thumbnail"),
Column("icon_mode", BIGINT, "Thumbnail icon mode"),
Column("cache_path", TEXT, "Path to cache data"),
])
attributes(cachable=True)
implementation("quicklook_cache@genQuicklookCache")

@ -1 +1 @@
Subproject commit a7c9f15f4bece0ecd1e9d66c9c6983e1750524c9
Subproject commit a5d9d9266031e1bbdced2ea268a26d1760e60127