一、简述
BugkuCTF 的猪圈密码题目,复盘一下,依旧 C 语言实现,万物终将归于 C。
题目地址:aHR0cHM6Ly9jdGYuYnVna3UuY29tL2NoYWxsZW5nZXMvZGV0YWlsL2lkLzE1OS5odG1s
二、思路概括
首先定义 2 个 3*3 的数组来分别存储两个井字格中分别对应的字母,然后再分别定义 2 个 2*2 的数组,来存储 x 格子中的 4 个英文字母,然后将特殊符号代表的英文字母组成一个数组,在控制台先输出图形对应的字母,然后输入对应的字母,将每个字符分别于定义的数组中的字母进行比较,如果相同,则就破解了密码。
三、解密原理
基于几何坐标的单表代换。
不依靠复杂的算法,而是根据视觉位置的逻辑映射。
核心原理:空间位置 = 特定字母
猪圈密码将 26 个字母预先 "关" 在四个特定的 "猪圈" 中。解密的过程就是寻找符号边框所对应的空间坐标。
3.1 布局规则
井字布局
- 第一个井字格:存储 A-I。符号由外部边框组成(如 L 型、口 型、U 型)。
- 第二个井字格:存储 J-R。形状与第一组完全相同,但在内部增加一个 "点" 作为状态位(Flag),表示偏移量。
X 型布局(X-shape)
- 第一个 X 格:存储 S-V。符号由对角线形成的三角形组成。
- 第二个 X 格:存储 W-Z。形状相同,内部增加一个 "点"。
3.2 解密的三步逻辑
- 判断容器类型:观察符号是直角的(来自井字格)还是斜角的(来自 X 格)。
- 判断状态位(点) :观察符号内是否有 "点"。
- 无点:对应第一组字母(A-I 或 S-V)。
- 有点:对应第二组字母(J-R 或 W-Z)。
- 确定精确坐标:根据边框的开口方向,确定字母在格子的哪个位置(左上、正中、右下等)。
3.3 映射 (Bijection)
在数学上,这是一种单表代换密码。
- 定义域:26 个几何符号集合。
- 值域:26 个英文字母集合。
- 每一个符号 通过固定的查找表 对应唯一的字母。
3.4 为什么它容易被破解?
- 结构固定:标准猪圈密码的字母顺序通常是按 A-Z 排列的,这使得攻击者不需要密钥,只要识别出是猪圈密码就能解密。
- 频率特征保留:它没有改变字母出现的频率。如果密文中某个 "左开口" 符号出现次数最多,那它极大概率就是英语中最常出现的字母 E。
C 语言中,数组下标 [i][j] 实际上就是模拟了这个 "空间坐标"。
四、代码
cpp
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<ctype.h>
// 定义第一个#数组
char j_zhujuan1[3][3] = {
'A','B','C',
'D','E','F',
'G','H','I',
};
// 定义第二个#数组
char j_zhujuan2[3][3] =
{
'J','K','L',
'M','N','O',
'P','Q','R',
};
// 定义第一个X数组
char x_zhujuan1[2][2] = {
'S','T',
'U','V',
};
// 定义第二个X数组
char x_zhujuan2[2][2] = {
'W','X',
'Y','Z',
};
void printCipherKey() {
int i, j;
printf("============================================================\n");
printf(" 猪圈密码 (Pigpen Cipher)\n");
printf("============================================================\n\n");
printf(" [ 井字格 1: A-I ] [ 井字格 2: J-R (带点 .) ]\n");
for (i = 0; i < 3; i++) {
printf(" %c | %c | %c ", j_zhujuan1[i][0], j_zhujuan1[i][1], j_zhujuan1[i][2]);
printf(" ");
printf(" %c. | %c. | %c.\n", j_zhujuan2[i][0], j_zhujuan2[i][1], j_zhujuan2[i][2]);
if (i < 2) {
printf(" ---+---+--- ---+---+---\n");
}
}
printf("\n\n");
printf(" [ X 型格 1: S-V ] [ X 型格 2: W-Z (带点 .) ]\n");
for (i = 0; i < 2; i++) {
printf(" %c | %c ", x_zhujuan1[i][0], x_zhujuan1[i][1]);
printf(" ");
printf(" %c. | %c.\n", x_zhujuan2[i][0], x_zhujuan2[i][1]);
if (i < 1) {
printf(" ---+--- ---+---\n");
}
}
printf("\n============================================================\n");
printf("操作提示: 请对照上图符号形状,依次输入对应的字母完成解密。\n");
}
int main(){
char result[100];
char user_input[100];
int found = 0;
int count = 0;
int i,j,k,m,n,rs;
printCipherKey();
printf("\n请输入识别出的字符序列 (例:ABC...): ");
scanf("%s", user_input);
for(k=0;k<(int)strlen(user_input);k++){
char target = toupper(user_input[k]);
int found = 0;
for (i = 0; i < 3; i++) {
for (j = 0; j < 3; j++) {
if (j_zhujuan1[i][j] == target || j_zhujuan2[i][j] == target) {
result[count++] = target;
found = 1;
break;
}
}
if (found) break;
}
if(!found){
for(m=0;m<2;m++){
for(n=0;n<2;n++){
if (x_zhujuan1[m][n] == target || x_zhujuan2[m][n] == target) {
result[count++] = target;
found = 1;
break;
}
}
if(found){
break;
}
}
}
}
result[count] = '\0';
printf("\n[+] 解密成功!");
printf("\n[+] 原始字符串: %s", result);
printf("\n[+] 建议 Flag 格式: flag{");
for(rs=0; rs<count; rs++) printf("%c",tolower(result[rs]));
printf("}\n\n");
}