Peering Inside...

Aller au contenu | Aller au menu | Aller à la recherche

jeudi 10 juillet 2008

NtQueryInformationProcess - ProcessInfoClass #32 - Partie 2

Suite de l’étude du cas numéro 32 de NtQueryInformationProcess, nommé ProcessHandleTracing.



Nous avons vu dans le post précédent que lorsque l’on utilise conjointement NtSetInformationProcess et NtQueyInformationProcess avec ProcessHandleTracing, nous récupérions les informations suivantes :

  • Le nombre de handles soumis au « handle tracing ».
  • Le processus et le thread à qui appartient un handle.
  • Le type (ou plutôt « état ») du handle.
  • Le stack trace du handle.

Lire la suite

mercredi 9 juillet 2008

NtQueryInformationProcess - ProcessInfoClass #32 - Partie 1

Etude du cas numéro 32 de NtQueryInformationProcess, nommé ProcessHandleTracing.

Lire la suite

vendredi 4 juillet 2008

NtQueryInformationProcess - ProcessInfoClass #31

Nous allons étudier le 31ème cas de NtQueryInformationProcess, nommé ProcessDebugFlags.

Lire la suite

vendredi 27 juin 2008

Visual Studio + ML64 (MASM 64)

Suite du post précédent, mais cette fois une combinaison Visual Studio + ML64 !

Lire la suite

jeudi 26 juin 2008

Intel AVX

Encore un nouveau jeu d'instructions ! Intel a récemment sortie la spécification d'un nouveau jeu d'instruction nommé AVX (pour Advanced Vector Extensions). Ce jeu sera disponible pour la microarchitecture (toujours x86 - x86-64) Intel nommée Sandy Bridge (courant 2010). Petit tour d'horizon de ce jeu d'instruction.

Lire la suite

mardi 24 juin 2008

Outils Sysinternals

Hey, non, je suis pas mort :P

Pas mal de boulot et la vie font que ... pas forcément le temps de poster. C'est pourtant pas l'envie qui me manque. Ceci dit, faut-il encore avoir des idées ! (ce qui, je dois l'avouer, n'est pas trop mon cas en ce moment). Du coup pour remplir un peu ce site, j'ai décidé de faire des petits posts pas forcément techniques mais toujours en rapport avec la ligne du site, quand même (rien sur les affres, ô combien préoccupant, de la vie des peoples :P).

Donc, revenons à nos moutons. Sysinternals de Mark Russinovich et Bryce Cogswell (désormais propriété de Microsoft) propose un site sous forme de repository permettant de retrouver tous les outils publiés : http://live.sysinternals.com/

N.B : il y a un répertoire tools, mais je cherche encore la différence avec le rep parent...

Si vous êtes perdu dans la liste des outils, n'hésitez pas à aller sur le site standard (à défaut d'un autre mot) : http://technet.microsoft.com/en-us/sysinternals/default.aspx

Voilà, un billet sans rien de palpitant, j'en ai bien peur, mais je reviens demain avec un peu plus de matière, c'est promis !

jeudi 20 décembre 2007

Visual Studio et compilation assembleur x86

Comment compiler et lier du C ou du C++ et de l'assembleur sous Visual Sudio sans sortir de l'IDE ni utiliser d'outil externes, ni d’assembleur en ligne ( "inline" ).

Lire la suite

mardi 4 décembre 2007

NtQueryInformationProcess - ProcessInfoClass #30

Aujourd’hui nous allons voir le cas #30 de NtQueryInformationProcess nommé ProcessDebugObjectHandle.

Le nom de ce cas est encore une fois très parlant, la fonction vous retournera tout simplement un HANDLE sur le DebugObject. Voilà, A+, c’est sympa d’être venu !

Hum, vous êtes un peu sur votre faim là ? Ok, voyons ça en détail. On commence par quelque chose de classique :

[asm]
PAGE:00496DCA case_ProcessDebugObjectHandle:          ; CODE XREF: NtQueryInformationProcess(x,x,x,x,x)+1B90

; size of buffer == 4 ?
PAGE:00496DCA                 cmp     edi, edx        ; jumptable 0049680C case 30
PAGE:00496DCC                 jnz     STATUS_INFO_LENGTH_MISMATCH

; Get EPROCESS from process handle
PAGE:00496DD2                 push    0               ; HandleInformation
PAGE:00496DD4                 lea     eax, [ebp+EPROCESS]
PAGE:00496DD7                 push    eax             ; Object
PAGE:00496DD8                 push    dword ptr [ebp+AccessMode] ; AccessMode
PAGE:00496DDB                 push    _PsProcessType  ; ObjectType
PAGE:00496DE1                 push    400h            ; DesiredAccess
PAGE:00496DE6                 push    [ebp+Handle]    ; Handle
PAGE:00496DE9                 call    _ObReferenceObjectByHandle@24 ; ObReferenceObjectByHandle(x,x,x,x,x,x)

; if NULL, get out…
PAGE:00496DEE                 test    eax, eax
PAGE:00496DF0                 jl      loc_494D40

