tags:

views:

117

answers:

3

I've tried, with little success, to identify how the variable length portion of the EVENTLOGRECORD data works.

Winnt.h defines the structure, and the following data, as follows:

typedef struct _EVENTLOGRECORD {
DWORD  Length;        // Length of full record
DWORD  Reserved;      // Used by the service
DWORD  RecordNumber;  // Absolute record number
DWORD  TimeGenerated; // Seconds since 1-1-1970
DWORD  TimeWritten;   // Seconds since 1-1-1970
DWORD  EventID;
WORD   EventType;
WORD   NumStrings;
WORD   EventCategory;
WORD   ReservedFlags; // For use with paired events (auditing)
DWORD  ClosingRecordNumber; // For use with paired events (auditing)
DWORD  StringOffset;  // Offset from beginning of record
DWORD  UserSidLength;
DWORD  UserSidOffset;
DWORD  DataLength;
DWORD  DataOffset;    // Offset from beginning of record
//
// Then follow:
//
// WCHAR SourceName[]
// WCHAR Computername[]
// SID   UserSid
// WCHAR Strings[]
// BYTE  Data[]
// CHAR  Pad[]
// DWORD Length;

    //
} EVENTLOGRECORD, *PEVENTLOGRECORD;

I can pull out the first chunk which appears to be the source with the following code, but its certainly not the intended method:

memcpy(&strings, pRecord+sizeof(EVENTLOGRECORD), tmpLog->UserSidOffset);

But from the comments in Winnt.h, I'm also getting the computer name.

So can someone explain how to determine the "SourceName" length from the EVENTLOGRECORD structure, and explain what StringOffset, DataLength and DataOffset are?

Thanks.

+1  A: 

So can someone explain how to determine the "SourceName" length from the EVENTLOGRECORD structure,

From what I can see, SourceName[] and Computername[] are one behind each other, separated by a '\0', with the first starting right behind DataOffset, and the second starting right behind the '\0' of the first, and going up to two bytes before UserSidOffset, with a '\0' trailing.

and explain what StringLength, DataLength and DataOffset are?

StringLength I cannot find (and StringOffset is where Strings[] starts), DataLength is the number of bytes in Data[], and DataOffset is where Data[] starts.

To read the strings, you could do something like this:

// Beware, brain-compiled code ahead!
void f(EVENTLOGRECORD* rec)
{
  std::wstring source_name(
    reinterpret_cast<const wchar_t*>( 
      reinterpret_cast<const unsigned char*>( rec 
                                            + sizeof(EVENTLOGRECORD ) ) ) );
  std::wstring computer_name( 
    reinterpret_cast<const wchar_t*>( 
      reinterpret_cast<const unsigned char*>( rec 
                                            + sizeof(EVENTLOGRECORD )
                                            + source_name.length()+1 ) ) );
  // ...
}
sbi
Careful - you should cast rec to (unsigned char *) before doing that pointer arithmetic, otherwise the compiler will offset the pointer of the offsets multiplied by the size of the EVENTLOGRECORD structure.
Matteo Italia
The StringOffset and StringLength fields DO NOT apply to the SourceName and ComputerName fields. Please read the documentation. The variable-length section of the EVENTLOGRECORD structure has a Strings[] field in between the UserSid field and the event-specific Data field.
Remy Lebeau - TeamB
@Matteo: Yeah, you're right. Sorry for that brainfart. I'll fix this.
sbi
@Remy: I have now re-read the docs and it seems you're right. `<sigh>` I should have kept my nose out of this. I haven't done any real Win API stuff in years and am not used to their idioms.
sbi
@sbi: there was also a sizeof missing, FTFY. :)
Matteo Italia
@Matteo: Thanks. `:(` FWIW, I had already up-voted your answer anyway.
sbi
FYI to compile, you need a fourth ")" on the first + sizeof(EVENTLOGRECORD ) ) );Also, windows provides a PEVENTLOGRECORD so you might be able to use that as the param type instead of EVENTLOGRECORD*. Anyway, thanks for the response, working on it now.
reuscam
@reuscam: Thanks, I've added the `)`. `PEVENTLOGRECORD`, however, is just an alias for `EVENTLOGRECORD*` (for which prefixing `const` has a subtly, but importantly, different meaning). I see no advantage in using it.
sbi
And one final comment, the pointer offset for the computer name needs a +1 to compensate for the null terminator - for the sake of correctness. Not sure what you mean in your last comment about "prefixing const has a subtly, ..."
reuscam
@reuscam: Thanks, I added that correction, too (plus my std disclaimer `<sigh>`). As for the `const`: `const T*` makes the object referred to `const`, `const PT` makes the referring pointer `const`. The postfix `const`, OTOH, `PT const` is the same as `T* const`.
sbi
+5  A: 

