WorstFit: revelan los Transformers ocultos en Windows ANSI
(blog.orange.tw)- La conversión de caracteres Best-Fit de Windows sustituye caracteres por otros de apariencia similar al convertir cadenas UTF-16 a páginas de códigos ANSI, y este comportamiento se convierte en la superficie de ataque WorstFit, que puede derivar en Path Traversal, Argument Injection y RCE
- El problema surge de la combinación de las API ANSI, el runtime de C/C++, el código de inicio insertado por el compilador y el uso por parte de desarrolladores de API de caracteres no wide; las rutas
GetCommandLineA,GetEnvironmentVariableA,getenveint main()se ven afectadas - CVE-2024-4577 eludió el parche de PHP-CGI cuando U+00AD se convertía en
-en páginas de códigos chinas/japonesas; Filename Smuggling genera confusión de rutas cuando¥,₩y la barra fullwidth se convierten en/o\ - Argument Splitting puede crear caracteres de parsing de línea de comandos mediante comillas dobles fullwidth o signos de yen/won, permitiendo inyectar argumentos en herramientas CLI como
wget.exe,tar.exe,openssl.exeyjava.exe; es difícil bloquearlo solo con el escape de argumentos habitual de PHP, Python, Node.js y Rust - Para mitigarlo, hay que activar la opción UTF-8 de Windows o que los desarrolladores usen la Wide Character API y rutas wide character como
_wgetcwd,_wgetenvywmain(); hasta que Microsoft active UTF-8 por defecto en todas las ediciones de Windows, podrían repetirse problemas similares
Estructura de codificación de Windows y Best-Fit
- Windows usaba inicialmente páginas de códigos ANSI, y según la región lingüística variaban páginas de códigos como 1252, 932, 936, 949 y 950
- ACP (ANSI Code Page) se usa en la mayoría de las aplicaciones y configuraciones del sistema, como operaciones de archivos y variables de entorno
- OEMCP (Original Equipment Manufacturer Code Page) se usa principalmente para comunicación con dispositivos, como lectura/escritura en consola
chcpmuestra la OEMCP, no la ACP, por lo que no sirve para verificar la ACP, que es el foco de esta investigación
- Windows migró a Unicode a mediados de la década de 1990, y hoy sus API centrales usan wide characters basados en UTF-16
- API centrales como sistema de archivos, información del sistema y procesamiento de texto pasaron a API de caracteres wide
- La funcionalidad UTF-8 existe, pero no viene activada por defecto en la mayoría de los idiomas; el artículo la describe como en fase beta
- Por compatibilidad hacia atrás, la API de Windows ofrece versiones ANSI y Unicode
- Las API ANSI llevan el sufijo
A, comoGetEnvironmentVariableA - Las API Unicode llevan el sufijo
W, comoGetEnvironmentVariableW - Cuando se llama una API ANSI, Windows convierte la cadena UTF-16 interna a una cadena ANSI mediante
RtlUnicodeStringToAnsiStringoWideCharToMultiByte
- Las API ANSI llevan el sufijo
Cómo Best-Fit se convierte en WorstFit
- Best-Fit es el comportamiento que mapea un carácter UTF-16 a otro de apariencia o sensación similar cuando no puede representarse exactamente en la página de códigos ANSI de destino
- Por ejemplo, en Windows-1252,
∞U+221E se mapea a8 - Si
√π⁷≤∞pasa por una API ANSI, puede convertirse en algo como"vp7=8"
- Por ejemplo, en Windows-1252,
- El mapeo se comporta de forma distinta según la página de códigos
¥U+00A5 se mapea a\en la página de códigos japonesa 932- En la página de códigos centroeuropea 1250 se mapea a
Y - En la mayoría de las demás páginas de códigos no cambia
- La misma conversión ocurre no solo al llamar directamente a la API de Windows, sino también en funciones CRT y en la ruta habitual de la función
main- La conversión Best-Fit se aplica en funciones CRT non-wide como
getenv - La conversión también interviene cuando se reciben argumentos y variables de entorno con la forma
int main(int argc, char* argv[], char* envp[]) - Esto se debe a la combinación del código de inicio CRT insertado por el compilador y el uso de API ANSI de Windows
- La conversión Best-Fit se aplica en funciones CRT non-wide como
- Para verificar los mapeos, se puede consultar Best-fit Mapping Grepper y los datos de mapeo sin procesar WindowsBestFit de Unicode.org
Primer caso de WorstFit: PHP-CGI CVE-2024-4577
- CVE-2024-4577 es un caso de ataque WorstFit que permitió comprometer servidores PHP-CGI configurados con páginas de códigos chinas/japonesas usando solo la solicitud
?%ADs- Las páginas de códigos afectadas son 932 (japonés), 936 (chino simplificado) y 950 (chino tradicional)
- El carácter amenazante es
U+00AD
- La vulnerabilidad de PHP-CGI de 2012 fue un caso de argument injection que ocurrió porque Apache procesaba automáticamente la query string como primer argumento del programa CGI
- Al agregar
?-s, era posible filtrar el código fuente de la página y lograr RCE - El parche de PHP consistía en detener el parsing de argumentos si la query string comenzaba con un dash
- Al agregar
- Debido a Best-Fit, el soft hyphen U+00AD se convierte en
-en páginas de códigos chinas/japonesas, lo que permite eludir el parche existente?%ADspuede comportarse como-sdesde la perspectiva de PHP-CGI- A partir de este caso, el equipo de investigación se encontró por primera vez con el término Best-Fit
Filename Smuggling: el problema de la conversión de caracteres de ruta
- Filename Smuggling es un ataque en el que caracteres Unicode incluidos en nombres de archivo se convierten en
/o\en rutas de API ANSI, lo que puede crear path traversal- Las API relacionadas incluyen
GetCurrentDirectoryA,getcwd,FindFirstFileA,findfirst*,GetFullPathNameA, entre otras - Las páginas de códigos afectadas son 874, 125x, 932 (JP) y 949 (KR)
- Los caracteres amenazantes son
/U+FF0F,\U+FF3C,¥U+00A5 (JP) y₩U+20A9 (KR)
- Las API relacionadas incluyen
d8.exe, el Developer Shell de Chrome V8, usaGetCurrentDirectoryA()en su implementación interna para obtener el directorio de trabajo actual- Si se puede crear un directorio de trabajo con caracteres Unicode maliciosos, al acceder mediante la API ANSI se convierte en un payload de path traversal
- Como ejemplo, permite un acceso no intencional a
C:/windows/win.ini
- La implementación para Windows de
Dir.getwd()en mruby depende de la función CRT ANSI_getcwd()- El valor devuelto puede contaminarse y derivar en Path Traversal
Cuckoo Sandbox: de Path Traversal a RCE
- El acceso al sistema de archivos de Windows desde Python podía usar la API wide o la API ANSI según si la cadena era wide o narrow
- Después de PEP 529, la codificación del sistema de archivos de Windows se estandarizó en UTF-8
- Python 2 y Python 3 antes de Python 3.6 quedaron vulnerables a ataques WorstFit
- Cuckoo Sandbox es una plataforma automatizada de análisis de malware, y su versión oficial más reciente depende de Python 2.7
- Cuckoo está compuesto por Cuckoo Host y un clúster de VM
- Las muestras subidas se ejecutan de forma aislada en una VM, y los paquetes de red, archivos droppeados y logs se sincronizan mediante su propio mecanismo
- Si el malware crea un archivo droppeado con nombre Unicode, puede producirse un Path Traversal en el procesamiento de rutas de Python en Cuckoo Host
- El PoC de ejemplo genera la ruta
AAAA\u00a5..\u00a5..\u00a5..\u00a5..\u00a5..\u00a5conf\u00a5cuckoo.conf - Tras finalizar el análisis, cuando el usuario presiona el botón de descarga en la interfaz web, se dispara una operación de archivo de Python
- Cuckoo Host puede procesar la ruta convertida que contiene
../y enviar datos sensibles al atacante
- El PoC de ejemplo genera la ruta
- El atacante puede descargar
cuckoo.conf, recolectar la información sensible necesaria para calcular el PIN de Flask y lograr RCE en el Sandbox Host- El video de demostración está disponible como Video 11
Argument Splitting: Best-Fit que cambia el parsing de la línea de comandos
- Argument Splitting es un ataque en el que la cadena de línea de comandos cambia y los argumentos se separan en la salida de
GetCommandLineAo en la ruta non-Unicode deint main()- Las API y rutas relacionadas son
GetCommandLineA,int main() - Las páginas de códigos afectadas son 874, 125x, 932(JP), 949(KR)
- Los caracteres de amenaza son
"U+FF02,\U+FF3C,¥U+00A5(JP),₩U+20A9(KR)
- Las API y rutas relacionadas son
- El código PHP de ejemplo envuelve de forma segura una URL con
escapeshellarg()y luego ejecutawget.exe -q, pero con la entrada" --use-askpass=calc "es posible ejecutarcalc.exe- La misma entrada no se mitiga aunque se cambie a Node.js, Rust o Python
- También funciona en el ejemplo con
subprocess.run(["wget", "-q", ...])de versiones recientes de Python
- Windows pasa al proceso nuevo toda la línea de comandos como una sola cadena, y el ejecutable la parsea directamente
- No es una estructura en la que siempre se entregue un arreglo de argumentos como en sistemas tipo UNIX
- La API
CreateProcessrecibe directamente el parámetrolpCommandLine
- En el parsing común de línea de comandos, los caracteres importantes son espacios y tabs, double quote y backslash
- Los espacios y tabs separan argumentos cuando no se está en quote mode
"alterna el quote mode\escapa double quote y backslash en secuencias específicas
- Las bibliotecas estándar de la mayoría de los lenguajes escapan los argumentos de usuario conforme a estas reglas, pero el escape termina antes de la conversión Best-Fit
- PHP
escapeshellargcambia los double quote por espacios, envuelve el argumento entre quotes y procesa los backslash - Python
subprocessusalist2cmdlinepara escapar según las reglas de parsing de línea de comandos de Microsoft CRT - Si después, durante la conversión ANSI,
"U+FF02 se convierte en"U+0022, cambia la sintaxis original de la línea de comandos
- PHP
- Los programas que solo usan
int main()también pueden ser vulnerables- El compilador genera
mainCRTStartupen el binario, y esta función de inicio se enlaza con la biblioteca CRT - Si internamente la CRT obtiene y parsea la línea de comandos con la API ANSI, interviene la conversión Best-Fit
- Debido a este comportamiento, es difícil bloquear completamente el ataque solo con la biblioteca estándar de un lenguaje de programación específico
- El compilador genera
Casos reales de Argument Splitting
- ElFinder es un administrador de archivos web open source basado en un backend PHP, y por defecto soporta servidores Windows y la creación/extracción de archivos comprimidos
- El procesamiento de archivos se implementa mediante ejecución de shell command, y los argumentos se escapan con
escapeshellarg - Para procesar el formato tar se usa el
tar.exeintegrado de Windows - Con un nombre de archivo tar como
aaa" "--use-compress-program=calc" "bbb.tar, es posible inyectar el argumento--use-compress-programy ejecutar comandos arbitrarios - La demo está basada en un servidor Windows configurado en inglés y Code Page 1252, y se resume que también debería funcionar en páginas de códigos 125x y Code Page 874
- El video de demostración está disponible como Video 12
- El procesamiento de archivos se implementa mediante ejecución de shell command, y los argumentos se escapan con
- El caso del
plink.exemodificado usado en TortoiseGit puede disparar ejecución de código si se ingresa un URI malicioso como entrada de clone- Los detalles se pueden consultar en la curated list
- El video de demostración es Video 13
- RStudio soporta control de versiones SVN, y si hay un proyecto SVN en una carpeta creada maliciosamente, es posible ejecutar la calculadora con un solo clic
- Los detalles se pueden consultar en la curated list
- El video de demostración es Video 14
- El caso de Microsoft Excel es CVE-2024-49026, que combina Argument Splitting con la función “Open-With” de Windows
- Windows mantiene una handler table por extensión de archivo, que se puede consultar con
ftypeyassoc - El nombre de archivo pasa a formar parte de los argumentos del programa handler, por lo que el ataque puede aplicarse mediante el nombre de archivo
- Un nombre de archivo en el que dots, slash, backslash y double quote se cambian a sus formas fullwidth provoca argument injection en
Excel.exe - Como Excel en sí no tiene argumentos adecuados para una explotación adicional, se logra RCE usando también NTLM Relay y RBCD/ADCS
- El video de demostración es Video 15
- Windows mantiene una handler table por extensión de archivo, que se puede consultar con
Confusión de variables de entorno
- Confusión de variables de entorno ocurre cuando
GetEnvironmentVariableA,GetEnvironmentStringsAychar *getenv()devuelven la versión de las variables de entorno transformada con Best-Fit- No se especifican las páginas de código afectadas ni los caracteres amenazantes
- En el caso de Apache HTTPd, están involucrados los valores 0x00-0xFF
- Para que este ataque funcione, las variables de entorno deben poder ser controladas por el usuario
- Esto aplica cuando un proceso padre pasa información a un proceso hijo que creó
- En CGI, gran parte de la información de una solicitud HTTP, como el query string y los headers HTTP, se pasa mediante variables de entorno
- El ejemplo de evasión de WAF trata una situación en la que un script CGI actúa como un routing service
- La configuración de Apache tiene una regla que rechaza
REQUEST_URIque incluya/admin, para impedir el acceso remoto a/cgi.pl/admin - Debido al comportamiento WorstFit de Perl en Windows, es posible evadirla si se reemplaza parte de
adminpor un equivalente Best-Fit - En la Code Page 1250,
àU+00E0 se convierte enadurante la conversión ANSI - La solicitud
/cgi.pl/%E0dminse ve como una ruta distinta para las reglas del lado del servidor, pero cuando el script Perl CGI leePATH_INFOcon la API ANSI, se procesa como/admin
- La configuración de Apache tiene una regla que rechaza
- En PHP-CGI on Windows, en ciertas configuraciones se confirmó un oracle de existencia de archivos y un posible LFI
- La causa es la forma en que se procesan
PATH_INFOy otras variables de entorno relacionadas con rutas - Una solicitud
/index.php/foo/bar, según Apache, se pasa mediante variables de entorno comoREDIRECT_URL,REQUEST_URI,PATH_INFOyPATH_TRANSLATED - Solo con esta información es difícil distinguir con claridad el límite entre el nombre del archivo PHP y el
PATH_INFOadicional, yphp-cgi.exelo interpreta
- La causa es la forma en que se procesan
- En la página de código japonesa, usar
¥hace que el servidor web y PHP-CGI interpreten las rutas de manera distinta- El servidor web trata todo
/..¥..¥windows/win.ini/foocomoPATH_INFOadicional - PHP-CGI recibe un valor transformado como
REQUEST_URI=/index.php/..\..\windows/win.ini/fooy se confunde al separar el archivo PHP real dePATH_INFO - En Apache, es posible un file existence oracle por la diferencia de respuesta entre archivos inexistentes y existentes
- En IIS, si está configurada la directiva
doc_root, es posible un LFI que incluya y leaC:\Windows\win.inimediante una ruta como/index.php/..¥..¥..¥windows/win.ini/ - Si el archivo incluido es ejecutable o contiene código controlable por el usuario, podría derivar en un RCE potencial, pero ese escenario se clasifica más bien como un bug poco común en aplicaciones reales
- El servidor web trata todo
Dificultades del proceso de divulgación y corrección
- El equipo de investigación reportó varios problemas en lenguajes de programación, proyectos open source y programas CLI integrados en Windows a cada upstream maintainer
- La mayor controversia surgió con Argument Splitting
- Algunos vendors consideran que pasar input del usuario a la línea de comandos ya es una vulnerabilidad en sí mismo
- La responsabilidad poco clara también fue un problema
- El código problemático abarca
mainCRTStartup(), que se inserta automáticamente durante la compilación, y llamadas internas a la API ANSI de MSVCRT/UCRT - Es difícil distinguir si el problema es que el desarrollador no usó
wmain()o si el CRT dividió mal la línea de comandos y pasó argumentos incorrectos amain() - Algunos proyectos solo proporcionan el código fuente, mientras que los Windows prebuilt executable son distribuidos por voluntarios terceros en Internet
- El código problemático abarca
- La corrección no consiste simplemente en cambiar
main()a una versión wide-character- Si cambia la firma de la función, las definiciones de variables y la lógica de parseo de argumentos deben reescribirse de
char *a una basewchar_t * - Este proceso es doloroso y propenso a errores
- Si cambia la firma de la función, las definiciones de variables y la lógica de parseo de argumentos deben reescribirse de
- Curl respondió que se trata de una funcionalidad de Windows y que no tiene planes de corregirlo; el Curl portado por Microsoft cambió el entry a
wmain(), por lo que elcurl.exeintegrado en Windows no se ve afectado- Los binarios oficiales de Curl sí se ven afectados por el ataque de Argument Splitting
- El reporte completo se publicó en HackerOne
- OpenSSL puede procesar argumentos en formato wide character mediante la variable de entorno
OPENSSL_WIN32_UTF8- Su objetivo original era corregir problemas de visualización de UTF-8 en la UI, pero también mitiga el ataque de Argument Splitting
- En el uso predeterminado de OpenSSL, muchos desarrolladores no saben que deben configurar esta variable de entorno, y es posible ejecutar código arbitrario usando el argumento
-engine
- La distribución oficial de Perl no proporciona Windows prebuilt executable, y suelen usarse instaladores de terceros como Strawberry Perl y ActiveState Perl
- Ambas distribuciones se ven afectadas por el ataque de Argument Splitting
- Tras conversar con los maintainers de Perl, la conclusión fue que “se parece más a un bug de Microsoft que a un bug de Perl”, por lo que actualmente sigue sin resolverse
- Se reportaron tres casos a Microsoft mediante MSRC, y todos fueron rechazados inicialmente por no alcanzar el umbral de severidad
- Después de reabrirlos varias veces, solo el caso de Excel fue aceptado tras el tercer intento
- Los demás casos siguen sin resolverse hasta ahora
- MSRC respondió que dependen de una vulnerabilidad en la que otra aplicación ejecuta input no confiable al ponerlo en la línea de comandos, y que la técnica que lo vuelve explotable no cumple por sí misma los requisitos para ser considerada una vulnerabilidad
- También se pidió ayuda a CERT/CC, y meses después Microsoft agregó una advertencia de seguridad a la documentación de
GetCommandLineA- La advertencia solo se agregó a
GetCommandLineA, y aún quedan más API ANSI que requieren precaución
- La advertencia solo se agregó a
Objetivos afectados reportados y estado
- Los elementos confirmados y reportados durante el proceso de divulgación son los siguientes
- 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
Mitigaciones y superficie de ataque restante
- Como los ataques WorstFit son un problema a nivel del sistema operativo, es posible que sigan reapareciendo problemas similares hasta que Microsoft active UTF-8 como valor predeterminado en todas las ediciones de Windows
- La medida que pueden tomar los usuarios es revisar y activar la opción UTF-8 de Windows
- Esta función todavía aparece marcada como beta, y no está claro si puede tener efectos secundarios
- Los desarrolladores deben usar la API de caracteres anchos siempre que sea posible
- El CRT también ofrece versiones de caracteres anchos como
_wgetcwdy_wgetenv - Si se siguen usando rutas no wide, la implementación interna puede llamar a la API ANSI y quedar expuesta a ataques WorstFit
- El CRT también ofrece versiones de caracteres anchos como
- Debido a la compatibilidad hacia atrás de Windows, puede haber más lugares donde la API ANSI esté oculta
- Por ejemplo, las consultas del Registro de Windows como
RegQueryValueApodrían verse afectadas, pero hay que encontrar escenarios vulnerables - El equipo de investigación también observó el comportamiento Best-Fit en Active Directory
- Por ejemplo, las consultas del Registro de Windows como
1 comentarios
Comentarios de Hacker News
Este es un problema bastante complicado. El mapeo de código “best fit” de Microsoft es un mapeador público, pero en la práctica “basado en sensaciones”, que convierte Unicode amplio a ASCII, y está presente en todo el sistema.
Este mapeador se vincula por defecto en muchísimos lugares y, por la forma en que Microsoft ve la compatibilidad hacia atrás, parece inevitable que siga incluido. Los exploits suelen surgir cuando puntos de código inusuales se mapean “por sensación” a barras, guiones o comillas. Dentro de lenguajes modernos se validan como Unicode correcto, pero cuando pasan a comandos de shell o a la API Win32, después de ceder el control se reducen de otra manera. Como dijo el mantenedor de curl, aquí “curl es la víctima”; la cuestión es quién es el culpable. Si un servidor aplasta de forma distinta los datos de entrada del usuario al validarlos y al pasarlos a una biblioteca del sistema, tarde o temprano habrá problemas. Una opción para desactivar la conversión best fit del lado de Win32 podría ser la solución, aunque no soy especialista en Windows, así que es una conjetura. Aun así, se seguiría interactuando con API oficiales o con software que todavía no la desactivó.
"w"en vez de"a". Este enfoque también resuelve el problema de rutas de más de 260 caracteres si se agrega el prefijo"\\?\"o se configura correctamente el manifiesto, y está disponible y recomendado desde Windows XP.No sé muy bien por qué las API no Unicode se siguen usando tan ampliamente. Cuesta imaginar que sea por el deseo de dar soporte a Windows 98 o Windows 2000.
También hace falta algún tipo de linting. En una aplicación moderna normalmente no hay motivo para llamar funciones ANSI de WinAPI. También podría existir el enfoque de configurar la locale como UTF-8 y usar solo funciones de 8 bits, pero no sé qué tan bien funciona. Tengo entendido que también hay algunas configuraciones y headers para hacer que
argv,printfystd::coutfuncionen en UTF-8, y que solo se usen funciones de conversión UTF-8/UTF-16 para WinAPI sin conversiones raras. Microsoft debería documentar todo ese procedimiento en un solo lugar.Esto es esperable hasta cierto punto, pero incluso para alguien que pasó unos 10 años desarrollando para Windows y hackeando la API de Wine en la época en que surgía la confusión W/A, fue algo nuevo.
Windows es como el juego de cartas Munchkin: cuando varias funciones encajan por casualidad, pueden combinarse en exploits increíblemente aleatorios y potentes. Es bueno que estén cambiando el subsistema ANSI a UTF-8 y, en teoría, eso podría mitigar muchos de estos problemas. Me pregunto si el equipo de Rust tendrá que hacer otra corrección más en la API de creación de procesos.
Por supuesto, Rust no puede controlar lo que pasa más allá del límite del proceso. Si una aplicación ejecutada por Rust usa la API ANSI, el problema aparece de ese lado, pero eso es responsabilidad de esa aplicación.
“Eliminar gradualmente ANSI y recomendar el uso de la API Wide Character” era, si no recuerdo mal, la postura oficial de Microsoft desde NT 3.5.
Por desgracia, uno de los grandes obstáculos es la forma en que está implementada la biblioteca de runtime C/C++ de Microsoft,
msvcrt.dll. Las funciones wide no estándar como_wfopen()y_wgetenv()usan internamente las funciones W de la Win API, pero las funciones narrow estándar comofopen()ygetenv()simplemente usan las funciones A en vez de convertir a sus versiones wide. Y las funciones A normalmente no informan fallos de conversión Unicode, sino que los tapan con best-fit. Quien porta software escrito en C a Windows no quiere cambiar todo el uso de funciones estándar por funciones no portables de Microsoft. A partir de ese punto, en la práctica es una reescritura completa.activeCodePagecomo UTF-8 en el manifiesto de la aplicación y usar solo funciones “ANSI”.#definede funciones estándar comomainyfopena sus equivalentes wide.Con eso ya no se pueden usar
char*y literales de cadena sin decorar tal cual, así que se define un tipotcharque escharen Linux ywchar_ten Windows, y una macro_T()para literales de cadena. En general funciona bien sin pensarlo demasiado.-Ay no la variante-W. No sé si hay algo raro enrobots.txt, pero es extraño que una API que recomienda usar la variante-Wpara código nuevo devuelva por defecto la API heredada.msvcrt.dll, fue reemplazado por el Universal C Runtime (UCRT)[1], y UCRT cumple con C99.Hay dos formas de forzar que la página de códigos “Ansi” sea realmente UTF-8 en aplicaciones propias o en EXE parcheados.
Una es usar un archivo de manifiesto, y funciona a partir de cierta build de Windows 10. También se puede aplicar a cualquier EXE después de compilarlo, así que permite meter soporte UTF-8 a la fuerza en un programa. Es especialmente útil para programas en modo consola. La otra es usar el tipo de hack que emplean herramientas estilo “App Locale”. Uno de los métodos incluye llamar funciones no documentadas de NTDLL. No sé exactamente qué funciones hacen falta, pero
RtlInitNlsTablesyRtlResetRtlTranslationspodrían estar relacionadas.No estoy seguro de que Microsoft vaya a activar UTF-8 por defecto en todas las ediciones de Windows. Hay muchas aplicaciones antiguas que asumen una página de códigos específica o 1 byte por carácter, y podrían romperse.
De forma más sutil, también hay aplicaciones que reutilizan buffers existentes asumiendo que, al convertir de caracteres wide a ANSI, la cantidad de bytes no aumenta. Con UTF-8 eso no es así, y como en la mayoría de las páginas de códigos existentes más o menos se cumplía, podrían aparecer nuevas vulnerabilidades. Creo que sería mucho menos disruptivo quitar la lógica Best-Fit de las API Win32
xxxAy reemplazar los caracteres que no se puedan mapear por algo comox, que no tiene un significado meta común.[0] https://tambre.ee/blog/adobe_after_effects_windows_utf-8/
Viendo los problemas que causa el mapeo Best-Fit, también tiene sentido convertirlo en el valor predeterminado, pero Microsoft debería ayudar a los usuarios a encontrar formas sencillas de ejecutar código antiguo. Una forma menos razonable sería eliminar del mapeo Best-Fit todas las correspondencias hacia caracteres ASCII “especiales”, pero eso no ayuda a las apps que enlazaron estáticamente el CRT. Tampoco corrige la vulnerabilidad, así que no es una buena solución. A veces, una vulnerabilidad de seguridad sirve como motivación para impulsar una ruptura de compatibilidad hacia atrás.
Microsoft conocía este problema desde hace al menos un año. Sacó una regla especial de análisis de código llamada CA2101[1], que desaconsejaba explícitamente el uso de mapeos best-fit.
La descripción de la regla mencionaba vulnerabilidades de seguridad, pero dejaba los detalles deliberadamente vagos.
[1] https://learn.microsoft.com/en-us/dotnet/fundamentals/code-a...
No hace falta cambiar todo de
char *awchar *. Puedes convertir los caracteres wide recibidos a UTF-8, o, si quieres permitir incluso secuencias inválidas como sustitutos sin pareja, convertirlos a algo como WTF-8 de Rust y seguir usandochar.Por supuesto, hay que tener cuidado de no mezclar cadenas ANSI u OEMCP con cadenas UTF-8, pero si usas solo UTF-8 es sencillo. Ese es el enfoque que recomienda el clásico sitio https://utf8everywhere.org/.
En mi computadora personal con Windows tenía activado el modo UTF-8 desde hace unos años, así que estaba evitando este bug por casualidad. Es la configuración que aparece al final del artículo.
Lo había activado porque algunos juegos extranjeros antiguos mostraban texto corrupto y, aunque aparece marcado como “Beta”, no noté bugs ni efectos secundarios.
Me preguntaba si la casilla beta era lo mismo que configurar
ActiveCodePagecomo UTF-8 en el manifiesto, pero la documentación[0] deja claro que GDI no sigue la página de códigos por proceso, sino solo la página de códigos global única que establece esa casilla.Es una pena que no puedas hacer opt-in completo a UTF-8 para las API
*Adesde tu propia app. Aun así, para los problemas que destaca el artículo, creo que sigue siendo una solución alternativa válida o una medida de defensa en profundidad.[0] https://learn.microsoft.com/en-us/windows/apps/design/global...
Dios mío. Sabía que la API de Windows ofrecía ese tipo de conversión best-fit, pero no sabía que era el comportamiento predeterminado de varias funciones ANSI en mi página de códigos predeterminada, 949[1].
A esta altura deberían prohibirlo sin más, como
gets. [1] Sé que existe la página de códigos UTF-8 65001. Durante mucho tiempo fue prácticamente inutilizable, y aún hoy tiene problemas de compatibilidad.