Comme vous pouvez le voir, on commence par regarder si la taille du buffer utilisateur vaut bien 4, puis on obtient un pointeur sur EPROCESS depuis le HANDLE du processus, rien de palpitant.

La suite à présent :

[asm]
;Get Debug Handle from EPROCESS
PAGE:00496DF6                 lea     eax, [ebp+DebugHandle]
PAGE:00496DF9                 push    eax             ; int
PAGE:00496DFA                 push    dword ptr [ebp+AccessMode] ; AccessMode
PAGE:00496DFD                 push    [ebp+EPROCESS]  ; EPROCESS
PAGE:00496E00                 call    _DbgkOpenProcessDebugPort@12 ; DbgkOpenProcessDebugPort(x,x,x)
; Test if handle is valid.
PAGE:00496E05                 mov     edi, eax
PAGE:00496E07                 test    edi, edi
PAGE:00496E09                 jge     short DebugHandleValid
PAGE:00496E0B                 and     [ebp+DebugHandle], 0
PAGE:00496E0F
PAGE:00496E0F DebugHandleValid:                     ; CODE XREF: NtQueryInformationProcess(x,x,x,x,x)+218D

; Dereference EPROCESS
PAGE:00496E0F                 mov     ecx, [ebp+EPROCESS]
PAGE:00496E12                 call    @ObfDereferenceObject@4 ; ObfDereferenceObject(x)

; Save handle to user buffer…
PAGE:00496E1E                 mov     eax, [ebp+DebugHandle]
PAGE:00496E21                 mov     [esi], eax

Bon, voilà, on obtient un handle sur le DebugObject à partir de EPROCESS grâce à la fonction DbgkOpenProcessDebugPort et on sauve ce handle dans le buffer utilisateur.

Voyons un peu comment on obtient ce handle depuis EPROCESS. Analysons la fonction DbgkOpenProcessDebugPort :

[asm]
; Test if DebugPort is set
PAGE:0058110C                 mov     esi, [ebp+PEPROCESS]
PAGE:0058110F                 add     esi, 0BCh       ;PEPROCESS->DebugPort
PAGE:00581115                 cmp     dword ptr [esi], 0
PAGE:00581118                 mov     [ebp+RetCode], 0C0000353h ; STATUS_PORT_NOT_SET
PAGE:0058111F                 jz      short DebugPortNotSet

Si le DebugPort d’EPROCESS n’est pas présent on sort directement de la fonction. Continuons :

[asm]
;Acquire DebugPort mutex
PAGE:00581122                 mov     edi, offset _DbgkpProcessDebugPortMutex
PAGE:00581127                 mov     ecx, edi
PAGE:00581129                 call    ds:__imp_@ExAcquireFastMutex@4 ; ExAcquireFastMutex(x)
PAGE:0058112F                 mov     esi, [esi]      ; ESI = DebugPort
PAGE:00581131                 test    esi, esi
PAGE:00581133                 jz      short loc_58113C

; Reference DebugPort
PAGE:00581135                 mov     ecx, esi
PAGE:00581137                 call    @ObfReferenceObject@4 ; ObfReferenceObject(x)
PAGE:0058113C
PAGE:0058113C loc_58113C:

; Release Mutex
PAGE:0058113C                 mov     ecx, edi
PAGE:0058113E                 call    ds:__imp_@ExReleaseFastMutex@4 ; ExReleaseFastMutex(x)

On acquière un mutex sur le debug port (avec DbgkpProcessDebugPortMutex), on référence le debug port en tant qu’objet (un structure de type DEBUG_OBJECT, comme l’a montré Alex Ionescu) et on relâche le mutex.

Vient enfin la fonction - importante - suivante :

[asm]
PAGE:00581149                 push    [ebp+arg_8]     ; int
PAGE:0058114C                 push    dword ptr [ebp+AccessMode] ; AccessMode
PAGE:0058114F                 push    _DbgkDebugObjectType ; ObjectType
PAGE:00581155                 push    2000000h        ; AccessMask
PAGE:0058115A                 push    0               ; int
PAGE:0058115C                 push    0               ; int
PAGE:0058115E                 push    esi             ; Object
PAGE:0058115F                 call    _ObOpenObjectByPointer@28 ; ObOpenObjectByPointer(x,x,x,x,x,x,x)
PAGE:00581164                 test    eax, eax
PAGE:00581166                 mov     [ebp+RetCode], eax ; Sauve le HANDLE

La fonction ObOpenObjectByPointer retourne un HANLDE à partir du pointeur sur un objet, elle est notamment appelée lors d’un appel à NtOpenProcess). Je vous fais grâce de son analyse, sachez simplement qu’elle utilise en interne la fonction ObpCreateHandle, qui justement créée – entre autre – un HANDLE à partir d’un objet.

La suite de la fonction DbgkOpenProcessDebugPort retourne simplement le HANDLE obtenu et NtQueryInformationProcess sauve celui-ci dans le buffer utilisateur.

