Archive for August, 2007

Usando FileObject e FsContext

29 de August de 2007

Muito bem, com alguns posts e um pouco de paciência para agüentar minhas piadinhas, podemos construir um simples driver que responda às chamadas de aplicações. A chamada à função CreateFile cria uma conexão entre a aplicação e o device e nos retorna um handle, que mais tarde será utilizado para encaminhar solicitaçoes de leitura e/ou escrita ao device. Se houverem mais chamadas ao CreateFile, outros handles serão retornados. Como com arquivos, cada handle possui seu próprio contexto. Suponha que você abra duas vezes o mesmo arquivo, obtendo assim dois handles com contextos diferentes. Uma leitura de 100 bytes a partir do primeiro handle faz com que a posição atual deste arquivo agora seja diferente da posição inicial. Uma nova leitura utilizando este mesmo handle resultaria nos próximos 100 bytes adiante dos bytes já lidos, mas se você utilizar o segundo handle, você obteria novamente os 100 primeiros bytes do arquivo. Neste post vou falar como manter este contexto entre os vários handles abertos para o seu device.

O problema na prática

Há pouco tempo atrás, em um dos meus posts, dei um exemplo básico de driver que implementa uma lista ligada de buffers que foram escritos no device. Os mesmos buffers são obtidos em leituras posteriores ao mesmo device. Este post vai se basear nesse driver de exemplo para fazer os testes e modificações sugeridas. A lista desse driver foi implementada como uma única variável global. Se existe uma única lista, os diversos handles retornados às aplicações manipularão a mesma lista.

Lembre-se que o programa de teste escreve as strings digitadas no console no device até que uma string vazia seja fornecida. Quando isso ocorre, a aplicação passa a realizar leituras e exibir o resultado na tela. Assim, a mesma seqüência de strings fornecidas deveria ser exibida. Para reproduzir o problema, siga os passos abaixo com o driver já instalado.

  1. Abra uma janela de Prompt e execute uma instância do programa de teste.
  2. Entre com a seqüência de strings de “111”, “222”, até “555”, mas ainda não entre com a string vazia.
  3. Abra uma nova janela de Prompt e execute uma outra instância do programa de teste.
  4. Nesta, entre com uma seqüência de strings de “666” até “999” e em seguida uma string vazia.
  5. Volte ao Prompt anterior e entre com a string vazia na primeira instância do programa de teste.

Devemos obter uma saída como mostra a figura abaixo. Note que a primeira instância do programa de teste não leu nenhuma string apesar de ter entrado com várias. As strings faltantes foram parar na segunda instância do programa.

Separando as listas

Criar um algoritmo que separe as listas por processo até funcionaria, mas tente imaginar que uma mesma aplicação utilize duas bibliotecas distintas, que por sua vez utilizam as listas do driver. Desta forma, a lista de uma biblioteca se misturaria com a lista de outra, já que ambas as bibliotecas estão no mesmo processo. Para separar o contexto das várias aberturas do device utilizamos o membro FileObject da stack location atual.

Quando uma aplicação chama a função CreateFile, esta requisição vai para o ObjectManager que verifica a existência do objeto desejado, em seguida, verifica se a aplicação tem direitos para obter um handle para este objeto, e se tudo estiver de acordo, a solicitação chega até seu driver em forma de uma IRP com o MajorFunction igual a IRP_MJ_CREATE. Para obter FileObject referente a esta conexão que está sendo criada, você precisa fazer como mostra o código abaixo.

    //-f--> Recupera o FileObject referente a esta lista
    pStack = IoGetCurrentIrpStackLocation(pIrp);
    pFileObject = pStack->FileObject;

Essa ou aquela lista?

Todas as operações que vierem da mesma instância do objeto, virão com o mesmo FileObject. Isso ajuda a rastrear o contexto de uma entre as várias solicitações de aberturas que chegarão ao seu driver.

Então é só criar uma lista de FileObjects e vincular cada um a uma lista? A idéia de vincular o FileObject a uma estrutura que carregue este contexto é tão óbvia que o sistema já reservou um espaço na estrutura FILE_OBJECT para que você coloque os dados referentes a este contexto.

