characterization of the windows kernel version …...characterization of the windows kernel version...

13
DIGITAL FORENSIC RESEARCH CONFERENCE Characterization Of The Windows Kernel Version Variability For Accurate Memory Analysis By Michael Cohen From the proceedings of The Digital Forensic Research Conference DFRWS 2015 EU Dublin, Ireland (Mar 23 rd - 26 th ) DFRWS is dedicated to the sharing of knowledge and ideas about digital forensics research. Ever since it organized the first open workshop devoted to digital forensics in 2001, DFRWS continues to bring academics and practitioners together in an informal environment. As a non-profit, volunteer organization, DFRWS sponsors technical working groups, annual conferences and challenges to help drive the direction of research and development. http:/dfrws.org

Upload: others

Post on 23-Apr-2020

10 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Characterization Of The Windows Kernel Version …...Characterization of the windows kernel version variability for accurate memory analysis Michael I. Cohen Google Inc., Brandschenkestrasse

DIGITAL FORENSIC RESEARCH CONFERENCE

Characterization Of The Windows Kernel Version

Variability For Accurate Memory Analysis

By

Michael Cohen

From the proceedings of

The Digital Forensic Research Conference

DFRWS 2015 EU

Dublin, Ireland (Mar 23rd- 26th)

DFRWS is dedicated to the sharing of knowledge and ideas about digital forensics

research. Ever since it organized the first open workshop devoted to digital forensics

in 2001, DFRWS continues to bring academics and practitioners together in an

informal environment.

As a non-profit, volunteer organization, DFRWS sponsors technical working groups,

annual conferences and challenges to help drive the direction of research and

development.

http:/dfrws.org

Page 2: Characterization Of The Windows Kernel Version …...Characterization of the windows kernel version variability for accurate memory analysis Michael I. Cohen Google Inc., Brandschenkestrasse

Characterization of the windows kernel version variability foraccurate memory analysis

Michael I. CohenGoogle Inc., Brandschenkestrasse 110, Zurich, Switzerland

Keywords:Memory analysisIncident responseBinary classificationMemory forensicsLive forensics

a b s t r a c t

Memory analysis is an established technique for malware analysis and is increasingly usedfor incident response. However, in most incident response situations, the responder oftenhas no control over the precise version of the operating system that must be responded to.It is therefore critical to ensure that memory analysis tools are able to work with a widerange of OS kernel versions, as found in the wild. This paper characterizes the properties ofdifferent Windows kernel versions and their relevance to memory analysis. By collecting alarge number of kernel binaries we characterize how struct offsets change with versions.We find that although struct layout is mostly stable across major and minor kernel ver-sions, kernel global offsets vary greatly with version. We develop a “profile indexing”technique to rapidly detect the exact kernel version present in a memory image. We cantherefore directly use known kernel global offsets and do not need to guess those byscanning techniques. We demonstrate that struct offsets can be rapidly deduced fromanalysis of kernel pool allocations, as well as by automatic disassembly of binary functions.As an example of an undocumented kernel driver, we use the win32k.sys GUI subsystemdriver and develop a robust technique for combining both profile constants and reversedstruct offsets into accurate profiles, detected using a profile index.© 2015 The Author. Published by Elsevier Ltd. This is an open access article under the CC