ProcessDebugObjectHandleProcessInfoClass #30

[in] HANDLE ProcessHandle,

[in] PROCESSINFOCLASS ProcessDebugObjectHandle,

[out] HANDLE ProcessInformation,

[in] sizeof(HANDLE) ProcessInformationLength

On return:

The HANDLE of the DebugObject.

A quoi sert ce handle ?

Ce handle est utilisé par les fonctions de debugging, comme :

  • NtWaitForDebugEvent
  • NtDebugContinue
  • NtDebugActiveProcess
  • NtRemoveProcessDebug
  • NtSetInformationDebugObject

L’utilité de ce handle en ring3 me semble limitée (à part faire un anti-debug supplémentaire et pas très documenté :p ).

Notez qu’un CloseHandle() sur ce handle ne change apparemment rien au comportement du debugger, même si CloseHanlde() ne retourne pas une erreur…

Je n’ai pas non plus trouvé d’appel à la fonction ZwQueryInformationProcess avec ce PROCESSINFOCLASS (numéro 30) dans ntdll ou kernel32, et comme NtQueryInformationProcess n’est pas appelée depuis le kernel, sauf par RtlCreateUserProcess… Bref, "Mystère et boule de gomme".

Je suis un peu dépité, à part une analyse de fonction pas grand chose à ce mettre sous la dent, je vous l'accorde. Si quelqu'un voit une quelconque "utilité" à avoir ce handle en ring3, je suis tout ouïe :D

dimanche 2 décembre 2007

NtQueryInformationProcess - ProcessInfoClass #29

On continue sur notre lancée avec une étude du cas #29 de PROCESSINFOCLASS pour NtQueryInformationProcess, j'ai nommé : ProcessBreakOnTermination.

Le code de ce cas est court et très facilement compréhensible :

[asm]
PAGE:00496FA0 case_ProcessBreakOnTermination:         ; CODE XREF: NtQueryInformationProcess(x,x,x,x,x)+1B90
PAGE:00496FA0                                         ; DATA XREF: PAGE:JmpTable
PAGE:00496FA0                 cmp     edi, edx        ; jumptable 0049680C case 29
PAGE:00496FA2                 jnz     STATUS_INFO_LENGTH_MISMATCH

; Get EPROCESS from Process Handle
PAGE:00496FA8                 push    0               ; HandleInformation
PAGE:00496FAA                 lea     eax, [ebp+var_9C]
PAGE:00496FB0                 push    eax             ; Object
PAGE:00496FB1                 push    dword ptr [ebp+AccessMode] ; AccessMode
PAGE:00496FB4                 push    _PsProcessType  ; ObjectType
PAGE:00496FBA                 push    400h            ; DesiredAccess
PAGE:00496FBF                 push    [ebp+Handle]    ; Handle
PAGE:00496FC2                 call    _ObReferenceObjectByHandle@24 ; ObReferenceObjectByHandle(x,x,x,x,x,x)
PAGE:00496FC7                 test    eax, eax
PAGE:00496FC9                 jl      loc_494D40

; Get 13th bit of EPROCESS.Flags
PAGE:00496FCF                 mov     ecx, [ebp+var_9C] ; ECX = PEPROCESS
PAGE:00496FD5                 mov     edi, [ecx+248h] ; EPROCESS.Flags
PAGE:00496FDB                 shr     edi, 0Dh        ; Get 13th bit of Flags (EPROCESS.BreakOnTermination)
PAGE:00496FDE                 and     edi, 1

; Dereference object.
PAGE:00496FE1                 call    @ObfDereferenceObject@4 ; ObfDereferenceObject(x)

Dans un premier temps, on compare la taille du buffer - dans edi - passé depuis le mode utilisateur (ring3) avec une constante (edx = 4), le buffer utilisateur devant donc avoir une taille de 4 octets.

Avec la fonction ObReferenceObjectByHandle, on obtient la strcture EPROCESS depuis le handle du processus, et l'on va ensuite chercher les drapeaux de ce même processus (membre Flags d’EPROCESS.)

De ces drapeaux on extrait uniquement le treizième qui est BreakOnTermination. Finalement, on déréférence l’objet puis (non montré dans le code), on sauve ce drapeau dans le buffer utilisateur. CQFD.

ProcessBreakOnTerminationProcessInfoClass #29

[in] HANDLE ProcessHandle,

[in] PROCESSINFOCLASS ProcessBreakOnTermination,

[out] BYTE ProcessInformation,

[in] sizeof(BYTE) ProcessInformationLength

On return:

The “break on termination” state of the process.

Remarks & Warning :

You must not set the EPROCESS BreakOnTermination flag if no kernel debugger is enabled. Doing so would result in a KeBugCheck().

A quoi sert ce drapeau ?

Ce drapeau est utilisé par la fonction PspTerminateProcess (merci ReactOS ), ou plus exactement dans la fonction PspExitThread.

Le bout de code qui test si le flag est armé (remarquez que le test se fait un octet plus loin que le début du membre EPROCESS.Flags) :

