HackThisSite - Extended Basic Mission 11
Challenge
关卡给出一段 Windows
批处理(batch)写的认证脚本,要求输入一个能让脚本认证通过的密码。脚本用
SET /P
读取输入,然后逐字符与字母表比对,命中时把一个累乘变量乘以对应的素数:
1 | @ECHO OFF |
Solution
脚本语义
PRIME 与 CHARS
都是等宽分隔的字符串:PRIME 里每个素数被右填充到 3
个字符宽,CHARS 里每个字母后面也有两个空格。内层循环的
CURRENTPOSITION 取 0,3,6,…,75 共 26
个位置,!PRIME:~%CURRENTPOSITION%,3!
正好截出对应的一个素数,!CHARS:~%CURRENTPOSITION%,1! 截出
a 到 z。
外层对输入里每个字符位置 CHARACTERPOSITION 走一遍这 26
个槽位。IF /I
是大小写不敏感的,所以一个大写字母也会命中。命中的后果只有一个:SET /A PASSWORDVALUE*=!PRIME:~…,3!,也就是把累乘值乘上该字母对应的那个素数。因此
- 输入里每个字符最多贡献一次乘法,乘的是
{2,3,5,…,101}中的某一个素数; - 非字母字符不贡献任何乘法;
- 重复同一个字母就是重复乘同一个素数。
为什么不能按原意凑出目标
关卡的门槛是
1 | IF NOT %PASSWORDVALUE%==1065435274 GOTO :ACCESSDENIED |
把目标值分解:
1 | 1065435274 = 2 × 6827 × 78031 |
6827 与 78031 都是素数,且都大于
101,不在脚本能乘到的素数表里。换句话说,任何由列表内素数组成的乘积都不可能精确等于
1065435274。如果只盯着凑乘积这一个方向,该关无法通过。
32 位溢出
SET /A 的整数运算是 32 位有符号
的,累乘过程中超过 2**31
会回环绕。于是真正需要满足的条件是同余:
1 | ∏(选中的素数) ≡ 1065435274 (mod 2**32) |
因为乘法在模 2**32
下可结合,最终值只取决于所选素数构成的多重集合(与输入顺序无关)。
再看目标值的 2-adic
赋值:1065435274 = 2 × 532717637,532717637
是奇数。2**32 以内的偶数按 2 的幂次分层,目标只含一个因子
2,所以素数 2(字母
a)必须恰好出现一次,其余部分是一个奇数乘积,需要满足
1 | X ≡ 532717637 (mod 2**32) |
Search
只剩 25 个奇素数(3 到
101)可选,每个的多重度未知。用中间相遇(meet-in-the-middle):把奇素数分成两半分别枚举各自的多重集合乘积,在一半的哈希表里查另一半需要的补数(对奇数取模逆),取总乘法次数最少的组合。每个素数的重数上限取
2 就足够:25 个奇素数、每个重数取 0..2,组合数在
3**25 量级,远多于模数
2**32,解很多而长度很小。
完整求解器:
1 | from itertools import product |
Result
搜索得到 9 个字符的最短解:
1 | 密码:aghilmort |
乘积在 32 位下回绕:
1 | 2×17×19×23×37×41×47×61×71 = 4588090507402 |
正好落在门槛值上。
Verify
把关卡脚本原样复制成 replica.bat(去掉末尾的
PAUSE),在 wine 下喂入输入:
1 | ### input=aghilmort -> AUTHENTICATED |
aghilmort 触发
You have been authenticated. Welcome aboard!,其它输入落到
Access denied!。
随后带 Referer: <关卡页> 提交到
POST /missions/extbasic/template.php:响应里出现指向下一关
/missions/playit/extbasic/12 的 go-on 链接,账号 profile 的
Extbasic: 行也新增了
(11)。两个独立判据同时成立。
Key points
批处理 SET /A 是 32
位有符号运算,累乘不检查溢出;一个乘法哈希只要允许回绕,就不能靠算术基本定理保证唯一可逆。用模
2**32 的同余条件反推多重集合,比精确因式分解更直接。