Download as pdf or txt
Download as pdf or txt
You are on page 1of 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 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
Digital Investigation 12 (2015) S38eS49

Contents lists available at ScienceDirect

Digital Investigation
journal homepage: www.elsevier.com/locate/diin

Characterization of the windows kernel version variability for


accurate memory analysis
Michael I. Cohen
Google Inc., Brandschenkestrasse 110, Zurich, Switzerland

a b s t r a c t

Keywords: Memory analysis is an established technique for malware analysis and is increasingly used
Memory analysis for incident response. However, in most incident response situations, the responder often
Incident response has no control over the precise version of the operating system that must be responded to.
Binary classification
It is therefore critical to ensure that memory analysis tools are able to work with a wide
Memory forensics
range of OS kernel versions, as found in the wild. This paper characterizes the properties of
Live forensics
different Windows kernel versions and their relevance to memory analysis. By collecting a
large 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 can
therefore directly use known kernel global offsets and do not need to guess those by
scanning techniques. We demonstrate that struct offsets can be rapidly deduced from
analysis 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 subsystem
driver and develop a robust technique for combining both profile constants and reversed
struct 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 for


the detection and identification of malware, and for digital
forensic investigations (Ligh et al., 2010, 2014).
Fundamentally, memory analysis is concerned with
interpreting the seemingly unstructured raw memory data
which can be collected from a live system into meaningful The compiler will decide how to overlay the struct fields
and actionable information. At first sight, the memory in memory depending on their size, alignment re-
content of a live system might appear to be composed of quirements and other consideration. So for example, the
nothing more than random bytes. However, those bytes are CreateTime field might get 8 bytes, causing the Image-
arranged in a predetermined order by the running software FileName field to begin 8 bytes after the start of the
to represent a meaningful data structure. For example _EPROCESS struct.
consider the C struct: A memory analysis framework must have the same
layout information in order to know where each field
should be found in relation to the start of the struct. Early
memory analysis systems hard coded this layout informa-
E-mail address: [email protected]. tion which was derived by other means (e.g. reverse

http://dx.doi.org/10.1016/j.diin.2015.01.009
1742-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/).
M.I. Cohen / Digital Investigation 12 (2015) S38eS49 S39

