Saudações.
Hoje vou ensinar como usar o strace e o ltrace, comandos que me ajudam a resolver problemas no Linux.
Esses dois comandos sempre aparecem nas minhas conversas quando pergunto a um programador como o software dele funciona e ele responde “é secreto”, minha resposta é “nada é secreto no computador de outra pessoa”.
Ao apresentar esses comandos às pessoas elas perdem rapidinho a ilusão de segurança que antes tinham licenciando ou criptografando códigos PHP, Javascript ou Python.
Com maestria nesses programas você encerra pra sempre o famoso problema “na minha máquina funciona”.
Pré-requisitos:
- Sistema Linux, Alpine ou Debian;
- Internet no servidor (sua VPS ou host);
1 – Sobre o strace
Para explicar o que o strace faz, preciso explicar antes como funciona a execução de programas no Linux.
Sistemas operacionais são frameworks de execução de programas, ele fica entre o hardware e os programas que você executa (processos).
Isso é necessário para compartilhar recursos, como CPU, memória, rede, disco, etc. Em vez do software acessar diretamente o circuito eletrônico, o sistema operacional (kernel Linux) impede esse acesso direto colocando os processos para rodarem em modo não privilegiado na CPU, isso faz com que a CPU não obedeça às instruções de acesso direto ao hardware quando o programa do usuário está rodando (violar isso resulta em “ilegal instruçtion error”.
Conceito:
- User-Space: Todos os programas que rodam em uma CPU com acesso a instruções simples (acesso limitado);
- Kernel-Space: Todos os programas que rodam dentro do espaço do kernel e podem acessar todas as instruções de hardware diretamente.
Na prática, nenhum programa em user-space pode acessar recursos de hardware sem pedir ao kernel por meio de canais de software entre ele e o kernel – as APIs.
É aqui que o strace entra. Ele ordena ao kernel que copie todos os pedidos e respostas envolvendo um processo e seus filhos (threads, forks) para o espaço do strace, que por sua vez exibirá na tela (ou em arquivos de log) tudo que está acontecendo.
Eu utilizo o strace todo dia, sempre que um programa para de funcionar ou apresenta comportamento estranho eu assisto tudo que ele está fazendo e quais chamadas de API deram erros, principalmente seus argumentos (caminho de arquivos, nomes de DNS, IPs e portas, etc).
Ainda não é possível acessar o que o software faz quando ele usa instruções normais, todavia, se ele tentar qualquer acesso a APIs do kernel, acesso a arquivos e I/O, acesso a rede, sinais entre processos, o kernel fará a fofoca ao strace.
2 – Sobre o ltrace
O ltrace atua um pouco diferente, enquanto o strace foca na espionagem entre o processo e o kernel, o ltrace foca na espionagem entre o processo e suas bibliotecas.
Programas escritos em qualquer linguagem acabam por serem compilados com “linkagem dinâmica”, ou seja, no arquivo principal fica o programa e em outros arquivos ficam as funções que esse programa usa.
Exemplo: o NGINX é um software de servidor HTTP, para criptografar dados ele usa as bibliotecas do OpenSSL (libssl.so e libcrypt.so), para comprimir dados ele utiliza o ZSTD (libzstd.so).
Se substituirmos qualquer arquivo de biblioteca por outro manipulado, infectado ou capaz de extrair dados entre chamadas e retornos, estariamos fazendo um ataque de “Shared Library Hijacking“. Isso requer conhecimento avançado e dá muito trabalho embora seja assustadoramente eficiente.
Não queremos ter esse trabalho, mas podemos nos valer da possibilidade para nos intrometermos entre o programa principal e as bibliotecas, capturando a comunicação para analise. Essa é a função do ltrace.
3 – Instalando e usando
Vamos instalar os dois comandos:
# Atualizar o sistema base
apt -y update;
apt -y upgrade;
# Instalar strace e ltrace
apt -y install strace;
apt -y install ltrace;
# Ferramentas auxiliares:
apt -y install sysvinit-utils; # comando 'pidof'
3.1 – Usando o strace
Existem duas formas de usar o strace:
- Programas em execução: Basta descobrir o PID do processos. Se o processo roda dentro de container é recomendável descobrir o PID dele no host, o strace não roda bem em containers pois requer acesso privilegiado ao kernel;
- Antes de iniciar o processo: Precedendo o comando do programa com o comando strace, ele fará a ativação do monitoramento no kernel antes de chamar o execve() que cria o processo.
Monitorando programas em execução:
# Monitorar software "unbound" em execucao:
# 1. Descobrir o pid do bundound
pifof unbound;
# 1257 < retornou o numero do processo
# 2. Acionar strace para espionar as chamadas de API do kernel
strace -p 1257;
# Retorno:
# strace: Process 1257 attached
# epoll_wait(49, [{events=EPOLLIN, data=0x3}], 32, 226455) = 1
# recvfrom(3, "oo\1\0\0\1\0\0\0\0\0\0\6google\3com\0\0\1\0\1", ...
# sin_addr=inet_addr("127.0.0.1")}, [128 => 16]) = 28
# epoll_wait(49, [], 32, 0) = 0
# socket(AF_INET, SOCK_DGRAM, IPPROTO_IP) = 58
# setsockopt(58, SOL_IP, IP_MTU_DISCOVER, [5], 4) = 0
# bind(58, {sa_family=AF_INET, sin_port=htons(55762),...
# ou:
strace -p $(pidof unbound);
Monitorando programas ao executá-los (saída resumida para exemplo):
# Preceder o comando com strace
strace curl "https://api.ipify.org?format=json";
# Retorno:
# execve("/usr/bin/curl", ["curl", "https://api.ipify.org?format=jso"...],
# mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0)
# access("/etc/ld.so.preload", R_OK)
# openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC)
# openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libcurl.so.4", ...
# read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\0\0\0\0\0\0\0\0"...
# close(3)
# openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libz.so.1", ...
# ...
# openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libresolv.so.2", ...
# newfstatat(AT_FDCWD, "/etc/gnutls/config", ENOENT (No such file or directory)
# ...
# openat(AT_FDCWD, "/root/.curlrc", O_RDONLY) = -1 ENOENT
# openat(AT_FDCWD, "/root/.config/curlrc", O_RDONLY) = -1 ENOENT
# ...
# getsockname(4, {sa_family=AF_INET, sin_port=htons(61054),
# sin_addr=inet_addr("191.37.79.91")}, [128 => 16]) = 0
# ...
# write(1, "{\"ip\":\"191.37.79.91\"}", 21{"ip":"191.37.79.91"}) = 21
#
# close(3)
# close(4)
# exit_group(0)
# +++ exited with 0 +++
Com esses dois exemplos básicos você ja sentirá o poder do strace.
Alguns argumentos devem ser usados para melhor aproveitamento da analise:
- -f segue forks e sub-processos;
- -ff segue forks e sub-processos separadamente;
- -t exibe a hora, ex: “[pid 1222] 18:33:18“;
- -tt exibe a hora precisa, ex: “[pid 1216] 18:33:42.613544“, meu favorito;
- -ttt exibe o timestamp, ex: “[pid 1777] 1681079647.005505“, ideal para plotar capturas em timeline e calcular a latência entre chamadas e respostas;
- -T exibe o tempo de execução no final da chamada;
- -s 128 exibe apenas os primeiros 128 bytes dos parâmetros (argumentos, caontúdo de arquivos lidos e escritos);
- -i ativa exibição de ponteiro das instruções;
- -c exibe sumário estatístico (tempo, chamadas, erros);
- -o /tmp/strace-01.log salva as capturas em arquivos;
- -q não mostra mensagens de anexação/desanexação;
- -v verbosidade máxima sem abreviações;
- -e …=… filtrar as chamadas a monitorar, coloque o nome das chamadas separando-as por virgula sem espaço, exemplo:
- -e trace=open,openat,close: monitora abertura e fechamento de file-descriptors (arquivos, sockets);
- -e trace=connect,accept,listen,bind: monitora chamadas de rede;
- -e trace=file: monitora todas as operações de arquivos;
- -e trace=network: monitora todas as operações de rede;
- -e read=3,5: rastreia file descriptos de leitura 3 e 5;
- -e write=4: rastreia file descripto de escrita 4;
- -e chdir,getcwd: rastreia operações de mudança de diretório corrente;
- -e open,close: rastreia abertura e fechamento de file descriptos;
- -e malloc,calloc,realloc,free: rastreia alocações de memória;
Exemplos rodando o comando “sleep 3”:
# Analise completa:
strace -tt -ff -T -s 128 sleep 3;
# Analise de abertura e fechamento de arquivos:
strace -tt -ff -T -s 128 -e trace=open,openat,close sleep 3;
# Analise de uso de rede:
strace -tt -ff -T -s 128 -e trace=connect,accept,listen,bind,getsockopt,setsockopt \
curl "https://api.ipify.org?format=json";
3.2 – Usando o ltrace
O ltrace é sem dúvida o mais assustador dos analisadores de processos, ele revela todos os segredos, strings, chaves, senhas, variáveis, atributos e o que o processo fizer uso.
Ele pode ser usado antes de rodar o processo ou em um processo já em execução (-p pid).
Exemplo:
# Analisar chamadas de funcoes em bibliotecas do comando curl:
ltrace curl "https://api.ipify.org?format=json";
# fcntl(0, 1, 0x7fff68812440, 0)
# fcntl(1, 1, 0, 0x7f07a9872aa0)
# fcntl(2, 1, 0, 0x7f07a9872aa0)
# signal(SIGPIPE, 0x1)
# malloc(1264)
# curl_global_init(3, 1264, 0, 0x561607cf39a0)
# curl_version_info(11, 0x7f07a9a3f6c0, 0, 76)
# curl_strequal(0x5615eb6f8ee3, 0x7f07a9a3acb9, 0, 0x7f07a9a71aeb) = 0
# ...
# strncmp("libssh2", "libssh2/1.11.1", 7)
# setlocale(LC_ALL, "")
# setlocale(LC_NUMERIC, "C")
# strcmp("https://api.ipify.org?format=jso"..., "--disable")
# curl_getenv(0x5615eb6f80bf, 1, 1, 45)
# curl_getenv(0x5615eb6f80c9, 1, 1, 45)
# open("/root/.curlrc", 0, 00)
# open("/root/.config/curlrc", 0, 00)
# geteuid()
# ...
# strncmp("url", "expand-", 7)
# strcmp("url", "ntlm-wb")
# strcmp("url", "retry-max-time")
# strcmp("url", "tftp-blksize")
# strcmp("url", "trace-config")
# strcmp("url", "user")
# strcmp("url", "upload-flags")
# strcmp("url", "url-query")
# strcmp("url", "url")
# ...
# strdup("https://api.ipify.org?format=jso"...)
# strdup("curl/8.14.1")
# ...
# memcpy(0x561607d0e220, "https://api.ipify.org?format=jso"..., 33)
# curl_easy_init(0, 0x561607d0c430, 1, 1)
# ...
# strcmp("CURLOPT_BUFFERSIZE", "CURLOPT_SSL_VERIFYPEER")
# strcmp("CURLOPT_BUFFERSIZE", "CURLOPT_SSL_VERIFYHOST")
# strcmp("CURLOPT_BUFFERSIZE", "CURLOPT_SSL_ENABLE_NPN")
# ...
# free(nil)
# free(nil)
# free(nil)
# free(0x561607cf39a0)
# +++ exited (status 0) +++
Argumentos e opções:
- -l /lib/libc.so.6 rastreia somente funções dessa biblioteca;
- Liste as bibliotecas com o comando ldd caminho_binario;
- -L para não mostrar chamadas para bibliotecas padrão;
- -f para rastrear processos filhos (forks);
- -o /tmp/ltrace-01.log salva as capturas em arquivos;
- -S para monitorar chamadas de sistema (strace);
- -t exibe o timestamp das chamadas;
- -n 2 indentar saída (aninhamento de chamadas);
- -s 128 exibe apenas os primeiros 128 bytes dos parâmetros (argumentos, caontúdo de arquivos lidos e escritos);
- -e …=… filtrar as chamadas a monitorar:
- -e malloc+free monitora alocação de memória virtual;
- -e “printf*” monitorar funções que comecem com “printf”;
- x
- x
- -e trace=file: monitora todas as operações de arquivos;
- -e trace=network: monitora todas as operações de rede;
- -e read=3,5: rastreia file descriptos de leitura 3 e 5;
- -e write=4: rastreia file descripto de escrita 4;
- -e chdir,getcwd: rastreia operações de mudança de diretório corrente;
- -e open,close: rastreia abertura e fechamento de file descriptos;
- -e malloc,calloc,realloc,free: rastreia alocações de memória;
Mais exemplos:
# Listar bibliotecas do binario:
ldd $(which ls);
# linux-vdso.so.1 (0x00007f289807c000)
# libselinux.so.1 => /lib/x86_64-linux-gnu/libselinux.so.1
# libcap.so.2 => /lib/x86_64-linux-gnu/libcap.so.2
# libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6
# libpcre2-8.so.0 => /lib/x86_64-linux-gnu/libpcre2-8.so.0
# /lib64/ld-linux-x86-64.so.2
# Rastrear alocações de memória apenas
ltrace -e malloc,calloc,realloc,free ls /tmp;
# Rastrear funções de string
ltrace -e "str*" ls /tmp;
# Sumário de chamadas de biblioteca
ltrace -c ls /tmp;
# ltrace + strace combinados
ltrace -S -e malloc+write ls /tmp;
# Rastrear apenas libc
ltrace -l /lib/x86_64-linux-gnu/libc.so.6 ls /tmp;
4 – Outros programas
Existem outros programas (comandos) que você pode aprender os 1% que faltam:
sysdigecsysdigperfbpftracelsoffatraceopensnooppcstatsstcpdumpnetstatvalgrindheaptrackpmapexecsnoop
Existem softwares feitos por pessoas paranóicas que aprenderam essas ferramentas e criaram artifícios para contorná-las, isso é perda de tempo.
O nível mais profundo e indefensável é o uso de QEMU-KVM.
Ao criar uma maquina virtual que faça a extração direto na CPU e memória da VM é possível enxergar absolutamente TUDO que um software faz, mesmo que esse software seja um módulo do kernel:
- Snapshot de Memória: Muito simples de realizar, quando o software está rodando na VM o dump da RAM é realizado para um arquivo binário, tudo que o software fez, desde senhas até chaves privadas são revelados;
- QEMU e o GDB Interativo: Permite controlar a execução da máquina virtual em tempo real e assistir linha a linha as instruções no processador e cada bloco lido ou escrito na memória;
- Memflow: Captura de tráfego entre a VM e a RAM em tempo real.
Bom… agora você já sabe como abrir as tripas de qualquer software em execução.
Terminamos por hoje!
Patrick Brandão, patrickbrandao@gmail.com
“Quem tem medo não mama em onça“
Ditado brasileiro
