2 pontos por GN⁺ 2025-01-10 | 1 comentários | Compartilhar no WhatsApp
  • A conversão de caracteres Best-Fit do Windows substitui caracteres por outros visualmente parecidos ao converter strings UTF-16 para páginas de código ANSI, e esse comportamento se torna a superfície de ataque WorstFit, levando a Path Traversal, Argument Injection e RCE
  • O problema surge de uma estrutura em que se sobrepõem a API ANSI, o runtime de C/C++, o código de inicialização inserido pelo compilador e o uso de APIs de caracteres não wide pelo desenvolvedor; os caminhos GetCommandLineA, GetEnvironmentVariableA, getenv e int main() são afetados
  • A CVE-2024-4577 contornou o patch do PHP-CGI quando U+00AD passou a - em páginas de código chinesas/japonesas, e o Filename Smuggling cria confusão de caminho quando ¥, e barra fullwidth viram / ou \\
  • Argument Splitting pode injetar argumentos em ferramentas CLI como wget.exe, tar.exe, openssl.exe e java.exe quando aspas duplas fullwidth ou os sinais de iene/won viram caracteres de parsing da linha de comando, algo difícil de bloquear apenas com o escaping comum de argumentos em PHP, Python, Node.js e Rust
  • Para mitigar, é preciso ativar a opção UTF-8 do Windows ou usar a Wide Character API e caminhos wide como _wgetcwd, _wgetenv e wmain(); até que a Microsoft ative o UTF-8 por padrão em todas as edições do Windows, problemas semelhantes podem continuar se repetindo

Estrutura de codificação do Windows e Best-Fit

  • O Windows usava inicialmente páginas de código ANSI, e variava conforme o idioma, com páginas de código como 1252, 932, 936, 949 e 950
    • ACP (ANSI Code Page) é usada na maioria das aplicações e configurações do sistema, como operações de arquivo e variáveis de ambiente
    • OEMCP (Original Equipment Manufacturer Code Page) é usada principalmente em comunicação com dispositivos, como leitura/gravação no console
    • chcp mostra a OEMCP, não a ACP, portanto não serve para verificar a ACP, que é o foco desta pesquisa
  • O Windows migrou para Unicode em meados dos anos 1990, e hoje as APIs centrais usam wide character com base em UTF-16
    • APIs essenciais como sistema de arquivos, informações do sistema e processamento de texto migraram para APIs de caracteres wide
    • Há suporte a UTF-8, mas ele não vem ativado por padrão na maioria das linguagens, e o texto o descreve como estando em fase beta
  • Por compatibilidade retroativa, a API do Windows oferece versões ANSI e Unicode ao mesmo tempo
    • APIs ANSI têm sufixo A, como GetEnvironmentVariableA
    • APIs Unicode têm sufixo W, como GetEnvironmentVariableW
    • Quando uma API ANSI é chamada, o Windows converte internamente a string UTF-16 em string ANSI com RtlUnicodeStringToAnsiString ou WideCharToMultiByte

Como o Best-Fit vira WorstFit

  • Best-Fit é o comportamento de mapear um caractere UTF-16 para outro visualmente ou semanticamente parecido quando ele não pode ser representado com exatidão na página de código ANSI de destino
    • Por exemplo, em Windows-1252, U+221E é mapeado para 8
    • √π⁷≤∞ pode virar "vp7=8" ao passar por uma API ANSI
  • O mapeamento funciona de forma diferente em cada página de código
    • ¥ U+00A5 é mapeado para \\ na página de código japonesa 932
    • Na página de código centro-europeia 1250, ele é mapeado para Y
    • Na maioria das outras páginas de código, ele não é alterado
  • A mesma conversão acontece não só em chamadas diretas à API do Windows, mas também em funções CRT e no caminho da função main comum
    • A conversão Best-Fit é aplicada em funções CRT non-wide como getenv
    • Ela também intervém quando argumentos e variáveis de ambiente são recebidos no formato int main(int argc, char* argv[], char* envp[])
    • Isso ocorre pela combinação do código de inicialização CRT inserido pelo compilador com o uso de APIs ANSI do Windows
  • Para verificar os mapeamentos, é possível consultar o Best-fit Mapping Grepper e os dados brutos de mapeamento WindowsBestFit da Unicode.org

