WorstFit: Hé lộ các Transformer ẩn trong Windows ANSI
(blog.orange.tw)- Chuyển đổi ký tự Best-Fit của Windows thay thế ký tự bằng những ký tự trông tương tự trong quá trình đổi chuỗi UTF-16 sang code page ANSI; hành vi này trở thành bề mặt tấn công WorstFit có thể dẫn đến Path Traversal, Argument Injection và RCE
- Vấn đề phát sinh từ cấu trúc chồng chéo giữa ANSI API, runtime C/C++, mã khởi động do trình biên dịch chèn vào và việc nhà phát triển dùng API ký tự không phải wide; các đường dẫn
GetCommandLineA,GetEnvironmentVariableA,getenv,int main()bị ảnh hưởng - CVE-2024-4577 đã vượt qua bản vá PHP-CGI khi U+00AD bị đổi thành
-trong các code page tiếng Trung/tiếng Nhật; Filename Smuggling tạo ra nhầm lẫn đường dẫn khi¥,₩, dấu gạch chéo fullwidth bị đổi thành/hoặc\ - Argument Splitting có thể tạo ra các ký tự phân tích cú pháp dòng lệnh bằng dấu nháy kép fullwidth hoặc ký hiệu Yen/Won, từ đó chèn đối số vào các công cụ CLI như
wget.exe,tar.exe,openssl.exe,java.exe; chỉ dùng cách escape đối số thông thường của PHP, Python, Node.js, Rust thì khó ngăn chặn - Để giảm thiểu, cần bật tùy chọn UTF-8 của Windows, hoặc nhà phát triển phải dùng Wide Character API và các đường dẫn ký tự wide như
_wgetcwd,_wgetenv,wmain(); cho đến khi Microsoft bật UTF-8 làm mặc định trên mọi phiên bản Windows, các vấn đề tương tự vẫn có thể lặp lại
Cấu trúc mã hóa của Windows và Best-Fit
- Ban đầu Windows dùng code page ANSI, và code page khác nhau tùy theo vùng ngôn ngữ, như 1252, 932, 936, 949, 950
- ACP (ANSI Code Page) được dùng cho phần lớn ứng dụng và thiết lập hệ thống như thao tác tệp, biến môi trường
- OEMCP (Original Equipment Manufacturer Code Page) chủ yếu được dùng cho giao tiếp thiết bị như đọc/ghi console
chcphiển thị OEMCP chứ không phải ACP, nên không phải phương tiện kiểm tra ACP — trọng tâm của nghiên cứu này
- Windows đã chuyển sang Unicode từ giữa thập niên 1990, và các API lõi hiện nay dùng wide character dựa trên UTF-16
- Các API lõi như hệ thống tệp, thông tin hệ thống, xử lý văn bản đã được chuyển sang API ký tự wide
- Chức năng UTF-8 có tồn tại nhưng không được bật mặc định ở hầu hết ngôn ngữ; bài viết mô tả nó đang ở giai đoạn beta
- Vì tương thích ngược, Windows API cung cấp song song phiên bản ANSI và phiên bản Unicode
- ANSI API có hậu tố
A, nhưGetEnvironmentVariableA - Unicode API có hậu tố
W, nhưGetEnvironmentVariableW - Khi ANSI API được gọi, Windows chuyển đổi chuỗi UTF-16 nội bộ thành chuỗi ANSI bằng
RtlUnicodeStringToAnsiStringhoặcWideCharToMultiByte
- ANSI API có hậu tố
Cách Best-Fit trở thành WorstFit
- Best-Fit là hành vi ánh xạ ký tự UTF-16 thành ký tự trông tương tự hoặc có cảm giác tương tự khi không thể biểu diễn chính xác ký tự đó trong code page ANSI đích
- Ví dụ trong Windows-1252,
∞U+221E được ánh xạ thành8 - Khi
√π⁷≤∞đi qua ANSI API, nó có thể bị đổi thành"vp7=8"
- Ví dụ trong Windows-1252,
- Ánh xạ hoạt động khác nhau theo từng code page
¥U+00A5 được ánh xạ thành\trong code page tiếng Nhật 932- Trong code page Trung Âu 1250, nó được ánh xạ thành
Y - Ở hầu hết code page khác, nó không bị thay đổi
- Cùng một chuyển đổi cũng xảy ra không chỉ khi gọi trực tiếp Windows API, mà còn trong các hàm CRT và đường dẫn hàm
mainthông thường- Chuyển đổi Best-Fit được áp dụng trong các hàm CRT non-wide như
getenv - Chuyển đổi cũng can thiệp khi nhận đối số và biến môi trường dưới dạng
int main(int argc, char* argv[], char* envp[]) - Nguyên nhân là mã khởi động CRT do trình biên dịch chèn vào kết hợp với việc dùng Windows API ANSI
- Chuyển đổi Best-Fit được áp dụng trong các hàm CRT non-wide như
- Có thể tham khảo Best-fit Mapping Grepper và dữ liệu ánh xạ thô WindowsBestFit của Unicode.org để kiểm tra ánh xạ
Trường hợp WorstFit đầu tiên: PHP-CGI CVE-2024-4577
- CVE-2024-4577 là một trường hợp tấn công WorstFit cho phép xâm nhập máy chủ PHP-CGI được thiết lập với code page tiếng Trung/tiếng Nhật chỉ bằng yêu cầu
?%ADs- Các code page bị ảnh hưởng là 932 (tiếng Nhật), 936 (tiếng Trung giản thể), 950 (tiếng Trung phồn thể)
- Ký tự nguy hiểm là
U+00AD
- Lỗ hổng PHP-CGI năm 2012 là một lỗi argument injection xảy ra khi Apache tự động xử lý chuỗi truy vấn làm đối số đầu tiên của chương trình CGI
- Thêm
?-scó thể làm lộ mã nguồn trang và dẫn đến RCE - Bản vá PHP hoạt động bằng cách dừng phân tích đối số nếu chuỗi truy vấn bắt đầu bằng dấu gạch ngang
- Thêm
- Do Best-Fit, soft hyphen U+00AD bị chuyển thành
-trong các code page tiếng Trung/tiếng Nhật, khiến bản vá cũ bị vượt qua- Với PHP-CGI,
?%ADscó thể hoạt động như-s - Từ trường hợp này, nhóm nghiên cứu lần đầu gặp thuật ngữ Best-Fit
- Với PHP-CGI,
Filename Smuggling: vấn đề ký tự đường dẫn bị chuyển đổi
- Filename Smuggling là kiểu tấn công trong đó ký tự Unicode nằm trong tên tệp bị đổi thành
/hoặc\trên đường dẫn ANSI API, từ đó có thể tạo ra path traversal- Các API liên quan gồm
GetCurrentDirectoryA,getcwd,FindFirstFileA,findfirst*,GetFullPathNameA, v.v. - Các code page bị ảnh hưởng gồm 874, 125x, 932 (JP), 949 (KR)
- Các ký tự nguy hiểm là
/U+FF0F,\U+FF3C,¥U+00A5 (JP),₩U+20A9 (KR)
- Các API liên quan gồm
d8.exe, Developer Shell của Chrome V8, dùngGetCurrentDirectoryA()trong phần triển khai nội bộ để lấy thư mục làm việc hiện tại- Nếu có thể tạo thư mục làm việc chứa ký tự Unicode độc hại, khi truy cập qua ANSI API nó sẽ bị đổi thành payload path traversal
- Ví dụ, có thể truy cập ngoài ý muốn tới
C:/windows/win.ini
- Phần triển khai Windows của
Dir.getwd()trong mruby phụ thuộc vào hàm ANSI CRT_getcwd()- Giá trị trả về có thể bị nhiễm bẩn và dẫn đến Path Traversal
Cuckoo Sandbox: Từ Path Traversal đến RCE
- Việc truy cập hệ thống tệp Windows của Python từng có thể dùng wide API hoặc ANSI API tùy theo chuỗi là wide hay narrow
- Sau PEP 529, mã hóa hệ thống tệp Windows đã được chuẩn hóa thành UTF-8
- Python 2 và Python 3 trước Python 3.6 vẫn còn ở trạng thái dễ bị tấn công WorstFit
- Cuckoo Sandbox là nền tảng phân tích mã độc tự động, và phiên bản chính thức mới nhất phụ thuộc vào Python 2.7
- Cuckoo gồm Cuckoo Host và VM Cluster
- Mẫu được tải lên sẽ chạy cách ly trong VM, còn gói mạng, tệp được thả và log được đồng bộ bằng cơ chế riêng
- Nếu mã độc tạo tệp được thả có tên tệp Unicode, Path Traversal có thể xảy ra trong xử lý đường dẫn Python trên Cuckoo Host
- PoC ví dụ tạo đường dẫn
AAAA\u00a5..\u00a5..\u00a5..\u00a5..\u00a5..\u00a5conf\u00a5cuckoo.conf - Sau khi phân tích kết thúc, khi người dùng nhấn nút tải xuống trên giao diện web, thao tác tệp của Python được kích hoạt
- Cuckoo Host có thể xử lý đường dẫn đã chuyển đổi chứa
../và gửi dữ liệu nhạy cảm cho kẻ tấn công
- PoC ví dụ tạo đường dẫn
- Kẻ tấn công có thể tải xuống
cuckoo.conf, thu thập thông tin nhạy cảm cần thiết để tính Flask PIN và đạt được RCE trên Sandbox Host- Video demo được cung cấp tại Video 11
Argument Splitting: Best-Fit làm thay đổi cách phân tích dòng lệnh
- Argument Splitting là kiểu tấn công trong đó chuỗi dòng lệnh bị thay đổi ở đầu ra
GetCommandLineAhoặc trên đường đi qua non-Unicodeint main(), khiến các đối số bị tách ra- API và đường đi liên quan là
GetCommandLineA,int main() - Các code page bị ảnh hưởng là 874, 125x, 932(JP), 949(KR)
- Ký tự đe dọa là
"U+FF02,\U+FF3C,¥U+00A5(JP),₩U+20A9(KR)
- API và đường đi liên quan là
- Mã PHP ví dụ dùng
escapeshellarg()để bọc URL một cách an toàn rồi chạywget.exe -q, nhưng với đầu vào" --use-askpass=calc "có thể thực thicalc.exe- Cùng đầu vào đó vẫn không được phòng vệ nếu đổi sang Node.js, Rust, Python
- Nó cũng hoạt động trong ví dụ
subprocess.run(["wget", "-q", ...])của phiên bản Python mới nhất
- Windows truyền toàn bộ dòng lệnh cho tiến trình mới dưới dạng một chuỗi duy nhất, và tệp thực thi tự phân tích chuỗi đó
- Đây không phải cấu trúc luôn truyền mảng đối số như các hệ UNIX
- API
CreateProcessnhận trực tiếp tham sốlpCommandLine
- Trong phân tích dòng lệnh thông thường, các ký tự quan trọng là dấu cách, tab, double quote và backslash
- Dấu cách và tab tách đối số khi không ở quote mode
"chuyển đổi quote mode\escape double quote và backslash trong một số chuỗi nhất định
- Thư viện chuẩn của hầu hết ngôn ngữ escape đối số người dùng theo các quy tắc này, nhưng việc escape kết thúc trước khi chuyển đổi Best-Fit
- PHP
escapeshellargthay double quote bằng dấu cách, bọc đối số bằng quote và xử lý backslash - Python
subprocessdùnglist2cmdlineđể escape theo quy tắc phân tích dòng lệnh Microsoft CRT - Sau đó, nếu trong chuyển đổi ANSI,
"U+FF02 bị đổi thành"U+0022, cú pháp dòng lệnh ban đầu sẽ thay đổi
- PHP
- Các chương trình chỉ dùng
int main()cũng có thể bị ảnh hưởng- Trình biên dịch tạo
mainCRTStartuptrong binary, và hàm khởi động này được liên kết với thư viện CRT - Nếu bên trong CRT lấy dòng lệnh bằng ANSI API rồi phân tích, chuyển đổi Best-Fit sẽ can thiệp
- Do hành vi này, rất khó chặn hoàn toàn tấn công chỉ bằng thư viện chuẩn của một ngôn ngữ lập trình cụ thể
- Trình biên dịch tạo
Các trường hợp thực tế của Argument Splitting
- ElFinder là trình quản lý tệp web mã nguồn mở dựa trên backend PHP, mặc định hỗ trợ máy chủ Windows và tạo/giải nén tệp nén
- Xử lý archive được triển khai bằng thực thi shell command, và đối số được escape bằng
escapeshellarg - Xử lý định dạng tar dùng
tar.exetích hợp sẵn của Windows - Với tên tệp tar như
aaa" "--use-compress-program=calc" "bbb.tar, có thể chèn đối số--use-compress-programđể thực thi lệnh tùy ý - Demo dựa trên Windows server cấu hình tiếng Anh, Code Page 1252, và được tổng kết là cũng sẽ hoạt động trên các code page 125x và Code Page 874
- Video demo được cung cấp tại Video 12
- Xử lý archive được triển khai bằng thực thi shell command, và đối số được escape bằng
- Trường hợp
plink.exeđã được sửa đổi dùng trong TortoiseGit có thể kích hoạt thực thi mã nếu đưa URI độc hại vào đầu vào clone- Có thể xem chi tiết trong curated list
- Video demo là Video 13
- RStudio hỗ trợ quản lý phiên bản SVN, và nếu có dự án SVN trong một thư mục được tạo độc hại thì chỉ cần một cú nhấp là có thể chạy Calculator
- Có thể xem chi tiết trong curated list
- Video demo là Video 14
- Trường hợp Microsoft Excel là CVE-2024-49026, kết hợp Argument Splitting với tính năng “Open-With” của Windows
- Windows duy trì handler table theo phần mở rộng tệp, có thể kiểm tra bằng
ftypevàassoc - Tên tệp trở thành một phần đối số của chương trình handler, nên có thể áp dụng tấn công thông qua tên tệp
- Tên tệp thay dots, slash, backslash, double quote bằng dạng fullwidth sẽ gây argument injection vào
Excel.exe - Bản thân Excel không có đối số phù hợp để khai thác thêm, nên RCE đạt được bằng cách dùng kèm NTLM Relay và RBCD/ADCS
- Video demo là Video 15
- Windows duy trì handler table theo phần mở rộng tệp, có thể kiểm tra bằng
Nhầm lẫn biến môi trường
- Nhầm lẫn biến môi trường xảy ra khi
GetEnvironmentVariableA,GetEnvironmentStringsA,char *getenv()trả về phiên bản biến môi trường đã được chuyển đổi Best-Fit- Code page bị ảnh hưởng và các ký tự đe dọa không được xác định cụ thể
- Trong trường hợp Apache HTTPd, dải 0x00-0xFF có liên quan
- Để cuộc tấn công này thành công, biến môi trường phải có thể bị người dùng kiểm soát
- Trường hợp này xảy ra khi tiến trình cha truyền thông tin cho tiến trình con do nó tạo ra
- Trong CGI, phần lớn thông tin của yêu cầu HTTP như query string, HTTP header được truyền qua biến môi trường
- Ví dụ vượt WAF xét tình huống một CGI script hoạt động giống như routing service
- Cấu hình Apache có quy tắc từ chối
REQUEST_URIchứa/adminđể chặn truy cập từ xa vào/cgi.pl/admin - Do hành vi WorstFit của Windows Perl, có thể vượt qua bằng cách thay một phần của
adminbằng Best-Fit equivalent - Trong Code Page 1250,
àU+00E0 được đổi thànhatrong quá trình chuyển đổi ANSI - Yêu cầu
/cgi.pl/%E0dmintrông như một đường dẫn khác đối với quy tắc phía máy chủ, nhưng khi Perl CGI script đọcPATH_INFObằng ANSI API, nó được xử lý thành/admin
- Cấu hình Apache có quy tắc từ chối
- Trên PHP-CGI on Windows, ở một số cấu hình nhất định đã xác nhận được file existence oracle và LFI tiềm tàng
- Nguyên nhân là cách xử lý
PATH_INFOvà các biến môi trường liên quan đến path khác - Yêu cầu
/index.php/foo/barđược truyền theo chuẩn Apache thành các biến môi trường nhưREDIRECT_URL,REQUEST_URI,PATH_INFO,PATH_TRANSLATED - Chỉ với thông tin này, khó phân biệt rõ ranh giới giữa tên file PHP và phần
PATH_INFObổ sung, vàphp-cgi.exesẽ diễn giải nó
- Nguyên nhân là cách xử lý
- Khi dùng
¥trong code page tiếng Nhật, cách diễn giải đường dẫn của web server và PHP-CGI sẽ khác nhau- Web server xử lý toàn bộ
/..¥..¥windows/win.ini/foonhư phầnPATH_INFObổ sung - PHP-CGI nhận giá trị đã chuyển đổi như
REQUEST_URI=/index.php/..\..\windows/win.ini/foovà bị nhầm lẫn trong quá trình phân biệt file PHP thực tế vớiPATH_INFO - Trên Apache, có thể tạo file existence oracle dựa vào khác biệt phản hồi giữa file không tồn tại và file tồn tại
- Trên IIS, nếu directive
doc_rootđược thiết lập, có thể thực hiện LFI để include và đọcC:\Windows\win.inibằng đường dẫn như/index.php/..¥..¥..¥windows/win.ini/ - Nếu file được include có thể thực thi hoặc chứa mã do người dùng kiểm soát, nó có thể dẫn tới RCE tiềm tàng, nhưng kịch bản này được phân loại gần với một bug hiếm gặp trong ứng dụng thực tế
- Web server xử lý toàn bộ
Những khó khăn trong quá trình công bố và sửa lỗi
- Nhóm nghiên cứu đã báo cáo nhiều vấn đề trong các ngôn ngữ lập trình, dự án mã nguồn mở và chương trình CLI tích hợp sẵn của Windows cho từng upstream maintainer
- Tranh luận nhiều nhất xảy ra ở Argument Splitting
- Một số vendor cho rằng bản thân việc đưa input của người dùng vào command line đã là lỗ hổng
- Việc không rõ trách nhiệm thuộc về ai cũng là một vấn đề
- Đoạn mã có vấn đề trải dài từ
mainCRTStartup()được tự động chèn trong quá trình biên dịch tới các lệnh gọi ANSI API nội bộ của MSVCRT/UCRT - Khó phân biệt đây là vấn đề do lập trình viên không dùng
wmain(), hay do CRT chia command line sai và truyền đối số sai chomain() - Một số dự án chỉ cung cấp mã nguồn, còn Windows prebuilt executable được các tình nguyện viên bên thứ ba trên Internet phân phối
- Đoạn mã có vấn đề trải dài từ
- Việc sửa không chỉ đơn giản là đổi
main()sang phiên bản wide-character- Khi function signature thay đổi, phần định nghĩa biến và logic phân tích đối số phải được viết lại từ nền tảng
char *sangwchar_t * - Quá trình này gây khó chịu và dễ phát sinh lỗi
- Khi function signature thay đổi, phần định nghĩa biến và logic phân tích đối số phải được viết lại từ nền tảng
- Curl trả lời rằng đây là tính năng của Windows nên không có kế hoạch sửa, còn Curl do Microsoft port đã sửa entry thành
wmain(), vì vậycurl.exetích hợp sẵn trong Windows không bị ảnh hưởng- Binary build chính thức của Curl bị ảnh hưởng bởi tấn công Argument Splitting
- Báo cáo đầy đủ đã được công khai trên HackerOne
- OpenSSL có thể xử lý đối số ở dạng wide character thông qua biến môi trường
OPENSSL_WIN32_UTF8- Mục đích ban đầu là sửa vấn đề hiển thị UTF-8 trong UI, nhưng nó cũng giảm thiểu tấn công Argument Splitting
- Trong cách dùng OpenSSL mặc định, nhiều nhà phát triển không biết cần thiết lập biến môi trường này, và có thể thực thi mã tùy ý bằng đối số
-engine
- Bản phân phối chính thức của Perl không cung cấp Windows prebuilt executable, và các trình cài đặt bên thứ ba như Strawberry Perl và ActiveState Perl thường được sử dụng
- Cả hai bản phân phối đều bị ảnh hưởng bởi tấn công Argument Splitting
- Sau khi thảo luận với Perl maintainer, kết luận là “gần với bug của Microsoft hơn là bug của Perl”, nên hiện vẫn chưa được xử lý
- Ba trường hợp đã được báo cáo cho Microsoft qua MSRC, và ban đầu tất cả đều bị từ chối vì không đạt ngưỡng mức độ nghiêm trọng
- Sau nhiều lần mở lại, chỉ trường hợp Excel được chấp nhận sau lần thử thứ ba
- Các trường hợp khác đến nay vẫn chưa được xử lý
- MSRC trả lời rằng chúng phụ thuộc vào lỗ hổng trong đó ứng dụng riêng biệt đưa input không đáng tin cậy vào command line để chạy, và bản thân technique khiến việc khai thác khả thi không đáp ứng yêu cầu để được xem là lỗ hổng
- Nhóm cũng đã nhờ CERT/CC hỗ trợ, và vài tháng sau Microsoft đã thêm cảnh báo bảo mật vào tài liệu
GetCommandLineA- Cảnh báo chỉ được thêm vào
GetCommandLineA, trong khi vẫn còn các ANSI API khác cần chú ý
- Cảnh báo chỉ được thêm vào
Đối tượng bị ảnh hưởng đã được báo cáo và trạng thái
- Các mục đã được xác nhận và báo cáo trong quá trình công bố như sau
- 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
Biện pháp giảm thiểu và bề mặt tấn công còn lại
- Tấn công WorstFit là vấn đề ở cấp hệ điều hành, nên các vấn đề tương tự có thể tiếp tục tái xuất hiện cho đến khi Microsoft bật UTF-8 làm mặc định trên mọi phiên bản Windows
- Việc người dùng có thể làm là kiểm tra và bật tùy chọn UTF-8 của Windows
- Tính năng này hiện vẫn được đánh dấu là beta, và chưa rõ liệu có tác dụng phụ hay không
- Nhà phát triển nên sử dụng Wide Character API nhiều nhất có thể
- CRT cũng cung cấp các phiên bản wide character như
_wgetcwd,_wgetenv - Nếu tiếp tục dùng đường dẫn non-wide, phần triển khai nội bộ có thể gọi ANSI API và bị phơi nhiễm trước tấn công WorstFit
- CRT cũng cung cấp các phiên bản wide character như
- Do tính tương thích ngược của Windows, có thể còn nhiều nơi ẩn chứa ANSI API hơn nữa
- Ví dụ, các truy vấn Windows Registry như
RegQueryValueAcó thể bị ảnh hưởng, nhưng cần tìm ra kịch bản dễ bị tấn công - Nhóm nghiên cứu cũng quan sát thấy hành vi Best-Fit trong Active Directory
- Ví dụ, các truy vấn Windows Registry như
1 bình luận
Ý kiến trên Hacker News
Đây là một vấn đề khá hóc búa. Ánh xạ mã “best fit” của Microsoft là một bộ ánh xạ công khai nhưng thực chất khá “theo cảm tính”, dùng để chuyển Unicode rộng sang ASCII, và nó hiện diện khắp hệ thống.
Bộ ánh xạ này được liên kết mặc định ở rất nhiều nơi, và với cách Microsoft nhìn nhận khả năng tương thích ngược, có vẻ nó buộc phải tiếp tục được đưa vào. Các exploit thường xuất phát từ việc những code point đặc biệt được ánh xạ “theo cảm giác” thành dấu gạch chéo, dấu gạch nối, dấu ngoặc kép, v.v. Bên trong các ngôn ngữ hiện đại, chúng được kiểm tra như Unicode đúng chuẩn, nhưng khi được chuyển sang lệnh shell hoặc Win32 API, sau khi đã trao quyền điều khiển, chúng lại bị thu hẹp/chuyển đổi theo một cách khác. Như quản trị viên curl nói, ở đây “curl là nạn nhân”, vấn đề là thủ phạm là ai. Nếu server làm méo dữ liệu đầu vào của người dùng theo cách khác nhau giữa lúc kiểm tra và lúc truyền cho thư viện hệ thống, rốt cuộc sẽ phát sinh vấn đề. Một tùy chọn tắt chuyển đổi best fit ở phía Win32 có thể là lời giải, nhưng tôi không phải chuyên gia Windows nên chỉ đoán vậy. Dù làm thế, nó vẫn tiếp tục phải tương tác với các API chính thức hoặc phần mềm chưa tắt cơ chế này.
"w"thay vì"a". Cách này cũng giải quyết luôn vấn đề đường dẫn dài quá 260 ký tự nếu thêm tiền tố"\\?\"hoặc thiết lập manifest đúng cách; nó đã khả dụng và được khuyến nghị từ sau Windows XP.Tôi không rõ vì sao API không phải Unicode đến nay vẫn được dùng phổ biến như vậy. Khó có thể tưởng tượng đó là vì mong muốn hỗ trợ Windows 98 hay Windows 2000.
Thứ cần thêm nữa là một dạng linting. Trong ứng dụng hiện đại, thường không có lý do gì để gọi các hàm ANSI WinAPI. Cũng có thể đặt locale là UTF-8 rồi chỉ dùng các hàm 8-bit, nhưng tôi không biết nó hoạt động tốt đến đâu. Theo tôi biết cũng có vài thiết lập và header để khiến
argv,printf,std::couthoạt động với UTF-8, và chỉ dùng các hàm chuyển đổi UTF-8/UTF-16 cho WinAPI mà không có các chuyển đổi kỳ quặc. Microsoft nên tài liệu hóa quy trình như vậy ở một nơi.Điều này ở mức nào đó có thể dự đoán được, nhưng ngay cả với tư cách người từng làm phát triển Windows và hack API Wine khoảng 10 năm vào thời điểm bắt đầu có sự lẫn lộn W/A, tôi vẫn thấy mới.
Windows giống trò bài Munchkin: khi nhiều tính năng vô tình ăn khớp với nhau, chúng có thể hợp thành những exploit ngẫu nhiên và mạnh đến khó tin. Việc họ đang chuyển subsystem ANSI sang UTF-8 là điều đáng mừng, và về lý thuyết có thể giảm nhẹ nhiều vấn đề kiểu này. Tôi cũng tò mò liệu nhóm Rust có phải thực hiện một bản sửa khác cho API tạo tiến trình hay không.
Tất nhiên Rust không thể kiểm soát những gì xảy ra bên ngoài ranh giới tiến trình. Nếu ứng dụng do Rust chạy dùng ANSI API thì phía đó sẽ gặp vấn đề, nhưng đó là trách nhiệm của ứng dụng ấy.
Nếu tôi nhớ không nhầm, “loại bỏ dần ANSI và khuyến nghị dùng Wide Character API” đã là lập trường chính thức của Microsoft từ thời NT 3.5.
Đáng tiếc là một trong những trở ngại lớn là cách triển khai thư viện runtime C/C++
msvcrt.dllcủa Microsoft. Các hàm wide không chuẩn như_wfopen(),_wgetenv()bên trong dùng các hàm W của Win API, nhưng các hàm narrow chuẩn nhưfopen(),getenv()thì không chuyển sang phiên bản wide mà cứ dùng thẳng hàm A. Và các hàm A thường không báo lỗi chuyển đổi Unicode mà che lấp bằng cơ chế best-fit. Người port phần mềm viết bằng C sang Windows thường không muốn đổi toàn bộ việc dùng hàm chuẩn sang các hàm không portable của Microsoft. Từ thời điểm đó trở đi thì gần như là viết lại toàn bộ.activeCodePagethành UTF-8 trong manifest của ứng dụng rồi chỉ dùng các hàm “ANSI”.#definecác hàm chuẩn nhưmainvàfopensang hàm wide tương ứng.Làm vậy thì không thể cứ dùng
char*và literal chuỗi không trang trí được nữa, nên ta định nghĩa kiểutcharlàchartrên Linux vàwchar_ttrên Windows, cùng macro_T()cho literal chuỗi. Nhìn chung nó hoạt động ổn mà không cần nghĩ nhiều.-Aluôn hiện trước chứ không phải biến thể-W. Không biết có gì lạ trongrobots.txthay không, nhưng thật kỳ quặc khi API vốn khuyến nghị dùng biến thể-Wtrong mã mới lại mặc định trả về API legacy.msvcrt.dllcủa Microsoft đã được thay thế bằng Universal C Runtime(UCRT)[1], và UCRT tuân thủ C99.Có hai cách để ép code page “Ansi” thực sự thành UTF-8 trong ứng dụng tự viết hoặc EXE đã patch.
Một là dùng tệp manifest, hoạt động từ một số build nhất định của Windows 10. Nó cũng có thể áp dụng cho EXE bất kỳ sau khi build, nên có thể cưỡng ép đưa hỗ trợ UTF-8 vào chương trình. Đặc biệt hữu ích với chương trình console mode. Cách còn lại là dùng kiểu hack mà các công cụ dạng “App Locale” sử dụng. Một phương pháp bao gồm gọi hàm không được tài liệu hóa của NTDLL. Tôi không biết chính xác cần hàm nào, nhưng
RtlInitNlsTablesvàRtlResetRtlTranslationscó thể liên quan.Tôi không chắc Microsoft có khả năng bật UTF-8 làm mặc định trên mọi phiên bản Windows hay không. Có nhiều ứng dụng cũ giả định một code page cụ thể hoặc 1 byte cho mỗi ký tự, nên có thể bị hỏng
Tinh vi hơn, cũng có những ứng dụng tái sử dụng buffer cũ khi chuyển đổi từ ký tự wide sang ANSI, vì giả định số byte sẽ không tăng. Với UTF-8 thì không đúng như vậy, còn với hầu hết các code page hiện có thì nhìn chung lại đúng, nên có thể phát sinh lỗ hổng mới. Có lẽ tốt hơn nhiều là loại bỏ logic Best-Fit khỏi API Win32
xxxA, và thay các ký tự không thể ánh xạ bằng một ký tự nhưxkhông có ý nghĩa meta phổ biến[0] https://tambre.ee/blog/adobe_after_effects_windows_utf-8/
Nhìn vào các vấn đề do ánh xạ Best-Fit gây ra, việc đặt nó làm mặc định cũng hợp lý, nhưng Microsoft sẽ cần giúp người dùng tìm cách chạy mã cũ dễ dàng. Một cách kém hợp lý hơn là loại bỏ toàn bộ các ánh xạ trong Best-Fit trỏ tới các ký tự ASCII “đặc biệt”, nhưng cách này không giúp được các ứng dụng liên kết tĩnh CRT. Nó cũng không sửa được lỗ hổng, nên không phải giải pháp tốt. Đôi khi lỗ hổng bảo mật lại là động lực để thúc đẩy phá vỡ tương thích ngược
Microsoft đã biết vấn đề này ít nhất từ 1 năm trước. Lý do là họ đã đưa ra một quy tắc phân tích mã đặc biệt tên CA2101[1], trong đó khuyến nghị rõ ràng không dùng ánh xạ best-fit
Phần mô tả quy tắc có nhắc đến lỗ hổng bảo mật, nhưng cố ý diễn đạt mơ hồ về chi tiết
[1] https://learn.microsoft.com/en-us/dotnet/fundamentals/code-a...
Không cần đổi mọi thứ từ
char *sangwchar *. Có thể chuyển ký tự wide nhận được sang UTF-8, hoặc nếu muốn cho phép cả các chuỗi không hợp lệ như surrogate không có cặp, thì chuyển sang thứ gì đó như WTF-8 của Rust rồi tiếp tục dùngcharTất nhiên cần cẩn thận không trộn chuỗi ANSI hoặc OEMCP với chuỗi UTF-8, nhưng nếu chỉ dùng UTF-8 thì đơn giản. Đây chính là cách tiếp cận mà trang kinh điển https://utf8everywhere.org/ khuyến nghị
Trên máy Windows cá nhân, tôi đã vô tình tránh được lỗi này vì đã bật chế độ UTF-8 từ vài năm trước. Đó là thiết lập được nói ở cuối bài
Tôi bật nó vì các game nước ngoài cũ hiển thị chữ bị lỗi, và dù được ghi là “Beta”, tôi không cảm thấy có lỗi hay tác dụng phụ nào
Tôi từng thắc mắc liệu checkbox beta có giống với việc đặt
ActiveCodePagethành UTF-8 trong manifest hay không, nhưng xem tài liệu[0] thì thấy ghi rõ GDI không tuân theo code page theo từng process, mà chỉ tuân theo một code page toàn cục duy nhất do checkbox đặtViệc không thể opt-in hoàn toàn UTF-8 cho các API
*Atrong ứng dụng của mình thì hơi đáng tiếc. Dù vậy, với các vấn đề mà bài viết nhấn mạnh, tôi nghĩ nó vẫn có thể là một biện pháp обход tránh hoặc phòng thủ theo chiều sâu còn hiệu lực[0] https://learn.microsoft.com/en-us/windows/apps/design/global...
Trời ạ. Tôi biết Windows API có cung cấp kiểu chuyển đổi best-fit đó, nhưng không biết đó lại là hành vi mặc định của nhiều hàm ANSI trên code page mặc định của tôi là 949[1]
Đến mức này thì nên cấm luôn như
gets. [1] Tôi biết có code page UTF-8 65001. Trong một thời gian dài nó gần như không thể dùng được, và đến nay vẫn còn gặp vấn đề tương thích