Note: throughout the answer I'll assume that you have a pointer to that structure like this:

EVENTLOGRECORD * elr;

to shorten the code snippets.

So can someone explain how to determine the "SourceName" length from the EVENTLOGRECORD structure

There's no field that specifies how long it is, but you can determine it quite easily: it is the first field of the record after the well-defined fields, so you can simply do:

WCHAR * SourceName=(WCHAR *)((unsigned char *)elr + sizeof(*elr));

Now, in SourceName you have a pointer to that string; you can easily determine its length with the usual string functions.

By the way, after the terminator of SourceName there should be the the ComputerName string.

and explain what StringLength

There's no StringLength member, what are you talking about?

DataLength and DataOffset are?

An event log is composed also of arbitrary binary data, that is embedded in the record.

The DataOffset member specifies the offset of such data from the beginning of the record, and DataLength specifies how long is that data. If you were to copy that data to a buffer (assuming that it's big enough), you'd do:

memcpy(targetBuffer,(unsigned char *)elr + elr->DataOffset,elr->DataLength);

By the way, instead of reading directly the include files you should read the documentation, it's far easier to understand.


Addendum about StringOffset

The StringOffset field specifies the offset of the strings associated to the event from the beginning of the record.

The StringOffset field works very much like the DataOffset field described above, but there's no corrispondent StringLength field, since the length of each string can be easily determined using the normal string functions (in fact the string section is just made of several NUL-terminated strings put one after the other).

Moreover, the location where the strings section ends can be easily determined examining the DataOffset member, in facts the strings section ends where the data chunk begins. The EVENTLOGRECORD structure also provides the NumStrings field to determine the number of strings contained in the strings section (thanks Remy Lebeau).

If you were to put these strings in a vector<wstring> you'd do something like this (careful, untested code):

vector<wstring> strings;
for(
        wchar_t * ptr=(wchar_t *)((unsigned char *)elr + elr->StringOffset);
        strings.size()<elr->NumStrings;
        ptr+=strings.back().length()
    )
    strings.push_back(wstring(ptr));
Matteo Italia
Sorry, I meant StringOffset. Edited the original.
reuscam
Added discussion about StringOffset.
Matteo Italia
The EVENTLOGRECORD has a NumStrings member. You are supposed to use that value to determine how many strings to extract from the list, where each string value is null-terminated. So, starting at the StringOffset, read a null-terminated string, move your pointer that many characters, repeat, until you reach the NumStrings limit.
Remy Lebeau - TeamB
Correct; I fixed the last example.
Matteo Italia
A: 

Please read the documentation. It tells you what the StringLength, DataLength and DataOffset members are.

As for the SourceName and ComputerName members, they are both null-terminated strings (with potentially extra padding after ComputerName to align the UserSid member). You saw the ComputerName appear in your buffer because you told memcpy() to copy the raw bytes of both members together. Try using lstrlenW() and lstrcpyW() (or equivilent functions).

Remy Lebeau - TeamB
I read the documentation several times before asking. While it makes sense now after reading these responses, it was not exactly clear to me before. The documentation does not say SourceName and ComputerName are null terminated and should be parsed accordingly. While this may be clear and common to many windows api developers, it was not to me.
reuscam
No, the docs do not explicitally state that they are null-terminated, that is true. But if you look at the associated example code, that is how they are treated in Microsoft's own code.
Remy Lebeau - TeamB