Desenvolvimento

18 fev, 2013

Depuração orientada a exceções: ficando por dentro dos macetes anti-depuração

Publicidade

É claro que toda depuração é orientada a exceções. Até porque um breakpoint gera exceção de depuração, o que é passado para o depurador. Neste artigo, no entanto, vou me referir às exceções regulares.

Existem dezenas, se não centenas, de protetores de software utilizados por fornecedores de software em todo o mundo. Alguns são bons, outros menos, mas em ambos os casos os vendedores raramente os usam de forma adequada, pensando que só permitir recursos anti-depuração, fornecidos pelo protetor de sua escolha, é o suficiente. Eu mesmo vi um aplicativo comercial amplamente conhecido e protegido utilizando Themida (que é um dos protetores mais complicados). Dessa forma, ele permanece muito desprotegido, já que o Themida não é nem mesmo avisado durante a extração de informações relativamente sensíveis usando o próprio aplicativo.

No entanto, o objetivo deste artigo não é discutir os prós e contras do Themida ou qualquer outro protetor, nem tenho qualquer intenção de desgraçar qualquer um dos fornecedores de software. O objetivo é descrever uma maneira relativamente fácil de contornar os truques comuns de anti depuração (incluindo a proteção DRM do Windows), com injeção de DLL.

Como o termo “anti-depuração” mesmo afirma: tais métodos alvejam depuradores modernos. Existem vários truques vulgarmente conhecidos:

  1. IsDebuggerPresent () – você ficaria surpreso em saber quantos vendedores confiam sóneste API;
  2. Métodos adicionais de depurador de detecção de presença;
  3. Modificação de IAT– a qual nem valerealment a pena tentar;
  4. Redirecionamento de depuração de API (por exemplo, para um loop infinito).
  5. E um pouco mais.

Ponto nº 4 – não permite que você implemente o seu próprio depurador na esperança que ele não seja notado pelo programa vítima (muitos iniciantes caem nesse ponto).

Ponto nº 3 – quanto você pode modificar o IAT? Quer dizer, o carregador de sistema tem que continuar a ser capaz de analisá-lo, assim, se o carregador do sistema pode, qualquer um pode.

Ponto nº 1 – nem vale a pena ainda mencionar aqui.

Neste artigo, vou descrever uma maneira simples (embora, alguns possam chorar e dizer que é uma maneira mais difícil) para se inteirar um pouco mais sobre os truques de anti-depuração mesmo sem perceber a sua presença, implementando um pseudo depurador DLL simples, que será injetado no processo de destino.

Etapa #1. Preparativos

A fim de utilizar qualquer depurador, você tem que saber onde colocar seus pontos de interrupção. Caso contrário, o processo como um todo é sem sentido. Mas como você pode definir os locais apropriados se o executável no disco é criptografado (por exemplo, com Themida) e você ainda não pode anexar um depurador para ver o que está acontecendo lá dentro?

A solução é bem simples: o Windows nos fornece todos os instrumentos para ler a memória de outro processo (dado que você tem direitos de acesso suficientes) com OpenProcess(), ReadProcessMemory() e NtQueryInformationProcess(). Usando-os, você pode simplesmente despejar o executável descriptografado e qualquer um de seus módulos (DLLs) para um arquivo separado no disco.

O NtQueryInformationProcess() fornece o endereço do PEB (veja este artigo para mais informações sobre PEB) do processo de destino. Então, você simplesmente analisa a lista vinculada de módulos carregados, obtém o endereço base (identificador de módulo) e o tamanho da imagem para cada um. Então use ReadProcessMemory para copiar a imagem para um arquivo. Uma complicação: você terá que usar ReadProcessMemory, a fim de acessar o PEB do processo remoto.

Depois de ter despejado a imagem de destino para um arquivo, esse arquivo pode ser facilmente carregado no IDA Pro, desmontado e pesquisado estaticamente.

Etapa #2. Injetor e DLL

