Misc
星辉骑士
把后缀.docx改为.zip后解压缩
在word/media下找到flag.zip
伪加密修复后解压,垃圾邮件加密spammimic - decoded解密999.txt得到flag{0231265452-you-kn*w-spanmimic}
H&NCTF{0231265452-you-kn*w-spanmimic}谁动了黑线?
附件sheidongleheixian.csv的部分数据如下:
from_address,to_address,amount_sol,timestamp,tx_hash1P8KDHGQC0NM5MD9WEBAT,1IPDXFBEFC6863BQY66KQ,0.2,1680000662,FPAYmszHFmWAYano3HfRWc11HPSDI3AL3BBAPYOBJR1,170J437LF23601EIOTQXN,0.2,1680000616,FPAYmszHFVG7mJr2sqKitb1JTKE95594O0YYLA6DGFU,1WRB9XEAL5I4ZP2KCRD4G,0.2,1680003430,FPAYmt3ZCaGnD1EQFquhMF11W0LGK7HE2JSA1RHV585,1JV5TMA23YNYCFDQSJZ4Q,0.2,1680001147,FPAYmt1MVg8rvnH8Fx7XAJ不难发现这是区块链交易数据,其中tx_hash还经过了base58编码,下面先对tx_hash列进行base58解码并将结果添加到一个新列decoded_tx_hash中,然后保存到文件sheidongleheixian_decoded.csv:
import csvimport base58import os input_file_path = "sheidongleheixian.csv"file_name_without_ext, file_extension = os.path.splitext(input_file_path)output_file_path = f"{file_name_without_ext}_decoded{file_extension}" new_column_name = 'decoded_tx_hash' processed_rows = []header = [] with open(input_file_path, 'r', encoding='utf-8') as infile: reader = csv.DictReader(infile) header = reader.fieldnames for row in reader: tx_hash_val = row.get('tx_hash', '') decoded_value = '' decoded_bytes = base58.b58decode(tx_hash_val) decoded_value = decoded_bytes.decode('utf-8') row[new_column_name] = decoded_value processed_rows.append(row) new_fieldnames = header + [new_column_name] with open(output_file_path, 'w', encoding='utf-8', newline='') as outfile: writer = csv.DictWriter(outfile, fieldnames=new_fieldnames) writer.writeheader() writer.writerows(processed_rows)得到的部分数据如下:
from_address,to_address,amount_sol,timestamp,tx_hash,decoded_tx_hash1P8KDHGQC0NM5MD9WEBAT,1IPDXFBEFC6863BQY66KQ,0.2,1680000662,FPAYmszHFmWAYano3HfRWc,tx000661YGG3QDKE11HPSDI3AL3BBAPYOBJR1,170J437LF23601EIOTQXN,0.2,1680000616,FPAYmszHFVG7mJr2sqKitb,tx000615QZN6T7KD1JTKE95594O0YYLA6DGFU,1WRB9XEAL5I4ZP2KCRD4G,0.2,1680003430,FPAYmt3ZCaGnD1EQFquhMF,tx003429JCPKOX0V下面根据转出地址 (from_address) 和转入地址 (to_address) 来重建资金的流动路径:
import pandas as pdfrom collections import defaultdict def find_transaction_chains(csv_path): """ 从交易数据中还原出交易链。 """ try: # 1. 加载数据 df = pd.read_csv(csv_path) print(f"成功加载 {len(df)} 条交易记录。") except FileNotFoundError: print(f"错误: 文件未找到,请检查路径 '{csv_path}' 是否正确。") return [] # 2. 按时间戳排序,这是构建链条的基础 df = df.sort_values(by='timestamp').reset_index(drop=True) # 3. 建立一个从 'from_address' 到其发起交易的快速查找字典 transactions_by_sender = defaultdict(list) all_transactions = df.to_dict('records') for tx in all_transactions: transactions_by_sender[tx['from_address']].append(tx) # 4. 遍历和构建链条 chains = [] visited_hashes = set() for tx in all_transactions: # 如果当前交易已经被包含在某个链中,则跳过 if tx['tx_hash'] in visited_hashes: continue # 发现了一个新链条的起点 current_chain = [] current_tx = tx # 循环追踪链条,直到中断 while current_tx is not None: # 将当前交易加入链条,并标记为已访问 current_chain.append(current_tx) visited_hashes.add(current_tx['tx_hash']) # 寻找下一个环节:当前交易的接收方是否是下一笔交易的发送方? next_sender = current_tx['to_address'] next_tx_candidate = None # 检查这个接收方是否发起过任何交易 if next_sender in transactions_by_sender: # 遍历该发送方的所有交易,找到第一个尚未被访问的交易 for potential_next_tx in transactions_by_sender[next_sender]: if potential_next_tx['tx_hash'] not in visited_hashes: next_tx_candidate = potential_next_tx break # 找到了,跳出内层循环 # 更新current_tx,如果没找到下一个环节,它会变为None,循环终止 current_tx = next_tx_candidate # 将构建完成的链条存入结果列表 chains.append(current_chain) return chains def print_chains_summary(chains): """打印找到的交易链的摘要信息""" print("\n" + "="*50) print(f"分析完成!共找到 {len(chains)} 条独立的交易链。") print("="*50 + "\n") # 按链条长度从长到短排序 chains.sort(key=len, reverse=True) for i, chain in enumerate(chains): print(f"--- 链条 {i+1} (长度: {len(chain)}) ---") # 打印链条的起点和终点 start_addr = chain[0]['from_address'] end_addr = chain[-1]['to_address'] print(f" 起点地址: {start_addr}") print(f" 终点地址: {end_addr}") path_str = chain[0]['from_address'] for tx in chain: path_str += f" --({tx['decoded_tx_hash']})--> {tx['to_address']}" print(f" 路径: {path_str}") if __name__ == "__main__": FILE_PATH = "sheidongleheixian_decoded.csv" found_chains = find_transaction_chains(FILE_PATH) if found_chains: print_chains_summary(found_chains)将运行结果保存到output.txt中,得到部分数据如下:
成功加载 7030 条交易记录。==================================================分析完成!共找到 3125 条独立的交易链。==================================================--- 链条 1 (长度: 6) --- 起点地址: 14B43RN0FRLN2MVPS9D1C 终点地址: 14QIWXQXAET9C1H8OR09U 路径: 14B43RN0FRLN2MVPS9D1C --(tx0000017XIY8MI0)--> 1V6BOKA7ZFVIHYE0NTU2K --(tx000006R6WLJTGV)--> 1NEC9LG6OX02GUPGS9EIS --(tx0000314J815ZDZ)--> 16AXD9WB9Q5DDBTIIQLZK --(tx0001566NBB4FG1)--> 1AYRO89L2T28WOTEU9O1M --(tx000781Z43PIY3K)--> 1Q1PQ0EL8HF0PY55KYKDN --(tx003906Z6HA9JZA)--> 14QIWXQXAET9C1H8OR09U--- 链条 2 (长度: 6) --- 起点地址: 14B43RN0FRLN2MVPS9D1C 终点地址: 14QIWXQXAET9C1H8OR09U 路径: 14B43RN0FRLN2MVPS9D1C --(tx000002litt7H6R)--> 1QLHG0KSCA7WIMUZOPPO7 --(tx000011F6JVVRGF)--> 146WVE6IQ95L936UO8Y8W --(tx00005602RR07WL)--> 1RDRUCP47T8X597CIT2A3 --(tx000281HKCM3YA1)--> 1LWW3QK5EXYZ39J2NS1C1 --(tx001406Z25GXL9L)--> 1T4T9LOYM46CQV5M8U9T5 --(tx004531CCLW6SRL)--> 14QIWXQXAET9C1H8OR09U观察到第2条链有一个decoded_tx_hash不对劲,tx000002litt7H6R里面包含小写字母,其他的decoded_tx_hash都是大写字母(当然tx是除外的)
并且里面的litt看起来像是little的前半部分,在sheidongleheixian_decoded.csv中搜索le发现了tx000014le_dWAHC
那么接下来只要找出带小写字母的然后把它们拼起来就可以了
import pandas as pd file_path = "sheidongleheixian_decoded.csv"df = pd.read_csv(file_path)pattern = r'^tx.*[a-z]'filtered_df = df[df['decoded_tx_hash'].str.contains(pattern, regex=True)]print(filtered_df['decoded_tx_hash'])得到的输出如下:
286 tx001890mr!!ZIU4848 tx000002litt7H6R2298 tx000075og_i43J22795 tx000377s_AoOCHG3801 tx000014le_dWAHCName: decoded_tx_hash, dtype: objectH&NCTF{little_dog_is_Aomr!!}Forensics
ez_game
对附件的磁盘镜像challenge.vhd进行分析,找到关键信息readme.txt,key.jpg,hhhh,hhhh.zip
其中readme.txt的内容如下:
这次是个简单的取证小游戏1.我藏了一个电脑,它的密码是很简单的弱密码,但是你找的到吗2.图片没有隐写,但是它凭什么可以成为key,好难猜啊3.如果你找的了flag,注意flag的内容全部大写首先第1条的“藏了一个电脑”指的是容器hhhh里的CentOS 7镜像(这个要靠第2条才能解出来),吐槽一下这里很简单的弱密码指的是系统的密码,不过都拿到镜像文件了这个提示就没啥用
第2条想表达的是,key.jpg就是VeraCrypt容器hhhh的密码,解开并挂载之后就能对里面的系统镜像进行分析了
第3条就是字面意思