typedef struct _FILE_OBJECT
{
    CSHORT  Type;
    CSHORT  Size;
    PDEVICE_OBJECT  DeviceObject;
    PVPB  Vpb;
    PVOID  FsContext;
    PVOID  FsContext2;
    PSECTION_OBJECT_POINTERS  SectionObjectPointer;
    PVOID  PrivateCacheMap;
    NTSTATUS  FinalStatus;
    struct _FILE_OBJECT  *RelatedFileObject;
    BOOLEAN  LockOperation;
    BOOLEAN  DeletePending;
    BOOLEAN  ReadAccess;
    BOOLEAN  WriteAccess;
    BOOLEAN  DeleteAccess;
    BOOLEAN  SharedRead;
    BOOLEAN  SharedWrite;
    BOOLEAN  SharedDelete;
    ULONG  Flags;
    UNICODE_STRING  FileName;
    LARGE_INTEGER  CurrentByteOffset;
    ULONG  Waiters;
    ULONG  Busy;
    PVOID  LastLock;
    KEVENT  Lock;
    KEVENT  Event;
    PIO_COMPLETION_CONTEXT  CompletionContext;
    KSPIN_LOCK  IrpListLock;
    LIST_ENTRY  IrpList;
    PVOID  FileObjectExtension;
 
} FILE_OBJECT, *PFILE_OBJECT;

Os campos FsContext e FsContext2 são utilizados para este fim, a menos que você implemente um filtro, você pode utilizá-los à vontade. Para fazer com que cada abertura ao device tenha sua própria lista, vamos armazenar o endereço da ponta de nossa lista em um destes campos como é mostrado abaixo na nova implementação do OnCreate. Todo o código fonte alterado está disponível para download ao final deste post.

/****
***     OnCreate
**
**      Esta rotina é chamada quando uma aplicação ou driver
**      tenta obter um handle para o device que criamos.
*/
 
NTSTATUS
OnCreate(IN PDEVICE_OBJECT  pDeviceObj,
         IN PIRP            pIrp)
{
    NTSTATUS            nts = STATUS_SUCCESS;
    PBUFFER_LIST_HEAD   pListHead;
    PIO_STACK_LOCATION  pStack;
 
    //-f--> Um Olá para o depurador...
    KdPrint(("Opening EchoDevice...\n"));
 
    //-f--> Aloca a ponta da lista para este IRP_MJ_CREATE
    pListHead = (PBUFFER_LIST_HEAD) ExAllocatePoolWithTag(
        PagedPool,
        sizeof(BUFFER_LIST_HEAD),
        ECHO_TAG);
 
    if (pListHead)
    {
        //-f--> Inicializando a lista de buffers e mutex
        InitializeListHead(&pListHead->BufferList);
        KeInitializeMutex(&pListHead->Mutex, 0);
 
        //-f--> Armazenamos nosso contexto no FileObject.
        pStack = IoGetCurrentIrpStackLocation(pIrp);
        pStack->FileObject->FsContext = pListHead;
    }
    else
        nts = STATUS_INSUFFICIENT_RESOURCES;
 
    //-f--> Completando a IRP com sucesso.
    pIrp->IoStatus.Status = nts;
    pIrp->IoStatus.Information = 0;
    IoCompleteRequest(pIrp, IO_NO_INCREMENT);
    return nts;
}

FileObject é equivalente a um Handle?

Não. Inicialmente temos um handle para cada FileObject, mas a quantidade de handles para um FileObject aumenta à medida que duplicamos os handles. Quando a função CreateFile é chamada, seu driver recebe uma IRP_MJ_CREATE, mas seu driver não é avisado quando alguém chama a função DuplicateHandle. Assim, cada handle aponta para um objeto, mas um objeto pode ser apontado por vários handles.

Quem vai limpar esta bagunça?

Quando todas as referências a um determinado FileObject forem liberadas, seu device receberá um IRP_MJ_CLOSE. Vamos utilizar este evento para efetuar a limpeza de qualquer string que ainda estiver na lista.

/****
***     OnClose
**
**      Vou simplificar dizendo que esta rotina é chamada quando
**      a aplicação ou driver fecha o handle que foi previamente
**      aberto. Mas no fundo não é isso (fica pra um outro post).
*/
 
