Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/krabsetw.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ jobs:

steps:
- uses: actions/checkout@v2
- uses: actions/setup-dotnet@v4
with:
global-json-file: global.json
- uses: microsoft/setup-msbuild@v1
- uses: NuGet/setup-nuget@v1.0.5
- uses: darenm/Setup-VSTest@v1
Expand Down
30 changes: 30 additions & 0 deletions Microsoft.O365.Security.Native.ETW/EventRecordMetadata.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@
#include "IEventRecordMetadata.hpp"
#include "Guid.hpp"

// Windows SDK may not define this constant on older SDK versions.
#ifndef EVENT_HEADER_EXT_TYPE_PROCESS_START_KEY
#define EVENT_HEADER_EXT_TYPE_PROCESS_START_KEY 0x000B
#endif

using namespace System;
using namespace System::Runtime::InteropServices;

Expand Down Expand Up @@ -232,6 +237,31 @@ namespace Microsoft { namespace O365 { namespace Security { namespace ETW {
return false;
}

/// <summary>
/// If the event's extended data contains a process start key
/// (enabled via EVENT_ENABLE_PROPERTY_PROCESS_START_KEY), retrieve it.
/// </summary>
virtual bool TryGetProcessStartKey([Out] uint64_t% result)
{
auto extended_data_count = record_->ExtendedDataCount;
for (USHORT i = 0; i < extended_data_count; i++)
{
auto& extended_data = record_->ExtendedData[i];

if (extended_data.ExtType == EVENT_HEADER_EXT_TYPE_PROCESS_START_KEY)
{
if (extended_data.DataPtr != 0)
{
result = *reinterpret_cast<const ULONG64*>(extended_data.DataPtr);
return true;
}
}
}

result = 0;
return false;
}

#pragma endregion
};

Expand Down
11 changes: 11 additions & 0 deletions Microsoft.O365.Security.Native.ETW/IEventRecordMetadata.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,17 @@ namespace Microsoft { namespace O365 { namespace Security { namespace ETW {
/// </returns>
bool TryGetContainerId([Out] System::Guid% result);

/// <summary>
/// If the event's extended data contains a process start key
/// (enabled via EVENT_ENABLE_PROPERTY_PROCESS_START_KEY), retrieve it.
/// The process start key uniquely identifies a process instance across
/// the lifetime of a boot session, unlike PID which can be recycled.
/// </summary>
/// <returns>
/// True if a process start key was present. False if not.
/// </returns>
bool TryGetProcessStartKey([Out] uint64_t% result);

#pragma endregion
};

Expand Down
1 change: 1 addition & 0 deletions examples/ManagedExamples/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ static void Main(string[] args)
//UserTrace005.Start();
//UserTrace006_Rundown.Start();
//UserTrace007_StackTrace.Start();
//UserTrace008_ProcessStartKey.Start();
//FakingEvents001.Start();
//WppTrace001.Start();
}
Expand Down
73 changes: 73 additions & 0 deletions examples/ManagedExamples/UserTrace008_ProcessStartKey.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

// This example demonstrates reading the ProcessStartKey from ETW extended data items.
// The ProcessStartKey uniquely identifies a process instance across a boot session
// (unlike PID which can be recycled).

using System;
using System.Security.Principal;
using System.Threading;
using Microsoft.O365.Security.ETW;

namespace ManagedExamples
{
public static class UserTrace008_ProcessStartKey
{
public static void Start()
{
if (!(new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator)))
{
Console.WriteLine("Microsoft-Windows-Kernel-Process provider requires Administrator privileges.");
return;
}

var trace = new UserTrace("UserTrace008_ProcessStartKey");
var provider = new Provider("Microsoft-Windows-Kernel-Process");
provider.Any = 0x10; // WINEVENT_KEYWORD_PROCESS
provider.TraceFlags |= TraceFlags.IncludeProcessStartKey;

int eventCount = 0;
int eventsWithKey = 0;
const int maxEvents = 10;

// Listen for ProcessStart (1) and ProcessStop (2)
var filter = new EventFilter(Filter.EventIdIs(1).Or(Filter.EventIdIs(2)));
filter.OnEvent += (record) =>
{
var pid = record.GetUInt32("ProcessID");
string imageName;
try { imageName = record.GetUnicodeString("ImageName"); }
catch { try { imageName = record.GetAnsiString("ImageName"); } catch { imageName = "<unknown>"; } }

ulong processStartKey = 0;
bool hasKey = record.TryGetProcessStartKey(out processStartKey);

if (hasKey && processStartKey != 0)
Interlocked.Increment(ref eventsWithKey);

int count = Interlocked.Increment(ref eventCount);

Console.WriteLine($"[{record.TaskName}] PID={pid} ImageName={imageName} " +
$"HasProcessStartKey={hasKey} ProcessStartKey=0x{processStartKey:X}");

if (count >= maxEvents)
{
Console.WriteLine($"\nReceived {count} events, {eventsWithKey} had a non-zero ProcessStartKey.");
if (eventsWithKey > 0)
Console.WriteLine("PASS: ProcessStartKey is being populated in extended data.");
else
Console.WriteLine("FAIL: No events had a ProcessStartKey set.");
trace.Stop();
}
};

provider.AddFilter(filter);
trace.Enable(provider);

Console.WriteLine("Listening for process start/stop events (will stop after 10 events)...");
Console.WriteLine("Tip: Start or stop some processes to generate events.\n");
trace.Start();
}
}
}
6 changes: 6 additions & 0 deletions global.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"sdk": {
"version": "8.0.100",
"rollForward": "latestFeature"
}
}
39 changes: 39 additions & 0 deletions krabs/krabs/schema.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@
#include "compiler_check.hpp"
#include "schema_locator.hpp"

// Windows SDK may not define this constant on older SDK versions.
#ifndef EVENT_HEADER_EXT_TYPE_PROCESS_START_KEY
#define EVENT_HEADER_EXT_TYPE_PROCESS_START_KEY 0x000B
#endif

#pragma comment(lib, "tdh.lib")


Expand Down Expand Up @@ -282,6 +287,24 @@ namespace krabs {
*/
std::vector<ULONG64> stack_trace() const;

/**
* <summary>
* Retrieves the process start key from the extended data, if enabled.
* The process start key is a ULONG64 that uniquely identifies a process
* instance across the lifetime of a boot session (unlike PID which can
* be recycled). Requires EVENT_ENABLE_PROPERTY_PROCESS_START_KEY.
* Returns 0 if the extended data item is not present.
* </summary>
* <example>
* void on_event(const EVENT_RECORD &record, const krabs::trace_context &trace_context)
* {
* krabs::schema schema(record, trace_context.schema_locator);
* ULONG64 psk = schema.process_start_key();
* }
* </example>
*/
ULONG64 process_start_key() const;

private:
const EVENT_RECORD &record_;
const TRACE_EVENT_INFO *pSchema_;
Expand All @@ -299,6 +322,8 @@ namespace krabs {
friend int event_id(const schema &);
friend std::vector<ULONG64> stack_trace(const schema&);
friend std::vector<ULONG64> stack_trace(const EVENT_RECORD&);
friend ULONG64 process_start_key(const schema&);
friend ULONG64 process_start_key(const EVENT_RECORD&);

friend class parser;
friend class property_iterator;
Expand Down Expand Up @@ -461,4 +486,18 @@ namespace krabs {

return call_stack;
}

inline ULONG64 schema::process_start_key() const
{
for (USHORT i = 0; i < record_.ExtendedDataCount; i++)
{
auto& item = record_.ExtendedData[i];
if (item.ExtType == EVENT_HEADER_EXT_TYPE_PROCESS_START_KEY)
{
if (item.DataPtr != 0)
return *reinterpret_cast<const ULONG64*>(item.DataPtr);
}
}
return 0;
}
}
15 changes: 15 additions & 0 deletions krabs/krabs/testing/extended_data_builder.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
#define EVENT_HEADER_EXT_TYPE_CONTAINER_ID 16
#endif

#ifndef EVENT_HEADER_EXT_TYPE_PROCESS_START_KEY
#define EVENT_HEADER_EXT_TYPE_PROCESS_START_KEY 0x000B
#endif

namespace krabs { namespace testing {
class extended_data_builder;

Expand Down Expand Up @@ -65,6 +69,9 @@ namespace krabs { namespace testing {
// Mocks a container ID type extended data item.
void add_container_id(const GUID& container_id);

// Mocks a process start key type extended data item.
void add_process_start_key(const ULONG64& process_start_key);

// This generates a contiguous buffer holding all of the data for
// the extended data items. Non-trivial because the actual structs
// have to be a contiguous array, and they each contain pointers,
Expand Down Expand Up @@ -107,6 +114,14 @@ namespace krabs { namespace testing {
items_.emplace_back(static_cast<USHORT>(EVENT_HEADER_EXT_TYPE_CONTAINER_ID), guid_data, GUID_STRING_LENGTH_NO_BRACES);
}

inline void extended_data_builder::add_process_start_key(const ULONG64& process_start_key)
{
items_.emplace_back(
static_cast<USHORT>(EVENT_HEADER_EXT_TYPE_PROCESS_START_KEY),
const_cast<BYTE*>(reinterpret_cast<const BYTE*>(&process_start_key)),
sizeof(ULONG64));
}

inline std::pair<std::shared_ptr<BYTE[]>, size_t> extended_data_builder::pack() const
{
// Return null for buffer if there are no extended data items.
Expand Down
Loading