sábado, 3 de outubro de 2015

KiSystemService e entrada no kernel

PS:Uma parte desse artigo é baseado em processadores intel. 

O Windows e funciona usando as funções que estão nas dlls na pasta "system32".Dependendo de qual dll pertença a função,ela vai ter dois caminhos para seguir até chegar ao kernel,se for da dll Kernel32, ela ainda vai ter que passar por uma função na ntdll,se for da User32,ela ainda vai chamar uma função na Gdi32,
se for da Gdi32 ,ela não vai chamar outra função de nenhuma outra dll.

Tanto a gdi32 quanto a ntdll ainda ficam no espaço de endereço do usuário(0 à 0x80000000).Geralmente a ntdll é carregada no endereço 0x7C900000,e a gdi32 em 0x77E50000.
Suas funções chamam ainda uma outra função,"KiFastSystemCall(0x7FFE0300)" e esta finalmente usa uma instrução "sysenter" para entrar no kernel.

O que essa instrução faz é carregar os valores que estão guardados nos "MSRs", nescessários para que o processador funcione corretamente agora que vai entrar no kernel.
SYSENTER_CS_MSR - Pega esse valor e põe no registro de segmento "CS" ,agora o processador sabe da onde deve executar código.
SYSENTER_EIP_MSR - Pega esse valor e põe no registro "EIP" ,agora o processador sabe qual deve ser a próxima instrução a ser executada. 
SYSENTER_ESP_MSR  - Pega esse valor e põe no registro "ESP",agora  o processador sabe que a stack que ele deve se basear é a do kernel.

Feito isso o processador alterna do privilégio 3 para o 0.Como o msr SYSENTER_EIP_MSR é quem tinha qual a próxima instrução à ser executada,logo ele é quem tinha o endereço de KiSystemService.
Ou seja,o código de KiSystemService é o primeiro código que é executado toda vez que há uma transição de modo de usuário para modo kernel,essa função fica no módulo "ntoskrnl.exe",que nada mais é do que o próprio kernel rodando.  

Vamos ver o que acontece realmente usando as chamadas que a função "CloseHandle" na kernel32 faz como exemplo:
BOOL WINAPI PseudoCloseHandle( _In_ HANDLE hObject )
{
     NtClose(hObject); }


NTSTATUS WINAPI NtClose(_In_ HANDLE Handle)
{ 
     _asm MOV EAX,0x19;
       KiFastSystemCall();
}

void KiFastSystemCall()
{
   _asm mov edx,esp;
   _asm sysenter;
}

Comentários
Toda função que chama KiFastSystemCall,antes põe um valor no registro eax,esse valor vai servir como entrada numa tabela de funções no kernel chamada  "KiServiceTable" para funções vindas da kernel32 ,ou "W32pServiceTable" para funções vindas da Gdi32.
NtClose passa o valor "0x19" para o registro eax,logo é possível assumir que na entrada 0x19,existe uma função que representa NtClose no kernel.
KiFastSystemCall guarda o valor do registro "esp"(que é o endereço da stack atual do programa em modo de usuario)em edx. 

Antes de mostrar KiSystemService,é preciso dizer que funções vindas da kernel32 chamam funções implementadas no próprio módulo ntoskrnl(onde fica a KiSystemService)e funções vindas da gdi32 chamam funções implementadas no módulo win32k.sys(que é um outro módulo que fica no espaço de endereço do kernel também).
Também é preciso dizer que o kernel usa duas estruturas para alcançar essas tabelas,SystemServiceTable e ServiceDescriptorTable :

struct SystemServiceTable
{
      //Se for do módulo ntoskrnl,então aponta para KiServiceTable, se for para a Win32k então aponta //para W32pServiceTable
      PULONG_PTR ServiceTableBase;
      PULONG ServiceCounterTableBase; //Contador de chamadas 
      ULONG NumberOfServices;   //Número de funções na tabela ServiceTableBase
      PUCHAR ParamTableBase;   //Tabela com a quantidade de argumentos que cada função pega       
}

struct ServiceDescriptorTable
{
     SystemServiceTable ntoskrnl; 
     SystemServiceTable win32k; 
}

Uma parte do que KiSystemService faz é :
void PseudoKiSystemService(_KTHREAD *ArgThread,
_KTRAP_FRAME *ArgTrapFrame,
ULONG      Instruction )
{
    KPCR *kpcraux = (KPCR_N_OPACA*)KIP0PCRADDRESS;
    KTHREAD *kthread_global = kpcraux->Prcb->CurrentThread;
  
   unsigned int *stack_usuario;   
   _asm mov stack_usuario,edx;
    unsigned int _indexsystemcall;
    _asm mov _indexsystemcall,eax; //EAX tem a entrada na tabela de funções

    //Ve qual das SystemServiceDescriptorTable vai ser usada
    _indexsystemcall = (_indexsystemcall>>8)& 0x30;
    SystemServiceTable *ssdtapoio = (SystemServiceTable*)(
        (unsigned int)kthread_global->ServiceTable + _indexsystemcall );

     unsigned int eaxapoio; 
    _asm mov eaxapoio,eax;
    eaxapoio &= 0xFFF;
 
    if(eaxapoio <= ssdtapoio->NumberOfServices)
    {
        //Verifica se algum desenho na gdi batch deve ser despejado
        if(_indexsystemcall == 0x10 && ((char*)kpcraux->NtTib.Self->GdiBatchCount) != 0)
        {
            KeGdiFlushUserBatch(eaxapoio,stack_usuario);
        }
         //Incrementa o contador de chamadas do sistema
        *(unsigned int*)kpcraux->Prcb->KeSystemCalls) +=1;

      
       //Pega o número de argumentos que a função recebe         
        unsigned char num_argapoio = ssdtapoio->ParamTableBase[eaxapoio];
      //Pega o endereço da função requisitada
        ULONG_PTR func_endapoio = ssdtapoio->ServiceTableBase[eaxapoio];
      
        if(stack_usuario <= (unsigned int*)MmUserProbeAddress)
        {
            unsigned int *pstackkernel;
            _asm mov pstackkernel,esp;
            while(num_argapoio > 0)
            {
                *pstackkernel= *stack_usuario;
                pstackkernel++;
                stack_usuario++;
                num_argapoio - = 1;
            }             
           *func_endapoio; //Chama a função
       }
}

Comentários
A maioria do código da função é fazendo checagens de segurança ,verificação de breakpoints para debuggers e salvando informações para a troca de contexto,então isso foi removido pra manter o foco no tópico

1 - EAX tem o valor de entrada para uma das tabelas de funções,veja que esse valor é usado para dois fins,um para determinar qual tabela de funções usar(se indexsystemcall for 0,então usa a KiServiceTable,se for 16 então usa a W32pServiceTable),veja que o membro da thread atual (estrutura KTHREAD),"ServiceTable",aponta para uma estrutura "SERVICE_DESCRIPTOR_TABLE",no qual tem membros do tipo "SystemServiceTable",que por sua vez tem em seu membro "ServiceTableBase",a tabela à ser usada.
2 - Depois é verificado se a index passada está dentro da quantidade de funções que essa tabela tem
3 - Depois do número de argumentos que a função recebe e o endereço da função serem pegos,ainda é verificado se o endereço da stack é menor que o endereço onde começa o kernel (MmUserProbeAddress) ,então é feito um loop que copia os argumentos da stack de modo usuário para a stack de modo kernel
4 - Agora a função é chamada

domingo, 27 de setembro de 2015

Formato PE(Portable Executable) e funções

