Emitting an event through an existing ETW provider
Let's assume a simple scenario.
I have an application that uses native plugins. I have my own binary format for
these plugins. Each plugin is loaded at runtime, using a method similar to
mapping a DLL into the process' space. This means, each plugin has its own
ImageBase
, sections like .text
or .data
are handled in the same way as in
conventional DLLs. The only thing that is different is the binary format of the
plugin (it's not a PE
file) and the loader code that maps the plugin into the
process space, which is located in my custom, plugin-loading application.
We know how ETW behaves when doing the trace by using this command line:
xperf -on latency -stackwalk profile -buffersize 1024 -minbuffers 300 -start tracea1 -on Microsoft-Windows-Win32k:::'stack'
It will emit events that can be used to reconstruct the process environment
during the trace capture. That is, it will emit events like add process, add
thread to a process, add DLL module to a process, so that tools like
xperfview
or wpa
can build a virtual environment of the state of processes
in the system, and build information like current process tree. These events
are, for example, ImageLoad events that provide information about each DLL that
is being loaded before, or during the trace.
Of course, for our plugins, these ImageLoad
events are not generated, because
they're not technically DLLs (that is, not loaded by the same system functions
as normal DLL files, although their function is the same). This is why tools
like xperfview
dosn't know about their existence in the process space.
What I wanted to do, is to write my own EventWrite
's in my plugin loader code,
and emit these ImageLoad
events with necessary information, so that
xperfview
, and similar tools, can interpret my plugins as normal DLLs. I would
fill up necessary information like ImageBase
, ProcessId
, ImageSize
, etc.
To do this, I started to try to solve the problem by registering the event provider
MSNT_SystemTrace
, which is the owner of ImageLoad
events, and trying to
build the event with this kind of structure:
<Data Name="ImageBase">0x7FEFDBD0000</Data>
<Data Name="ImageSize">0x12D000</Data>
<Data Name="ProcessId"> 548</Data>
...
<Data Name="Reserved0"> 0</Data>
<Data Name="DefaultBase">0x7FEFDBD0000</Data>
After event building would be complete, maybe I could send the event.
The problem I had that I was getting the ERROR_ACCESS_DENIED
when trying to
register another MSNT_SystemTrace
provider, which was logical, since that
provider already existed.
After some time, digging and reading, I've found a solution that solves my problem in a slightly different way.
While I still don't know how to emit an event through an existing provider in
realtime, it seems that Windows 8 exposes an interface which allows to modify
ETL
trace logs, so it's possible to alter the ProviderId
of an event to a
different value. The interface in question is ITraceRelogger
.
To be able to use it, these GUID
's are needed:
EXTERN_GUID(CLSID_TraceRelogger, 0x7b40792d, 0x05ff, 0x44c4, 0x90, 0x58, 0xf4, 0x40, 0xc7, 0x1f, 0x17, 0xd4);
DEFINE_GUID(IID_ITraceRelogger, 0xF754AD43, 0x3BCC, 0x4286, 0x80, 0x09,0x9C, 0x5D, 0xA2, 0x14, 0xE8, 0x4E); // {F754AD43-3BCC-4286-8009-9C5DA214E84E}
DEFINE_GUID(IID_ITraceEventCallback, 0x3ED25501, 0x593F, 0x43E9, 0x8F, 0x38,0x3A, 0xB4, 0x6F, 0x5A, 0x4A, 0x52); // {3ED25501-593F-43E9-8F38-3AB46F5A4A52}
Together with these values, the relogger.h
file from Windows 8 SDK from this
path is needed:
c:\program files (x86)\windows kits\8.0\include\um\relogger.h
Original relogger.h
seems to be broken somehow, because it references some
external symbols, but it seems there's no library file (.lib
) to supplement
it. I'm sure a hacker like you can resolve it though!
To use it, it's necessary to create its instance by:
ITraceRelogger *relog = NULL;
hres = CoCreateInstance(CLSID_TraceRelogger, 0, CLSCTX_INPROC_SERVER, IID_ITraceRelogger2, (LPVOID *)& relog);
Then, input.etl
and output.etl
files need to be specified:
#include <windows.h>
#include <cguid.h>
#include <atlbase.h>
#include <comdef.h>
// ...
CComBSTR input = "input.etl";
CComBSTR output = "output.etl";
// ...
hres = relog->AddLogfileTraceStream(input, NULL, & trace);
// ...
hres = relog->SetOutputFilename(output);
Then, we need to register a callback, which will handle event modification(s).
Event callback's implementation example is placed at the end of this post. Here
is the code which demonstrates how to use it with current ITraceRelogger
:
EventCallback *ec = new EventCallback();
hres = relog->RegisterCallback(ec);
// ...
hres = relog->ProcessTrace();
Warning: ProcessTrace()
will return an error if you haven't registered any
callbacks to the interface.
Here is an example of a working callback:
class EventCallback: public ITraceEventCallback {
private:
DWORD ref_count;
DWORD64 evno;
public:
EventCallback() {
ref_count = 0;
evno = 0;
}
STDMETHODIMP QueryInterface(const IID& iid, void **obj) {
if(iid == IID_IUnknown) {
*obj = dynamic_cast<IUnknown *>(this);
} else if(iid == IID_ITraceEventCallback) {
*obj = dynamic_cast<ITraceEventCallback *>(this);
} else {
*obj = NULL;
return E_NOINTERFACE;
}
return S_OK;
}
STDMETHODIMP_ (ULONG) AddRef(void) {
return InterlockedIncrement(& ref_count);
}
STDMETHODIMP_ (ULONG) Release() {
ULONG ucount = InterlockedDecrement(& ref_count);
if(ucount == 0) {
delete this;
}
return ucount;
}
HRESULT STDMETHODCALLTYPE OnBeginProcessTrace(ITraceEvent *HeaderEvent, ITraceRelogger *Relogger) {
return S_OK;
}
HRESULT STDMETHODCALLTYPE OnEvent(ITraceEvent *Event, ITraceRelogger *Relogger) {
// Your main method.
evno++;
Relogger->Inject(Event);
}
HRESULT STDMETHODCALLTYPE OnFinalizeProcessTrace(ITraceRelogger *Relogger) {
return S_OK;
}
};
Relogger->Inject()
will copy current Event
to the output file. It is
possible to use MSDN entry for ITraceRelogger
to check for the available
methods, which allow us to change the desired event properties. The method that
I was interested most was named SetProviderId()
.
Also, the MSDN entry states that ITraceRelogger
is available since Windows 7.
it is not the whole truth, because you will need KB2882822 to be able to use it on
Windows 7. However, on Windows 8 it should be available without installation of
additional patches.
PS. Yes, this note first was "presented" on StackOverflow in the form of a question, but since I've answered my question myself, I think it's legit to include it on this blog for future reference.