[asm]
PAGE:004AD8F5 loc_4AD8F5:                             ; CODE XREF: PspExitThread(x)+11D

; EPROCESS.Flags (Is BreakOnTermination enabled ?)
PAGE:004AD8F5                 test    byte ptr [edi+249h], 20h 
PAGE:004AD8FC                 jz      loc_4A4501      ; nop
PAGE:004AD902                 jmp     BreakOnTerminationEnabled ; yep

Le code qui appelle PspCatchCriticalBreak test si le drapeau est armé. Remarquez que si aucun debugger kernel n’est attaché, on va direct sur un BugCheck !

PAGE:00522EFF BreakOnTerminationEnabled:              ; CODE XREF: PspExitThread(x)+9524
PAGE:00522EFF                 cmp     _KdDebuggerEnabled, 0
PAGE:00522F06                 jz      short BugCheck ; BugCheck if no Kernel Debugger
PAGE:00522F08                 lea     eax, [edi+174h]
PAGE:00522F0E                 push    eax             ; BugCheckParameter3
PAGE:00522F0F                 push    edi             ; BugCheckParameter2
PAGE:00522F10                 push    offset str->CriticalProces ; "Critical process 0x%p (%s) exited\n"
PAGE:00522F15                 call    _PspCatchCriticalBreak@12 ; PspCatchCriticalBreak(x,x,x)

Cette dernière function (PspCatchCriticalBreak) vérifie à nouveau qu’un debugger Kernel est attaché et affiche un message (cf. code ci-dessus, ligne 0x00522F10) et demande à l’utilisateur s’il souhaite breaker (fonction DbgBreakPoint) ou non.

Remarquez que le BugCheck lancé a un code 0x0F4 que l'on peut facilement interpréter avec Windbg :

kd> !analyze -show 0x0F4
[...]
CRITICAL_OBJECT_TERMINATION (f4)
A process or thread crucial to system operation has unexpectedly exited or been
terminated.
Several processes and threads are necessary for the operation of the
system; when they are terminated (for any reason), the system can no
longer function.
Arguments:
Arg1: 00000000, Terminating object type
Arg2: 00000000, Terminating object
Arg3: 00000000, Process image file name
Arg4: 00000000, Explanatory message (ascii)

Script WinDbg

Voilà un petit script Windbg qui vérifie le statut du drapeau BreakOnTermination, et affiche le nom des processus concernés :

[windbg]
$$  Get process list LIST_ENTRY in $t0.
r $t0 = nt!PsActiveProcessHead

$$  Iterate over all processes in list.
.for (r $t1 = poi(@$t0);
      (@$t1 != 0) & (@$t1 != @$t0);
      r $t1 = poi(@$t1))
{
    r? $t2 = #CONTAINING_RECORD(@$t1, nt!_EPROCESS, ActiveProcessLinks);
    .process @$t2

    $$  Get image name into $ImageName.
    r? $t4 = @@c++(&@$t2->ImageFileName[0])

    $$ get Eprocess->Flags
    r? $t3 = @@c++(&@$t2->Flags)

    .if(((poi(@$t3) & 0x2000)) != 0)
    {
        .printf "_EPROCESS: %N ; File name : %ma ; BreakOnTermination enabled ! EPROCESS.Flags : 0x%x8\n",@$t2,  
@$t4, poi(@$t3)
    }
}

Sur mon système (Win XP SP2 - 32bits) un seul des processus actifs du système dispose de ce drapeau, il s’agit de csrss.exe :

_EPROCESS: 85F58B70 ; File name : csrss.exe ; BreakOnTermination enabled ! EPROCESS.Flags : 0xd2a008

Note

Il y a deux tests dans la fonction PspCatchCriticalBreak() que je trouve étranges, ou disons plutôt, redondants :

cmp _KdDebuggerEnabled, bl

et :

cmp _KdDebuggerNotPresent, 0

Si KdDebuggerEnabled est faux et KdDebuggerNotPresent est vrai on a droit à un « joli » bugcheck. Mais peut-on avoir un debugger qui soit à la fois « Enabled » et « NotPresent » ? Ou bien s’agit-il simplement de programmation défensive ?

[update]

Merci à Ivanlef0u pour sa réponse à la question précédente. Je vous invite à voir son commentaire qui fait la lumière sur ce point.

mercredi 21 novembre 2007

Fast Memory copy

Aujourd’hui, dans un programme, j’ai dû copier un gros paquet de donnée d’un buffer vers un autre. Je me suis posé la question quant à savoir qu’elle était la manière la plus rapide de le faire.

J’ai donc conduit un petit benchmark avec différent code, dont voici la procédure de test :

  • Allocation de deux tampons de 2^27 octets (134217728 octets) chacun, soit 128 mégaoctets.
  • Le tampon source est rempli avec des octets au hasard.
  • Les procédures de copie mémoire copient chacune à leur tour 20 fois le tampon source vers le tampon de destination (soit 2560 mégaoctets copiés par fonction).
  • Chaque fonction de copie est chronométrée avec le compteur haute précision de Windows.