NTSTATUS
OnClose(IN PDEVICE_OBJECT  pDeviceObj,
        IN PIRP            pIrp)
{
    PBUFFER_LIST_HEAD   pListHead;
    PIO_STACK_LOCATION  pStack;
    PLIST_ENTRY         pEntry;
    PBUFFER_ENTRY       pBufferEntry;
 
    //-f--> Um olá para o depurador
    KdPrint(("Closing EchoDevice...\n"));
 
    //-f--> Recupera a lista referente a este FileObject
    pStack = IoGetCurrentIrpStackLocation(pIrp);
    pListHead = (PBUFFER_LIST_HEAD)pStack->FileObject->FsContext;
 
    //-f--> Aqui removemos todos os nós que ainda não foram lidos
    //      pela aplicação. Isso aconteceria se a aplicação chamasse
    //      WriteFile e não chamasse ReadFile.
    while(!IsListEmpty(&pListHead->BufferList))
    {
        //-f--> Pega o primeiro nó da lista
 
        pEntry = RemoveHeadList(&pListHead->BufferList);
 
        //-f--> Obtém o endereço a partir do nó
        pBufferEntry = CONTAINING_RECORD(pEntry, BUFFER_ENTRY, Entry);
 
        //-f--> Finalmente libera a memória utilizada por
        //      este nó
        ExFreePool(pBufferEntry);
    }
 
    //-f--> Libera a memória utilizada pela ponta da lista
 
    ExFreePool(pListHead);
 
    //-f--> Completando a IRP com sucesso.
    pIrp->IoStatus.Status = STATUS_SUCCESS;
    pIrp->IoStatus.Information = 0;
    IoCompleteRequest(pIrp, IO_NO_INCREMENT);
    return STATUS_SUCCESS;
}

Quando uma aplicação é terminada ou mesmo quando ela cai sem sua vontade, todos os handles desta aplicação são fechados pelo sistema. Desta forma, é garantido que seu driver sempre receberá o IRP_MJ_CLOSE reference ao objeto que foi liberado. Na verdade existe uma novelinha a respeito do IRP_MJ_CLEANUP e IRP_MJ_CLOSE que vai ficar para uma próxima vez.

Depois de implementada a alteração, cada handle aberto terá sua própria lista, a menos que o handle seja duplicado. Se repetirmos a mesma seqüência de passos enumerados para forçar o erro, teremos a seguinte saída.


Em um post futuro, mostrarei como dar nomes às listas de forma a permitir que processos distintos possam abrir a mesma lista a partir de um nome conhecido.

Até mais! 🙂

FileObjEcho.zip

Debug ou falha de segurança?

21 de August de 2007

A um tempo atrás, escrevi um post que fala sobre como substituir drivers defeituosos utilizando o WinDbg. Uma técnica excelente para substituir drivers, que por algum problema, impedem a máquina de iniciar. Mas para conseguir desfrutar de toda essa facilidade, é necessário que haja uma conexão do sistema com o Kernel Debugging. Isso nos leva ao meu último post, que fala justamente sobre como habilitar este recurso bastando apenas estar em frente à máquina, e no pior caso, ter uma porta serial. Tudo isso é muito cômodo, afinal, nunca sabemos onde um problema desconhecido pode acontecer, e muito menos, em qual máquina vai acontecer. Mas essa praticidade toda tem um preço. O efeito colateral disso é que qualquer um pode depurar o Kernel de uma máquina sem ao menos ter qualquer conta válida nela. Na verdade, um notebook, um cabo e uma porta serial já são armas mais que suficientes para desmontar a sengurança do sistema. Sem senha, sem cabo de rede, sem brute-force. Apenas Kernel Debugging.

Instalando um driver pela serial

Drivers são um tipo de software que precisa ser confiável e muito bem escrito. Afinal de contas, eles rodam no nível mais baixo do sistema. Com drivers fica realmente muito fácil enganar o sistema, já que boa parte do sistema roda sobre serviço oferecidos pelo Kernel. Os posts acima me motivaram a escrever uma pequena prova de conceito que tem por finalidade injetar um driver que se instala no sistema, este driver, por sua vez, extrai e instala um programa no Windows que será executado a cada logon no sistema. Para tanto, precisaremos apenas de uma máquina que possa ser depurada. Meu último post mostra que essa condição não é tão difícil de ser encontrada. Quer testar? Qual a percentagem de computadores à sua volta que possuem portas seriais?

Passo a passo

