voidprint_question(question *queue, int head, int tail) { printf("Question Set:\n"); for (int i = head; i != tail; i = (i + 1) % SHOW_SIZE) { printf("%lld ? %lld\n", queue[i].a, queue[i].b); } printf("\nNow input your answers (like '><<<>>=<<>'), or input 'submit' to submit your score:\n"); }
voidmaster() { char ans[0x1000]; question queue[SHOW_SIZE]; int head = 0, tail = 0; printf("%s", master_banner); while (1) { while ((tail + 1) % SHOW_SIZE != head) { push_question(queue, &tail); } print_question(queue, head, tail); memset(ans, 0, 0x1000); read(0, ans, 0x1000); printf("\nchecking answers...\n"); sleep(1); for (int i = 0; ans[i] || head == tail; i++) { if (!strncmp(ans + i, "submit", 6)) { printf("\nsubmitting...\n"); return; } if (ans[i] != '<' && ans[i] != '>' && ans[i] != '=') { continue; } if (ans[i] == queue[head].ans) { score++; } else { score--; } pop_question(queue, &head); push_question(queue, &tail); } printf("\nOK! Now your score is %lld\n\n", score); } }
中 A 口算主要考察伪随机数生成,代码原理是用循环队列显示题目,但是每批改一题就生成下一题,所以能读多少答案就能改多少题,一口气发 4096 个答案就不会被 sleep(1) 卡住了。然后问题是怎么猜到没显示的题目。所以只要得到随机数种子就好了。此时你可能发现了,代码里的随机数种子用的是 time(0),也就是当前(以秒为单位)的时间戳,那理论上是非常好得到的,在本地也能非常轻松的得到验证。
但是跟远程交互的时候发现寄了。怎么会是呢?
在大 A 口算里我加了个提示,暗示远程的时间是不一样的,在很久很久之前。(事实上因为容器的原因,改时间的操作是在程序里把变量减掉然后把下发的程序 patch 掉实现的……)。为什么中 A 不给提示呢?因为理论上你可以轻松地反爆出种子然后发现这点……虽然发现的人也不多。
中 A 口算里生成的题目是可以算出每次 rand() 函数的结果的,而 libc 的随机数生成器是 LCG 实现的,破解肥肠简单(可以出门左转看看 X 老师出的困难版),或者实在不想学数学,可以直接枚举所有种子,没几个小时也能爆出来。然后爆出种子就好办了。多试几次应该能发现远程时间与现在的偏移是不变的(事实上恰好是 14 年前,那时候 GDK 是个纯种小学生),然后就更好办了……如果没猜出来,至少也可以靠 LCG 先做两套题然后解出种子……总之办法不止一种,总有一种适合你(x)。