BY-NC-ND license (http://creativecommons.org/licenses/by-nc-nd/4.0/).

Introduction

Memory analysis has become a powerful technique forthe detection and identification of malware, and for digitalforensic investigations (Ligh et al., 2010, 2014).

Fundamentally, memory analysis is concerned withinterpreting the seemingly unstructured raw memory datawhich can be collected from a live system into meaningfuland actionable information. At first sight, the memorycontent of a live system might appear to be composed ofnothing more than random bytes. However, those bytes arearranged in a predetermined order by the running softwareto represent a meaningful data structure. For exampleconsider the C struct:

The compiler will decide how to overlay the struct fieldsin memory depending on their size, alignment re-quirements and other consideration. So for example, theCreateTime field might get 8 bytes, causing the Image-FileName field to begin 8 bytes after the start of the_EPROCESS struct.

A memory analysis framework must have the samelayout information in order to know where each fieldshould be found in relation to the start of the struct. Earlymemory analysis systems hard coded this layout informa-tion which was derived by other means (e.g. reverseE-mail address: [email protected].

Contents lists available at ScienceDirect

Digital Investigation

journal homepage: www.elsevier .com/locate/d i in

http://dx.doi.org/10.1016/j.diin.2015.01.0091742-2876/© 2015 The Author. Published by Elsevier Ltd. This is an open access article under the CC BY-NC-ND license (http://creativecommons.org/licenses/by-nc-nd/4.0/).

Digital Investigation 12 (2015) S38eS49

Page 3: Characterization Of The Windows Kernel Version …...Characterization of the windows kernel version variability for accurate memory analysis Michael I. Cohen Google Inc., Brandschenkestrasse

engineering or simply counting the fields in the structheader file (Schuster, 2007)).

This approach is not scalable though, since the structdefinition change routinely between versions of the oper-ating system. For example, in the above simplified struct ofan _EPROCESS, if additional fields are inserted, the layout ofthe field members will change to make room for the newelements. So for example, if another 4 byte field is addedbefore the CreateTime field, all other offsets will have toincrease by 4 bytes to accommodate the new field. This willcause all the old layout information to be incorrect and ourinterpretation of the struct in memory to be wrong.

Modern memory analysis frameworks address the var-iations across different operating system versions by use ofa version specific memory layout template mechanism. Forexample in Volatility (The Volatility Foundation, 2014) orRekall (The Rekall Team, 2014a, b) this information is calleda profile.

The Volatility memory analysis framework (TheVolatility Foundation, 2014) is shipped with a number ofWindows profiles embedded into the program. The userchooses the correct profile to use depending on theirimage. For example, if analyzing a Windows 7 image, theprofile might be specified as Win7SP1x64. In Volatility, theprofile name conveys major version information (i.e. Win-dows 7), minor version information (i.e. Service Pack 1) andarchitecture (i.e. !64). Volatility uses this information toselect a profile from the set of built-in profiles.

Deriving profile information

The problem still remains how to derive this structlayout information automatically. The Windows kernelcontains many struct definitions, and these change for eachversion, so a brute force solution is not scalable (Okolicaand Peterson, 2010).

Memory analysis frameworks are not the only casewhere information about memory layout is required. Spe-cifically, when debugging an application, the debuggerneeds to know how to interpret the memory of thedebugged program in order to correctly display it to theuser. Since the compiler is the one originally deciding onthe memory layout, it makes sense that the compiler gen-erates debugging information about memory layout for thedebugger to use.

On Windows systems, the most common compiler usedis the Microsoft Visual Studio compiler (MSVCC). Thiscompiler shares debugging information via a PDB file(Schreiber, 2001), generated during the build process forthe executable. The PDB file format is unfortunately un-documented, but has been reverse engineered sufficientlyto be able to extract accurate debugging information, suchas struct memory layout, reliably (Schreiber, 2001; Dolan-Gavitt, 2007a).

The PDB file for an executable is normally not shippedtogether with the executable. The executable contains aunique GUID referring to the PDB file that describes thisexecutable. When the debugger wishes to debug a partic-ular executable, it can then request the correct PDB filefrom a symbol server. This design allows production

binaries to be debugged, without needing to ship bulkydebug information with final release binaries.

The PDB file contains a number of useful pieces of in-formation for a memory analysis framework:

" Struct members and memory layout. This contains in-formation about memory offsets for struct members,and their types. This is useful in order to interpret thecontents of memory.

" Global constants. The Windows kernel contains manyimportant constants, which are required for analysis. Forexample, the PsActiveProcessHead is a constant pointerto the beginning of the process linked list, and isrequired in order to list processes by walking that list.

" Function addresses. The location of functions inmemoryis also provided in the PDB file e even if these functionsare not exported. This is important in order to resolveaddresses back to functions (e.g. in viewing the Inter-rupt Descriptor Table e IDT).

" Enumeration. In C an enumeration is a compact way torepresent one of a set of choices using an integer. Themapping between the integer value and a humanmeaningful string is stored in the PDB file, and it isuseful for interpreting meaning from memory.

Characterizing kernel version variability

As described previously, the Volatility tool only containsa handful of profiles generated for different major releasesof the Windows kernel. However, each time the kernel isrebuilt by Microsoft (e.g. for a security hot fix), the codecould be changed, and the profile could be different. Theassumption made by the Volatility tool is that thesechanges are not significant and therefore, a profile gener-ated from a single version of a major release will work onall versions from that release.

We wanted to validate this assumption. We collectedthe Windows kernel binary (ntkrnlmp.exe, ntkrpamp.exe,ntoskrnl.exe) from several thousand machines in the wildusing the GRR tool (Cohen et al., 2011). Each of these bi-naries has a unique GUID, and we were therefore able todownload the corresponding PDB file from the publicMicrosoft symbol server. We then used Rekall's mspdbparser to extract debugging information from each PDB file.

This resulted in 168 different binaries of the Windowskernel for various versions (e.g. Windows XP, WindowsVista, Windows 7 and Windows 8) and architectures (e.g.I386 and AMD64). Clearly, there are many more versions ofthe Windows kernel in the wild than exist in the Volatilitytool. It is also very likely that we have not collected all theversions that were ever released by Microsoft, so oursample size, although large, is not exhaustive.

Fig. 1 shows sampled offsets of four critical structmembers for memory analysis:

" The _EPROCESS.VadRoot is the location of the Vad withinthe process. This is used to enumerate process alloca-tions (Dolan-Gavitt, 2007b).

" The _KPROCESS.DirectoryTableBase is the location of theDirectory Table Base (i.e. the value loaded into the CR3

M.I. Cohen / Digital Investigation 12 (2015) S38eS49 S39

Page 4: Characterization Of The Windows Kernel Version …...Characterization of the windows kernel version variability for accurate memory analysis Michael I. Cohen Google Inc., Brandschenkestrasse

register) which is critical in constructing the VirtualAddress Space abstraction.

