前言
前段时间感觉忙的要死,最近终于算是有了一点空余时间,正好刚打完的 HICTF2024 有一点有意思的点子,正好记录一下
共四题
- strhash
- poly
- supercomputer
- potter
解出来前三题
strhash
gen.c
1 |
|
res.txt
1 | 1804289383 1190008377 |
记得之前做过一道哈希的密码题,当时似乎是用双向搜索给爆出来的,这次显然不一样
学长讲了才看出来((
将 hash 用多项式表示 \[ hash(s) = \text{0x114514} \cdot 233^{n} + s_0 \cdot 233^{n-1} + s_1 \cdot 233^{n-2} + \cdots + s_{n-1}\cdot233^0 \pmod {M_i} \] 这样就能看出来,是个 CRT 问题
1 | from sympy.ntheory.modular import crt |
poly
题目附件
gen.py
1 | from secret import flag |
out.txt
1 | [125, 2996, 452635961261861, 5640012588785953252742, 663780389523502221593026277, 5845169438213803974237048659360, 9887686914794970408229246877837261, 5337690699007613492950100661443838386, 1246816454677070240427082753828238163437, 153460164802093351535746693021820792205932, 11388634061367409705082975835211766201321045, 561110174338870387179778825126282853603302526, 19707795929255451006208863819787576643829770261, 520881194974728297519631110000138317264353793432, 10805092812709807233540502091207064827348602091837, 181889014548692631324457891650518586246753508973930, 2552507382661657536880498041728210235325197081880541, 30526683751460091693383854169085501144119622770115236, 316865109417499484669699487403444039480141224348233477, 2898653848895952221370426792142177095594525094522186742, 23673843301340973009664515191259831073817022946431821765, 174539049232138773732842515032017313916154669096708343056, 1172733551080318790000994789603513594241654018460818966061, 7240523617671723109603340026802418603641003659017975484322, 41374103585680599659105199547442215866047005195866840568397, 220199857217101214349082389841043173220339718159981048158300, 1097626867177210522501108725164094179734775217327646541376821, 5149764870836833092990530866127123407661712365054932690325486, 22841615951981693536493860837124386678939843016655243827081717, 96157610067108350424366847753813154389844296991097936198764552, 385562422203859626216008093491644153104891734674128504106992285, 1477221645609927756179115371650629543504813846434947526324458586, 5423633936317940537039114323538499637949438626254731152203731261, 19132290548239069397038234653722740430029897400022438133844971412, 64999757417753858733496171026572335062387390211817558640690377957, 213142909661667580939007866138222724809887474172878584374436922470, 675944013875172159995957069622450735032975415629563730466165480101, 2076951592374410038709677191998396913746258932819180055450081131136, 6193688888445369259889561497114942414360276754293622820640778134157, 17953715518690549873845638320302721807364019714363222114478505961362, 50659931725962874327370012361788145918161221262881139309840223762605, 139334089414772808841572383856822545223058318039478733568743937623116] |
仔细分析 map
, reduce
和
lambda
,可以得到关系: \[
\mathbf{M} = \begin{bmatrix}
0 & \cdots & 0 & 0 & 1\\
1^{n-1} & \cdots & 1^{2} & 1^{1} & 1\\
2^{n-1} & \cdots & 2^{2} & 2^{1} & 1\\
\vdots & \ddots & \vdots & \vdots & \vdots\\
(n-1)^{n-1} & \cdots & (n-1)^{2} & (n-1)^{1} & 1
\end{bmatrix}_{n \times n}
\] \[
\mathbf{M} \mathbf{u} = \mathbf{v}
\]
其中 \(\mathbf{M}\)
为系数矩阵,\(\mathbf{u}\) 为编码的
flag
,\(\mathbf{v}\)
为输出的密文,很容易写出解题脚本:
1 | M = matrix(42) |
不过,比赛期间并非是这样出的
当时想到 sage 可以用 CAS 计算,那为何不试试让他直接解方程组呢?
于是有了下面的脚本,都不需要手撕原来的 map
,
reduce
和 lambda
1 | from functools import reduce |
虽然直接列线性方程组难度不高,但是这个很有趣,并且直接启发我做出了下一题
supercomputer
1 | def f(x): |
这就纯纯算法题(?
一开始把已知的一串十六进制改成二进制观察规律,找到了个别 bit 至始至终没有发生改变,但是其他的没有任何思路
然后基于上一题的灵感,想到 f(x)
不就是函数式编程中所谓的纯函数吗?
那么我们可以把这个 函数 方法
f(x)
改写为等效的 函数
映射 \(f: ULL \to
ULL\)
即一个在 64bit
的非负整数 (unsigned long long)
域内的映射
那么只要找到这个改写方法,这样就可以从数学角度研究该 函数 映射 的可结合性
我的方法是将这个 64bit 的 ULL
拆成 64 个
PolynomialRing(Zmod(2))
1 | Bool.<x0,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33,x34,x35,x36,x37,x38,x39,x40,x41,x42,x43,x44,x45,x46,x47,x48,x49,x50,x51,x52,x53,x54,x55,x56,x57,x58,x59,x60,x61,x62,x63> = PolynomialRing(Zmod(2)) |
我确实不知道怎么直接申请多项式环上的 64
个变量了,丑点就丑点吧(逃
拆开后很方便位移
我们约定 x0
表示最低位,x63
表示最高位
那么例如 \(6_{10}\) 就可以写作
[x0, x1, x2, x3, x4, x5, x6, x7, ...] = [0, 1, 1, 0, 0, 0, 0, 0, ...]
1 | def hex2bin(a): |
数学左移 \(a\) 位就是这个数组向 后/右 移动 \(a\) 位, 数学右移 \(a\) 位就是这个数组向 前/左 移动 \(a\) 位
但是由于每次左右移后都有一个按位与逻辑,溢出的位等效于被直接丢弃,而非从另一端循环或扩展数组长度
1 | def left(x, a): |
实现按位与逻辑和按位或逻辑,也就是转成 \(\mathbb{F}_2[x_0, x_1, \cdots, x_{63}]\) 下的 \(+\) 和 \(\times\)
底层全部交给 sage 的 CAS 系统,非常舒适
1 | def and_(x, a): |
然后就可以组合函数以实现 f()
和 calc()
1 | from copy import deepcopy |
上面 X
是所有元的向量,也可以是一个恒等函数
F = f(X)
是进行一次 \(f\) 的映射
简单测验不难发现并证明: \[ (f^a \circ f^b)(\mathbf{x}) = f^b (f^a(\mathbf{x})) = f^{b+a}(\mathbf{x}) = f^{a+b}(\mathbf{x}) = f^a (f^b(\mathbf{x})) = (f^b \circ f^a)(\mathbf{x}) \] 即纯函数 \(f^n\) 是可结合的
然后借用了快速幂的思想,也是尝试利用可结合性来优化,将复杂度由 \(O(n)\) 降低至 \(O(\log n)\)
1 | def compose(x, y): |
剩下就可以通过
1 | def compute(F, a): |
来带值计算
综上,整理脚本如下:
1 | # 将 unsigned long long 数转化为 $\mathbb{F}_2[x_0, x_1, \cdots, x_{63}]$ |
后记
前段时间网鼎杯半决赛,当时打算晚上就整理一下的,结果各种事一直拖了这么久((
接下来几周要好好整理博客了
__END__