DF Cyber Defense Write-Up

Lời đầu tiên chúng tôi xin thay mặt BTC gửi lời cảm ơn đến các đội đã tham gia cuộc thi. Sau một thời gian để các đội tiếp tục tự nghiên cứu và tìm hướng giải cho các thử thách còn lại nhằm nâng cao chuyên môn cũng như kỹ năng nghiệp vụ của mình, hôm nay BTC sẽ công khai lời giải cho các thử thách có ít đội giải được. Đối với các thử thách có nhiều đội giải được các đội nếu có thắc mắc có thể liên hệ BTC thông qua kênh hỗ trợ Discord để được giúp đỡ.

Backdoor Detected (Category: Webshell)

Mô tả của thử thách

Ở thử thách này BTC gửi cho các đội một tập tin bao gồm một file access_log.txt và một thư mục ROOT của web server. Sau khi xem qua các tệp do BTC cung cấp, tệp tin đáng ngờ nhất là tệp tin banking.jsp.

Nội dung file banking.jsp nghi ngờ là webshell

Step 1: Decode UTF-16

Thoạt nhìn qua chúng ta có thể phát hiện đây là một kiểu encode dạng UTF-16, chính vì thế chúng ta cần thực hiện decode UTF-16 để có thể có được một nội dung có thể đọc được.

Decode UTF-16

Sau khi thực hiện decode UTF-16 chúng ta có thể thấy đây là một đoạn mã java, đoạn mã này tiếp tục bị làm rối bằng cách thay đổi tên các biến có khuôn dạng $_$ làm cho việc đọc hiểu đoạn mã trở nên khó khăn hơn.

Mã nguồn được giải mã UTF-16

Step 2: Khôi phục các hàm, biến và string của webshell

Sau khi có được tên webshell là banking.jsp, kết hợp với access_log.txt, bước đầu có thể thấy payload được gửi đến webshell qua POST method và ta hoàn toàn không có trong tay payload, vì vậy flag có thể được giấu đâu đó trong webshell hoặc ở một trong số các tệp còn lại của thư mục ROOT. Để biết được nội dung bị đánh cắp được lưu ở đâu chúng ta cần phải hiểu được cách thức hoạt động của web shell này.

Sử dụng một trang web https://kt.gy để khôi phục nhanh các chuỗi byte thành các string

Khôi phục các chuỗi byte thành string

Dựa vào các string, class và function mặc định được sử dụng ta có thể dự đoán được chức năng chính của các hàm được sử dụng trong web shell

Function Write_File
Function Base64_Decode
Function Base64_Encode_AES_Encrypt
Function AES_Decrypt
Function Exec_Command

Step 3: Khôi phục hàm main của webshell

Thực hiện tương tự như các bước ở step 2 chúng ta có được nội dung hàm main như sau

Hàm main

Có thể viết một cách ngắn gọn lại như sau

Hàm main thu gọn

Có thể nhận thấy sau khi payload được gửi lên, web shell thực hiện giải mã chúng, sau đó thực thi, mã hóa output và ghi vào file 404.html.

Như đã trình bày ở step 2, ta không có payload của kẻ tấn công và payload của kẻ tấn công gửi lên sẽ được giải mã và thực thi trong hàm Exec_Command. Do đó, ta chỉ tiến hành dịch ngược đầy đủ hàm Base64_Encode_AES_Encrypt để có các giá trị, từ đó khôi phục data được giấu trong tệp “404.html

Step 4: Dịch ngược Base64_Encode_AES_Encrypt và giải mã dữ liệu

Sau khi trích xuất được dữ liệu được mã hóa và giấu trong file 404.html, chúng ta cần phải nắm được cách thức mà web shell đã thực hiện mã hóa chúng mà không cần quan tâm payload kẻ tấn công gửi lên được giải mã như thế nào. Chính vì thế chúng ta chỉ cần tập trung vào đọc hiểu hàm Base64_Encode_AES_Encrypt.

Lời gọi hàm Base64_Encode_AES_Encrypt dạng ngắn gọn

Hàm Base64_Encode_AES_Encrypt được gọi

Dựa vào string, class và tham số, biến được sử dụng trong các hàm, ta dịch ngược hàm Base64_Encode_AES_Encrypt.

Hàm Base64_Encode_AES_Encrypt sau khi khôi phục

Có thể thấy các tham số được truyền vào hàm Base64_Encode_AES_Encrypt bao gồm như sau:

  • Param1 = “99wp_B0uNTy_p1Z!”
  • Param2 = “192.168.18.80” chính là remote addr, ip nguồn của kẻ tấn công được lấy từ file access_log.txt
  • data = nội dung được đưa vào để mã hóa

Step 5: Thực hiện giải mã bằng cách viết lại script bằng python.

Script giải mã được viết bằng python

Zerologon 01 (Category: Zerologon)

Mô tả của thử thách

Lỗ hổng Zerologon được khai thác theo flow sau:

Flow khai thác lỗ hổng Zerologon