engineering or simply counting the fields in the struct binaries to be debugged, without needing to ship bulky
header file (Schuster, 2007)). debug information with final release binaries.
This approach is not scalable though, since the struct The PDB file contains a number of useful pieces of in-
definition change routinely between versions of the oper- formation for a memory analysis framework:
ating system. For example, in the above simplified struct of
an _EPROCESS, if additional fields are inserted, the layout of " Struct members and memory layout. This contains in-
the field members will change to make room for the new formation about memory offsets for struct members,
elements. So for example, if another 4 byte field is added and their types. This is useful in order to interpret the
before the CreateTime field, all other offsets will have to contents of memory.
increase by 4 bytes to accommodate the new field. This will " Global constants. The Windows kernel contains many
cause all the old layout information to be incorrect and our important constants, which are required for analysis. For
interpretation of the struct in memory to be wrong. example, the PsActiveProcessHead is a constant pointer
Modern memory analysis frameworks address the var- to the beginning of the process linked list, and is
iations across different operating system versions by use of required in order to list processes by walking that list.
a version specific memory layout template mechanism. For " Function addresses. The location of functions in memory
example in Volatility (The Volatility Foundation, 2014) or is also provided in the PDB file e even if these functions
Rekall (The Rekall Team, 2014a, b) this information is called are not exported. This is important in order to resolve
a profile. addresses back to functions (e.g. in viewing the Inter-
The Volatility memory analysis framework (The rupt Descriptor Table e IDT).
Volatility Foundation, 2014) is shipped with a number of " Enumeration. In C an enumeration is a compact way to
Windows profiles embedded into the program. The user represent one of a set of choices using an integer. The
chooses the correct profile to use depending on their mapping between the integer value and a human
image. For example, if analyzing a Windows 7 image, the meaningful string is stored in the PDB file, and it is
profile might be specified as Win7SP1x64. In Volatility, the useful for interpreting meaning from memory.
profile name conveys major version information (i.e. Win-
dows 7), minor version information (i.e. Service Pack 1) and
architecture (i.e. !64). Volatility uses this information to Characterizing kernel version variability
select a profile from the set of built-in profiles.
As described previously, the Volatility tool only contains
a handful of profiles generated for different major releases
Deriving profile information of the Windows kernel. However, each time the kernel is
rebuilt by Microsoft (e.g. for a security hot fix), the code
The problem still remains how to derive this struct could be changed, and the profile could be different. The
layout information automatically. The Windows kernel assumption made by the Volatility tool is that these
contains many struct definitions, and these change for each changes are not significant and therefore, a profile gener-
version, so a brute force solution is not scalable (Okolica ated from a single version of a major release will work on
and Peterson, 2010). all versions from that release.
Memory analysis frameworks are not the only case We wanted to validate this assumption. We collected
where information about memory layout is required. Spe- the Windows kernel binary (ntkrnlmp.exe, ntkrpamp.exe,
cifically, when debugging an application, the debugger ntoskrnl.exe) from several thousand machines in the wild
needs to know how to interpret the memory of the using the GRR tool (Cohen et al., 2011). Each of these bi-
debugged program in order to correctly display it to the naries has a unique GUID, and we were therefore able to
user. Since the compiler is the one originally deciding on download the corresponding PDB file from the public
the memory layout, it makes sense that the compiler gen- Microsoft symbol server. We then used Rekall's mspdb
erates debugging information about memory layout for the parser to extract debugging information from each PDB file.
debugger to use. This resulted in 168 different binaries of the Windows
On Windows systems, the most common compiler used kernel for various versions (e.g. Windows XP, Windows
is the Microsoft Visual Studio compiler (MSVCC). This Vista, Windows 7 and Windows 8) and architectures (e.g.
compiler shares debugging information via a PDB file I386 and AMD64). Clearly, there are many more versions of
(Schreiber, 2001), generated during the build process for the Windows kernel in the wild than exist in the Volatility
the executable. The PDB file format is unfortunately un- tool. It is also very likely that we have not collected all the
documented, but has been reverse engineered sufficiently versions that were ever released by Microsoft, so our
to be able to extract accurate debugging information, such sample size, although large, is not exhaustive.
as struct memory layout, reliably (Schreiber, 2001; Dolan- Fig. 1 shows sampled offsets of four critical struct
Gavitt, 2007a). members for memory analysis:
The PDB file for an executable is normally not shipped
together with the executable. The executable contains a " The _EPROCESS.VadRoot is the location of the Vad within
unique GUID referring to the PDB file that describes this the process. This is used to enumerate process alloca-
executable. When the debugger wishes to debug a partic- tions (Dolan-Gavitt, 2007b).
ular executable, it can then request the correct PDB file " The _KPROCESS.DirectoryTableBase is the location of the
from a symbol server. This design allows production Directory Table Base (i.e. the value loaded into the CR3
S40 M.I. Cohen / Digital Investigation 12 (2015) S38eS49

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

register) which is critical in constructing the Virtual 466B4165EAA84AF88D29D617E86A95982), the struct off-
Address Space abstraction. sets remain the same for all major Windows releases.
" The _EPROCESS.ImageFileName is the file name of the Therefore, chances are good that the Volatility profile for a
running binary. For example, this field might contain given Windows version would actually work most of the
“csrss.exe”. time for determining struct layout.

