Multiple CAs in the Kernel

Hi,

I’m researching how things work in the kernel and I’ve followed the “How to get base address of ntdll.dll under kernel Windows 8”: https://osronline.com/showthread.cfm?link=268187 thread, which presents the following function to determine the base address of ntdll.dll inside the kernel. The code is slightly modified as shown below - and yes, I know I don’t have to map the DLL, but I was using this when testing (however the bug is no longer reproducible).

NTSTATUS LookforNtdll(void** ppv)
{
NTSTATUS status;
PVOID pAddr = NULL;
SIZE_T sz = 0;
STATIC_OBJECT_ATTRIBUTES(oa, “\KnownDlls\ntdll.dll”);
HANDLE hSection;
if (0 <= (status = ZwOpenSection(&hSection, SECTION_QUERY, &oa)))
{
if (0 <= (status = ZwMapViewOfSection(hSection, ZwCurrentProcess(), &pAddr, 0, 0, NULL, &sz, (SECTION_INHERIT)ViewUnmap, MEM_TOP_DOWN, PAGE_READONLY)))
{
SECTION_IMAGE_INFORMATION sii;
if (0 <= (status = ZwQuerySection(hSection, SectionImageInformation, &sii, sizeof(sii), 0)))
{
*ppv = sii.EntryPoint;
}
ZwUnmapViewOfSection(ZwCurrentProcess(), pAddr);
}
ZwClose(hSection);
}

return status;
}

However while testing whether this actually works on Windows 10, I’ve stumbled upon a problem where it mostly works, but not always. The problem is that when using https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/-ca and passing 0 as the first parameter (meaning to display all CAs) and 0x40 as the second parameter (meaning to display image control areas) I actually get 2 CAs, but I’m not sure why - this directly affects ZwQuerySection, which sometimes obtains the right address and other times it doesn’t. The characteristics of the CAs are the following:

  1. “Mapped Views”
  • Valid CA: has a number of mapped views, which directly correspond to it being loaded into all user-mode processes.
  • Invalid CA: doesn’t have any mapped views, so it’s not mapped anywhere.
  1. “User Ref”
  • Valid CA: has number of processes running in the system + 1, which means #MappedViews+1.
  • Invalid CA: only has 1.