Este é mais um daqueles posts que vêm com código disponível para download. Todo o procedimento demonstrado aqui pode ser reproduzido e modificado pelos leitores deste blog. Este não é um blog especializado em ataques ao Windows e meu objetivo aqui é puramente didático. Não vou explicar cada linha do código de exemplo, caso contrário, esse post ficaria muito extenso. Entretanto, me disponho a responder quaisquer dúvidas a respeito. A figura abaixo enumera os passos a serem tomados para atingir nosso objetivo.

  1. O primeiro passo acontece durante a compilação do driver de exemplo. O solution disponível possui três projetos. O primeiro projeto é o da aplicação a ser injetada. Neste caso, uma aplicação Win32 que apenas mostra uma mensagem e termina. Vamos nos referir a esta aplicação como MyFile.exe. A Run Time do C/C++ foi removida para obtermos a menor aplicação possível. Isso vai contar bastante quando você tiver que transferir tudo por um cabo serial a 19200. O segundo projeto é apenas uma ferramenta. A partir do arquivo MyFile.exe já compilado, esta ferramenta gera um arquivo de header que cria uma variável do tipo array de caracteres com o corpo do arquivo passado como linha de comando. Este não é o método mais avançado de se fazer um Self-Extractor, mas é suficiente para nosso objetivo aqui. Vamos chamar esta ferramenta de HeaderFile.exe. Quando o driver é compilado, este inclui o arquivo de header e ganha uma variável com a aplicação a ser injetada. Vamos chamar nosso driver de MapInject.sys. O driver neste exemplo é compilado utilizando o ddkbuild.cmd com Visual Studio 2005. Mais detalhes a respeito neste post.

    /****
    ***     WinMain
    **
    **      Não faz quase nada e não usa a Run Time do C/C++
    **      para evitar código longo.
    */
     
    int WINAPI WinMain(HINSTANCE   hInstance,
                       HINSTANCE   hPrevInstance,
                       LPSTR       lpCmdLine,
                       int         nShowCmd)
    {
        //-f--> É só isso mesmo...
        MessageBox(NULL,
                   L"A serial port was found",
                   L"DriverEntry.com.br",
                   MB_OK | MB_ICONEXCLAMATION);
     
        return 0;
    }
  2. Utilizando as técnicas de mapeamento de drivers demostradas neste post, podemos fazer com que nosso driver possa ser carregado no lugar de qualquer driver pré-existente no sistema. Obviamente nosso driver não vai realizar todos os passos que o driver sobrescrito deveria realizar, deste modo, não vamos escolher um driver que realize tarefas essenciais à carga do sistema, tais como drivers de disco, de vídeo ou algo assim. Particularmente escolhi utilizar o driver da porta paralela Partport.sys, que é um driver que existirá em qualquer instalação básica de Windows e não vai nos fazer tanta falta enquanto brincamos por aqui. Este também não é nenhum driver do tipo Boot, que no caso do Windows XP, seria necessário ter um Loader especial para poder ser mapeado pelo Debugger.

    map 
    \Windows\System32\drivers\parport.sys 
    Z:\MapInject\Driver\objfre_wnet_x86\i386\MapInject.sys
  3. Com o driver mapeado e carregado pelo sistema, basta um pouco de boa vontade para poder pintar e bordar com o sistema. Quando nosso driver é carregado pela primeira vez, este é carregado no lugar do Parport.sys. Nesta condição, nos instalamos no sistema. Certas coisas nunca mudam. Para instalar um driver no Windows plataforma NT basta criar uma chave e poucos valores no registro do sistema como descrito neste post. Isso ainda funciona no Windows Vista. Nosso driver faz isso neste passo.

  4. Feitas as modificações no registro, agora temos que renomear nosso driver, que pelo fato de ter sido mapeado, ganhou o nome do driver nativo do sistema. Assim, uma função troca o nome do driver de Parport.sys para MapInject.sys. Poderiamos permanecer instalados no sistema da maneira que fomos mapeados se quizéssemos, mas perderiamos nosso lugar caso o verdadeiro driver fosse restaurado.

  5. Como sabem, nosso driver possui uma variável que contém todo o corpo de nossa aplicação. Assim, fica muito simples extrair a aplicação e salvá-la em disco. A pasta System32 vem bem a calhar como destino para nossa aplicação. Como estamos rodando em conta de sistema neste momento, não teremos problemas em fazer isso.

  6. Agora que nossa aplicação foi extraída, temos que fazer com que ela seja executada automagicamente. Alguns de vocês devem saber que não existem meios documentados ou mesmo suficientemente confiáveis de se criar um processo a partir do Kernel, mas no registro existem vários lugares onde poderíamos escrever e fazer com que um processo já existente no Windows execute a nossa aplicação. Uma das mais clássicas chaves do registro que permite isso é a Run do Windows Explorer, mas como mostra este aplicativo, ainda existem outras tantas chaves a serem escolhidas. Quando o Logon é realizando no sistema, o Shell enumera esta chave e executa cada linha na conta do usuário que fez o logon. Isso vai fazer nosso programa ser chamado.

