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.
sexta-feira, 15 de maio de 2015
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).
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).
Assinar:
Postagens (Atom)