Search code examples
windowsdllx86x86-64windows-kernel

x86 - Does Windows map dlls to the same physcal page in different processes?


Suppose we have process A and processB, both are using example.dll.

Now let's suppose that this dll was mapped to different addresses in process A and process B(say, it's due to ASLR or some other conflict).

Will the operating system map it twice or will it still be able to use the same physical page where the dll was mapped for both processes? I mean, that's the whole point of a DLL right? shared memory so we don't have to map things twice.


Solution

  • You already got all the right answers from the comments, I just want to demonstrate it from a kernel debugger (Windbg).

    I have notepad.exe and explorer.exe running.

    • Get notepad EPROCESS structure (this is the structure that describes a process from the kernel point of view, this is the 64-bit number after PROCESS):
    0: kd> !process 0 0 notepad.exe
    PROCESS ffffe28aca250080
        SessionId: 2  Cid: 0f58    Peb: 5903bde000  ParentCid: 1c60
        DirBase: 16543002  ObjectTable: ffffab8f834dac40  HandleCount: 240.
        Image: notepad.exe
    

    Notice the CR3 of the process which is given by DirBase, here: 0x16543002.

    • Switch into notepad.exe context (basically, schedule one of its threads on a CPU so we have access to its memory, basically mapping its page tables entries from its CR3. For this we need the EPROCESS structure):
    0: kd> .process /i ffffe28aca250080
    
    • View user mode modules:
    3: kd> lmu
    start             end                 module name
    ; snip          
    00007ff9`c6120000 00007ff9`c61cc000   ADVAPI32   (deferred)             
    00007ff9`c6330000 00007ff9`c64d0000   USER32     (deferred)             
    00007ff9`c6510000 00007ff9`c6705000   ntdll      (pdb symbols)
    

    We have the module bases, ends and their names. On Windows, ASLR is per boot (not per process). So, in all processes ntdll will be mapped at the same address until the system is rebooted and new random address is chosen.

    • Checking the virtual address content, for the base address of the ntdll module:
    3: kd> db 00007ff9`c6510000
    00007ff9`c6510000  4d 5a 90 00 03 00 00 00-04 00 00 00 ff ff 00 00  MZ..............
    00007ff9`c6510010  b8 00 00 00 00 00 00 00-40 00 00 00 00 00 00 00  ........@.......
    00007ff9`c6510020  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
    00007ff9`c6510030  00 00 00 00 00 00 00 00-00 00 00 00 e8 00 00 00  ................
    00007ff9`c6510040  0e 1f ba 0e 00 b4 09 cd-21 b8 01 4c cd 21 54 68  ........!..L.!Th
    00007ff9`c6510050  69 73 20 70 72 6f 67 72-61 6d 20 63 61 6e 6e 6f  is program canno
    00007ff9`c6510060  74 20 62 65 20 72 75 6e-20 69 6e 20 44 4f 53 20  t be run in DOS 
    00007ff9`c6510070  6d 6f 64 65 2e 0d 0d 0a-24 00 00 00 00 00 00 00  mode....$.......
    

    Typical PE module header (MZ and This program cannot ...).

    • Convert the virtual address to a physical one. The command !vtop (Virtual to Physical) requires the base of the PML4 table - for the given process - and the virtual address to be translated (thus, we need to set the lower 12 bits of CR3 to 0 since there are not used for the address of the PML4 base):
    0: kd> !vtop 16543000 00007ff9c6510000
    Amd64VtoP: Virt 00007ff9c6510000, pagedir 0000000016543000
    Amd64VtoP: PML4E 00000000165437f8
    Amd64VtoP: PDPE 000000012ce4ff38
    Amd64VtoP: PDE 000000012cd52190
    Amd64VtoP: PTE 0000000012d53880
    Amd64VtoP: Mapped phys 00000002c1b98000
    Virtual address 7ff9c6510000 translates to physical address 2c1b98000.
    
    • Confirming the physical address by reading it:
    0: kd> !db 2c1b98000
    #2c1b98000 4d 5a 90 00 03 00 00 00-04 00 00 00 ff ff 00 00 MZ..............
    #2c1b98010 b8 00 00 00 00 00 00 00-40 00 00 00 00 00 00 00 ........@.......
    #2c1b98020 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
    #2c1b98030 00 00 00 00 00 00 00 00-00 00 00 00 e8 00 00 00 ................
    #2c1b98040 0e 1f ba 0e 00 b4 09 cd-21 b8 01 4c cd 21 54 68 ........!..L.!Th
    #2c1b98050 69 73 20 70 72 6f 67 72-61 6d 20 63 61 6e 6e 6f is program canno
    #2c1b98060 74 20 62 65 20 72 75 6e-20 69 6e 20 44 4f 53 20 t be run in DOS 
    #2c1b98070 6d 6f 64 65 2e 0d 0d 0a-24 00 00 00 00 00 00 00 mode....$.......
    

    Now let's try with explorer.exe:

    2: kd> !process 0 0 explorer.exe
    PROCESS ffffe28ac9c4f080
        SessionId: 2  Cid: 1c60    Peb: 00568000  ParentCid: 1c3c
        DirBase: 1546ad002  ObjectTable: ffffab8f83851cc0  HandleCount: 2638.
        Image: explorer.exe
    
    2: kd> .process /i ffffe28ac9c4f080    
    
    1: kd> db 00007ff9`c6510000 L10
    00007ff9`c6510000  4d 5a 90 00 03 00 00 00-04 00 00 00 ff ff 00 00  MZ..............
    
    • Translating (V to P):
    1: kd> !vtop 1546ad000 00007ff9c6510000
    Amd64VtoP: Virt 00007ff9c6510000, pagedir 00000001546ad000
    Amd64VtoP: PML4E 00000001546ad7f8
    Amd64VtoP: PDPE 0000000155db9f38
    Amd64VtoP: PDE 0000000155dbf190
    Amd64VtoP: PTE 0000000155dc0880
    Amd64VtoP: Mapped phys 00000002c1b98000
    Virtual address 7ff9c6510000 translates to physical address 2c1b98000.
    

    As you can see we get the same resulting physical address. Note that the table entries (PML4E, PDPTE, PDE and PTE) are not the same in both processes, but the resulting physical page is exactly the same.

    Obviously, Windows has a CoW (Copy-on-Write) mechanisms that takes place if you'd write to one of those pages: it would be immediately copied and only the process where the write had happened would see the modified page.