Skip to content

MASTG-TECH-0041: Injeção de Biblioteca

Na seção anterior, aprendemos sobre a aplicação de patches no código do aplicativo para auxiliar em nossa análise, mas essa abordagem tem várias limitações. Por exemplo, você gostaria de registrar tudo o que é enviado pela rede sem precisar realizar um ataque MITM. Para isso, seria necessário aplicar patches em todas as chamadas possíveis para as APIs de rede, o que pode rapidamente se tornar impraticável ao lidar com aplicativos grandes. Além disso, o fato de que a aplicação de patches é única para cada aplicativo também pode ser considerada uma desvantagem, pois esse código não pode ser facilmente reutilizado.

Usando a injeção de bibliotecas, você pode desenvolver bibliotecas reutilizáveis e injetá-las em diferentes aplicativos, fazendo com que eles se comportem de maneira diferente sem precisar modificar seu código-fonte original. Isso é conhecido como injeção de DLL no Windows (amplamente usado para modificar e contornar mecanismos anti-trapaça em jogos), LD_PRELOAD no Linux e DYLD_INSERT_LIBRARIES no macOS. No Android e iOS, um exemplo comum é usar o Frida Gadget quando o chamado modo injetado de operação do Frida não for adequado (ou seja, você não pode executar o servidor do Frida no dispositivo de destino). Nessa situação, você pode injetar a biblioteca Gadget usando os mesmos métodos que você aprenderá nesta seção.

A injeção de bibliotecas é desejável em muitas situações, como:

  • Realizar introspecção de processos (por exemplo, listar classes, rastrear chamadas de métodos, monitorar arquivos acessados, monitorar acesso à rede, obter acesso direto à memória).
  • Suportar ou substituir código existente com suas próprias implementações (por exemplo, substituir uma função que deveria gerar números aleatórios).
  • Introduzir novos recursos a um aplicativo existente.
  • Depurar e corrigir bugs de runtime difíceis de identificar em códigos para os quais você não tem o código-fonte original.
  • Habilitar testes dinâmicos em um dispositivo sem root (por exemplo, com o Frida).

Nesta seção, aprenderemos sobre técnicas para realizar injeção de bibliotecas no Android, que basicamente consistem em aplicar patches no código do aplicativo (smali ou nativo) ou, alternativamente, usar o recurso LD_PRELOAD fornecido pelo próprio carregador do sistema operacional.

Aplicando Patch no Código Smali do Aplicativo

O código smali descompilado de um aplicativo Android pode ser modificado para introduzir uma chamada a System.loadLibrary. O seguinte patch em smali injeta uma biblioteca chamada libinject.so:

const-string v0, "inject"
invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V

Idealmente, você deve inserir o código acima no início do ciclo de vida do aplicativo, por exemplo, no método onCreate. É importante lembrar de adicionar a biblioteca libinject.so na pasta de arquitetura correspondente (armeabi-v7a, arm64-v8a, x86) da pasta lib no APK. Por fim, você precisa reassinar o aplicativo antes de usá-lo.

Um caso de uso bem conhecido dessa técnica é carregar o gadget do Frida em um aplicativo, especialmente ao trabalhar em um dispositivo sem root (é basicamente o que o objection patchapk faz).

Aplicando Patch na Biblioteca Nativa do Aplicativo

Muitos aplicativos Android usam código nativo além do código Java por várias razões de desempenho e segurança. O código nativo está presente na forma de bibliotecas compartilhadas ELF. Um executável ELF inclui uma lista de bibliotecas compartilhadas (dependências) que são vinculadas ao executável para que ele funcione de forma ideal. Essa lista pode ser modificada para inserir uma biblioteca adicional a ser injetada no processo.

Modificar a estrutura do arquivo ELF manualmente para injetar uma biblioteca pode ser trabalhoso e propenso a erros. No entanto, essa tarefa pode ser realizada com relativa facilidade usando LIEF (Biblioteca para Instrumentar Formatos Executáveis). Usá-la requer apenas algumas linhas de código Python, como mostrado abaixo:

import lief

libnative = lief.parse("libnative.so")
libnative.add_library("libinject.so") # Injeção!
libnative.write("libnative.so")

No exemplo acima, a biblioteca libinject.so é injetada como uma dependência de uma biblioteca nativa (libnative.so), que o aplicativo já carrega por padrão. O gadget do Frida pode ser injetado em um aplicativo usando essa abordagem, conforme explicado em detalhes na documentação do LIEF. Como na seção anterior, é importante lembrar de adicionar a biblioteca à pasta lib da arquitetura correspondente no APK e, finalmente, reassinar o aplicativo.

Pré-carregamento de Símbolos

Acima, vimos técnicas que exigem algum tipo de modificação do código do aplicativo. Uma biblioteca também pode ser injetada em um processo usando funcionalidades oferecidas pelo carregador do sistema operacional. No Android, que é um sistema operacional baseado em Linux, você pode carregar uma biblioteca adicional definindo a variável de ambiente LD_PRELOAD.

Conforme afirma a página de manual do ld.so, os símbolos carregados da biblioteca passada usando LD_PRELOAD sempre têm precedência, ou seja, eles são pesquisados primeiro pelo carregador durante a resolução dos símbolos, efetivamente substituindo os originais. Esse recurso é frequentemente usado para inspecionar os parâmetros de entrada de algumas funções comumente usadas da libc, como fopen, read, write, strcmp, etc., especialmente em programas ofuscados, onde entender seu comportamento pode ser desafiador. Portanto, ter uma visão sobre quais arquivos estão sendo abertos ou quais strings estão sendo comparadas pode ser muito valioso. A ideia principal aqui é o "encapsulamento de função" (function wrapping), significando que você não pode aplicar patches em chamadas de sistema como fopen da libc, mas pode substituí-la (encapsulá-la) incluindo código personalizado que irá, por exemplo, imprimir os parâmetros de entrada para você e ainda chamar a fopen original, permanecendo transparente para o chamador.

No Android, definir LD_PRELOAD é um pouco diferente em comparação com outras distribuições Linux. Se você se lembra da seção "Visão Geral da Plataforma))", cada aplicativo no Android é bifurcado a partir do Zygote, que é iniciado muito cedo durante a inicialização do Android. Assim, definir LD_PRELOAD no Zygote não é possível. Como uma solução alternativa para esse problema, o Android suporta a funcionalidade setprop (definir propriedade). Abaixo, você pode ver um exemplo para um aplicativo com nome do pacote com.foo.bar (observe o prefixo adicional wrap.):

setprop wrap.com.foo.bar LD_PRELOAD=/data/local/tmp/libpreload.so

Observe que se a biblioteca a ser pré-carregada não tiver contexto SELinux atribuído, a partir do Android 5.0 (nível de API 21), você precisa desativar o SELinux para fazer o LD_PRELOAD funcionar, o que pode exigir root.