Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
11 changes: 11 additions & 0 deletions Microsoft.O365.Security.Native.ETW/Testing/RecordBuilder.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ namespace Microsoft { namespace O365 { namespace Security { namespace ETW { name
/// </summary>
void AddContainerId(System::Guid container_id);

/// <summary>
/// Adds a process start key extended data item
/// </summary>
void AddProcessStartKey(System::UInt64 process_start_key);

internal:
NativePtr<krabs::testing::record_builder> builder_;

Expand Down Expand Up @@ -185,4 +190,10 @@ namespace Microsoft { namespace O365 { namespace Security { namespace ETW { name
builder_->add_container_id_extended_data(ConvertGuid(container_id));
}

inline void RecordBuilder::AddProcessStartKey(System::UInt64 process_start_key)
{
ULONG64 key = process_start_key;
builder_->add_process_start_key_extended_data(key);
}

} /* namespace Testing */ } /* namespace ETW */ } /* namespace Security */ } /* namespace O365 */ } /* namespace Microsoft */
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
12 changes: 12 additions & 0 deletions krabs/krabs/testing/record_builder.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,13 @@ namespace krabs { namespace testing {
*/
void add_container_id_extended_data(const GUID& container_id);

/**
* <summary>
* Adds extended data representing a process start key
* </summary>
*/
void add_process_start_key_extended_data(const ULONG64& process_start_key);

/**
* <summary>
* Gives direct access to the EVENT_HEADER that will be packed into
Expand Down Expand Up @@ -308,6 +315,11 @@ namespace krabs { namespace testing {
extended_data_.add_container_id(container_id);
}

inline void record_builder::add_process_start_key_extended_data(const ULONG64& process_start_key)
{
extended_data_.add_process_start_key(process_start_key);
}

inline std::pair<std::vector<BYTE>, std::vector<std::wstring>>
record_builder::pack_impl(const EVENT_RECORD &record) const
{
Expand Down
18 changes: 18 additions & 0 deletions tests/ManagedETWTests/Events/PowerShellEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,23 @@ public static SynthRecord CreateRecordWithContainerId(
return rb.Pack();
}
}

public static SynthRecord CreateRecordWithProcessStartKey(
string userData,
string contextInfo,
string payload,
ulong processStartKey)
{
using (var rb = new RecordBuilder(ProviderId, EventId, Version))
{
rb.AddUnicodeString(UserData, userData);
rb.AddUnicodeString(ContextInfo, contextInfo);
rb.AddUnicodeString(Payload, payload);

rb.AddProcessStartKey(processStartKey);

return rb.Pack();
}
}
}
}
33 changes: 33 additions & 0 deletions tests/ManagedETWTests/describe_EventRecord.cs
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,39 @@ public void it_should_read_container_id()
proxy.PushEvent(PowerShellEvent.CreateRecordWithContainerId(
"Test data", String.Empty, String.Empty, guid));
}

[TestMethod]
public void it_should_read_process_start_key()
{
ulong expectedKey = 0x123456789ABCDEF0;
var provider = new Provider(PowerShellEvent.ProviderId);
provider.OnEvent += e =>
{
ulong processStartKey;
Assert.IsTrue(e.TryGetProcessStartKey(out processStartKey));
Assert.AreEqual(expectedKey, processStartKey);
};

trace.Enable(provider);
proxy.PushEvent(PowerShellEvent.CreateRecordWithProcessStartKey(
"Test data", String.Empty, String.Empty, expectedKey));
}

[TestMethod]
public void it_should_return_false_when_process_start_key_not_present()
{
var provider = new Provider(PowerShellEvent.ProviderId);
provider.OnEvent += e =>
{
ulong processStartKey;
Assert.IsFalse(e.TryGetProcessStartKey(out processStartKey));
Assert.AreEqual(0UL, processStartKey);
};

trace.Enable(provider);
proxy.PushEvent(PowerShellEvent.CreateRecord(
"Test data", String.Empty, String.Empty));
}
}

[TestClass]
Expand Down
Loading
Loading