Primeiro caso de WorstFit: PHP-CGI CVE-2024-4577

  • CVE-2024-4577 é um caso de ataque WorstFit que permitia comprometer um servidor PHP-CGI configurado com páginas de código chinesas/japonesas apenas com uma requisição ?%ADs
    • As páginas de código afetadas são 932 (japonesa), 936 (chinês simplificado) e 950 (chinês tradicional)
    • O caractere ameaçador é ­ U+00AD
  • A vulnerabilidade do PHP-CGI de 2012 era uma injeção de argumentos causada porque o Apache tratava automaticamente a query string como o primeiro argumento do programa CGI
    • Ao anexar ?-s, era possível vazar o código-fonte da página e obter RCE
    • O patch do PHP interrompia o parsing de argumentos se a query string começasse com um dash
  • Por causa do Best-Fit, o soft hyphen U+00AD é convertido em - em páginas de código chinesas/japonesas, contornando o patch existente
    • ?%ADs pode funcionar como -s do ponto de vista do PHP-CGI
    • Foi esse caso que levou a equipe de pesquisa a se deparar pela primeira vez com o termo Best-Fit

Filename Smuggling: o problema da conversão de caracteres de caminho

  • Filename Smuggling é um ataque em que caracteres Unicode no nome de arquivo viram / ou \\ no caminho ANSI, permitindo criar path traversal
    • As APIs relacionadas incluem GetCurrentDirectoryA, getcwd, FindFirstFileA, findfirst* e GetFullPathNameA
    • As páginas de código afetadas são 874, 125x, 932 (JP) e 949 (KR)
    • Os caracteres ameaçadores são U+FF0F, U+FF3C, ¥ U+00A5 (JP) e U+20A9 (KR)
  • O d8.exe, Developer Shell do Chrome V8, usa GetCurrentDirectoryA() na implementação interna para obter o diretório de trabalho atual
    • Se for possível criar um diretório de trabalho com caracteres Unicode maliciosos, o acesso via API ANSI pode se transformar em payload de path traversal
    • Como exemplo, isso pode permitir um acesso não intencional a C:/windows/win.ini
  • A implementação de Windows de Dir.getwd() no mruby depende da função ANSI CRT _getcwd()
    • O valor de retorno pode ser contaminado e levar a Path Traversal

Cuckoo Sandbox: de Path Traversal a RCE

  • O acesso do Python ao sistema de arquivos do Windows podia usar a API wide ou a API ANSI dependendo de a string ser wide ou narrow
    • Após a PEP 529, a codificação do sistema de arquivos do Windows foi padronizada em UTF-8
    • O Python 2 e o Python 3 antes do 3.6 continuaram vulneráveis ao ataque WorstFit
  • O Cuckoo Sandbox é uma plataforma automatizada de análise de malware, e a versão oficial mais recente depende de Python 2.7
    • O Cuckoo é composto por Cuckoo Host e VM Cluster
    • As amostras enviadas são executadas de forma isolada na VM, e pacotes de rede, arquivos dropados e logs são sincronizados por um mecanismo próprio
  • Se um malware criar um arquivo dropado com nome de arquivo Unicode, pode ocorrer Path Traversal no processamento de caminhos em Python no Cuckoo Host
    • O PoC de exemplo cria o caminho AAAA\u00a5..\u00a5..\u00a5..\u00a5..\u00a5..\u00a5conf\u00a5cuckoo.conf
    • Após o fim da análise, quando o usuário clica no botão de download na interface web, operações de arquivo em Python são acionadas
    • O Cuckoo Host processa o caminho convertido com ../ incluído e pode enviar dados sensíveis ao atacante
  • O atacante pode baixar cuckoo.conf e coletar informações sensíveis necessárias para calcular o Flask PIN, alcançando RCE no Sandbox Host
    • O vídeo de demonstração é o Video 11