Les tests ont été effectués sur un AMD XP à 3Ghz.

J’ai utilisé 8 fonctions de copie en mémoire différentes :

  • La fonction memcpy() du CRT de Microsoft.
  • Fonction assembleur avec MOVSB (copie octet par octet).
  • Fonction assembleur avec MOVSW (copie mot par mot). 1 mot = 2 octets.
  • Fonction assembleur avec MOVSD (copie double-mot par double-mot). 1 double-mot = 4 octets.
  • Fonction assembleur utilisant l’instruction MMX MOVQ. (8 octets copiés par instruction).
  • Fonction en C utilisant le type unsigned __int64 (unsigned long long).
  • Fonction en assembleur utilisant l’instruction SSE MOVAPS (copie 16 octets par 16 octets).
  • Fonction en C avec les intrinsics Intel utilisant MOVAPS.

Le programme est compilé sous Visual 2005 en C (/TC) avec « full optimisation » (/Ox). Voilà le résultat :

E:\AppData\Projects\CPP\CopyTest\Release>CopyTest.exe
Enter buffer size in bytes : 134217728

Filling random buffer
End filling

Starting performance measures:
CRT memcpy function result (in seconds) : 4.60489
MOVSB result (in seconds) : 4.93718
MOVSW result (in seconds) : 4.62504
MOVSD result (in seconds) : 2.3289
Copy64bits in Asm result (in seconds) : 4.51805
Copy64bits in C result (in seconds) : 4.68405
MOVAPS in Asm result (in seconds) : 4.10173
MOVAPS in C result (in seconds) : 4.20978

Premier constat :

  • La fonction CRT memcpy n’est pas vraiment très rapide…
  • MOVSD est bigrement rapide !
  • MOVAPS est trop lent…

Je pensais que MOVAPS (qui copie quand même par paquet de 128 bits) serait au niveau de MOVSD, voir même en dessous, mais ça n’est pas le cas. Il est possible que l’implémentation de MOVAPS soit mauvaise pour mon CPU (qui n’est pas tout jeune), ou que l’utilisation que j’en fais soit mauvaise aussi…

Quelqu'un aurait-il une idée de la mauvaise performance de MOVAPS ?

Si jamais quelqu’un veut tester sur sa machine en donnant son type de CPU et me donner ses résultats, je vous donne le code source ici (archive .rar): Solution Visual 2005.

mardi 20 novembre 2007

NtQueryInformationProcess - ProcessInfoClass #28

Suite du post précédent concernant NtQueryInformationProcess, cette fois ci voyons le cas ProcessLUIDDeviceMapsEnabled (cas #28).

[asm]
PAGE:004C4305 case_ProcessLUIDDeviceMapsEnabled:      
; jumptable 0049680C case 28
PAGE:004C4305                 cmp     edi, edx        ; edi = ProcInfoLength ; edx = 4
PAGE:004C4307                 jnz     STATUS_INFO_LENGTH_MISMATCH ; 0x0C0000004h
PAGE:004C4314                 call    _ObIsLUIDDeviceMapsEnabled@0 ; ObIsLUIDDeviceMapsEnabled()
PAGE:004C4319
PAGE:004C4319                 mov     [esi], eax ; esi = pointer to user buffer.

La fonction ObIsLUIDDeviceMapsEnabled est d’une simplicité affolante :

[asm]
PAGE:004C42D9                 mov     eax, _ObpLUIDDeviceMapsEnabled
PAGE:004C42DE                 retn

La question étant de savoir :

  • 1) Où est initialisée cette variable (ObpLUIDDeviceMapsEnabled).
  • 2) A quoi sert cette variable.

La variable suscitée est initialisée dans la fonction ObpCreateDosDevicesDirectory (notez le « Ob » préfixant le nom de fonction et désignant tout ce qui se rapporte à l’« object manager »), fonction qui sert à créer les fameux DosDevice, notamment :

  • - ??
  • - \GLOBALROOT
  • - \Global ??
  • - \DosDevices

Voilà le début de la fonction ObpCreateDosDevicesDirectory :

[asm]
INIT:005DD9BF                 xor     esi, esi
INIT:005DD9C1                 cmp     ds:_ObpProtectionMode, esi
INIT:005DD9C7                 jz      NoProtectionMode
INIT:005DD9CD                 cmp     ds:_ObpLUIDDeviceMapsDisabled, esi
INIT:005DD9D3                 mov     ds:_ObpLUIDDeviceMapsEnabled, 1
INIT:005DD9DD                 jnz     NoProtectionMode
INIT:005DD9E3
INIT:005DD9E3 loc_5DD9E3:

Le code de la branche :

[asm]
INIT:005DDB5D NoProtectionMode:
INIT:005DDB5D                 mov     ds:_ObpLUIDDeviceMapsEnabled, esi
INIT:005DDB63                 jmp     loc_5DD9E3

