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

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

使用F5查看

一个伪随机,种子是0xDEADBEEF,编译环境是MSVC,所以找对应的伪随机就行
import ctypes# MSVC rand() implementationclass MSVCRand:def __init__(self, seed):self.state = seeddef rand(self):self.state = (self.state * 214013 + 2531011) & 0xFFFFFFFFreturn (self.state >> 16) & 0x7FFFtarget = [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 % 255a = result[i]b = result[i + 1]# Reverse: a ^= ba = a ^ b# Reverse: b -= a ^ r => b += a ^ rb = (b + (a ^ r)) & 0xFF# Reverse: a += r => a -= ra = (a - r) & 0xFFresult[i] = aresult[i + 1] = bflag = ''.join(chr(c) for c in result)print(flag)
DASCTF{ae457007d86e4509587fc27fa32e3ce3}
Crypt
重复密钥异或
from math import gcdhex_output = '2899d080c62a8379947adb554e92c83f1df96b54e1e6f4a20d9b7994768c544b96c03d1cfb695aa5c782c12fac5ad82ada594a98ca3c15fb355ebebbfaf75bc87dc02ada551d99cf381df9345cbcb5bed62dab5ff70996094b94ce3617f83d5ce0b1a5aa559d2b932e8e094b98993712fc355ee1b3a7a411bc5df00cb92a07c4cf3a13f23f5fe8b3fba00ac025c678dd0d1fc4cf3644f33a5be0b1faa208ce61e70ebe2f28e7836a12fe3b55eab0f3a254ca7a9b76885b4cc09b6a12f26c54efb4fba055c8789532a92d2fe2ac495eae3a59eebaf1a15cc8249129d5551996c86e46ae3a55b9bbf4a554ca25932bdb1138e0ab4c718d7609efb6f5ab5ecb2c9377df0a44989d3815aa6e09efbaa2aa5bcf249176dd084adcbc4e7688592aa3e6f4a75ac12e907fdd544ec7c03640fc3d0dbbe6f4ab0dc02b9477df554cc5ce72618a5e2f8cc5b8f75bcd2a9a7dde5c4c99ca691df2685be8e2a0f75bc17d9b78da544e98c86b13b6492d8bc097d4179d2b9679d45e4f91c83717ad3555bdb4f3f30f9d2b9a2ed55b4b99ca3615af3b119cc290d138be67c678d85a4593cb3f15f33f0ae0baa6a55c997fc678d40d4496cf3717f23d08eefe87d33fbb48e534885b4997c13d16fb3d54eae5fbab09cf2cc22c885b45c0c03812f33f55e8e7f5ef28b94fe01bab171996cd391cf93e5ce8bbf1f454c179947f8c0f1996c16e1dfc3a54eabaf3f65a8558e21cae383ada9d3810fd345eebb3f3aa5e9e249a2ada5c1dc29d381caa355befbbf1ab5c9c2ade0bac3f3ff5be7440fc385ae1b1f0a25cc02ec577d4094b91996c40fc340de0b4f4aa5ec12cc77990283df2bb5b63b0685bedb5faa05fc82c9b7d8b5445c4cf3f44a8685be1e2fba55bc02e9a7f895a01e5b95c669f4b17bdb4f6a455ca2f937fd55e1a99c16a12fb6c0fbdb4faf354cf2b9b7dd45c1897854b64984e389ef8a6a559ce25917cdd5c44939e371cae3a5cb9e0a6a55599249478d55e45919c39588f4c3f9bd785e909cf299576df5f4c91c03d43f33409efb3a2f109cf25c277da5b4493c13f41fd70'O = bytes.fromhex(hex_output)for L, K in [(40, 19), (19, 40)]:print(f'\\nTrying L={L} (flag_len), K={K} (key_len)...')R = [None] * KF = [None] * L# Known prefixprefix = 'DASCTF{'for i, ch in enumerate(prefix):F[i] = ord(ch)# Recover R from known Ffor t in range(len(O)):i = t % Lif F[i] is not None:j = t % Kval = O[t] ^ F[i]if R[j] is None:R[j] = valelif R[j] != val:print(f' Inconsistency at R[{j}]: {R[j]} vs {val}')breakelse:if None in R:print(f' Missing R bytes: {[j for j,v in enumerate(R) if v is None]}')else:# Recover F from Rfor t in range(len(O)):i = t % Lif F[i] is None:j = t % KF[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 scapypip 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 framepairs = []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.payloadreturn 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,标准库没,用 pycryptodomefrom Crypto.MAC import CMACfrom Crypto.Cipher import AESc = 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 全零且尝试反复不对,再试 80return 81def 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(), 4096, 32)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] = 0mic = compute_mic(kck, bytes(eapol_zeroed), version)return mic == captured_mic# ---------- 主流程 ----------def main():ap = argparse.ArgumentParser()ap.add_argument(”-c”, ”--cap”, required=True, help=”抓包文件 .cap/.pcap”)ap.add_argument(”-w”, ”--wordlist”, required=True, help=”字典文件”)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, 16) for b in m1[”addr2”].split(”:”))sta_mac = bytes(int(b, 16) for 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 1safe_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() - t0safe_print(f”\n*** KEY FOUND: {pwd} ***”)safe_print(f”耗时 {elapsed:.2f}s”)return 0if 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 1if __name__ == ”__main__”:sys.exit(main())
密码是password(确实对上了题干:密码很简单),哥斯拉那个没看,不知道有啥用
碎碎念:
原来参加比赛是看谁准备的脚本多,谁脑子好使分析的快,有能力的抢枪一血,差一点的的做出来就行。
现在就看谁的本地模型好用,谁的harness能够发挥极限实力了。
AI发展的快,现在内部比武都开始库库用AI做题了,估计明年自动化一套都出来了。
个人感觉这个倒不是坏事,原来是“一台电脑一根烟,一个框架看一天”,现在AI能干的我躺着就好了,必要时按两下键盘再加个回车。
只是希望能够不忘初心吧,比赛归比赛,不要让AI这个“工具”吞噬了对未知探索的欲望~
最后是附件
https://peng1ai.lanzouv.com/iy1Yb40t4t4b