Argument Splitting: Best-Fit que altera o parsing da linha de comando

  • Argument Splitting é um ataque em que a string da linha de comando é alterada e os argumentos são divididos na saída de GetCommandLineA ou no caminho non-Unicode de int main()
    • APIs e caminhos relacionados: GetCommandLineA, int main()
    • Code pages afetadas: 874, 125x, 932(JP), 949(KR)
    • Caracteres de risco: U+FF02, U+FF3C, ¥ U+00A5(JP), U+20A9(KR)
  • Um exemplo em PHP executa wget.exe -q após envolver a URL com segurança usando escapeshellarg(), mas a entrada " --use-askpass=calc " permite executar calc.exe
    • A mesma entrada não é bloqueada mesmo se o código for reescrito em Node.js, Rust ou Python
    • Isso também funciona no exemplo com subprocess.run(["wget", "-q", ...]) das versões mais recentes do Python
  • O Windows passa toda a linha de comando para o novo processo como uma única string, e o executável a analisa diretamente
    • Ao contrário dos sistemas tipo UNIX, não há uma estrutura em que um array de argumentos é sempre passado
    • A API CreateProcess recebe diretamente o parâmetro lpCommandLine
  • Na análise comum de linha de comando, os caracteres importantes são espaço e tab, aspas duplas e barra invertida
    • Espaço e tab dividem argumentos quando não se está em quote mode
    • " alterna o quote mode
    • \ escapa aspas duplas e barras invertidas em sequências específicas
  • As bibliotecas padrão da maioria das linguagens escapam os argumentos do usuário conforme essas regras, mas o escaping termina antes da conversão Best-Fit
    • O escapeshellarg do PHP troca aspas duplas por espaço, envolve o argumento em aspas e processa barras invertidas
    • O subprocess do Python faz o escaping com list2cmdline conforme as regras de parsing de linha de comando do Microsoft CRT
    • Depois, se U+FF02 for convertido para " U+0022 na conversão ANSI, a sintaxe original da linha de comando muda
  • Programas que usam apenas int main() também podem ser vulneráveis
    • O compilador gera mainCRTStartup no binário, e essa função de inicialização se liga à biblioteca CRT
    • Se o interior da CRT buscar e analisar a linha de comando via API ANSI, a conversão Best-Fit entra em ação
    • Por causa desse comportamento, é difícil bloquear completamente o ataque apenas com a biblioteca padrão de uma linguagem específica

Casos reais de Argument Splitting

  • O ElFinder é um gerenciador de arquivos web open source com backend em PHP, com suporte padrão a servidor Windows e criação e extração de arquivos compactados
    • O processamento de arquivos compactados é implementado com execução de shell command, e os argumentos são escapados com escapeshellarg
    • Para o formato tar, é usado o tar.exe nativo do Windows
    • Com um nome de arquivo tar como aaa" "--use-compress-program=calc" "bbb.tar, é possível injetar o argumento --use-compress-program e executar comandos arbitrários
    • A demonstração usa um Windows server configurado em inglês, com base na Code Page 1252, e conclui que também deve funcionar nas code pages 125x e na Code Page 874
    • O vídeo de demonstração é o Video 12
  • No caso do plink.exe modificado usado no TortoiseGit, inserir uma URI maliciosa como entrada de clone pode acionar execução de código
  • O RStudio oferece suporte a controle de versão SVN, e se houver um projeto SVN em uma pasta criada maliciosamente, um clique basta para executar a calculadora
  • O caso do Microsoft Excel é a CVE-2024-49026, que combina Argument Splitting com o recurso “Open-With” do Windows
    • O Windows mantém uma handler table por extensão de arquivo, que pode ser verificada com ftype e assoc
    • Como o nome do arquivo se torna parte dos argumentos do programa handler, o ataque pode ser aplicado via nome de arquivo
    • Um nome de arquivo que substitui pontos, slash, backslash e aspas duplas por formas fullwidth provoca argument injection em Excel.exe
    • O próprio Excel não tem argumentos adequados para exploração adicional, então NTLM Relay e RBCD/ADCS são usados em conjunto para alcançar RCE
    • O vídeo de demonstração é o Video 15

