Jump to content
  • Sign in to follow this  

    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/

    Sign in to follow this  


    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.

    Guest

  • Similar Content

    • By Fernando Mercês
      Comecei a estudar a linguagem Go há alguns dias e fiquei muito impressionado com seus recursos. A facilidade para programação paralela, o fato de ter ponteiros, funções que retornam mais de um valor, código enxuto (se você declarar uma variável e não usar, o programa nem compila!) e outros realmente me encantaram.
      Recentemente precisei disassemblar um trecho de código de um binário PE para um projeto que está escrito em Go. Vi que existem algumas bibliotecas prontas para serem usadas, como gapstone (bindings da Capstone) e go-zydis (bindings da Zydis) mas não encontrei uma nativa.
      No entanto, vi que existe uma ferramenta nativa no toolset da linguagem similar ao objdump do GNU binutils:
      $ go doc cmd/objdump Objdump disassembles executable files. Usage: go tool objdump [-s symregexp] binary Objdump prints a disassembly of all text symbols (code) in the binary. If the -s option is present, objdump only disassembles symbols with names matching the regular expression. Compilei um "hello, world" em Go só pra ver:
      ~/hello $ cat main.go package main import "fmt" func main() { fmt.Println("menteb.in") } ~/hello $ go build E de fato o objdump da Go funciona:
      ~/hello $ go tool objdump hello | head TEXT go.buildid(SB) :-134217728 0x1001000 ff20 JMP 0(AX) :-134217728 0x1001002 476f OUTSD DS:0(SI), DX :-134217728 0x1001004 206275 ANDB AH, 0x75(DX) :-134217728 0x1001007 696c642049443a20 IMULL $0x203a4449, 0x20(SP), BP :-1 0x100100f 226d35 ANDB 0x35(BP), CH :-1 0x1001012 4c6f OUTSD DS:0(SI), DX :-1 0x1001014 6a52 PUSHL $0x52 :-1 0x1001016 436e OUTSB DS:0(SI), DX :-1 0x1001018 4a31794f XORQ DI, 0x4f(CX) Mas ao tentar com o um PE compilado pra 64-bits, descobri que só funciona com binários feito em Go. 😩
      $ go tool objdump putty.exe objdump: disassemble putty.exe: no runtime.pclntab symbol found De qualquer forma, resolvi olhar o código-fonte deste objdump interno da linguagem pra ver qual é dessa mandinga.  Na linha 43 do main.go do objdump tem um import pra uma biblioteca chamada objfile. Pensei: Wow, deve ser uma biblioteca de disassembly, talvez eu possa alterar ! E na hora já criei um projeto tentando usá-la mas fui surpreendido com um errão! kkkk
      ~hello $ cat main.go package main import "fmt" import "cmd/internal/objfile" func main() { fmt.Println("menteb.in") } ~hello $ go build main.go:4:8: use of internal package cmd/internal/objfile not allowed Não pesquisei muito sobre essa história sobre eu não poder usar um pacote interno (por quê o objdump pode e eu não posso?!), mas fui olhar esta objfile e terminei encontrando seu fonte. Para minha alegria, neste arquivos disasm.go vi os seguintes imports:
      "golang.org/x/arch/arm/armasm" "golang.org/x/arch/arm64/arm64asm" "golang.org/x/arch/ppc64/ppc64asm" "golang.org/x/arch/x86/x86asm" Agora sim, carái! É tudo público e posso usar. Desculpe o desabafo.. hehe o artigo na verdade começa aqui mas quis contar como cheguei porque né. 😁
      Cada uma dessas bibliotecas possui uma função Decode() justamente pra decodificar uma instrução (tipo Inst). Testei com um NOP em 64-bits, só pra ver:
      package main import ( "fmt" "log" "golang.org/x/arch/x86/x86asm" ) func main() { dados := []byte{0x90} ins, err := x86asm.Decode(dados, 64) if err != nil { log.Fatalln(err) } fmt.Println(ins) } A saída foi exatamente a esperada:
      $ ./hello NOP Show. Agora é abrir um PE, ler de onde quero e daí disassemblar usado essa x86asm.Decode() num loop, mas vou deixar esse exercício aí pra quem quiser treinar Go. Ou se acharem útil posso postar um aqui mais tarde. Aqui já funcionou mas precisa de uma polida. 🙂
      Perceba também que há bibliotecas para ARM e PowerPC. Achei bem maneiro. Talvez em breve o time da Go adicione suporte a mais arquiteturas. Amém! 🙏 
    • By lucass
      Vou começar agradecendo ao @Fernando Mercês pela oportunidade e por ter sugerido este artigo, que também me motivou bastante a escrevê-lo!
      Introdução
      Não sou conhecido internet a dentro, apenas acompanho alguns canais no Discord (tal como o do Mente Binária). Meu nível de programação e engenharia reversa não é algo admirável ainda. Em um grupo especifico intitulado "Terra do 1337", que é um grupo fechado de amigos com finalidade de estudar engenharia reversa, programação e descontrair, eu surgi com uma idéia de escrever uma ferramenta que iria facilitar a vida de muitos nesta área de engenharia reversa e achei de API Inspector.
      A seguir um spoiler de como foi o início do projeto, para quem se interessar. 😉
      O que é o API Inspector
      É uma ferramenta de código-aberto voltada para área de engenharia reversa, que irá auxiliar na análise de funções correspondentes a certas API's do Windows, retornando informações obtidas dos argumentos caso a função seja chamada pela aplicação.
      O que ele faz
      Ele faz um hook (do Inglês "gancho"), que consiste num desvio na função original da API solicitada para nossa própria função e com isso podemos obter os dados (argumentos/parâmetros) que foram passados para tal função.
      Como ele funciona
      O princípio de um hook é simples: você insere no inicio da função um salto que irá levar para a sua função (que é uma cópia da função original) e depois de efetuar o que quiser, irá retornar para a função original prosseguir.
      Talvez mais fácil visualizar o que expliquei com código:
      //Aqui está a função //ZwWriteVirtualMemory | NtWriteVirtualMemory, originada do binário: ntdll.dll //créditos ao https://undocumented.ntinternals.net/ NTSYSAPI NTSTATUS NTAPI //WINAPI NtWriteVirtualMemory( IN HANDLE ProcessHandle, IN PVOID BaseAddress, IN PVOID Buffer, IN ULONG NumberOfBytesToWrite, OUT PULONG NumberOfBytesWritten OPTIONAL ); //Sua versão assembly 777F2110 mov eax,0x3A 777F2115 mov edx,ntdll.77808D30 777F211A call edx 777F211C ret 0x14 //O que nós vamos fazer é criar uma função similar á ela com o nome que decidirmos //Então vamos inserir um jmp no início da função original para nossa função, ficando assim: 777F2110 jmp api inspector.573523EC 777F2115 mov edx,ntdll.77808D30 777F211A call edx 777F211C ret 0x14 //Usei como exemplo minha próprio ferramenta! //Então quando ocorrer a chamada desta função ela será jogada em nossa função! Depois de nós fazermos que desejar vamos retorna-la, porém para uma região que aloquei onde contém //Um buffer dos bytes que foram sobrescritos da função original: 03610000 mov eax,0x3A 03610005 jmp ntdll.777F2115 //Ela irá retornar depois do jmp que existe na função original e continuar o código.... Vantagens de se utilizar o API Inspector ao invés de um debugger
      Imagine que você está visualizando as chamadas intermodulares (para bibliotecas externas, no caso) que um programa faz, utilizando um debugger (o x64dbg por exemplo) e notou que uma certa função que deseja inspecionar é chamada em diversos pontos do programa. Vejo duas opções neste caso: colocar vários breakpoints, um em cada chamada à função, no código do programa ou colocar um único breakpoint função em si, no código dela, na DLL.
      Em ambos os casos, você vai precisar analisar chamada por chamada, parâmetro por parâmetro. E se a função for chamada 20 vezes consecutivamente? O tempo que você levaria para visualizar apenas o primeiro parâmetro da chamada é o tempo que a ferramenta iria levar para exibir todas as 20 chamadas, com os argumentos formatados bonitinhos ao seu dispor. Entende a vantagem? 🙂
      E as desvantagens?
      Por hora, uma desvantagem é a quantidade de funções e API's suportadas. De fato, a primeira release não possui uma quantidade significativa que vá fazer você utilizar a ferramenta e nem uma quantidade de recursos interessantes na ferramenta. Mas é ai que vem o ponto chave, o fato de deixar ela pública remete ao próprio crescimento da mesma, no primeiro momento é necessário uma orientação da parte de vocês para me ajudar a melhorar o código visual. O segundo passo é eu e vocês começarem a fornecerem mais recursos para ela. Eu irei adicionar todo ou qualquer recurso que seja significativo para a mesma, e para isso eu já tenho mais funcionalidades para implementar na ferramenta que são excelentes.
      Interface gráfica
      Na imagem abaixo, utilizei o API Inspector para hookar a função MessageBoxW() da USER32.DLL. Depois disso, escrevi um texto num novo arquivo no Notepad++ e tentei fechar o programa. Ao fazer isso, o Notepad++ perguntou se eu queria salvar o arquivo e ele faz isso através de uma chamada à MessageBoxW(), que o API Inspector interceptou prontamente.

      Na imagem acima, a janela à esquerda mostra o que está atualmente passando pelas funções hookadas. Na janela a direita, temos um log.
      Como utilizar o API Inspector
      A única coisa que você precisa fazer é anexar a DLL do API Inspector ao processo desejado e para isso existem os softwares chamados "Injetores de DLL" que podem ser achados na internet.
      Você também pode criar o seu próprio injetor. Uma dica é pesquisar sobre injeção com a função LoadLibrary(), mas no exemplo a seguir eu vou mostrar como utilizar o Process Hacker para fazer a injeção.
      1 - Abra o Process Hacker e identifique no mesmo o processo no qual você quer injectar a DLL do API Inspector. No exemplo, usei o processo do Notepad++.

      2 - Clique com o botão direito sobre o processo e escolha Miscellaneous > Inject DLL.

      3 - Selecione a DLL API-Inspector.dll e clique em Abrir.

      4 - Se o Process Hacker possuir privilégios suficientes a ferramenta irá ser carregada, caso contrário, não.

      Após isso você precisa selecionar a API desejada, a função desejada e clicar em GO Hook!
      O step call é uma funcionalidade que vai fazer a ferramenta aguardar o pressionamento da tecla ENTER para retornar para a função original. Pronto, o seu hook está feito e você já poderá inspecionar a função desejada.
      Download e código
      No repositório do API Inspector no Github você pode baixar a versão compilada e ter acesso ao código-fonte também. Contribuições são muito bem vindas!
      Bom, eu nunca tinha escrito um artigo. Se faltou informação ou coloquei informação demais me desculpe. Estou aberto pra ler os comentários. Ah, e participem deste projeto! Eu quero fazer ele crescer muito. Caso precise de referências de como cheguei a este projeto, tem tudo na página inicial do projeto no Github.
      Agradecimentos
      Obrigado novamente ao Fernando Mercês, ao pessoal do Terra 1337 que me incentiva cada vez mais e em especial para o iPower e Luan que são colaboradores do projeto.
      Referências
      Dear ImGui Programming reference for the Win32 API NTAPI Undocumented Functions C++ 3D DirectX Programming
    • By ncaio
      ====== Bem-vindo a bordo ======

      Este é um repositório/espaço aberto/livre de conteúdo referente a hardware hacking em geral. Sinta-se a vontade para contribuir e retirar suas dúvidas. Assim como em outros espaços de conhecimento compartilhado na Internet, este Fórum tem regras. Algumas delas, são:
        * Seja educado(a) e respeitoso(a);
        * Pesquise antes;
        * Seja claro(a) e descritivo(a);
        * Esteja preparado(a) para compartilhar informações relevantes a sua dúvida;
        * Não fuja do foco;
        * Referencie autores;
        * E etc.
    • By Fabiano Furtado
      Pessoal...
      Ontem achei um artigo na Internet bem escrito, interessante e detalhado sobre Engenharia Reversa em ELF.
      É um reversing básico, mas não tããããão básico assim. Acho que vale a pena conferir.
      http://manoharvanga.com/hackme/
      Valeu!
    • By Ciro Moises Seixas Dornelles
      Olá a todos, existe alguma maneira de se extrair o conteúdo do livro de engenharia reversa para que eu posso lê-lo em um dispositivo kindle?

       
×
×
  • Create New...