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.
ProcessDebugObjectHandle – ProcessInfoClass
#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