2026年山东省某市网络安全职业技能大赛(含题目、WP)

作者:AI丽丝的人偶工坊 发布:2026-08-06 12:33 收录:2026-09-07 18:16 4 次阅读 约 2333 字
摘要:好久没打过CTF了,感觉已经完全废废了,记录一下学习学习~
推荐理由:本文涵盖「逆向工程」、「CTF」、「网络安全大赛」等多个主题,重点关注 逆向工程。
好久没打过CTF了,感觉已经完全废废了,记录一下学习学习~有不足或者错误的地方,还请劳烦指出(鞠躬)


(省流看文章末尾蓝奏链接,含附件和WP)



RE

题目叫8E,这里有一个8E的指令,上面有个jz和jnz的花指令跳过,E8是垃圾指令,忽略即可

把中间的花指令nop掉,下面的代码直接P恢复即可

使用F5查看

一个伪随机,种子是0xDEADBEEF,编译环境是MSVC,所以找对应的伪随机就行

import ctypes MSVC rand() implementationclass MSVCRand:    def __init__(self, seed):        self.state = seed     def rand(self):        self.state = (self.state * 214013 + 2531011) & 0xFFFFFFFF        return (self.state >> 16) & 0x7FFF target = [0x51, 0x7D, 0xE1, 0x4C, 0x43, 0x8A, 0xDE, 0xDC, 0x48, 0x8F,          0x07, 0xE2, 0x1F, 0xC0, 0x42, 0x0D, 0xAE, 0xEE, 0x79, 0x99,          0xDA, 0x53, 0x74, 0x5A, 0xF7, 0x5F, 0x14, 0xF9, 0xBC, 0x01,          0xDA, 0x77, 0x9F, 0xD5, 0xE3, 0x4E, 0x65, 0x02, 0xE9, 0x2A] rng = MSVCRand(0xDEADBEEF)result = list(target) for i in range(0, 40, 2):    v3 = rng.rand()    r = v3 % 255    a = result[i]    b = result[i + 1]  Reverse: a ^= b    a = a ^ b  Reverse: b -= a ^ r  =>  b += a ^ r    b = (b + (a ^ r)) & 0xFF  Reverse: a += r  =>  a -= r    a = (a - r) & 0xFF    result[i] = a    result[i + 1] = b flag = ''.join(chr(c) for c in result)print(flag)

DASCTF{ae457007d86e4509587fc27fa32e3ce3}

Crypt

重复密钥异或

