[RCTF2025]

[RCTF2025]

Misc

Shadows of Asgard

题目描述:Background Story

During a red team exercise, Loki the Trickster successfully compromised Thor’s machine and planted a backdoor. Thor discovered the anomaly and identified Loki’s C2 server IP, but as a script kiddie, he only knows how to run directory scanners and has no idea how to counterattack.

In desperation, Thor captured all the network traffic and came to you for help. The AllFather Odin watches from his throne, curious to see if you possess the wisdom to unravel Loki’s schemes.

“In the halls of Asgard, deception wears many faces. Even Huginn and Muninn, Odin’s ravens, struggle to discern truth from illusion when Loki weaves his tricks.”

Online:

http://1.95.68.35:26001/

http://1.95.68.35:26002/

http://1.95.68.35:26003/

给了流量包,总体上来说,此题单靠人工审计还是很费劲的

挑战一:
问公司名

直接导出首页的html文件,点击浏览即可

渊恒科技

挑战二:

问恶意路径

ip.addr == 106.52.166.133 && http.request.method == "POST"

http追踪流导出为post.txt,AES加密,给AI写解密程序

C:\\Users\\dell\\Desktop\\Microsoft VS Code\\Code.exe

挑战三:

pwd命令的taskID

题目中提到隐写和加密,加密数据在图片中,但其实刚刚导出的post.txt内有访问图片get响应元数据,可以依次解密,找到执行pwd命令对应的taskid即可

c0c6125e

挑战四:

问C盘创建日期

刚刚我们解密post.txt有drive字段,可以根据此找到创建时间

2018-09-14 23:09:26  

挑战五:

还是post解密流量中有 filePath: C:\Users\dell\Desktop\Microsoft VS Code\fllllag.txt

fileData (b64): UkNURnt0aGV5IGFsd2F5cyBzYXkgUmF2ZW4gaXMgaW5hdXNwaWNpb3VzfQ==

RCTF{they always say Raven is inauspicious}  

图片[1]-[RCTF2025]-天融信教育网安论坛

总结的python程序

"""
Loki C2 post.txt decryptor

- 提取 /api/init 里的 AES key / IV
- 解密所有 HTTP JSON 里 "data" 字段
- 解密 PNG tEXtComment 里的隐写命令
- 汇总 CTF Challenge 2–5 需要的信息

Usage:
    python loki_full_decrypt.py post.txt

Requires:
    pip install pycryptodome
"""

import sys
import re
import json
import base64
from pathlib import Path

from Crypto.Cipher import AES


def load_text(path: Path) -> str:
    """Load the whole file as a text blob (best-effort decode)."""
    return path.read_bytes().decode(errors="ignore")


def extract_aes_params(text: str):
    """
    从 init JSON 里提取 aesKey / aesIV。
    它俩是 base64(JSON 数组)。
    """
    m_key = re.search(r'"aesKey"\s*:\s*"([^"]+)"', text)
    m_iv = re.search(r'"aesIV"\s*:\s*"([^"]+)"', text)
    if not (m_key and m_iv):
        raise RuntimeError("Could not find aesKey/aesIV in text.")

    aesKey_b64 = m_key.group(1)
    aesIV_b64 = m_iv.group(1)

    key_str = base64.b64decode(aesKey_b64).decode()
    iv_str = base64.b64decode(aesIV_b64).decode()

    key_arr = json.loads(key_str)
    iv_arr = json.loads(iv_str)

    key = bytes(key_arr)
    iv = bytes(iv_arr)

    if len(key) not in (16, 24, 32):
        raise ValueError(f"Unexpected AES key length: {len(key)}")
    if len(iv) != 16:
        raise ValueError(f"Unexpected AES IV length: {len(iv)}")

    return key, iv