Le code est très simple : la variable ObpLUIDDeviceMapsEnabled acquière la même valeur que la variable ObpProtectionMode !

Malheureusement je n’ai pas trouvé dans le kernel la moindre initialisation de la variable ObpProtectionMode. Il existe toutefois une très bonne piste, située dans le kernel :

[asm]
INIT:005D1D68                                         ; "Session Manager"
INIT:005D1D6C                 dd offset str->Protectionmode ; "ProtectionMode"
INIT:005D1D70                 dd offset _ObpProtectionMode

Une chaîne de caractère « Session manager » et une autre chaîne « Protection mode » ! Un petit coup de google nous donne la réponse :

La variable est initialisée suivant cette clé du registre :

 HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Protection Mode

Cette clé permet de renforcer la protection sur les objets du « Windows NT manager namespace ».

In fine, le ProcessInfoClass #28 nous permet d'obtenir l'état de la protection des objets de l'espace de nom du Windows NT manager !

ProcessLUIDDeviceMapsEnabled ProcessInfoClass #28

[in] HANDLE ProcessHandle,

[in] PROCESSINFOCLASS ProcessLUIDDeviceMapsEnabled,

[out] BOOL ProcessInformation,

[in] sizeof(BOOL) ProcessInformationLength

On return:

The ProcessInformation holds a BOOL value indicating whether the Windows NT manager namespace is protected (TRUE ; 1) on not (FALSE ; 0).

dimanche 18 novembre 2007

NtQueryInformationProcess et ses paramètres non documentés

C’est en analysant la fonction de NTDLL.dll sous Windows XP SP2 nommée LdrpCheckForSecuROMImage (dont je parlerais dans un autre billet) que je suis tombé sur quelque chose de plutôt intrigant. La fonction fait en effet appel à la fonction ZwQueryInformationProcess avec un paramètre PROCESSINFOCLASS de 0x25…

Je décide de jeter un simple coup d’œil dans le livre de Gary Nebbett pour voir où s’arrêtaient les PROCESSINFOCLASS qu’il avait documenté et en effet il se révèle que ceux-ci s’arrêtaient à 0x1A (soit 26 en décimal).

J’ai finalement fini d’analyser la fonction mais je voulais avant tout vous présenter l’analyse des paramètres non-documentés de ZwQueryInformationProcess (ou NtQueryInformationProcess pour être plus exact), d’autant plus qu’on ne trouve quasiment aucune info sur le net à ce sujet, mes recherches sur Google, ReactOS ou Wine étant restées infructueuses si ce n’est le nom des valeurs de l’énumération.

[c]
typedef enum _PROCESSINFOCLASS
{
//[…]
    ProcessImageFileName,//27
    ProcessLUIDDeviceMapsEnabled,
    ProcessBreakOnTermination,
    ProcessDebugObjectHandle,//30
    ProcessDebugFlags,
    ProcessHandleTracing,
    ProcessIoPriority,
    ProcessExecuteFlags,
    ProcessTlsInformation,//35
    ProcessCookie,
    ProcessImageInformation,//dernier cas possible sous Win XP SP2
    ProcessCycleTime,
    ProcessPagePriority,
    ProcessInstrumentationCallback,
    MaxProcessInfoClass
} PROCESSINFOCLASS;

Le début de la fonction est très simple : On commence par regarder si le buffer utilisateur est en écriture avec un ProbeForWrite, puis on arrive à ce bout de code chargé de choisir une branche suivant le ProcessInfoClass :

[asm]
PAGE:00494CC7                 mov     eax, [ebp+ProcessInformationClass]
PAGE:00494CCA                 push    16h             ; 22d
PAGE:00494CCC                 pop     ecx
PAGE:00494CCD                 cmp     eax, ecx
PAGE:00494CCF                 jg      Jmp_Table_cases_23_37

On arrive alors sur une table de saut (Jmp table) :

[asm]
PAGE:00496800 Jmp_Table_cases_23_37:                  ; CODE XREF: NtQueryInformationProcess(x,x,x,x,x)+53
PAGE:00496800                 add     eax, 0FFFFFFE9h ; switch 15 cases (SUB eax, 23h)
PAGE:00496803                 cmp     eax, 0Eh
PAGE:00496806                 ja      INVALID_INFO_CLASS ; default case
PAGE:0049680C                 jmp     ds:JmpTable[eax*4] ; switch jump

Voici cette Jmp Table :