" The _EPROCESS.ImageFileName is the file name of therunning binary. For example, this field might contain“csrss.exe”.

Microsoft Windows kernel versions contain four parts:The major and minor versions, the revision and the buildnumber. The build number increases for each build (e.g.security hotfix).

As can be seen in the figure, struct offsets do tend toremain stable acrossWindows versions. In most cases, witha single notable exception e version 5.2.3970.175 (GUID

466B4165EAA84AF88D29D617E86A95982), the struct off-sets remain the same for all major Windows releases.Therefore, chances are good that the Volatility profile for agiven Windows version would actually work most of thetime for determining struct layout.

Kernel global constants variability

It is generally not sufficient to determine only the structmemory layout for memory analysis. For example, considerlisting the running processes. One technique is to followthe doubly linked list of EPROCESS.ActiveProcessLinks ineach process struct (Okolica and Peterson, 2010). This

Fig. 1. Offsets for a few critical struct members across various versions of the Windows kernel. These offsets were derived by analyzing public debug informationfrom the Microsoft debug server for the binaries in our collection.

M.I. Cohen / Digital Investigation 12 (2015) S38eS49S40

Page 5: Characterization Of The Windows Kernel Version …...Characterization of the windows kernel version variability for accurate memory analysis Michael I. Cohen Google Inc., Brandschenkestrasse

technique needs to find the start of the list which begins atthe global kernel constant PsActiveProcessHead. The loca-tion for this global constant in memory is determinedstatically by the compiler at compile time, and it is usuallystored in one of the data sections in the PE file itself.

Since this information is also required by the debugger,the PDB file also contains information about global con-stants and functions (even if these are not actually exportedvia the Export Address Table). Rekall's mspdb plugin alsoextract this information into the profile.

Fig. 2 illustrates the memory addresses of some impor-tant kernel constants for the kernels in our collection:

" NtBuildLab is the location of the NT version string (e.g.“7600.win7_rtm.090713-1255”). This is used to identifythe running kernel.

" PsActiveProcessHead is the head of the active process list.This is required in order to list the running processes.

" NtCreateToken is an example of a kernel function. Thiswill normally exist in the .text section of the PE file.

" str:FILE_VERSION is literally the string “FILE_VERSION”.Usually the compiler will place all literal strings intotheir own string table in the .rdata section of the PE file.The compiler will then emit debugging symbols for thelocation of each string e indicating that they are literal

Fig. 2. Offsets for a few global kernel constants across various versions of the Windows kernel. These offsets were derived by analyzing public debug informationfrom the Microsoft debug server for the binaries in our collection. Offsets are provided relative to the kernel image base address.

M.I. Cohen / Digital Investigation 12 (2015) S38eS49 S41

Page 6: Characterization Of The Windows Kernel Version …...Characterization of the windows kernel version variability for accurate memory analysis Michael I. Cohen Google Inc., Brandschenkestrasse

strings. The importance of this symbol will be discussedin the following sections.

As can be seen, the offsets of global kernel constantschange dramatically between each build e even for thesame version. This makes sense, since the compiler ar-ranges global constants in their own PE section, so if anyglobal constant is added or removed in the entire kernel,this affects the ordering of all other constants placed afterit.

It is therefore clear that it is unreliable to directly obtainthe addresses of kernel globals by simply relying on theversion alone. The Volatility tool resorts to a number oftechniques to obtain these globals:

" Many globals are obtained from the KdDebuggerData-Block e another global kernel struct which containspointers to many other globals. This structure is usuallyscanned for.

" Scanning for kernel objects which refer to global con-stants (e.g. via pool tag scanning or other signatures).

" Examining the export tables of various PE binaries forexported functions.

" Dynamically disassembling code to detect calls to nonexported functions.

These techniques are complex and error prone. They arealso susceptible to anti-forensics as signature scanners cantrivially be fooled by spurious signatures (Williams andTorres, 2014). Scanning for signatures over very largememory images is also slow and inefficient.

The Rekall memory forensic framework (The RekallTeam, 2014a, b), a fork of the Volatility framework, takesa different approach. Instead of guessing the location ofvarious kernel constants, the framework relies on a publicprofile repository which contains every known profile fromevery known build of the Windows kernel. This greatlysimplifies memory analysis algorithms because the addressof global kernel variables and functions is directly knownfrom public debugging information provided by Microsoft.There is no need to scan or guess at all. Locating theseglobals is very efficient since there is no need to scan forsignatures, making the framework fast and reducing theability of attackers to subvert analysis.

Identifying binary versions

The Rekall profile repository contains, at the time ofwriting, 309 profiles for various Windows kernel versions(and this number is constantly increasing). Typically, userswill simply report the GUID of the Windows kernel foundin their image, but will not provide the actual kernel binary.