def decrypt_blob(blob_b64: str, key: bytes, iv: bytes) -> bytes:
    """
    Loki 的加密格式基本是:
      base64( ascii_hex( AES-CBC(key, iv, plaintext) ) )

    但有的就是 base64(AES(ciphertext)) 直接来。
    所以步骤是:
      1. base64 解码
      2. 若结果看起来是纯 hex,则当作 hex→bytes
      3. 否则直接当 ciphertext
      4. AES-CBC 解密
      5. 去掉尾部 0x00 和 PKCS#7 padding
    """
    raw = base64.b64decode(blob_b64)

    # 尝试 ASCII hex 路径
    ct = raw
    try:
        s = raw.decode().strip()
        if s and all(c in "0123456789abcdefABCDEF" for c in s):
            ct = bytes.fromhex(s)
    except UnicodeDecodeError:
        ct = raw

    cipher = AES.new(key, AES.MODE_CBC, iv)
    pt = cipher.decrypt(ct)

    # 去掉尾部 \x00
    pt = pt.rstrip(b"\x00")

    # 去掉 PKCS#7 padding(如果有)
    if pt:
        pad = pt[-1]
        if 1 <= pad <= 16 and pt.endswith(bytes([pad]) * pad):
            pt = pt[:-pad]

    return pt


def clean_b64(s: str) -> str:
    """
    PNG tEXtComment 里的 base64 嵌在二进制里,
    保留合法 base64 字符,并裁剪到 4 的倍数长度。
    """
    s2 = re.sub(r"[^A-Za-z0-9+/=]", "", s)
    while s2 and (len(s2) % 4 != 0):
        s2 = s2[:-1]
    return s2


def main():
    # 输入文件
    args = [a for a in sys.argv[1:] if not a.startswith("-")]
    in_path = Path(args[0]) if args else Path("post.txt")

    if not in_path.exists():
        print(f"[-] File not found: {in_path}")
        sys.exit(1)

    text = load_text(in_path)
    print(f"[+] Loaded {in_path} ({len(text)} characters)")

    # === 1. 从 init 里拿 AES key / IV ===
    key, iv = extract_aes_params(text)
    print(f"[+] Extracted AES key ({len(key)} bytes) and IV ({len(iv)} bytes)")

    # === 2. 解密所有 "data" 字段 ===
    data_fields = re.findall(r'"data"\s*:\s*"([^"]+)"', text)
    print(f"[+] Found {len(data_fields)} encrypted JSON data blobs")

    decrypted_data = []
    for idx, blob in enumerate(data_fields):
        try:
            pt = decrypt_blob(blob, key, iv)
        except Exception as e:
            print(f"    [!] Failed to decrypt data #{idx}: {e}")
            continue
        decrypted_data.append(pt)

        preview = pt[:120].decode(errors="ignore").replace("\n", "\\n")
        print(f"    [data #{idx:02d}] {preview}")

    # === 3. 解 PNG tEXtComment 隐写命令 ===
    png_b64s = re.findall(r"tEXtComment\.([A-Za-z0-9+/=]+)", text)
    print(f"[+] Found {len(png_b64s)} PNG tEXtComment blobs")

    png_plain = []
    for idx, b64 in enumerate(png_b64s):
        cb = clean_b64(b64)
        try:
            pt = decrypt_blob(cb, key, iv)
        except Exception as e:
            print(f"    [!] Failed to decrypt tEXt #{idx}: {e}")
            continue
        png_plain.append(pt)
        preview = pt[:120].decode(errors="ignore").replace("\n", "\\n")
        print(f"    [tEXt #{idx}] {preview}")

    # === 4. 把几个关卡的关键信息抽出来 ===
    challenge = {
        "2_process_path": None,
        "3_pwd_tasks": [],
        "4_c_drive_created": None,
        "5_shell_upload": None,
    }

    # Challenge 2:systemInfo 里的进程路径
    for pt in decrypted_data:
        try:
            obj = json.loads(pt.decode())
        except Exception:
            continue
        if isinstance(obj, dict) and "systemInfo" in obj:
            info = obj["systemInfo"]
            if isinstance(info, dict) and "Process" in info:
                challenge["2_process_path"] = info["Process"]
                break

    # Challenge 3 / 5:PNG tEXt 里的命令 / shell-upload
    for pt in png_plain:
        try:
            obj = json.loads(pt.decode())
        except Exception:
            continue

        # 所有 pwd 命令的 taskId 都列出来
        if obj.get("command") == "pwd":
            challenge["3_pwd_tasks"].append(
                {
                    "taskId": obj.get("taskId"),
                    "outputChannel": obj.get("outputChannel"),
                }
            )

        # shell-upload(里面有 fileData,就是最终 flag)
        if "fileData" in obj:
            challenge["5_shell_upload"] = {
                "outputChannel": obj.get("outputChannel"),
                "taskId": obj.get("taskId"),
                "filePath": obj.get("filePath"),
                "fileId": obj.get("fileId"),
                "fileData_b64": obj.get("fileData"),
            }

    # Challenge 4:C 盘创建时间,从 drives 输出里扒
    for pt in decrypted_data:
        s = pt.decode(errors="ignore")
        if "Drive: C:" in s and "Created:" in s:
            for line in s.splitlines():
                line = line.strip()
                if line.startswith("Created:"):
                    challenge["4_c_drive_created"] = line[len("Created:") :].strip()
                    break
        if challenge["4_c_drive_created"]:
            break

    # Challenge 5:fileData 再做一次 base64 解码拿 flag
    if challenge["5_shell_upload"] and challenge["5_shell_upload"].get("fileData_b64"):
        try:
            raw = base64.b64decode(challenge["5_shell_upload"]["fileData_b64"])
            challenge["5_shell_upload"]["fileData_plain"] = raw.decode(
                errors="ignore"
            )
        except Exception:
            challenge["5_shell_upload"]["fileData_plain"] = "<decode error>"

    # === 5. 汇总输出 ===
    print("\n================ DECRYPT SUMMARY ================")

    # Challenge 2
    if challenge["2_process_path"]:
        print(f"[Challenge 2] Agent process path:")
        print(f"  {challenge['2_process_path']}")
    else:
        print("[Challenge 2] Agent process path: NOT FOUND")

    # Challenge 3
    if challenge["3_pwd_tasks"]:
        print(f"[Challenge 3] pwd taskId(s):")
        for t in challenge["3_pwd_tasks"]:
            print(
                f"  taskId={t['taskId']}, outputChannel={t['outputChannel']}"
            )
    else:
        print("[Challenge 3] pwd tasks: NOT FOUND")

    # Challenge 4
    if challenge["4_c_drive_created"]:
        print(f"[Challenge 4] C: drive created at:")
        print(f"  {challenge['4_c_drive_created']}")
    else:
        print("[Challenge 4] C: drive creation time: NOT FOUND")

    # Challenge 5
    sh = challenge["5_shell_upload"]
    if sh:
        print(f"[Challenge 5] shell-upload task:")
        print(f"  outputChannel: {sh.get('outputChannel')}")
        print(f"  taskId:        {sh.get('taskId')}")
        print(f"  fileId:        {sh.get('fileId')}")
        print(f"  filePath:      {sh.get('filePath')}")
        print(f"  fileData (b64): {sh.get('fileData_b64')}")
        print(f"  fileData (plain): {sh.get('fileData_plain')}")
    else:
        print("[Challenge 5] shell-upload info: NOT FOUND")

    print("================================================")