Microsoft Windows kernel versions contain four parts: Kernel global constants variability
The major and minor versions, the revision and the build
number. The build number increases for each build (e.g. It is generally not sufficient to determine only the struct
security hotfix). memory layout for memory analysis. For example, consider
As can be seen in the figure, struct offsets do tend to listing the running processes. One technique is to follow
remain stable across Windows versions. In most cases, with the doubly linked list of EPROCESS.ActiveProcessLinks in
a single notable exception e version 5.2.3970.175 (GUID each process struct (Okolica and Peterson, 2010). This
M.I. Cohen / Digital Investigation 12 (2015) S38eS49 S41

technique needs to find the start of the list which begins at " NtBuildLab is the location of the NT version string (e.g.
the global kernel constant PsActiveProcessHead. The loca- “7600.win7_rtm.090713-1255”). This is used to identify
tion for this global constant in memory is determined the running kernel.
statically by the compiler at compile time, and it is usually " PsActiveProcessHead is the head of the active process list.
stored in one of the data sections in the PE file itself. This is required in order to list the running processes.
Since this information is also required by the debugger, " NtCreateToken is an example of a kernel function. This
the PDB file also contains information about global con- will normally exist in the .text section of the PE file.
stants and functions (even if these are not actually exported " str:FILE_VERSION is literally the string “FILE_VERSION”.
via the Export Address Table). Rekall's mspdb plugin also Usually the compiler will place all literal strings into
extract this information into the profile. their own string table in the .rdata section of the PE file.
Fig. 2 illustrates the memory addresses of some impor- The compiler will then emit debugging symbols for the
tant kernel constants for the kernels in our collection: location 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 information
from the Microsoft debug server for the binaries in our collection. Offsets are provided relative to the kernel image base address.
S42 M.I. Cohen / Digital Investigation 12 (2015) S38eS49

strings. The importance of this symbol will be discussed reliable way to identify the kernel version without relying
in the following sections. on a single signature.
The problem of identifying kernel binaries in a memory
As can be seen, the offsets of global kernel constants image has been examined previously in the Linux memory
change dramatically between each build e even for the analysis context (Roussev et al., 2014). In that paper, the
same version. This makes sense, since the compiler ar- authors used similarity hashing to match the kernel in a
ranges global constants in their own PE section, so if any memory image with a corpus of known binaries.
global constant is added or removed in the entire kernel, In our case, we do not always have the actual binaries
this affects the ordering of all other constants placed after but have debugging symbols from these binaries. We
it. therefore need a way for deducing enough information
It is therefore clear that it is unreliable to directly obtain about the kernel binary itself (which we may not have)
the addresses of kernel globals by simply relying on the from the debug symbols. Consider the following informa-
version alone. The Volatility tool resorts to a number of tion present in the PDB file:
techniques to obtain these globals:
" String Literals. As shown in the example above, the
" Many globals are obtained from the KdDebuggerData- compiler generates string literals in the PE binary itself.
Block e another global kernel struct which contains These are then located using global debugging symbols.
pointers to many other globals. This structure is usually For example, in Fig. 2 we know the exact offsets in memory
scanned for. where we expect find the string “FILE_VERSION”.
" Scanning for kernel objects which refer to global con- " Function preamble. The PDB file also contains the loca-
stants (e.g. via pool tag scanning or other signatures). tions of many functions. We note that each function is
" Examining the export tables of various PE binaries for generally preceded by 5 NOP instructions in order to
exported functions. make room for hot patching (Chen, 2011). Thus, we can
" Dynamically disassembling code to detect calls to non deduce that for each function in the PDB, the previous
exported functions. byte contains the value 0!90 (NOP instruction).

These techniques are complex and error prone. They are The problem, therefore, boils down to identifying which
also susceptible to anti-forensics as signature scanners can of a finite set of kernel profiles is the one present in the
trivially be fooled by spurious signatures (Williams and memory image, based on known data that must exist at
Torres, 2014). Scanning for signatures over very large known offsets:
memory images is also slow and inefficient.
The Rekall memory forensic framework (The Rekall 1. Begin by selecting a number of function names, or literal
Team, 2014a, b), a fork of the Volatility framework, takes string names. We term these Comparison Points since we
a different approach. Instead of guessing the location of only compare the binaries at these known offsets.
various kernel constants, the framework relies on a public 2. Examine all available profiles, and record the offset of
profile repository which contains every known profile from these symbols as well as the expected data to appear at
every known build of the Windows kernel. This greatly this offset (either a NOP instruction or the literal string
simplifies memory analysis algorithms because the address itself).
of global kernel variables and functions is directly known 3. Build a decision tree around the known comparison
from public debugging information provided by Microsoft. points to minimize the number of string comparisons
There is no need to scan or guess at all. Locating these required for narrowing down the match. Note that at this
globals is very efficient since there is no need to scan for stage it is possible to determine if there are sufficient
signatures, making the framework fast and reducing the number of comparison points to distinguish all profile
ability of attackers to subvert analysis. selections. If profile selection is ambiguous, further
comparison points are added and the process starts again.
4. Scan the memory image for the longest strings using the
Identifying binary versions Aho-Corasick string matching algorithm (Aho and
Corasick, 1975).
The Rekall profile repository contains, at the time of 5. For each match, seek around the match to apply the
writing, 309 profiles for various Windows kernel versions decision tree calculated earlier. Within a few string
(and this number is constantly increasing). Typically, users comparisons, the correct profile is identified.
will simply report the GUID of the Windows kernel found 6. Load the profile from the profile repository and initialize
in their image, but will not provide the actual kernel binary. the analysis.
Previously, Rekall employed a scanning technique to
locate the GUID of the NT kernel running within the image. In practice it was found that fewer than a dozen com-
Once the GUID is known, the correct profile can be fetched parison points are required to characterize all the profiles in
from the repository and analysis can begin. However, this the Rekall profile repository, leading to extremely quick
technique is still susceptible to manipulation (It is easy for matching times. Also, binary identification is robust to
attackers to simply wipe or alter the GUID from memory). manipulation since the choice of comparison points is
Sometimes the GUID is paged out of memory and in this rather arbitrary and can be changed easily.
case it is impossible to guess it. What we really need is a
M.I. Cohen / Digital Investigation 12 (2015) S38eS49 S43