I have a couple of questions:

  1. Is it possible to obtain a pointer to SECTION object to determine more information about the CA. When a system boots, it goes over every KnownDll and creates a SECTION/SEGMENT objects for each of the DLLs.
  2. Is it possible to obtain information about who created the CA or where does the CA come from (why was it created)?
  3. What is “User Ref” inside the CA and how can I display more information about the CA “User Ref” references inside windbg?
  4. How is it possible for the \KnownDll\ntdll.dll to have more CAs and consequently also more SECTION objects inside the kernel (but the only a single \KnownDll\ntdll.dll exists, if somebody tries to create another one, an status section already exists will be return. Can it do something with the system updates - this bug is highly irreproducible, so I’m having issues testing this.

Basically I’m interested in which scenario a second SECTION for ntdll.dll KnownDll becomes available in the kernel, which is then returned when calling ZwOpenSection/ZwQuerySection for ntdll.dll.

First, the KnownDlls directory is created by SMSS.EXE, the kernel uses a table of section objects named PspSystemDlls:

kd> dps nt!PspSystemDlls L4
fffff8004702c1b0 fffff8004702c7e0 nt!PspNativeSystemDllData
fffff8004702c1b8 fffff8004702c880 nt!PspWowX86SystemDllData
fffff8004702c1c0 fffff8004702c830 nt!PspWowArm32SystemDllData
fffff8004702c1c8 fffff8004702c8d0 nt!PspWowAmd64SystemDllData

As yo can see there is an NTDLL.DLL for x86, x64, ARM32 and ARM64.

But NTDLL.DLL is mapped by the kernel well before SMSS.EXE starts.

I actually get 2 CAs, but I’m not sure why…

There are much more CAs. You have drivers binaries as well.

  1. Is it possible to obtain a pointer to SECTION object to determine more information about the CA.

The section object type is exported by the kernel (MmSectionObjectType) so you could try to use ObReferenceObjectByHandle when you have a handle to a section object but the handle should be enough.

  1. Is it possible to obtain information about who created the CA or where does the CA come from (why was it created)?

Section objects have a security descriptor.

  1. What is “User Ref” inside the CA …

Probably a counter that is incremented when a view of the section is mapped and decremented when a view is unmapped.

  1. How is it possible for the \KnownDll\ntdll.dll to have more CAs and consequently also more SECTION objects inside the kernel (but the only a single \KnownDll\ntdll.dll exists, if somebody tries to create another one, an status section already exists will be return.

I think both have the same CA (0xffffb80d`b0704870 below).

kd> !object \KnownDlls\ntdll.dll
Object: ffff920e2290c800 Type: (ffffb80daf32c910) Section
ObjectHeader: ffff920e2290c7d0 (new version)
HandleCount: 0 PointerCount: 1
Directory Object: ffff920e21da4330 Name: ntdll.dll
kd> !object poi(poi(nt!PspSystemDlls)) & 0xFFFFFFFFFFFFFFF0
Object: ffff920e21850f10 Type: (ffffb80daf32c910) Section
ObjectHeader: ffff920e21850ee0 (new version)
HandleCount: 0 PointerCount: 16
Directory Object: 00000000 Name: \Windows\System32\ntdll.dll
kd> dt /r1 nt!_SECTION ffff920e2290c800
+0x000 SectionNode : _RTL_BALANCED_NODE
+0x000 Children : [2] (null)
+0x000 Left : (null)
+0x008 Right : (null)
+0x010 Red : 0y0
+0x010 Balance : 0y00
+0x010 ParentValue : 0
+0x018 StartingVpn : 0
+0x020 EndingVpn : 0
+0x028 u1 :
+0x000 ControlArea : 0xffffb80db0704870 _CONTROL_AREA<br> +0x000 FileObject : 0xffffb80db0704870 _FILE_OBJECT
+0x000 RemoteImageFileObject : 0y0
+0x000 RemoteDataFileObject : 0y0
+0x030 SizeOfSection : 0x1db000
+0x038 u :
+0x000 LongFlags : 0x100a0
+0x000 Flags : _MMSECTION_FLAGS
+0x03c InitialPageProtection : 0y000000010000 (0x10)
+0x03c SessionId : 0y0000000000000000000 (0)
+0x03c NoValidationNeeded : 0y0
kd> dt /r1 nt!_SECTION ffff920e21850f10
+0x000 SectionNode : _RTL_BALANCED_NODE
+0x000 Children : [2] (null)
+0x000 Left : (null)
+0x008 Right : (null)
+0x010 Red : 0y0
+0x010 Balance : 0y00
+0x010 ParentValue : 0
+0x018 StartingVpn : 0
+0x020 EndingVpn : 0
+0x028 u1 :
+0x000 ControlArea : 0xffffb80db0704870 _CONTROL_AREA<br> +0x000 FileObject : 0xffffb80db0704870 _FILE_OBJECT
+0x000 RemoteImageFileObject : 0y0
+0x000 RemoteDataFileObject : 0y0
+0x030 SizeOfSection : 0x1db000
+0x038 u :
+0x000 LongFlags : 0x100a0
+0x000 Flags : _MMSECTION_FLAGS
+0x03c InitialPageProtection : 0y000000010000 (0x10)
+0x03c SessionId : 0y0000000000000000000 (0)
+0x03c NoValidationNeeded : 0y0

Hi, this is

There are much more CAs. You have drivers binaries as well.

Yeah, I was referring to the fact that there are 2 CAs for the x64 ntdll.dll (disregard the other architectures here).

The section object type is exported by the kernel (MmSectionObjectType) so you could try to use ObReferenceObjectByHandle when you have a handle to a section object but the handle should be enough.

To use this I would also need to define the _SECTION object data structure, since the function will return a pointer to the _SECTION objects’s body? Is that documented anywhere and how often does the data structure change across different versions of Windows?

  1. Is it possible to obtain information about who created the CA or where does
    the CA come from (why was it created)?
    Section objects have a security descriptor.

Where is the security descriptor, it’s not in the _SECTION object?

  1. What is “User Ref” inside the CA …
    Probably a counter that is incremented when a view of the section is mapped and
    decremented when a view is unmapped.

I think you’ve described the “Mapped Views” counter, not the “User Ref”, which is why we still need to figure out what the user reference is.

I think both have the same CA (0xffffb80d`b0704870 below).

This is not true in my case, which is the root of the problem. You can see below that the first CA is different than the second CA, while they’re both a reference to the ntdll.dll KnownDll.

kd> !object \KnownDlls\ntdll.dll
Object: ffff910e28450f20 Type: (ffffe001e442da50) Section
ObjectHeader: ffffc001ba932ed0 (new version)
HandleCount: 0 PointerCount: 1
Directory Object: ffffc000bf461a50 Name: ntdll.dll

kd> dt /r1 nt!_SECTION ffff910e28450f20
+0x028 u1 :
+0x000 ControlArea : 0xffffd003edda9ed0 _CONTROL_AREA<br> +0x000 FileObject : 0xffffd003edda9ed0 _FILE_OBJECT
+0x03c SessionId : 0y1111111111111111110 (0x7fffe)

kd> !object poi(poi(nt!PspSystemDlls)) & 0xFFFFFFFFFFFFFFF0
Object: ffffc000ba41bbe0 Type: (ffffe001e442da50) Section
ObjectHeader: ffffc001bd856f50 (new version)
HandleCount: 0 PointerCount: 16
Directory Object: 00000000 Name: \Windows\System32\ntdll.dll

kd> dt /r1 nt!_SECTION ffffc000ba41bbe0
+0x028 u1 :
+0x000 ControlArea : 0xffffd003e5dc88d0 _CONTROL_AREA<br> +0x000 FileObject : 0xffffd003e5dc88d0 _FILE_OBJECT
+0x03c SessionId : 0y0000000000000000000 (0)

A couple of additional questions:

5. The interesting field is SessionId, which is 0 (for valid CA) and 0x7fffe (for invalid CA). When is the SessionId set and used?
6. In which cases can the CAs for ntdll.dll become different, I’m looking for possible explanations whatever the cause (even in case malware is running in kernel-mode). It’s obvious that the \KnownDlls\ntdll.dll (invalid) differs from the “poi(poi(nt!PspSystemDlls)) & 0xFFFFFFFFFFFFFFF0” (valid), but how can they become out of sync?
7. Why does the ZwOpenSection find the “invalid” ffff910e28450f20 section object, from which the ZwQuerySection pulls the information.

Drivers can register a LoadImage notification routine with PsSetLoadImageNotifyRoutine.

With such a callback, a driver is notified when a thread maps an executable binary. You have access to the identification informations.

A Win32 application can map multiple views of NTDLL.DLL with CreateFile/CreateFileMapping/MapViewOfFile. You can make a test, it is not complicated.

Typically if a user app calls LoadLibrary(TEXT(“NTDLL.DLL”)), the user loader will increment a usermode reference count on the already loaded NTDLL.DLL and return the base address of NTDLL.DLL. But if you call CreateFile/CreateFileMapping/MapViewOfFile, you get a new section object with a new mapping (a different base address).

Hi, thank you for the provided answer, but I have a few more questions.

  1. That doesn’t explain why the \KnownDlls\ntdll.dll is updated to point to the newly mapped view. Shouldn’t the \KnownDlls\ntdll.dll always refer to the section object created when the system was booted? I’ve tried calling ZwOpenSection/ZwMapViewOfSection first to map another view, then ZwOpenSection/ZwQuerySection to query the \KnownDlls\ntdll.dll, however the returned result always points to the view created by the system during boot time and not to the new mapped view.

  2. Is it possible to determine who holds the user reference, whether it is a kernel-mode/user-mode code corresponding with process/driver?

  3. If the ntdll.dll has been mapped into the kernel multiple times (by driver, by malware, …), and the \KnownDlls\ntdll.dll has been updated to point to the newly mapped view, how does kernel determine the ntdll.dll’s user-mode base address to which the DLL should be mapped inside the newly created process?

What does !ca say about the control areas? In Windows we use different
control areas for data mapping versus executable mapping, so not surprising
that there would be two.

-scott
OSR
@OSRDrivers

“%%merge inmail_.HdrFromSpc_%%” wrote in message news:xxxxx@ntdev…

Hi, thank you for the provided answer, but I have a few more questions.

  1. That doesn’t explain why the \KnownDlls\ntdll.dll is updated to point to
    the newly mapped view. Shouldn’t the \KnownDlls\ntdll.dll always refer to
    the section object created when the system was booted? I’ve tried calling
    ZwOpenSection/ZwMapViewOfSection first to map another view, then
    ZwOpenSection/ZwQuerySection to query the \KnownDlls\ntdll.dll, however the
    returned result always points to the view created by the system during boot
    time and not to the new mapped view.

  2. Is it possible to determine who holds the user reference, whether it is a
    kernel-mode/user-mode code corresponding with process/driver?

  3. If the ntdll.dll has been mapped into the kernel multiple times (by
    driver, by malware, …), and the \KnownDlls\ntdll.dll has been updated to
    point to the newly mapped view, how does kernel determine the ntdll.dll’s
    user-mode base address to which the DLL should be mapped inside the newly
    created process?

I realize there are two CA’s for data/executable mapping, but then there are 4 CAs. Basically, the kernel has two instances of ntdll.dll loaded into the kernel as well as section objects created for it. I’m interested what would cause the second section objects (and consequently CAs) to be created for the ntdll.dll if one is already loaded in the system.

What happens when updating ntdll.dll is part of a system update? Is the system capable of updating to the new version without restarting a computer, if yes, how does it happen exactly?

Use the !vad (vad for Virtual Address Descriptor) to list the loaded binaries of a process. On x64, the kernel loads the x86 and x64 versions of NTDLL.DLL in the address space of the system process:

kd> !vad
VAD Level Start End Commit
ffffcb0429e00190 2 77c80 77e0d 9 Mapped Exe EXECUTE_WRITECOPY \Windows\SysWOW64\ntdll.dll
ffffcb042924f3c0 1 7ffe0 7ffe0 1 Private READONLY
ffffcb042927f0c0 0 7ffe1 7ffef -1 Private READONLY
ffffcb042b24c410 3 1c600000 1c600000 0 Mapped READWRITE Pagefile section, shared commit 0
ffffcb042afff460 2 1c600010 1c600010 0 Mapped READWRITE Pagefile section, shared commit 0
ffffcb042b2b7270 1 1c600020 1c600020 0 Mapped READWRITE Pagefile section, shared commit 0
ffffcb042a542850 2 7ffc159e0 7ffc15bba 12 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\ntdll.dll

Total VADs: 7, average level: 2, maximum depth: 3
Total private commit: 0x16 pages (88 KB)
Total shared commit: 0 pages (0 KB)

Thanks for the explanation, it seems interesting that the SYSTEM process maps both, 32-bit as well as 64-bit DLLs. Is there a reason why 32-bit ntdll.dll is also mapped into the 64-bit SYSTEM process? Is it possible for a malicious driver to unmap either a 32-bit/64-bit ntdll.dll from SYSTEM process?

What happens if the ntdll.dll is updated by the system update. I’m particularly interested in whether the updated ntdll.dll will be remapped to the new base address and \KnownDlls\ntdll.dll updated accordingly, or is a restart required in order to actually update the ntdll.dll (as far as processes are concerned, I’m not interested in the actual binary being saved to the filesystem, since that is indeed possible).

Also, is there any legitimate reason why the ntdll.dll is remapped into the system memory (with new base address) and \KnownDlls\ntdll.dll updated to point to the new DLL. I know this is possible (by a malicious driver), but I’m trying to determine whether there is any legitimate reason a system might do this?

>Is there a reason why 32-bit ntdll.dll is also mapped into the 64-bit SYSTEM process?

The kernel needs to know the address of some functions that are exported by ntdll.dll (32 bits and 64 bits).

For the other questions, I suggest you look at the book “Windows Internals” written by Mark E. Russinovich, David A. Solomon and Alex Ionescu. Hotpatching is treated.

xxxxx@gmail.com wrote:

What happens if the ntdll.dll is updated by the system update.

You reboot.  It used to be a priority to have updates happen without
requiring a reboot.  That no longer seems to be a concern.  However,
this is a DLL that is guaranteed to be loaded into each and every
process.  It’s not really surprising that you’d need to reboot to make a
new version take effect.


Tim Roberts, xxxxx@probo.com
Providenza & Boekelheide, Inc.