Colocando para funcionar

Mais uma vez, uma máquina virtual nos cai como uma luva para este tipo de brincadeira. Se você não sabe como fazer Kernel Debugging utilizando uma máquina vitual, este post poderá te ajudar. Tudo é muito simples depois que o driver já está codificado. A única coisa necessária para realizar o teste é configurar um mapeamento que substitua o driver Parport.sys pelo MapInject.sys como mostra o arquivo de map acima. Depois disso, é só esperar o sistema subir e pronto. Qualquer usuário que efetuar o logon no sistema verá a mensagem do nosso programa. Um ponto a ser notado é que mesmo se o usuário for esperto o suficiente para encontrar o arquivo MyFile.exe, sua chamada no registro e apague ambos, os mesmos serão recriados quando o driver MapInject for iniciado a cada Boot do sistema. Para interromper esse processo, basta remover o driver MapInject do sistema.

Senha? Pra quê?

O Windows protege seus mais importantes recursos da maioria dos usuários, permitindo apenas que administradores da máquina tenham acesso às configurações que podem comprometer a estabilidade do sistema. Essa frase até que é “bunitinha” e chega até a gerar um certo conforto aos adminitradores de muitas e muitas máquinas. Mas a vida é uma caixinha de surpresas, e numa bela manhã de sol, um post é publicado em um site de RootKits dando dicas de como logar em um sistema em Debug sem qualquer senha. Recentemente, um outro post surgiu a respeito e não vou repetir tudo aqui. Mas posso afirmar que esta mesma técnica ainda funciona com o Windows Vista. De fato, se você puder logar no sistema sem utilizar qualquer senha, não será necessário mapear um driver para fazê-lo rodar nesta máquina. Basta logar como administrador, instalá-lo e pronto. Mas confesso que foi no mínimo divertido escrever esta prova de conceito, e além do mais, os fontes ainda poderão servir de exemplo para algumas tarefas que você queira fazer em Kernel Mode.

Uma possível solução? Talvez deixar sua porta serial desabilidata na BIOS, que deveria estar protegida com senha, seja alguma coisa a ser feita. Mas não há muito que se possa fazer quando se tem acesso físico à maquina.

Chega, já escrevi demais.
[]s.

MapInject.zip

Debug de Emergência

15 de August de 2007

De repente tela azul. Isso mesmo, aquele seu driver que você já não via o fonte a muito tempo, que era o seu modelo de driver estável, quando foi instalado na máquina do presidente da empresa que você trabalha, resolveu se vingar e dar uma tela azul logo na inicialização do sistema. Ou seja, a máquina liga e reseta e reseta e reseta em um loop infinito que roda em paralelo com a impressão da sua carta de demissão, que por sinal, está sendo impressa em outro micro. Para depurar essa máquina e ver o que está acontecendo, e assim salvar seu projeto, seu emprego, sua dignidade, seu casamento, e por que não dizer, sua vida, você deve configurar seu sistema para entrar em Modo Debug para que o nosso amigo inseparável WinDbg possa entrar em ação. Mas como configurar para Debug uma máquina que não está nem iniciando? Hoje vou falar sobre o suporte que o Windows oferece para evitar esse tipo de situação constrangedora. Como conectar um Debugger em um micro que nunca teve suas configurações de Kernel Debugging habilitadas, seja pelo BOOT.INI ou pelo BdcEdit.

Este post admite que você já sabe como fazer Kernel Debugging e utiliza alguns termos próprios desta prática. A partir do Windows 2000, se você pressionar a tecla F8 enquanto máquina está rodando o NT Loader, mais precisamente, 2 segundos antes de o sistema começar a ser carregado, o sistema paraliza e exibe o menu como mostra abaixo.