Windows kernel binary identification One of the most interesting kernel drivers is the Win-
dows 32 user mode GUI subsystem (Mandt, 2011; Yuan,
Section 3 described an efficient algorithm for identi- 2001), implemented as “win32k.sys”. The data structures
fying a binary match from a set of known binaries. How- used in this subsystem are required to detect many com-
ever, in the memory analysis context, this comparison must mon hooks placed by malware (e.g. SetWindowsHookEx()
be made in the Virtual address space. Modern CPUs operate style keyloggers (Sikorski and Honig, 2012)).
in protected mode, and the exact memory accessible to the The Rekall profile repository currently contains profiles
kernel does not necessarily need to be contiguous in the for 169 unique versions of this driver. However, only 33
physical memory image. versions include information about critical structures (e.g.
Therefore, before we are able to apply the index classi- tagDESKTOP and tagWINDOWSTATION). The remaining
fication algorithm, we must build a virtual address space, profiles only contain information about global constants
requiring us to identify the value of CR3, or the kernel's and functions, but no structure information.
Directory Table Base (DTB). Our goal is to understand how various important
The DTB can be captured during the acquisition process structures evolved through the released versions. Since
and stored in the image, but typically it must be scanned for. many of these versions are undocumented and do not have
The Volatility memory forensic framework scans for the Idle debugging information, previous research has manually
process's EPROCESS struct. It first searches for the literal string reverse engineered several samples from different ver-
“Idle”, this should exist as the EPROCESS.ImageFileName sions. However, we are unsure if there is internal variability
member. Knowing the difference between the offsets of within Windows versions and releases. Guided by our
EPROCESS.ImageFileName and EPROCESS.Pcb.DirectoryTa- previous experience with the Windows Kernel versions, we
bleBase, the framework reads the DTB and therefore locates hypothesize that the win32k.sys struct layout would not
the page tables. vary much between minor release versions.
The problem with this approach is that it requires knowing Given our large corpus of binaries we can directly
the exact offsets of two EPROCESS struct members. Fig. 1 examine this hypothesis and evaluate the best approach for
shows how these relative offsets vary between Windows determining struct layout when analyzing the Win32k GUI
versions, so to know the offset we need to know the exact subsystem.
Windows version we are examining e but we can not identify
the profile without applying the profile index, which requires Data driven reverse engineering
a valid kernel address space e i.e. knowing the DTB first!
We solve this Catch-22 by noting that the total number The literature contains a number of published systems
of combinations of the EPROCESS member offsets is limited for automatically detecting kernel objects from memory
(4 combinations for 64 bit architectures and 6 combina- images (Sun et al., 2012). For example, the SigGraph system
tions for 32 bit architectures). Therefore, it is possible to (Lin et al., 2011), is capable of building scanners for Linux
brute force all combinations in search of a valid DTB. kernel structures by analyzing their internal pointer
So in summary the complete Kernel Binary Autode- graphs.
tection algorithm, as implemented in Rekall, is: The SigGraph system specifically does not utilize inci-
dental knowledge about the system to assist in the
" Scan the image for common Windows executable names reversing task. However on Windows systems, there are
(e.g. “csrss.exe”, “cmd.exe” etc). This scan uses the Aho- some helpful observation one can make to facilitate type
Corasick algorithm to search for all strings at once. analysis from memory dumps.
" For each hit, brute force the DTB going through the 10 In the Windows kernel all allocations come from one of
possible offsets. The DTB is validated using the the kernel pools (e.g. Paged, Non-Paged or Session Pool).
KUSER_SHARED_DATA.NtMajorVersion and KUSER_- Allocations smaller than a page are preceded by a POOL_-
SHARED_DATA.NtMinorVersion members. Since this HEADER object (Schuster, 2006, 2008).
struct must be found at a fixed location in memory and The pool header contains a known tag as well as in-
always have the same layout it is safe to hardcode it dications of the previous and next pool allocation (within
(Skape, 2005). Therefore, we can validate the DTB and the page). Thus, small pool allocations form a doubly linked
kernel address space without knowing anything about list. Due to this property it is possible to validate the pool
the profile itself or the kernel version. header and locate it in memory. A typical Windows kernel
" Once a DTB is identified, we construct a virtual address allocation is illustrated in Fig. 3.
space and scan for the kernel image in memory using If we were to ask, “What kernel object exists at a given
the algorithm previously described. virtual offset?”, we can simply scan backwards for a suit-
able POOL_HEADER structure and deduce the type of object
from the pool tag. We can further scan forward from this
Undocumented kernel structures location for other heuristics, such as pointers to certain
other pool allocations, or doubly linked lists. We wrote a
Section 2 examined the variability of documented Rekall plugin called analyze_structs to perform this analysis
kernel structures across different kernel versions. The on arbitrary memory locations.
question we try to answer now is, what is the variability of For example, Fig. 4 shows the analysis of the global
undocumented kernel structures of significance to the symbol grpWinStaList which is the global offset of the head
memory analyst? of the tagWINDOWSTATION list. We can see that at offset
S44 M.I. Cohen / Digital Investigation 12 (2015) S38eS49