Confusão de variáveis de ambiente

  • A confusão de variáveis de ambiente ocorre porque GetEnvironmentVariableA, GetEnvironmentStringsA e char *getenv() retornam versões convertidas por Best-Fit das variáveis de ambiente
    • A code page afetada e os caracteres problemáticos não foram especificados
    • No caso do Apache HTTPd, 0x00-0xFF está envolvido
  • Para que esse ataque funcione, a variável de ambiente precisa poder ser controlada pelo usuário
    • Isso inclui casos em que o processo pai passa informações ao processo filho que ele criou
    • Em CGI, grande parte das informações da requisição HTTP, como query string e cabeçalhos HTTP, é passada por variáveis de ambiente
  • O exemplo de bypass de WAF trata de uma situação em que o script CGI atua como um serviço de roteamento
    • Na configuração do Apache, existe uma regra que rejeita REQUEST_URI contendo /admin, bloqueando o acesso remoto a /cgi.pl/admin
    • Por causa do comportamento WorstFit do Perl no Windows, é possível contornar isso substituindo parte de admin por um equivalente Best-Fit
    • Na Code Page 1250, à U+00E0 é convertido para a durante a conversão ANSI
    • A requisição /cgi.pl/%E0dmin parece ser outro caminho para a regra no lado do servidor, mas quando o script Perl CGI lê PATH_INFO via API ANSI, ela é tratada como /admin
  • No PHP-CGI no Windows, foram observados um oracle de verificação de existência de arquivo e uma LFI potencial em certas configurações
    • A causa é a forma como PATH_INFO e outras variáveis de ambiente relacionadas a caminho são tratadas
    • A requisição /index.php/foo/bar é passada pelo Apache como variáveis de ambiente como REDIRECT_URL, REQUEST_URI, PATH_INFO e PATH_TRANSLATED
    • Só com essas informações, é difícil distinguir claramente o limite entre o nome do arquivo PHP e o PATH_INFO adicional, e o php-cgi.exe interpreta isso
  • Na code page japonesa, o uso de ¥ faz com que o servidor web e o PHP-CGI interpretem o caminho de forma diferente
    • O servidor web trata /..¥..¥windows/win.ini/foo inteiro como PATH_INFO adicional
    • O PHP-CGI recebe um valor convertido como REQUEST_URI=/index.php/..\\..\\windows/win.ini/foo e se confunde ao separar o arquivo PHP real do PATH_INFO
    • No Apache, a diferença de resposta entre um arquivo inexistente e um existente permite um oracle de existência de arquivo
    • No IIS, se a diretiva doc_root estiver configurada, é possível fazer LFI com um caminho como /index.php/..¥..¥..¥windows/win.ini/, incluindo e lendo C:\Windows\win.ini
    • Se o arquivo incluído puder ser executado ou contiver código controlado pelo usuário, isso pode levar a RCE potencial, mas esse cenário é classificado mais como um bug raro em aplicações reais