Previously, Rekall employed a scanning technique tolocate the GUID of the NT kernel running within the image.Once the GUID is known, the correct profile can be fetchedfrom the repository and analysis can begin. However, thistechnique is still susceptible to manipulation (It is easy forattackers to simply wipe or alter the GUID from memory).Sometimes the GUID is paged out of memory and in thiscase it is impossible to guess it. What we really need is a

reliable way to identify the kernel version without relyingon a single signature.

The problem of identifying kernel binaries in a memoryimage has been examined previously in the Linux memoryanalysis context (Roussev et al., 2014). In that paper, theauthors used similarity hashing to match the kernel in amemory image with a corpus of known binaries.

In our case, we do not always have the actual binariesbut have debugging symbols from these binaries. Wetherefore need a way for deducing enough informationabout the kernel binary itself (which we may not have)from the debug symbols. Consider the following informa-tion present in the PDB file:

" String Literals. As shown in the example above, thecompiler generates string literals in the PE binary itself.These are then located using global debugging symbols.Forexample, inFig. 2weknowtheexactoffsets inmemorywhere we expect find the string “FILE_VERSION”.

" Function preamble. The PDB file also contains the loca-tions of many functions. We note that each function isgenerally preceded by 5 NOP instructions in order tomake room for hot patching (Chen, 2011). Thus, we candeduce that for each function in the PDB, the previousbyte contains the value 0!90 (NOP instruction).

The problem, therefore, boils down to identifying whichof a finite set of kernel profiles is the one present in thememory image, based on known data that must exist atknown offsets:

1. Begin by selecting a number of function names, or literalstring names. We term these Comparison Points since weonly compare the binaries at these known offsets.

2. Examine all available profiles, and record the offset ofthese symbols as well as the expected data to appear atthis offset (either a NOP instruction or the literal stringitself).

3. Build a decision tree around the known comparisonpoints to minimize the number of string comparisonsrequired for narrowing down thematch. Note that at thisstage it is possible to determine if there are sufficientnumber of comparison points to distinguish all profileselections. If profile selection is ambiguous, furthercomparisonpoints are addedand theprocess starts again.

4. Scan the memory image for the longest strings using theAho-Corasick string matching algorithm (Aho andCorasick, 1975).

5. For each match, seek around the match to apply thedecision tree calculated earlier. Within a few stringcomparisons, the correct profile is identified.

6. Load the profile from the profile repository and initializethe analysis.

In practice it was found that fewer than a dozen com-parison points are required to characterize all the profiles inthe Rekall profile repository, leading to extremely quickmatching times. Also, binary identification is robust tomanipulation since the choice of comparison points israther arbitrary and can be changed easily.

M.I. Cohen / Digital Investigation 12 (2015) S38eS49S42

Page 7: Characterization Of The Windows Kernel Version …...Characterization of the windows kernel version variability for accurate memory analysis Michael I. Cohen Google Inc., Brandschenkestrasse

Windows kernel binary identification

Section 3 described an efficient algorithm for identi-fying a binary match from a set of known binaries. How-ever, in the memory analysis context, this comparisonmustbe made in the Virtual address space. Modern CPUs operatein protected mode, and the exact memory accessible to thekernel does not necessarily need to be contiguous in thephysical memory image.

Therefore, before we are able to apply the index classi-fication algorithm, we must build a virtual address space,requiring us to identify the value of CR3, or the kernel'sDirectory Table Base (DTB).

The DTB can be captured during the acquisition processand stored in the image, but typically it must be scanned for.The Volatility memory forensic framework scans for the Idleprocess's EPROCESS struct. Itfirst searches for the literal string“Idle”, this should exist as the EPROCESS.ImageFileNamemember. Knowing the difference between the offsets ofEPROCESS.ImageFileName and EPROCESS.Pcb.DirectoryTa-bleBase, the framework reads the DTB and therefore locatesthe page tables.

Theproblemwith thisapproach is that it requiresknowingthe exact offsets of two EPROCESS struct members. Fig. 1shows how these relative offsets vary between Windowsversions, so to know the offset we need to know the exactWindowsversionweare examininge butwecannot identifythe profilewithout applying the profile index,which requiresa valid kernel address space e i.e. knowing the DTB first!

We solve this Catch-22 by noting that the total numberof combinations of the EPROCESS member offsets is limited(4 combinations for 64 bit architectures and 6 combina-tions for 32 bit architectures). Therefore, it is possible tobrute force all combinations in search of a valid DTB.

So in summary the complete Kernel Binary Autode-tection algorithm, as implemented in Rekall, is:

" Scan the image for commonWindows executable names(e.g. “csrss.exe”, “cmd.exe” etc). This scan uses the Aho-Corasick algorithm to search for all strings at once.

" For each hit, brute force the DTB going through the 10possible offsets. The DTB is validated using theKUSER_SHARED_DATA.NtMajorVersion and KUSER_-SHARED_DATA.NtMinorVersion members. Since thisstruct must be found at a fixed location in memory andalways have the same layout it is safe to hardcode it(Skape, 2005). Therefore, we can validate the DTB andkernel address space without knowing anything aboutthe profile itself or the kernel version.