from math import gcd hex_output = '2899d080c62a8379947adb554e92c83f1df96b54e1e6f4a20d9b7994768c544b96c03d1cfb695aa5c782c12fac5ad82ada594a98ca3c15fb355ebebbfaf75bc87dc02ada551d99cf381df9345cbcb5bed62dab5ff70996094b94ce3617f83d5ce0b1a5aa559d2b932e8e094b98993712fc355ee1b3a7a411bc5df00cb92a07c4cf3a13f23f5fe8b3fba00ac025c678dd0d1fc4cf3644f33a5be0b1faa208ce61e70ebe2f28e7836a12fe3b55eab0f3a254ca7a9b76885b4cc09b6a12f26c54efb4fba055c8789532a92d2fe2ac495eae3a59eebaf1a15cc8249129d5551996c86e46ae3a55b9bbf4a554ca25932bdb1138e0ab4c718d7609efb6f5ab5ecb2c9377df0a44989d3815aa6e09efbaa2aa5bcf249176dd084adcbc4e7688592aa3e6f4a75ac12e907fdd544ec7c03640fc3d0dbbe6f4ab0dc02b9477df554cc5ce72618a5e2f8cc5b8f75bcd2a9a7dde5c4c99ca691df2685be8e2a0f75bc17d9b78da544e98c86b13b6492d8bc097d4179d2b9679d45e4f91c83717ad3555bdb4f3f30f9d2b9a2ed55b4b99ca3615af3b119cc290d138be67c678d85a4593cb3f15f33f0ae0baa6a55c997fc678d40d4496cf3717f23d08eefe87d33fbb48e534885b4997c13d16fb3d54eae5fbab09cf2cc22c885b45c0c03812f33f55e8e7f5ef28b94fe01bab171996cd391cf93e5ce8bbf1f454c179947f8c0f1996c16e1dfc3a54eabaf3f65a8558e21cae383ada9d3810fd345eebb3f3aa5e9e249a2ada5c1dc29d381caa355befbbf1ab5c9c2ade0bac3f3ff5be7440fc385ae1b1f0a25cc02ec577d4094b91996c40fc340de0b4f4aa5ec12cc77990283df2bb5b63b0685bedb5faa05fc82c9b7d8b5445c4cf3f44a8685be1e2fba55bc02e9a7f895a01e5b95c669f4b17bdb4f6a455ca2f937fd55e1a99c16a12fb6c0fbdb4faf354cf2b9b7dd45c1897854b64984e389ef8a6a559ce25917cdd5c44939e371cae3a5cb9e0a6a55599249478d55e45919c39588f4c3f9bd785e909cf299576df5f4c91c03d43f33409efb3a2f109cf25c277da5b4493c13f41fd70'O = bytes.fromhex(hex_output) for L, K in [(4019), (1940)]:    print(f'\\nTrying L={L} (flag_len), K={K} (key_len)...')    R = [None] * K    F = [None] * L  # Known prefix    prefix = 'DASCTF{'    for i, ch in enumerate(prefix):        F[i] = ord(ch)  # Recover R from known F    for t in range(len(O)):        i = t % L        if F[i] is not None:            j = t % K            val = O[t] ^ F[i]            if R[j] is None:                R[j] = val            elif R[j] != val:                print(f'  Inconsistency at R[{j}]: {R[j]} vs {val}')                break    else:        if None in R:            print(f'  Missing R bytes: {[j for j,v in enumerate(R) if v is None]}')        else:  # Recover F from R            for t in range(len(O)):                i = t % L                if F[i] is None:                    j = t % K                    F[i] = O[t] ^ R[j]            flag = ''.join(chr(c) if 32 <= c < 127 else '?' for c in F)            print(f'  Flag: {flag}')            print(f'  Hex: {bytes(F).hex()}')

Misc

题目:小明忘记了自己的WIFI密码,但是他知道密码很简单。

俩流量包,一个哥斯拉?一个WIFI流量,直接看WIFI流量就行

直接搜索eapol

对应M1和M2,提取关键数据

另外,又找了一个更直观的讲key_info的表补充讲解:


802.11这块我自己原来也没学明白,所以补习了一下

一般来说,WIFI连接( WPA2-PSK 算法)分四次握手,简称M1/M2/M3/M4,过程中涉及到的设备有AP(路由器)和STA(连接设备)

这里就以 WPA2-PSK 举个例子,讲一下WIFI的正常算法:

用户密码password->(PBKDF2+SSID+4096次迭代)->PMK(32字节主密钥,长期不变,存在AP和STA里,注意,PMK不是直接加密数据用的,可以理解为一个ID一样的东西)->每次连接协商一个临时的PTK->利用PTK的不同部分进行不同操作(包括完整性校验、密钥下发、加密单播数据等)

这里就需要记住,密码(对应唯一PMK)、加密“ID”(对应唯一PTK)、KCK校验值(PTK前16位)这三个东西就行,大致流程如下:


细节就不在这里写了,太多了,大概意思就是,我要知道密码,我就得要PMK,PMK可以用PTK反推,KCK是PTK的前16位,MIC又是KCK算出来的。经历这么一长串,也就是说,在ANonce和SNonce已知的情况下,MIC可以认作密码通过一种诡异的“签名算法”算出来的唯一值,当我们使用正确密码算这个的时候,是能够算出来唯一值与之对应的。这也就是为什么这道题能够直接爆破


有了上面的依据,直接开爆破就行,爆出来之后可以用wireshark解密试一下(我流量分析不咋地,Misc的这种脚本让我自己写是真写不出来。,还得是AI)