Vì là zerologon nên ta sẽ tập trung vào giao thức RPC_NETLOGON (trong file pcap được ký hiệu là những packets màu tím). Trong zerologon, có hai lần chúng ta xác thực thành công Netlogon, một là khi ta muốn set password về rỗng, và 2 là khi chúng ta muốn set lại về password ban đầu.

Step 1: Set empty password

Forensic một lượt từ đầu file pcap, tại packet 3816, đây là lần xác thực thành công đầu tiên dựa vào status của packet:

Lần xác thực thành công đầu tiên

Trong packet 3818, là request NetrServerPasswordSet2 -> set password về rỗng. Malformed Packet (được chú thích bên trong packet này) nghĩa là wireshark không thể phân tích nội dung gói tin thêm nữa.

Step 2: Secrets dump

Và một đoạn các gói tin tiếp là là quá trình dump data. Flag cần tìm là NTLM hash ban đầu của account computer, đó cũng là data được lấy ra trong quá trình dump. Tuy nhiên việc đọc được NTLM hash từ dữ liệu thu được trong quá trình dump là không khả thi. Chính vì thế ta cần quan tâm đến bước tiếp theo là bước reinstall password của account computer.

Step 3: Reinstall original password

Tiếp theo là quá trình reinstall original password, mà NTLM hash của nó chính là cái chúng ta cần tìm. Theo dõi tiếp file pcap, sau đoạn dump data và tập trung vào những packet màu tím, ta tìm được đoạn xác thực thành công số hai như hình dưới:

Xác thực không thành công sẽ trả về STATUS_ACCESS_DENIED
Xác thực thành công ở packet 8789

Sau khi xác thực thành công, ta thấy packet NetServerPasswordSet request được sử dụng để cài đặt lại mật khẩu của account computer. Đi sâu vào packet này, ta thấy được Encrypted NTLM Pwd:

Encrypted NTLM Pwd

NTLM dùng thuật toán mã hóa SAM, để decrypt thì chúng ta còn cần thêm key, trong trường hợp này, chính là sessionkey, mà sessionkey được tính toán dựa trên giá trị của server challenge:

Script attack [link]

Tại packet 8787, là response từ server, ta thấy giá trị của server challenge:

Giá trị của server challenge

Sau khi đã có đủ ciphertext + key, ta dùng thư viện có sẵn của python để decrypt và tìm được flag:

Script decrypt NTLM hash [link]

Zerologon02 (Category: Zerologon)

Mô tả của thử thách

Step 1: Tìm kiếm câu lệnh được thực thi

Trong quá trình phân tích file pcap, chúng tôi phát hiện có một số packet chứa nhiều chuỗi được mã hóa base64. Nghi ngờ kẻ tấn công sử dụng script powershell được mã hóa base64 tấn công máy chủ AD. Chúng tôi thực hiện search chuỗi string “powershell -e” hoặc “powershell.exe -e”

Tìm kiếm chuỗi powershell
Dữ liệu nghi ngờ là script tấn công

Thực hiện bóc tách script powershell từ pcap cần lưu ý rằng data bắt đầu từ sau cặp từ “IO” do script được chia thành nhiều packet để gửi đi.

Cần loại bỏ cặp từ “IO” khi bóc tách dữ liệu

Step 2: Dịch ngược script tấn công

Thực hiện decode base64 ta được một đoạn mã powershell được encrypt bằng SecureString

Powershell được encrypt bằng SecureString [link]

Thực hiện decrypt đoạn powershell ở trên bằng script sau:

Script decrypt powershell [link]

Sau khi decrypt ta được một đoạn mã powershell như sau:

Mã powershell được giải mã [link]

Các số ở đầu là index của các string phía sau, chúng ta có thể viết script python convert về script ps ban đầu, hoặc dùng tool PSDecode. Từ đó ta có được đoạn mã powershell như sau:

Mã powershell cuối cùng

Với k là mảng đã biết, host name trong file pcap đã có là ABC-DC-01, dùng xor để tìm được nội dung đã ghi vào file và đó chính là flag.

Malware 02 (Category: Malware)

Mô tả thử thách

Step 1: Phân tích

Phân tích file mã độc được BTC cung cấp ta thấy mã độc thực hiện các hành động sau:

  • decrypt resource để có được shellcode
  • copy file mã độc vào %APPDATA%
  • Ghi key auto run vào registry
  • Thực hiện injection shellcode vào một process phù hợp bất kỳ được tìm thấy
Hàm main của file mã độc
Shellcode được inject vào một process phù hợp
Shellcode được load và thực thi dưới dạng một thread chạy ẩn

Có thể thấy file mã độc này đơn giản chỉ inject shellcode vào một process khác trong máy tính và thực thi. Chính vì thế ta cần tập trung phân tích shellcode này.

Có nhiều hướng để có thể phân tích shellcode trong mã độc này, tuy nhiên chúng tôi sử dụng cách làm tương tự như các bước được mô tả trong bài viết tại đường [link] đối với thử thách này ta có được file dll