" Once a DTB is identified, we construct a virtual addressspace and scan for the kernel image in memory usingthe algorithm previously described.

Undocumented kernel structures

Section 2 examined the variability of documentedkernel structures across different kernel versions. Thequestion we try to answer now is, what is the variability ofundocumented kernel structures of significance to thememory analyst?

One of the most interesting kernel drivers is the Win-dows 32 user mode GUI subsystem (Mandt, 2011; Yuan,2001), implemented as “win32k.sys”. The data structuresused in this subsystem are required to detect many com-mon hooks placed by malware (e.g. SetWindowsHookEx()style keyloggers (Sikorski and Honig, 2012)).

The Rekall profile repository currently contains profilesfor 169 unique versions of this driver. However, only 33versions include information about critical structures (e.g.tagDESKTOP and tagWINDOWSTATION). The remainingprofiles only contain information about global constantsand functions, but no structure information.

Our goal is to understand how various importantstructures evolved through the released versions. Sincemany of these versions are undocumented and do not havedebugging information, previous research has manuallyreverse engineered several samples from different ver-sions. However, we are unsure if there is internal variabilitywithin Windows versions and releases. Guided by ourprevious experiencewith theWindows Kernel versions, wehypothesize that the win32k.sys struct layout would notvary much between minor release versions.

Given our large corpus of binaries we can directlyexamine this hypothesis and evaluate the best approach fordetermining struct layout when analyzing the Win32k GUIsubsystem.

Data driven reverse engineering

The literature contains a number of published systemsfor automatically detecting kernel objects from memoryimages (Sun et al., 2012). For example, the SigGraph system(Lin et al., 2011), is capable of building scanners for Linuxkernel structures by analyzing their internal pointergraphs.

The SigGraph system specifically does not utilize inci-dental knowledge about the system to assist in thereversing task. However on Windows systems, there aresome helpful observation one can make to facilitate typeanalysis from memory dumps.

In the Windows kernel all allocations come from one ofthe kernel pools (e.g. Paged, Non-Paged or Session Pool).Allocations smaller than a page are preceded by a POOL_-HEADER object (Schuster, 2006, 2008).

The pool header contains a known tag as well as in-dications of the previous and next pool allocation (withinthe page). Thus, small pool allocations form a doubly linkedlist. Due to this property it is possible to validate the poolheader and locate it in memory. A typical Windows kernelallocation is illustrated in Fig. 3.

If we were to ask, “What kernel object exists at a givenvirtual offset?”, we can simply scan backwards for a suit-able POOL_HEADER structure and deduce the type of objectfrom the pool tag. We can further scan forward from thislocation for other heuristics, such as pointers to certainother pool allocations, or doubly linked lists. We wrote aRekall plugin called analyze_structs to perform this analysison arbitrary memory locations.

For example, Fig. 4 shows the analysis of the globalsymbol grpWinStaList which is the global offset of the headof the tagWINDOWSTATION list. We can see that at offset

M.I. Cohen / Digital Investigation 12 (2015) S38eS49 S43

Page 8: Characterization Of The Windows Kernel Version …...Characterization of the windows kernel version variability for accurate memory analysis Michael I. Cohen Google Inc., Brandschenkestrasse

0!10 there is a pointer to the tagDESKTOP object, at offset0!18 there is a pointer to the global gTermIO object etc.

With Windows 7 we can find the complete struct in-formation in the PDB file. This is also shown in Fig. 4. Wecan see that the detected pointers correspond with therpdeskList, pTerm, spklList, pGlobalAtomTable and psidUsermembers.

An obvious limitation of this technique is that if apointer in the struct is set to NULL, we are unable to sayanything about it. Hence to reveal as many fields aspossible we need to examine as many instances of thesame object type as we can find (e.g. via pool scanningtechniques).

Code based reverse engineering

The previous section demonstrates how we can deducesome struct layouts by observation of allocationswe canfindfrom the kernel pools. However, these observations are notsufficient to deduce all types of members. Specifically, onlypointers are reliably deduced by this method. Additionally,wemust observe allocatedmemory in amemorydump froma running system. Oftenwe only have the executable binary(e.g. from disk) but not the full memory image.

In these cases, we need to resort to the more traditionalreverse engineering approach. Previously, researchers havereverse engineered specific exemplars of the win32k.sysbinary which is representative of a specific Windowsversion (The Volatility Foundation, 2014). However,manually reverse engineering every file in our large corpusof win32k.sys binaries is time consuming and error prone.Some forensic tools simply contain the reversed profiledata as “Magic Numbers” embedded within their code (TheVolatility Foundation, 2014) without an explanation ofwhere these numbers came from, making forensic valida-tion and cross checking difficult.

We wish to automatically extend this analysis to newbinaries with minimal effort. We therefore want to expressthe required assembler pattern as a template which can beapplied to the new file's disassembly. In practice, however,the compiler is free to mix use of registers in functions, orreorder branches. Often identical source code will generateassembler code using different registers, and differentbranching order.