在系统的/root/test目录下发现了文件hhhh.txt,内容如下:
·1234567890-=这里存在零宽字符隐写

得到提示shift,按住shift键把键盘上面那一排从左到右按一遍就行,得到:
~!@#$%^&*()_+这个就是加密压缩包hhhh.zip的解压密码,这个压缩包被删除了,就是$RQCSJAK.zip
解压得到flag.drawio,用官网提供的在线工具draw.io打开查看

H&NCTF{YOU_R_SSSO_COOL}OSINT
Chasing Freedom 1
查询图片exif得知是5月3日
图片上的渔船有名字闽平渔65599
船位查询查询发现该渔船位置

H&NCTF{0503-丁鼻垄}Chasing Freedom 2
查询图片exif得知是5月4日
识图搜索图中的黑白灯塔

H&NCTF{0504-东庠岛灯塔}Chasing Freedom 3
查询图片exif得知是5月34日
图片下方的水桶写着岚庠渡
搜索发现这篇文章等一刻轮渡,过一天属于岛屿的生活 - 东庠岛生活画册_码头_海面_渔船
得到两个信息:位于流水码头,船的名字是岚庠渡x号
由于不知道具体是几号,从1号开始试,最终试出来是3号
H&NCTF{0504-流水码头-岚庠渡3号}Crypto
哈基coke
import numpy as npimport cv2import osdef arnold_decode(arnold_image, shuffle_times, a, b): """ Arnold shuffle for rgb image Args: arnold_image: input arnold encoded rgb image shuffle_times: how many times to shuffle a: element a of the arnold transform matrix b: element b of the arnold transform matrix Returns: Arnold decode image """ # 1: 创建新图像 decode_image = np.zeros(shape=arnold_image.shape) # 2: 计算N h, w = arnold_image.shape[0], arnold_image.shape[1] N = h # 或N=w # 3: 遍历像素坐标变换 for time in range(shuffle_times): for ori_x in range(h): for ori_y in range(w): # 按照公式坐标变换 new_x = ((a * b + 1) * ori_x + (-b) * ori_y) % N new_y = ((-a) * ori_x + ori_y) % N decode_image[new_x, new_y, :] = arnold_image[ori_x, ori_y, :] # 更新坐标 arnold_image = np.copy(decode_image) return decode_image# 检查文件路径是否存在image_path = 'en_flag.png'if not os.path.exists(image_path): print(f"文件 {image_path} 不存在") exit()encoded_image = cv2.imread(image_path)if encoded_image is None: print(f"无法读取文件 {image_path}") exit()decoded_image = arnold_decode(encoded_image,6,9,1)output_path = "coke.png"cv2.imwrite(output_path, decoded_image)print("处理完成")猫变换

