第一次作为较大型比赛的出题人, 各位师傅轻点拷打QAQ.
CheckFlow
本题主要考点是LDPC校验码, 使⽤6 * 12的校验矩阵, 原码⻓度6位, 校验码⻓度6位, 总⻓12位.
LDPC码
⾸先是⽣成过程
为了便于计算, 下⾯使⽤了简化的校验过程, 使⽤了3 * 6校验矩阵作为实例.
原码:
|0 1 0|
⽣成矩阵:
|1 0 0 0 1 1|
|0 1 0 1 0 1|
|0 0 1 1 1 0|
LDPC校验码⽣成, 就是矩阵乘法(元素乘法使⽤与运算代替, 加法使⽤异或代替):
|1 0 0 0 1 1|
|0 1 0| * |0 1 0 1 0 1| = |0 1 0 1 0 1|
|0 0 1 1 1 0|
经过矩阵乘法得到的1 * 6矩阵就是LDPC校验码. 其中: 前三位是原码, 后三位是校验码
其次是校验过程 再次观察LDPC码的结构, 可以发现其实校验码只跟⽣成矩阵的后三列和原码相关, 列出关系:
假设: 原码位为 |x0 x1 x2| , 校验位为 |y0 y1 y2|
- y0 = x1 ^ x2
- y1 = x0 ^ x2
- y2 = x0 ^ x1
对上⾯三个关系进⾏变换:
- 0 = y0 ^ x1 ^ x2
- 0 = y1 ^ x0 ^ x2
- 0 = y2 ^ x0 ^ x1
也就是说当原码没有出错的情况下, 校验位和原码规定的位进⾏异或全部会得出0, 出错则得出1.
根据这个关系就可以得到校验矩阵
校验矩阵中的每一行对应一个位的校验, ⾸先对LDPC码进⾏掩码操作, 然后异或得到校验结果
关于LDPC实现检验的原理,可以⾃⾏假设⼀个位和两个位出错这两种情况,再观察三个检验位的具体值, 当然本题只涉及检验矩阵的输出结果是否为0, 所以这不是必须要理解的,感兴趣的可以了解⼀下.
|0|
|1|
|0 1 1 1 0 0| |0|
|1 0 1 0 1 0| * |0| = |0|
|1 1 0 0 0 1| |1|
|0|
|0|
|1|
题目分析
去符号C++, 可以⽤bindiff或flair恢复符号, 基本都是string和vector.
⾸先是检验矩阵⽣成
每个元素都是4字节, 是6 * 12矩阵
得到校验矩阵
|1 1 1 0 1 1 0 1 0 1 0 1|
|1 0 0 1 1 0 1 1 0 1 0 0|
|0 1 1 1 0 0 1 0 1 0 0 1|
|0 1 0 0 0 1 1 0 1 1 1 0|
|1 0 1 1 1 0 0 0 1 0 1 0|
|0 0 0 0 0 1 0 1 0 0 1 1|
通过调试查看内存, 容器判断vector, 下⾯还调⽤了函数得到检验矩阵的逆, 但是实际上并不影响最终的判断, 只是结果矩阵的格式从6 * 1变成了1 * 6, 主要是可以从该函数的两个参数中得到容器类型.
主要的验证逻辑就在主函数中, 根据sub_407028()出现的位置在if判断条件中可以猜测是check函数.
// 总体逻辑: 从小到大输入12位LDPC校验码
// 校验过程中有两个LDPC变量: back和front
// back记录上次检验的LDPC码
// front记录当前检验的LDPC码
// 校验的具体逻辑是饶了一圈的从小到大正确LDPC码的排列检查:
//1. 首先检验当前LDPC码是否正确, 出错则退出
//2. 检查back < front, 保证LDPC码从小到大排列
//3. 检查back和front之间是否存在正确的LDPC码, 如果存在则说明漏掉了
//4. 检查最后一个front到"111111111111"之间是否存在正确的LDPC码, 如果存在说明后面少了LDPC码
for ( i = 0; ; i += 12 ) // 轮检验
{
v12 = i;
if ( v12 >= len(v30) ) // 循环结束条件: 读取全部字符串
break;
sub_496C80(v34, v30, i, 12LL); // 获取一个PDPC码字符串
sub_493D80(v33, v34);
sub_493D30(v34);
if ( len(v33) != 12 ) // 检查LDPC码长度
{
v0 = sub_481E90(&unk_5E32E0, "Length error.");
sub_480730(v0, sub_481760);
exit(1LL);
}
if ( !(unsigned __int8)check(v33, v27, v28) ) // 检测front的LDPC码是否出错
{
v11 = sub_481E90(&unk_5E32E0, "Emmmmmm......Wrong.");
sub_480730(v11, sub_481760);
exit(1LL);
}
if ( i ) // 针对第一轮"000000000000"校验的判断, 第一轮设置v18变量位-1, 这样下面j进行检测的时候j初始化就是0, 与front相等, 通过检验
// 假设输入不是"0000000000": 则会判断0到front之间存在多余的正确LDPC码 (就是"000000000000")
{
sub_496AA0(v34, v31);
v18 = sub_407141(v34);
sub_493D30(v34);
}
else
{
v18 = -1;
}
sub_496AA0(v34, v33); // v33 = front字符串
v1 = sub_407141(v34);
v2 = v18 < v1; // 得到back < front
sub_493D30(v34);
if ( !v2 ) // 判断back < front
{
v10 = sub_481E90(&unk_5E32E0, "Emmmmmm......Wrong.");
sub_480730(v10, sub_481760);
exit(1LL);
}
for ( j = v18 + 1; ; ++j ) // 检查back和front之间是否存在正确的校验码,
{
sub_496AA0(v34, v33);
v4 = sub_407141(v34);
v5 = j < v4;
sub_493D30(v34);
if ( !v5 )
break;
sub_4070E1(v34, j);
sub_493D80(v32, v34);
sub_493D30(v34);
if ( (unsigned __int8)check(v32, v27, v28) ) // 存在说明漏了正确的LDPC码
{
v3 = sub_481E90(&unk_5E32E0, "Emmmmmm......Wrong.");
sub_480730(v3, sub_481760);
exit(1LL);
}
}
v6 = (unsigned int)(i + 12);
if ( v6 == len(v30) ) // 当读取到最后一个LDPC码的时候
{
sub_496AA0(v34, v33);
v20 = sub_407141(v34) + 1;
sub_493D30(v34);
while ( 1 )
{
v26 = v29;
sub_407782(v34, "111111111111", v29); // 最后一个LDPC码
v8 = sub_407141(v34);
v9 = v8 >= v20;
sub_493D30(v34);
sub_407A48(v29);
if ( !v9 )
break;
sub_4070E1(v34, v20);
sub_493D80(v32, v34);
sub_493D30(v34);
if ( (unsigned __int8)check(v32, v27, v28) ) // 循环检查与最后的LDPC码之间是否存在其他正确点, 如有则说明输入不完整
{
v7 = sub_481E90(&unk_5E32E0, "Emmmmmm......Wrong.");
sub_480730(v7, sub_481760);
exit(1LL);
}
++v20;
}
}
sub_493D60(v31, v33);
}
check函数, 可以查看参数内存, 判断容器类型, 具体逻辑如下
- a1: 字符串指针, 就是待检验的LDPC码
- sub_406D40(): 检查字符串, 并将字符串转换为矩阵
- sub_406E17(): 实现的是标准矩阵乘法, ⼀个提⽰点是开头的矩阵乘法原则判断.
- 矩阵都是vector容器 最后检查结果矩阵全0
__int64 __fastcall sub_407028(__int64 a1, __int64 a2, __int64 a3)
{
__int64 v3; // rax
__int64 v5; // rax
int i; // [rsp+2Ch] [rbp-14h]
sub_406D40(a1, a2);
sub_406E17(a2, &off_5E23B0, a3);
for ( i = 0; ; ++i )
{
v5 = sub_407822(a3, 0LL);
if ( i >= (unsigned __int64)sub_4078BC(v5) )
break;
v3 = sub_407822(a3, 0LL);
if ( *(_DWORD *)sub_40784C(v3, i) )
return 0LL;
}
return 1LL;
}
解题脚本
import numpy as np
N=12
K=6
H=np.array([
[1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1],
[1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0],
[0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1],
[0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0],
[1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0],
[0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1]])
#print(H)
b=[]
for i in range(2**N): #2^10 = 1024
a=format(i,'b') # 列举出所有可能的校验码
b.append("{:0>12s}".format(a))
v=np.zeros((2**N,N)) # 存储所有的校验码的元组
for i in range(2**N): # 从⼩到⼤
v[i]=b[i]
for j in range(N): # 存储校验码
v[i][j]=b[i][j] #v是0000000~1111111
w=np.zeros((1,N-K))
for i in range(2**N):
if np.all(np.dot(v[i],H.T)%2==w):
print(v[i])