Fig. 5 shows the same code segment from two differentversions of the xxxCreateWindowStation function. As can beseen, although the general sequence of instructions issimilar, the exact registers are different for each case (This

Fig. 4. Rekall analysis of the global symbol grpWinStaList which contains an allocation of type tagWINDOWSTATION. This is followed by the exact struct layout asextracted from the PDB file.

Fig. 3. An example of a typical Windows Kernel pool allocation. ThePOOL_HEADER indicates the type of the allocation. This header is also part ofa doubly linked list with the next/previous allocation e a relationwhich maybe used to validate it. By observing the type of allocations the structmembers are pointing to it is possible to deduce the pointers and theirtarget type.

M.I. Cohen / Digital Investigation 12 (2015) S38eS49S44

Page 9: Characterization Of The Windows Kernel Version …...Characterization of the windows kernel version variability for accurate memory analysis Michael I. Cohen Google Inc., Brandschenkestrasse

function essentially checks the rpdesk pointer of the globalvariable gptiCurrent, a global tagTHREADINFO struct). Wetherefore construct our pattern match in such a way thatexact register names are not specified. We only require thesame register to be used for $var1 throughout the pattern.

Additionally, the compiler may reorder Assembler codefragments from version to version. When a branch isreordered, the pattern match may be split into differentparts of the branching instruction. In order to normalize theeffect of branching, we unroll all branches in the assemblyoutput. This means we follow all branches until we reachcode that is already disassembled and then backtrack toresume disassembly from the branch onwards. This tech-nique allows us to match our pattern against the completecode of each function.

For example consider Fig. 6. This shows a very shortfunction win32k!SetGlobalCursorLevel which dereferencesmany pointers to a number of structs. The function iteratesover all desktops (tagDESKTOP) and all threads (tag-THREADINFO) and sets their cursor level. It is quite simpleto infer the structs and fields involved when reading theassembly code (for Windows 7) in conjunction with thestruct definitions exported in the PDB files for Windows 7.The same templates can then be applied for other versionsof the binary for which there are no exported symbols.

Our template can now be published and independentlycross validated for accuracy. For example, in the event thatinvestigators find a different version of the binary in thewild, they are able to apply the templates and re-derive thestruct offsets directly from the binarye cross validating theresulting profile.

It must be noted that this technique does not work inevery case since the code does change from version toversion, sometimes dramatically. We therefore offer anumber of possible templates (to different functions) thatcan be applied in turn until a match is found.

Results

We have collected 133 unique versions of the“win32k.sys” driver binary, and downloaded PDB files for

these samples. We then generated assembler templates formany struct fields and ran these templates over these bi-naries in our collections.

Fig. 7 shows a summary of struct offsets across differentversions of the win32k driver. As can be seen, the structoffsets are generally not changed betweenmajor andminorbinary versions, although they do vary between eachminorversion.

Similarly, Fig. 8 shows that global constants vary wildlyfrom build to build, hence version number alone is insuf-ficient to provide reliable offsets for these constants.

Discussion

This study's main goal was to characterize what factorschange between various binary versions, and how these arerelevant to memory analysis. We found that generally,struct layout does not change within the same minorversion, but global constants were found to vary wildlywith version.

In our quest to characterize the variation we havedeveloped a number of very useful techniques:

1. We have developed a technique to build a “profile index”e a mechanism to quickly detect which profile from apre-calculated profile repository is applicable for a spe-cific memory image. Our method is resilient to anti-forensic manipulation since it uses a random selectionof comparison points chosen from the binary code anddata segments themselves.

2. We have also demonstrated a data analysis technique forrapidly determining struct offsets by analyzing kernelpool allocations.

3. We have created an Assembler templating languagewhich can be used to match sequences of assemblercode in order to extract struct offsets for struct members.This technique can be applied for static binaries as wellas binaries found in memory images.

How should these techniques be applied in order toimprove the accuracy of memory analysis software?

Fig. 5. Disassembled code for finding the tagTHREADINFO.rpdesk member offset. Even though the code is identical, different versions use different registers. Wedefine a search template (Below) in YAML format to describe the required pattern regardless of the exact registers used.

M.I. Cohen / Digital Investigation 12 (2015) S38eS49 S45

Page 10: Characterization Of The Windows Kernel Version …...Characterization of the windows kernel version variability for accurate memory analysis Michael I. Cohen Google Inc., Brandschenkestrasse

As noted previously, somememory analysis frameworkscurrently use techniques such as pool scanning, disassem-bling and other heuristics to guess the locations of globalkernel variables (The Volatility Foundation, 2014). This isespecially problematic when trying to locate win32k.sysglobal parameters since the GUI subsystem has a differentpool area for each session. Without contextual information,pool scanning techniques can not associate the correctkernel structures to the correct session, leading to manyerroneous results.

It is therefore desirable to rely on accurate profile in-formation in locating global structures. This warrants thecreation and maintenance of a public profile repositorywith accurate symbol information for each versionobserved in the wild (The Rekall Team, 2014a, b). Theproblem remains however, how does one know whichprofile should be used for a specific memory image?

