WorstFit: Revelando os Transformers ocultos do ANSI do Windows
(blog.orange.tw)- 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,getenveint 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.exeejava.exequando 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,_wgetenvewmain(); 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
chcpmostra 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, comoGetEnvironmentVariableA - APIs Unicode têm sufixo
W, comoGetEnvironmentVariableW - Quando uma API ANSI é chamada, o Windows converte internamente a string UTF-16 em string ANSI com
RtlUnicodeStringToAnsiStringouWideCharToMultiByte
- APIs ANSI têm sufixo
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 para8 √π⁷≤∞pode virar"vp7=8"ao passar por uma API ANSI
- Por exemplo, em Windows-1252,
- 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
maincomum- 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
- A conversão Best-Fit é aplicada em funções CRT non-wide como
- 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
- Ao anexar
- Por causa do Best-Fit, o soft hyphen U+00AD é convertido em
-em páginas de código chinesas/japonesas, contornando o patch existente?%ADspode funcionar como-sdo 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*eGetFullPathNameA - 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)
- As APIs relacionadas incluem
- O
d8.exe, Developer Shell do Chrome V8, usaGetCurrentDirectoryA()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 PoC de exemplo cria o caminho
- O atacante pode baixar
cuckoo.confe 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
GetCommandLineAou no caminho non-Unicode deint 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)
- APIs e caminhos relacionados:
- Um exemplo em PHP executa
wget.exe -qapós envolver a URL com segurança usandoescapeshellarg(), mas a entrada" --use-askpass=calc "permite executarcalc.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
CreateProcessrecebe diretamente o parâmetrolpCommandLine
- 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
escapeshellargdo PHP troca aspas duplas por espaço, envolve o argumento em aspas e processa barras invertidas - O
subprocessdo Python faz o escaping comlist2cmdlineconforme 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
- O
- Programas que usam apenas
int main()também podem ser vulneráveis- O compilador gera
mainCRTStartupno 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
- O compilador gera
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.exenativo do Windows - Com um nome de arquivo tar como
aaa" "--use-compress-program=calc" "bbb.tar, é possível injetar o argumento--use-compress-programe 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
- O processamento de arquivos compactados é implementado com execução de shell command, e os argumentos são escapados com
- No caso do
plink.exemodificado usado no TortoiseGit, inserir uma URI maliciosa como entrada de clone pode acionar execução de código- Os detalhes podem ser vistos na curated list
- O vídeo de demonstração é o Video 13
- 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
- Os detalhes podem ser vistos na curated list
- O vídeo de demonstração é o Video 14
- 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
ftypeeassoc - 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
- O Windows mantém uma handler table por extensão de arquivo, que pode ser verificada com
Confusão de variáveis de ambiente
- A confusão de variáveis de ambiente ocorre porque
GetEnvironmentVariableA,GetEnvironmentStringsAechar *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_URIcontendo/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
adminpor um equivalente Best-Fit - Na Code Page 1250,
àU+00E0 é convertido paraadurante a conversão ANSI - A requisição
/cgi.pl/%E0dminparece ser outro caminho para a regra no lado do servidor, mas quando o script Perl CGI lêPATH_INFOvia API ANSI, ela é tratada como/admin
- Na configuração do Apache, existe uma regra que rejeita
- 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_INFOe 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 comoREDIRECT_URL,REQUEST_URI,PATH_INFOePATH_TRANSLATED - Só com essas informações, é difícil distinguir claramente o limite entre o nome do arquivo PHP e o
PATH_INFOadicional, e ophp-cgi.exeinterpreta isso
- A causa é a forma como
- 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/foointeiro comoPATH_INFOadicional - O PHP-CGI recebe um valor convertido como
REQUEST_URI=/index.php/..\\..\\windows/win.ini/fooe se confunde ao separar o arquivo PHP real doPATH_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_rootestiver configurada, é possível fazer LFI com um caminho como/index.php/..¥..¥..¥windows/win.ini/, incluindo e lendoC:\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
- O servidor web trata
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 paramain() - Alguns projetos fornecem apenas o código-fonte, enquanto executáveis precompilados para Windows são distribuídos por voluntários terceiros na internet
- O código problemático envolve
- 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 *parawchar_t * - Esse processo é doloroso e propenso a erros
- Quando a assinatura da função muda, é preciso reescrever as definições de variáveis e a lógica de parsing de argumentos de
- 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 ocurl.exeembutido 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
- O aviso foi adicionado apenas a
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.exe— CVE-2024-4577 - 2024/06/13: Curl Official Build — Won’t Fix
- 2024/06/13: Apache Subversion
svn.exe— CVE-2024-45720 - 2024/06/16: Microsoft Tar
tar.exe— Won’t Fix - 2024/06/19: Microsoft Excel
excel.exe— CVE-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.exe— CVE-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
- 2024/05/07: PHP
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
_wgetcwde_wgetenv - Se caminhos non-wide continuarem sendo usados, a implementação interna pode chamar a API ANSI e ficar exposta ao ataque WorstFit
- A CRT também oferece versões wide character, como
- 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
- Como exemplo, consultas ao Registro do Windows, como
1 comentários
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
"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
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,printfestd::coutfuncionarem 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ó lugarIsto é 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
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 comofopen()egetenv()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 completaactiveCodePagecomo UTF-8 no manifesto da aplicação e usar apenas funções “ANSI”mainefopensão definidas com#definepara suas correspondentes wide.Com isso, não dá para usar simplesmente
char*e literais de string sem decoração, então define-se um tipotchar, que écharno Linux ewchar_tno Windows, e a macro_T()para literais de string. Em geral funciona bem sem precisar pensar muito-A, não a variante-W. Não sei se há algo estranho norobots.txt, mas é esquisito que uma API que recomenda usar a variante-Wem código novo retorne por padrão a API legadamsvcrt.dll, foi substituído pelo Universal C Runtime (UCRT)[1], e o UCRT é compatível com C99Há 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
RtlInitNlsTableseRtlResetRtlTranslationspodem estar relacionadasNã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
xxxAe substituir caracteres impossíveis de mapear por algo comox, que não tem significado meta comum[0] https://tambre.ee/blog/adobe_after_effects_windows_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 *parawchar *. 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 usandocharClaro 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
Fiquei curioso para saber se a caixa de seleção beta era equivalente a definir
ActiveCodePagecomo 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
*Adentro 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