Eu não vejo qualquer razão para descrever o processo de injeção de DLL. Você é livre para utilizar os métodos de injeção padrão, injeção de DLL avançado ou usar este método se você tiver problemas com os dois mencionados anteriormente.

DllMain()

Sugere-se a não executar qualquer ação pesada nesta função, no entanto, realmente não temos uma escolha (embora, você possa lançar um thread separado). A primeira coisa a fazer é suspender todos os threads em execução (exceto o atual, claro). O problema é que o Windows não tem nenhuma função API que te permitirá enumerar tópicos de um único processo. Em vez disso, ele permite que você passe por todos os threads no sistema. Veja as páginas do MSDN para Thread32First e Thread32Next – não deve ser um exemplo perfeito de obter threads do processo atual. Uma vez que todos eles estiverem suspensos, você estará pronto para prosseguir.

Instalação de breakpoints

Não, nós não vamos usar os breakpoints de software 0xCC regulares, nem vamos fazer qualquer uso de breakpoints de hardware aqui. Em vez disso, vamos colocar uma instrução que aumentaria uma exceção para o local do breakpoint desejado. Para manter tal instrução breve e evitar a alteração dos valores dos registros, ‘0 AAM’ parece ser um candidato perfeito. Ele possui só dois bytes de 0x00 0xD4 e aumenta a exceção EXCEPTION_INT_DIVIDE_BY_ZERO (código de exceção 0xC0000094).

Use o VirtualProtect() para alterar os direitos de acesso do endereço de destino, para que você possa alterar o seu conteúdo, fazer backup dos originais dois bytes daquele endereço e substituí-los com 0x00D4.

VirtualProtect((LPVOID)(target & ~0xFFF), 0x1000, PAGE_EXECUTE_READWRITE, (PDWORD)&prevProtect);
*((unsigned short*)target) = 0x00D4;
VirtualProtect((LPVOID)(target & ~0xFFF), 0x1000, prevProtect, (PDWORD)&prevProtect);

Nesse ponto, o processo vítima está quase pronto para ser continuado. Uma coisa – handler de exceção. Nós vamos usar o mecanismo de tratamento de exceção vectored, pois permite que nosso handler seja (pelo menos entre) os primeiros a lidar com uma exceção. Uma vez que ele seja adicionado com AddVectoredExceptionHandler (), você pode retomar os threads suspensos do processo.

Handler

Uma coisa importante a se fazer uma vez que seu handler obtém o controle é verificar o endereço onde ocorreu a exceção e o código de exceção, já que não temos a intenção de lidar com exceções irrelevantes:

LONG CALLBACK handler(PEXCEPTION_POINTERS ep)
{
if(ep->ContextRecord->Eip == target && ep->ExceptionRecord->ExceptionCode == 0xC0000094)
{
// Do your stuff here
}
else
// Optionally log other exceptions
return EXCEPTION_CONTINUE_SEARCH;
return EXCEPTION_CONTINUE_EXECUTION;
}

Suas coisas

Um dos parâmetros que você obtém com o seu handler é o indicador para a estrutura de contexto, que te fornece o conteúdo de todos os registros no momento da exceção. É desnecessário mencionar que você tem o acesso à memória do processo também. Tal como você estava em um depurador, com a única diferença de que você tem que implementar a rotina que irá mostrar os dados nos quais você está interessado. Não se esqueça de emular a instrução original substituída pelo breakpoint e avançar o pseudo Eip de acordo antes de retornar do handler.

Mais uma coisa para mencionar: pode ser uma boa ideia suspender todos os outros threads do processo vítima, enquanto na parcela ‘suas coisas’ do handler.

Estabilidade

Não estou afirmando que este método seja infalível e estou mais do que certo (eu simplesmente sei) – existem maneiras de derrotá-lo, no entanto, pessoalmente, eu ainda não encontrei esse software. Além disso, este método é testado e estável.

Espero que este artigo tenha sido útil. Vejo você no próximo!

***

Artigo original disponível em: http://syprog.blogspot.com.br/2012/10/exception-driven-debugging-getting.html