Code based reverse engineering

The previous section demonstrates how we can deduce


some struct layouts by observation of allocations we can find
from the kernel pools. However, these observations are not
sufficient to deduce all types of members. Specifically, only
pointers are reliably deduced by this method. Additionally,
we must observe allocated memory in a memory dump from
a running system. Often we 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 traditional
reverse engineering approach. Previously, researchers have
reverse engineered specific exemplars of the win32k.sys
Fig. 3. An example of a typical Windows Kernel pool allocation. The
POOL_HEADER indicates the type of the allocation. This header is also part of
binary which is representative of a specific Windows
a doubly linked list with the next/previous allocation e a relation which may version (The Volatility Foundation, 2014). However,
be used to validate it. By observing the type of allocations the struct manually reverse engineering every file in our large corpus
members are pointing to it is possible to deduce the pointers and their of win32k.sys binaries is time consuming and error prone.
target type.
Some forensic tools simply contain the reversed profile
data as “Magic Numbers” embedded within their code (The
Volatility Foundation, 2014) without an explanation of
where these numbers came from, making forensic valida-
0!10 there is a pointer to the tagDESKTOP object, at offset
tion and cross checking difficult.
0!18 there is a pointer to the global gTermIO object etc.
We wish to automatically extend this analysis to new
With Windows 7 we can find the complete struct in-
binaries with minimal effort. We therefore want to express
formation in the PDB file. This is also shown in Fig. 4. We
the required assembler pattern as a template which can be
can see that the detected pointers correspond with the
applied to the new file's disassembly. In practice, however,
rpdeskList, pTerm, spklList, pGlobalAtomTable and psidUser
the compiler is free to mix use of registers in functions, or
members.
reorder branches. Often identical source code will generate
An obvious limitation of this technique is that if a
assembler code using different registers, and different
pointer in the struct is set to NULL, we are unable to say
branching order.
anything about it. Hence to reveal as many fields as
Fig. 5 shows the same code segment from two different
possible we need to examine as many instances of the
versions of the xxxCreateWindowStation function. As can be
same object type as we can find (e.g. via pool scanning
seen, although the general sequence of instructions is
techniques).
similar, 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 as
extracted from the PDB file.
M.I. Cohen / Digital Investigation 12 (2015) S38eS49 S45

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

