Jump to content
  • Entendendo o imphash

       (0 reviews)

    Fernando Mercês

    No início de 2014, a Mandiant publicou que estava calculando o hash MD5 das funções importadas por binários PE para buscar variantes de malware [1]. Eles também fizeram um patch na biblioteca pefile [2] para suportar o novo cálculo. A ideia colou e até o Virus Total passou a utilizar [3]. Eu mesmo utilizei um tempo sem entender direito até que um dia decidi estudá-lo para implementar no pev [4] (que ainda não fiz) e hoje decidi escrever sobre.:)

    Todo binário PE que se preze usa funções de bibliotecas. Assim sendo, desde os primórdios da especificação PE, há o que é conhecido por IT – Import Table, que é uma tabela contendo uma lista de cada módulo (DLL) que o binário utiliza (importa) com suas respectivas funções. Por exemplo, vamos analisar o programa no estilo “Hello, world” abaixo:

    #include <stdio.h>
     
    int main(void)
    {
    	puts("Quem avisa chato eh!");
    	return 0;
    }

    Ao compilar usando o Dev-Cpp [5] no Windows 7, podemos checar quais funções são importadas por ele:

    C:\> readpe --imports hello.exe
     
    Imported functions
        Library
            Name:                            KERNEL32.dll
            Functions
                Function
                    Name:                            DeleteCriticalSection
                Function
                    Name:                            EnterCriticalSection
                Function
                    Name:                            GetCurrentProcess
                Function
                    Name:                            GetCurrentProcessId
                Function
                    Name:                            GetCurrentThreadId
                Function
                    Name:                            GetLastError
                Function
                    Name:                            GetStartupInfoA
                Function
                    Name:                            GetSystemTimeAsFileTime
                Function
                    Name:                            GetTickCount
                Function
                    Name:                            InitializeCriticalSection
                Function
                    Name:                            LeaveCriticalSection
                Function
                    Name:                            QueryPerformanceCounter
                Function
                    Name:                            RtlAddFunctionTable
                Function
                    Name:                            RtlCaptureContext
                Function
                    Name:                            RtlLookupFunctionEntry
                Function
                    Name:                            RtlVirtualUnwind
                Function
                    Name:                            SetUnhandledExceptionFilter
                Function
                    Name:                            Sleep
                Function
                    Name:                            TerminateProcess
                Function
                    Name:                            TlsGetValue
                Function
                    Name:                            UnhandledExceptionFilter
                Function
                    Name:                            VirtualProtect
                Function
                    Name:                            VirtualQuery
        Library
            Name:                            msvcrt.dll
            Functions
                Function
                    Name:                            __C_specific_handler
                Function
                    Name:                            __dllonexit
                Function
                    Name:                            __getmainargs
                Function
                    Name:                            __initenv
                Function
                    Name:                            __iob_func
                Function
                    Name:                            __lconv_init
                Function
                    Name:                            __set_app_type
                Function
                    Name:                            __setusermatherr
                Function
                    Name:                            _acmdln
                Function
                    Name:                            _amsg_exit
                Function
                    Name:                            _cexit
                Function
                    Name:                            _fmode
                Function
                    Name:                            _initterm
                Function
                    Name:                            _lock
                Function
                    Name:                            _onexit
                Function
                    Name:                            _unlock
                Function
                    Name:                            abort
                Function
                    Name:                            calloc
                Function
                    Name:                            exit
                Function
                    Name:                            fprintf
                Function
                    Name:                            free
                Function
                    Name:                            fwrite
                Function
                    Name:                            malloc
                Function
                    Name:                            memcpy
                Function
                    Name:                            puts
                Function
                    Name:                            signal
                Function
                    Name:                            strlen
                Function
                    Name:                            strncmp
                Function
                    Name:                            vfprintf
    

    Pois é, apesar de eu ter chamado apenas a função puts(), o binário precisa de muito mais para funcionar.:)

    Agora um segundo exemplo, com um recurso a mais, mas sem chamar nenhuma outra função da mesma biblioteca:

    #include <stdio.h>
     
    char *superfuncaonova(int n)
    {
      if (n >= 666)
        return "arre, porra!";
      else
        return "eu quero eh rock, diabo!";
    }
     
    int main(void)
    {
      puts("Quem avisa chato eh!");
      puts(superfuncaonova(1));
      
      return 0;
    }

    Após compilar e listar os imports deste hello2.exe, você vai confirmar que é exatamente a mesma lista do primeiro hello.exe. Isto porque, apesar de ter uma função interna nova, não utiliza outra função de biblioteca nova, o que não altera a IAT. Isso é comum em variantes de uma mesma família de malware já que as funcionalidades estão prontas e o que difere de uma variante para outra normalmente são informações como servidor de comando e controle, algumas strings, etc. Sendo assim, é inteligente utilizar essa lista para buscar famílias. Parabéns para quem pensou nisso.:)

    E como o imphash foi implementado?

    Comparar cada item da lista não seria prático então alguém teve a ideia de tirar o hash MD5 da lista, obedecendo o seguinte padrão:

    Varrendo a import table (IT), na ordem em que aparecem os imports:

    1. Prefixar cada função com o nome do módulo sem extensão (mas mantendo o ponto) da qual ela é importada. Por exemplo, se o binário importa DeleteCriticalSection() da KERNEL32.DLL, esse import reescrito ficaria KERNEL32.EnterCriticalSection.
    2. Caso não haja nome da função, tentar resolver e, caso não consiga, utilizar seu número ordinal.
    3. Converter tudo para minúsculo.
    4. Criar uma string com os imports no padrão acima, separados por vírgula.
    5. Calcular o hash MD5 da string acima.

    Eu salvei a saída do readpe para um arquivo de texto e, usando expressões regulares, o editei até que ficasse no padrão definido acima:

    $ cat hello.imports
    kernel32.deletecriticalsection,kernel32.entercriticalsection,kernel32.getcurrentprocess,kernel32.getcurrentprocessid,kernel32.getcurrentthreadid,kernel32.getlasterror,kernel32.getstartupinfoa,kernel32.getsystemtimeasfiletime,kernel32.gettickcount,kernel32.initializecriticalsection,kernel32.leavecriticalsection,kernel32.queryperformancecounter,kernel32.rtladdfunctiontable,kernel32.rtlcapturecontext,kernel32.rtllookupfunctionentry,kernel32.rtlvirtualunwind,kernel32.setunhandledexceptionfilter,kernel32.sleep,kernel32.terminateprocess,kernel32.tlsgetvalue,kernel32.unhandledexceptionfilter,kernel32.virtualprotect,kernel32.virtualquery,msvcrt.__c_specific_handler,msvcrt.__dllonexit,msvcrt.__getmainargs,msvcrt.__initenv,msvcrt.__iob_func,msvcrt.__lconv_init,msvcrt.__set_app_type,msvcrt.__setusermatherr,msvcrt._acmdln,msvcrt._amsg_exit,msvcrt._cexit,msvcrt._fmode,msvcrt._initterm,msvcrt._lock,msvcrt._onexit,msvcrt._unlock,msvcrt.abort,msvcrt.calloc,msvcrt.exit,msvcrt.fprintf,msvcrt.free,msvcrt.fwrite,msvcrt.malloc,msvcrt.memcpy,msvcrt.puts, msvcrt.signal,msvcrt.strlen,msvcrt.strncmp,msvcrt.vfprintf

    NOTA: O arquivo tem somente uma linha, sendo que não há um caractere de nova linha (\n ) no final dela, veja:

    $ hdump hello.imports | tail
    000003c0  74 2e 65 78 69 74 2c 6d  73 76 63 72 74 2e 66 70  |t.exit,msvcrt.fp|
    000003d0  72 69 6e 74 66 2c 6d 73  76 63 72 74 2e 66 72 65  |rintf,msvcrt.fre|
    000003e0  65 2c 6d 73 76 63 72 74  2e 66 77 72 69 74 65 2c  |e,msvcrt.fwrite,|
    000003f0  6d 73 76 63 72 74 2e 6d  61 6c 6c 6f 63 2c 6d 73  |msvcrt.malloc,ms|
    00000400  76 63 72 74 2e 6d 65 6d  63 70 79 2c 6d 73 76 63  |vcrt.memcpy,msvc|
    00000410  72 74 2e 70 75 74 73 2c  6d 73 76 63 72 74 2e 73  |rt.puts,msvcrt.s|
    00000420  69 67 6e 61 6c 2c 6d 73  76 63 72 74 2e 73 74 72  |ignal,msvcrt.str|
    00000430  6c 65 6e 2c 6d 73 76 63  72 74 2e 73 74 72 6e 63  |len,msvcrt.strnc|
    00000440  6d 70 2c 6d 73 76 63 72  74 2e 76 66 70 72 69 6e  |mp,msvcrt.vfprin|
    00000450  74 66                                             |tf|
    

    O imphash deste arquivo então deve ser o hash MD5 do conteúdo de hello.imports. Vamos ver?

    $ python imphash.py hello.exe # usando a pefile
    3856e6eb1020e4f12c9b8f75c966a09c
    
    $ md5 hello.imports
    MD5 (hello.imports) = 3856e6eb1020e4f12c9b8f75c966a09c

    Legal! Agora que sabemos como calcular o imphash de binários PE, podemos implementar em qualquer software, certo? Mais ou menos. Você percebeu que ignoramos o passo 2? Pois é, no próximo artigo sobre o assunto vou explicar o motivo pelo qual o imphash está quebrado. Até lá. o/

     

    Referências

    [1] https://www.fireeye.com/blog/threat-research/2014/01/tracking-malware-import-hashing.html
    [2] https://github.com/erocarrera/pefile
    [3] http://blog.virustotal.com/2014/02/virustotal-imphash.html
    [4] https://github.com/merces/pev
    [5] https://sourceforge.net/projects/orwelldevcpp/


    Revisão: Leandro Fróes

    User Feedback

    Join the conversation

    You can post now and register later. If you have an account, sign in now to post with your account.
    Note: Your post will require moderator approval before it will be visible.

    Guest

    • This will not be shown to other users.
    • Add a review...

      ×   Pasted as rich text.   Paste as plain text instead

        Only 75 emoji are allowed.

      ×   Your link has been automatically embedded.   Display as a link instead

      ×   Your previous content has been restored.   Clear editor

      ×   You cannot paste images directly. Upload or insert images from URL.


  • Similar Content

×
×
  • Create New...