Salut, j’ai pas lu l’article mais j’ai juste aperçu ton pb avec FS au retour en userland. En fait déjà je crois que la stack que tu montres est fausse : INT push sur la pile kernel ss, esp, eflags, cs et eip. Les regsitres fs, ed, ds, gs sont normalement pushés plus tard dans le prologue du service, t’es pas d’accord ?
2.
admin | février 4th, 2008 at 22:53
Hum oui bien vu mon cher Taron. On a bien sur la stack
error code
eip
cs
eflags
esp
ss
Pour s’en assurer il suffit de mettre un BP sur la fonction NewInt0EHandler et de regarder esp
J’ai mal lu les man intel, désolé. Tu as encore raison quand tu dis que c’est le prologue de la fonction qui gère l’exception qui push fs sur la stack :
Comme on peut le voir, fs est pushé sur la stack par le prologue de KiTrap0E et mit à la valeur 0×30 qui correspond bien au descripteur de segment kernel-land. La vrai question qui vaut très cher c’est pourquoi, lorsqu’il y une exception, la valeur de fs vaut déja 0×30 alors que le prologue ne s’est pas exécuté ? Par exemple
Breakpoint 0 hit
nt!KiTrap0E:
804e0877 66c74424020000 mov word ptr [esp+2],0
kd> r
eax=f9c8aaa0 ebx=00000000 ecx=00000000 edx=00000000 esi=e15c12f8 edi=00012c5a
eip=804e0877 esp=f9c8aa7c ebp=f9c8aad4 iopl=0 nv up di pl nz na po nc
cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00000002
nt!KiTrap0E:
804e0877 66c74424020000 mov word ptr [esp+2],0 ss:0010:f9c8aa7e=0000
Fs est déjà mit à la valeur 0×30 alors qu’en user-land il vaut 0x3B. Celon le « AMD64 Architecture Programmer’s Manual Volume 2 System Programming » section 8.7.3 Interrupt To Higher Privilege on a :
When a control transfer to an exception or interrupt handler running at a higher privilege occurs
(numerically lower CPL value), the processor performs a stack switch using the following steps:
1. The target CPL is read by the processor from the target code-segment DPL and used as an index
into the TSS for selecting the new stack pointer (SS:ESP). For example, if the target CPL is 1, the
processor selects the SS:ESP for privilege-level 1 from the TSS.
2. Pushes the return stack pointer (old SS:ESP) onto the new stack. The SS value is padded with two
bytes to form a doubleword.
3. Pushes the EFLAGS register onto the new stack.
4. Clears the following EFLAGS bits to 0: TF, NT, RF, and VM.
5. The processor handles the EFLAGS.IF bit based on the gate-descriptor type:
- If the gate descriptor is an interrupt gate, EFLAGS.IF is cleared to 0.
- If the gate descriptor is a trap gate, EFLAGS.IF is not modified.
6. Saves the return-address pointer (CS:EIP) by pushing it onto the stack. The CS value is padded
with two bytes to form a doubleword.
7. If the interrupt-vector number has an error code associated with it, the error code is pushed onto
the stack.
8. The CS register is loaded from the segment-selector field in the gate descriptor, and the EIP is
loaded from the offset field in the gate descriptor.
9. The interrupt handler begins executing with the instruction referenced by new CS:EIP.
Sinon tu peux pas tester de le restaurer à l’épilogue de ton KiTtrap ?
5.
admin | février 5th, 2008 at 19:17
Wai, j’ai testé et ca marche, j’en ai profité pour maj les sources sur le repo. Mais bon c’est quand même bizarre cette affaire. Merci de ton aide en tout cas
6.
question | février 9th, 2008 at 04:04
J’ai pas trop compris.
En gros il sert a quoi ton code ?
Merci.
7.
admin | février 9th, 2008 at 15:07
Lol k, le code que je file consiste en 2 parties un prog userland et un driver. Le soft userland va allouer une page en mémoire avec tous les droits et demander au driver de la protéger en exécution en lui envoyant un IOCTL. Dans le code user-land j’ai mit :
Les 2 accès à la page mémoire seront gérer par mon page fault-handler, le premier, une lecture sur la page sera autorisé, le second, une demande d’exécution sera renvoyer comme si rien ne s’était passé. Voilà, j’espère que c’est plus clair.
8.
martin | février 12th, 2008 at 07:53
iretd ne ferait pas un tss switch ?
la pagination se complique avec les nouveaux systemes, le PAE: extension 36bits comme xp sp2 ou le 64bits avec 4 indirections au lieu de 3…
Ca peut faire l’objet d’un autre post?
Le pb majeur de la TLB comme methode de detection VM est de trouver la geometrie du cache pour tout les CPUs, pas evident. Si la taille tlb est mal evaluee la methode ne marche pas.
Un outil free comme codeanalyze d’amd aide bien il permet de tracer les tlb miss.
9.
Helpless | février 13th, 2008 at 01:43
Salut à tous !
Je me posais une question sur l’adressage mémoire sur un système 32 Bits. Un process n’a alors accès qu’à 4Go de mémoire, répartis par défaut entre 2Go de user-space, et 2 Go de kernel space. Est-ce que qq’un pourrait m’expliquer à quoi servent les 2Go du noyau ? J’ai par exemple tester de lancer un prog chargeant l’adresse mémoire de kernel32.dll, et celle-ci se trouve dans le user-space… (0x7c800000)
10.
admin | février 13th, 2008 at 11:46
@martin
J’ai matté le manuel, il n’y a rien qui stipule un changement de TSS …
« IRET, Less Privilege. If an IRET changes privilege levels, the return program must be at a lower
privilege than the interrupt handler. The IRET in this case causes a stack switch to occur:
1. The return pointer is popped off of the stack, loading both the CS register and EIP register
(RIP[31:0]) with the saved values. The return code-segment RPL is read by the processor from the
CS value stored on the stack to determine that a lower-privilege control transfer is occurring.
2. The saved EFLAGS image is popped off of the stack and loaded into the EFLAGS register.
3. The return-program stack pointer is popped off of the stack, loading both the SS register and ESP
register (RSP[31:0]) with the saved values.
4. Control is transferred to the return program at the target CS:EIP. »
Le mystère du segment fs reste donc entier
Sinon je vais voir pour faire un sur la pagination sur systèmes avec PAE et 64 bits plus tard. Je note l’idée.
Merci pour le nom du soft « codeanalyze » je vais faire mumuse avec.
@Helpless
Kernel32 n’est qu’une DLL servant d’interface avec les appels système situés dans ntdll.dll, c’est normal que ces 2 DLL soient chargées en user-space. Ntdll se charge de faire l’appel système au noyau qui est par défaut ntoskrnl.exe dans ton c:\windows\system32 et qui se situe bien à une adresse > 0×80000000. Dans les 2GO > 0×80000000 on retrouve le kernel et les drivers.
Pour le registre FS, il n’y a pas de mystère. En fait, vous cherchez dans la mauvaise direction car le phénomène ne vient pas du hardware mais bel et bien du système d’exploitation lui-même.
Vous oubliez que vos observations se font par l’intermédiaire d’un debugger et qu’une « Trap Frame » est construite par le système d’exploitation et non pas par le cpu !!!
Plusieurs indices indiquent la piste à suivre.
1- Le registre FS est un registre de segment qui n’a pas de rôle prédéfini. Il est là en option au même titre que le registre GS. Et ils sont libres d’utilisation par le système d’exploitation. Contrairement aux registres de segment CS, DS ou bien SS qui ont un rôle bien précis et définissent des segments précis. Ils sont alors susceptibles d’être modifiés directement par le cpu lors d’évènement hardware.
2- Tous les prologues des routines KiTrapXX s’occupent eux-mêmes de modifier le registre FS. Ceci prouve bien que cela doit être fait « à la main » et qu’il ne faut pas compter sur le hardware.
Pour vérifier cela, il suffit simplement de placer le breakpoint un tout petit peu plus loin.
En plaçant le breakpoint juste après le « push fs » dans le prologue de la routine nt!KiTrap0E, on peut vérifier ce que j’avance : Lorsque la faute de page vient du mode user, on trouve bien la valeur 0x3B sur la pile.
Donc c’est bien le système qui modifie FS avant de passer la main au debugger.
Pour trouver le code qui place FS à 0×30, il faut aller voir dans le dispatcher d’exception (KiDispatchException) qui est appelé lors d’un évènement de type break point.
Le Dispatcher sauve le CONTEXT en fonction du KTRAP_FRAME avant de passer la main au debugger. Tout se passe dans la routine KeContextFromKframes.
Et dans cette routine, on y trouve ceci :
Dans le KTRAP_FRAME,
SI les interruptions sont masquées (KTRAP_FRAME.EFlags.IF == 0)
ET SI le registre de segment de code CS est égal à 8 (KTRAP_FRAME.SegCs == 8)
ALORS placer les registres de segments :
- KTRAP_FRAME.SegGs à 0
- KTRAP_FRAME.SegFs à 0×30
- KTRAP_FRAME.SegEs à 0×23
- et KTRAP_FRAME.SegDs à 0×23
AVANT de construire le CONTEXT à partir de KTRAP_FRAME.
Dans le code :
.text:00410163 test byte ptr [edi+(KTRAP_FRAME.EFlags+2)], 2 ;EFlags.IF
.text:00410167 jnz loc_44ACB0
.text:0041016D cmp [edi+KTRAP_FRAME.SegCs], 8
.text:00410171 jz loc_42AB2D
.
.
.
.text:0042AB2D loc_42AB2D: ; CODE XREF: KeContextFromKframes(x,x,x)+AB j
.text:0042AB2D and [edi+_KTRAP_FRAME.SegGs], 0
.text:0042AB31 push 23h
.text:0042AB33 pop eax
.text:0042AB34 mov [edi+_KTRAP_FRAME.SegFs], 30h
.text:0042AB3B mov [edi+_KTRAP_FRAME.SegEs], eax
.text:0042AB3E mov [edi+_KTRAP_FRAME.SegDs], eax
.text:0042AB41 jmp loc_410177 -> Construire la structure CONTEXT à partir du Kframes
Et pour un break point à l’entrée de la routine nt!KiTrap0E, on a bien toutes ces conditions de réunies
Je pense que le truc important à retenir est que :
Il ne faut jamais oublier que le système d’exploitation agit TOUJOURS entre un évènement hardware et un résultat obtenu avec le debugger.
Voilà
En espérant que mon petit message amènera une petite pierre à l’édifice.
Bonne continuation Ivan.
Et bon week-end,
Lionel
12.
lol | février 17th, 2008 at 00:08
ce beau bordel :’(
13.
admin | février 17th, 2008 at 11:26
HAN ! Bien vu Yolejedi ! J’avais complètement oublier que le debugger pouvait avoir une influence . Merci beaucoup.
Yolejedi >> pourquoi KiDispatchException a besoin de créer une CONTEXT ? normalement ce serait pour appeler KiUserDispatchException, mais vu qu’on vient du kernel mode, je vois pas l’intérêt ..
Taron : Je suis désolé mais je ne comprends pas très bien ta question.
> pourquoi KiDispatchException a besoin de créer une CONTEXT ?
Windows essaye toujours en premier lieu de passer une exception à un debugger s’il y en a un d’actif. Pour cela, une structure CONTEXT est crée à partir du TrapFrame. C’est comme ça. Windows est pensé comme ça. Ça permet au debugger de modifier les infos proposées dans le CONTEXT et éventuellement de rattraper l’exception.
> normalement ce serait pour appeler KiUserDispatchException,
Pourquoi normalement ?
> vu qu’on vient du kernel mode, je vois pas l’intérêt ..
Le « kernel mode » c’est tout simplement un environnement avec des privilèges hardware en plus. Mais il existe les mêmes mécanismes de debuggage et de SEH en user et en kernel mode. Il ne faut pas oublier qu’un thread user passe très régulièrement en mode kernel par le biais des syscall ou des interruptions.
Dans notre cas, nous avons un beakpoint sur l’entrée de nt!KiTrap0E. Ce breakpoint n’a pu que être placé à partir d’un debugger kernel. Et lors de l’interruption 3, le système formate le TrapFrame en CONTEXT avant de passer l’exception au debugger kernel.
Une interruption est toujours interceptée en kernel mode. Ensuite le système traite l’exception suivant sa provenance. Il filtre quoi
Le passage par KiUserExceptionDispatcher de ntdll ne se fait que si l’exception à eu lieu à partir du user mode. Tout ceci me semble logique.
Et même lorsque l’exception passe par KiUserExceptionDispatcher, le retour se fait par du kernel mode avec NtContinue. D’ailleurs, le fait que l’on puisse modifier les registres de debug Dr0-Dr3, Dr6 et Dr7 dans le CONTEXT à partir du user mode ne fait qu’appuyer ceci.
Voilà
Je ne sais pas si j’ai répondu à ta question Taron. Mais pour bien comprendre, il suffit de suivre tout cela en static avec IDA et en regardant dans ntdll.dll et ntoskrnl.exe.
Si si, t’as bien répondu à ma question, juste un dernier détail, pourquoi dans le cas où l’exception parvient du kernel land, on place FS à KCPR, et ES/DS à des segments user-land avant de construire le CONTEXT ?
Sous Windows, lorsqu’un thead passe du user au kernel mode, le registre FS doit OBLIGATOIREMENT être placé sur le KPCR. De plus, pour pouvoir changer FS de 0x3B à 0×30 il faut OBLIGATOIREMENT que les interruptions soit masquées et ceci dés la première instruction assembleur exécutée en kernel mode.
En passant par un system call (sysenter) ou par un interrupt gate, les interruptions sont automatiquement masquées par le cpu avant l’exécution de la première instruction en kernel mode…donc pas de souci. Ceci explique pourquoi il n’y a pas d’instruction « cli » à l’entrée des routines nt!KiTrapXX avant de modifier le registre FS.
Mais (et voilà où je voulais en venir) en utilisant une Call Gate pour changer de privilège, les interruptions ne sont pas automatiquement masquées par le cpu ! Et on a beau placer en toute première instruction exécutée en kernel mode un « cli », il y a toujours un risque que le thread soit switché avant que les interruptions aient été masquées et donc avant que le registre FS ait été modifié. Et dans ce cas là, c’est le crash assuré. J’avais un peu parlé de cela (tardivement) dans un commentaire sur le post d’Ivan parlant des CallGate.
C’est pourquoi je pense que la technique des CallGate est une possibilité offerte par le cpu mais qui n’est pas adaptée et fiable dans un environnement tel que Windows.
Je me permets d’insister là dessus car je pense que c’est un point très important à comprendre qui n’est pas du tout abordé en général lorsque l’on parle des CallGate sous Windows.
Et dans notre cas d’un breakpoint sur le début de la routine nt!KiTrap0E, le système vérifie avant tout l’environnement. Et s’il y a eu interruption en kernel mode (registre CS = 8) et SI les interruptions sont masquées (Eflags.IF = 0), le système assure les arrières en modifiant le registre FS à 0×30 directement dans le KTRAP_FRAME car suivant où le breakpoint a été placé, FS peut ne pas encore avoir été modifié.
Mais bon, je m’égare un peu
Pour ce qui est des registres DS et ES placé à 0×23, il semble que le système soit pensé comme cela. Seul le segment de pile en kernel mode est un segment uniquement accessible au Kernel mode (SS = 0×10). Les autres (DS et ES) reste à 0×23.
Mais ce ne sont pas des « segments user-land » comme tu dis. Ceux sont des segments accessibles aussi à partir du user-land. Ce qui n’est pas tout à fait la même chose.
La différence est qu’en user-land (pour un système 32bit) seul les 2 premiers Go sont accessible. A partir du kernel-land, la totalité du segment est accessible.
Mais ta remarque est pertinente Taron. Et je t’avoue que je ne sais pas vraiment pourquoi ces registres de segments ne sont pas modifiés pour être uniquement accessibles à partir du kernel mode. Il existe dans la GDT qu’un segment de DATA32 RING0 de 4Go, c’est le selecteur 0×10. Et celui-ci semble être exclusivement utilisé pour la pile kernel (SS = 0×10).
Si quelqu’un croise Bill… ça serait cool de lui en glisser 2 mots
Voilà Taron. J’ai été un peu bavard.
Et désolé, Ivan, de m’étendre sur ton blog.
Je me calme là…promis…
En tout cas, merci Taron pour cette remarque. Je vais corriger cette info dans mes scripts pour windbg. Car je donnais finalement de mauvaises infos pour les GDT :
Plop YoLeJedi, nan tk ya pas de problème tu peux squatter autant que tu veux, ca fait plaisir d’avoir des bons commentaires. Je t’avouerais que ca m’intriguais un peu aussi, mais si on regarde bien, au final les segments ES et DS ne sont jamais modifiés et cela quelque soit le CPL courant, je dirais que les dev MS on du s’en foutent un peu dans le sens que les segments sont defs en flat model et que donc ils représentent tout l’espace mémoire sur 32 bits, après qu’on soit en r0 ou r3 ça ne change rien, le système reste valable et fonctionne bien.
[...] réaliser de driver pas comme dans l’exemple que j’ai fait avec la pagination dans ce post. Il me fallait donc une solution implémentable depuis le user-land qui ne va défoncer tout mon [...]
24.
Null | mai 22nd, 2012 at 13:08
Plop, nice article !
Je voulais savoir s’il est possible de jouer avec toutes ces structures pour déterminer si une adresse mémoire correspond à quelque chose dans le kernel.
En gros, l’idée ce serait d’émuler un IsBadReadPtr() dans le kernel, histoire de pas faire crasher le système en tentant de lire à une adresse qui ne contient rien. Apparemment c’est faisable pour un process, mais pour le kernel, je sais pas trop, et je ne trouve pas grand chose sur le sujet…
24 Comments
1. Taron | février 4th, 2008 at 22:21
Salut, j’ai pas lu l’article mais j’ai juste aperçu ton pb avec FS au retour en userland. En fait déjà je crois que la stack que tu montres est fausse : INT push sur la pile kernel ss, esp, eflags, cs et eip. Les regsitres fs, ed, ds, gs sont normalement pushés plus tard dans le prologue du service, t’es pas d’accord ?
2. admin | février 4th, 2008 at 22:53
Hum oui bien vu mon cher Taron. On a bien sur la stack
error code
eip
cs
eflags
esp
ss
Pour s’en assurer il suffit de mettre un BP sur la fonction NewInt0EHandler et de regarder esp
J’ai mal lu les man intel, désolé. Tu as encore raison quand tu dis que c’est le prologue de la fonction qui gère l’exception qui push fs sur la stack :
Comme on peut le voir, fs est pushé sur la stack par le prologue de KiTrap0E et mit à la valeur 0×30 qui correspond bien au descripteur de segment kernel-land. La vrai question qui vaut très cher c’est pourquoi, lorsqu’il y une exception, la valeur de fs vaut déja 0×30 alors que le prologue ne s’est pas exécuté ? Par exemple
Fs est déjà mit à la valeur 0×30 alors qu’en user-land il vaut 0x3B. Celon le « AMD64 Architecture Programmer’s Manual Volume 2 System Programming » section 8.7.3 Interrupt To Higher Privilege on a :
When a control transfer to an exception or interrupt handler running at a higher privilege occurs
(numerically lower CPL value), the processor performs a stack switch using the following steps:
1. The target CPL is read by the processor from the target code-segment DPL and used as an index
into the TSS for selecting the new stack pointer (SS:ESP). For example, if the target CPL is 1, the
processor selects the SS:ESP for privilege-level 1 from the TSS.
2. Pushes the return stack pointer (old SS:ESP) onto the new stack. The SS value is padded with two
bytes to form a doubleword.
3. Pushes the EFLAGS register onto the new stack.
4. Clears the following EFLAGS bits to 0: TF, NT, RF, and VM.
5. The processor handles the EFLAGS.IF bit based on the gate-descriptor type:
- If the gate descriptor is an interrupt gate, EFLAGS.IF is cleared to 0.
- If the gate descriptor is a trap gate, EFLAGS.IF is not modified.
6. Saves the return-address pointer (CS:EIP) by pushing it onto the stack. The CS value is padded
with two bytes to form a doubleword.
7. If the interrupt-vector number has an error code associated with it, the error code is pushed onto
the stack.
8. The CS register is loaded from the segment-selector field in the gate descriptor, and the EIP is
loaded from the offset field in the gate descriptor.
9. The interrupt handler begins executing with the instruction referenced by new CS:EIP.
Ou qu’on parle de FS ici ?
3. ... | février 5th, 2008 at 03:13
DTC
4. Taron | février 5th, 2008 at 18:26
Sinon tu peux pas tester de le restaurer à l’épilogue de ton KiTtrap ?
5. admin | février 5th, 2008 at 19:17
Wai, j’ai testé et ca marche, j’en ai profité pour maj les sources sur le repo. Mais bon c’est quand même bizarre cette affaire. Merci de ton aide en tout cas
6. question | février 9th, 2008 at 04:04
J’ai pas trop compris.
En gros il sert a quoi ton code ?
Merci.
7. admin | février 9th, 2008 at 15:07
Lol k, le code que je file consiste en 2 parties un prog userland et un driver. Le soft userland va allouer une page en mémoire avec tous les droits et demander au driver de la protéger en exécution en lui envoyant un IOCTL. Dans le code user-land j’ai mit :
Les 2 accès à la page mémoire seront gérer par mon page fault-handler, le premier, une lecture sur la page sera autorisé, le second, une demande d’exécution sera renvoyer comme si rien ne s’était passé. Voilà, j’espère que c’est plus clair.
8. martin | février 12th, 2008 at 07:53
iretd ne ferait pas un tss switch ?
la pagination se complique avec les nouveaux systemes, le PAE: extension 36bits comme xp sp2 ou le 64bits avec 4 indirections au lieu de 3…
Ca peut faire l’objet d’un autre post?
Le pb majeur de la TLB comme methode de detection VM est de trouver la geometrie du cache pour tout les CPUs, pas evident. Si la taille tlb est mal evaluee la methode ne marche pas.
Un outil free comme codeanalyze d’amd aide bien il permet de tracer les tlb miss.
9. Helpless | février 13th, 2008 at 01:43
Salut à tous !
Je me posais une question sur l’adressage mémoire sur un système 32 Bits. Un process n’a alors accès qu’à 4Go de mémoire, répartis par défaut entre 2Go de user-space, et 2 Go de kernel space. Est-ce que qq’un pourrait m’expliquer à quoi servent les 2Go du noyau ? J’ai par exemple tester de lancer un prog chargeant l’adresse mémoire de kernel32.dll, et celle-ci se trouve dans le user-space… (0x7c800000)
10. admin | février 13th, 2008 at 11:46
@martin
J’ai matté le manuel, il n’y a rien qui stipule un changement de TSS …
« IRET, Less Privilege. If an IRET changes privilege levels, the return program must be at a lower
privilege than the interrupt handler. The IRET in this case causes a stack switch to occur:
1. The return pointer is popped off of the stack, loading both the CS register and EIP register
(RIP[31:0]) with the saved values. The return code-segment RPL is read by the processor from the
CS value stored on the stack to determine that a lower-privilege control transfer is occurring.
2. The saved EFLAGS image is popped off of the stack and loaded into the EFLAGS register.
3. The return-program stack pointer is popped off of the stack, loading both the SS register and ESP
register (RSP[31:0]) with the saved values.
4. Control is transferred to the return program at the target CS:EIP. »
Le mystère du segment fs reste donc entier
Sinon je vais voir pour faire un sur la pagination sur systèmes avec PAE et 64 bits plus tard. Je note l’idée.
Merci pour le nom du soft « codeanalyze » je vais faire mumuse avec.
@Helpless
Kernel32 n’est qu’une DLL servant d’interface avec les appels système situés dans ntdll.dll, c’est normal que ces 2 DLL soient chargées en user-space. Ntdll se charge de faire l’appel système au noyau qui est par défaut ntoskrnl.exe dans ton c:\windows\system32 et qui se situe bien à une adresse > 0×80000000. Dans les 2GO > 0×80000000 on retrouve le kernel et les drivers.
11. YoLeJedi | février 16th, 2008 at 21:37
Salut,
Pour le registre FS, il n’y a pas de mystère. En fait, vous cherchez dans la mauvaise direction car le phénomène ne vient pas du hardware mais bel et bien du système d’exploitation lui-même.
Vous oubliez que vos observations se font par l’intermédiaire d’un debugger et qu’une « Trap Frame » est construite par le système d’exploitation et non pas par le cpu !!!
Plusieurs indices indiquent la piste à suivre.
1- Le registre FS est un registre de segment qui n’a pas de rôle prédéfini. Il est là en option au même titre que le registre GS. Et ils sont libres d’utilisation par le système d’exploitation. Contrairement aux registres de segment CS, DS ou bien SS qui ont un rôle bien précis et définissent des segments précis. Ils sont alors susceptibles d’être modifiés directement par le cpu lors d’évènement hardware.
2- Tous les prologues des routines KiTrapXX s’occupent eux-mêmes de modifier le registre FS. Ceci prouve bien que cela doit être fait « à la main » et qu’il ne faut pas compter sur le hardware.
Pour vérifier cela, il suffit simplement de placer le breakpoint un tout petit peu plus loin.
En plaçant le breakpoint juste après le « push fs » dans le prologue de la routine nt!KiTrap0E, on peut vérifier ce que j’avance : Lorsque la faute de page vient du mode user, on trouve bien la valeur 0x3B sur la pile.
Donc c’est bien le système qui modifie FS avant de passer la main au debugger.
Pour trouver le code qui place FS à 0×30, il faut aller voir dans le dispatcher d’exception (KiDispatchException) qui est appelé lors d’un évènement de type break point.
Le Dispatcher sauve le CONTEXT en fonction du KTRAP_FRAME avant de passer la main au debugger. Tout se passe dans la routine KeContextFromKframes.
Et dans cette routine, on y trouve ceci :
Dans le KTRAP_FRAME,
SI les interruptions sont masquées (KTRAP_FRAME.EFlags.IF == 0)
ET SI le registre de segment de code CS est égal à 8 (KTRAP_FRAME.SegCs == 8)
ALORS placer les registres de segments :
- KTRAP_FRAME.SegGs à 0
- KTRAP_FRAME.SegFs à 0×30
- KTRAP_FRAME.SegEs à 0×23
- et KTRAP_FRAME.SegDs à 0×23
AVANT de construire le CONTEXT à partir de KTRAP_FRAME.
Dans le code :
.text:00410163 test byte ptr [edi+(KTRAP_FRAME.EFlags+2)], 2 ;EFlags.IF
.text:00410167 jnz loc_44ACB0
.text:0041016D cmp [edi+KTRAP_FRAME.SegCs], 8
.text:00410171 jz loc_42AB2D
.
.
.
.text:0042AB2D loc_42AB2D: ; CODE XREF: KeContextFromKframes(x,x,x)+AB j
.text:0042AB2D and [edi+_KTRAP_FRAME.SegGs], 0
.text:0042AB31 push 23h
.text:0042AB33 pop eax
.text:0042AB34 mov [edi+_KTRAP_FRAME.SegFs], 30h
.text:0042AB3B mov [edi+_KTRAP_FRAME.SegEs], eax
.text:0042AB3E mov [edi+_KTRAP_FRAME.SegDs], eax
.text:0042AB41 jmp loc_410177 -> Construire la structure CONTEXT à partir du Kframes
Et pour un break point à l’entrée de la routine nt!KiTrap0E, on a bien toutes ces conditions de réunies
Je pense que le truc important à retenir est que :
Il ne faut jamais oublier que le système d’exploitation agit TOUJOURS entre un évènement hardware et un résultat obtenu avec le debugger.
Voilà
En espérant que mon petit message amènera une petite pierre à l’édifice.
Bonne continuation Ivan.
Et bon week-end,
Lionel
12. lol | février 17th, 2008 at 00:08
ce beau bordel :’(
13. admin | février 17th, 2008 at 11:26
HAN ! Bien vu Yolejedi ! J’avais complètement oublier que le debugger pouvait avoir une influence . Merci beaucoup.
14. Taron | février 17th, 2008 at 12:06
c’etait donc ça..
15. Taron | février 17th, 2008 at 16:58
Yolejedi >> pourquoi KiDispatchException a besoin de créer une CONTEXT ? normalement ce serait pour appeler KiUserDispatchException, mais vu qu’on vient du kernel mode, je vois pas l’intérêt ..
16. YoLeJedi | février 18th, 2008 at 18:34
Salut,
Taron : Je suis désolé mais je ne comprends pas très bien ta question.
> pourquoi KiDispatchException a besoin de créer une CONTEXT ?
Windows essaye toujours en premier lieu de passer une exception à un debugger s’il y en a un d’actif. Pour cela, une structure CONTEXT est crée à partir du TrapFrame. C’est comme ça. Windows est pensé comme ça. Ça permet au debugger de modifier les infos proposées dans le CONTEXT et éventuellement de rattraper l’exception.
> normalement ce serait pour appeler KiUserDispatchException,
Pourquoi normalement ?
> vu qu’on vient du kernel mode, je vois pas l’intérêt ..
Le « kernel mode » c’est tout simplement un environnement avec des privilèges hardware en plus. Mais il existe les mêmes mécanismes de debuggage et de SEH en user et en kernel mode. Il ne faut pas oublier qu’un thread user passe très régulièrement en mode kernel par le biais des syscall ou des interruptions.
Dans notre cas, nous avons un beakpoint sur l’entrée de nt!KiTrap0E. Ce breakpoint n’a pu que être placé à partir d’un debugger kernel. Et lors de l’interruption 3, le système formate le TrapFrame en CONTEXT avant de passer l’exception au debugger kernel.
Une interruption est toujours interceptée en kernel mode. Ensuite le système traite l’exception suivant sa provenance. Il filtre quoi

Le passage par KiUserExceptionDispatcher de ntdll ne se fait que si l’exception à eu lieu à partir du user mode. Tout ceci me semble logique.
Et même lorsque l’exception passe par KiUserExceptionDispatcher, le retour se fait par du kernel mode avec NtContinue. D’ailleurs, le fait que l’on puisse modifier les registres de debug Dr0-Dr3, Dr6 et Dr7 dans le CONTEXT à partir du user mode ne fait qu’appuyer ceci.
Voilà
Je ne sais pas si j’ai répondu à ta question Taron. Mais pour bien comprendre, il suffit de suivre tout cela en static avec IDA et en regardant dans ntdll.dll et ntoskrnl.exe.
17. Taron | février 18th, 2008 at 19:04
Si si, t’as bien répondu à ma question, juste un dernier détail, pourquoi dans le cas où l’exception parvient du kernel land, on place FS à KCPR, et ES/DS à des segments user-land avant de construire le CONTEXT ?
Merci
18. YoLeJedi | février 19th, 2008 at 21:58
Sous Windows, lorsqu’un thead passe du user au kernel mode, le registre FS doit OBLIGATOIREMENT être placé sur le KPCR. De plus, pour pouvoir changer FS de 0x3B à 0×30 il faut OBLIGATOIREMENT que les interruptions soit masquées et ceci dés la première instruction assembleur exécutée en kernel mode.
En passant par un system call (sysenter) ou par un interrupt gate, les interruptions sont automatiquement masquées par le cpu avant l’exécution de la première instruction en kernel mode…donc pas de souci. Ceci explique pourquoi il n’y a pas d’instruction « cli » à l’entrée des routines nt!KiTrapXX avant de modifier le registre FS.
Mais (et voilà où je voulais en venir) en utilisant une Call Gate pour changer de privilège, les interruptions ne sont pas automatiquement masquées par le cpu ! Et on a beau placer en toute première instruction exécutée en kernel mode un « cli », il y a toujours un risque que le thread soit switché avant que les interruptions aient été masquées et donc avant que le registre FS ait été modifié. Et dans ce cas là, c’est le crash assuré. J’avais un peu parlé de cela (tardivement) dans un commentaire sur le post d’Ivan parlant des CallGate.
C’est pourquoi je pense que la technique des CallGate est une possibilité offerte par le cpu mais qui n’est pas adaptée et fiable dans un environnement tel que Windows.
Je me permets d’insister là dessus car je pense que c’est un point très important à comprendre qui n’est pas du tout abordé en général lorsque l’on parle des CallGate sous Windows.
Et dans notre cas d’un breakpoint sur le début de la routine nt!KiTrap0E, le système vérifie avant tout l’environnement. Et s’il y a eu interruption en kernel mode (registre CS = 8) et SI les interruptions sont masquées (Eflags.IF = 0), le système assure les arrières en modifiant le registre FS à 0×30 directement dans le KTRAP_FRAME car suivant où le breakpoint a été placé, FS peut ne pas encore avoir été modifié.
Mais bon, je m’égare un peu
Pour ce qui est des registres DS et ES placé à 0×23, il semble que le système soit pensé comme cela. Seul le segment de pile en kernel mode est un segment uniquement accessible au Kernel mode (SS = 0×10). Les autres (DS et ES) reste à 0×23.
Mais ce ne sont pas des « segments user-land » comme tu dis. Ceux sont des segments accessibles aussi à partir du user-land. Ce qui n’est pas tout à fait la même chose.
La différence est qu’en user-land (pour un système 32bit) seul les 2 premiers Go sont accessible. A partir du kernel-land, la totalité du segment est accessible.
Mais ta remarque est pertinente Taron. Et je t’avoue que je ne sais pas vraiment pourquoi ces registres de segments ne sont pas modifiés pour être uniquement accessibles à partir du kernel mode. Il existe dans la GDT qu’un segment de DATA32 RING0 de 4Go, c’est le selecteur 0×10. Et celui-ci semble être exclusivement utilisé pour la pile kernel (SS = 0×10).
Si quelqu’un croise Bill… ça serait cool de lui en glisser 2 mots
Voilà Taron. J’ai été un peu bavard.
Et désolé, Ivan, de m’étendre sur ton blog.
Je me calme là…promis…
En tout cas, merci Taron pour cette remarque. Je vais corriger cette info dans mes scripts pour windbg. Car je donnais finalement de mauvaises infos pour les GDT :
19. admin | février 19th, 2008 at 22:20
Plop YoLeJedi, nan tk ya pas de problème tu peux squatter autant que tu veux, ca fait plaisir d’avoir des bons commentaires. Je t’avouerais que ca m’intriguais un peu aussi, mais si on regarde bien, au final les segments ES et DS ne sont jamais modifiés et cela quelque soit le CPL courant, je dirais que les dev MS on du s’en foutent un peu dans le sens que les segments sont defs en flat model et que donc ils représentent tout l’espace mémoire sur 32 bits, après qu’on soit en r0 ou r3 ça ne change rien, le système reste valable et fonctionne bien.
Merci quand même d’avoir éclaircit le sujet.
20. Taron | février 20th, 2008 at 00:39
C’est moi qui te remercie YoLeJedi, géniaux tes posts.
21. newsoft | février 22nd, 2008 at 09:38
Un commentaire du « vieux » (que je suis) : « dans le temps », Windows NT4 tournait sur processeurs x86, MIPS R3000 et Alpha.
Donc le kernel a été conçu pour être indépendant des fonctionnalités matérielles disponibles sur le processeur.
Tous les processeurs n’ont pas forcément de support de la segmentation, c’est p/e ce qui explique pourquoi la gestion des segments est minimaliste …
22. Hooking IDT. | Nibbles mi&hellip | juin 7th, 2008 at 15:15
[...] donc d’installer son propre handler afin de lancer du code r0 par exemple, deux exemples : un article de ivan, et un crackme de [...]
23. Ivanlef0u’s Blog &r&hellip | juillet 2nd, 2008 at 18:43
[...] réaliser de driver pas comme dans l’exemple que j’ai fait avec la pagination dans ce post. Il me fallait donc une solution implémentable depuis le user-land qui ne va défoncer tout mon [...]
24. Null | mai 22nd, 2012 at 13:08
Plop, nice article !
Je voulais savoir s’il est possible de jouer avec toutes ces structures pour déterminer si une adresse mémoire correspond à quelque chose dans le kernel.
En gros, l’idée ce serait d’émuler un IsBadReadPtr() dans le kernel, histoire de pas faire crasher le système en tentant de lire à une adresse qui ne contient rien. Apparemment c’est faisable pour un process, mais pour le kernel, je sais pas trop, et je ne trouve pas grand chose sur le sujet…
Qu’est-ce que tu en penses ?
Trackback this post