Each process started on x86 version of Windows uses a flat memory model that ranges from 0x00000000 – 0xFFFFFFFF. The lower half of the memory, 0x00000000 – 0x7FFFFFFF, is reserved for user space code.While the upper half of the memory, 0x80000000 – 0xFFFFFFFF, is reserved for the kernel code. The Windows operating system also doesn’t use the segmentation (well actually it does, because it has to), but the segment table contains segment descriptors that use the entire linear address space. There are four segments, two for user and two for kernel mode, which describe the data and code for each of the modes. But all of the descriptors actually contain the same linear address space. This means they all point to the same segment in memory that is 0xFFFFFFFF bits long, proving that there is no segmentation on Windows systems.

Let’s execute the “dg 0 30” command to display the first 7 segment descriptors that can be seen on the picture below:

Notice that the 0008, 0010, 0018 and 0020 all start at base address 0x00000000 and end at address 0xFFFFFFFF: They represent the data and code segments of user and kernel mode. This also proves that the segmentation is actually not used by the Windows system. Therefore we can use the terms”virtual address space” and “linear address space” interchangeably, because they are the same in this particular case. Because of this, when talking about user space code being loaded in the virtual address space from 0x00000000 to 0x7FFFFFFF, we’re actually talking about linear addresses. Those addresses are then sent into the paging unit to be translated into physical addresses. We’ve just determined that even though each process uses a flat memory model that spans the entire 4GB linear address space, it can only use half of it.This is because the other half is reserved for kernel code: the program can thus use, at most, 2GB of memory.

Every process has its own unique value in the CR3 register that points to the process’ page directory table. Because each process has its own page directory table that is used to translate the linear address to physical address, two processes can use the same linear address, while their physical address is different. Okay, so each program has its own address space reserved only for that process, but what about the kernel space? Actually, the kernel is loaded only once in memory and each program must use it. This is why each process needs appropriate pointers set-up that do just that. It means that the PDEs stored in the page directory of every process point to the same kernel page table.

We know that the kernel’s memory is protected by various mechanisms, so the user process can’t call it directly, but can call into the kernel by using special gates that only allow certain actions to be requested of kernel. Thus, we can’t simply tell a program to go into kernel mode and do whatever it pleases,because the code that does whatever we want is in user space. Therefore,the programs can only request a special action that’s already provided by a kernel’s code to do it for them. If we would like to run our own code in kernel memory, we must first find a way to pass the code to the kernel and stay there. One such example would be the installation of some driver that requires kernel privileges to be run,because it’s interacting more or less directly with the hardware component for which the driver has been written. The user processes can then use that driver’s functionality to request certain actions to be done: but only those that have been implemented in the driver itself. I hope I made it clear that the functionality run in kernel mode is limited to the functionality of the code loaded in kernel mode, and thus the code from user mode is not allowed to be executed with kernel privileges.

Let’s take a look at an example. On the Windows system being debugged by Windbg, I started Internet Explorer and Opera to analyze their memory space. After the programs have been started, I run the “!process 0 0” command to get the basic information about all the processes currently active on a system. The result of the command can be shown below where we’ve only listed the two processes in question (not all of them):

kd> !process 0 0
PROCESS 821e23c0  SessionId: 0  Cid: 0380    Peb: 7ffd4000  ParentCid: 05d4
DirBase: 094c01a0  ObjectTable: e1e88650  HandleCount: 307.
    Image: opera.exe

PROCESS 81f799f8  SessionId: 0  Cid: 047c    Peb: 7ffd8000  ParentCid: 05d4
DirBase: 094c0200  ObjectTable: e1c7b630  HandleCount: 570.

The DirBase element above is actually the value that gets stored in the CR3 register every time a context-switch to a particular process occurs. That value is used as a pointer to the page table directory that contains PDEs. The page directory table of process opera.exe is stored at physical address 0x094c01a0, while the page directory table of process IEXPLORE.EXE is stored at physical address 0x094c0200. Because the 0x00000000 to 0x7FFFFFFF linear address space is used by the program and not the kernel, the program will use the first half of the PDE entries in its page directory, while the kernel will use the second half. Since, if PAE is disabled, each program has 1024 PDE entries, 512 of them refer to user space memory, and the other 512 refer to kernel space memory.

The !process commands above have been used to get the pointer to the process’s EPROCESS structure in memory. With the .process command, we can set the context to the current process. This is mandatory step if we would like to run some other commands that require us to be in the process’s context in order to be run successfully. To switch to the context of the iexplore.exe process, we have to execute the “.process 81f799f8” command as can be seen below:

There was some warning message about forcedecodeuser not being enabled. The problem with this is that the context switch won’t occur to the requested process. To solve that we need to enable the forcedecodeuser with the “.cache forcedecodeuser” command. The output from that command can be seen on the picture below:

Notice that we’ve first enabled the forcedecodeuser, and then run the .process command to switch to the context of the iexplore.exe’s process? This time there was no error message and the context-switch actually occurred.

