Reverse engineering

Windows Architecture and User/Kernel Mode

Dejan Lukan
March 27, 2013 by
Dejan Lukan

Introduction

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.

Become a certified reverse engineer!

Become a certified reverse engineer!

Get live, hands-on malware analysis training from anywhere, and become a Certified Reverse Engineering Analyst.

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):

[plain]

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.

Image: IEXPLORE.EXE

[/plain]

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:WINDOWSsystem32 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.

Conclusion

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.

[plain]

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

[/plain]

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.

References:

[1] PankajGarg, Windows Memory Management, accessible at http://www.intellectualheaven.com/Articles/WinMM.pdf.

[2] HanyBarakat's Technical Blog, accessible at http://blogs.msdn.com/b/hanybarakat/archive/2007/02/25/deeper-into-windows-architecture.aspx.

[3] Windows API, accessible at http://en.wikipedia.org/wiki/Windows_API.

/strong

Dejan Lukan
Dejan Lukan

Dejan Lukan is a security researcher for InfoSec Institute and penetration tester from Slovenia. He is very interested in finding new bugs in real world software products with source code analysis, fuzzing and reverse engineering. He also has a great passion for developing his own simple scripts for security related problems and learning about new hacking techniques. He knows a great deal about programming languages, as he can write in couple of dozen of them. His passion is also Antivirus bypassing techniques, malware research and operating systems, mainly Linux, Windows and BSD. He also has his own blog available here: http://www.proteansec.com/.