By applying the profile indexing technique, one canreliably detect the correct profile to use for each memory

image. The profiles can then contain exact offsets of globalvariables and functions. This improves analysis becausethere is a large amount of accurate information available(for example it is possible to resolve addresses to functionnames e really helping with disassembly views).

Finally, we can address the problem of undocumentedstruct layouts. While the win32k.sys profiles do contain theaddresses of global variables and functions, most do notcontain struct layout.

Although we can apply the assembler templates todeduce the struct layouts directly within the memoryimage, this is not a reliable technique since in practice,many code pages will not be mapped into memory ecausing the disassembly of the required functions to fail.

Instead we can collect win32k.sys binaries of all majorandminor versions and apply the disassembly templates tothe binaries themselves. Although we can never be abso-lutely sure that struct layouts are the same in all builds ofthe same version, our analysis suggests this is the case. That

Fig. 6. An example of matching an assembler pattern across a short function. First the function is unrolled such that all its branches are displayed. The pattern isthen applied such that the same registers are used in a consistent manner. By comparing the assembly code to the struct field offsets in the exported PDB we caneasily infer the types of structs used in this function. We can then extrapolate this inference to deduce struct offsets for binary versions we have no debugginginformation for.

M.I. Cohen / Digital Investigation 12 (2015) S38eS49S46

Page 11: Characterization Of The Windows Kernel Version …...Characterization of the windows kernel version variability for accurate memory analysis Michael I. Cohen Google Inc., Brandschenkestrasse

is, the struct layout for win32k.sys depends only on themajor and minor version numbers of the win32k.sys binaryitself. We therefore make the assumption that struct layoutdoes not vary between major and minor versions (thisassumption seems to hold well as a result of this research).

Therefore, we construct a profile for all win32k.sys bi-naries bymerging the global constants and functions foundin the PDB files provided by Microsoft with the canonicalstruct layout for the specific major and minor version. Wethen similarly create a “profile index” for all knownwin32k.sys profiles and apply it on in the memory image todetect the correct profile to use.

Once the correct profile is found (containing both ac-curate constants and accurate struct layouts) we can use it

to conduct analysis of the memory image withoutproblems.

Limitations of symbol based memory analysis

In this paper we find that kernel constants vary greatlybetween kernel builds. We advocate locating the kernelconstants directly from the debugging symbols distributedby Microsoft. While this approach makes for an efficientanalysis, which is less susceptible to manipulation, it doeshave some shortcomings.

Themain problem is that we require the PDB files for theexact versions of the kernel we are dealing with to beavailable. While Microsoft typically publishes PDB files for

Fig. 7. Offsets for a selection of struct members across various versions of the Windows GUI subsystem. These offsets were derived by applying the automateddisassembly templates on the driver executable.

M.I. Cohen / Digital Investigation 12 (2015) S38eS49 S47

Page 12: Characterization Of The Windows Kernel Version …...Characterization of the windows kernel version variability for accurate memory analysis Michael I. Cohen Google Inc., Brandschenkestrasse

publicly released versions of the operating system, it ispossible that PDB files for private, or development versionsof the operating system are not published.

When Rekall encounters a windows kernel versionwhich does not exist in the repository, the user mayfollow a procedure to add it to the repository by down-loading the corresponding debug information from theMicrosoft symbol server. However, if this is not possible(perhaps because the PDB file is not published), the useris unable to proceed at all. Rekall does not employscanning or guessing techniques for locating kernelglobal constants without having the profile information(e.g. like Volatility does).

Conclusions and future work

Although this paper concentrates specifically on theWindows kernel binary and the win32k.sys GUI subsystemdriver, the techniques presented are applicable for otherdrivers and binaries.

Specifically, the tcpip.sys driver manages the networkstack and is largely undocumented. The same techniqueswe develop for constructing profiles from a mixture ofdocumented and undocumented (reversed) informationcan be applied to this case.

Identifying which of a set of known binaries matchesthe exact running binary in a memory image is a critical

Fig. 8. Offsets for a selection of global constants across various versions of the Windows GUI subsystem. These offsets were derived by parsing the provided PDBfiles for these binary versions.

M.I. Cohen / Digital Investigation 12 (2015) S38eS49S48

Page 13: Characterization Of The Windows Kernel Version …...Characterization of the windows kernel version variability for accurate memory analysis Michael I. Cohen Google Inc., Brandschenkestrasse

first step to memory analysis of all operating systems. Forexample, we have extended this method to auto-detect theexact kernel running on an OSX system.

The ability to generate profiles with more accurate in-formation allows one to abandon using scanning andguessing techniques for determining this information fromthe potentially compromised memory image itself. The lessthe framework relies on the memory image to deriveanalysis information, the more resilient it is to maliciousmanipulation. For example, the literature has noted thatthe Kernel Debugger Block can be easily overwritten bymalware in such away that memory analysis can fail to findit (Haruyama and Suzuki, 2012).

