Re:
1.Realme
根据提示往前找,可以知道这里会检查调试状态然后异或
直接改跳转绕过一下
改完如果直接进行调试会报错
但是根据前面的xor使用idapython脚本异或回去调试就不会死了,我也不知道啥原因,求解答(别保存 保存了前面的改跳转过调试就白弄了)
加密算法是魔改的rc4
断点打在这里看加密过后的数据
0x43,0x35,0x73,0x92,0x1B,0x89,0x22,0x7D,0x8A,0xE2,0xC7,0x31,0x38,0xBA,0xF8,0xD4,0x2C,0x5A,0xDD,0xA9,0x46,0x96,0x55,0x17,0x73,0xE,0x7D,0xB6,0xD6,0x70,0x43,0x14,0xDA,0x82,0x94
这是我拿35个A跑出来的结果
0x44,0x36,0x74,0x93,0x1C,0x8A,0x23,0x7E,0x8B,0xE3,0xC8,0x32,0x39,0xBB,0xF9,0xD5,0x2D,0x5B,0xDE,0xAA,0x47,0x97,0x56,0x18,0x74,0xF,0x7E,0xB7,0xD7,0x71,0x44,0x15,0xDB,0x83,0x95
这是35个B跑出来的
发现是线性加密
A = [
0x43,0x35,0x73,0x92,0x1B,0x89,0x22,0x7D,0x8A,0xE2,0xC7,0x31,0x38,0xBA,0xF8,0xD4,
0x2C,0x5A,0xDD,0xA9,0x46,0x96,0x55,0x17,0x73,0x0E,0x7D,0xB6,0xD6,0x70,0x43,0x14,0xDA,0x82,0x94
]
PY = [
0x50,0x59,0xA2,0x94,0x2E,0x8E,0x5C,0x95,0x79,0x16,0xE5,0x36,0x60,0xC7,0xE8,0x06,
0x33,0x78,0xF0,0xD0,0x36,0xC8,0x73,0x1B,0x65,0x40,0xB5,0xD4,0xE8,0x9C,0x65,0xF4,0xBA,0x62,0xD0
]
flag=''
for i in range(35):
enc=PY[i] - A[i]
flag+=chr((enc+65)& 0xFF)
print(flag)
2.Crackme
将用户名的md5作为aes密钥,用户名+showmaker11作为aes的密文,解密得出的就是用户的注册码
md5和AES都是从libcrypto.dll中加载的,其中md5可以直接调用dll里的,但是aes存在混淆和魔改
aes的魔改在最后的MixColumns异或了0x55
解密脚本如下
// ① 选中用户名——页面里的每个 label 就是用户名
const names = [...document.querySelectorAll('#formFields label')]
.map(e => e.textContent.trim()) // 去掉空白
.filter(Boolean); // 过滤空字符串
// ② 复制到剪贴板(Edge / Chrome 都自带 copy)
copy(names.join('\n'));
console.log(`✅ 共抓到 ${names.length} 个用户名,已放进剪贴板`);
import ctypes
import os
import sys
from tqdm import tqdm # 引入tqdm来显示进度条
s_box = (
0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9, 0x7d,
0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc,
0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2,
0xeb, 0x27, 0xb2, 0x75, 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, 0xd0, 0xef, 0xaa, 0xfb,
0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5,
0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d,
0x64, 0x5d, 0x19, 0x73, 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, 0xe7, 0xc8, 0x37, 0x6d,
0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6,
0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9,
0x86, 0xc1, 0x1d, 0x9e, 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16)
inv_s_box = (
0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, 0x7c, 0xe3, 0x39, 0x82,
0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d,
0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49,
0x6d, 0x8b, 0xd1, 0x25, 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92,
0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, 0x90, 0xd8, 0xab, 0x00,
0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02,
0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce,
0xf0, 0xb4, 0xe6, 0x73, 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e,
0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, 0xfc, 0x56, 0x3e, 0x4b,
0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31,
0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f,
0x93, 0xc9, 0x9c, 0xef, 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61,
0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d)
r_con = (
0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63,
0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39)
def text2matrix(text): return [list(text[i:i + 4]) for i in range(0, len(text), 4)]
def matrix2text(matrix): return bytes(sum(matrix, []))
def sub_bytes(s, sbox=s_box):
for i in range(4):
for j in range(4): s[i][j] = sbox[s[i][j]]
def inv_shift_rows(s):
s[0][1], s[1][1], s[2][1], s[3][1] = s[3][1], s[0][1], s[1][1], s[2][1]
s[0][2], s[1][2], s[2][2], s[3][2] = s[2][2], s[3][2], s[0][2], s[1][2]
s[0][3], s[1][3], s[2][3], s[3][3] = s[1][3], s[2][3], s[3][3], s[0][3]
def add_round_key(s, k):
for i in range(4):
for j in range(4): s[i][j] ^= k[i][j]
def xtime(a): return (((a << 1) ^ 0x1b) & 0xff) if (a & 0x80) else (a << 1)
def mix_single_column(a):
t = a[0] ^ a[1] ^ a[2] ^ a[3]
u = a[0]
a[0] ^= t ^ xtime(a[0] ^ a[1])
a[1] ^= t ^ xtime(a[1] ^ a[2])
a[2] ^= t ^ xtime(a[2] ^ a[3])
a[3] ^= t ^ xtime(a[3] ^ u)
def mix_columns(s):
for i in range(4): mix_single_column(s[i])
def inv_mix_columns(s):
for i in range(4):
u = xtime(xtime(s[i][0] ^ s[i][2]))
v = xtime(xtime(s[i][1] ^ s[i][3]))
s[i][0] ^= u;
s[i][1] ^= v;
s[i][2] ^= u;
s[i][3] ^= v
mix_columns(s)
for i in range(4):
for j in range(4): s[i][j] ^= 0x55
def expand_key(master_key, n_rounds=11):
key_columns = text2matrix(master_key)
i = 1
while len(key_columns) < n_rounds * 4:
word = list(key_columns[-1])
if len(key_columns) % 4 == 0:
word.append(word.pop(0))
word = [s_box[b] for b in word]
word[0] ^= r_con[i]
i += 1
for j in range(4): word[j] ^= key_columns[-4][j]
key_columns.append(word)
return [key_columns[i:i + 4] for i in range(0, len(key_columns), 4)]
def aes_decrypt(ciphertext, key):
round_keys = expand_key(key)
state = text2matrix(ciphertext)
add_round_key(state, round_keys[-1])
for i in range(len(round_keys) - 2, 0, -1):
inv_shift_rows(state)
sub_bytes(state, sbox=inv_s_box)
add_round_key(state, round_keys[i])
inv_mix_columns(state)
inv_shift_rows(state)
sub_bytes(state, sbox=inv_s_box)
add_round_key(state, round_keys[0])
return matrix2text(state)
# ==============================================================================
# 主逻辑:批量处理 user.txt 并生成 JS 代码
# ==============================================================================
class KeyGenerator:
def __init__(self, dll_path):
if sys.platform != "win32":
print("错误:此脚本需要使用 Windows DLL,请在 Windows 环境下运行。")
sys.exit(1)
try:
libcrypto = ctypes.CDLL(dll_path)
self.md5_func = libcrypto.sign
self.md5_func.argtypes = [ctypes.c_char_p, ctypes.c_int, ctypes.c_char_p, ctypes.c_int]
self.md5_func.restype = ctypes.c_void_p
except (OSError, AttributeError) as e:
print(f"错误:加载或初始化 DLL '{dll_path}' 失败: {e}")
print("请确保 'libcrypto.dll' 文件存在且有效。")
sys.exit(1)
def get_password(self, username: str) -> str:
# 生成密钥
username_bytes = username.encode('utf-8')
key_buffer = ctypes.create_string_buffer(16)
self.md5_func(username_bytes, len(username_bytes), key_buffer, 16)
key = key_buffer.raw
# 生成密文
str_to_hash = username + "Showmaker11"
str_to_hash_bytes = str_to_hash.encode('utf-8')
cipher_buffer = ctypes.create_string_buffer(16)
self.md5_func(str_to_hash_bytes, len(str_to_hash_bytes), cipher_buffer, 16)
ciphertext = cipher_buffer.raw
# 解密
password_bytes = aes_decrypt(ciphertext, key)
return password_bytes.hex()
def main():
dll_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'libcrypto.dll')
users_file = 'user.txt'
# 1. 读取所有用户名
try:
with open(users_file, 'r', encoding='utf-8') as f:
# 读取并去除每行的空白字符,过滤掉空行
usernames = [line.strip() for line in f if line.strip()]
if not usernames:
print(f"错误: '{users_file}' 为空或只包含空行。")
return
except FileNotFoundError:
print(f"错误:找不到文件 '{users_file}'。请创建该文件并将用户名放入其中。")
return
# 2. 初始化密钥生成器
generator = KeyGenerator(dll_path)
results_table = {}
# 3. 循环处理每个用户名,并显示进度条
print(f"[*] 正在从 '{users_file}' 处理 {len(usernames)} 个用户名...")
for name in tqdm(usernames, desc="生成密码"):
password = generator.get_password(name)
results_table[name] = password
# 4. 生成指定格式的 JavaScript 输出
print("\n[*] 所有用户名处理完毕,正在生成JS代码...")
# 构建 table 对象的内容
table_entries = []
for name, pwd in results_table.items():
# 格式化为 "username": "password",
table_entries.append(f' "{name}": "{pwd}"')
table_content = ",\n".join(table_entries)
# 拼接完整的JS代码
js_output = f"""
// 复制以下 JS 到浏览器 Console → 回车
const table = {{
{table_content}
}};
Object.entries(table).forEach(([n,c])=>{{
const inp = document.getElementById(n);
if(inp){{
inp.value = c;
inp.dispatchEvent(new Event("input",{{bubbles:true}}));
}}
}});
console.log("✅ 填完",Object.keys(table).length,"项");
"""
# 5. 打印最终结果
print("-" * 50)
print(js_output.strip())
print("-" * 50)
if __name__ == "__main__":
main()
3.QRS
打断点调试交叉引用最后定位到加密函数,也可以搜索qrs找到
一个魔改xtea,可以直接调试发现TickCount是固定值
密文密钥教程引用就能找到
import struct
def Dec(v24, v25, Tick):
key = [0x01234567, 0x89ABCDEF, 0xFEDCBA98, 0x76543210]
TickCount = Tick & 0xFFFFFFFF
v26 = 48
v28 = (TickCount * 48) & 0xFFFFFFFF
while v26 > 0:
v28 = (v28 - TickCount) & 0xFFFFFFFF
a = (v24 + (((v24 << 4) & 0xFFFFFFFF) ^ (v24 >> 5))) & 0xFFFFFFFF
sum_tick = (v28 + TickCount) & 0xFFFFFFFF
idx = (sum_tick >> 9) & 0xC
idx //= 4
b = (sum_tick + key[idx]) & 0xFFFFFFFF
c = a ^ b
v25 = (v25 - c) & 0xFFFFFFFF
d = (v25 + (((v25 << 4) & 0xFFFFFFFF) ^ (v25 >> 5))) & 0xFFFFFFFF
e = (key[v28 & 3] + v28) & 0xFFFFFFFF
f = d ^ e
v24 = (v24 - f) & 0xFFFFFFFF
v26 -= 1
return v24, v25
enc = [0x083EA621, 0xC745973C, 0xE3B77AE8, 0xCDEE8146, 0x7DC86B96, 0x6B8C9D3B, 0x79B14342, 0x2ECF0F0D]
for s in range(4):
v1 = enc[s * 2]
v2 = enc[s * 2 + 1]
v1, v2 = Dec(v1, v2, 0x68547369)
enc[s * 2] = v1
enc[s * 2 + 1] = v2
bytes_data = b''.join(struct.pack('<I', x) for x in enc)
print(f"NepCTF{{{bytes_data.decode('ascii')}}}")
#NepCTF{a4747f82be106d3f8c4d747c744d7ee5}
Misc:
1.SpeedMino
方法很多,我可能选了个相对麻烦的,ce改数据;写自动脚本;改源码重打包都可以
NepCTF{You_ARE_SpeedMino_GRAND-MASTER_ROUNDS!_TGLKZ}
把给的exe用binwalk解压打开main.lua,里面是游戏的核心代码
魔改rc4
def KSA(key: bytes):
S = list(range(256))
j = 0
for i in range(256):
j = (j + S[i] + key[i % len(key)]) % 256
S[i], S[j] = S[j], S[i]
return S
def PRGA(S):
i = j = 0
while True:
i = (i + 1) % 256
j = (j + S[i]) % 256
S[i], S[j] = S[j], S[i]
yield S[(S[i] + S[j]) % 256]
# ---------- SpeedMino 专用部分 ----------
KEY = b"Speedmino Created By MrZ and modified by zxc"
# 初始明文数组 (源码直接抄的,y[0] 是长度 52,不参与加密)
y = [
52,
187,24,5,131,58,243,176,235,179,159,170,155,201,23,6,3,
210,27,113,11,161,94,245,41,29,43,199,8,200,252,86,17,
72,177,52,252,20,74,111,53,28,6,190,108,47,16,237,148,
82,253,148,6
]
def speedmino_flag():
# 1) 做 RC4 的 KSA
S = KSA(KEY)
keystream = PRGA(S)
# 2) 先“烧掉”剪贴板 55 个空格(32)──推进 RC4 状态
for _ in range(55):
_ = (32 + next(keystream)) & 0xFF
# 3) 对同一数组反复加密 2600 次 (每轮把 y[1:] 加上新的 keystream)
for _ in range(2600):
for i in range(1, len(y)):
y[i] = (y[i] + next(keystream)) & 0xFF
# 4) 转成可打印字符串
flag_bytes = y[1:] # 去掉长度位
flag = ''.join(chr(b) for b in flag_bytes)
return flag
print(speedmino_flag())
2.MoewBle喵泡
玩游戏把flag玩出来
94721248-773d-0b25-0e2d-db***c29-9389
010查看global-metadata.dat找到隐藏的第7段
3.NepBotEvent
简单先处理一下数据 48为一组,同时去掉最后的无规律的数据
# -*- coding: utf-8 -*-
# 步骤 1: 定义Linux输入事件的按键码与字符的映射关系
# 该映射基于/usr/include/linux/input-event-codes.h头文件
KEYMAP = {
0x01: "ESC", 0x02: "1", 0x03: "2", 0x04: "3", 0x05: "4", 0x06: "5", 0x07: "6", 0x08: "7", 0x09: "8", 0x0a: "9", 0x0b: "0", 0x0c: "-", 0x0d: "=", 0x0e: "BACKSPACE", 0x0f: "TAB",
0x10: "q", 0x11: "w", 0x12: "e", 0x13: "r", 0x14: "t", 0x15: "y", 0x16: "u", 0x17: "i", 0x18: "o", 0x19: "p", 0x1a: "[", 0x1b: "]", 0x1c: "ENTER", 0x1d: "L_CTRL",
0x1e: "a", 0x1f: "s", 0x20: "d", 0x21: "f", 0x22: "g", 0x23: "h", 0x24: "j", 0x25: "k", 0x26: "l", 0x27: ";", 0x28: "'", 0x29: "`", 0x2a: "L_SHIFT", 0x2b: "\\",
0x2c: "z", 0x2d: "x", 0x2e: "c", 0x2f: "v", 0x30: "b", 0x31: "n", 0x32: "m", 0x33: ",", 0x34: ".", 0x35: "/", 0x36: "R_SHIFT", 0x37: "*", 0x38: "L_ALT", 0x39: "SPACE",
0x3a: "CAPS_LOCK"
}
# 步骤 2: 定义Shift键按下时的映射关系
SHIFT_KEYMAP = {
0x02: "!", 0x03: "@", 0x04: "#", 0x05: "$", 0x06: "%", 0x07: "^", 0x08: "&", 0x09: "*", 0x0a: "(", 0x0b: ")", 0x0c: "_", 0x0d: "+",
0x10: "Q", 0x11: "W", 0x12: "E", 0x13: "R", 0x14: "T", 0x15: "Y", 0x16: "U", 0x17: "I", 0x18: "O", 0x19: "P", 0x1a: "{", 0x1b: "}",
0x1e: "A", 0x1f: "S", 0x20: "D", 0x21: "F", 0x22: "G", 0x23: "H", 0x24: "J", 0x25: "K", 0x26: "L", 0x27: ":", 0x28: "\"", 0x29: "~", 0x2b: "|",
0x2c: "Z", 0x2d: "X", 0x2e: "C", 0x2f: "V", 0x30: "B", 0x31: "N", 0x32: "M", 0x33: "<", 0x34: ">", 0x35: "?"
}
def parse_key_log(file_path):
"""
解析键盘记录文件并返回还原的文本。
"""
reconstructed_text = []
# 步骤 3: 初始化状态变量
shift_pressed = False
caps_lock_on = False
try:
with open(file_path, 'r') as f:
for line in f:
line = line.strip()
# 确保是有效的48字符行
if len(line) != 48:
continue
# 步骤 4: 提取事件的各个部分
# struct input_event 占用 24 字节 (48 个十六进制字符)
# bytes 16-17: type, bytes 18-19: code, bytes 20-23: value
type_hex = line[32:36]
code_hex = line[36:40]
value_hex = line[40:48]
# 步骤 5: 将小端序的十六进制转换为整数
# 例如 '1c00' -> 0x001c
event_type = int(type_hex[2:4] + type_hex[0:2], 16)
event_code = int(code_hex[2:4] + code_hex[0:2], 16)
event_value = int(value_hex[6:8] + value_hex[4:6] + value_hex[2:4] + value_hex[0:2], 16)
# 步骤 6: 只处理键盘按键事件 (EV_KEY)
if event_type != 0x01:
continue
is_press = (event_value == 1)
is_release = (event_value == 0)
# 更新Shift键状态
if event_code in [0x2a, 0x36]: # L_SHIFT or R_SHIFT
shift_pressed = is_press
continue
# 更新Caps Lock状态 (按下时切换)
if event_code == 0x3a and is_press:
caps_lock_on = not caps_lock_on
continue
# 步骤 7: 处理字符按键的按下事件
if is_press:
char_to_add = None
# 根据Shift状态选择正确的映射表
if shift_pressed:
char_to_add = SHIFT_KEYMAP.get(event_code)
else:
char_to_add = KEYMAP.get(event_code)
# 根据Caps Lock状态调整字母大小写
if caps_lock_on and char_to_add and char_to_add.isalpha() and len(char_to_add) == 1:
# Caps Lock和Shift同时按下,效果抵消,输出小写
if shift_pressed:
char_to_add = char_to_add.lower()
# 仅Caps Lock按下,输出大写
else:
char_to_add = char_to_add.upper()
# 步骤 8: 将解析出的字符添加到结果中
if char_to_add:
if char_to_add == "BACKSPACE":
if reconstructed_text:
reconstructed_text.pop()
elif char_to_add == "ENTER":
reconstructed_text.append("\n")
elif char_to_add == "SPACE":
reconstructed_text.append(" ")
elif char_to_add == "TAB":
reconstructed_text.append("\t")
# 忽略其他控制字符如 "L_SHIFT", "CAPS_LOCK" 等
elif len(char_to_add) == 1:
reconstructed_text.append(char_to_add)
except FileNotFoundError:
return f"错误: 文件 '{file_path}' 不存在。"
return "".join(reconstructed_text)
# --- 主程序 ---
if __name__ == "__main__":
# 将题目提供的1-2.txt文件放在和脚本相同的目录下
log_file = "/Users/sinqwq/Downloads/1-2.txt"
decoded_text = parse_key_log(log_file)
print("--- 还原出的键盘输入内容 ---")
print(decoded_text)
Crypto
1.Nepsign
import ast
import os
import socket
import ssl
import struct
import time
from typing import List, Tuple
HOST = "nepctf30-y5ee-u90s-qszf-rxn9rtsbo733.nepctf.com"#改成自己靶机
PORT = 443
PREFER_SSL = True # 如果你确认是裸 TCP,把它改成 False
CONNECT_TIMEOUT = 30.0
RW_TIMEOUT = 30.0
HEX = "01234A56789abcdef"
TARGET = b"happy for NepCTF 2025"
# ===================== 纯 Python SM3 实现(与 GM/T 0004-2012 兼容) =====================
# 说明:服务端使用 gmssl.sm3.sm3_hash(list(data))。本实现等价:SM3_hex(data) -> 64 位小写 hex。
IV = (
0x7380166F, 0x4914B2B9, 0x172442D7, 0xDA8A0600,
0xA96F30BC, 0x163138AA, 0xE38DEE4D, 0xB0FB0E4E
)
def _rotl(x, n): return ((x << n) | (x >> (32 - n))) & 0xFFFFFFFF
def _ff(j, x, y, z): return (x ^ y ^ z) if j <= 15 else ((x & y) | (x & z) | (y & z))
def _gg(j, x, y, z): return (x ^ y ^ z) if j <= 15 else ((x & y) | ((~x) & z))
def _p0(x): return x ^ _rotl(x, 9) ^ _rotl(x, 17)
def _p1(x): return x ^ _rotl(x, 15) ^ _rotl(x, 23)
def SM3_hex(data: bytes) -> str:
"""SM3 摘要,返回 64 个十六进制字符(小写)"""# padding
ml = len(data) * 8
msg = data + b'\x80'
while (len(msg) % 64) != 56:
msg += b'\x00'
msg += struct.pack(">Q", ml)
# compress
V = list(IV)
for i in range(0, len(msg), 64):
B = msg[i:i+64]
W = list(struct.unpack(">16I", B))
for j in range(16, 68):
W.append((_p1(W[j-16] ^ W[j-9] ^ _rotl(W[j-3], 15)) ^ _rotl(W[j-13], 7) ^ W[j-6]) & 0xFFFFFFFF)
W1 = [(W[j] ^ W[j+4]) & 0xFFFFFFFF for j in range(64)]
A,Bb,C,D,E,F,G,Hh = V
for j in range(64):
Tj = 0x79CC4519 if j <= 15 else 0x7A879D8A
SS1 = _rotl((_rotl(A,12) + E + _rotl(Tj, j % 32)) & 0xFFFFFFFF, 7)
SS2 = SS1 ^ _rotl(A,12)
TT1 = (_ff(j, A, Bb, C) + D + SS2 + W1[j]) & 0xFFFFFFFF
TT2 = (_gg(j, E, F, G) + Hh + SS1 + W[j]) & 0xFFFFFFFF
D = C
C = _rotl(Bb, 9)
Bb = A
A = TT1
Hh = G
G = _rotl(F, 19)
F = E
E = _p0(TT2)
V = [a ^ b for a, b in zip(V, [A,Bb,C,D,E,F,G,Hh])]
return "".join(f"{x:08x}" for x in V)
def SM3_n_hex(data_hex: str, n: int) -> str:
"""题面同款链式:把 hex 视为字节,迭代 n 次 SM3,取前 256 bit(64 hex)。"""b = bytes.fromhex(data_hex)
for _ in range(n):
b = bytes.fromhex(SM3_hex(b))
return b.hex()[:64]
# ===================== 题面同款步数计算 =====================
def steps_from_msg(msg: bytes) -> List[int]:
h = SM3_hex(msg) # 64 hex chars
# 前 32 条链:摘要的 32 个字节
a = [int(h[2*i:2*i+2], 16) for i in range(32)]
# 后 16 条链:每个 hex 字符在 h 中的“位置(1..64)之和” mod 255
sums = []
for sym in HEX:
s = 0
for j, ch in enumerate(h):
if ch == sym:
s += j + 1
sums.append(s % 255)
return a + sums # 共 48 个
# ===================== 构造满足条件的消息(找到 ≤ 目标步数) =====================
def find_msg_for_byte(idx: int, limit: int) -> bytes:
base = os.urandom(8) + b'|i=' + bytes([idx]) + b'|'
nonce = 0
while True:
msg = base + str(nonce).encode()
if int(SM3_hex(msg)[2*idx:2*idx+2], 16) <= limit:
return msg
nonce += 1
def find_msg_for_sum(sym_idx: int, limit: int) -> bytes:
base = os.urandom(8) + b'|s=' + bytes([sym_idx]) + b'|'
nonce = 0
while True:
h = SM3_hex(base + str(nonce).encode())
s = 0
for j, ch in enumerate(h):
if ch == HEX[sym_idx]:
s += j + 1
if (s % 255) <= limit:
return base + str(nonce).encode()
nonce += 1
# ===================== 极简远程交互(TLS/明文) =====================
class Remote:
def __init__(self, host: str, port: int, use_ssl: bool):
raw = socket.create_connection((host, port), timeout=CONNECT_TIMEOUT)
if use_ssl:
ctx = ssl.create_default_context()
self.sock = ctx.wrap_socket(raw, server_hostname=host)
else:
self.sock = raw
self.sock.settimeout(RW_TIMEOUT)
self.buf = b""
def sendline(self, data: bytes):
# CRLF 兼容性更高
self.sock.sendall(data + b"\r\n")
def _recv(self, n=4096) -> bytes:
chunk = self.sock.recv(n)
if not chunk:
raise EOFError("connection closed")
return chunk
def recv_until(self, token: bytes, max_bytes: int = 1_000_000) -> bytes:
while token not in self.buf:
self.buf += self._recv()
if len(self.buf) > max_bytes:
raise RuntimeError("recv buffer exceeded limit")
i = self.buf.index(token) + len(token)
out, self.buf = self.buf[:i], self.buf[i:]
return out
def recv_line(self) -> bytes:
return self.recv_until(b"\n")
def try_connect(host: str, port: int, prefer_ssl: bool = True) -> Tuple[Remote, bool]:
order = [True, False] if prefer_ssl else [False, True]
last_err = None
for use_ssl in order:
try:
r = Remote(host, port, use_ssl)
return r, use_ssl
except Exception as e:
last_err = e
time.sleep(0.5)
raise RuntimeError(f"无法连接(TLS/明文均失败):{last_err}")
# ===================== 与服务交互(不等 '> ',直接发 1/2) =====================
def get_signature(io: Remote, msg: bytes):
# 菜单 1:请求签名(服务端遇到目标消息会拒绝,我们只签自造消息)
io.sendline(b"1")
io.recv_until(b"msg: ")
io.sendline(msg.hex().encode())
# 返回一个 Python 列表字符串,可能被拆成多行;凑到最后一个 ']'
line = io.recv_line().strip()
while b"]" not in line:
line += io.recv_line().strip()
raw = line[: line.rfind(b"]") + 1]
return ast.literal_eval(raw.decode())
def submit_verify(io: Remote, forged_list: List[str]) -> str:
io.sendline(b"2")
io.recv_until(b"give me a qq: ")
io.sendline(str(forged_list).encode())
return io.recv_line().decode(errors="ignore")
# ===================== 主流程 =====================
def main():
print("[+] 计算目标步数 ...")
steps_target = steps_from_msg(TARGET)
print(f"[+] 连接 {HOST}:{PORT},优先 TLS={PREFER_SSL} ...")
io, used_tls = try_connect(HOST, PORT, prefer_ssl=PREFER_SSL)
print(f"[+] 已连接,传输方式:{'TLS' if used_tls else '明文 TCP'}")
chosen: List[Tuple[bytes, List[str], List[int]]] = [None] * 48 # (msg, sig, steps_known)
def try_msg_fill(m: bytes) -> int:
nonlocal chosen
steps_known = steps_from_msg(m)
sig = get_signature(io, m)
filled = 0
for i in range(48):
if chosen[i] is None and steps_known[i] <= steps_target[i]:
chosen[i] = (m, sig, steps_known)
filled += 1
return filled
# 先打一发随机覆盖
covered = try_msg_fill(os.urandom(32))
print(f"[+] 预热覆盖 {covered} 条链。")
# 覆盖前 32 条(字节步数)
for i in range(32):
if chosen[i] is None:
m = find_msg_for_byte(i, steps_target[i])
c = try_msg_fill(m)
print(f"[+] 覆盖字节链 #{i:02d},本次新增 {c} 条。")
# 覆盖后 16 条(位置和 %255)
for j in range(16):
idx = 32 + j
if chosen[idx] is None:
m = find_msg_for_sum(j, steps_target[idx])
c = try_msg_fill(m)
print(f"[+] 覆盖 sum 链 #{idx:02d} (sym='{HEX[j]}'),本次新增 {c} 条。")
if not all(chosen):
missing = [i for i, v in enumerate(chosen) if v is None]
raise RuntimeError(f"还有链未覆盖:{missing}")
print("[+] 组装伪造签名 ...")
forged: List[str] = []
for i in range(48):
_, sig, steps_known = chosen[i]
delta = steps_target[i] - steps_known[i]
q = sig[i]
if delta > 0:
q = SM3_n_hex(q, delta)
forged.append(q)
print("[+] 提交验证 ...")
resp = submit_verify(io, forged).strip()
print("[+] 服务端返回:", resp)
if __name__ == "__main__":
try:
main()
except Exception as e:
print("[-] 运行出错:", repr(e))
print(" 提示:若连接卡住/超时,请把 PREFER_SSL 改为相反值再试(True/False)。")