Notice that all the loaded DLLs that the program uses have been loaded in the user space (keep in mind that we didn’t present all the DLLs because the iexplore.exe uses too many of them to be presented in a sane manner). All the loaded DLLs also have their path displayed, which makes it very easy to find them on the hard drive. Let’s also present the second part of the output, which has long lines, so they can’t be presented here. Luckily the windbg has a “word wrap” functionality that can be enabled by right-clicking on the window and selecting “Word wrap” as can be seen below:

Notice that the “Word wrap” is enabled, which means that the lines will only be as long as the window’s width.If lines are longer they will be wrapped and put into the second line. Let’s now present the second half of the output from running the !peb command:

There are quite many information available on the picture above, like the address of the process heap, the process parameters, the command line used to invoke the program, the path where to search for DLLs, etc. Also all the environment variables of this process are shown, but only the first three are presented for clarity.

Let’s now download the Sysinternals Suite and run the vmmap tool, which will ask us to select a process when launching. We can see that on the picture below, we must select the IEXPLORE.EXE to view it’s memory:

The vmmon will then analyze the memory and present us with the statistics about which memory is used for heap, stack, data, etc. This can be seen on the picture below:

Windows Architecture

Let’s present a picture about Windows Architecture taken from [2]:

On the picture above we can see the HAL (Hardware Abstraction Layer), which is the first abstraction layer that abstracts the hardware details from the operating system. The operating system can then call the same API functions and the HAL takes care of how they are actually executed on the underlying hardware. Every driver that is loaded in the kernel mode uses HAL to interact with the hardware components, so even the drivers don’t interact with hardware directly. The kernel drivers can interact with the hardware directly, but usually they don’t need to, since they can use HAL API to execute some action. The hardware abstraction layer’s API is provided with the hal.dll library file that is located in the C:\WINDOWS\system32\ directory as can be seen below:

The rest of the system kernel components are provided by the following libraries and executables [3]:

  • csrss.exe : manages user processes and threads
  • win32k.sys : user and graphics device driver (GDI)
  • kernel32.dll : access to resources like file system, devices, processes, threads and error handling in Windows systems
  • advapi32.dll : access to windows registry, shutdown/restart the system, start/stop/create services, manage user accounts
  • user32.dll : create and manage screen windows, buttons, scrollbars, receive mouse and keyboard input
  • gdi32.dll : outputs graphical content to monitors, printers and other output devices
  • comdlg32.dll : dialog boxes for opening and saving files, choosing color and font
  • comctl32.dll : access to status bars, progress bars, tools, tabs
  • shell32.dll : access the operating system shell
  • netapi32.dll : access to networking functions

The programs can use Windows API (Win 32 API) to implement the needed actions. But the WinAPI uses the described DLL libraries underneath.

Right above the kernel mode is the user mode, where the most important library is ntdll.dll.Thatcan be used as an entry point into the kernel if some process needs services of the kernel.


We’ve seen how the user and kernel mode are separated and what each of those provide to the user. The kernel mode is used to provide services to the user mode applications. The kernel mode is capable of doing almost anything with the underlying system, but the most important thing is the existence of Win32 API that provides another abstraction layer over the underlying hardware components.

If you’re interested in knowing all of the system libraries that a kernel uses, you can run the “lm n” command in Windbg, which will list all of the libraries used by the kernel mode.