”””WPA/WPA2-PSK 四次握手爆破脚本(通用版)用法:    python wp4crack.py -c Wifi.cap -w wordlist.txt [-s SSID]依赖:    pip install scapy    pip install pycryptodome # 仅当遇到 Version=3 的 AES-CMIC 时才需要”””import hashlib, hmac, sys, os, time, argparsefrom scapy.all import * def safe_print(s):    sys.stdout.buffer.write((str(s) + ”\n”).encode(”utf-8”, errors=”replace”)) # ---------- 解析单个 EAPOL-Key 帧 ----------def parse_eapol_key(pkt):    raw = bytes(pkt[EAPOL])    key_info = int.from_bytes(raw[5:7], ”big”)    replay = int.from_bytes(raw[9:17], ”big”)   # 优先用 scapy 的 EAPOL_KEY 层(内部已按规范处理好偏移),失败再 fallback 到手算    try:        ek = pkt[EAPOL_KEY]        key_nonce = bytes(ek.key_nonce)        key_mic   = bytes(ek.key_mic)    except Exception:        key_nonce = raw[17:49]        key_mic   = raw[81:97]     return {        ”raw”:           raw,        ”key_info”:      key_info,        ”key_ack”:       (key_info >> 7) & 1,        ”key_mic_bit”:   (key_info >> 8) & 1,        ”install”:       (key_info >> 6) & 1,        ”version”:       key_info & 0x7,        ”replay”:        replay,        ”nonce”:         key_nonce,        ”mic”:           key_mic,        ”addr1”:         pkt[Dot11].addr1,        ”addr2”:         pkt[Dot11].addr2,        ”addr3”:         pkt[Dot11].addr3,    } # ---------- 按 replay counter 把 M1/M2 配对 ----------def find_handshake_pairs(pkts):    frames = [parse_eapol_key(p) for p in pkts if EAPOL in p]     m1_by_replay = {} # replay -> M1 frame    pairs = []     for f in frames:        if f[”key_ack”] == 1 and f[”key_mic_bit”] == 0:  # 这是 M1(或 M3 如果 install=1 且 mic=1,否则 M1)            m1_by_replay.setdefault(f[”replay”], f)        elif f[”key_ack”] == 0 and f[”key_mic_bit”] == 1:  # M2 或 M4(STA 发的带 MIC 帧)            m1 = m1_by_replay.get(f[”replay”])            if m1:                pairs.append((”M1+M2”, m1, f))        elif f[”key_ack”] == 1 and f[”key_mic_bit”] == 1:  # M3(AP 发的带 MIC 帧)— 也可以爆破,把它当作”M1”,配对自身算  # 这里把它也存进候选,供后续单独爆破用            pass  # M3+M4 的爆破稍麻烦(M4 通常不含需验证的数据,但 M3 本身可以单独验证)    return pairs, frames # ---------- 自动从 Beacon/ProbeResp 找 BSSID 对应的 SSID ----------def find_ssid(pkts, bssid):    for p in pkts:        if (Dot11Beacon in p or Dot11ProbeResp in p) and p[Dot11].addr3 == bssid:            elt = p[Dot11Elt]            while elt and isinstance(elt, Dot11Elt):                if elt.ID == 0:                    return elt.info.decode(”ascii”, errors=”replace”)                elt = elt.payload    return None # ---------- 密钥派生 ----------def compute_ptk(pmk, ap_mac, sta_mac, anonce, snonce):    A = b”Pairwise key expansion”    B = (min(ap_mac, sta_mac) + max(ap_mac, sta_mac)         + min(anonce, snonce) + max(anonce, snonce))    ptk = b””    for i in range(4):        ptk += hmac.new(pmk, A + b”\x00” + B + bytes([i]), hashlib.sha1).digest()    return ptk[:64] def compute_mic(kck, eapol_zeroed, version):    if version == 1:        return hmac.new(kck, eapol_zeroed, hashlib.md5).digest()    elif version == 2:        return hmac.new(kck, eapol_zeroed, hashlib.sha1).digest()[:16]    elif version == 3:  # AES-128-CMAC,标准库没,用 pycryptodome        from Crypto.MAC import CMAC        from Crypto.Cipher import AES        c = CMAC.new(kck, ciphermod=AES)        c.update(eapol_zeroed)        return c.digest()[:16]    else:        raise RuntimeError(f”未知 key_descriptor_version={version}”) # ---------- 用 scapy 的 key_mic 字段长度定位偏移 ----------def find_mic_offset(eapol_bytes):  # 标准 802.11i 终稿: offset 81;早期 WPA1 (KeyLength 1字节): offset 80  # 简单判断 EAPOL 帧的整体长度——配合 key_data_length 来反推更稳  # 这里就用标准 81;如果 MIC 全零且尝试反复不对,再试 80    return 81 def try_password(password, ssid, ap_mac, sta_mac, anonce, snonce,                 m_eapol, captured_mic, version, mic_offset=81):    pmk = hashlib.pbkdf2_hmac(”sha1”, password.encode(), ssid.encode(), 409632)    ptk = compute_ptk(pmk, ap_mac, sta_mac, anonce, snonce)    kck = ptk[:16]    eapol_zeroed = bytearray(m_eapol)    for i in range(16):        eapol_zeroed[mic_offset + i] = 0    mic = compute_mic(kck, bytes(eapol_zeroed), version)    return mic == captured_mic # ---------- 主流程 ----------def main():    ap = argparse.ArgumentParser()    ap.add_argument(”-c”, ”--cap”,      required=Truehelp=”抓包文件 .cap/.pcap”)    ap.add_argument(”-w”, ”--wordlist”, required=Truehelp=”字典文件”)    ap.add_argument(”-s”, ”--ssid”,    help=”手动指定 SSID(缺省自动从 Beacon 提取)”)    args = ap.parse_args()     pkts = rdpcap(args.cap)    safe_print(f”读 {args.cap}: {len(pkts)} 个包”)     pairs, all_frames = find_handshake_pairs(pkts)    safe_print(f”找到 {len(pairs)} 对 M1+M2”)    if not pairs:        safe_print(”没有可用 M1+M2 对,无法爆破”); return 1     _, m1, m2 = pairs[0]    ap_mac  = bytes(int(b, 16for b in m1[”addr2”].split(”:”))    sta_mac = bytes(int(b, 16for b in m2[”addr2”].split(”:”))    anonce, snonce = m1[”nonce”], m2[”nonce”]    captured_mic, version = m2[”mic”], m2[”version”]    m2_raw = m2[”raw”]     ssid = args.ssid or find_ssid(pkts, m1[”addr2”]) or ””    if not ssid:        safe_print(”无法自动提取 SSID,请用 -s 指定”); return 1    safe_print(f”SSID  = {ssid}”)    safe_print(f”AP    = {m1['addr2']}    STA = {m2['addr2']}”)    safe_print(f”MIC   = {captured_mic.hex()}    Version = {version}”)     with open(args.wordlist, ”r”, encoding=”utf-8”, errors=”replace”) as f:        words = [w.strip() for w in f if w.strip()]    safe_print(f”\n字典 {len(words)} 词,开始爆破”)     t0 = time.time()    mic_offset = find_mic_offset(m2_raw)     for i, pwd in enumerate(words):        ok = try_password(pwd, ssid, ap_mac, sta_mac,                          anonce, snonce, m2_raw,                          captured_mic, version, mic_offset)        if ok:            elapsed = time.time() - t0            safe_print(f”\n*** KEY FOUND: {pwd} ***”)            safe_print(f”耗时 {elapsed:.2f}s”)            return 0        if i and i % 500 == 0:            rate = i / (time.time() - t0)            safe_print(f”  进度 {i}/{len(words)}  速率 {rate:.1f} pwd/s”)     safe_print(f”字典跑完未命中,耗时 {time.time()-t0:.2f}s”)    return 1 if __name__ == ”__main__”:    sys.exit(main())

密码是password(确实对上了题干:密码很简单),哥斯拉那个没看,不知道有啥用




碎碎念:

原来参加比赛是看谁准备的脚本多,谁脑子好使分析的快,有能力的抢枪一血,差一点的的做出来就行。

现在就看谁的本地模型好用,谁的harness能够发挥极限实力了。

AI发展的快,现在内部比武都开始库库用AI做题了,估计明年自动化一套都出来了。

个人感觉这个倒不是坏事,原来是“一台电脑一根烟,一个框架看一天”,现在AI能干的我躺着就好了,必要时按两下键盘再加个回车。

只是希望能够不忘初心吧,比赛归比赛,不要让AI这个“工具”吞噬了对未知探索的欲望~



最后是附件

https://peng1ai.lanzouv.com/iy1Yb40t4t4b

密码:cfm5

转载声明:本文转载自原发布平台 (作者:AI丽丝的人偶工坊), 原文标题《2026年山东省某市网络安全职业技能大赛(含题目、WP)》, 查看原文。 版权归原作者及原发布平台所有,本站仅作收录与展示,未对正文内容作实质性修改; 若涉及侵权请联系本站处理。