3.编码分析(决策树版)
用法:见到密文 → 先看第 0 节判断编码 → 查对应工具 → 记坑。
原百科版备份:_backup_md/pre-rewrite-20260818/3.编码分析.md
🏷️ 例题:
| 20251108浙江省赛 RealSignin(Base64换表)
| 20251201ISCTF 木林森(RC4)/ Miscrypto(Base64换表+费马)
| 20260124SpringCTF 零、强运和福来阁(多层编码+零宽)
| 20260130春秋杯 Log_Detective(SQL盲注日志提取)
| 20260130furryCTF CyberChef(chef语言)
| 20260202hgame 打好基础(base100/base家族混合解码)
| NSSCTF刷题 我的银行卡密码(T9/Atbash/Qwerty/凯撒)
0. 判断决策树(先看这个)
- 看字符集:只有
[]()!+ → JSFuck;只有 ><+-.,[] → BrainFuck;Ook. Ook? → Ook;全角颜文字 → aaencode;emoji → base100;兔/子汉字 → Rabbit
- 看格式:
begin 644 开头 → uuencode;末尾 = → base64/32(= 最多 2 个是 b64,最多 6 个是 b32);只有 0-9A-F → hex;&#x..; → HTML实体
- 看语义:佛经体 → 与佛论禅;”富强民主…” → 核心价值观;字形拆分的怪中文 → 阴阳怪气
- 看视觉:盲文点阵 → Braille;小人简笔画 → 跳舞的小人;
∟⊔⊐ 几何符号 → 猪圈密码;”由中人工大王” → 当铺密码
- 看不到内容 → whitespace / 零宽(全选复制到 CyberChef 看有没有红点)
- 纯数字:都在 32-126 → ASCII;带小数点 → IEEE754
- 字母类:数字不被加密 → 维吉尼亚(多表);像英文但拼不对 → 键盘密码;纯单表偏移 → 凯撒
1. 进制
- hex↔ascii:
bytes.fromhex(s).decode() / CyberChef From Hex
- 三进制转十进制:
1 2 3 4 5 6 7 8 9 10 11
| def ternary_to_decimal(ternary_str): if not ternary_str: raise ValueError("输入不能为空") if not all(c in '012' for c in ternary_str): raise ValueError("三进制字符串只能包含字符 '0', '1', '2'") decimal = 0 power = 0 for digit in reversed(ternary_str): decimal += int(digit) * (3 ** power) power += 1 return decimal
|
- 坑:8 位二进制解出来是乱码 → 不是 ASCII,是自定义编码
2. Base 家族
特征速查
| 编码方式 |
字符集 |
= |
flag 编码后 |
| base16(Hex) |
0-9 A-F |
无 |
666c6167 |
| base32 |
A-Z 2-7 |
最多6个 |
MZWGCZY= |
| base64 |
A-Za-z0-9+/ |
1-2个 |
ZmxhZw== |
| base58 |
去掉 0 O I l + / |
无 |
3cr9Ae |
| base62 |
0-9a-zA-Z |
无 |
1sI8pz |
| base85 |
可打印ASCII 33-117 |
无 |
Ao(mg |
| base91/92 |
91/92个可打印字符 |
无 |
@iH<Z |
| base100(emoji) |
emoji |
无 |
👝👣👘👞 |
判断:
- 末尾有
= 一定是 base;没有也可能是
- base58 无
0OIl+/;base62 纯字母数字;base85 是 ASCII 33-117;base100 全 emoji
- 混合多重解码(如 base92→91→85→64→62→58→45→32)见 20260202hgame 打好基础
换表 base64
- 特征:像 base64 但标准表解不开;或题目直接给了”打乱的表”
- 原理一行:只换查表映射,6bit 分组逻辑不变
- 脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import base64 STD = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" def A_C_to_B(table, plain): std_b64 = base64.b64encode(plain.encode()).decode() return std_b64.translate(str.maketrans(STD, table)) def A_B_to_C(table, cipher): return base64.b64decode(cipher.translate(str.maketrans(table, STD))).decode() def B_C_to_A(cipher, plain): std_b64 = base64.b64encode(plain.encode()).decode() table = ['?'] * 64 for c, s in zip(cipher, std_b64): if c == '=' or s == '=': break table[STD.index(s)] = c return ''.join(table)
|
- 坑:
- 新表不一定能完整恢复——只出现过的字符才能定位;缺 1-2 个字符时爆破排列
- 想拿完整表:造一段恰好用全 64 个字符的明文,编码结果就是整张表
- 文件头是天然已知明文:PK、PNG magic、IEND → 用明文对齐反推换表(见 1.md)
- 例题:20251108浙江省赛 RealSignin / 例题-换表Base64
多次 base64
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| import base64 import sys import re
def is_base64(s): if isinstance(s, str): s = s.strip() if len(s) == 0: return False if not re.fullmatch(r'[A-Za-z0-9+/]*={0,2}', s): return False if len(s) % 4 != 0: return False return True return False
def multi_decode(data): current = data.encode() if isinstance(data, str) else data step = 0 while True: try: decoded = base64.b64decode(current) step += 1 print(f"[Step {step}] Decoded: {decoded}") try: decoded_str = decoded.decode('utf-8') if is_base64(decoded_str): current = decoded continue else: print("\n[+] Final result (not Base64 anymore):") print(decoded_str) return decoded_str except UnicodeDecodeError: print("\n[+] Final result is binary data:") print(decoded) return decoded except Exception as e: print(f"\n[!] Decoding stopped at step {step}: {e}") try: print("Last readable content:", current.decode('utf-8', errors='replace')) except: print("Last content (binary):", current) return current
if __name__ == "__main__": if len(sys.argv) < 2: print("Usage:") print(" python multi_base64_decode.py <string>") print(" python multi_base64_decode.py file:<filename>") sys.exit(1) input_arg = sys.argv[1] if input_arg.startswith("file:"): filename = input_arg[5:] with open(filename, 'r') as f: content = f.read().strip() else: content = input_arg.strip() multi_decode(content)
|
base64 隐写
- 特征:多行 base64,解出来是正常文本 → 再看隐写位
- 原理一行:
= 前的最后一个字符可多塞 2/4/6 bit
- 脚本:
1 2 3 4 5 6 7 8 9 10 11 12
| import base64 b64chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' with open('stego.txt', 'rb') as f: bin_str = '' for line in f.readlines(): stegb64 = str(line, "utf-8").strip("\n") rowb64 = str(base64.b64encode(base64.b64decode(stegb64)), "utf-8").strip("\n") offset = abs(b64chars.index(stegb64.replace('=', '')[-1]) - b64chars.index(rowb64.replace('=', '')[-1])) equalnum = stegb64.count('=') if equalnum: bin_str += bin(offset)[2:].zfill(equalnum * 2) print(''.join([chr(int(bin_str[i:i + 8], 2)) for i in range(0, len(bin_str), 8)]))
|
3. 古典密码
栅栏密码
- 判断:密文按行数斜向重排;key = 行数
- 脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| def rail_fence_decrypt(ciphertext: str, rails: int = 4) -> str: n = len(ciphertext) if rails <= 1: return ciphertext row_counts = [0] * rails r, step = 0, 1 for _ in range(n): row_counts[r] += 1 if r == 0: step = 1 elif r == rails - 1: step = -1 r += step rows = [] idx = 0 for count in row_counts: rows.append(list(ciphertext[idx:idx + count])) idx += count result = [] r, step = 0, 1 pointers = [0] * rails for _ in range(n): result.append(rows[r][pointers[r]]) pointers[r] += 1 if r == 0: step = 1 elif r == rails - 1: step = -1 r += step return ''.join(result)
|
维吉尼亚
摩斯
猪圈密码
当铺密码
- 特征:汉字”由中人工大王主井羊非”→ 出头数 = 数字
由=1 中=2 人=3 工=4 大=5 王=6 主=7 井=8 羊=9 非=10/0
键盘密码
- 三种:键位坐标 / 键位包围(如 SDF 围住 X)/ 按键偏移(W→Q 或 E)
云隐密码
跳舞的小人
4. 代码混淆变体
| 编码 |
特征 |
工具 |
| JSFuck |
只有 []()!+ |
jsfuck.com / CyberChef JS Parser |
| aaencode |
全角颜文字 ゚ω゚ノ |
浏览器控制台直接跑 |
| BrainFuck |
><+-.,[] |
CyberChef Brainfuck |
| Ook |
Ook. Ook? Ook! |
splitbrain |
| Rabbit |
兔/子等汉字 |
先转 BrainFuck 再解 |
| whitespace |
只有空格/Tab/换行 |
vii5ard |
| Braille |
U+2800-28FF |
CyberChef / 盲文翻译 |
- 代码混淆(JS/Py/PHP):找最外层编码函数逐层剥;PHP 经典
eval(gzinflate(base64_decode(...)));JS 混淆直接 eval 跑(CTF 无所谓安全性)
5. 文本混淆
6. 数值/字符编码
- hex:
bytes.fromhex / CyberChef From Hex(输入必须偶数长度)
- URL:
%+hex;保留字符 :/?#[]@!$&'()*+,;=;非保留 -_.~
- IEEE754:特征 = 浮点数序列;
struct.pack('>f', 3.14).hex() → 4048f5c3
- ASCII:可打印范围 32-126,解出来落在这个区间就是 ASCII
- HTML实体:CyberChef From HTML Entity;或
sed 's/&#x\([0-9a-fA-F]*\);/\\x\1/g' | xargs -0 printf "%b\n"
7. 附录:文件头/尾速查
1 2 3 4 5 6 7 8 9 10 11 12 13
| zip 文件头:50 4B 03 04 rar 文件头:52 61 72 21 7z 文件头:37 7A BC AF 27 1C png 文件头:89 50 4E 47 0D 0A 1A 0A 文件尾:00 00 00 00 49 45 4E 44 AE 42 60 82 jpg 文件头:FF D8 FF E0 文件尾:FF D9 gif 文件头:47 49 46 38 39 61/37 61 文件尾:00 3B bmp 文件头:42 4D psd 文件头:38 42 50 53 tiff 文件头:49 49 2A 00 mp3 文件头:49 44 33 wav 文件头:52 49 46 46 mid 文件头:4D 54 68 64 avi 文件头:41 56 49 20 mov 文件头:00 00 00 20 66 74 79 70 71 74 swf 文件头:46 57 53 pyc 文件头:03 F3 0D 0A Office2003:D0 CF 11 E0 xml 文件头:3C 3F 78 6D 6C html 文件头:3C 21 44 4F 43 54 59 50 45(<!DOCTYPE)或 3C 68 74 6D 6C(<html) rtf 文件头:7B 5C 72 74 66 eml 文件头:44 65 6C 69 76 65 72 79 2D 64 61 74 65 3A mdb 文件头:53 74 61 6E 64 61 72 64 20 4A pdf 文件头:25 50 44 46 2D 31 2E
|