[Fix #758] Parse startup_items Alias data

This commit is contained in:
Teddy Reed 2015-02-13 17:40:02 -08:00
parent deae24b662
commit 1ea06a9d15
3 changed files with 112 additions and 103 deletions

View File

@ -25,6 +25,8 @@ namespace osquery {
typedef bai::binary_from_base64<const char*> base64_str;
typedef bai::transform_width<base64_str, 8, 6> base64_dec;
typedef bai::transform_width<std::string::const_iterator, 6, 8> base64_enc;
typedef bai::base64_from_binary<base64_enc> it_base64;
std::string base64Decode(const std::string& encoded) {
std::string is;
@ -44,7 +46,7 @@ std::string base64Decode(const std::string& encoded) {
}
if (size == 0) {
return std::string();
return "";
}
std::copy(base64_dec(is.data()),
@ -57,14 +59,12 @@ std::string base64Decode(const std::string& encoded) {
std::string base64Encode(const std::string& unencoded) {
std::stringstream os;
typedef boost::archive::iterators::base64_from_binary<boost::archive::iterators::transform_width<std::string::const_iterator,6,8> > it_base64_t;
uint32_t size = unencoded.size();
if (size == 0) {
if (unencoded.size() == 0) {
return std::string();
}
unsigned int writePaddChars = (3-unencoded.length()%3)%3;
std::string base64(it_base64_t(unencoded.begin()),it_base64_t(unencoded.end()));
std::string base64(it_base64(unencoded.begin()), it_base64(unencoded.end()));
base64.append(writePaddChars,'=');
os << base64;
return os.str();

View File

@ -30,6 +30,15 @@ TEST_F(ConversionsTests, test_conversion) {
boost::shared_ptr<Foobar> b2 = std_to_boost_shared_ptr(s2);
EXPECT_EQ(s2.get(), b2.get());
}
TEST_F(ConversionsTests, test_base64) {
std::string unencoded = "HELLO";
auto encoded = base64Encode(unencoded);
EXPECT_NE(encoded.size(), 0);
auto unencoded2 = base64Decode(encoded);
EXPECT_EQ(unencoded, unencoded2);
}
}
int main(int argc, char* argv[]) {

View File

@ -8,8 +8,6 @@
*
*/
#include <signal.h>
#include <boost/filesystem.hpp>
#include <osquery/core.h>
@ -17,6 +15,8 @@
#include <osquery/filesystem.h>
#include <osquery/logger.h>
#include "osquery/core/conversions.h"
namespace fs = boost::filesystem;
namespace pt = boost::property_tree;
@ -30,115 +30,115 @@ const std::vector<std::string> kLibraryStartupItemPaths = {
// Path (after /Users/foo) where the login items plist will be found
const std::string kLoginItemsPlistPath =
"Library/Preferences/com.apple.loginitems.plist";
// Key to the array within the Login Items plist containing the items
const std::string kLoginItemsKeyPath = "SessionItems.CustomListItems";
/**
* Find startup items in Library directories
*
* Based on
* https://github.com/synack/knockknock/blob/master/plugins/startupItem.py
*/
void getLibraryStartupItems(QueryData& results) {
for (const auto& dir : kLibraryStartupItemPaths) {
fs::directory_iterator it((fs::path(dir))), end;
try {
for (; it != end; ++it) {
if (!fs::exists(it->status()) || !fs::is_directory(it->status())) {
continue;
}
Row r;
r["name"] = it->path().string();
r["path"] = it->path().string();
r["type"] = "Startup Item";
r["source"] = dir;
results.push_back(r);
}
} catch (const fs::filesystem_error& e) {
VLOG(1) << "Error traversing " << dir << ": " << e.what();
}
}
}
/**
* Parse a Login Items Plist Alias data for bin path
*/
Status parseAliasData(const std::string& data, std::string& filepath) {
for (int i = 0; i < data.size(); i++) {
int size = (int)data[i];
if (size < 2 || size > data.length() - i) {
continue;
}
std::string possible_file = "/" + data.substr(i + 1, size);
// This data sometimes contains null bytes. We don't want to consider a
// path that wasn't the expected length due to null bytes.
if (strlen(possible_file.c_str()) != size + 1) {
continue;
}
if (fs::exists(possible_file)) {
filepath = possible_file;
return Status(0, "OK");
}
}
return Status(1, "No file paths found");
}
/*
* Get the login items available in System Preferences
*
* Based on
* https://github.com/synack/knockknock/blob/master/plugins/loginItem.py
*/
void getLoginItems(QueryData& results) {
for (const auto& dir : getHomeDirectories()) {
pt::ptree tree;
fs::path plist_path = dir / kLoginItemsPlistPath;
try {
if (!fs::exists(plist_path) || !fs::is_regular_file(plist_path)) {
void genLibraryStartupItems(const std::string& sysdir, QueryData& results) {
try {
fs::directory_iterator it((fs::path(sysdir))), end;
for (; it != end; ++it) {
if (!fs::exists(it->status()) || !fs::is_directory(it->status())) {
continue;
}
} catch (const fs::filesystem_error& e) {
// Likely permission denied
VLOG(1) << "Error checking path " << plist_path << ": " << e.what();
continue;
}
auto status = osquery::parsePlist(plist_path.string(), tree);
if (!status.ok()) {
VLOG(1) << "Error parsing " << plist_path << ": " << status.toString();
continue;
}
// Enumerate Login Items if we successfully opened the plist
for (const auto& entry : tree.get_child(kLoginItemsKeyPath)) {
Row r;
auto name = entry.second.get<std::string>("Name");
r["name"] = name;
r["type"] = "Login Item";
r["source"] = plist_path.string();
auto alias_data = entry.second.get<std::string>("Alias");
try {
std::string bin_path;
if (!parseAliasData(alias_data, bin_path).ok()) {
VLOG(1) << "No valid path found for " << name << " in " << plist_path;
}
r["path"] = bin_path;
} catch (const std::exception& e) {
VLOG(1) << "Error parsing alias data for " << name << " in "
<< plist_path;
}
r["name"] = it->path().string();
r["path"] = it->path().string();
r["type"] = "Startup Item";
r["source"] = sysdir;
results.push_back(r);
}
} catch (const fs::filesystem_error& e) {
VLOG(1) << "Error traversing " << sysdir << ": " << e.what();
}
}
/// Parse a Login Items Plist Alias data for bin path
Status parseAliasData(const std::string& data, std::string& result) {
auto decoded = base64Decode(data);
if (decoded.size() == 0) {
// Base64 encoded data (from plist parsing) failed to decode.
return Status(1, "Failed base64 decode");
}
auto alias = CFDataCreate(
kCFAllocatorDefault, (const UInt8*)decoded.c_str(), decoded.size());
if (alias == nullptr) {
// Failed to create CFData object.
return Status(2, "CFData allocation failed");
}
auto bookmark =
CFURLCreateBookmarkDataFromAliasRecord(kCFAllocatorDefault, alias);
if (bookmark == nullptr) {
CFRelease(alias);
return Status(1, "Alias data is not a bookmark");
}
auto url = CFURLCreateByResolvingBookmarkData(
kCFAllocatorDefault, bookmark, 0, NULL, NULL, NULL, NULL);
if (url == nullptr) {
CFRelease(alias);
CFRelease(bookmark);
return Status(1, "Alias data is not a URL bookmark");
}
// Get the URL-formatted path.
result = stringFromCFString(CFURLGetString(url));
if (result.substr(0, 7) == "file://") {
result = result.substr(7);
}
CFRelease(alias);
CFRelease(bookmark);
CFRelease(url);
return Status(0, "OK");
}
void genLoginItems(const fs::path& homedir, QueryData& results) {
pt::ptree tree;
fs::path sipath = homedir / kLoginItemsPlistPath;
if (!pathExists(sipath.string()).ok() || !isReadable(sipath.string()).ok()) {
// User does not have a startup items list, or bad permissions.
return;
}
if (!osquery::parsePlist(sipath.string(), tree).ok()) {
// Could not parse the user's startup items plist.
return;
}
// Enumerate Login Items if we successfully opened the plist.
for (const auto& entry : tree.get_child(kLoginItemsKeyPath)) {
Row r;
r["name"] = entry.second.get<std::string>("Name");
r["type"] = "Login Item";
r["source"] = sipath.string();
auto alias_data = entry.second.get<std::string>("Alias");
std::string bin_path;
if (!parseAliasData(alias_data, bin_path).ok()) {
VLOG(1) << "No valid path found for " << r["name"] << " in " << sipath;
}
r["path"] = bin_path;
results.push_back(r);
}
}
QueryData genStartupItems(QueryContext& context) {
QueryData results;
getLoginItems(results);
getLibraryStartupItems(results);
// Get the login items available in System Preferences for each user.
for (const auto& dir : getHomeDirectories()) {
genLoginItems(dir, results);
}
// Find system wide startup items in Library directories.
for (const auto& dir : kLibraryStartupItemPaths) {
genLibraryStartupItems(dir, results);
}
return results;
}
}