摸鱼的 2020 最后几小时 Web 手的 Misc/Crypto 修行 在结束之前一直保持密码保护 已经结束力( ToC 开始 一只方熊猫 🤝 粗心的小明 太 🆒 啦 提示 🍿 🤔 最后 Happy 2021 参考 开始 由于 <a> 设置了 pointer-events: none; 因此无法点击。复制链接进入第一题。 一只方熊猫 下载后发现图片无法打开: 使用 010Editor 打开后提示 CRC 错误: 随便找了个爆破图片宽高的脚本: 1# -*- coding: utf8 -*-2 3import os4import binascii5import struct6misc = open("panda.png","rb").read()7 8# 爆破宽9for i in range(1024):10 data = misc[12:16] + struct.pack('>i',i)+ misc[20:29] #IHDR数据11 crc32 = binascii.crc32(data) & 0xffffffff12 if crc32 == 0x4A920BA4: #IHDR块的crc32值13 print("w")14 print(i)15 print("hex:"+hex(i))16 17# 爆破高18for i in range(1024):19 data = misc[12:20] + struct.pack('>i',i)+ misc[24:29]20 crc32 = binascii.crc32(data) & 0xffffffff21 if crc32 == 0x4A920BA4:22 print("h")23 print(i)24 print("hex:"+hex(i)) 拿脚本跑一下,得到图片的真正 height: 修改原文件,得到 flag: 根据 UUID 进入下一题。 🤝 给出的链接是 http://arealexistingdomain/flag.html,第一想法就是在 /etc/hosts 里加个域名( 然后 curl 一下就出来了( 根据 UUID 进入下一题( 粗心的小明 题中给出的代码如下: 1const fs = require("fs");2const uuid = require("uuid");3const crypto = require("crypto");4 5let red_envelope_2021 = uuid.v4();6 7let key = crypto.scryptSync("xiaomingSecureKey2021", "xiaomingSuperSalt", 32);8let cipher = crypto.createCipheriv("aes-256-cfb", key, crypto.randomBytes(16));9 10let json = JSON.stringify({11 red_envelope_2021,12});13 14fs.writeFile(15 "red_envelope_encrypted.hex",16 cipher.update(json, "utf8", "hex") + cipher.final("hex"),17 function (err) {18 if (err) {19 console.error(err);20 } else {21 console.info("Red envelope stored successfully!");22 }23 }24); 给出的 hex 如下: 1d365895fbcdbd3a29b1bf00307429fd07d53ba3c0553b8789867d4aee3b8c3bbb0e5a8fd582a9696aabbdc1e373f97efac2529d588320800449553f6 加密使用的是 aes-256-cfb,以 IV 作为初始文本,加密(encrypt_block)得到一个块(block_encrypted),再将 block_encrypted 和明文异或(⊕)得到密文(prev_ciphertext)。之后,再以 prev_ciphertext 为初始文本,加密得到下一个 block_encrypted,再与下一段明文异或……以此类推,如下图所示[1]: 而文本的前半段是确定的,为 {"red_envelope_2021":,这已经超过了一个块的大小(16),因此可以通过第一个块得到 IV。简单步骤如下: 将原文转换为 hex:7b227265645f656e76656c6f70655f32 从密文中摘取第一块:d365895fbcdbd3a29b1bf00307429fd0 异或得到 AES 加密后的内容:a847fb3ad884b6cced7e9c6c7727c0e2 从 Node 的运行结果中得到 32 位密钥:03da3479601ff722a6472329347a161bf8a712ead1d04b5560414be1dead3566 解密即可 解密使用了 boppreh/aes 的 AES 实现[2],修改后的脚本如下: 1#!/usr/bin/env python32 3s_box = (4 0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76,5 0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0,6 0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15,7 0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75,8 0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84,9 0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF,10 0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8,11 0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2,12 0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73,13 0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB,14 0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79,15 0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08,16 0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A,17 0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E,18 0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF,19 0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16,20)21 22inv_s_box = (23 0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB,24 0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB,25 0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E,26 0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25,27 0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92,28 0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA, 0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84,29 0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A, 0xF7, 0xE4, 0x58, 0x05, 0xB8, 0xB3, 0x45, 0x06,30 0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02, 0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B,31 0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA, 0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73,32 0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, 0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E,33 0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B,34 0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20, 0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4,35 0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F,36 0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF,37 0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61,38 0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D,39)40 41 42def sub_bytes(s):43 for i in range(4):44 for j in range(4):45 s[i][j] = s_box[s[i][j]]46 47 48def inv_sub_bytes(s):49 for i in range(4):50 for j in range(4):51 s[i][j] = inv_s_box[s[i][j]]52 53 54def shift_rows(s):55 s[0][1], s[1][1], s[2][1], s[3][1] = s[1][1], s[2][1], s[3][1], s[0][1]56 s[0][2], s[1][2], s[2][2], s[3][2] = s[2][2], s[3][2], s[0][2], s[1][2]57 s[0][3], s[1][3], s[2][3], s[3][3] = s[3][3], s[0][3], s[1][3], s[2][3]58 59 60def inv_shift_rows(s):61 s[0][1], s[1][1], s[2][1], s[3][1] = s[3][1], s[0][1], s[1][1], s[2][1]62 s[0][2], s[1][2], s[2][2], s[3][2] = s[2][2], s[3][2], s[0][2], s[1][2]63 s[0][3], s[1][3], s[2][3], s[3][3] = s[1][3], s[2][3], s[3][3], s[0][3]64 65def add_round_key(s, k):66 for i in range(4):67 for j in range(4):68 s[i][j] ^= k[i][j]69 70 71# learned from http://cs.ucsb.edu/~koc/cs178/projects/JT/aes.c72xtime = lambda a: (((a << 1) ^ 0x1B) & 0xFF) if (a & 0x80) else (a << 1)73 74 75def mix_single_column(a):76 # see Sec 4.1.2 in The Design of Rijndael77 t = a[0] ^ a[1] ^ a[2] ^ a[3]78 u = a[0]79 a[0] ^= t ^ xtime(a[0] ^ a[1])80 a[1] ^= t ^ xtime(a[1] ^ a[2])81 a[2] ^= t ^ xtime(a[2] ^ a[3])82 a[3] ^= t ^ xtime(a[3] ^ u)83 84 85def mix_columns(s):86 for i in range(4):87 mix_single_column(s[i])88 89 90def inv_mix_columns(s):91 # see Sec 4.1.3 in The Design of Rijndael92 for i in range(4):93 u = xtime(xtime(s[i][0] ^ s[i][2]))94 v = xtime(xtime(s[i][1] ^ s[i][3]))95 s[i][0] ^= u96 s[i][1] ^= v97 s[i][2] ^= u98 s[i][3] ^= v99 100 mix_columns(s)101 102 103r_con = (104 0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40,105 0x80, 0x1B, 0x36, 0x6C, 0xD8, 0xAB, 0x4D, 0x9A,106 0x2F, 0x5E, 0xBC, 0x63, 0xC6, 0x97, 0x35, 0x6A,107 0xD4, 0xB3, 0x7D, 0xFA, 0xEF, 0xC5, 0x91, 0x39,108)109 110 111def bytes2matrix(text):112 """ Converts a 16-byte array into a 4x4 matrix. """113 return [list(text[i:i+4]) for i in range(0, len(text), 4)]114 115def matrix2bytes(matrix):116 """ Converts a 4x4 matrix into a 16-byte array. """117 return bytes(sum(matrix, []))118 119def xor_bytes(a, b):120 """ Returns a new byte array with the elements xor'ed. """121 return bytes(i^j for i, j in zip(a, b))122 123def inc_bytes(a):124 """ Returns a new byte array with the value increment by 1 """125 out = list(a)126 for i in reversed(range(len(out))):127 if out[i] == 0xFF:128 out[i] = 0129 else:130 out[i] += 1131 break132 return bytes(out)133 134def pad(plaintext):135 """136 Pads the given plaintext with PKCS#7 padding to a multiple of 16 bytes.137 Note that if the plaintext size is a multiple of 16,138 a whole block will be added.139 """140 padding_len = 16 - (len(plaintext) % 16)141 padding = bytes([padding_len] * padding_len)142 return plaintext + padding143 144def unpad(plaintext):145 """146 Removes a PKCS#7 padding, returning the unpadded text and ensuring the147 padding was correct.148 """149 padding_len = plaintext[-1]150 assert padding_len > 0151 message, padding = plaintext[:-padding_len], plaintext[-padding_len:]152 assert all(p == padding_len for p in padding)153 return message154 155def split_blocks(message, block_size=16, require_padding=True):156 assert len(message) % block_size == 0 or not require_padding157 return [message[i:i+16] for i in range(0, len(message), block_size)]158 159 160class AES:161 rounds_by_key_size = {16: 10, 24: 12, 32: 14}162 def __init__(self, master_key):163 """164 Initializes the object with a given key.165 """166 assert len(master_key) in AES.rounds_by_key_size167 self.n_rounds = AES.rounds_by_key_size[len(master_key)]168 self._key_matrices = self._expand_key(master_key)169 170 def _expand_key(self, master_key):171 """172 Expands and returns a list of key matrices for the given master_key.173 """174 # Initialize round keys with raw key material.175 key_columns = bytes2matrix(master_key)176 iteration_size = len(master_key) // 4177 178 # Each iteration has exactly as many columns as the key material.179 columns_per_iteration = len(key_columns)180 i = 1181 while len(key_columns) < (self.n_rounds + 1) * 4:182 # Copy previous word.183 word = list(key_columns[-1])184 185 # Perform schedule_core once every "row".186 if len(key_columns) % iteration_size == 0:187 # Circular shift.188 word.append(word.pop(0))189 # Map to S-BOX.190 word = [s_box[b] for b in word]191 # XOR with first byte of R-CON, since the others bytes of R-CON are 0.192 word[0] ^= r_con[i]193 i += 1194 elif len(master_key) == 32 and len(key_columns) % iteration_size == 4:195 # Run word through S-box in the fourth iteration when using a196 # 256-bit key.197 word = [s_box[b] for b in word]198 199 # XOR with equivalent word from previous iteration.200 word = xor_bytes(word, key_columns[-iteration_size])201 key_columns.append(word)202 203 # Group key words in 4x4 byte matrices.204 return [key_columns[4*i : 4*(i+1)] for i in range(len(key_columns) // 4)]205 206 def encrypt_block(self, plaintext):207 """208 Encrypts a single block of 16 byte long plaintext.209 """210 assert len(plaintext) == 16211 212 plain_state = bytes2matrix(plaintext)213 214 add_round_key(plain_state, self._key_matrices[0])215 216 for i in range(1, self.n_rounds):217 sub_bytes(plain_state)218 shift_rows(plain_state)219 mix_columns(plain_state)220 add_round_key(plain_state, self._key_matrices[i])221 222 sub_bytes(plain_state)223 shift_rows(plain_state)224 add_round_key(plain_state, self._key_matrices[-1])225 226 return matrix2bytes(plain_state)227 228 def decrypt_block(self, ciphertext):229 """230 Decrypts a single block of 16 byte long ciphertext.231 """232 assert len(ciphertext) == 16233 234 cipher_state = bytes2matrix(ciphertext)235 236 add_round_key(cipher_state, self._key_matrices[-1])237 inv_shift_rows(cipher_state)238 inv_sub_bytes(cipher_state)239 240 for i in range(self.n_rounds - 1, 0, -1):241 add_round_key(cipher_state, self._key_matrices[i])242 inv_mix_columns(cipher_state)243 inv_shift_rows(cipher_state)244 inv_sub_bytes(cipher_state)245 246 add_round_key(cipher_state, self._key_matrices[0])247 248 return matrix2bytes(cipher_state)249 250if __name__ == '__main__':251 from base64 import b64encode252 from binascii import unhexlify253 ret=AES(unhexlify('03da3479601ff722a6472329347a161bf8a712ead1d04b5560414be1dead3566')).decrypt_block(unhexlify('a847fb3ad884b6cced7e9c6c7727c0e2'))254 print(b64encode(ret)) 得到 IV 的 base64:Jle9TPezMnv770Y5b+Wc1g==,最后使用 CyberChef 解密: 得到下一题的地址。 太 🆒 啦 找了好久,结果 Esolang Wiki 还真有全名就叫这个的语言[3]( 题面如下: 1🆕6️⃣🍿🍔🌭🥪🌮🍆🥑💬🍔💬🌭💬🥪💬🌮💬🍆💬🥑🛑🆕🔟🤔🖇🙃🍖🍟🥔🌽🥩🥕🌶️🌯🥥❓🆓⚖️🆓✖️🆓➗🙃🔢1️⃣8️⃣4️⃣7️⃣🔢🛑4️⃣🛑🔢2️⃣4️⃣8️⃣🔢🛑🆓🍿🌶️🥥🌽🥕🌽🥔🍿🌽🍖🌽🥩🌯🍟🛑🆓💬🔤🚫📥️🔛🔤🛑🛑💬🔤💻🔑🔑🔑🔑🔑🔑🔤🤔🎛️🔤9️⃣❤️9️⃣4️⃣🔤🔤1️⃣9️⃣7️⃣4️⃣🔤🔤4️⃣💜💛7️⃣🔤🔤➖🔤🔤🧡7️⃣9️⃣9️⃣🔤🔤8️⃣❤️4️⃣💛🔤🔤💚6️⃣0️⃣💛🔤🔤2️⃣5️⃣💙1️⃣🔤🔤2️⃣3️⃣7️⃣2️⃣🔤 提示 做猜到一半给出了提示: 1❤️ A 🧡 B 💛 C 💚 D 💙 E 💜 F 于是下面分析的内容就将这些红心全都替换成对应的文本了。 🍿 按 square-cool 仓库[4] 的描述,🆕 是定义函数,后面跟随一个数字 n,表示参数的数量,然后是函数名 emoji,最后的 n 个 emoji 表示参数名,函数以 🛑 结尾。于是开头的一一部分可以翻译成下面的形式: 1# 🍿: Print six arguments2🆕6️🍿3 4🍔🌭🥪🌮🍆🥑5 6💬🍔7💬🌭8💬🥪9💬🌮10💬🍆11💬🥑12 13🛑 其中 💬 是内置的函数,表示输出。这个函数的功能也就是连续输出对应的 6 个参数。 🤔 同样是一个函数,翻译如下: 1# 🤔:2🆕🔟🤔3🖇 # modifier4🙃 # input5🍖 # p16🍟 # p27🥔 # p38🌽 # p49🥩 # p510🥕 # p611🌶️ # p712🌯 # p813🥥 # p914❓ # Returns the second argument if the first is true, otherwise returns the third argument.15# input / 1847 * 4 == 2️4️8️16🆓17 ⚖️18 🆓19 ✖️20 🆓21 ➗🙃🔢1️8️4️7️🔢22 🛑23 4️24 🛑25 🔢2️4️8️🔢26🛑27 28🆓29 # 🍿(p7, p9, -, p6, -, p3)30 🍿🌶️🥥🌽🥕🌽🥔31 # 🍿(-, p1, -, p5, p8, p2)32 🍿🌽🍖🌽🥩🌯🍟33🛑34 35🆓36 # print('🚫📥️🔛')37 💬🔤🚫📥️🔛🔤38🛑39 40🛑 其中 🆓 的功能类似作用域开始,并以 🛑 结束。 最后 1# main2# print('💻🔑🔑🔑🔑🔑🔑')3💬🔤💻🔑🔑🔑🔑🔑🔑🔤4 5# 🤔(stdin, '9️A9️4️', '1️9️7️4️', '4️FC7️', '-', 'B7️9️9️', '8️A4️C', 'D6️0️C', '2️5️E1️', '2️3️7️2️')6# d60c2372-8a4c-4fc7-9a94-b79925e119747🤔🎛️8🔤9️A9️4️🔤9🔤1️9️7️4️🔤10🔤4️FC7️🔤11🔤➖🔤12🔤B7️9️9️🔤13🔤8️A4️C🔤14🔤D6️0️C🔤15🔤2️5️E1️🔤16🔤2️3️7️2️🔤 最后就是调用了,其中 🎛️ 表示从 stdin 读入一个整数,但在实际分析中并没有起到作用。 最后得到 UUID:d60c2372-8a4c-4fc7-9a94-b79925e11974,进入终章。 Happy 2021 游戏结束,新年快乐!あけましておめでとうございます!(笑) 参考 https://ctf-wiki.github.io/ctf-wiki/crypto/blockcipher/mode/cfb-zh/ https://github.com/boppreh/aes https://esolangs.org/wiki/%F0%9F%86%92 https://gitlab.com/fogity/squared-cool