H&NCTF{haji_coke_you_win}Reverse
签到re
Gemini一把梭
代码分析:
-
main函数:- 程序使用硬编码的密钥
"MySecretKey123!"。 - 调用
sub_11B9函数,基于该密钥生成一个32位的加密密钥v5。 - 读取用户输入
s。 - 调用
sub_1452函数,使用v5对用户输入s进行加密。 - 将加密后的结果与
byte_4080的内容逐字节比较。如果完全相同,则输出 “right”。
- 程序使用硬编码的密钥
-
sub_11B9函数:- 计算
"MySecretKey123!"的 SHA256 哈希值。 - 取哈希值的前4个字节,记为
d0, d1, d2, d3。 - 通过位运算生成最终的4字节加密密钥
k = [k0, k1, k2, k3]:k0 = d0 | 1k1 = d1 & 0xFEk2 = d2 & 0xFEk3 = d3 | 1
- 这个过程确保了
k0和k3是奇数,而k1和k2是偶数。
- 计算
-
sub_13AC和sub_1452函数:sub_1452是主加密函数。它将输入数据填充到4字节的倍数,然后以4字节为一块进行处理。- 每一块4字节的明文被分成两个2字节的块。
sub_13AC是核心的加密算法。它对每个2字节的块进行操作。其运算本质上是一个模256的2x2矩阵乘法:其中 (p0, p1)是2字节的明文,(c0, c1)是加密后的2字节密文,k是从sub_11B9得到的4字节密钥。
解题思路:
要得到 flag,我们需要执行以下步骤:
- 重新生成加密密钥: 完全按照
sub_11B9的逻辑,从"MySecretKey123!"生成4字节的密钥矩阵K。 - 求逆矩阵: 为了解密,我们需要找到加密矩阵
K在模256下的逆矩阵K_inv。解密操作就是P = K_inv * C mod 256。- 求矩阵的行列式:
det(K) = (k0*k3 - k1*k2) mod 256。 - 求行列式的模逆元:
det(K)^-1 mod 256。 - 计算逆矩阵:
K_inv = det(K)^-1 * [[k3, -k1], [-k2, k0]] mod 256。
- 求矩阵的行列式:
- 解密数据:
- 从
byte_4080中提取密文数据(从第5个字节开始)。 - 将密文数据以4字节为单位进行分割。
- 将每个4字节的块再分成两个2字节的块。
- 使用求得的逆矩阵
K_inv对每个2字节的密文块进行解密。 - 将解密后的数据拼接起来。
- 从
- 提取Flag:
byte_4080的前4个字节(00 00 00 25)指明了原始明文的长度是37字节。从解密后的数据中截取前37个字节,即为最终的 flag。
import hashlib def generate_key(secret: str) -> list[int]: """ 根据 sub_11B9 的逻辑从密钥字符串生成加密密钥。 """ sha256 = hashlib.sha256(secret.encode()).digest() d = sha256[:4] # k = [k0, k1, k2, k3] key = [0] * 4 key[0] = d[0] | 1 key[1] = d[1] & 0xFE key[2] = d[2] & 0xFE key[3] = d[3] | 1 return key def get_inverse_matrix(key: list[int]) -> list[int]: """ 计算加密矩阵在模256下的逆矩阵。 """ k0, k1, k2, k3 = key # 计算行列式 det(K) = (k0*k3 - k1*k2) mod 256 det = (k0 * k3 - k1 * k2) % 256 # 计算行列式的模256乘法逆元 # 在 Python 3.8+ 中,可以直接使用 pow(det, -1, 256) try: det_inv = pow(det, -1, 256) except ValueError: raise ValueError(f"Determinant {det} has no modular inverse for modulus 256") # 计算逆矩阵 K_inv = det_inv * [[k3, -k1], [-k2, k0]] mod 256 inv_matrix = [0] * 4 inv_matrix[0] = (det_inv * k3) % 256 inv_matrix[1] = (det_inv * -k1) % 256 inv_matrix[2] = (det_inv * -k2) % 256 inv_matrix[3] = (det_inv * k0) % 256 return inv_matrix def decrypt_block(cipher_block: bytes, inv_matrix: list[int]) -> bytes: """ 使用逆矩阵解密一个2字节的数据块。 P = K_inv * C mod 256 """ c0, c1 = cipher_block m0, m1, m2, m3 = inv_matrix p0 = (m0 * c0 + m1 * c1) % 256 p1 = (m2 * c0 + m3 * c1) % 256 return bytes([p0, p1]) def solve(): """ 主函数,执行完整的解密流程并输出Flag。 """ # .data:0000000000004080 byte_4080 = bytes([ 0x00, 0x00, 0x00, 0x25, 0x0C, 0xE2, 0x70, 0x89, 0x98, 0xB2, 0xBB, 0xE4, 0x94, 0xA0, 0x95, 0xAC, 0x38, 0x92, 0x22, 0xF8, 0x0E, 0x7B, 0x76, 0x1A, 0x66, 0xC8, 0x03, 0x05, 0x2E, 0x7D, 0xA1, 0x04, 0x3D, 0xC0, 0x62, 0xFE, 0x66, 0x67, 0x02, 0x87, 0x81, 0xF4, 0x00, 0x00 ]) # 1. 生成密钥 secret = "MySecretKey123!" key = generate_key(secret) print(f"🔑 生成的加密密钥 (k0,k1,k2,k3): {key}") # 2. 计算逆矩阵 inv_matrix = get_inverse_matrix(key) print(f"🔢 逆矩阵 (mod 256): {inv_matrix}") # 3. 提取密文和明文长度 original_len = int.from_bytes(byte_4080[:4], 'big') ciphertext = byte_4080[4:] decrypted_padded = bytearray() # 4. 循环解密 # 将密文按4字节分块 for i in range(0, len(ciphertext), 4): chunk = ciphertext[i : i+4] if len(chunk) < 4: # 处理可能的填充不完整情况(虽然在此题中不会发生) continue # 将4字节块分成两个2字节块并解密 decrypted_padded.extend(decrypt_block(chunk[0:2], inv_matrix)) decrypted_padded.extend(decrypt_block(chunk[2:4], inv_matrix)) # 5. 截取原始长度,得到Flag flag = decrypted_padded[:original_len].decode('utf-8') print("\n" + "="*40) print(f"🚩 解密得到的 Flag: {flag}") print("="*40) if __name__ == "__main__": solve() 运行结果
🔑 生成的加密密钥 (k0,k1,k2,k3): [81, 22, 52, 251]🔢 逆矩阵 (mod 256): [217, 238, 4, 171]========================================🚩 解密得到的 Flag: H&NCTF{840584fb08a26f01c471054628e451========================================H&NCTF{840584fb08a26f01c471054628e451}