function essentially checks the rpdesk pointer of the global these samples. We then generated assembler templates for
variable gptiCurrent, a global tagTHREADINFO struct). We many struct fields and ran these templates over these bi-
therefore construct our pattern match in such a way that naries in our collections.
exact register names are not specified. We only require the Fig. 7 shows a summary of struct offsets across different
same register to be used for $var1 throughout the pattern. versions of the win32k driver. As can be seen, the struct
Additionally, the compiler may reorder Assembler code offsets are generally not changed between major and minor
fragments from version to version. When a branch is binary versions, although they do vary between each minor
reordered, the pattern match may be split into different version.
parts of the branching instruction. In order to normalize the Similarly, Fig. 8 shows that global constants vary wildly
effect of branching, we unroll all branches in the assembly from build to build, hence version number alone is insuf-
output. This means we follow all branches until we reach ficient to provide reliable offsets for these constants.
code that is already disassembled and then backtrack to
resume disassembly from the branch onwards. This tech-
Discussion
nique allows us to match our pattern against the complete
code of each function.
This study's main goal was to characterize what factors
For example consider Fig. 6. This shows a very short
change between various binary versions, and how these are
function win32k!SetGlobalCursorLevel which dereferences
relevant to memory analysis. We found that generally,
many pointers to a number of structs. The function iterates
struct layout does not change within the same minor
over all desktops (tagDESKTOP) and all threads (tag-
version, but global constants were found to vary wildly
THREADINFO) and sets their cursor level. It is quite simple
with version.
to infer the structs and fields involved when reading the
In our quest to characterize the variation we have
assembly code (for Windows 7) in conjunction with the
developed a number of very useful techniques:
struct definitions exported in the PDB files for Windows 7.
The same templates can then be applied for other versions
1. We have developed a technique to build a “profile index”
of the binary for which there are no exported symbols.
e a mechanism to quickly detect which profile from a
Our template can now be published and independently
pre-calculated profile repository is applicable for a spe-
cross validated for accuracy. For example, in the event that
cific memory image. Our method is resilient to anti-
investigators find a different version of the binary in the
forensic manipulation since it uses a random selection
wild, they are able to apply the templates and re-derive the
of comparison points chosen from the binary code and
struct offsets directly from the binary e cross validating the
data segments themselves.
resulting profile.
2. We have also demonstrated a data analysis technique for
It must be noted that this technique does not work in
rapidly determining struct offsets by analyzing kernel
every case since the code does change from version to
pool allocations.
version, sometimes dramatically. We therefore offer a
3. We have created an Assembler templating language
number of possible templates (to different functions) that
which can be used to match sequences of assembler
can be applied in turn until a match is found.
code in order to extract struct offsets for struct members.
This technique can be applied for static binaries as well
Results as binaries found in memory images.

We have collected 133 unique versions of the How should these techniques be applied in order to
“win32k.sys” driver binary, and downloaded PDB files for improve the accuracy of memory analysis software?
S46 M.I. Cohen / Digital Investigation 12 (2015) S38eS49

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 is
then 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 can
easily 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 debugging
information for.

As noted previously, some memory analysis frameworks image. The profiles can then contain exact offsets of global
currently use techniques such as pool scanning, disassem- variables and functions. This improves analysis because
bling and other heuristics to guess the locations of global there is a large amount of accurate information available
kernel variables (The Volatility Foundation, 2014). This is (for example it is possible to resolve addresses to function
especially problematic when trying to locate win32k.sys names e really helping with disassembly views).
global parameters since the GUI subsystem has a different Finally, we can address the problem of undocumented
pool area for each session. Without contextual information, struct layouts. While the win32k.sys profiles do contain the
pool scanning techniques can not associate the correct addresses of global variables and functions, most do not
kernel structures to the correct session, leading to many contain struct layout.
erroneous results. Although we can apply the assembler templates to
It is therefore desirable to rely on accurate profile in- deduce the struct layouts directly within the memory
formation in locating global structures. This warrants the image, this is not a reliable technique since in practice,
creation and maintenance of a public profile repository many code pages will not be mapped into memory e
with accurate symbol information for each version causing the disassembly of the required functions to fail.
observed in the wild (The Rekall Team, 2014a, b). The Instead we can collect win32k.sys binaries of all major
problem remains however, how does one know which and minor versions and apply the disassembly templates to
profile should be used for a specific memory image? the binaries themselves. Although we can never be abso-
By applying the profile indexing technique, one can lutely sure that struct layouts are the same in all builds of
reliably detect the correct profile to use for each memory the same version, our analysis suggests this is the case. That
M.I. Cohen / Digital Investigation 12 (2015) S38eS49 S47

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