A dll Dbghelp e ImageHlp do windows,tem apis que servem exatamente para esse propósito.Com ela é possível que você veja todas as informações(úteis e um pouco inúteis)que os módulos(exe,dll)que estão mapeados no seu espaço de endereço,guardam.Você pode pensar em um módulo como sendo um arquivo cheio de estruturas.Se você fosse fazer um debugger independente das versões do windows,essas apis seriam a maneira mais prática para os ler.Nós vamos ver o que essas funções fazem e se possível acessarmos nós mesmos diretamente essas informações.

As estruturas que vão ser vistas aqui podem ser encontradas no header "WinNT" do seu compilador,se você quiser saber mais sobre seus campos,acesse https://msdn.microsoft.com/pt-br/library/windows/desktop/ms680198%28v=vs.85%29.aspx .
Um módulo PE é composto basicamente de :
IMAGE_XXX_HEADERS - estruturas usadas para representar cabeçalhos
IMAGE_SECTION_HEADER - estrutura geral para representar seções
IMAGE_XXX_DIRECTORY - estruturas usadas para representar diretórios,(para alguns só chamados de tabelas,para outros até dado cru),muitas vezes isso fica dentro da própria seção.

Considerados "PE HEADERS"  são :
IMAGE_NT_HEADERS,IMAGE_FILE_HEADER ,IMAGE_OPTIONAL_HEADER
Veja que todas as estruturas que formam o PE HEADER estão dentro de IMAGE_NT_HEADERS

struct _IMAGE_NT_HEADERS {
    DWORD Signature;
    IMAGE_FILE_HEADER FileHeader;
    IMAGE_OPTIONAL_HEADER32 OptionalHeader;

}

Logo após os HEADERS vem as seções :
.text ,.data,.bss ,.CRT,.rdata,.rsrc,.edata,.rdata,.idata,.reloc,.icode,.debug
 
Para você acessar os diretórios a primeira coisa é saber quem guarda os deslocamentos para esses diretórios,e isso fica nos headers.
Para você acessar seções basta deslocar o espaço de todos os headers juntos.

"IMAGE_NT_HEADERS" é o começo do PE HEADER,para ler e acessar esse header você usa uma estrutura IMAGE_NT_HEADERS com a função "ImageNtHeader"
IMAGE_NT_HEADERS *pheadernt = ImageNtHeader(EnderecoBasedoModulo);
 
Primeiro vamos o que ImageNtHeader faz :
PIMAGE_NT_HEADERS ImageNtHeader(PVOID base)
{
    IMAGE_DOS_HEADER *pidh_apoio;
    IMAGE_NT_HEADERS *pnt = NULL;

     if(base != NULL && (int)base != -1)
//Verifica a validade do ponteiro passado
    {
        pidh_apoio = (IMAGE_DOS_HEADER*)base;
        if(pidh_apoio->e_magic == IMAGE_DOS_SIGNATURE && pidh_apoio->e_lfanew < 0x10000000)
        {
            pnt = (IMAGE_NT_HEADERS*)((char*)base + pidh_apoio->e_lfanew);
            if(pnt->Signature != (DWORD)"PE")
            {
                pnt = NULL;
            }
        }
    }

    return pnt;
}


Comentários 
1 - No pedaço "pidh_apoio = (IMAGE_DOS_HEADER*)base;" ,nos mostra que todo executável tem logo no começo,uma estrutura "IMAGE_DOS_HEADER"
2 - IMAGE_DOS_SIGNATURE é uma assinatura padrão "MZ" , o membro "e_lfanew" tem o endereço relativo de onde começa realmente a estrutura "IMAGE_NT_HEADERS" do módulo,
veja que ele é somado ao endereço base de onde módulo foi carregado.O membro "Signature" fica no deslocamento 0 da estrutura,logo a constante "PE" deve estar ali.

"ImageDirectoryEntryToData",é uma função que simplismente retorna o diretório desejado.
Tipos de diretórios :
IMAGE_DIRECTORY_ENTRY_ARCHITECTURE
Dados sobre arquitetura

 
IMAGE_DIRECTORY_ENTRY_BASERELOC        
Se o módulo não pode ser carregado no seu endereço base preferido,logo essa seção será usada(dificilmente é usada)
IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT
Se o módulo foi bindado com os endereços para tais dados

IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR       
Dados sobre interface COM

IMAGE_DIRECTORY_ENTRY_DEBUG
Dados sobre como debugar o módulo(diretório para o arquivo de símbolos por exemplo)
IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT
Dados sobre dlls que devem esperar por algum motivo para serem carregadas

IMAGE_DIRECTORY_ENTRY_EXCEPTION
Dados sobre exceções

IMAGE_DIRECTORY_ENTRY_EXPORT
Dados que uma dll exporta(geralmente módulos exe não exportam nada)

IMAGE_DIRECTORY_ENTRY_GLOBALPTR
Endereço relativo do ponteiro global

IMAGE_DIRECTORY_ENTRY_IAT
Endereços que a dll ou exe importam(funções de uma outra dll por exemplo)

IMAGE_DIRECTORY_ENTRY_IMPORT
Dados sobre  o que é importado pelo módulo

IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG
Dados sobre a configuração de carregamento 

IMAGE_DIRECTORY_ENTRY_RESOURCE
Dados sobre recursos que o módulo pode requerer

IMAGE_DIRECTORY_ENTRY_SECURITY
Diretório sobre informações de segurança

IMAGE_DIRECTORY_ENTRY_TLS
Diretório com informações sobre armazenamentos específicos para cada thread

Aqui está o que a função faz ,ImageDirectoryEntryToData chama ImageDirectoryEntryToDataEx:
PVOID WINAPI ImageDirectoryEntryToDataEx(
    PVOID Base,
//Endereço base de onde o módulo foi carregado(pego com "GetModuleHandle")
    BOOLEAN MappedAsImage,
//Se o módulo foi mapeado como imagem ou dado
    USHORT DirectoryEntry,
//Uma das constantes dos diretórios acima
      PULONG Size,
//Ponteiro que recebe o tamanho do diretório procurado
      PIMAGE_SECTION_HEADER *FoundHeader //
Ponteiro para a seção do tal diretório procurado(se existir)
    )
{
    PIMAGE_NT_HEADERS pnt_apoio;

    if((int)Base & 1 == 0)/
/Testa alinhamento do endereço(verifica se é par)
    {
        pnt_apoio =  ImageNtHeader(Base);
        if(pnt_apoio != NULL)
        {
            if(pnt_apoio->OptionalHeader.Magic == 0x10B)
//Isso testa para ver se é um executável
            {
                return RetornaDiretorioSecao(Base,MappedAsImage,DirectoryEntry,Size,FoundHeader,
                    &pnt_apoio->FileHeader,&pnt_apoio->OptionalHeader);
            }
        }
    }
}


Comentários
A função é simples,veja que ela também chama  ImageNtHeader.
Quem faz o grosso é RetornaDiretorioSecao,essa é uma função não documentada,retornando um ponteiro para o começo do diretório no módulo