[asm]
PAGE:0049724A JmpTable        dd offset case23        ; DATA XREF: NtQueryInformationProcess(x,x,x,x,x)+1B90
PAGE:0049724A                 dd offset case24        ; jump table for switch statement
PAGE:0049724A                 dd offset INVALID_INFO_CLASS
PAGE:0049724A                 dd offset case26
PAGE:0049724A                 dd offset case_ProcessImageFileName
PAGE:0049724A                 dd offset case_ProcessLUIDDeviceMapsEnabled
PAGE:0049724A                 dd offset case_ProcessBreakOnTermination
PAGE:0049724A                 dd offset case_ProcessDebugObjectHandle
PAGE:0049724A                 dd offset case_ProcessDebugFlags
PAGE:0049724A                 dd offset case_ProcessHandleTracing
PAGE:0049724A                 dd offset INVALID_INFO_CLASS
PAGE:0049724A                 dd offset case_ProcessExecuteFlags
PAGE:0049724A                 dd offset INVALID_INFO_CLASS
PAGE:0049724A                 dd offset case_ProcessCookie
PAGE:0049724A                 dd offset case_ProcessImageInformation

On remarque dans un premier temps que les cas 25, 33 et 35 retournent un NTSTATUS valant INVALID_INFO_CLASS (0x0C0000003).

On commencera donc une analyse de cas avec le cas numéro 27 (0x1B) passé à ZwQueryInformationProcess, nommément : ProcessImageFileName

[asm]
PAGE:004FEF45                 lea     eax, [ebp+PEPROCESS]
PAGE:004FEF48                 push    eax             ; Object
PAGE:004FEF49                 push    dword ptr [ebp+AccessMode] ; AccessMode
PAGE:004FEF4C                 push    _PsProcessType  ; ObjectType
PAGE:004FEF52                 push    400h            ; DesiredAccess
PAGE:004FEF57                 push    [ebp+ProcessHandle] ; Handle
PAGE:004FEF5A                 call    _ObReferenceObjectByHandle@24 ; ObReferenceObjectByHandle(x,x,x,x,x,x)
PAGE:004FEF5F                 test    eax, eax
PAGE:004FEF61                 jl      loc_494D40
PAGE:004FEF67                 lea     eax, [ebp+PUNICODE_STRING]
PAGE:004FEF6A                 push    eax
PAGE:004FEF6B                 push    [ebp+PEPROCESS]
PAGE:004FEF6E                 call    _SeLocateProcessImageName@8 ; SeLocateProcessImageName(x,x)

Le code ci-dessus appelle ObReferenceObjectByHandle pour obtenir un pointeur sur la structure EPROCESS à partir du HANDLE du processus. On voit ensuite un appel à SeLocateProcessImageName, qui prend en entrée un pointeur sur une structure EPROCESS et un pointeur sur une structure UNICODE_STRING.

Voilà le passage intéressant de cette fonction :

[asm]
PAGE:004AC5B8                 mov     eax, [ebp+PEPROCESS]
PAGE:004AC5BB                 push    esi
PAGE:004AC5BC                 lea     esi, [eax+1F4h] ; SE_AUDIT_PROCESS_CREATION_INFO.POBJECT_NAME_INFORMATION

Le membre à l’offset 0x1F4 de la structure EPROCESS est une structure SE_AUDIT_PROCESS_CREATION_INFO elle-même contenant un pointeur sur une structure OBJECT_NAME_INFORMATION, cette dernière contenant uniquement une structure UNICODE_STRING :

kd> dt _EPROCESS
nt!_EPROCESS
[…]
  +0x1f4 SeAuditProcessCreationInfo : _SE_AUDIT_PROCESS_CREATION_INFO

kd> dt _SE_AUDIT_PROCESS_CREATION_INFO
nt!_SE_AUDIT_PROCESS_CREATION_INFO
  +0x000 ImageFileName    : Ptr32 _OBJECT_NAME_INFORMATION

kd> dt _OBJECT_NAME_INFORMATION
nt!_OBJECT_NAME_INFORMATION
  +0x000 Name             : _UNICODE_STRING

Pour synthétiser : Le membre à l’offset 0x1F4 de la structure EPROCESS est un pointeur sur une structure UNICODE_STRING contenant le nom de l’image.

Le reste de la fonction SeLocateProcessImageName ne fait que copier la chaîne unicode vers un une mémoire alloué dans le pool Kernel et le reste de NtQueryInformationProcess copie la string du pool vers le buffer utilisateur et libère la mémoire allouée.

Notez que le nom de l’image est en réalité un chemin (ou plutôt un objet) de type « volume device » et non un lien symbolique de type « ??\C:\ ».

En voilà un exemple :

kd> !process 0 0
**** NT ACTIVE PROCESS DUMP ****
[…]
PROCESS 854aa020  SessionId: 0  Cid: 0f00    Peb: 7ffdd000  ParentCid: 01b4
   DirBase: 1b5f3000  ObjectTable: e149dc08  HandleCount:  45.
   Image: calc.exe

kd> dt _SE_AUDIT_PROCESS_CREATION_INFO 854aa020 + 0x1f4
nt!_SE_AUDIT_PROCESS_CREATION_INFO
  +0x000 ImageFileName    : 0x85f4b640 _OBJECT_NAME_INFORMATION


kd> dt _UNICODE_STRING 0x85f4b640
nt!_UNICODE_STRING
 "\Device\HarddiskVolume2\WINDOWS\system32\calc.exe"
  +0x000 Length           : 0x62
  +0x002 MaximumLength    : 0x64
  +0x004 Buffer           : 0x85f4b648  "\Device\HarddiskVolume2\WINDOWS\system32\calc.exe"

