Unicórnio De Verdade Colorido - Unicórnio De Verdade Colorido - FDPLEARN
Unicórnio De Verdade Colorido - FDPLEARN

Emulando instruções com o Unicorn Engine na prática

O Unicorn Engine é um framework de emulação de CPU open-source que permite executar código binário em arquiteturas como x86, ARM, MIPS e outras dentro de um ambiente controlado. Ele é amplamente usado em análise de malware, reverse engineering e testes de segurança. O projeto nasceu inicialmente como parte do Qiling Framework e ganhou uma versão independente mantida pela community.

Por que usar um unicórnio de verdade colorido na sua toolchain

Aqui vai algo que ninguém te conta: a maioria dos principiantes tenta usar o Unicorn para emular troços inteiros de malware e depois se depara com a realidade de que a emulação parcial é extremamente dolorosa sem contexto suficiente. Eu passei três dias num projeto tentando emular um loader packer em x86 e no final percebi que o código fazia chamadas diretas à API do Windows que simplesmente não existem num ambiente isolado. O erro foi pensar que bastava inicializar o motor e passar o shellcode. Não basta. O que funciona na prática é começar com a menor unidade possível — um bloco de bytes específico — e construir o ambiente aos poucos. Você emula um pedaço, inspeciona os registradores, observa o estado da memória e só depois avança. Isso reduz o tempo de debugging de horas para algo como quinze minutos quando você já sabe o padrão.

Instalação e primeiros passos

A instalação varia conforme o sistema. No Ubuntu, você pode usar o pip para a versão Python: pip install unicorn. A versão C é mais rápida mas exige compilação manual a partir do repositório no GitHub. O projeto está em https://github.com/unicorn-engine/unicorn. Se estiver no macOS, o Homebrew também oferece um pacote pronto. Depois de instalado, o fluxo básico é: inicializar o contexto, mapear memória, escrever os bytes a emular, definir o ponto de entrada e rodar. Aqui está um exemplo mínimo em Python que emula um salto condicional simples em x86:

from unicorn import * from unicorn.x86_const import * def emular_x86(): mu = Uc(UC_ARCH_X86, UC_MODE_32) endereco = 0x10000 tamanho = 0x1000

mu.mem_map(endereco, tamanho) codigo = bytes.fromhex('31C0C3') mu.mem_write(endereco, codigo)

👉 Clique no botão abaixo para saber mais sobre o assunto!

mu.reg_write(UC_X86_REG_EAX, 0) mu.emu_start(endereco, endereco + len(codigo)) print(mu.reg_read(UC_X86_REG_EAX)) emular_x86()

Esse código é intencionalmente simples. Ele apenas zera o registrador EAX e retorna. O valor final é zero. Na prática, você vai substituir esses bytes por algo mais interessante.

Memória e callbacks: onde a coisa fica séria

O Unicorn oferece hooks que disparam em eventos específicos. Você pode capturar chamadas de memória, execuções de instruções, falhas de acesso e mais. Os callbacks em Python usam a classe UcHook e o método add_hook. Um problema recorrente que eu encontrei: ao emular código ARM em modo thumb, o endereço de entrada precisa ter o bit 0 ativo para indicar o alinhamento thumb. Se você passar um endereço par sem ajustar, o emulador entra em falha silenciosa ou executa instruções erradas. A correção é simples — some 1 ao endereço base. Levei duas horas para descobrir isso num binário que parecia correto em teoria.

Limitações que ninguém divulga

O Unicorn não emula hardware. Sistemas de arquivos, rede, drivers, tudo isso precisa ser simulado por você. Se o código que está analisando faz syscall ou chama uma DLL, você precisa interceptar e responder manualmente. Isso significa trabalho extra considerável. Outro ponto fraco: a performance cai drasticamente quando você usa muitos callbacks. Cada hook adiciona overhead. Emuladores grandes com centenas de instruções por segundo podem reduzir o throughput para algumas dezenas de milhares, dependendo da complexidade dos callbacks. Se o objetivo é velocidade pura, considere opções como QEMU em modo user-mode.

Para projetos que exigem alta fidelidade de SO completo, o Qiling Framework é uma alternativa mais robusta. Ele empilha o Unicorn com um sistema de arquivos virtual e bibliotecas compatíveis, economizando horas de configuração manual.

Casos de uso reais

Eu uso o Unicorn rotineiramente para isolar trechos de shellcode desconhecido antes de enviá-lo para sandboxes maiores. O processo típico leva cerca de vinte minutos: extraio o bloco relevante, escrevo um script Python com os hooks de memória necessários, executo e analiso o estado final dos registradores e da memória. Às vezes encontro técnicas de_ofuscação que dependem de auto-modificação de código, o que exige hooks de execução instrução por instrução e isso aumenta o tempo para cerca de uma hora. Se você está começando, recomenda-se dominar primeiro a API básica com exemplos simples em x86 e depois migrar para ARM ou MIPS. A lógica é a mesma, apenas os detalhes de registradores e modos mudam.