Finally, this paper presents the groundwork for ulti-mately addressing the difficult problem of Linux memoryanalysis. Linux kernel struct layouts vary wildly based onkernel configuration as well as purely on kernel version.Only recently has it become possible to acquire memory ona Linux system in a kernel version agnostic manner(Stüttgen and Cohen, 2014), but there is a wide need toreliably determine the correct profile for unknown kernelse often encountered during incident response situations.

Previously, systems were proposed that attempted toderive all kernel struct offsets by examining the specificassembly instructions. However these systems, failed totake into account register swapping and function re-branching (Case et al., 2010), making them less reliablefor matching real kernels in practice. This paper's proposedassembler templates are much more robust to these vari-ations. Previous dynamic analysis platforms attempt tobuild a complete profile from the reversed parameters.However, as shown in this paper, we only need to gatherjust enough information to select the correct profile from afinite set of knownprofile variations. Futurework can applythe techniques discussed in this paper to auto-detecting aLinux profile from an unknown kernel.

References

Aho AV, Corasick MJ. Efficient string matching: an aid to bibliographicsearch. Commun ACM 1975;18(6):333e40.

Case A, Marziale L, Richard III GG. Dynamic recreation of kernel datastructures for live forensics. Digit Investig 2010;7:S32e40.

Chen R. Why do windows functions all begin with a pointless mov edi, ediinstruction?. 2011. URL, http://blogs.msdn.com/b/oldnewthing/archive/2011/09/21/10214405.aspx.

Cohen M, Bilby D, Caronni G. Distributed forensics and incident responsein the enterprise. Digit Investig 2011;8:S101e10.

Dolan-Gavitt B. Push the red button: the types stream. 2007. http://moyix.blogspot.de/2007/10/types-stream.html.

Dolan-Gavitt B. The vad tree: a process-eye view of physical memory.Digit Investig 2007b;4:62e4.

Haruyama T, Suzuki H. One-byte modification for breaking memoryforensic analysis. Black Hat Europe; 2012.

Ligh M, Adair S, Hartstein B, Richard M. Malware analyst's cookbook andDVD: tools and techniques for fighting malicious code. Wiley Pub-lishing; 2010.

Ligh MH, Case A, Levy J, Walters A. The art of memory forensics: detectingmalware and threats in Windows, Linux, and Mac memory. 1st ed.Wiley Publishing; 2014.

Lin Z, Rhee J, Zhang X, Xu D, Jiang X. Siggraph: brute force scanning ofkernel data structure instances using graph-based signatures. In:NDSS symposium; 2011.

Mandt T. Kernel attacks through user-mode callbacks. 2011. URL, http://media.blackhat.com/bh-us-11/Mandt/BH/_US/_11/_Mandt/_win32k/_WP.pdf.

Okolica J, Peterson GL. Windows operating systems agnostic memoryanalysis. Digit Investig 2010;7:S48e56.

Roussev V, Ahmed I, Sires T. Image-based kernel fingerprinting. DigitInvestig 2014;11:S13e21.

Schreiber SB. Undocumented Windows 2000 secrets: a programmer'scookbook. Boston, MA, USA: Addison-Wesley Longman PublishingCo., Inc.; 2001.

Schuster A. Pool allocations as an information source in windows mem-ory forensics. In: IMF; 2006. p. 104e15.

Schuster A. Ptfinder (version 0.3.05). 2007. http://computer.forensikblog.de/en/2007/11/ptfinder-version-0305.html.

Schuster A. The impact of Microsoft Windows pool allocation strategieson memory forensics. Digit Investig 2008;5:S58e64.

Sikorski M, Honig A. Practical malware analysis: the hands-on guide todissecting malicious software. 1st ed. San Francisco, CA, USA: NoStarch Press; 2012.

Skape. Temporal return addresses, exploitation chronomancy. 2005. Un-informed 2. URL, http://www.uninformed.org/?v¼2&a¼2.

Stüttgen J, Cohen M. Robust Linux memory acquisition with minimaltarget impact. Digit Investig 2014;11:S112e9.

Sun XX, Chen H, Wen Y, Huang MH. Reversing engineering data structuresin binary programs: overview and case study. In: Innovative mobileand internet services in ubiquitous computing (IMIS), 2012 Sixth in-ternational conference on IEEE; 2012. p. 400e4.

The Rekall Team. The rekall memory forensic framework. 2014. URL,http://www.rekall-forensic.com/.

The Rekall Team. The rekall profile repository. 2014. URL, https://github.com/google/rekall-profiles.

The Volatility Foundation. The volatility framework. 2014. URL, http://www.volatilityfoundation.org/.

Williams J, Torres A. Add e complicating memory forensics throughmemory disarray. 2014. URL, https://archive.org/details/ShmooCon2014/_ADD/_Complicating/_Memory/_Forensics/_Through/_Memory/_Disarray.

Yuan F. Windows graphics programming: Win32 GDI and DirectDraw.Prentice Hall Professional; 2001.

M.I. Cohen / Digital Investigation 12 (2015) S38eS49 S49