Dificuldades no processo de divulgação e correção

  • A equipe de pesquisa reportou vários problemas em linguagens de programação, projetos open source e programas CLI embutidos do Windows aos respectivos mantenedores upstream
    • A maior controvérsia ocorreu em Argument Splitting
    • Alguns fornecedores consideraram que o próprio ato de passar entrada do usuário para a linha de comando já é a vulnerabilidade
  • A falta de clareza sobre a responsabilidade também foi um problema
    • O código problemático envolve mainCRTStartup(), inserido automaticamente durante a compilação, e chamadas internas de API ANSI em MSVCRT/UCRT
    • É difícil distinguir se o problema é o desenvolvedor não ter usado wmain() ou o CRT ter dividido a linha de comando incorretamente e passado argumentos errados para main()
    • Alguns projetos fornecem apenas o código-fonte, enquanto executáveis precompilados para Windows são distribuídos por voluntários terceiros na internet
  • A correção não se resume a trocar main() pela versão wide-character
    • Quando a assinatura da função muda, é preciso reescrever as definições de variáveis e a lógica de parsing de argumentos de char * para wchar_t *
    • Esse processo é doloroso e propenso a erros
  • O Curl respondeu que isso é um recurso do Windows e que não tem plano de correção; já o Curl portado pela Microsoft alterou a entrada para wmain(), então o curl.exe embutido no Windows não é afetado
    • Os binários oficiais do Curl são afetados pelo ataque de Argument Splitting
    • O relatório completo foi publicado no HackerOne
  • O OpenSSL pode processar argumentos em formato wide character por meio da variável de ambiente OPENSSL_WIN32_UTF8
    • O objetivo original era corrigir problemas de exibição de UTF-8 na UI, mas isso também mitiga o ataque de Argument Splitting
    • No uso padrão do OpenSSL, muitos desenvolvedores não sabem que precisam definir essa variável de ambiente, e a execução arbitrária de código é possível usando o argumento -engine
  • A distribuição oficial do Perl não fornece executáveis precompilados para Windows, e instaladores de terceiros como Strawberry Perl e ActiveState Perl são comumente usados
    • As duas distribuições são afetadas pelo ataque de Argument Splitting
    • Após discussão com os mantenedores do Perl, a conclusão foi que isso “se parece mais com um bug da Microsoft do que com um bug do Perl”, e por isso o caso segue sem solução
  • Três casos foram reportados à MSRC, e todos foram inicialmente rejeitados por não atenderem ao critério de gravidade
    • Após várias reaberturas, apenas o caso do Excel foi aceito na terceira tentativa
    • Os outros casos seguem sem solução até agora
    • A MSRC respondeu que eles dependem de uma vulnerabilidade em que um aplicativo separado executa entrada não confiável na linha de comando, e que a técnica em si que torna isso explorável não atende aos requisitos para ser considerada uma vulnerabilidade
  • Também foi solicitado apoio ao CERT/CC, e alguns meses depois a Microsoft adicionou um aviso de segurança à documentação de GetCommandLineA
    • O aviso foi adicionado apenas a GetCommandLineA, e ainda restam outras APIs ANSI que exigem cautela

Alvos afetados reportados e status

  • Os itens confirmados e reportados durante o processo de divulgação foram os seguintes
    • 2024/05/07: PHP php-cgi.exeCVE-2024-4577
    • 2024/06/13: Curl Official BuildWon’t Fix
    • 2024/06/13: Apache Subversion svn.exeCVE-2024-45720
    • 2024/06/16: Microsoft Tar tar.exe — Won’t Fix
    • 2024/06/19: Microsoft Excel excel.exeCVE-2024-49026
    • 2024/06/19: Microsoft PhoneBook rasphone.exe — Won’t Fix
    • 2024/06/19: Oracle Java java.exe — Pending Fix
    • 2024/06/19: Perl perl.exe — Won’t Fix
    • 2024/07/15: Perforce p4.exeCVE-2024-8067
    • 2024/08/05: PostgreSQL psql.exe — Won’t Fix
    • 2024/08/08: Putty plink.exe — Fixed
    • 2024/08/19: OpenSSL openssl.exe — Other
    • 2024/08/19: wkhtmltopdf wkhtmltopdf.exe — EOL
    • 2024/08/19: GNU Wget — No Reply

