quarta-feira, 16 de março de 2011

Windows 7/2008 Event Log forensic and reversing analysis

eseugutroP Reversed by ar1vr

This text refers to the 32bit version of Windows, unfortunately I don't have access to a 64bit development environment.

Opening an .evtx log is pretty straightforward, the only problem is that the eventlog service opens this files exclusively and if we try to make a copy, we'll get "Access is denied" error. So, we need to get them with the machine offline.

One of the reasons beyond the handles to this files being exclusively open, is that the eventlog service memory maps chunks of this files, holding and manipulating some housekeeping metadata information directly in the memory mapped files.
Performance and security issues also come to the discussion: the files aren't always in sync with the data in memory for example, and each file has an associated timer that CRCs the headers and flushes the mapped views. Also this metadata information allows for file recovery in case of unexpected shutdown or crash.

Figure 1: Event log layout

What this means for a "interested" person, is that we need to be careful if trying to change the logs content online, because not everything is what it seems. But it is indeed possible to manipulate the logs directly in the eventlog service: remove and change log entries, insert new entries, stealthily clear the logs, add log garbage, etc. without restarting or shuting down the service or the machine.
I wrote a small POC tool that shows this being done, I called it Elchomp. Elchomp basically clears the last log record entry in the event log of choosing. You can download Elchomp at the end of this post.

The .evtx files are binary files, to where the eventlog service streams log records using XML templates to format each entry. This makes easier switching between the Event Viewer general and details panels.
These binary files are structured as follows:
- The file starts with a file header that describes the chunks used by the file. The stored information pertains to the number of chunks, the chunk size, and some duplicated information for failover purposes. A chunk is a container block of event records.
- N Chunks serialized. Each chunk points to a set of event log entries.
- N records. Each record represents an event log entry.

Let's view in more detail each of these structures. I'll cover only the most important data, so keep this in mind while reading. I'll open a real .evtx log for analysis. Figure 2 shows the first bytes of the file.

As can be seen, there are a couple of signatures in the file, the first signature "ElfFile" at position zero in the file, marks the beginning of the file, and is used to detect if this is an event log file type.

Figure 2: File Header

At offset 0x10 we have the CurrentChunkCount. This DWORD value doesn't show up in the eventlog file, I just mention it here because I wanted to enforce the differences between the offline and online layouts. When the log is opened by the eventlog service, the CurrentChunkCount is set to the chunk that the NumberOfChunks references, a new chunk is created and the ChunkOffset is updated accordingly.
At offset 0x28, NumberOfChunks, a WORD value, indicates how many chunks are in the file.
At offset 0x30, SizeOfHeader a WORD value, sets the size for the file header.
Positioning to the end of the File header, value 0x1000, we can see the first chunk signature. (Figure 3)

Figure 3: First Chunk

It's time to say that the chunks are fixed sized 0x10000 = 65536 bytes. The last chunk is always memory mapped in the eventlog service process.
Multiplying size of chunk by NumberOfChunks we get 0x10000*0x001d = 0x1d0000,
sum the FileHeader and we get to last valid chunk header: 0x1d0000+0x1000 = 0x1d1000. (Figure 4)

Figure 4: Last Chunk Header Data

"ElfChnk" at position zero in the ChunkHeader, validates the chunk beginning.
At offset 0x10 and 0x20 we've got the same value, for failsafe purpose. It's a DWORD (0x00000e15) that represents the number of records contained in the chunk. Actually, if a new record is written it will use this value as Record counter, so the Number of records can be calculated by subtracting one to this value.
At offset 0x30 a DWORD that indicates the last offset position in the chunk that can be written to.

Figure 5: Last valid position in log

LastChunkStart = 0x001d1000
LastValidOffset = 0x000066b8
LastValidPosition = 0x001d1000 + 0x000066b8 = 0x001d76b8

From position 0x001d76b8 forward, there are only zeros. (Figure 5)

The last valid position also gives us the last valid record end. going backwards, we reach the signature (position 0x1d7438 in figure 6) for the last event record '**' or 0x2A2A.

Figure 6: Event Record Data

