Skip to content

Commit

Permalink
eBPF tracking program for any syscall exit event (osquery#5403)
Browse files Browse the repository at this point in the history
Summary:
Pull Request resolved: osquery#5403

Part of a linux  tracing system, blueprint: [osquery#5218](osquery#5218)

Reviewed By: SAlexandru

Differential Revision: D13690684

fbshipit-source-id: 039fc89929de49fcc7bd2287a98ffc68450fcada
  • Loading branch information
akindyakov authored and Nick Anderson committed Feb 1, 2019
1 parent 3be36be commit 1779ba4
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 101 deletions.
130 changes: 75 additions & 55 deletions osquery/tables/system/windows/processes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,19 @@
#include <osquery/utils/conversions/join.h>
#include <osquery/utils/conversions/tryto.h>
#include <osquery/utils/conversions/windows/strings.h>
#include <osquery/utils/scope_guard.h>

namespace osquery {
int getUidFromSid(PSID sid);
int getGidFromSid(PSID sid);
namespace tables {

enum class WindowsProcessTableError {
HandleOpenError = 1,
ApiCallError = 2,
ProcessReadError = 3,
};

const std::map<unsigned long, std::string> kMemoryConstants = {
{PAGE_EXECUTE, "PAGE_EXECUTE"},
{PAGE_EXECUTE_READ, "PAGE_EXECUTE_READ"},
Expand Down Expand Up @@ -121,7 +128,8 @@ typedef struct _PROCESS_BASIC_INFORMATION {
} PROCESS_BASIC_INFORMATION;

/// Given a pid, enumerates all loaded modules and memory pages for that process
Status genMemoryMap(unsigned long pid, QueryData& results) {
ExpectedSuccess<WindowsProcessTableError> genMemoryMap(unsigned long pid,
QueryData& results) {
auto proc = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid);
if (proc == nullptr) {
Row r;
Expand All @@ -135,13 +143,17 @@ Status genMemoryMap(unsigned long pid, QueryData& results) {
r["path"] = "";
r["pseudo"] = INTEGER(-1);
results.push_back(r);
return Status(1, "Failed to open handle to process " + std::to_string(pid));
return createError(
WindowsProcessTableError::HandleOpenError,
"Failed to open handle to process " + std::to_string(pid));
}
auto modSnap =
CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, pid);
if (modSnap == INVALID_HANDLE_VALUE) {
CloseHandle(proc);
return Status(1, "Failed to enumerate modules for " + std::to_string(pid));
return createError(
WindowsProcessTableError::HandleOpenError,
"Failed to enumerate modules for " + std::to_string(pid));
}

auto formatMemPerms = [](unsigned long perm) {
Expand Down Expand Up @@ -186,14 +198,15 @@ Status genMemoryMap(unsigned long pid, QueryData& results) {
}
CloseHandle(proc);
CloseHandle(modSnap);
return Status(0, "Ok");
return Success{};
}

/// Helper function for enumerating all active processes on the system
Status getProcList(std::set<long>& pids) {
ExpectedSuccess<WindowsProcessTableError> getProcList(std::set<long>& pids) {
auto procSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (procSnap == INVALID_HANDLE_VALUE) {
return Status(1, "Failed to open process snapshot");
return createError(WindowsProcessTableError::HandleOpenError,
"Failed to open process snapshot");
}

PROCESSENTRY32 procEntry;
Expand All @@ -202,7 +215,8 @@ Status getProcList(std::set<long>& pids) {

if (ret == FALSE) {
CloseHandle(procSnap);
return Status(1, "Failed to open first process");
return createError(WindowsProcessTableError::HandleOpenError,
"Failed to open first process");
}

while (ret != FALSE) {
Expand All @@ -211,65 +225,67 @@ Status getProcList(std::set<long>& pids) {
}

CloseHandle(procSnap);
return Status(0, "Ok");
return Success{};
}

/// For legacy systems, we retrieve the commandline from the PEB
Status getProcessCommandLineLegacy(HANDLE proc,
std::string& out,
const unsigned long pid) {
ExpectedSuccess<WindowsProcessTableError> getProcessCommandLineLegacy(
HANDLE proc, std::string& out, const unsigned long pid) {
PROCESS_BASIC_INFORMATION pbi;
unsigned long len{0};
NTSTATUS status = NtQueryInformationProcess(
proc, ProcessBasicInformation, &pbi, sizeof(pbi), &len);

SetLastError(RtlNtStatusToDosError(status));
if (NT_ERROR(status) || !pbi.PebBaseAddress) {
return Status(1,
"NtQueryInformationProcess failed for " +
std::to_string(pid) + " with " + std::to_string(status));
return createError(WindowsProcessTableError::ApiCallError,
"NtQueryInformationProcess failed for " +
std::to_string(pid) + " with " +
std::to_string(status));
}

size_t bytes_read = 0;
PEB peb;
if (!ReadProcessMemory(
proc, pbi.PebBaseAddress, &peb, sizeof(peb), &bytes_read)) {
return Status(1,
"Reading PEB failed for " + std::to_string(pid) + " with " +
std::to_string(status));
return createError(WindowsProcessTableError::ProcessReadError,
"Reading PEB failed for " + std::to_string(pid) +
" with " + std::to_string(status));
}

RTL_USER_PROCESS_PARAMETERS upp;
if (!ReadProcessMemory(
proc, peb.ProcessParameters, &upp, sizeof(upp), &bytes_read)) {
VLOG(1) << "Reading USER_PROCESS_PARAMETERS failed for " << pid;
return Status(
1, "Reading USER_PROCESS_PARAMETERS failed for " + std::to_string(pid));
return createError(
WindowsProcessTableError::ProcessReadError,
"Reading USER_PROCESS_PARAMETERS failed for " + std::to_string(pid));
}

std::unique_ptr<wchar_t> command_line(new wchar_t[kMaxPathSize]);
SecureZeroMemory(command_line.get(), kMaxPathSize);
std::vector<wchar_t> command_line(kMaxPathSize, 0x0);
SecureZeroMemory(command_line.data(), kMaxPathSize);
if (!ReadProcessMemory(proc,
upp.CommandLine.Buffer,
command_line.get(),
command_line.data(),
upp.CommandLine.Length,
&bytes_read)) {
return Status(1, "Failed to read command line for " + std::to_string(pid));
return createError(
WindowsProcessTableError::ProcessReadError,
"Failed to read command line for " + std::to_string(pid));
}
out = wstringToString(command_line.get());
return Status();
out = wstringToString(command_line.data());
return Success{};
}

// On windows 8.1 and later, there's an enum for commandline
Status getProcessCommandLine(HANDLE& proc,
std::string& out,
const unsigned long pid) {
ExpectedSuccess<WindowsProcessTableError> getProcessCommandLine(
HANDLE& proc, std::string& out, const unsigned long pid) {
if (kNtQueryInformationProcess == nullptr) {
VLOG(1) << "Failed to resolve NtQueryInformationProcess with "
<< GetLastError();
return Status(1,
"Failed to resolve NtQueryInformationProcess with " +
std::to_string(GetLastError()));
return createError(WindowsProcessTableError::ApiCallError,
"Failed to resolve NtQueryInformationProcess with " +
std::to_string(GetLastError()));
}

unsigned long size_out = 0;
Expand All @@ -282,53 +298,55 @@ Status getProcessCommandLine(HANDLE& proc,

if (ret != STATUS_BUFFER_OVERFLOW && ret != STATUS_BUFFER_TOO_SMALL &&
ret != STATUS_INFO_LENGTH_MISMATCH) {
return Status(1,
"NtQueryInformationProcess failed for " +
std::to_string(pid) + " with " + std::to_string(ret));
return createError(WindowsProcessTableError::ApiCallError,
"NtQueryInformationProcess failed for " +
std::to_string(pid) + " with " +
std::to_string(ret));
}

auto cmdline = static_cast<PVOID>(malloc(size_out));
ret = kNtQueryInformationProcess(proc, 60, cmdline, size_out, &size_out);
std::vector<char> cmdline(size_out, 0x0);
ret =
kNtQueryInformationProcess(proc, 60, cmdline.data(), size_out, &size_out);

if (!NT_SUCCESS(ret)) {
free(cmdline);
return Status(1,
"NtQueryInformationProcess failed for " +
std::to_string(pid) + " with " + std::to_string(ret));
return createError(WindowsProcessTableError::ApiCallError,
"NtQueryInformationProcess failed for " +
std::to_string(pid) + " with " +
std::to_string(ret));
}
auto ustr = reinterpret_cast<PUNICODE_STRING>(cmdline);
// TODO: Check this.
auto ustr = reinterpret_cast<PUNICODE_STRING>(cmdline.data());
out = wstringToString(ustr->Buffer);
free(cmdline);
return Status();
return Success{};
}

void getProcessPathInfo(HANDLE& proc, const unsigned long pid, DynamicTableRowHolder& r) {
void getProcessPathInfo(HANDLE& proc,
const unsigned long pid,
DynamicTableRowHolder& r) {
auto out = kMaxPathSize;
std::unique_ptr<char> path(new char[kMaxPathSize]);
SecureZeroMemory(path.get(), kMaxPathSize);
auto ret = QueryFullProcessImageName(proc, 0, path.get(), &out);
std::vector<wchar_t> path(kMaxPathSize, 0x0);
auto ret = QueryFullProcessImageName(proc, 0, path.data(), &out);
if (ret != TRUE) {
VLOG(1) << "Failed to lookup path information for process " << pid;
r["path"] = "-1";
} else {
r["path"] = TEXT(path.get());
return;
}
r["path"] = TEXT(path.data());

auto s = osquery::pathExists(path.get());
auto s = osquery::pathExists(path.data());
r["on_disk"] = INTEGER(s.getMessage());

SecureZeroMemory(path.get(), kMaxPathSize);
SecureZeroMemory(path.data(), kMaxPathSize);
if (pid == GetCurrentProcessId()) {
ret = GetModuleFileName(nullptr, path.get(), kMaxPathSize);
ret = GetModuleFileName(nullptr, path.data(), kMaxPathSize);
} else {
ret = GetModuleFileNameEx(proc, nullptr, path.get(), kMaxPathSize);
ret = GetModuleFileNameEx(proc, nullptr, path.data(), kMaxPathSize);
}

if (ret == FALSE) {
VLOG(1) << "Failed to get cwd for " << pid << " with " << GetLastError();
r["cwd"] = "-1";
} else {
r["cwd"] = TEXT(path.get());
r["cwd"] = TEXT(path.data());
}
r["root"] = r["cwd"];
}
Expand Down Expand Up @@ -424,7 +442,9 @@ void genProcRssInfo(HANDLE& proc, DynamicTableRowHolder& r) {
TableRows genProcesses(QueryContext& context) {
TableRows results;

// TODO: Investigate scope guards.
auto proc_snap = CreateToolhelp32Snapshot(TH32CS_SNAPALL, NULL);

if (proc_snap == INVALID_HANDLE_VALUE) {
LOG(ERROR) << "Failed to create snapshot of processes with "
<< std::to_string(GetLastError());
Expand Down
2 changes: 1 addition & 1 deletion osquery/utils/scope_guard.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ namespace scope_guard {
/**
* The RAII based scope guard class.
*
* To be sure that resources are always released/removed/closed/verified/stoped
* To be sure that resources are always released/removed/closed/verified/stopped
* in face of multiple return statements from the function.
*
* It takes functor object by value during the construction. It is going to be
Expand Down
2 changes: 2 additions & 0 deletions specs/processes.table
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ schema([
])
extended_schema(WINDOWS, [
Column("is_elevated_token", INTEGER, "Process uses elevated token yes=1, no=0"),
Column("elapsed_time", BIGINT, "DEPRECATED, WILL BE REMOVED: Elapsed time in seconds this process has been running."),
Column("handle_count", BIGINT, "Total number of handles that the process has open. This number is the sum of the handles currently opened by each thread in the process."),
Column("percent_processor_time", BIGINT, "DEPRECATED, WILL BE REMOVED: Returns elapsed time that all of the threads of this process used the processor to execute instructions in 100 nanoseconds ticks."),
])
extended_schema(DARWIN, [
Column("upid", BIGINT, "A 64bit pid that is never reused. Returns -1 if we couldn't gather them from the system."),
Expand Down
69 changes: 24 additions & 45 deletions tests/integration/tables/processes.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
Expand All @@ -16,58 +15,38 @@ namespace osquery {
namespace table_tests {

class processes : public testing::Test {
protected:
void SetUp() override {
setUpEnvironment();
}
protected:
void SetUp() override {
setUpEnvironment();
}
};

TEST_F(processes, test_sanity) {
// 1. Query data
auto const data = execute_query("select * from processes");

// 2. Check size before validation
// ASSERT_GE(data.size(), 0ul);
// ASSERT_EQ(data.size(), 1ul);
// ASSERT_EQ(data.size(), 0ul);
ASSERT_GE(data.size(), 0ul);
ASSERT_EQ(data.size(), 1ul);
ASSERT_EQ(data.size(), 0ul);

// 3. Build validation map
// See helper.h for avaialbe flags
// Or use custom DataCheck object
// ValidatatioMap row_map = {
// {"pid", IntType}
// {"name", NormalType}
// {"path", NormalType}
// {"cmdline", NormalType}
// {"state", NormalType}
// {"cwd", NormalType}
// {"root", NormalType}
// {"uid", IntType}
// {"gid", IntType}
// {"euid", IntType}
// {"egid", IntType}
// {"suid", IntType}
// {"sgid", IntType}
// {"on_disk", IntType}
// {"wired_size", IntType}
// {"resident_size", IntType}
// {"total_size", IntType}
// {"user_time", IntType}
// {"system_time", IntType}
// {"disk_bytes_read", IntType}
// {"disk_bytes_written", IntType}
// {"start_time", IntType}
// {"parent", IntType}
// {"pgroup", IntType}
// {"threads", IntType}
// {"nice", IntType}
// {"is_elevated_token", IntType}
// {"upid", IntType}
// {"uppid", IntType}
// {"cpu_type", IntType}
// {"cpu_subtype", IntType}
//}
// See helper.h for available flags or use custom DataCheck object
ValidatatioMap row_map =
{ {"pid", IntType} {"name", NormalType} {"path", NormalType} {
"cmdline", NormalType} {"state", NormalType} {"cwd", NormalType} {
"root", NormalType} {"uid", IntType} {"gid", IntType} {"euid", IntType} {
"egid", IntType} {"suid", IntType} {"sgid", IntType} {
"on_disk", IntType} {"wired_size", IntType} {"resident_size", IntType} {
"total_size", IntType} {"user_time", IntType} {"system_time", IntType} {
"disk_bytes_read", IntType} {"disk_bytes_written", IntType} {
"start_time", IntType} {"parent", IntType} {"pgroup", IntType} {
"threads", IntType} {"nice", IntType} {"is_elevated_token", IntType} {
"upid", IntType} {"uppid", IntType} {"cpu_type", IntType} {"cpu_subtype",
IntType} }

// 4. Perform validation
// validate_rows(data, row_map);
validate_rows(data, row_map);
}

} // namespace table_tests
} // namespace osquery

0 comments on commit 1779ba4

Please sign in to comment.