[ misc 知识点 ] 编码分析
2026-08-22 20:05:38

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. 判断决策树(先看这个)

  1. 看字符集:只有 []()!+ → JSFuck;只有 ><+-.,[] → BrainFuck;Ook. Ook? → Ook;全角颜文字 → aaencode;emoji → base100;兔/子汉字 → Rabbit
  2. 看格式begin 644 开头 → uuencode;末尾 = → base64/32(= 最多 2 个是 b64,最多 6 个是 b32);只有 0-9A-F → hex;&#x..; → HTML实体
  3. 看语义:佛经体 → 与佛论禅;”富强民主…” → 核心价值观;字形拆分的怪中文 → 阴阳怪气
  4. 看视觉:盲文点阵 → Braille;小人简笔画 → 跳舞的小人;∟⊔⊐ 几何符号 → 猪圈密码;”由中人工大王” → 当铺密码
  5. 看不到内容 → whitespace / 零宽(全选复制到 CyberChef 看有没有红点)
  6. 纯数字:都在 32-126 → ASCII;带小数点 → IEEE754
  7. 字母类:数字不被加密 → 维吉尼亚(多表);像英文但拼不对 → 键盘密码;纯单表偏移 → 凯撒

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): # 已知 A新表 + C原内容 -> B编码结果
std_b64 = base64.b64encode(plain.encode()).decode()
return std_b64.translate(str.maketrans(STD, table))
def A_B_to_C(table, cipher): # 已知 A新表 + B编码结果 -> C原内容
return base64.b64decode(cipher.translate(str.maketrans(table, STD))).decode()
def B_C_to_A(cipher, plain): # 已知 B编码结果 + C原内容 -> A新表(部分)
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
# much64.py 用法:python much64.py file:decoded.txt
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)

维吉尼亚

  • 判断:数字未被加密(多表替换通常保留数字);IC 0.038-0.045
  • 已知明文推密钥:密钥 = (密文 - 明文 + 26) mod 26 → 见 20260322polarctfmisc time
  • 工具:dcode vigenere / moersima
  • 坑:
    • 随波逐流 7.2 对维吉尼亚大小写不分
    • dcode 无密钥破解我没研究出来(TODO)

摩斯

  • 工具:iamwawa
  • 坑:-. 可能互换、结果可能要转小写(见 20260202SHCTF 不止二维码)

猪圈密码

  • 特征:∟⊔⊐ 等几何符号
  • 格子:4 个 # 格 + 4 个 X 格放字母,带点表示第二个字母
  • 工具:boxentriq pigpen

当铺密码

  • 特征:汉字”由中人工大王主井羊非”→ 出头数 = 数字
  • 由=1 中=2 人=3 工=4 大=5 王=6 主=7 井=8 羊=9 非=10/0

键盘密码

  • 三种:键位坐标 / 键位包围(如 SDF 围住 X)/ 按键偏移(W→Q 或 E)

云隐密码

  • 汉字笔画数 → 数字 → ASCII

跳舞的小人

  • 特征:火柴人简笔画 → dcode

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. 文本混淆

编码 特征 工具
阴阳怪气 字形拆分(亻尔 女子) juzy.wang
社会主义核心价值观 富强民主文明和谐… bugku / apiked
与佛论禅 佛曰:+ 文言 hi.pcmoe.net(常挂,备多个)
零宽 复制后 CyberChef 有红点 Misc tools(Escape Unicode Characters 看 \u200B\u200C\u200D

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
上一页
2026-08-22 20:05:38
下一页