O que RetornaDiretorioSecao faz:
PVOID RetornaDiretorioSecao(
    PVOID Base,
    BOOLEAN MappedAsImage,
    USHORT DirectoryEntry,
    PULONG Size,
     PIMAGE_SECTION_HEADER *FoundHeader,
    PIMAGE_FILE_HEADER pifh,
    PIMAGE_OPTIONAL_HEADER pioh
    )
{

   //Checa para ver se o diretório passado é válido
    if(DirectoryEntry > pioh->NumberOfRvaAndSizes)
    {
        *Size &= 0;

        return;
    }


//Checa para ver se o diretório é válido
     if(pioh->DataDirectory[DirectoryEntry * 8].VirtualAddress == 0)
    {
        *Size = 0;
        return;
    }

   
   //Põe o tamanho do diretório no argumento passado

    *Size = pioh->DataDirectory[DirectoryEntry * 8].Size;
    IMAGE_SECTION_HEADER *secao;


    if(MappedAsImage == FALSE) 

    {    
              if(pioh->DataDirectory[DirectoryEntry * 8].VirtualAddress > pioh->SizeOfHeaders )
        {

           //Acessa as seções
            secao_ecx = (IMAGE_SECTION_HEADER*)((char*)pioh +pifh->SizeOfOptionalHeader);
            if(pifh->NumberOfSections <= 0)
            {
                return NULL;
            }
            
            int contador = 0;
            while(true)
            {  //
Verifica se o rva do diretório procurado seria para a seção atual
                if(pioh->DataDirectory[DirectoryEntry * 8].VirtualAddress > secao_ecx->VirtualAddress &&
                     pioh->DataDirectory[DirectoryEntry * 8].VirtualAddress <
                      
secao->VirtualAddress + secao->SizeOfRawData )
                    {
                        break;
                    }
                }           
               
               
//Vai para a próxima IMAGE_SECTION_HEADER(tem 0x28 bytes essa estrutura) no array,dado em "IMAGE_NT_HEADER.FileHeader.NumberOfSections"
                secao_ecx += 0x28;
                contador +=1;
                if(contador < pifh->NumberOfSections)
                {
                    return NULL;
                }
            }
           
            if(FoundHeader != NULL)
                *FoundHeader = secao; 

          //Retorna o ponteiro para o diretório procurado com algumas correções de alinhamento
            return (PVOID)((char*)Base + pioh->DataDirectory[DirectoryEntry * 8].VirtualAddress +
                (secao->PointerToRawData - secao->VirtualAddress));   
        }
    }

    if(FoundHeader != NULL)
    {
        FoundHeader = NULL;
    }
 
//Arquivo foi mapeado pelo loader do S.O, logo "VirtualAddress" tem o rva para o diretório em si
 return (PVOID)((char*)Base +  pioh->DataDirectory[DirectoryEntry * 8].VirtualAddress);
 
}

Comentários:
1 - //"IMAGE_OPTIONAL_HEADER.NumberOfRvaAndSizes" é uma constante que tem o número  de diretórios possíveis.No deslocamento OptionalHeader existe o membro "DataDirectory" que é um array do tipo "IMAGE_DATA_DIRECTORY",onde o membro "VirtualAddress" tem o rva para esse diretório. "IMAGE_DATA_DIRECTORY "é uma estrutura de 8 bytes,logo  "pioh->DataDirectory[DirectoryEntry * 8]".
2 - "if(MappedAsImage == FALSE)",se você mesmo carregou o módulo para o seu programa ,e esse argumento for FALSE,logo a função faz algumas checagens para ver onde estão as informações.
IMAGE_SECTION_HEADER.VirtualAddress é o rva de onde a seção seria carregada no módulo, IMAGE_SECTION_HEADER.SizeOfRawData é o tamanho da seção,
IMAGE_SECTION_HEADER.PointerToRawData tem o rva de onde essa seção fica no arquivo.
3 -  As seções ficam logo após os headers no arquivo. IMAGE_FILE_HEADER.SizeOfOptionalHeader tem o tamanho da estrutura IMAGE_OPTIONAL_HEADER, e IMAGE_FILE_HEADER.NumberOfSections  tem a quantidade de seções no arquivo.




sexta-feira, 15 de maio de 2015

Threads e sincronização

Antes de começar é preciso saber que a microsoft faz mudanças de tempo em tempo no código fonte do seu S.O sobre esse tema,então certas coisas vão ser(ter) abordadas de uma forma mais geral.Como também é um assunto extenso e que ganha mais complexidade a cada nova versão do windows,o que vai ser abordado é o básico,porém com o menos possível de abstração.
Basicamente as estruturas que o windows usa para controlar sincronização de threads são KWAIT_BLOCK  _DISPATCHER_HEADER e a própria KTHREAD:

struct  _DISPATCHER_HEADER
{
   UCHAR Type            ;
   UCHAR Absolute       ;  
   UCHAR Size               ;
   UCHAR Inserted          ;
   int SignalState      ;
   _LIST_ENTRY WaitListHead  ;
};

struct _KWAIT_BLOCK
{
    LIST_ENTRY WaitListEntry;
    _KTHREAD *Thread;
    PVOID Object;
    _KWAIT_BLOCK *NextWaitBlock;
    USHORT WaitKey;
    USHORT WaitType;
};

Veja que KWAIT_BLOCK é usada por ambas,DISPATCHER_HEADER e KTHREAD
1 - O membro "WaitListHead" do objeto despachante(DISPATCHER_HEADER)tem todas as thread que estão esperando por ele,na verdade ele tem entradas do tipo KWAIT_BLOCK,no qual o membro "Thread" é que tem o endereço da thread e "Object" apontando para o próprio objeto.

2 - Tanto o objeto sincronizador quanto a thread(estrutura KTHREAD "vista aqui")precisam de informações sobre que threads estão esperando(fica no objeto)e que objetos estão sendo esperados(fica na thread)
KTHREAD tem um membro "WaitBlockList"(também do tipo KWAIT_BLOCK)onde ficam todos os objetos de sincronização que essa thread está esperando.

3 - O membro "WaitBlock" também de KTHREAD,é um array de até [4]entradas do tipo KWAIT_BLOCK.Essas entradas também são usadas para referenciar objetos de sincronização.A diferença é que WaitBlockList é uma lista usada para quando a thread estiver esperando por mais objetos que o array WaitBlock pode comportar.
A intenção de ter um array de KWAIT_BLOCKs built-in para cada thread como você pode ver é otimização,em vez de ter que ficar alocando KWAIT_BLOCKs para referenciar objetos e suas threads esperando sempre que a thread entrar em modo de espera.

4 - O membro "WaitListEntry" de KWAIT_BLOCK tem as threads esperando pelo objeto.


5 - O membro "WaitListEntry" de KTHREAD contém todas as threads que estão esperando naquela CPU

6 - O membro "NextWaitBlock" é usado para espera por múltiplos objetos.Se tiver limite de tempo,mais um objeto será usado,esse membro irá apontar para uma entrada no "WaitBlock"(um KWAIT_BLOCK)que é usado para um objeto esperando com limite de tempo,ele irá servir como um tipo KTIMER.    

7- O membro "WaitKey" servirá como um índice dizendo qual o lugar que esse KWAIT_BLOCK atual(representando o objeto)está.

8 - O membro "WaitType" diz quando esperar por todos os objetos ou só por algum dele. 


9 - KiDispatcherReadyListHead,uma lista de threads que estão prontas para rodar.

ANALISANDO KeSetEvent
---------------------------------------------------------------------------------
Qual o trabalho de WaitForSingleObject?Colocar a thread que a chamou para esperar.Quem irá tira-la de seu modo de espera?Será a função SetEvent.

Vamos ver o que KeSetEvent faz :