ProcessImageFileName – ProcessInfoClass #27

[in] HANDLE ProcessHandle,

[in] PROCESSINFOCLASS ProcessImageFileName,

[out] UNICODE_STRING ProcessInformation,

[in] sizeof(UNICODE_STRING) ProcessInformationLength

On Return:

The UNICODE_STRING structure is filled with the volume device path from the process image file.  

typedef struct _UNICODE_STRING {
  USHORT  Length;
  USHORT  MaximumLength;
  PWSTR  Buffer;
} UNICODE_STRING, *PUNICODE_STRING;

Members

Length

The length in bytes of the string stored in Buffer.

MaximumLength

The length in bytes of Buffer.

Buffer

Pointer to a buffer used to contain a string of wide characters.

jeudi 15 novembre 2007

MD5 facile !

C'est en bidouillant le PDB de NTDLL sous XP que je me suis aperçu que les 3 fonctions "standards" du MD5 étaient présentes dans cette DLL (nommément MD5Init, MD5Update et MD5Final). Malheureusement, ces trois fonctions ne sont pas exportées...

En cherchant sur la MSDN si les architectes de l'API Windows avaient eu la bonne idée d'exporter ces fonctions dans une DLL, on trouve effectivement ces trois fonctions dans une DLL nommée "Cryptdll.dll" qui n'a ni fichier d'en-tête (*.h) , ni bibliothèque (*.lib). cf sur la MSDN.

Notez de plus que ces fonctions ne font en réalité que sauter sur les fonctions respectives (et portant le même nom) dans Advapi32.dll sans utiliser la technique bien connue de l'Export Forwarding...

Disassembly of MD5Init  (0x767914A0) in Cryptdll.dll
; Section: .text
;= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 
; EXP: MD5Init (13)
; SYM:MD5Init
0x767914A0: FF2558107976           JMP         DWORD PTR [ADVAPI32.DLL!MD5Init]; (0x76791058) 
0x767914A6: 90                     NOP

Bref, on dispose cette fois d'un MD5 à peu de frais, et plutôt que d'embarquer le source (souvent résultat d'un copié / collé) des fonctions MD5, voilà qui nous permet d'appeler une DLL système (au choix ADVAPI32 ou CryptDLL, sachant que seule la deuxième est officiellement documentée).

Un exemple de code :

[c]
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <tchar.h>

/* Data structure for MD5 (Message Digest) computation */
typedef struct _MD5_CTX
{
  UINT32 i[2];					/* number of _bits_ handled mod 2^64 */
  UINT32 buf[4];				/* scratch buffer */
  unsigned char in[64];			/* input buffer */
  unsigned char digest[16];		/* actual digest after MD5Final call */
} MD5_CTX;

typedef void (__stdcall * pfnMD5Init) (MD5_CTX* mdContext);
typedef void (__stdcall * pfnMD5Update) (MD5_CTX* mdContext, char* pString, size_t stringLen);
typedef void (__stdcall * pfnMD5Final) (MD5_CTX* mdContext);

void MDString (char* pString);
void MDPrint (MD5_CTX *mdContext);

int main(void)
{
	MDString("Simple test");

	return 0;
}

void MDString (char* pString)
{
	HMODULE hNtdll = NULL;
	pfnMD5Init pMD5Init = NULL;
	pfnMD5Update pMD5Update = NULL;
	pfnMD5Final pMD5Final = NULL;

	MD5_CTX mdContext;
	size_t stringLen;

	RtlZeroMemory(&mdContext, sizeof(MD5_CTX));
	
	stringLen = strlen (pString);
	

	hNtdll = LoadLibrary(_T("Cryptdll"));
	pMD5Init = (pfnMD5Init) GetProcAddress(hNtdll, "MD5Init");
	pMD5Update = (pfnMD5Update) GetProcAddress(hNtdll, "MD5Update");
	pMD5Final = (pfnMD5Final) GetProcAddress(hNtdll, "MD5Final");

	pMD5Init (&mdContext);
	pMD5Update (&mdContext, pString, stringLen);
	pMD5Final (&mdContext);

	printf ("Hash for \"%s\" :\n\n", pString);

	MDPrint (&mdContext);
}
void MDPrint (MD5_CTX *mdContext)
{
  int i;

  for (i = 0; i < 16; i++)
    printf ("%02x", mdContext->digest[i]);
}

L'output du programme :

Hash for "Simple test" :

f8000b87eee30d3906f763adfa621f71

Premier Post

Je disposais d'un système de blog sur mon site depuis maintenant plus de trois ans sans y avoir jamais réellement touché. L'avantage du blog étant, de mon point de vue, de pouvoir écrire des articles relativement courts sans artillerie lourde. Voilà donc un système (nommément Dotclear) à jour, et qui j'espère se remplira avec des choses que vous jugerez intéressantes et/ou apprécierez !