Suffix suffices, right?
Hint for beginners: Can you see the two assert functions in the attached python script? These are describing the preconditions to be met. These statements read the secret content of the file named flag.txt and check if they are the correct flag or not. Your task is to find a string that satisfies these conditions. Actually, only one golden value can satisfy these conditions, so if you do it correctly, you’ll get the correct flag then.
flag.txt 文件满足以下条件
assert(len(open('flag.txt', 'rb').read()) <= 50)
assert(str(int.from_bytes(open('flag.txt', 'rb').read(), byteorder='big') << 10000).endswith('1002773875431658367671665822006771085816631054109509173556585546508965236428620487083647585179992085437922318783218149808537210712780660412301729655917441546549321914516504576'))
这样的 flag 有且只有一个,我们的任务就是找出符合上述条件的 flag
这道题的关键点有二
第一个条件说,flag 长度小于 50,结合第二个条件,要将 flag 转换成数字,那么第一表达式相当于(因为一个字符能够表示的最大数值是 2 ** 8 - 1
)
flag <= 2 ** (8 * 50) - 1
第二个条件,int.from_bytes,是python3 的函数,是将字符串按照 ascii 码转换成数字,而不是传统意义上的 int(),注意区分。转换成数值之后,左移 10000,又转换成字符型的字符串,取末尾值进行对比,相当于
flag * (2 ** 10000) % (10 ** 175)= 1002773875431658367671665822006771085816631054109509173556585546508965236428620487083647585179992085437922318783218149808537210712780660412301729655917441546549321914516504576
不难看出,上述表达式其实是一个数论中的同余,设 m = 10 ** 175
,余数 r = 10027...
,那么有
f l a g × 2 10000 ≡ r ( m o d m ) flag \times 2^{10000} \equiv r (\bmod m) flag×210000≡r(modm)
同余运算具有同乘性,即两边同乘一个数,表达式仍然成立
f l a g ≡ 2 − 10000 r ( m o d m ) flag \equiv 2^{-10000} r (\bmod m) flag≡2−10000r(modm)
尝试计算 2 − 10000 r ( m o d m ) 2^{-10000} r (\bmod m) 2−10000r(modm)。这里有个小窍门, 2 − 10000 2^{-10000} 2−10000 明显过小,double精度不够,会有溢出(你可以用 python 计算 2 10000 2^{10000} 210000,但是没有办法计算 2 − 10000 2^{-10000} 2−10000 ),因此,计算此类表达式,我们要利用一个数论中的一个概念——逆元 (数论中的倒数)
什么是逆元?其实刚刚已经说明了为什么需要逆元,如果
a b ( m o d n ) = 1 ab (\bmod \ n) = 1 ab(mod n)=1
即
a b ≡ 1 ( m o d n ) a b \equiv 1 (\bmod \ n) ab≡1(mod n)
那么,b 称之为 a 对模数 n 的逆元,整数 a 对模数 n 存在逆元的充要条件是,a 与 n 互素。若 a 对 n 的逆元存在,在模数 n 下的除法,可以用对应模数的逆元的乘法来构成,防止溢出。
推论:若 a b ( m o d n ) = 1 ab(\bmod \ n) = 1 ab(mod n)=1,则 ( c a ) ( m o d n ) = c b ( m o d n ) ({c \over a})(\bmod \ n) = cb(\bmod \ n) (ac)(mod n)=cb(mod n)
证明:
( c a ) ( m o d n ) = c a × 1 ( m o d n ) = c a × ( a b ( m o d n ) ) ( m o d n ) = ( c a × a ( m o d n ) ) b ( m o d n ) = c b ( m o d n ) ({c \over a})(\bmod \ n) = {c \over a} \times 1 (\bmod \ n) \\ = {c \over a} \times (ab (\bmod \ n))(\bmod \ n) \\ = ( {c \over a} \times a (\bmod \ n)) b (\bmod \ n) \\ = cb (\bmod \ n) (ac)(mod n)=ac×1(mod n)=ac×(ab(mod n))(mod n)=(ac×a(mod n))b(mod n)=cb(mod n)
上述过程先后用到了模运算的公式 6 和公式 3
回到经过转换的表达式,可以通过逆元,计算以下表达式
2 − 10000 r ( m o d m ) 2^{-10000} r (\bmod m) 2−10000r(modm)
检查 2 − 10000 2^{-10000} 2−10000关于模 m m m 是否有逆元,这里要用到一个库 gmpy2,针对数论中的快速计算(大数分解,代数数论,椭圆曲线…)而设计。详细可参考 Linux 环境下安装和使用 gmpy2
>>> gmpy2.gcd(2 ** 10000, 1002773875431658367671665822006771085816631054109509173556585546508965236428620487083647585179992085437922318783218149808537210712780660412301729655917441546549321914516504576)
mpz(47890485652059026823698344598447161988085597568237568)
由此可见, 2 − 10000 2^{-10000} 2−10000 与 m m m 并不互质,不满足具有逆元的条件。那么目前的想法是,进行化简,对模数 m m m 进行因式分解
模数 m m m 因式分解
推荐使用 factordb,在线进行大数分解,也可以使用 factordb 工具
pip install factordb-pycli -i https://pypi.tuna.tsinghua.edu.cn/simple
factordb 1002773875431658367671665822006771085816631054109509173556585546508965236428620487083647585179992085437922318783218149808537210712780660412301729655917441546549321914516504576
r = 2 ** 175 * 61 * 343260582281778161791406870624542263498245279735080935957616380366178857319377077244490078139487
化简 f l a g × 2 10000 ≡ r ( m o d m ) flag \times 2^{10000} \equiv r (\bmod m) flag×210000≡r(modm)
f l a g × 2 10000 ≡ 2 175 y ( m o d m ) f l a g × 2 10000 = x 1 0 175 + 2 175 y f l a g × 2 9825 = x 5 175 + y flag \times 2^{10000} \equiv 2^{175}y (\bmod m)\\ flag \times 2^{10000} = x 10^{175}+ 2^{175}y \\ flag \times 2^{9825} = x5^{175} + y flag×210000≡2175y(modm)flag×210000=x10175+2175yflag×29825=x5175+y
y = r ÷ 2 175 y=r \div 2^{175} y=r÷2175, x x x 为除数,那么有
f l a g × 2 9825 m o d 5 175 = y f l a g ≡ 2 − 9825 y m o d 5 175 flag \times 2^{9825} \ \bmod 5^{175} = y \\ flag \equiv 2^{-9825}y \ \bmod 5^{175} flag×29825 mod5175=yflag≡2−9825y mod5175
上面就是化简的最终表达式,已经将模数 m 进行了分解,现在再来计算值,同样的,判断 2 9825 2^{9825} 29825 关于模数 5 175 5^{175} 5175 是否存在逆元
>>> gmpy2.gcd(2**9875, 5**175)
mpz(1)
两者最大公约数为 1,互质,所以满足条件!逆元使用 gmpy2 模块是可以直接计算出来的
flag = x * (5 ** 175) + (y * gmpy2.invert((2 ** 9825), (5 ** 175))) % (5 ** 175)
y y y 是确定的值, x x x 作为除数,可以是 0、1、2…,但要满足 flag 的第一个条件。备注:% 优先级是大于加减乘除的。
笔者给出的解法是按照正常解题思维顺序进行的,这样做的好处是能够理解解题的每一个过程,缺点是较为繁琐,官网给出的解法更多的是按照一次同余式,或者结合欧拉定理,较为抽象,而且难以理解其中的原理,但是根据公式可能更为简单。作为 TSG CTF 2020 密码学的入门题,本题实际上并没有那么简单,需要读者具有一定的数论和离散数学的基础,看清题目的条件也较为关键。