Introdução Prática ao Framework angr para Análise de Binários

Configuração do Ambiente de Desenvolvimento

A instalação do angr foi realizada em um ambiente Kali Linux, utilizando um ambiente virtual Python isolado para garantir a pureza das dependências. O comando source venv/bin/activate inicia o ambiente virtual, enquanto deactivate o finaliza. O uso do IPython é recomendado para uma experiência interativa aprimorada.

Explorando Mecanismos Básicos de Simulação

Exemplo Inicial: Localização por Endereço

O primeiro caso demonstra a localização de um estado específico utilizando um endereço de memória como alvo.

import angr

proj = angr.Project('./exemplo_00')
estado_inicial = proj.factory.entry_state()
gerenciador = proj.factory.simulation_manager(estado_inicial)

gerenciador.explore(find=0x8048678)

if gerenciador.found:
    estado_encontrado = gerenciador.found[0]
    print(estado_encontrado.posix.dumps(0))

Exemplo com Condições Personalizadas

Em cenários mais complexos, é possível definir condições personalizadas para busca e evitar caminhos indesejados, otimizendo a execução.

import angr

def estado_valido(estado):
    return b'Sucesso' in estado.posix.dumps(1)

def estado_invalido(estado):
    return b'Falha' in estado.posix.dumps(1)

proj = angr.Project('./exemplo_02')
estado_inicial = proj.factory.entry_state()
gerenciador = proj.factory.simulation_manager(estado_inicial)

gerenciador.explore(find=estado_valido, avoid=estado_invalido)

if gerenciador.found:
    solucao = gerenciador.found[0].posix.dumps(0)
    print(solucao)

Trabalhando com Variáveis Simbólicas e Estados

Variáveis em Registradores

Para problemas que envolvem múltiplas entradas simbólicas armazenadas em registradores, pode-se utilizar estados em branco para inicialização personalizada.

import angr
import claripy

proj = angr.Project('./exemplo_03')
ponto_inicio = 0x08048980
estado_inicial = proj.factory.blank_state(addr=ponto_inicio)

entrada_a = claripy.BVS('entrada_a', 32)
entrada_b = claripy.BVS('entrada_b', 32)
entrada_c = claripy.BVS('entrada_c', 32)

estado_inicial.regs.eax = entrada_a
estado_inicial.regs.ebx = entrada_b
estado_inicial.regs.edx = entrada_c

gerenciador = proj.factory.simulation_manager(estado_inicial)
gerenciador.explore(find=estado_valido, avoid=estado_invalido)

if gerenciador.found:
    resultado = gerenciador.found[0]
    sol_a = resultado.solver.eval(entrada_a)
    sol_b = resultado.solver.eval(entrada_b)
    sol_c = resultado.solver.eval(entrada_c)
    print(f"Resultado: {sol_a:x} {sol_b:x} {sol_c:x}")

Variáveis na Pilha

Quando as entradas são passadas através da pilha, é necessário simular seu layout manualmente.

import angr

proj = angr.Project('./exemplo_04')
estado_inicial = proj.factory.blank_state(addr=0x08048697)

estado_inicial.stack_push(estado_inicial.regs.ebp)
estado_inicial.regs.ebp = estado_inicial.regs.esp
estado_inicial.regs.esp -= 8

var_a = estado_inicial.solver.BVS('var_a', 32)
var_b = estado_inicial.solver.BVS('var_b', 32)
estado_inicial.stack_push(var_a)
estado_inicial.stack_push(var_b)

gerenciador = proj.factory.simgr(estado_inicial)
gerenciador.explore(find=estado_valido, avoid=estado_invalido)

if gerenciador.found:
    estado_final = gerenciador.found[0]
    print(f"Solução: {estado_final.solver.eval(var_a)} {estado_final.solver.eval(var_b)}")

Manipulação de Memória e Arquivos

Armazenamento Simbólico em Memória

Para variáveis armazenadas em regiões específicas da memória, utiliza-se o método memory.store.

import angr