Step 2: Phân tích file dll được dump ra

Bắt đầu với DllMain:

Function init() được call bởi DllMain()
Một phần nội dung hàm init()

Trong hàm init có define một số chức năng và mã ảnh hưởng đến thuật toán dùng để encrypt dữ liệu của các hàm như sau:

  • RemoteCommand => cmd = 3
  • Upload => cmd = 5
  • Keylogger => cmd = 2
  • Download => cmd = 4
  • Screenshot => cmd = 1

IP và port mà file kết nối đến:

Thông tin c&c

Parse chức năng của các hàm trên

RemoteCommand

Trong hàm Command, ta thấy một số chức năng mà file thực thi.

Case 0 là tạo process cmd.exe, tạo ra một pipe để nhận lệnh và giao tiếp
Case 1 là write command vào pipe
Case 2 là terminate pipe (terminate process)
Case 3 là copy dữ liệu từ pipe, đọc và gửi data nhận được về c2, data sẽ đi vào hàm flow_send_data để mã hóa trước khi được gửi đi.

Download

Dữ liệu từ file được đọc, sau đó đi qua hàm nén dữ liệu (rice compress) và được mã hóa trong hàm flow_send_data trước khi gửi về c&c.

Function Download

Screenshot

Tương tự như hàm Download, dữ liệu từ ảnh chụp màn hình được nén lại qua hàm rice compress -> mã hóa -> gửi về c&c. Mà yêu cầu của challenge này chính là một trong số các ảnh chụp được gửi đi.

Function Screenshot

Flow send data

Hàm này được sử dụng để xử lý và gửi dữ liệu đến c&c

Hàm xử lý dữ liệu và gửi đến c&c

Sau khi phân tích ta thấy hàm này dựa trên giá trị của cmd để random một số thuật toán mã hóa, và tạo 16 bytes key random. Trong hàm algorithm, chúng ta thấy có 4 thuật toán được sử dụng ở đây

Các option mã hóa

Vì ScreenShot có cmd = 1 thuật toán mã hóa sẽ là random XOR, RC4 hoặc là AES. Cuối cùng, quá trình encrypt dữ liệu screenshot giống như sơ đồ sau:

Flow xử lý dữ liệu chụp màn hình

Step 3: Lấy dữ liệu ảnh chụp màn hình

Đầu tiên, chúng ta cần extract data từ file pcap bằng cách filter theo ip và port, sau đó viết script đơn giản để lấy từng packet send and recv.

Script parse data [link]

Sau khi đã có các packet, phân tích theo struct của các packet như sau (trừ serpent encrypt):

  • 4 bytes đầu là signature
  • 4 bytes sau là insize
  • 1 bytes tiếp là định danh hàm encrypt (0 => XOR, 1 => RC4, 2 =>AES)
  • 16 bytes kế tiếp là key
  • 4 bytes tiếp là outsize
  • Và còn lại là data
Minh họa trên một packet

Viết script đơn giản để decrypt 3 thuật toán mã hóa trên (trừ serpent):

Script decrypt 3 thuật toán [link]

Tiếp đó là decompress rice. Dùng hàm decompress tương tự như [link] để giải nén dữ liệu và ta thu được một số ảnh màn hình gửi đi trong đó có một ảnh có chứa flag.

Flag thu được

Malware 03 (Category: Malware)

Mô tả thử thách

Từ dữ liệu lấy được ở thử thách Malware 02 ta nhận thấy có một số packet sử dụng serpent là thuật toán mã hóa sẽ được parse theo struct sau:

  • 4 bytes đầu là signature
  • 4 bytes sau là insize
  • 1 bytes tiếp là định danh hàm encrypt bằng serpent với giá trị mặc định bằng 4
  • Và còn lại là data

Step 1: Decrypt serpent

Sử dụng hàm như trong [link] để decrypt dữ liệu extract được từ file pcap được mã hóa bằng serpent.

Packet được encrypt bằng serpent

Tại packet số 30, sau khi decrypt ta nhận thấy có signature của file zip và một file có tên là flag2.txt trong đó và một số file Keylogger khác.

File zip được tìm thấy

Thực hiện giải nén file zip này ta thấy có yêu cầu nhập password. Khả năng password được lưu ở đâu đó trong những file Keylogger.

Có thể thấy trong các file Keylogger có một nội dung dạng “Th1s_1s_p4ssw0rd” tuy nhiên đây không phải là password cần tìm vì sử dựng để giải nén không thành công.

Nội dung một file Keylogger

Quay trở lại đọc kỹ hơn hàm Keylogger trong file dll, ta thấy có một hàm hook_keylogger, chính hàm này đã thay đổi giá trị của phím được nhấn trước khi ghi vào file.

Function hook_keylogger

Theo như trên, nó đã thay đổi i -> 1, a -> 4, o -> 0. Từ đó ta có thể suy ra được password chính xác sẽ là: This_is_password

Thực hiện extract zip file với password có được ta có flag như sau:

Flag cần tìm

Credit: @nhiephon, @lanleft