kd> lm n
start end        module name
7c900000 7c9b2000   ntdll    ntdll.dll
804d7000 806d0280   nt       ntkrnlpa.exe
806d1000 806e4d00   hal      halacpi.dll
bf000000 bf011600   dxg      dxg.sys
bf012000 bf028000   VBoxDisp VBoxDisp.dll
bf800000 bf9c7e00   win32k   win32k.sys
f57cf000 f580fa80   HTTP     HTTP.sys
f5978000 f59cf600   srv      srv.sys
f5a20000 f5a4c180   mrxdav   mrxdav.sys
f5b0d000 f5b17000   secdrv   secdrv.sys
f5e79000 f5e7c900   ndisuio  ndisuio.sys
f6ee9000 f6f00900   dump_atapi dump_atapi.sys
f6fa1000 f6fc6500   ipnat    ipnat.sys
f6fef000 f705e780   mrxsmb   mrxsmb.sys
f705f000 f7089e80   rdbss    rdbss.sys
f709c000 f70d9000   VBoxSF   VBoxSF.sys
f70d9000 f70fad00   afd      afd.sys
f70fb000 f7122c00   netbt   netbt.sys
f7123000 f717b480   tcpip    tcpip.sys
f717c000 f718e600   ipsec    ipsec.sys
f71cb000 f71cd900   Dxapi    Dxapi.sys
f71d7000 f7234f00   update   update.sys
f7235000 f7257700   ks       ks.sys
f7264000 f7266f80   mouhid   mouhid.sys
f7268000 f726a880   hidusb   hidusb.sys
f7280000 f72afe80   rdpdr    rdpdr.sys
f72b0000 f72c0e00   psched   psched.sys
f72c1000 f72d7580   ndiswan  ndiswan.sys
f72d8000 f72fb200   USBPORT  USBPORT.SYS
f72fc000 f730ff00   VIDEOPRT VIDEOPRT.SYS
f7310000 f7334000   VBoxVideo VBoxVideo.sys
f7334000 f7347900   parport  parport.sys
f7348000 f7362000   VBoxMouse VBoxMouse.sys
f838d000 f838f280   rasacd   rasacd.sys
f83c1000 f83dab80   Mup      Mup.sys
f83db000 f8407980   NDIS     NDIS.sys
f8408000 f8494600   Ntfs     Ntfs.sys
f8495000 f84b4000   VBoxGuest VBoxGuest.sys
f84b4000 f84cab00   KSecDD   KSecDD.sys
f84cb000 f84dcf00   sr       sr.sys
f84dd000 f84fcb00   fltMgr   fltMgr.sys
f84fd000 f8514900   atapi    atapi.sys
f8515000 f853a700   dmio     dmio.sys
f853b000 f8559880   ftdisk   ftdisk.sys
f855a000 f856aa80   pci      pci.sys
f856b000 f8598d80   ACPI     ACPI.sys
f869a000 f86a3180   isapnp   isapnp.sys
f86aa000 f86b4580   MountMgr MountMgr.sys
f86ba000 f86c6c80   VolSnap  VolSnap.sys
f86ca000 f86d2e00   disk     disk.sys
f86da000 f86e6180   CLASSPNP CLASSPNP.SYS
f872a000 f8736d00   i8042prt i8042prt.sys
f873a000 f8749600   cdrom    cdrom.sys
f874a000 f8752a00   pcntpci5 pcntpci5.sys
f875a000 f8766880   rasl2tp  rasl2tp.sys
f876a000 f8774200   raspppoe raspppoe.sys
f877a000 f8785d00   raspptp  raspptp.sys
f878a000 f8792900   msgpc    msgpc.sys
f87da000 f87e3f00   termdd   termdd.sys
f87ea000 f87f3e80   NDProxy  NDProxy.SYS
f87fa000 f8808880   usbhub   usbhub.sys
f881a000 f8822780   netbios  netbios.sys
f885a000 f8864e00   Fips     Fips.SYS
f888a000 f8899900   Cdfs     Cdfs.SYS
f889a000 f88a2700   wanarp   wanarp.sys
f88aa000 f88b3000   HIDCLASS HIDCLASS.SYS
f891a000 f8920180   PCIIDEX  PCIIDEX.SYS
f8922000 f8926d00   PartMgr  PartMgr.sys
f895a000 f8960000   kbdclass kbdclass.sys
f8962000 f8967a00   mouclass mouclass.sys
f896a000 f896e300   usbohci  usbohci.sys
f8972000 f8979600   usbehci  usbehci.sys
f897a000 f897ea80   TDI      TDI.SYS
f8982000 f8986580   ptilink  ptilink.sys
f898a000 f898e080   raspti   raspti.sys
f89b2000 f89b7200   vga      vga.sys
f89ba000 f89bea80   Msfs     Msfs.SYS
f89c2000 f89c9880   Npfs     Npfs.SYS
f89ca000 f89d0180   HIDPARSE HIDPARSE.SYS
f89e2000 f89e6500   watchdog watchdog.sys
f8aaa000 f8aad000   BOOTVID  BOOTVID.dll
f8aae000 f8ab0800   compbatt compbatt.sys
f8ab2000 f8ab5780   BATTC    BATTC.SYS
f8b3e000 f8b41680   CmBatt   CmBatt.sys
f8b42000 f8b44780   ndistapi ndistapi.sys
f8b7a000 f8b7dc80   mssmbios mssmbios.sys
f8b9a000 f8b9bb80   kdcom    kdcom.dll
f8b9c000 f8b9d100   WMILIB   WMILIB.SYS
f8b9e000 f8b9f580   intelide intelide.sys
f8ba0000 f8ba1700   dmload   dmload.sys
f8bca000 f8bcb100   swenum   swenum.sys
f8bcc000 f8bcd280   USBD     USBD.SYS
f8bce000 f8bcff00   Fs_Rec   Fs_Rec.SYS
f8bd0000 f8bd1080   Beep     Beep.SYS
f8bd2000 f8bd3080   mnmdd    mnmdd.SYS
f8bd4000 f8bd5080   RDPCDD   RDPCDD.sys
f8be4000 f8be5100   dump_WMILIB dump_WMILIB.SYS
f8c00000 f8c01a80   ParVdm   ParVdm.SYS
f8ca7000 f8ca7d00   dxgthk   dxgthk.sys
f8d38000 f8d38c00   audstub  audstub.sys
f8de1000 f8de1b80   Null     Null.SYS

Unloaded modules:
f884a000 f8855000   imapi.sys
f883a000 f8849000   redbook.sys
f882a000 f883a000   serial.sys
f89aa000 f89af000   Cdaudio.SYS
f8391000 f8394000   Sfloppy.SYS
f89a2000 f89a7000   Flpydisk.SYS
f899a000 f89a1000   Fdc.SYS

We can see that there are a lot of loaded libraries that kernel mode uses to provide services to the user application and to keep track of everything the operating system must do.


[1] PankajGarg, Windows Memory Management, accessible at

[2] HanyBarakat’s Technical Blog, accessible at

[3] Windows API, accessible at