PsSetCreateProcessNotifyRoutine no Windows XP

Mar 20, 2008  

Enquanto estudava os “pedaços” em modo kernel dos sistemas de anti-hacking atuais (GG, XTrap e HShield), tive a curiosidade de saber o que queriam fazer registrando uma rotina de notificação de criação de processos. No caso do GG, talvez injetar o aquele DLL maligno em todos os processos, sejam inocentes ou não.

Para registrar um callback que será chamado para cada processo criado, se usa a API:

NTSTATUS PsSetCreateProcessNotifyRoutine( IN PCREATE_PROCESS_NOTIFY_ROUTINE NotifyRoutine, IN BOOLEAN Remove );

Seria interessante colocar um breakpoint nesta API, assim poderia-se obter o endereço da rotina de callback e o estudar mecanismo. Talvez, se evitássemos que a rotina fosse registrada, mas ainda sim retornando STATUS_SUCCESS, não teriamos o driver do sistema anti-hacking monitorando os novos processos! Vou dar uma olhada nisso com mais tempo depois…

Pode-se acessar o trecho de memória onde o Kernel guarda as rotinas atualmente registradas pelo endereço em PspCreateProcessNotifyRoutine, e a quantidade delas em PspCreateProcessNotifyRoutineCount. Vamos dar uma olhada o que estes ponteiros têm:

80559400  01 00 00 00 00 00 00 00-f8 e0 fb 81 38 42 f9 81  …………8B.. 80559410  00 2e 1e 82 18 2f 1e 82-e8 2c 1e 82 00 00 00 00  …../…,…… … kd> d PspCreateProcessNotifyRoutine 805593e0  b7 0f 05 e1 00 00 00 00-00 00 00 00 00 00 00 00  ……………. 805593f0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ……………. …

Nosso ponteiro que guarda a quantidade de rotinas atualmente registradas mostra 1 rotina. Realmente, podemos ver 1 endereço armazenado em PspCreateProcessNotifyRoutine. Este endereço deve ser o ponteiro para a rotina. Vamos testá-lo:

e1050fb7 f8              clc e1050fb8 0000            add     byte ptr [eax],al e1050fba 0000            add     byte ptr [eax],al e1050fbc 0d73125103      or      eax,3511273h e1050fc1 0401            add     al,1 e1050fc3 00434d          add     byte ptr [ebx+4Dh],al e1050fc6 4e              dec     esi e1050fc7 e201            loop    e1050fca

Não aparenta ser uma entrada de alguma rotina. Ainda, o endereço está alocado na faixa 0xE1000000. Bem, segundo o layout de memória do Kernel do Windows, a faixa 0xE1000000 pertence a memória Pool Paginada (é Paged Pool, não devia ter “tentado” traduzir, eu sei…). É lá a grande “piscina” que a maioria dos componentes do Kernel tem seu heap. Isso nos dá uma certeza – pausa – que os ponteiros em PspCreateProcessNotifyRoutine apontam para dados/estruturas, que provavelmente vão conter o endereço real da rotina. Se dermos uma olhada na implementação da rotina:

nt!PsSetCreateProcessNotifyRoutine: 805c46d2 8bff            mov     edi,edi 805c46d4 55              push    ebp 805c46d5 8bec            mov     ebp,esp 805c46d7 53              push    ebx 805c46d8 33db            xor     ebx,ebx 805c46da 385d0c          cmp     byte ptr [ebp+0Ch],bl 805c46dd 56              push    esi 805c46de 57              push    edi 805c46df 7465            je      nt!PsSetCreateProcessNotifyRoutine+0x74 (805c4746) …

nt!PsSetCreateProcessNotifyRoutine+0x74: 805c4746 53              push    ebx 805c4747 ff7508          push    dword ptr [ebp+8] 805c474a e8e1d30300      call    nt!ExAllocateCallBack (80601b30) 805c474f 8bf0            mov     esi,eax 805c4751 3bf3            cmp     esi,ebx 805c4753 7507            jne     nt!PsSetCreateProcessNotifyRoutine+0x8a (805c475c) 805c4755 b89a0000c0      mov     eax,0C000009Ah 805c475a eb2a            jmp     nt!PsSetCreateProcessNotifyRoutine+0xb4 (805c4786)

Hmm… ExAllocateCallBack? Isso é uma pista. O MSDN não ajudou muito, não existe esta API. Não-documentado? Pelo nome, temos uma idéia sobre o que faz. Uma breve análise leva a:

805c4747 ff7508          push    dword ptr [ebp+8]  <- Rotina a ser registrada 805c474a e8e1d30300      call    nt!ExAllocateCallBack (80601b30)