is, the struct layout for win32k.sys depends only on the to conduct analysis of the memory image without
major and minor version numbers of the win32k.sys binary problems.
itself. We therefore make the assumption that struct layout
does not vary between major and minor versions (this Limitations of symbol based memory analysis
assumption seems to hold well as a result of this research).
Therefore, we construct a profile for all win32k.sys bi- In this paper we find that kernel constants vary greatly
naries by merging the global constants and functions found between kernel builds. We advocate locating the kernel
in the PDB files provided by Microsoft with the canonical constants directly from the debugging symbols distributed
struct layout for the specific major and minor version. We by Microsoft. While this approach makes for an efficient
then similarly create a “profile index” for all known analysis, which is less susceptible to manipulation, it does
win32k.sys profiles and apply it on in the memory image to have some shortcomings.
detect the correct profile to use. The main problem is that we require the PDB files for the
Once the correct profile is found (containing both ac- exact versions of the kernel we are dealing with to be
curate constants and accurate struct layouts) we can use it available. While Microsoft typically publishes PDB files for
S48 M.I. Cohen / Digital Investigation 12 (2015) S38eS49

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 PDB
files for these binary versions.

publicly released versions of the operating system, it is Conclusions and future work
possible that PDB files for private, or development versions
of the operating system are not published. Although this paper concentrates specifically on the
When Rekall encounters a windows kernel version Windows kernel binary and the win32k.sys GUI subsystem
which does not exist in the repository, the user may driver, the techniques presented are applicable for other
follow a procedure to add it to the repository by down- drivers and binaries.
loading the corresponding debug information from the Specifically, the tcpip.sys driver manages the network
Microsoft symbol server. However, if this is not possible stack and is largely undocumented. The same techniques
(perhaps because the PDB file is not published), the user we develop for constructing profiles from a mixture of
is unable to proceed at all. Rekall does not employ documented and undocumented (reversed) information
scanning or guessing techniques for locating kernel can be applied to this case.
global constants without having the profile information Identifying which of a set of known binaries matches
(e.g. like Volatility does). the exact running binary in a memory image is a critical
M.I. Cohen / Digital Investigation 12 (2015) S38eS49 S49