proj = angr.Project('./exemplo_05')
estado_inicial = proj.factory.blank_state(addr=0x08048601)

dado_1 = estado_inicial.solver.BVS('dado_1', 64)
dado_2 = estado_inicial.solver.BVS('dado_2', 64)
endereco_1 = 0x0A1BA1C0
endereco_2 = 0x0A1BA1C8

estado_inicial.memory.store(endereco_1, dado_1)
estado_inicial.memory.store(endereco_2, dado_2)

gerenciador = proj.factory.simgr(estado_inicial)
gerenciador.explore(find=estado_valido, avoid=estado_invalido)

if gerenciador.found:
    resultado = gerenciador.found[0]
    valor_1 = resultado.solver.eval(dado_1, cast_to=bytes).decode('utf-8')
    valor_2 = resultado.solver.eval(dado_2, cast_to=bytes).decode('utf-8')
    print(f"Solução: {valor_1} {valor_2}")

Memória Dinâmica e Manipulação de Arquivos

Cenários que envolvem alocação dinâmica ou leitura de arquivos exigem configuração adicional de bufffers e sistemas de arquivos simbólicos.

import angr

proj = angr.Project('./exemplo_07')
estado_inicial = proj.factory.blank_state(addr=0x080488D6)

conteudo_arquivo = estado_inicial.solver.BVS('conteudo', 0x40 * 8)
arquivo_sim = angr.storage.SimFile("dados.txt", content=conteudo_arquivo, size=0x40)
estado_inicial.fs.insert("dados.txt", arquivo_sim)

gerenciador = proj.factory.simgr(estado_inicial)
gerenciador.explore(find=estado_valido, avoid=estado_invalido)

if gerenciador.found:
    solucao_bytes = gerenciador.found[0].solver.eval(conteudo_arquivo, cast_to=bytes)
    print(f"Solução: {solucao_bytes}")

Otimizações e Técnicas Avançadas

Mitigação de Explosão de Caminhos com Restrições

Para evitar a explosão combinatória de caminhos, podem-se adicionar restrições diretamente aos estados encontrados.

import angr

proj = angr.Project('./exemplo_08')
estado_inicial = proj.factory.blank_state(addr=0x08048625)

buffer = 0x0804A050
entrada_sim = estado_inicial.solver.BVS('entrada', 16 * 8)
estado_inicial.memory.store(buffer, entrada_sim)

gerenciador = proj.factory.simgr(estado_inicial)
ponto_verificacao = 0x08048673
gerenciador.explore(find=ponto_verificacao)

if gerenciador.found:
    estado_verif = gerenciador.found[0]
    string_esperada = "AUPDNNPROEZRJWKB"
    dados_carregados = estado_verif.memory.load(buffer, 16)
    estado_verif.add_constraints(string_esperada == dados_carregados)
    solucao_final = estado_verif.solver.eval(entrada_sim, cast_to=bytes)
    print(f"Solução: {solucao_final}")

Utilização de Hooks para Substituição de Comportamento

Hooks permitem interceptar e substituir a execução de funções ou instruções específicas, útil para simplificar a análise.

import angr
import claripy

proj = angr.Project('./exemplo_09')
estado_inicial = proj.factory.entry_state()

endereco_hook = 0x080486B3
@proj.hook(endereco_hook, length=5)
def substituir_verificacao(estado):
    endereco_input = 0x0804A054
    dados = estado.memory.load(endereco_input, 16)
    string_alvo = "XYMKBKUHNIQYNQXE"
    estado.regs.eax = claripy.If(
        string_alvo == dados,
        claripy.BVV(1, 32),
        claripy.BVV(0, 32)
    )

gerenciador = proj.factory.simgr(estado_inicial)
gerenciador.explore(find=estado_valido, avoid=estado_invalido)

if gerenciador.found:
    print(f"Entrada bem-sucedida: {gerenciador.found[0].posix.dumps(0)}")

Tags: angr engenharia-reversa análise-binária execução-simbólica Python

Publicado em 8-31 10:28