At offset 0x6 we find the current record count 0xe14 (being this the last valid record in the chunk this value is equal to the chunk's number of records minus one).
At offset 0xe and 0x86 the timestamp in file format. Corresponds to the event viewer's "Logged" time field.
At offset 0x7a the Event Id.
At offset 0x7e the Keywords field.
At offset 0x96 the record Id.
At offset 0x9e the SID.
At offset 0xda we've got the log message.

Figure 7: Event Viewer log entry

Hope you enjoyed

ElChomp is available here https://www.filesanywhere.com/fs/v.aspx?v=8a6b66865967747da3a5

You need to run the tool with UAC elevated administrator token in order to successfully eat the last log entry.

How does Elchomp works?

At the base core of Event log service is the wevtsvc.dll dll. Wevtsvc keeps track of the log files using a global variable called LogFilesHead.

x wevtsvc!LogFilesHead
6bfb6130 wevtsvc!LogFilesHead = <>

You call walk this list, and see which log files are registered:

r $t1=poi(6bfb6130); .for (r $t2=1; @$t2 < 9;r $t2 = @$t2+1 ) { du poi(@$t1+c)+8; r $t1=poi(@$t1); } 00bd7658 "Windows PowerShell" 005f85a8 "System" 005f8800 "Security" 005f8a58 "Media Center" 00bd60c8 "Key Management Service" 00bd7348 "Internet Explorer" 005f88e0 "HardwareEvents" 00bd08e0 "Application"

At 0x98 offset from the LOG_FILE struct, we get to the FILE object:

r $t1=poi(6bfb6130); .for (r $t2=1; @$t2 < 9;r $t2 = @$t2+1 ) { dd @$t1 l1; r $t1=poi(@$t1); } 005fe410 005f9130 005f9130 005f8fa0 005f8fa0 005f8da0 005f8da0 005f8bc8 005f8bc8 005f8a80 005f8a80 005f8820 005f8820 00bd0820 00bd0820 6bfb6130

dd 00bd0820+98 l1
00bd08b8 0036db20 <--- File Object dc 0036db20
0036db20 6bfb6108 003835f8 0048f2bc 00000000 .a.k.58...H.....
0036db30 00000000 00000009 46666c45 00656c69 ........ElfFile.
0036db40 00000000 00000000 00000000 00000000 ................
0036db50 00000e68 00000000 00000080 00030001 h...............

ln 6bfb6108
Exact matches:
wevtsvc!FileTrackHeader =

Now what? As the Wevtsvc is build in C++, we need to respect object hierarchy and follow its rules to facilitate getting the object pointers we’re interested in.
Lets introduce ChannelManager. ChannelManager is a one instance object that manages communication channels between logs and subscribers, it keeps record of open logs, starts and stops event, enforces policies, etc. ChannelManager is available at the wevtsvc!gChannelMgr.

Each log file is tracked by ChannelManager, using a File RTTI object. To get to a specific object File, we need to call ChannelManager::GetChannelFile using the name of the log we’re targeting.

The FileObject is composed of a WriteFileView object, a pointer can be referenced at offset 0xF8. Using this WriteFileView object and calling the method WriteFileView::GetBufferPtr we finally can position ourselves to the Chunk buffer covered before, as you can see, we’re getting the chunk signature. This mapped Chunk is the last Chunk on the file. If you need to walk backwards and map any previous Chunk, File::MapInNewWriteChunk, is the right function for you.

001bdbe0 43666c45 006b6e68 00000001 00000000 ElfChnk.........
001bdbf0 00000006 00000000 0000000c 00000000 ................
001bdc00 00000011 00000000 00000080 00000ac0 ................
001bdc10 00000de0 06a174ed 00000000 00000000 .....t..........
001bdc20 00000000 00000000 00000000 00000000 ................
001bdc30 00000000 00000000 00000000 00000000 ................
001bdc40 00000000 00000000 00000000 00000000 ................
001bdc50 00000000 00000000 00000001 7b1dda33 ............3..{

So in order to alter the event log state we need to change the Chunk contents, and we need to update some metadata in the RTTI File Object.
Most important attributes to change for a successful update (the names are based on their purpose):

File::RecordCount (Offset 0x30)
File::PreviousEventPublisherRecordNr (Offset 0xF0)
File::LastValidOffset (Offset 0x130)
File::SaveToFileFlag1 (Offset 0x10)
File::SaveToFileFlag2 (Offset 0x330)

As Elchomp is a PoC, I don’t deal with concurrency, so it might crash the eventlog service. This is no big deal as you can restart it again, but don’t tell that I didn’t warn you.

Hope you enjoyed