Ir para conteúdo
  • Categorias de Anti-Debugging: TLS Callback

       (0 análises)

    paulosgf

    Introdução

    Reunir informações sobre técnica de "Anti- Engenharia  Reversa" é a proposta dessa série de artigos que serão publicados aqui no Mente Binária. É um tema em que tenho pesquisado e criado pequenos tutoriais práticos para mim mesmo durante o último ano, e resolvi compartilhá-los. 

    Em cada um deles darei uma passada nas principais técnicas encontradas em todas as classes de defesa dos criadores de malware, além de mostrar como desabilitá-las. Tudo com exemplos de código para se entender na prática mesmo. 

    *Para a implementação estarei utilizando o Visual Studio Community, pois este tem o suporte ao ambiente C Runtime (CRT) necessário.

    A grande maioria das técnicas que serão apresentadas é para o ambiente Windows. Alguns poucos casos, que serão informados, são para Linux.

    Como pré-requisito, é necessário algum conhecimento de linguagem C\C++, um pouco de Assembly e principalmente Engenharia Reversa. Todos estes tópicos são abordados nos cursos gratuitos no canal do Mente Binária no Youtube.

    *No caso, não apresentarei a técnica mais simples/comum que utiliza a função IsDebuggerPresent(), pois esta técnica é explicada na última aula do curso do CERO aqui do Mente Binária.

    Classes de Anti-Engenharia Reversa

    Essas classes são uma forma de categorizar os métodos de "Anti-Engenharia Reversa", agrupando métodos de evasão semelhantes num mesmo grupo ou “classe”. Como não existe uma classificação oficial, estas classes estão baseadas na divisão apresentada nesta referência, com algumas adaptações.

    TLS Callback

    Windows:

    A Thread Local Storage (TLS) é um recurso do Windows  para definir objetos (variáveis) na memória que possam ter valores diferentes para cada thread criada por um processo.

    Por exemplo, ao imprimir um documento, uma thread se encarrega de mostrar o documento na tela, enquanto outra acessa esta mesma informação simultaneamente para lidar com a impressão. A thread de impressão pode ter uma variável que armazena a quantidade de páginas impressas, porém, esta variável não se faz necessária na thread que apresenta o documento na tela.

    Esta informação é armazenada (daí o nome "Thread Local Storage") numa região definida no cabeçalho dos binários PE (Portable Executable), e o acesso só é permitido para sua respectiva thread.

    As funções TLS Callback executam antes do binário alcançar seu "ponto de início", chamando e inicializando estes objetos TLS através de métodos construtores e os removendo da memória por métodos destrutores após seu uso. Com isso em mente, as TLS Callbacks também podem ser utilizadas pelos desenvolvedores de malware para permitir que se execute código antes que o binário chegue à região tradicional de início do programa, conhecido como Entrypoint, que irá levar em seguida à função main(). Isto cria diversas oportunidades como por exemplo executar código malicioso antes que o depurador possa detectá-lo (levar o malware a encerrar o processo antes mesmo de executar as rotinas maliciosas ao perceber que está sendo depurado, por exemplo).

    Alguns exemplos de malwares que empregam TLS Callbacks no Windows são:

    • Nadnadzzz botnet de 2009;
    • Grum botnet de 2008, através do Grum rootkit;
    • Ursnif (mais recente);

    Implementação

    A biblioteca C Runtime (CRT) do Visual Studio provê suporte para fácil criação de TLS Callbacks (como comentado aqui) graças ao código em “C:\Program Files (x86)\Microsoft Visual Studio xx.0\VC\crt\src\tlssup.c”, que cria um diretório de dados TLS baseado na seguinte estrutura:

    typedef struct _IMAGE_TLS_DIRECTORY32 {
        DWORD StartAddressOfRawData; /* Início da seção TLS AddressOfRawData*/
        DWORD EndAddressOfRawData; /* Endereço final na seção TLS */
        DWORD AddressOfIndex; /* Índice da seção TLS */
        DWORD AddressOfCallBacks; /* Ponteiro para o array de funções callback */
        DWORD SizeOfZeroFill;
        DWORD Characteristics;
    } __tls_used;

     

     Para criarmos a nossa própria callback precisamos primeiro defini-la no seguinte formato:

    VOID WINAPI tls_callback1(
        PVOID DllHandle,
        DWORD Reason,
        PVOID Reserved)
    { codigo_funcao; }

    As constantes para DWORD Reason podem ser:

    DLL_PROCESS_DETACH = 0
    DLL_PROCESS_ATTACH = 1
    DLL_THREAD_ATTACH = 2
    DLL_THREAD_DETACH = 3

    Depois a callback precisa ser alocada da seguinte forma:

    PIMAGE_TLS_CALLBACK ponteiro_tls_callback = tls_callback1;

    Após esta contextualização do mecanismo de funcionamento, segue um exemplo de código onde a função de callback detecta o ambiente de depuração através da função IsDebuggerPresent() e sai informando que o mesmo foi descoberto. Caso contrário, informa que o programa está executando normalmente:

    #include <iostream>
    #include <windows.h>
    
    using namespace std;  
    
    // Declara uma variável global requerida para a chamada TLS Callback
    
    static int v1 = 0;
    
    // Declara a callback
    
    VOID WINAPI tls_callback1(
        PVOID DllHandle,
        DWORD Reason,
        PVOID Reserved)
    {
        if (Reason == DLL_PROCESS_ATTACH) {
    
            v1 = 1;                 // dentro da Callback altera o valor da variável
    
            if (IsDebuggerPresent())
    
            {
                cout << "Stop debugging program!" << endl;
                TerminateProcess(GetCurrentProcess(), 0x1);
                exit(1);
            }
        }
    }
    
    // Cria objeto conforme a arquitetura através de #pragmas, que são instruções específicas do compilador
    
    #ifdef _M_AMD64		// para arquitetura x86_64
    
    #pragma comment (linker, "/INCLUDE:__tls_used") // instrui linker usar o diretório TLS
    
    #pragma comment (linker, "/INCLUDE:p_tls_callback1") // instrui linker usar ponteiro do mesmo tipo da callback tls_callback1 declarada antes
    
    #pragma const_seg(push) // carrega o ponteiro na stack do compilador para uso da callback no segmento de dados const (.rdata)
    
    #pragma const_seg(".CRT$XLA") // cria nova seção TLS
    
    EXTERN_C const PIMAGE_TLS_CALLBACK p_tls_callback1 = tls_callback1; // atribui tls_callback1 ao ponteiro p_tls_callback1
    
    #pragma const_seg(pop) // remove o ponteiro da stack após o uso
    
    #endif	// fim deste bloco
    
    #ifdef _M_IX86		// para a arquitetura x86. as instruções tem as mesmas finalidades do bloco anterior
    
    #pragma comment (linker, "/INCLUDE:__tls_used")
    
    #pragma comment (linker, "/INCLUDE:_p_tls_callback1")
    
    #pragma data_seg(push)
    
    #pragma data_seg(".CRT$XLA")
    
    EXTERN_C PIMAGE_TLS_CALLBACK p_tls_callback1 = tls_callback1;
    
    #pragma data_seg(pop)
    
    #endif
    
    // main() só será executada depois, quando um depurador não for detectado
    
    int main(int argc, char* argv[])
    {
        cout << "Normal execution!" << endl;
        printf("test value from tls callback is: tls = %d\n", v1);
        return 0;
    }

    Abaixo estão algumas dicas de como lidar com TLS Callbacks em diversas ferramentas:

    • x64dbg: Options → Preferencias → Eventos → TLS Callbacks
    • OllyDbg: Options -> Debug Options -> Events -> Make first pause at -> System breakpoint (para em TLS Callbacks)
    • OllyDbg: Plugin Olly Advanced → Break on TLS Callback
    • IDA Pro: Ctrl+E → Choose an entry point
    • IDA Pro (Debugger): Debugger -> Debugger options -> Events -> Stop on (marcar todas as opções)

    Linux:

    O Linux também suporta o Thread Local Storage (TLS), conforme descrito nestes 2 excelentes artigos:

    No entanto, o recurso de TLS no Linux aparentemente só permite a inicialização de variáveis ou objetos, e não chamada de função (como é o caso no Windows). Isto somente seria possível de dentro da main(), como nesse pequeno exemplo que usa o recurso de suporte a threads da Glibc:

     

    #include <stdio.h>
    
    // TLS que define e inicializa a variável
    __thread int main_tls_var = 2;
    
    int main() {
        printf("%d\n", main_tls_var);
        return 0;
    }

     

    $ ./tls
    2

    No entanto, ainda é possível executar funções antes ou depois da main() no Linux através das funções construtoras e destrutoras devido ao suporte do GCC. Observem que uma função construtora em C++ instancia uma variável, definindo seu valor, enquanto que a destrutora a remove da memória após a conclusão da execução (normalmente em retorno de funções):

     

    #include<stdio.h>
    
    /* atributo construtor em myStartupFun() para executar antes de main() */
    
    void myStartupFun (void) __attribute__ ((constructor));
    
    /* atributo destrutor em myCleanupFun() para executar depois de main() */
    
    void myCleanupFun (void) __attribute__ ((destructor));
    
    /* implementacao de myStartupFun */
    
    void myStartupFun (void)
    {
            printf ("startup code before main()\n");
    }
    
    /* implementacao de myCleanupFun */
    
    void myCleanupFun (void)
    {
            printf ("cleanup code after main()\n");
    }
    
    int main (void)
    {
            printf ("hello\n");
            return 0;
    }

    *Por padrão a __libc_start_main(*main(), argc, **argv, __libc_csu_init(), __libc_csu_fini()) é a função da glibc que chama a main(), onde __libc_csu_init é o construtor e __libc_csu_fini é o destrutor.

    No Linux também é possível evadir o depurador através da função ptrace, mas isso é um assunto que abordarei mais para frente, inclusive com exemplo prático.

    Por hora convido vocês a continuarem sintonizados, pois no próximo artigo começarei a tratar de técnicas da classe que utiliza flags de depuração para detectar o debugger.

    Forte abraço & até lá!

    Referências:


    • Curtir 4

    Feedback do Usuário

    Participe da conversa

    Você pode postar agora e se cadastrar mais tarde. Se você tem uma conta, faça o login para postar com sua conta.
    Nota: Sua postagem exigirá aprovação do moderador antes de ficar visível.

    Visitante

    • Isso não será mostrado para outros usuários.
    • Adicionar um análise...

      ×   Você colou conteúdo com formatação.   Remover formatação

        Apenas 75 emojis são permitidos.

      ×   Seu link foi automaticamente incorporado.   Mostrar como link

      ×   Seu conteúdo anterior foi restaurado.   Limpar o editor

      ×   Não é possível colar imagens diretamente. Carregar ou inserir imagens do URL.


  • Conteúdo Similar

×
×
  • Criar Novo...