Je vais aujourd'hui vous parlez d'un espace un peu spécial situé dans le kernel land ; j'ai du mener de longues recherches, des nuits de reverses et j'en passe :).
Tous cela parce qu'il s'agit d'un sujet qui au final reste très très peu documenté, on trouve très peu de précision et lorsque le sujet est abordé dans certains article, les descriptions sont plutôt sommaire.

Le session space, est en fait un espace session (comme son nom l'indique) ; comme vous le savez les OS modernes sont tous capables de gérer les sessions multiples ; et bien windows dédie un espace privé par session !
Une session est en fait un espace privé reservé pour chaque utilisateur par exemple, le fait que plusieurs utilisateurs puissent se connecter en même temps implique alors le cloisonnement de celles-ci !

En fait, tous commence lorsque je me mis à rédiger un Proof of concept concernant la technique de xrayn. Celle-ci permettait de cacher ses hooks dans la SSDT/SSDT Shadow ; 90% environ des anti-rootkits étaient mis à l'echec !
A première vue, le code ne devrait pas trop me poser de problèmes..jusqu'au moment où je dois réaliser une copie de la SSDT Shadow.
En bref la SSDT Shadow est la SSDT pour les threads qui vont gérer une interface graphique ; la SSDT shadow se décompose en deux parties : la première qui est la SSDT que l'on connait bien (dite SSDT system), et la seconde est la SSDT gui.
Un petit dump du kd pour vous montrez ça :

kd> dd nt!KeServiceDescriptorTable l 0x8 80559b80 804e2d20 00000000 0000011c 804d8f48 80559b90 00000000 00000000 00000000 00000000 kd> dd nt!KeServiceDescriptorTableShadow l 0xC 80559b40 804e2d20 00000000 0000011c 804d8f48 80559b50 bf999400 00000000 0000029b bf99a110 80559b60 00000000 00000000 00000000 00000000
Les threads gui peuvent alors gérer les syscalls système, et les syscalls gui sans aller chercher les fonctions dans une autre table.
Sans rentrer dans les détails de la technique (un article sortira bientôt concernant cette technique), lorsque que mon driver tentait d'accéder au tableau de pointeurs sur les fonctions syscalls de win32k et ben BIM BOOOM %*&1 écran bleu !

Je sors alors mon kd :

kd> dd nt!KeServiceDescriptorTableShadow l 0x8
80559b40 804e2d20 00000000 0000011c 804d8f48
80559b50 bf999400 00000000 0000029b bf99a110

kd> dds poi(nt!KeServiceDescriptorTableShadow+0x4*4)
bf999400 ????????
bf999404 ????????
bf999408 ????????
bf99940c ????????
bf999410 ????????
bf999414 ????????
bf999418 ????????
bf99941c ????????
bf999420 ????????
bf999424 ????????

Hmm l'espace mémoire parait comme innexistant, et c'est là que le problème s'esquisse.
Tout ça parait plutôt floue, j'ai donc potasser un peu les Windows internals, c'est donc à ce moment que je découvre le session space !
On peux y lire :

Each session has a session-specific paged pool area used by the kernel-mode portion of the Windows subsystem (Win32k.sys) to allocate session-private GUI data structures.
In addition, each session has its own copy of the Windows subsystem process (Csrss.exe) and logon process (Winlogon.exe).
The session manager process (Smss.exe) is responsible for creating new sessions, which includes loading a session-private copy of Win32k.sys,
creating the session-private object manager namespace, and creating the session-specific instances of the Csrss and Winlogon processes.

When a process is created, this range of addresses is mapped to the pages appropriate to the session that the process belongs to.


Nous voilà avec quelques pistes, on se rend compte alors que la Shadow n'est accessible seulement aux processus qui appartiennent à la dîtes session ; autrement dit tous les threads de ces processus auront accès à cet table.

Cet à ce moment qu'il faut réfléchir un peu si l'on veut dégoter quelques informations intéressante ; on pourrait déjà regarder du coté du gestionnaire de sessions : smss.exe, ensuite du driver win32k.sys ..j'ai donc essayé de les analyser à la recherche d'infos plus ou moins pertinente.
Smss.exe se charge à son initialisation de charger le driver win32k.sys :

Main!smss.exe
-> smss!SmpInit
-> smss!SmpLoadDataFromRegistry
-> smss!SmpLoadSubSystemsForMuSession

.text:485887B2 push offset aSystemrootSyst ; "\\SystemRoot\\System32\\win32k.sys"
.text:485887B7 lea eax, [ebp+DestinationString]
.text:485887BA push eax ; DestinationString
.text:485887BB call ds:__imp__RtlInitUnicodeString@8 ; RtlInitUnicodeString(x,x)
.text:485887C1 push 8
.text:485887C3 lea eax, [ebp+DestinationString]
.text:485887C6 push eax
.text:485887C7 push 26h
.text:485887C9 call edi ; NtSetSystemInformation(x,x,x) ; NtSetSystemInformation(x,x,x)
Smss.exe utilise une technique peu rencontré pour charger le driver, certains parle de "quick an dirty-way" (cf Suberving the windows kernel). Il suffit d'appeler NtSetSystemInformation avec l'argument SystemLoadAndCallImage ainsi que le full path du driver.
On parle de dirty way, tout simplement parce que le driver sera chargé dans une zone mémoire swappable, pour plus d'information chapitre 2 du bouqin :).

Une fois le driver lancé, celui-ci va ajouter la SSDT shadow :

INIT:BF9AFF13 push offset _W32pArgumentTable
INIT:BF9AFF18 push _W32pServiceLimit
INIT:BF9AFF1E mov _countTable, esi
INIT:BF9AFF24 push esi
INIT:BF9AFF25 push offset _W32pServiceTable
INIT:BF9AFF2A call ds:__imp__KeAddSystemServiceTable@20 ; KeAddSystemServiceTable(x,x,x,x,x)

.data:BF999B00 _W32pServiceTable dd offset _NtGdiAbortDoc@4
.data:BF999B04 dd offset _NtGdiAbortPath@4 ; NtGdiAbortPath(x)
.data:BF999B08 dd offset _NtGdiAddFontResourceW@24 ; NtGdiAddFontResourceW(x,x,x,x,x,x)
.data:BF999B0C dd offset _NtGdiAddRemoteFontToDC@16 ; NtGdiAddRemoteFontToDC(x,x,x,x)
.data:BF999B10 dd offset _NtGdiAddFontMemResourceEx@20 ; NtGdiAddFontMemResourceEx(x,x,x,x,x)
.data:BF999B14 dd offset _NtGdiRemoveMergeFont@8 ; NtGdiRemoveMergeFont(x,x)
.data:BF999B18 dd offset _NtGdiAddRemoteMMInstanceToDC@12 ; NtGdiAddRemoteMMInstanceToDC(x,x,x)
.data:BF999B1C dd offset _NtGdiAlphaBlend@48 ; NtGdiAlphaBlend(x,x,x,x,x,x,x,x,x,x,x,x)
[...]

.data:BF99A810 _W32pArgumentTable db 4 ; DATA XREF: DriverEntry(x,x)+E4o
.data:BF99A811 db 4
.data:BF99A812 db 18h
.data:BF99A813 db 10h
.data:BF99A814 db 14h
.data:BF99A815 db 8
.data:BF99A816 db 0Ch
.data:BF99A817 db 30h
[...]
Avec ce session space il y a donc une histoire d'espace qui est accessible/existe, dans certains contexte ; en fait tous les processus d'une session voient cette table, seul system ne peux y accéder ; par analogie c'est comme un processus avec ses dlls, system serait un processus sans la dll Session space (c'est bien sûr une analogie, soyons d'accord :)) .

On peut verifier tout ça avec le kd ; en premier lieu se placer dans le contexte du processus System (le cas de notre driver donc), et ce placer dans le contexte d'un tout autre processus :

kd> !process 0 0
**** NT ACTIVE PROCESS DUMP ****
PROCESS 817cc7c0 SessionId: none Cid: 0004 Peb: 00000000 ParentCid: 0000
DirBase: 00039000 ObjectTable: e1001cb0 HandleCount: 206.
Image: System
[..]
kd> .process /i 817cc7c0
You need to continue execution (press 'g' ) for the context
to be switched. When the debugger breaks in again, you will be in
the new process context.
kd> g
Break instruction exception - code 80000003 (first chance)
nt!RtlpBreakWithStatusInstruction:
804e3b25 cc int 3
kd> dds poi(nt!KeServiceDescriptorTableShadow+0x4*4)
bf999400 ????????
bf999404 ????????
bf999408 ????????


kd> !process 0 0
**** NT ACTIVE PROCESS DUMP ****
[..]
PROCESS
81549b28 SessionId: 0 Cid: 05a8 Peb: 7ffdf000 ParentCid: 0588
DirBase: 093c9000 ObjectTable: e1a74620 HandleCount: 346.
Image:
explorer.exe
[..]
kd> .process /i
81549b28
You need to continue execution (press 'g' ) for the context
to be switched. When the debugger breaks in again, you will be in
the new process context.
kd> g
Break instruction exception - code 80000003 (first chance)
nt!RtlpBreakWithStatusInstruction:
804e3b25 cc int 3
kd> dds poi(nt!KeServiceDescriptorTableShadow+0x4*4)
bf999400 bf9357a3 win32k!NtGdiAbortDoc
bf999404 bf947361 win32k!NtGdiAbortPath
bf999408 bf896625 win32k!NtGdiAddFontResourceW
bf99940c bf93ef25 win32k!NtGdiAddRemoteFontToDC
bf999410 bf948978 win32k!NtGdiAddFontMemResourceEx
[..]
J'ai donc réaliser une fonction qui permettait ça copie, celle-ci fonctionne plutôt simplement, je m'attache au processus explorer.exe (Il faut s'attacher à un processus qui sera lancé sur le systeme ; j'ai pris explorer.exe comme j'aurais pu prendre calc.exe!), et réalise la copie normalement (vous verrez alors que mon ancienne technique qui utilisait un apc kernel envoyé sur un thread gui était bien trop compliqué pour pas grand chose :).

Sinon, on pourrait s'occuper de mapper win32k.sys dans l'espace du driver en utilisant son adresse physique avec des apis comme MmMapIoSpace(), il suffirait ensuite de parser le pe du binaire, pour atterrir dans la section .data ; où est stocké la SSDT Shadow.

.data:BF999C00 ; Segment type: Pure data
.data:BF999C00 ; Segment permissions: Read/Write
.data:BF999C00 _data segment para public 'DATA' use32
.data:BF999C00 assume cs:_data
.data:BF999C00 ;org 0BF999C00h
.data:BF999C00 _W32pServiceTable dd offset _NtGdiAbortDoc@4
.data:BF999C00 ; DATA XREF: DriverEntry(x,x)+F6o
.data:BF999C00 ; NtGdiAbortDoc(x)
.data:BF999C04 dd offset _NtGdiAbortPath@4 ; NtGdiAbortPath(x)
.data:BF999C08 dd offset _NtGdiAddFontResourceW@24 ; NtGdiAddFontResourceW(x,x,x,x,x,x)
.data:BF999C0C dd offset _NtGdiAddRemoteFontToDC@16 ; NtGdiAddRemoteFontToDC(x,x,x,x)
.data:BF999C10 dd offset _NtGdiAddFontMemResourceEx@20 ; NtGdiAddFontMemResourceEx(x,x,x,x,x)
.data:BF999C14 dd offset _NtGdiRemoveMergeFont@8 ; NtGdiRemoveMergeFont(x,x)
.data:BF999C18 dd offset _NtGdiAddRemoteMMInstanceToDC@12 ; NtGdiAddRemoteMMInstanceToDC(x,x,x)
.data:BF999C1C dd offset _NtGdiAlphaBlend@48 ; NtGdiAlphaBlend(x,x,x,x,x,x,x,x,x,x,x,x)
.data:BF999C20 dd offset _NtGdiAngleArc@24 ; NtGdiAngleArc(x,x,x,x,x,x)
.data:BF999C24 dd offset _NtGdiAnyLinkedFonts@0 ; NtGdiAnyLinkedFonts()
.data:BF999C28 dd offset _NtGdiFontIsLinked@4 ; NtGdiFontIsLinked(x)
.data:BF999C2C dd offset _NtGdiArcInternal@40 ; NtGdiArcInternal(x,x,x,x,x,x,x,x,x,x)
.data:BF999C30 dd offset _NtGdiBeginPath@4 ; NtGdiBeginPath(x)

Un peu tricky, mais ça se tiens, seul problème c'est la translation de l'adresse Virtuelle à l'adresse Physique, étant donné que le driver ne voit pas l'espace en question, l'adresse physique ne peut être récupérer avec MmGetPhysicalAddress() (l'idéal serait de ne pas s'attacher à un processus).


Bilan de ce post, un peu décevant, mes recherches ont été longues pour au final pas grand chose..m'enfin bon :].
Sinan, je vous recommande le blog de bons amis, Futurezone ; le blog est mis à jour regulierement avec des articles très clean et tout, c'est que du bonheur quoi.


Voilà pour aujourd'hui :
CopySSDTShadow.c

Je compte aussi sortir une version un peu plus propre de HC-L, en effet j'ai décidé d'embarquer le NDK, de séparer les en-têtes de fonctions des définitions, ajouter des fonctions etc..!