LONG __stdcall PseudoKeSetEvent ( _KEVENT      *Event,
        KPRIORITY      Increment,
        BOOLEAN      Wait 
    )
{
 
    LONG estado_aux = Event->Header.SignalState;
KIRQL Irqeax_aux = KeAcquireQueuedSpinLockRaiseToSynch();
   
    _KWAIT_BLOCK *Kwaitthread = (_KWAIT_BLOCK*)Event->Header.WaitListHead.Flink;
        if(Event->Header.WaitListHead.Flink == &Event->Header.WaitListHead)
    {
        Event->Header.SignalState = 1;    //
Sinaliza
    }
    else
    {
        if(Event->Header.Type == SynchronizationEvent  || Kwaitthread->WaitKey == 1)
        {            

            Event->Header.SignalState = 1;
            KiUnwaitThread(Kwaitthread->Thread,Increment,0);
        }
        else //
Se FOR NotificationEvent
        {
            if(Event->Header.SignalState == 0)
            {
                Event->Header.SignalState = 1; 

                KiWaitTest();
            }
        }
    }

    if(Wait != false)
    {
        _KTHREAD * thread = (_KTHREAD*)PsGetCurrentThread();
        thread->WaitNext = Wait; 
        thread->WaitIrql = Irqeax_aux; 

    }
    else
    {
        KiUnlockDispatcherDatabase();   

   }

    return estado_aux;
}   



1 - Primeiro ela ergue a IRQL,ou seja,interrupções de threads rodando à uma IRQL menor que DISPATCH_LEVEL(que é SYNCH_LEVEL à partir do windows xp)NÃO vão poder interromper.
 Se não tiver nenhuma thread esperando esse objeto,então só sinaliza


2 -  Quando o tipo de espera é sincronização,ela irá retirar a thread que estava esperando da lista de espera no objeto e irá colocar a thread em uma lista de threads prontas para rodar chamada "KiDispatcherReadyListHead", para rodar.Quem faz isso é KiReadyThread chamada em KiUnwaitThread.
Quando o tipo de espera é de notificação,então ele deve remover todas threads que estavam esperando pelo objeto da lista de espera (KiWaitTest).


3 - "Wait" é testado,se FALSE,então "KiUnlockDispatcherDatabase" é usada para fazer o contrário que "KeAcquireQueuedSpinLockRaiseToSynch" fez,sendo assim assim as interrupções de threads com IRQL mais baixas podem voltar a acontecer.Se "Wait" for TRUE veja que a thread não vai diminuir a IRQL.

O que a microsoft diz sobre isso é que se Wait for TRUE então quem chamou "KeSetEvent" deve chamar em seguida  uma "KeWaitXxx" pois esta função irá diminuir a IRQL e isso evitaria mudanças de contexto. O que isso quer dizer é que continuaria nenhuma outra thread em uma IRQL menor podendo interromper a sua atual.O que também acontece é que "KeWaitXxx" também vai erguer a IRQL se "WaitNext" for FALSE.Passar TRUE para Wait geralmente seria útil em situações que você precisa setar um objeto e esperar por outro.
Veja que os membros "WaitNext" e "WaitIrql" da thread atual serão setados com TRUE e com o valor da IRQL erguida ainda.

ANALISANDO KeWaitForSingleObject
-------------------------------------------------------------------------------------------------
KeWaitForSingleObject é uma função bem grande,cheia de encadeamento de listas e com algumas partes que variam dependendo da versão do S.O,então vamos focar só na parte em que o código se mantém.
Algumas coisas para se entender antes é que as funções  1-KiAdjustQuantumThread é quem reajusta o quantum de tempo pra thread.

2 - KiSwapThread é quem põe a próxima thread que está pronta para rodar como a atual que vai rodar.
3 - KiSwapContext é quem realmente muda o contexto da thread,ou seja,bota uma thread para rodar no lugar da atual,ela é chamada por KiSwapThread

Juntamente com KiUnWaitThread que foi vista em KeSetEvent, essas funções formam o layout mais básico do entra e sai e espera de threads


NTSTATUS
    PseudoKeWaitForSingleObject (
    __in __deref __drv_notPointer PVOID Object,
    __in __drv_strictTypeMatch(__drv_typeCond) KWAIT_REASON WaitReason,
    __in __drv_strictType(KPROCESSOR_MODE/enum _MODE,__drv_typeConst)
    KPROCESSOR_MODE WaitMode,
    __in BOOLEAN Alertable,
    __in_opt PLARGE_INTEGER Timeout
    )


KIRQL kirql_aux;
 LARGE_INTEGER timeout_local = TimeOut
_KMUTANT *objeto = (_KMUTANT *)Object; //Objetos mesmo não sendo mutant são tratados assim
KTHREAD *thread_aux = PsGetCurrentThread();
if(thread_aux->WaitNext == 0)
//Testa para ver se a IRQL ja esta erguida,se não,então vai erguer
{
  goto KeWaitErgueIRQL;
}



KeWaitInicio:
 
//Se waitnext for 1,ou seja,se a IRQL já foi erguida 
    thread_aux->WaitNext = 0;   

//Espera só por um objeto,então usa um KWAIT_BLOCK built-in
    thread_aux->WaitBlockList = &thread_aux->WaitBlock[0] ;
    thread_aux->WaitBlock[0].WaitKey &= 0;
//índice
    thread_aux->WaitBlock[0].Object = Object;
//objeto passado
    thread_aux->WaitBlock[0].WaitType = 1;   //
número de objetos
    thread_aux->WaitStatus &= STATUS_SUCCESS;

    if(Timeout != NULL)
    {
        thread_aux->WaitBlock[0].NextWaitBlock = &thread_aux->WaitBlock[2];
        thread_aux->WaitBlock[2].NextWaitBlock = &thread_aux->WaitBlock[0]; //EDI
        thread_aux->Timer.Header.WaitListHead.Flink = &thread_aux->WaitBlock[2];
        thread_aux->Timer.Header.WaitListHead.Blink = &thread_aux->WaitBlock[2];
    }
    else
//Se Timeout FOR NULL então não tem nenhuma contagem de tempo pra fazer
    {
        thread_aux->WaitBlock[0].NextWaitBlock = &thread_aux->WaitBlock[0];
    }

    thread_aux->WaitListEntry.Flink = NULL;
    thread_aux->Alertable = Alertable;
    thread_aux->WaitMode = WaitMode;
    thread_aux->WaitReason = WaitReason;
 