Necessita de 2 argumentos, sendo o primeiro, a rotina a ser registrada (ebp+8) e o segundo NULL (EBX foi zerado em 805CE50A). Vamos carregar algum driver que chame a rotina PsSetCreateProcessNotifyRoutine, para que possamos executar passo-a-passo e ver o resultado da função ExAllocateCallBack. Retornou 0xe15713f8. Se olharmos dentro desta rotina (não vou colar aqui, não interessa muito), vamos ver que é alocado um bloco de memória no pool com o tag “Cbrb”. Então, é preenchido com os 2 argumentos. O bloco alocado tem 12 bytes de tamanho. Porém, o primeiro DWORD é colocado como 0 pelo ExAllocateCallback. Algo como: struct { DWORD Zero; PVOID Rotina; DWORD Desconhecido; } CALLBACK; O que há no callback alocado então?

e15713f8  00 00 00 00 40 c4 cc f8-00 00 00 00 0d 73 12 51  ….@4…….s.Q

Opa. Realmente, o segundo DWORD é o endereço de uma rotina que foi solicitado o registro (é proveniente de um pequeno driver de teste que escrevi para este propósito).

Agora, temos o conhecimento suficiente para conseguir o endereço real das rotinas, a partir dos ponteiros na lista de rotinas PspCreateProcessNotifyRoutine. Não, o Kernel não fará mais nada de estranho, já chequei (tirando o detalhe que virá logo em seguida). O resto da função checará se há espaço para mais uma rotina, caso sim, acrescentá-la na lista e incrementar o contador.

De volta ao PspCreateProcessNotifyRoutine:

805593e0  ff 13 57 e1 00 00 00 00-00 00 00 00 00 00 00 00  ..W……..

Hmm… nosso Callback não estava em 0xe15713f8? O endereço na lista está 7 bytes a frente. Então, para obtermos o segundo membro do Callback (endereço da rotina real), teremos que fazer [PspCreateProcessNotifyRoutine[i] – 7 + 4]. Voltamos para o início da estrutura (-7) e pegamos o segundo membro (+4). Veremos:

e15713fc  40 c4 cc f8 00 00 00 00-36 00 00 00 03 02 0f 0c  @…….6…….

Hmm! Se não é nossa rotina real 😉 0xf8ccc440:

CREATE_PROC!NotifyRoutine [d:\driverdev\createproc\crtprc.c @ 13]: f8ccc440 8bff            mov     edi,edi f8ccc442 55              push    ebp f8ccc443 8bec            mov     ebp,esp f8ccc445 0fb64510        movzx   eax,byte ptr [ebp+10h] f8ccc449 f7d8            neg     eax f8ccc44b 1bc0            sbb     eax,eax f8ccc44d 83e00b          and     eax,0Bh f8ccc450 83c04e          add     eax,4Eh

Ótimo! Vamos testar em um sistema com mais rotinas registradas:

80562940  2f 17 45 e1 6f 64 79 e1-bf 48 79 e1 e7 67 5f e1  /.E.ody..Hy..g_. 80562950  47 64 5f e1 00 00 00 00-00 00 00 00 00 00 00 00  Gd_………….

Vemos que temos 5 Callback alocados.

80562960  05 00 00 00 00 00 00 00-b8 7b ad 89 b8 aa ab 88  ………{……

O PspCreateProcessNotifyRoutineCount confirma. Vamos ver alguns:

e145172c  1c 1e 6d ba 00 00 00 00-0e 00 4c 45 03 06 02 00  ..m…….LE…. 0: kd> u ba6d1e1c spdi+0x28e1c: ba6d1e1c 55              push    ebp ba6d1e1d 8bec            mov     ebp,esp ba6d1e1f 807d1000        cmp     byte ptr [ebp+10h],0 ba6d1e23 0f85ed000000    jne     spdi+0x28f16 (ba6d1f16) ba6d1e29 83651000        and     dword ptr [ebp+10h],0 ba6d1e2d 56              push    esi ba6d1e2e 57              push    edi ba6d1e2f ff15d4c06eba    call    dword ptr [spdi+0x430d4 (ba6ec0d4)]

e15f6444  20 a1 e1 a7 00 00 00 00-f0 1c 78 e1 03 08 03 00   ………x….. 0: kd> u a7e1a120 IsDrv122+0x9120: a7e1a120 55              push    ebp a7e1a121 8bec            mov     ebp,esp a7e1a123 51              push    ecx a7e1a124 51              push    ecx a7e1a125 807d1000        cmp     byte ptr [ebp+10h],0 a7e1a129 53              push    ebx a7e1a12a 56              push    esi a7e1a12b 57              push    edi

Analisando o PsSetCreateProcessNotifyRoutine no Windows 2000, percebe-se que ele utiliza uma maneira muito mais simples: é armazenado os endereços reais na lista PspCreateProcessNotifyRoutine. Não existem estruturas Callbacks para isso. Apenas:

const DWORD MAX_ROUTINES = 8; PVOID RegisteredNotifyRoutines[MAX_ROUTINES];

Do Vista em diante, este limite de rotinas foi aumentado para 64.

Estudos aplicados estão por vir.