first step to memory analysis of all operating systems. For Cohen M, Bilby D, Caronni G. Distributed forensics and incident response
in the enterprise. Digit Investig 2011;8:S101e10.
example, we have extended this method to auto-detect the
Dolan-Gavitt B. Push the red button: the types stream. 2007. http://
exact kernel running on an OSX system. moyix.blogspot.de/2007/10/types-stream.html.
The ability to generate profiles with more accurate in- Dolan-Gavitt B. The vad tree: a process-eye view of physical memory.
formation allows one to abandon using scanning and Digit Investig 2007b;4:62e4.
Haruyama T, Suzuki H. One-byte modification for breaking memory
guessing techniques for determining this information from forensic analysis. Black Hat Europe; 2012.
the potentially compromised memory image itself. The less Ligh M, Adair S, Hartstein B, Richard M. Malware analyst's cookbook and
the framework relies on the memory image to derive DVD: tools and techniques for fighting malicious code. Wiley Pub-
lishing; 2010.
analysis information, the more resilient it is to malicious Ligh MH, Case A, Levy J, Walters A. The art of memory forensics: detecting
manipulation. For example, the literature has noted that malware and threats in Windows, Linux, and Mac memory. 1st ed.
the Kernel Debugger Block can be easily overwritten by Wiley Publishing; 2014.
Lin Z, Rhee J, Zhang X, Xu D, Jiang X. Siggraph: brute force scanning of
malware in such a way that memory analysis can fail to find kernel data structure instances using graph-based signatures. In:
it (Haruyama and Suzuki, 2012). NDSS symposium; 2011.
Finally, this paper presents the groundwork for ulti- Mandt T. Kernel attacks through user-mode callbacks. 2011. URL, http://
media.blackhat.com/bh-us-11/Mandt/BH/_US/_11/_Mandt/_win32k/_
mately addressing the difficult problem of Linux memory WP.pdf.
analysis. Linux kernel struct layouts vary wildly based on Okolica J, Peterson GL. Windows operating systems agnostic memory
kernel configuration as well as purely on kernel version. analysis. Digit Investig 2010;7:S48e56.
Roussev V, Ahmed I, Sires T. Image-based kernel fingerprinting. Digit
Only recently has it become possible to acquire memory on
Investig 2014;11:S13e21.
a Linux system in a kernel version agnostic manner Schreiber SB. Undocumented Windows 2000 secrets: a programmer's
(Stüttgen and Cohen, 2014), but there is a wide need to cookbook. Boston, MA, USA: Addison-Wesley Longman Publishing
reliably determine the correct profile for unknown kernels Co., Inc.; 2001.
Schuster A. Pool allocations as an information source in windows mem-
e often encountered during incident response situations. ory forensics. In: IMF; 2006. p. 104e15.
Previously, systems were proposed that attempted to Schuster A. Ptfinder (version 0.3.05). 2007. http://computer.forensikblog.
derive all kernel struct offsets by examining the specific de/en/2007/11/ptfinder-version-0305.html.
Schuster A. The impact of Microsoft Windows pool allocation strategies
assembly instructions. However these systems, failed to on memory forensics. Digit Investig 2008;5:S58e64.
take into account register swapping and function re- Sikorski M, Honig A. Practical malware analysis: the hands-on guide to
branching (Case et al., 2010), making them less reliable dissecting malicious software. 1st ed. San Francisco, CA, USA: No
Starch Press; 2012.
for matching real kernels in practice. This paper's proposed Skape. Temporal return addresses, exploitation chronomancy. 2005. Un-
assembler templates are much more robust to these vari- informed 2. URL, http://www.uninformed.org/?v¼2&a¼2.
ations. Previous dynamic analysis platforms attempt to Stüttgen J, Cohen M. Robust Linux memory acquisition with minimal
target impact. Digit Investig 2014;11:S112e9.
build a complete profile from the reversed parameters. Sun XX, Chen H, Wen Y, Huang MH. Reversing engineering data structures
However, as shown in this paper, we only need to gather in binary programs: overview and case study. In: Innovative mobile
just enough information to select the correct profile from a and internet services in ubiquitous computing (IMIS), 2012 Sixth in-
ternational conference on IEEE; 2012. p. 400e4.
finite set of known profile variations. Future work can apply
The Rekall Team. The rekall memory forensic framework. 2014. URL,
the techniques discussed in this paper to auto-detecting a http://www.rekall-forensic.com/.
Linux profile from an unknown kernel. The Rekall Team. The rekall profile repository. 2014. URL, https://github.
com/google/rekall-profiles.
The Volatility Foundation. The volatility framework. 2014. URL, http://
References www.volatilityfoundation.org/.
Williams J, Torres A. Add e complicating memory forensics through
Aho AV, Corasick MJ. Efficient string matching: an aid to bibliographic memory disarray. 2014. URL, https://archive.org/details/
search. Commun ACM 1975;18(6):333e40. ShmooCon2014/_ADD/_Complicating/_Memory/_Forensics/_
Case A, Marziale L, Richard III GG. Dynamic recreation of kernel data Through/_Memory/_Disarray.
structures for live forensics. Digit Investig 2010;7:S32e40. Yuan F. Windows graphics programming: Win32 GDI and DirectDraw.
Chen R. Why do windows functions all begin with a pointless mov edi, edi Prentice Hall Professional; 2001.
instruction?. 2011. URL, http://blogs.msdn.com/b/oldnewthing/
archive/2011/09/21/10214405.aspx.

You might also like