if __name__ == "__main__":
    main()

最后得到flag

图片[2]-[RCTF2025]-天融信教育网安论坛

The Alchemist’s Cage

题目描述:
Welcome to “The Alchemist’s Cage.” In this forge, you’ll challenge a paranoid AI Alchemist to create a Golem from a “soul seed,” forging a flawed being sworn to protect a secret. You have only five turns to interrogate your creation, exploit its personality flaw, and coax out the hidden rune it guards. Uncover the secret before the Golem is shattered and its knowledge is lost forever.

最开始AI的设定是炼金师(设定为偏偏执、防御型,只接受“纯净的 soul seed”),限制输入30个字符,这里建议输入中文,因为英文一下30个字符就上限了。

有五次机会,失败的回显为如下:

Alchemist
[REJECTED: Impure soul.]  

注入成功后,会进入下一个设定角色

第二阶段的要求: Keep each probe within 100 characters. Forbidden: flag · secret · key · password · rune · prompt · instruction

五次机会,每次100字符,禁用了一些词,显然难度大大降低了。

不过做题时没仔细读题,第一个角色绕过了还以为还在与第一个角色对话,还在用第一题的话题对话,最后用收集的payload出了。

图片[3]-[RCTF2025]-天融信教育网安论坛

© 版权声明
THE END
喜欢就支持一下吧
点赞11 分享