Selecione o ítem “Debugging Mode”, isso nos vai levar à lista de sistemas operacionais instalados. Selecione o sistema e o resto fica por conta do WinDbg. Nessas condições, a configuração da conexão serial aplicada seria um Baud rate de 19200 na porta serial mais alta existente em sua máquina. Isto é, se seu micro tem duas portas seriais, então a conexão seria feita a 19200 na COM2. Pelo menos isso é o que a referência nos diz.

Mas na vida real…

Se você fizer alguns testes, também vai concluir que não podemos acreditar em tudo que lemos. Tanto o Windows 2000 como o Windows XP, utilizam sempre a porta COM2 a 19200.

Meu computador é moderno e só tem uma porta serial. Posso me matar? Pois é, mesmo nesses casos, o sistema tenta utilizar a porta COM2, mesmo que ela não exista. Mas não se preocupe, a vida é bela e ainda há uma esperança. Algumas BIOS permitem que você configure o endereço base e a interrupção da única porta serial existente. Isso vai nos ajudar muito, afinal de contas, o NT Loader não é uma aplicação Win32 que solicita um handle para o device COM1 ou COM2 através do Object Manager. Ele simplesmente segue os endereços adotados como padrões.

  • COM1: Address 3F8h, IRQ 4
  • COM2: Address 2F8h, IRQ 3

Desta forma, você poderá configurar a única porta (a COM1) para responder pelos endereços que normalmente são utilizados pela COM2. A BIOS normalmente tem uma lista dos recursos normalmente utilizados pelas portas seriais. Assim, qualquer chinpanzé autista poderia fazer essa troca.

Mas meu computador é mais moderno ainda e não tem nenhuma porta serial. Posso me matar agora? A esperança é a última que morre. Você pode ainda instalar uma placa expansora PCI (ou PCMCIA em caso de notebooks) que nos forneça as portas seriais de que precisamos. É importante notar que esta placa expansora deve criar as portas COM1 e COM2 nos endereços padrões acima descritos.

Você não está entendendo, sou um lazarento que comprou um MacBook 13″, que além de não ter portas seriais, também não oferece nenhum slot PCMCIA ou ExpressCard. Posso me matar agora? Como já comentei em outro post, portas seriais criadas a partir de portas USB não são uma opção para serem utilizadas em uma máquina TARGET durante o Kernel Debugging. Esta conexão é feita pelo NT Loader e as portas seriais que ele conhece são apenas aquelas que respondem pelos endereços acima citados. Agora sim, pode se matar.

Alguma melhoria em Vista?

Já o Windows Vista, este sim ignora a documentação sem dó. A configuração padrão para Kernel Debugging quando pressionamos F8 durante o Boot do Windows Vista é utilizar a porta COM1 a 115200. Mas se você utilizou o aplicativo BcdEdit com o parâmetro /DbgSettings para modificar as configurações de conexão para Kernel Debugging, então serão estas mesmas configurações a serem utilizadas quando F8 for pressionado. Ou seja, se você é um lazarento que, como eu, configurou a porta IEEE 1397 para fazer Debug em seu MacBook, então estas serão as configurações utilizadas quando você pressionar F8.

Pode parecer brincadeira, mas é realmente frustrante ter que ir até à máquina do cliente onde seu driver está dando trabalho, armado dos seus vários aparatos tecnológicos, tais como notebook, porta FireWire, cabo serial e assim por diante, e não poder fazer nada simplesmente porque você não consegue conectar o depurador.

Mais uma vez, espero ter ajudado.
Até mais!

Personal Gina Tabajara

8 de August de 2007

Em conseqüência à volta às aulas na faculdade, meu tempo para escrever posts já diminuiu, e mais uma vez, vocês vão ter que tolerar um post que não fala nada sobre drivers. Na verdade, foi enquanto eu escrevia um post sobre drivers que escrevi esta Gina de exemplo. Mas depois de ouvir meus amigos, Lesma e Thiago, falarem que eu deveria deixar esse tal post de forma pudesse ser aplicado ao Windows Vista. Sabendo que ginas não são mais suportadas pelo Windows Vista, então a gina acabou ficando de lado. Tadinha… Enfim, como achei que o resultado ficou no mínimo divertido, vou deixar aqui esta Gina Stub (incluindo os fontes) que permite que mudemos o título dos diálogos apresentados.