Mitigações e superfície de ataque restante

  • Como o ataque WorstFit é um problema no nível do sistema operacional, questões semelhantes podem continuar reaparecendo até que a Microsoft ative o UTF-8 por padrão em todas as edições do Windows
  • A medida que o usuário pode tomar é verificar e ativar a opção UTF-8 do Windows
    • Esse recurso ainda é marcado como beta, e não está claro se há efeitos colaterais
  • Os desenvolvedores devem usar a Wide Character API sempre que possível
    • A CRT também oferece versões wide character, como _wgetcwd e _wgetenv
    • Se caminhos non-wide continuarem sendo usados, a implementação interna pode chamar a API ANSI e ficar exposta ao ataque WorstFit
  • Por causa da compatibilidade retroativa do Windows, pode haver mais lugares onde APIs ANSI estejam ocultas
    • Como exemplo, consultas ao Registro do Windows, como RegQueryValueA, podem ser afetadas, mas é preciso encontrar um cenário vulnerável
    • A equipe de pesquisa também observou o comportamento Best-Fit no Active Directory

1 comentários

 
GN⁺ 2025-01-10
Opiniões do Hacker News
  • Este é um problema bem complicado. O mapeamento de código “best fit” da Microsoft é um mapeador público, mas na prática “baseado em intuição”, que transforma Unicode amplo em ASCII e está espalhado por todo o sistema.
    Esse mapeador é vinculado por padrão em inúmeros lugares e, pela forma como a Microsoft encara compatibilidade retroativa, parece inevitável que continue incluído. Os exploits geralmente surgem quando code points incomuns são mapeados “por semelhança” para barra, hífen, aspas etc. Dentro de linguagens modernas, eles são verificados como Unicode correto, mas, ao serem passados para comandos de shell ou para a API Win32, sofrem uma redução diferente depois que o controle já foi entregue. Como disse o mantenedor do curl, aqui “o curl é a vítima”; a questão é quem é o culpado. Se o servidor amassa a entrada do usuário de um jeito ao validá-la e de outro ao passá-la para a biblioteca do sistema, no fim aparece um problema. Uma opção para desativar a conversão best fit no lado do Win32 poderia ser a solução, mas não sou especialista em Windows, então é só um palpite. Mesmo assim, continuaria havendo interação com APIs oficiais ou com softwares que ainda não a desativaram

    • O opt-out é usar a API Windows Unicode, ou seja, usar funções que terminam em "w", não em "a". Essa abordagem também resolve o problema de caminhos com mais de 260 caracteres se você adicionar o prefixo "\\?\" ou configurar corretamente o manifesto, e está disponível e é recomendada desde o Windows XP.
      Não sei bem por que APIs não Unicode ainda são tão usadas. É difícil imaginar que seja por vontade de dar suporte ao Windows 98 ou ao Windows 2000
    • O Windows tem, desde o Windows XP, arquivos de manifesto como forma de desativar comportamentos legados. Se não me engano, sem um manifesto nem o GetWindowsVersion retornava a versão atual. Acrescentar um opt-out ali e, algum dia, torná-lo o padrão do Visual Studio não parece muito difícil.
      Outra coisa necessária é algum tipo de linting. Em aplicações modernas, normalmente não há motivo para chamar funções ANSI da WinAPI. Também existe a abordagem de definir o locale como UTF-8 e usar apenas funções de 8 bits, mas não sei quão bem isso funciona. Pelo que sei, também há algumas configurações e headers para fazer argv, printf e std::cout funcionarem em UTF-8 e usar apenas funções de conversão UTF-8/UTF-16 para WinAPI, sem conversões estranhas. A Microsoft deveria documentar esse procedimento em um só lugar
    • Seja ou não uma vulnerabilidade de segurança, se o curl não consegue lidar corretamente com argumentos Unicode no Windows, isso também é um bug do curl
    • Essa forma de mapear code points para caracteres de maneira frouxa sempre me incomodou no Unicode
  • Isto é previsível até certo ponto, mas foi novidade mesmo para alguém que fez desenvolvimento Windows e hacks na API do Wine por uns 10 anos na época em que surgia a confusão W/A.
    O Windows é como o jogo de cartas Munchkin: quando vários recursos se encaixam por acaso, eles podem se combinar em exploits inacreditavelmente aleatórios e poderosos. É bom ver que o subsistema ANSI está sendo migrado para UTF-8, e em tese isso pode mitigar muitos desses problemas. Fico curioso se a equipe do Rust talvez precise fazer mais uma correção na API de criação de processos

    • A biblioteca padrão do Rust basicamente não usa a API ANSI por padrão. O texto não mostrou um ataque que funcione contra Rust; se houver um, seria bom reportá-lo.
      Claro que Rust não consegue controlar o que acontece além da fronteira do processo. Se uma aplicação executada por Rust usa a API ANSI, o problema aparece ali, mas isso é responsabilidade dessa aplicação
  • “Eliminar o ANSI gradualmente e recomendar o uso da Wide Character API” era a posição oficial da Microsoft desde o NT 3.5, se minha memória não falha.
    Infelizmente, um dos grandes obstáculos é a forma como a biblioteca de runtime C/C++ da Microsoft, msvcrt.dll, é implementada. Funções wide não padrão como _wfopen() e _wgetenv() usam internamente as funções W da Win API, mas funções narrow padrão como fopen() e getenv() simplesmente usam as funções A, em vez de converter para as versões wide. E as funções A geralmente não reportam falhas de conversão Unicode; elas as encobrem com best-fit. Quem porta software escrito em C para Windows não quer trocar todo uso de funções padrão por funções Microsoft não portáveis. A partir daí, é praticamente uma reescrita completa

    • A impressão que tive lendo a documentação da Microsoft nos últimos 2 anos foi a oposta: definir activeCodePage como UTF-8 no manifesto da aplicação e usar apenas funções “ANSI”
    • Em código portável, quando é um build para Windows, funções padrão como main e fopen são definidas com #define para suas correspondentes wide.
      Com isso, não dá para usar simplesmente char* e literais de string sem decoração, então define-se um tipo tchar, que é char no Linux e wchar_t no Windows, e a macro _T() para literais de string. Em geral funciona bem sem precisar pensar muito
    • O que realmente irrita hoje é que, ao pesquisar a Win32 API no Google, quase sempre aparece primeiro a variante -A, não a variante -W. Não sei se há algo estranho no robots.txt, mas é esquisito que uma API que recomenda usar a variante -W em código novo retorne por padrão a API legada
    • O runtime C/C++ da Microsoft, msvcrt.dll, foi substituído pelo Universal C Runtime (UCRT)[1], e o UCRT é compatível com C99
    • O Windows deveria ter oferecido uma API que tratasse nomes de caminho simplesmente como sequências de bytes, sem esse tratamento idiota de codificação. Acho que dava para ter feito isso quando introduziram caminhos UNC
  • Há duas formas de forçar a code page “Ansi” para UTF-8 de fato em uma aplicação que você escreveu ou em um EXE que você patchou.
    Uma é usar um arquivo de manifesto, e funciona a partir de determinados builds do Windows 10. Também dá para aplicar a qualquer EXE depois do build, então é possível enfiar suporte a UTF-8 à força em um programa. É especialmente útil para programas em modo console. A outra é usar o hack empregado por ferramentas do tipo “App Locale”. Um dos métodos envolve chamar funções não documentadas da NTDLL. Não sei exatamente quais funções são necessárias, mas RtlInitNlsTables e RtlResetRtlTranslations podem estar relacionadas

  • Não sei se a Microsoft algum dia vai ativar UTF-8 por padrão em todas as edições do Windows. Há muitos aplicativos antigos que assumem uma página de código específica ou 1 byte por caractere, então isso poderia quebrá-los
    De forma mais sutil, também há aplicativos que reutilizam buffers existentes ao assumir que, ao converter de caracteres wide para ANSI, o número de bytes não aumenta. Isso não é verdade em UTF-8, e como era em geral verdade na maioria das páginas de código antigas, novas vulnerabilidades podem surgir. Acho que seria muito menos propenso a quebrar coisas simplesmente remover a lógica Best-Fit das APIs Win32 xxxA e substituir caracteres impossíveis de mapear por algo como x, que não tem significado meta comum

    • Um exemplo desse tipo de aplicativo é o Adobe After Effects[0]. Pelo menos era assim no passado; hoje não uso Windows
      [0] https://tambre.ee/blog/adobe_after_effects_windows_utf-8/
    • Se ainda não existir, talvez desse para introduzir uma versão de API do sistema operacional, de modo que apps novos/atualizados que miram uma nova versão de API ou um novo SDK passem a assumir UTF-8 por padrão. Abaixo de certa versão de API, bastaria emular em modo legado. O Windows já tem o conceito de shim, que imita o comportamento de várias versões do Windows
    • Mesmo antes do UTF-8 no Windows, já existia o problema de apps se comportarem de forma estranha quando a página de código padrão era alterada. Portanto, é razoável dar ao usuário uma opção de UTF-8
      Vendo os problemas causados pelo mapeamento Best-Fit, também é razoável torná-lo o padrão, mas a Microsoft deveria ajudar os usuários a encontrar uma forma fácil de executar código antigo. Uma abordagem menos razoável seria remover, no mapeamento Best-Fit, todos os mapeamentos que levam a caracteres ASCII “especiais”, mas isso não ajuda apps que vinculam a CRT estaticamente. Também não corrige a vulnerabilidade, então não é uma boa solução. Às vezes, vulnerabilidades de segurança viram motivação para empurrar uma quebra de compatibilidade retroativa
  • A Microsoft sabia desse problema havia pelo menos 1 ano. Afinal, publicou uma regra especial de análise de código chamada CA2101[1], que desencoraja explicitamente o uso de mapeamento best-fit
    A descrição da regra mencionava vulnerabilidades de segurança, mas os detalhes foram deixados propositalmente vagos
    [1] https://learn.microsoft.com/en-us/dotnet/fundamentals/code-a...

  • Não é necessário trocar tudo de char * para wchar *. Você pode converter os caracteres wide recebidos para UTF-8 ou, se quiser aceitar até sequências inválidas como substitutos sem par, converter para algo como o WTF-8 do Rust e continuar usando char
    Claro que é preciso tomar cuidado para não misturar strings ANSI ou OEMCP com strings UTF-8, mas, se você simplesmente usar só UTF-8, fica fácil. Essa é a abordagem recomendada pelo clássico site https://utf8everywhere.org/

  • No meu computador pessoal com Windows, eu vinha evitando esse bug por acaso graças ao modo UTF-8 ativado há alguns anos. É a configuração mostrada no fim do texto
    Eu a tinha ativado porque jogos estrangeiros antigos exibiam caracteres quebrados e, embora esteja marcada como “Beta”, não percebi bugs nem efeitos colaterais

    • Interessante, mas no meu caso essa caixa de seleção não fez nada além de causar crash em um monte de apps aleatórios. Parece que o bom funcionamento depende de qual é a página de código padrão do usuário quando ela está desativada
    • Acabei de ativar a opção “Beta: Use Unicode UTF-8 for worldwide language support”. Vai ser interessante ver quantos apps vão quebrar
  • Fiquei curioso para saber se a caixa de seleção beta era equivalente a definir ActiveCodePage como UTF-8 no manifesto, mas a documentação[0] deixa claro que o GDI não segue a página de código por processo e segue apenas a página de código global única definida por essa caixa
    É um pouco decepcionante não poder fazer opt-in completo para UTF-8 nas APIs *A dentro do próprio app. Ainda assim, para os problemas destacados no texto, acho que ela continua sendo uma solução de contorno válida ou uma medida de defesa em profundidade
    [0] https://learn.microsoft.com/en-us/windows/apps/design/global...

  • Caramba. Eu sabia que a API do Windows oferecia esse tipo de conversão best-fit, mas não sabia que esse era o comportamento padrão de várias funções ANSI na minha página de código padrão, 949[1]
    A essa altura, deveriam simplesmente proibir isso como fizeram com gets. [1] Sei que existe a página de código UTF-8 65001. Por muito tempo ela foi praticamente inutilizável, e ainda hoje sofre com problemas de compatibilidade