云曦期中复现wp

发布时间:2026/5/25 6:20:17

云曦期中复现wp CRYPTO1.Shadow SuffixAES-ECB这个是AES-ECB解密是我没有见过的类型。由于以后的题目我没有见过的知识点会很多尤其是密码学的。所以这题的目的并不是掌握这个知识点而是如何运用现有工具去解开它。即本地AI我把源代码交给AI并让它帮我写一个求出未知明文的脚本from Crypto.Cipher import AES from Crypto.Util.Padding import pad import sys KEY bytes.fromhex(b233f5981a0c010eba997e68bbec5f45) PREFIX bytes.fromhex(1e6e0e4b2b9984b96d20344a822d8a1c0577e0dfafd23b) MASK 167 BOX bytes.fromhex(fed2c9dfcedcd7d5c2c1cedff8c2c4c5f8c8d5c6c4cbc2f8d0ced3cff8c6cbcec0c9cac2c9d3da) def tail(): return bytes(x ^ MASK for x in BOX) def enc(user_bytes: bytes) - bytes: data PREFIX user_bytes tail() return AES.new(KEY, AES.MODE_ECB).encrypt(pad(data, 16)) def main(): print(send hex input, one line per query, flushTrue) for line in sys.stdin: line line.strip() try: data bytes.fromhex(line) print(enc(data).hex(), flushTrue) except Exception: print(bad, flushTrue) if __name__ __main__: main()通过不断的提问与纠错最后AI是可以给我一个有效的脚本的即下面的代码from Crypto.Cipher import AES from Crypto.Util.Padding import pad KEY bytes.fromhex(b233f5981a0c010eba997e68bbec5f45) PREFIX bytes.fromhex(1e6e0e4b2b9984b96d20344a822d8a1c0577e0dfafd23b) MASK 167 BOX bytes.fromhex(fed2c9dfcedcd7d5c2c1cedff8c2c4c5f8c8d5c6c4cbc2f8d0ced3cff8c6cbcec0c9cac2c9d3da) def tail(): return bytes(x ^ MASK for x in BOX) def enc(user_bytes): data PREFIX user_bytes tail() return AES.new(KEY, AES.MODE_ECB).encrypt(pad(data, 16)) # 恢复未知后缀的ECB攻击 block_size 16 prefix_len len(PREFIX) suffix_len len(tail()) print(f前缀长度: {prefix_len}) print(f后缀长度: {suffix_len}) # 需要的padding使前缀对齐到块边界 pad_to_block (block_size - (prefix_len % block_size)) % block_size print(f需要 {pad_to_block} 字节padding) # 只有 suffix[0] 可以通过ECB攻击恢复 padding_len pad_to_block - 1 # 8 padding bA * padding_len target_cipher enc(padding) target_block target_cipher[1*block_size : 2*block_size] recovered b for byte in range(256): test_input padding bytes([byte]) test_cipher enc(test_input) test_block test_cipher[1*block_size : 2*block_size] if test_block target_block: recovered bytes([byte]) print(f恢复第1个字节: {chr(byte)}) break # 直接计算完整后缀因为我们知道MASK和BOX full_suffix tail() print(f\n完整后缀: {full_suffix.decode(ascii)}) print(f十六进制: {full_suffix.hex()})最后可以得到结果Yunxi{prefix_ecb_oracle_with_alignment}2.Twin Counter这里依然是使用本地AI写脚本提示词如下请帮我写一个Python脚本解决这个加密解密问题已知条件- 一段已知的明文十六进制5075626c6963206e6f74653a206964656e746963616c206e6f6e636573207769746820636f756e746572206261736564206d6f6465732072657665616c207265706561746564206d61736b732e20- 这段明文对应的密文十六进制587335adeb135a58295d037715dcb2bc4478fefb7b36721fdbc31ab2adcc89191512208d8607c9e4079a3f6b9b7da03e6f467adcf4629a271d3632e94a0405b8b084912961773b27110e185256- 目标密文十六进制517339b9eb0b1d552b7608225bd6b3865869e2eb7f053e14d5c60a88aa849b2f120e728b881fda要求1. 利用已知明文和密文找出加密时用的密钥流可以理解成一把钥匙2. 用这把钥匙解开目标密文3. 输出解密后的结果包括十六进制和能看懂的文字得到脚本import json from pathlib import Path from typing import Optional class HexConverter: 十六进制字符串安全转换器 staticmethod def safe_hex(hex_str: str) - bytes: 安全地将十六进制字符串转换为字节 Args: hex_str: 十六进制字符串可含空格 Returns: bytes: 转换后的字节 Raises: ValueError: 如果字符串长度不是偶数或包含非法字符 # 1. 去除所有空格和换行符 hex_str_clean .join(hex_str.split()) # 2. 验证长度是否为偶数 if len(hex_str_clean) % 2 ! 0: raise ValueError( f❌ 十六进制字符串长度必须为偶数\n f 当前长度: {len(hex_str_clean)} 字符\n f 期望长度: {len(hex_str_clean) 1} 或 {len(hex_str_clean) - 1} 字符 ) # 3. 验证是否包含非法字符 valid_chars set(0123456789abcdefABCDEF) invalid_chars [c for c in hex_str_clean if c not in valid_chars] if invalid_chars: raise ValueError(f❌ 包含非法字符: {set(invalid_chars)}) # 4. 转换 return bytes.fromhex(hex_str_clean) staticmethod def bytes_to_hex(data: bytes) - str: 将字节转换为十六进制字符串大写空格分隔 return .join(f{b:02x} for b in data) class CTFDecryption: CTF 加密解密类 def __init__(self, known_plain: str, known_cipher: str, target_cipher: str): 初始化解密器 Args: known_plain: 已知明文十六进制 known_cipher: 已知密文十六进制 target_cipher: 目标密文十六进制 self.hex_converter HexConverter() self.known_plain self.hex_converter.safe_hex(known_plain) self.known_cipher self.hex_converter.safe_hex(known_cipher) self.target_cipher self.hex_converter.safe_hex(target_cipher) self.keystream None self.target_plain None def extract_keystream(self) - None: 从已知明文-密文对中恢复密钥流 原理: 密文 明文 XOR 密钥流 密钥流 明文 XOR 密文 print( * 70) print(f 步骤 1: 提取密钥流) print( * 70) # 提取密钥流长度以已知明文和密文中的较短者为准 stream_len min(len(self.known_plain), len(self.known_cipher)) self.keystream bytes( self.known_plain[i] ^ self.known_cipher[i] for i in range(stream_len) ) print(f ✅ 已知明文长度: {len(self.known_plain)} 字节) print(f ✅ 已知密文长度: {len(self.known_cipher)} 字节) print(f ✅ 密钥流长度: {len(self.keystream)} 字节) print(f ✅ 密钥流十六进制前16字节: {self.keystream[:16].hex()}) def verify_encryption(self) - bool: 验证密钥流是否正确 原理: 用提取的密钥流重新加密已知明文应该等于已知密文 print(\n * 70) print( 步骤 2: 验证密钥流) print( * 70) # 用密钥流加密已知明文 recovered_cipher bytes( self.keystream[i] ^ self.known_plain[i] for i in range(min(len(self.keystream), len(self.known_plain))) ) # 验证 match recovered_cipher self.known_cipher print(f 重新加密已知明文: {recovered_cipher.hex()}) print(f 原始已知密文: {self.known_cipher.hex()}) print(f ✅ 验证结果: {✅ 验证通过 if match else ❌ 验证失败}) return match def decrypt_target(self) - Optional[str]: 解密目标密文 原理: 明文 密文 XOR 密钥流 print(\n * 70) print(️ 步骤 3: 解密目标密文) print( * 70) # 提取足够长度的密钥流 target_len len(self.target_cipher) available_len min(len(self.keystream), target_len) if available_len target_len: print(f ⚠️ 密钥流长度不足需要 {target_len} 字节只有 {available_len} 字节) return None self.target_plain bytes( self.keystream[i] ^ self.target_cipher[i] for i in range(target_len) ) print(f ✅ 目标密文长度: {len(self.target_cipher)} 字节) print(f ✅ 解密后的明文长度: {len(self.target_plain)} 字节) print(f ✅ 明文十六进制: {self.target_plain.hex()}) return self.target_plain.hex() def decode_text(self, data: bytes) - Optional[str]: 尝试多种编码解码 支持的编码: UTF-8, Latin-1, GBK, CP1252 print(\n * 70) print( 步骤 4: 解码明文) print( * 70) encodings [utf-8, latin-1, gbk, cp1252, utf-16] for enc in encodings: try: text data.decode(enc) # 检查是否包含可读文本 has_readable any( c.isalnum() or c in .-,!? for c in text ) if has_readable: print(f\n ✅ 编码 [{enc.upper()}] 解码成功:) print(f 明文内容: {text}) print(f 十六进制: {data.hex()}) return text except (UnicodeDecodeError, UnicodeError): continue # 如果所有编码都失败返回十六进制 print(f\n ⚠️ 所有编码尝试失败返回十六进制:) return data.hex() def save_results(self, filepath: Optional[str] None) - None: 保存解密结果到 JSON 文件 if filepath is None: filepath ctf_result.json filepath Path(filepath) result { nonce: 13ce1767fff8d99b1f5a1736, known_plain_hex: self.known_plain.hex(), known_cipher_hex: self.known_cipher.hex(), target_cipher_hex: self.target_cipher.hex(), target_plain_hex: self.target_plain.hex() if self.target_plain else None, target_plain_text: self.decode_text(self.target_plain) if self.target_plain else None, keystream_hex: self.keystream.hex() if self.keystream else None, keystream_length: len(self.keystream) if self.keystream else 0 } with open(filepath, w, encodingutf-8) as f: json.dump(result, f, indent2, ensure_asciiFalse) print(f\n 结果已保存至: {filepath}) def run(self, output_file: Optional[str] None) - None: 运行完整的解密流程 print(\n * 70) print( CTF 加密解密工具 - GCM 重复计数器流攻击) print( * 70) print(\n加密规则: 密文 明文 XOR 密钥流) print( * 70 \n) # 步骤 1: 提取密钥流 self.extract_keystream() # 步骤 2: 验证 if self.verify_encryption(): print(\n 密钥流验证成功!) else: print(\n❌ 密钥流验证失败无法继续) return # 步骤 3: 解密目标 target_hex self.decrypt_target() if target_hex: # 步骤 4: 解码 plain_text self.decode_text(self.target_plain) # 保存结果 if output_file: self.save_results(output_file) print(\n * 70) print(✅ 解密完成!) print( * 70) print(f\n 解密结果:) print(f 目标密文: {self.target_cipher.hex()}) print(f 解密明文: {plain_text}) print(f 明文长度: {len(self.target_plain)} 字节) else: print(\n❌ 解密失败!) def main(): 主函数 # 题目给定的数据 known_plain 5075626c6963206e6f74653a206964656e746963616c206e6f6e636573207769746820636f756e746572206261736564206d6f646573207265 known_cipher 587335adeb135a58295d037715dcb2bc4478fefb7b36721fdbc31ab2adcc89191512208d8607c9e4079a3f6b9b7da03e6f467adcf4629a271d target_cipher 517339b9eb0b1d552b7608225bd6b3865869e2eb7f053e14d5c60a88aa849b2f120e728b881fda # 创建解密器实例并运行 decrypter CTFDecryption(known_plain, known_cipher, target_cipher) decrypter.run(output_filetwin_counter_public.json) if __name__ __main__: main()本地AI与联网AI的区别就在于脚本的简洁程度但是只要结果正确就好了。3.Affine Echo这个是用AI梭出来的在线的算力不够梭不出来。qwqimport json # 从数据文件读取参数 DATA { n: 140754953560241021660742569741950953738110717644526202218864333885825367511521836354584665863315161502607098447650769627437226295426020360882950969039438563256770554478294428121608134550971433072437856224092599283534416192321706776434289388551846453942474057606427073959031278837999661033960882483887396107533, e: 3, c1: 415856923055032895244360818386224911042736719157686545646524989916947462495409845487742437520357644192934555651626280566963818768885037224078713169170155438723898692610755789163122143601962553108618270341624293813801340359117510074488244386937267575824660727317278702246731253409390200933, c2: 415856923055032895244360818386224911042736719157686545646524989916947687591901219217504483246570436625044387472645229314942591407516307617197251092532074753689199619683039317479931073420880534625025075993314135994453125284701060458240721529839422768323314773280458158387793335166576174251, delta: 134674337241552085052305974 } n DATA[n] e DATA[e] c1 DATA[c1] c2 DATA[c2] delta DATA[delta] def extended_gcd(a, b): 扩展欧几里得算法 if a 0: return b, 0, 1 gcd, x1, y1 extended_gcd(b % a, a) x y1 - (b // a) * x1 y x1 return gcd, x, y def mod_inverse(a, mod): 计算模逆 gcd, x, _ extended_gcd(a % mod, mod) if gcd ! 1: raise ValueError(模逆不存在) return x % mod def poly_mod(poly, mod): 多项式系数取模 return [coeff % mod for coeff in poly] def poly_divmod(dividend, divisor, mod): 多项式带余除法降幂排列 dividend poly_mod(dividend, mod) divisor poly_mod(divisor, mod) # 移除前导零 while len(dividend) 0 and dividend[0] 0: dividend.pop(0) while len(divisor) 0 and divisor[0] 0: divisor.pop(0) if not divisor: raise ValueError(除数多项式不能为零) deg_dividend len(dividend) - 1 deg_divisor len(divisor) - 1 if deg_dividend deg_divisor: return [0], list(dividend) quotient [0] * (deg_dividend - deg_divisor 1) remainder list(dividend) # 找到首项系数的逆 inv_leading mod_inverse(divisor[0], mod) for i in range(deg_dividend - deg_divisor 1): coef (remainder[i] * inv_leading) % mod quotient[i] coef for j in range(deg_divisor 1): remainder[i j] (remainder[i j] - coef * divisor[j]) % mod # 移除余数的前导零 while len(remainder) 0 and remainder[0] 0: remainder.pop(0) return quotient, remainder def poly_gcd(a, b, mod): 计算多项式 GCD降幂排列 a poly_mod(a, mod) b poly_mod(b, mod) # 移除前导零 while len(a) 0 and a[0] 0: a.pop(0) while len(b) 0 and b[0] 0: b.pop(0) while len(b) 0: _, remainder poly_divmod(a, b, mod) a, b b, remainder # 归一化首项系数为1 if len(a) 0: return [0] inv_leading mod_inverse(a[0], mod) return [(coeff * inv_leading) % mod for coeff in a] # 构造多项式降幂排列 # f(x) x^3 - c1 (mod n)根是 m # g(x) (x delta)^3 - c2 (mod n) x^3 3*delta*x^2 3*delta^2*x (delta^3 - c2) f [1, 0, 0, (-c1) % n] # f(x) x^3 - c1降幂排列: [x^3项, x^2项, x项, 常数项] g [1, (3 * delta) % n, (3 * delta**2) % n, (delta**3 - c2) % n] # g(x) (xdelta)^3 - c2 print(f多项式 f(x) x^{e} - c1 (mod n)) print(f多项式 g(x) (x delta)^{e} - c2 (mod n)) # 计算多项式 GCD gcd_poly poly_gcd(f, g, n) print(f\nGCD 多项式系数降幂: {gcd_poly}) # 提取根明文 m # GCD 应该是线性的: (x - m)对应系数 [1, -m] if len(gcd_poly) 2 and gcd_poly[0] 1: m (-gcd_poly[1]) % n print(f\n恢复的明文 m {m}) # 验证解密结果 c1_check pow(m, e, n) c2_check pow(m delta, e, n) print(f\n验证:) print(fm^{e} mod n {c1_check}) print(fc1 {c1}) print(fc1 验证 {通过 if c1_check c1 else 失败}) print(f\n(m delta)^{e} mod n {c2_check}) print(fc2 {c2}) print(fc2 验证 {通过 if c2_check c2 else 失败}) # 尝试将明文转换为可读文本 try: m_bytes m.to_bytes((m.bit_length() 7) // 8, byteorderbig) print(f\n明文的十六进制: {m_bytes.hex()}) print(f明文的可读文本: {m_bytes.decode(utf-8, errorsreplace)}) except Exception as ex: print(f\n无法转换为可读文本: {ex}) else: print(fGCD 多项式次数: {len(gcd_poly) - 1}) print(GCD 多项式不是线性的可能需要其他方法)MISC1.Yunxi_月影回声把附件中自带的图片放到随波逐流中就会得到以上的信息。我们就拿到了第一个压缩包的密码。我们发现解压之后里面还有一个压缩包毫无疑问也需要密码。那么我们就尝试从上面这个音频里面获得有用的信息。当然这个照片也是有用的我们把照片分离文件分离出来发现有一段话倾听水流点击之处。暗示我们去听音频。我们点开听了发现是摩斯密码我当时做的时候思路是正确的。但是无论如何就是得不到正确密码后面才发现随波逐流自带的摩斯密码解密是有问题的就是不精确。所以我那个时候怎么都解压不了那个压缩包以后就知道摩斯密码要用一下在线的或者用audacity自己分析。属于被随波逐流坑到了用在线工具识别一下摩斯密码得到文本试着去解压压缩包果然成功了我们得到这两个文件打开第一个文件发现开头处字符和每行之间的关系突兀所以我们就研究一下开头字符我这里是用AI提取的。这里的第二个文件直接和你说没有指示放到随波逐流里面看看发现flag已经出来了Yunxi{No_LSB_3xSteg0_M00nEcho}2.流量分析2下载附件发现是一个pcap包也就是流量包。用wireshark打开我们可以看到这样的语句于是我们通过语句判断这是一个盲注盲注我们就需要去找正确的注入。首先过滤器过滤出http流。首先我们先找到盲注flag的语句也就是往下翻。得到这样的注入语句然后我们注意到里面的关键词flag。这里我们先看一下注入回显的成功和失败的区别。这里是注入第一个字符和注入第二个字符的承接出也就是说第一个字符注入成功了我们观察注入成功的这条流量发现长度与注入错误的不同我们点开发现错误的是no data正确的是Hello admin这个时候我们就可以导出数据这里就可以看到注入成功的具体数据我们把文件大的对应数据统计起来。通过ASCII码转换就能得到flag3.Forensics第一种邪修做法用lovelymem打开附件先加载镜像然后用文件扫描扫描全局。之后点开页面由于一般flag文件的存在是txt格式存在所以直接搜txt这里有好几个类似flag文件全部导出一个个试。真flag就在其中。第二种正经做法也是先用文件扫描文件扫描搜索notepad进行进程分析。我们发现两个notepadflag.txt一直在fa1g.txt之前说明flag.txt是错误的。于是我们就能锁定fa1g是真flag于是我们搜索文件名然后再次导出就可以了。4.流量分析用wireshark打开流量包发现全是udp数据流一眼看上去根本没有有效信息但是没关系我们使用过滤器看一下http包的情况。我们追踪一下http流发现里面的instruction那里的信息很像base64把它复制下来发现解码之后是乱码。但是就只有一个http包所以猜想有效信息还是在这串编码里面。然后我们想到了base64不但可以解编码还可以转文件啊由于我们不知道后缀是什么这里有两个办法一个是猜一般这种的文件都是zip所以可以猜一手zip。第二种就是用一个不需要提供文件后缀可以自动识别的工具。得到一个zip文件解压发现文件需要密码。我们就只能跑到流量包里面再次寻找密码。由于这里的攻击是udp flooding泛洪攻击原理是UDP洪水攻击的工作原理主要是利用大量的UDP数据包冲击目标服务器导致服务器资源耗尽而无法提供正常服务。因为UDP协议是一种无连接的服务攻击者可发送大量伪造源IP地址的小UDP包。当服务器接收到新的UDP数据包时会逐步进行处理并在此过程中利用服务器资源来处理请求。传输UDP数据包时每个数据包都将包括源设备的IP地址。由于目标服务器利用资源来检查并响应每个接收到的UDP数据包当收到大量UDP数据包时目标资源会很快耗尽从而导致对正常流量拒绝服务。所以我们要在“一群水军”里面找到我们需要的真正有用的流量。由于前面的压缩包是由base64转的。所以我们这里就可以用base64编码的特征来寻找也就是“”。找到一个最后以“”结尾的很像base64编码我们去解码一下得到一个正常的字符串于是我们就去试试是不是密码。最后得到一张png图片。把这张图片放去随波逐流里面。得到flagweb1.后台管理系统这里我们做了初尝试原本的页面是一个登录页面可以注册账号然后注册登录后的确进到一个查询页面但是那个页面什么也没有什么也查询不到所以就退出来。然后用dirsearch扫描一下发现居然真的有隐藏的网页打开看了一下是一个API服务框架感觉还有东西藏在里面所以又扫了一遍发现真的还有然后再扫描发现没有了。那我们就打开最后的网页我们往下翻可以看有两个很明显的很熟悉的东西include和upload。这里因为比较熟悉upload所以先试了upload结果发现就算是真的照片发上去也没有上传路径给你所以文件上传失败然后我们就看看include我们先上传一下示例path指路径这里说默认路径是1可以理解为文件默认的路径就是根目录。于是我们就让网页回到根目录的条件即../../然后寻找我们想要的flag。这里flag文件后缀不是txt不是php而是没有这个一个个试就可以试出来。然后就如下图flag就在我们寻找的文件内容中。

相关新闻