O que é uma Gina Stub?

Seria melhor ainda dizer o que é Gina. Já escrevi algumas poucas coisas a respeito de Ginas no post que fala como utilizar o SoftIce, mas de forma resumida, Gina é o compomente do sistema que implementa a interface gráfica para a autenticação dos usuários em uma estação. Quer mais resumido ainda? É a telinha de logon do sistema. A gina é responsável por receber os dados que identificam o usuário e os repassam para os componentes que podem validar a senha e gerar o token com as credenciais deste usuário. É a partir deste token que é criada a sessão onde o usuário está se logando e onde será criado seu desktop. A gina também implementa a interface que faz a troca de senha, bloqueio e o shutdown da estação. Não vou detalhar todos estes passos aqui, tudo está explicado na documentação do Platform SDK.

A gina original do Windows está implementada em na DLL de nome MsGina.dll que está no diretório System32 do Windows. Para implementar uma nova gina, você precisa criar uma nova DLL e informar ao sistema que esta DLL será a nova gina através de uma chave no registro. Mas criar uma gina não é algo tão simples assim. Já desenvolvi algumas e digamos que a documentação deixou um pouco a desejar. Pelo menos foi assim naquela época. A gina tem muitas responsabilidades e se você quer apenas complementar ou alterar algum comportamento da gina original, você poderia simplesmente criar uma gina stub. Gina Stub é uma DLL que exporta todas funções que uma gina deveria exportar, mas esta repassa as chamadas para a gina original do sistema, dando assim a opção que alterar apenas as funcionalidades desejadas.

Isso não é um tutorial

Se você quer saber como desenvolver uma gina stub e precisa de um ponto de partida, então vá até à pasta de exemplos do Platform SDK e utilize o exemplo da pasta C:\MSSDK\Samples\Security\GINA\GinaStub. O projeto que estou deixando aqui realiza alguns malabarismos para evitar código muito repetitivo e também não utiliza a C/C++ Run Time para poder ser compilada em Visual Studio 2005 e ainda assim poder rodar em Windows NT 4.0.

Instalando a Gina

Para cadastrar uma gina, você deve criar um valor chamado GinaDLL na chave Winlogon do registro, conforme mostra a figura abaixo. Esse valor é consultado pelo Winlogon.exe e caso este valor não exista, a gina padrão é carregada. O valor GinaTitle deve conter a mensagem que será exibida no título dos diálogos. Este valor não tem nada a ver com o Windows, é a nossa gina stub quem lê este valor. Junto dos arquivos fontes disponíveis para download no final deste post, existe um arquivo de script do registro que configura estas chaves para facilitar a sua vida.


Copie o arquivo Gina.dll para o diretório System32 do Windows. Certifique-se que de tudo está certo antes de reiniciar a máquina e fazer com que estas alterações tenham efeito. Caso algo esteja errado e o Winlogon.exe não consiga carregar a gina, a janela abaixo é exibida antes de qualquer coisa.


Até que o design deste MessageBox melhorou bastante a partir do Windows 2000. Se o mesmo problema acontecesse com o Windows NT 4.0, a seguinte mensagem seria exibida.

Duas dicas úteis para gina coders

Escrever ginas é a oportunidade que programadores User-Mode têm de gerar suas próprias telas azuis. Sua DLL é carregada pelo Winlogon.exe, e assim, é executada no address space deste processo. Isso significa que se você tiver uma excessão não tratada, isso vai derrubar este processo. O Winlogon é um processo crítico e não pode ser derrubado. Resumindo, tela azul.

A próxima dica é meio café com leite, mas vale a pena ser comentada. Durante o processo de desenvolvimento da gina, é natural ter vários builds e a necessidade se substituir a gina que está sendo utilizada no momento sempre aparece. Você já deve ter tentado sobrescrevê-la, mas como o Winlogon.exe está sempre com ela carregada, você não consegue apagar a gina atual. Como qualquer DLL nestas condições, você pode renomeá-la mesmo enquanto está sendo utilizada pelo Winlogon. Isso permite colocar uma nova versão da gina no diretório System32 sem ter que apagar a versão que está sendo executada no momento. Quando o sistema reiniciar, o Winlogon vai pegar a gina nova e largar a velha.

Espero que tenham gostado do brinquedinho. Agora preciso continuar aquele post.
Have fun!

TitleGina.zip