while(true)
{
    thread_aux->WaitTime = KeTickCount.LowPart;

    if(thread_aux->ApcState.KernelApcInProgress != 0)
    {
        if(thread_aux->WaitIrql <= 1)
        {
            KiUnlockDispatcherDatabase();
            goto KeWaitErgueIRQL;
        }
    }

if(objeto->Header0x0.Type == MUTANT)
    {   
        if(objeto->Header0x0.SignalState > 0 &&
        objeto->OwnerThread0x18 == thread_aux)
        {
            if( objeto->Header0x0.SignalState == 0x80000000)
            {
                KiUnlockDispatcherDatabase();
                ExRaiseStatus(STATUS_MUTANT_LIMIT_EXCEEDED);
            }
            else
            {
                if(objeto->Header0x0.SignalState -= 1 == 0) 
                {
                    thread_aux->KernelApcDisable -objeto->ApcDisable0x1d;
                    (objeto->OwnerThread0x18 = thread_aux;
                   
                    if(objeto->Abandoned0x1c == 1)
                    {
                        objeto->Abandoned0x1c = 0;
                        thread_aux->WaitStatus = STATUS_ABANDONED_WAIT_0;
                    }
                   
                    objeto->MutantListEntry0x10.Flink = thread_aux->MutantListHead.Flink; objeto->MutantListEntry0x10.Blink = thread_aux->MutantListHead.Flink;                
thread_aux->MutantListHead.Blink =     &objeto->MutantListEntry0x10;
                    thread_aux->MutantListHead.Flink = &objeto->MutantListEntry0x10;
                }


                    KiAdjustQuantumThread(thread_aux);
                    KiUnlockDispatcherDatabase();
                    return WaitStatus;           

             }
        }
    }
    else
//Se o objeto NÃO é um mutant
    {
        if( (objeto->Header0x0.SignalState > 0)        

       {      
            if( (objeto->Header0x0.Type == SYNCHRONIZATION_EVENT )
            {
                objeto->Header0x0.SignalState &= 0;
            }
            else if((objeto->Header0x0.Type == SEMAPHORE)
            {
                objeto->Header0x0.SignalState -= 1;
            }

            KiAdjustQuantumThread(thread_aux);
            KiUnlockDispatcherDatabase();
            return STATUS_SUCCESS;
        }
    }


LARGER_INTEGER tempoori;

if(Timeout != NULL)
    {
         if((Timeout->HighPart | Timeout->LowPart) == 0)
        {
            KiAdjustQuantumThread(thread_aux);
            KiUnlockDispatcherDatabase();
            return STATUS_TIME_OUT;
        }

         if(KiInsertTreeTimer(Timeout->HighPart,Timeout->LowPart ) == false)
        {
            KiAdjustQuantumThread(thread_aux);
            KiUnlockDispatcherDatabase();
            return STATUS_TIMEOUT;
        }

        tempoori.LowPart = thread_aux->Timer.DueTime.LowPart;        

       tempoori.HighPart = thread_aux->Timer.DueTime.HighPart;
    }


//Salva lista de espera
thread_aux->WaitBlockList->WaitListEntry.Flink = objeto->Header0x0.WaitListHead;
    thread_aux->WaitBlockList->WaitListEntry.Blink = objeto->Header0x0.WaitListHead->Blink;
         

objeto->Header0x0.WaitListHead->Blink->Flink = thread_aux->WaitBlockList;
    objeto->Header0x0.WaitListHead->Blink = thread_aux->WaitBlockList;


    if(thread_aux->Queue != NULL)
    {
        KiActivateWaiterQueue();
    }


     int re_KiSwapThread;
    if((re_KiSwapThread = KiSwapThread() ) != STATUS_KERNEL_APC)
    {
        return re_KiSwapThread;
    }


LARGE_INTEGER duetime_aux;
    if(Timeout != NULL)
    {
        Timeout = KiComputeWaitInterval(&tempoori,&duetime_aux,timeout_local);
    }
 

KeWaitErgueIRQL:
 kirql_aux = KeRaiseIrqlToSynchLevel();
    thread_aux->WaitIrql = kirql_aux;

goto KeWaitInicio;
 .
 .
 . 

COMENTÁRIOS :
Primeiro ela entra no loop vai checar se o objeto já foi sinalizado ou não.Também é feita checagem para ver se alguma apc(asynchronous procedure call) .

Checa para ver o tipo do objeto,se for mutant,o membro signalstate é tratado como um contador em vez de sinalizador,se a quantidade possuidores para o mutant chegou à um nível maximo,uma exceção é lançada.Se não,então um decremento nesse membro é feito.
Se não for um mutant, é testado para ver se o objeto está sinalizado,
depois os tipos são testados e os devidos valores colocados e a thread então retornaria.

A partir daqui é porque o objeto não estava sinalizado.

Checa para ver se o tempo passado foi 0,nesse caso ele já vai ter testado que a thread não foi sinalizada.
Como a descrição de WaitForSingleObject diz "a função retorna imediatamente se 0 foi passado como contador de tempo".
PS:Para não ficar mal entendidos,na verdade todos os códigos até esse ponto devem ser executados já que não há como saber em modo de usuário se a thread foi sinalizada ou não

O que acontece é que se o objeto não estiver sinalizado e o tempo ou não foi passado ou ainda não terminou,a thread entrará em modo de espera(thread->state = waitingThread). A thread então será salva na lista de espera tanto do objeto quanto a da thread nos KWAIT_BLOCKs.
PS:Veja que ambos,objeto e thread apontam para os mesmos KWAIT_BLOCKs.Sendo assim,quando a função KiUnWaitThread 
remover um KWAIT_BLOCK de "WaitBlockList->WaitListEntry" da thread,automaticamente irá remover o KWAIT_BLOCK que representa a thread,no membro "WaitListHead" do objeto. 

KiSwapThread então é chamada,ela irá trocar a thread atual pela próxima esperando.
O que KiSwapThread faz é procurar na lista de threads que estão prontas para rodar(KiDispatcherReadyListHead)e chamar 
KiSwapContext que de fato vai salvar os estado da thread atual, carregar os valores da próxima thread pronta para rodar nos registros e então mudando o rumo da execução.
Esses valores vão ficar guardados no membro "ProcessorState" da estrutura KPRCB(única para cada CPU)

Threads normalmente chamam KiSwapThread para colocar uma próxima thread na CPU atual quando terminam seu quantum de tempo.Sendo assim alguma hora então sua thread será chamada para rodar.

sábado, 2 de maio de 2015

Objects e Handles

Quando um objeto é criado o que é retornado é um handle(identificador).Um handle na maioria dos casos é um valor que serve como índice em um tabela onde se encontra o objeto em si.O objeto fica no kernel,então mesmo se você tivesse seu endereço,não teria como acessá-lo em modo de usuário.
Geralmente para as funções que você passa um handle,elas vão para o kernel para poderem referenciar esse objeto.


O S.O usa algumas estruturas para apoiar determinado tipo de objeto.No objeto ficam guardadas informações cruciais sobre o tal,o tipo de informação varia conforme o tipo de objeto,por exemplo para um objeto process,ficam nele informações como quantidades de handles que o processo tem atualmente,quantidade de threads,ID única,hora em que foi criado,terminado,quantidade de páginas físicas cometidas atualmente para o processo(que estão presentes na memória RAM),pico de páginas físicas na RAM que o processo já teve,etc..

Esse tipo de informação pode ser facilmente conseguida através de APIs como "GetProcessMemoryInfo", "GetProcessHandleCount",algumas informações sobre os objetos process e thread podem ser encontradas na área da TEB e PEB no espaço de endereço do usuário,mas as que o S.O considera cruciais para segurança,não.

Essa é a teoria,agora vamos ver como o sistema operacional apóia isso.


KERNEL
======================================================================

ESTRUTURAS "Kxxx" , "Exxx" --

O nome das estruturas usadas para os objetos em si no kernel tem prefixos K e E.
Estruturas com prefixo "K" são conhecidas como estruturas de objetos despachantes.

Objetos despachantes são aqueles que podem ser usados para sincronização , como process,thread,I/O, event,semaphore,etc.. Todo objeto despachante tem um membro do tipo "DISPATCHER_HEADER". Neste ficam informações como lista de threads(consequentemente processos)que estão esperando por esse objeto,informações sobre controle de tempo,tipo,se o objeto esta sinalizado ou não.Está é _KPROCESS para objetos process :

struct _KPROCESS
{
     _DISPATCHER_HEADER header0x0;
     LIST_ENTRY ProfileListHead0x10;
     ULONG DirectoryTableBase0x18;
     _KGDTENTRY LdtDescriptor0x20;
     _KIDTENTRY Int21Descriptor0x28;
     unsigned short IopmOffset0x030;
     UCHAR Iopl0x32;
     UCHAR Unused0x33;
     ULONG ActiveProcessors0x34;
     ULONG KernelTime0x38;
     ULONG UserTime0x3c;
     LIST_ENTRY ReadyListHead0x40;
     SINGLE_LIST_ENTRY SwapListEntry0x48;
     PVOID VdmTrapcHandler0x4c;
     LIST_ENTRY ThreadListHead0x50;
     ULONG ProcessLock0x58;
     ULONG Affinity0x5c;
     unsigned short StackCount0x60;
     CHAR BasePriority0x62;
     CHAR ThreadQuantum0x63;
     UCHAR AutoAlignment0x64;
     UCHAR State0x65;
     UCHAR ThreadSeed0x66;
     UCHAR DisableBoost0x67;
     UCHAR PowerState0x68;
     UCHAR DisableQuantum0x69;
     UCHAR IdealNode0x6a;
     union
     {
     _KEXECUTE_OPTIONS Flags0x6b;
     UCHAR ExecuteOptions0x6b;
     };  
};


struct  _DISPATCHER_HEADER
{
   UCHAR Type            ;

   UCHAR Absolute       ;   
   UCHAR Size               ;
   UCHAR Inserted          ;
   int SignalState      ;
   _LIST_ENTRY WaitListHead  ;

};

Estruturas com prefixo "E" são conhecidas como estruturas executivas.
Essa representa inteiramente o objeto,no caso de objetos process e threads que também são sincronizáveis, elas tem um membro "K" no começo delas,além de outras informações para que o kernel possa manter o objeto funcionando,está é _EPROCESS para objetos process :


 struct _EPROCESS
{
   _KPROCESS Pcb0x0              ; //objeto dispachante
   _EX_PUSH_LOCK ProcessLock0x6c  ;  
   _LARGE_INTEGER CreateTime0x70    ;
   _LARGE_INTEGER ExitTime0x78       ;
   _EX_RUNDOWN_REF RundownProtect0x80 ;
   PVOID UniqueProcessId0x84 ;
   _LIST_ENTRY ActiveProcessLinks0x88;
 
   unsigned int QuotaUsage0x90 [3] ;
   unsigned int QuotaPeak0x9c [3] ;
   unsigned int CommitCharge0xa8   ;
   unsigned int PeakVirtualSize0xac ;
   unsigned int VirtualSize0xb0      ;
   _LIST_ENTRY SessionProcessLinks0xb4 ;
   PVOID DebugPort0xbc        ;
   PVOID ExceptionPort0xc0   ;
   _HANDLE_TABLE *ObjectTable0xc4;   
   _EX_FAST_REF Token0xc8         ; 
   _FAST_MUTEX WorkingSetLock0xcc  ;
   unsigned int WorkingSetPage0xec  ;
   _FAST_MUTEX AddressCreationLock0xf0;
   unsigned int HyperSpaceLock0x110   ;
   _ETHREAD * ForkInProgress0x114   ;
   unsigned int HardwareTrigger0x118  ;
 
   PVOID VadRoot0x11c         ;
   PVOID VadHint0x120         ;
   PVOID CloneRoot0x124        ;
   unsigned int NumberOfPrivatePages0x128 ;
   unsigned int NumberOfLockedPages0x12c ;
   PVOID Win32Process0x130     ;
   _EJOB *Job0x134             ;
   PVOID SectionObject0x138   ;
   PVOID SectionBaseAddress0x13c ;
   _EPROCESS_QUOTA_BLOCK * QuotaBlock0x140;  
   _PAGEFAULT_HISTORY * WorkingSetWatch0x144 ;
   PVOID Win32WindowStation0x148 ;
   PVOID InheritedFromUniqueProcessId0x14c;
   PVOID LdtInformation0x150  ;
   PVOID VadFreeHint0x154      ;
   PVOID VdmObjects0x158       ;
   PVOID DeviceMap0x15c        ;
   _LIST_ENTRY PhysicalVadList0x160;
   union
   {
   _HARDWARE_PTE PageDirectoryPte0x168;
   unsigned long long Filler0x168      ;
   };

   PVOID Session0x170         ;
   unsigned char ImageFileName0x174[16];
   _LIST_ENTRY JobLinks0x184  ;    
   PVOID LockedPagesList0x18c  ;
   _LIST_ENTRY ThreadListHead0x190; 
   PVOID SecurityPort0x198     ;
   PVOID PaeTop0x19c          ;
   unsigned int ActiveThreads0x1a0    ;
   unsigned int GrantedAccess0x1a4    ;
   unsigned int DefaultHardErrorProcessing0x1a8 ;
   int LastThreadExitStatus0x1ac ;
   _PEB Peb0x1b0            ;
   _EX_FAST_REF PrefetchTrace0x1b4   ;

   _LARGE_INTEGER ReadOperationCount0x1b8 ;
   _LARGE_INTEGER WriteOperationCount0x1c0 ;
   _LARGE_INTEGER OtherOperationCount0x1c8 ;
   _LARGE_INTEGER ReadTransferCount0x1d0;
   _LARGE_INTEGER WriteTransferCount0x1d8;
   _LARGE_INTEGER OtherTransferCount0x1e0 ;

   unsigned CommitChargeLimit0x1e8 ;
   unsigned CommitChargePeak0x1ec ;
   PVOID AweInfo0x1f0         ;
   _SE_AUDIT_PROCESS_CREATION_INFO SeAuditProcessCreationInfo0x1f4 ;
   _MMSUPPORT Vm0x1f8          ;   
   unsigned LastFaultCount0x238  ;
   unsigned ModifiedPageCount0x23c;
   unsigned NumberOfVads0x240     ;
   unsigned JobStatus0x244       ;
   unsigned Flags0x248           ;
   unsigned CreateReported0x248   :1  ;
   unsigned NoDebugInherit0x248   :1 ;
   unsigned ProcessExiting0x248   :1 ;
   unsigned ProcessDelete0x248    :1 ;
   unsigned Wow64SplitPages0x248  :1 ;
   unsigned VmDeleted0x248        :1 ;
   unsigned OutswapEnabled0x248   :1 ;
   unsigned Outswapped0x248       :1 ;
   unsigned ForkFailed0x248       :1 ;
   unsigned HasPhysicalVad0x248   :1 ;
   unsigned AddressSpaceInitialized0x248 :1;
   unsigned SetTimerResolution0x248 :1 ;
   unsigned BreakOnTermination0x248 :1 ;
   unsigned SessionCreationUnderway0x248 :1 ;
   unsigned WriteWatch0x248       :1 ;
   unsigned ProcessInSession0x248 :1 ;
   unsigned OverrideAddressSpace0x248 :1;
   unsigned HasAddressSpace0x248  :1 ;

   unsigned LaunchPrefetched0x248 :1 ;
   unsigned InjectInpageErrors0x248 :1;
   unsigned VmTopDown0x248        :1;
   unsigned Unused30x248          :1 ;
   unsigned Unused40x248         :1 ;
   unsigned VdmAllowed0x248       :1 ;
   unsigned Unused0x248           :1 ;
   unsigned Unused10x248          :1 ;
   unsigned Unused20x248          :1 ;
   int ExitStatus0x24c      ;
   unsigned short NextPageColor0x250 ;
   union
   {
   unsigned char SubSystemMinorVersion0x252;
   unsigned char SubSystemMajorVersion0x253 ;
   unsigned short SubSystemVersion0x252;
   };
 
   unsigned char  PriorityClass0x254   ;
   unsigned char WorkingSetAcquiredUnsafe0x255;
   unsigned Cookie0x258           ;
};


Mais uma estrutura em comum chamada OBJECT_HEADER é usada para todos os objetos.Ela é nescessária para que possa ser referenciado qualquer objeto independente do tipo,nela existe um membro chamado "Body" que tem o endereço do corpo do objeto em si :

struct _OBJECT_HEADER
{
    int PointerCount0x0;   
    union
    {
        int HandleCount0x4    ;
        PVOID NextToFree0x4    ;
    };

    _OBJECT_TYPE *Type0x8    ; 
    unsigned char NameInfoOffset0xc ;
    unsigned char HandleInfoOffset0xd;
    unsigned char QuotaInfoOffset0xe  ;
    unsigned char Flags0xf           ;

    union
    {
        _OBJECT_CREATE_INFORMATION *ObjectCreateInfo0x10;
        PVOID QuotaBlockCharged0x10 ;
    };

    PVOID SecurityDescriptor0x14 ;
    _QUAD Body0x18      ;  
};


Ok,agora que temos uma ideia de como os objetos ficam quando formados,vamos ver como eles começam e onde eles ficam.


CRIAÇÃO DE UM OBJETO --

O nascimento de um objeto vem da função "ObCreateObject", essa função é implementada no módulo ntoskrnl,e só pode ser usada em modo kernel.Uma hora ObCreateObject será chamada para criar um objeto. 
ObCreateObject serve para criar vários tipos de objeto,desde que seja passado seu tipo e tamanho para a função,este é seu pseudo código : 

NTSTATUS NTAPI  ObCreateObject(    
        KPROCESSOR_MODE PreviousMode OPTIONAL,
        _OBJECT_TYPE      *ObjectType,
       OBJECT_ATTRIBUTES * ObjectAttributes OPTIONAL,    
        KPROCESSOR_MODE      AccessMode,
        PVOID ParseContext      OPTIONAL,
        ULONG      ObjectSize,
        ULONG PagedPoolCharge      OPTIONAL,
        ULONG NonPagedPoolCharge      OPTIONAL,
        VOID ** object);   
 {

//PARTE 1
   _KPRCB *kprcb = (_KPRCB*)((_KPCR*)KIP0PCRADDRESS)->Prcb;
    kprcb->PPLookasideList[3].P->TotalAllocates += 1;
   
    SINGLE_LIST_ENTRY *lista_aux = InterlockedPopEntrySList(
        &kprcb->PPLookasideList[3].P->ListHead);
   
    kprcb->PPLookasideList[3].P->AllocateHits +=1;    

    kprcb->PPLookasideList[4].P->TotalAllocates +1;
       
    SINGLE_LIST_ENTRY *lista_aux2 = InterlockedPopEntrySList(
        &kprcb->PPLookasideList[4].P->ListHead);
 

    _OBJECT_CREATE_INFORMATION *pobmemoriainfo = (_OBJECT_CREATE_INFORMATION*)
        kprcb->PPLookasideList[4].P->Allocate(
        kprcb->PPLookasideList[4].P->Type,kprcb->PPLookasideList[4].P->Size,
        kprcb->PPLookasideList[4].P->Tag
        );


     if(lista_aux == NULL || lista_aux2 == NULL || pobmemoriainfo == NULL)
    {   
        return STATUS_INSUFFICIENT_RESOURCES;
    }


//PARTE 2
    UNICODE_STRING nome0x8;
     NTSTATUS status1 = ObpCaptureObjectCreateInformation(ObjectType,PreviousMode,AccessMode,
        ObjectAttributes,&nome0x8,pobmemoriainfo,0);
      

      if(status1 >= STATUS_SUCCESS || pobmemoriainfo->Attributes0x0 &
        ObjectType->TypeInfo0x60.InvalidAttributes0x4)
    {
ObCreateObject8b: 
        if(pobmemoriainfo->SecurityDescriptor0x1c != NULL)
        {
            _LUID_AND_ATTRIBUTES *pluid = (_LUID_AND_ATTRIBUTES*)pobmemoriainfo->SecurityDescriptor0x1c;
            SeReleaseLuidAndAttributesArray(pluid,pobmemoriainfo->ProbeMode0xc,TRUE);
            pobmemoriainfo->SecurityDescriptor0x1c = NULL;
        }

          if(nome0x8.Buffer != NULL)
        {
            ObpFreeObjectNameBuffer(nome0x8);
        }


        kprcb->PPLookasideList[3].P->TotalFrees += 1;
               if(kprcb->PPLookasideList[3].P->ListHead.Depth < kprcb->PPLookasideList[3].P->Depth)
        {
            RtlpInterlockedPushEntrySList(kprcb->PPLookasideList[3].P,pobmemoriainfo);
            return STATUS_INVALID_PARAMETER;
        }

        kprcb->PPLookasideList[3].P->FreeMisses += 1;

        if(kprcb->PPLookasideList[4].P->ListHead.Depth < kprcb->PPLookasideList[4].P->Depth)
        {
            RtlpInterlockedPushEntrySList(kprcb->PPLookasideList[3].P,pobmemoriainfo);
            return STATUS_INVALID_PARAMETER ;
        }

        kprcb->PPLookasideList[4].P->FreeMisses += 1;
        kprcb->PPLookasideList[4].P->Free(pobmemoriainfo);
        return  STATUS_INVALID_PARAMETER; 

    }

//PARTE 3
if(PagedPoolCharge == 0)
    {
        pobmemoriainfo->PagedPoolCharge0x10 = ObjectType->TypeInfo0x60.DefaultPagedPoolCharge0x24;
    }
  
    ULONG NonPagedPoolCharge_eax_aux;
    NonPagedPoolCharge_eax_aux = NonPagedPoolCharge;
    

    if(NonPagedPoolCharge == 0)
    {
        pobmemoriainfo->NonPagedPoolCharge0x14 = ObjectType->TypeInfo0x60.DefaultNonPagedPoolCharge0x28;
    }

    _OBJECT_HEADER *objetoheader_aux = (_OBJECT_HEADER*)&AccessMode;
   

//PARTE 4
    if(ObpAllocateObject(pobmemoriainfo,AccessMode,ObjectType,&nome0x8,ObjectSize,
        &objetoheader_aux) < STATUS_SUCCESS)
        goto ObCreateObject8b;//Faz a limpeza


      *object = (void *)&objetoheader_aux->Body0x18.UseThisFieldToCopy;

    status1 = STATUS_SUCCESS;
   

//PARTE 5
        if(objetoheader_aux->Flags0xf != 0x10 ||         SeSinglePrivilegeCheck(SeCreatePermanentPrivilege,PreviousMode) == FALSE)
    {
        ObpFreeObject(object);
        status1 = STATUS_PRIVILEGE_NOT_HELD;
    }

     if(ObpTraceEnabled != FALSE && status1 == STATUS_SUCCESS)  
    {
        ObpRegisterObject(objetoheader_aux); 

        ObpPushStackInfo(objeto_aux_edi,TRUE);
    }
     
    return status1;
}


Resumo ObCreateObject :
Parte 1 - "PPLookasideList"(do tipo PP_LOOKASIDE_LIST) é um array de 16 elementos,membro de KPRCB,que o windows usa para controlar as pools de memória,dando assim um layout de controle divido em 16 pedaços.
O membro "Allocate"(que é um ponteiro para a função "ExAllocatePoolWithTag")aloca memória para uma estrutura "OBJECT_CREATE_INFORMATION".


Parte2 - O que ObpCaptureObjectCreateInformation faz são inicializações de valores nas estruturas de "ObjectType" ,"ObjectAttributes" e "pobmemoriainfo".Se a função falha então é feita uma limpeza para o que já foi alocado até agora,faz atualizações nas estruturas usadas para controle das pools
e retorna.

Parte 3 - Com todos os testes feitos,agora é verificado se o objeto requer algum recurso extra nas pools passado como argumento,se não,então a pool padrão é usada,essas informações serão usadas
por "ObpAllocateObject" para onde alocar o objeto em si.

Parte 4 - ObpAllocateObject aloca memória para o objeto mais o OBJECT_HEADER.  
O ponteiro para o objeto,até então vazio,passado como argumento para "ObCreateObject" é setado para apontar para o corpo do objeto.


Parte 5 - Faz checagens de privilégio dado o modo de acesso.


Funções "CreateXxx" chamam "ObCreateObject",e depois chamam "ObInsertObject" para essa sim chamar "ObpCreateUnnamedHandle" para criar o handle e colocar uma entrada na tabela de objetos apontando para o objeto.Se o objeto já foi criado então "ObInsertObject" retorna sem fazer nada.

ACESSO AOS OBJETOS E HANDLES --

Agora que o objeto foi criado,é preciso ter algum modo de referenciá-lo depois.

Os objetos criados para um processo são referenciados por uma tabela única de cada processo,especificamente no membro "ObjectTable" do objeto EPROCESS.O tamanho dessa tabela pode variar de versão para versão do windows nt.
"ObpKernelHandleTable",é uma outra tabela usada para objetos que pertencem ao kernel,por exemplo, objetos criados que só serão usados em modo kernel(que um driver criaria).Ela na verdade aponta para a "ObjectTable" do processo System.

O Handle agora é nescessário,ele servirá como índice nessa tabela."ObjectTable" é do tipo "_HANDLE_TABLE" que é uma estrutura assim :

struct _HANDLE_TABLE  //Representa uma tabela de objetos
{
  unsigned int TableCode0 ;
    _EPROCESS *QuotaProcess0x4;
     PVOID UniqueProcessId0x8  ;               
   
   _EX_PUSH_LOCK HandleTableLock0xc[4]  ;  
   _LIST_ENTRY HandleTableList0x1c  ;
   _EX_PUSH_LOCK HandleContentionEvent0x24;
   _HANDLE_TRACE_DEBUG_INFO *DebugInfo0x28;       
   int ExtraInfoPages0x2c ;
   unsigned int FirstFree0x30    ;  
   unsigned int LastFree0x34     ;  
   unsigned int NextHandleNeedingPool0x38;
   unsigned int HandleCount0x3c ;            

   union
   {
   unsigned  Flags0x40   ;
   unsigned  short StrictFIFO0x40 :1;
   };
};

Veja que "ObjectTable" é uma instância do tipo "_HANDLE_TABLE",e não a tabela em si ainda. O que "_HANDLE_TABLE" tem,é um membro chamado "TableCode" e este sim tem o endereço da tabela de objetos.Essa tabela é composta por entradas do tipo "_HANDLE_TABLE_ENTRY",e o membro "Object" dessa  estrutura é quem tem o endereço do OBJECT_HEADER do objeto em si :

struct _HANDLE_TABLE_ENTRY  //Representa uma entrada na tabela de objetos
{
    union
    {
        _OBJECT_HEADER *Object0  ;                 
        unsigned int ObAttributes0     ;
        _HANDLE_TABLE_ENTRY_INFO * InfoTable0;  
        unsigned int Value0    ;
    };

    union
    {
    unsigned int GrantedAccess4           ;
    unsigned short GrantedAccessIndex4 ;
    int NextFreeTableEntry4;
    };

    unsigned short  CreatorBackTraceIndex6;
};
 

A tabela de objetos na verdade é dividida em 3 níveis.O membro "TableCode" é quem diz em qual nível da tabela estamos assim como seu endereço.
Bem isso quer dizer que se é preciso um membro para dizer qual nível está a tabela atualmente,então é porque essa tabela muda por alguma razão."Nível" nesse caso quer dizer quantidade de memória.O windows não aloca memória para a quantidade máxima de objetos por processo de uma vez.

Dessa forma a tabela de último nível cresce conforme a quantidade de objetos nescessários para serem referenciados por ela também cresce.
Somente a tabela do último nível é que tem entradas para _HANDLE_TABLE_ENTRY,os outros níveis da tabela são na verdade referências para a tabela de próximo nível,até que no fim mapeiem para a tabela de último nível.
    
Um algoritmo para quebrar os valores dos handles é usado como entradas dependendo do nível.Os 2 bits mais baixos dos handles são descartados sempre qualquer que seja o nível da tabela.
Um algoritmo para saber o endereço e em qual nível da tabela estamos é usado também.
Aqui está um pseudo código que mostra o algoritmo :


//Descarta todos os bits exceto os 2 mais baixos
nivel_tabela = ObjectTable.TableCode0 & 3;
//Faz o contrário do que foi feito acima
end_TabelaObjetos = ObjectTable.TableCode0 & 0xFFFFFFFC ;

//Faz um shift para direita por 2 com o valor do handle descartando os 2 bits mais baixos 
 handle = handle>>2;
handle_aux = handle;

//Estamos no 1º nível,então já faz a entrada direto
if(nivel_tabela == 0)

  //multiplica o valor no handle pelo tamanho de cada entrada
 _HANDLE_TABLE_ENTRY hob= end_TabelaObjetos + (handle_aux * sizeof(_HANDLE_TABLE_ENTRY))
 
  end_objeto = hob.Object->Body;
}
else if(nivel_tabela == 1)
{
  //Usa o valor do handle à partir dos 11 bits mais baixos no handle como entrada na tabela de nível 1
   handle_aux = handle_aux>>9;
  //Pega um endereço da tabela do próximo nível
   tabela1= end_TabelaObjetos + handle * 4 ;
 
 //Usa os 9 bits mais baixos do handle original como entrada  na tabela de último nível
  handle = handle & 0x1FF;
 //Acessa uma entrada na tabela de último nível
 _HANDLE_TABLE_ENTRY hob = tabela1 + handle *8;
  end_objeto = hob.Object->Body
}
else if((nivel_tabela == 2)
{
  //Usa o valor à partir dos 20 bits mais baixos no handle como entrada na tabela de 2º nível
   handle_aux  = handle_aux >>18d;
  //Acessa uma entrada e pega um endereço da próxima tabela
  tabela2 = end_TabelaObjetos + handle_aux * 4
  
//Usa o valor do handle à partir dos 11 bits mais baixos no handle como entrada na tabela de nível 1  

 handle_aux = handle >> 9;  
tabela1 = tabela2 + handle_aux * 4
 //Acessa uma entrada na última tabela  
_HANDLE_TABLE_ENTRY hob= tabela1 + (handle * sizeof(_HANDLE_TABLE_ENTRY))
 
  end_objeto = hob.Object->Body;
}


Para acessar uma entrada no nível atual,um pedaço do valor do handle é usado,(como mostrado no algoritmo).O que acontece é que se a tabela de último nível cresceu,então é preciso saber à partir de qual deslocamento se deve somar com o valor do handle para conseguir a entrada desejada.

OBSERVAÇÃO
1 - O que foi dito no começo do artigo sobre "nem todos os objetos serem referenciados nessa tabela" é porque objetos process e thread são acessados de uma outra maneira,mais rápida.
 

Veja que os valores dos handles de um processo geralmente são 0xFFFFFFFF,interprentando isso como um inteiro sinalizado seria -1,ou como um inteiro sem sinal seria mais de 4bilhões,o que torna impossível tal índice como entrada na tabela de objetos.
Como é sabido,o windows na verdade não roda processos,e sim threads.Cada thread é representada por KTHREAD e ETHREAD.Quando chega a vez de uma thread rodar,o sistema já vai estar ciente de qual thread(ou seja,de qual objeto thread)e de qual processo(objeto process)está sendo executado no momento,não há nescessidade então de procurar numa tabela por esses objetos,até porque a "ObjectTable" é apontada pela própria EPROCESS,o que tornaria nescessário antes conhecer o objeto process para então acessar a tabela.
Uma thread pode saber à qual processo pertence através do membro "Process" que fica em seu membro "ApcState"(tipo KAPC_STATE).
O objeto thread atual é sabido através do membro "CurrentThread" que fica na estrutura KPRCB(essa estrutura é única para cada núcleo do processador,ela é inicializada em tempo de boot).