论如何又收一个新年解谜红包 – 2021 篇

注意:题目中出现的链接需要替换后才能访问 redpacket.kaaass.net=>redpacket.kaaass.net/archived/2021/。

2021年的新年解谜红包也顺利结束了!所以按照惯例,我也写一篇官方的解题思路来分析分析今年的解谜红包。题目在这里:https://redpacket.kaaass.net/

今年的红包较往年相比有了不少变化,首先就是红包的形式。往年的红包其实没有明显的“关卡”概念,藏匿的信息也只有最终的红包码是固定的。过程中通常用URL串起各个关卡,但是由于提示并不明显,解谜时就很难找到方向。于是今年的解谜红包参考了@Berd的红包:有固定平台、发多个红包、用UUID作为通关凭据。总体而言我觉得这个转变是成功的,从参加规模也可以看出:第一关的页面访问人数达到了300。虽说这个数字统计的是不同IP数,会因为运营商变更等等而变化,但是比历年解谜红包参与人数都更多是确定的。

#1 点击就送

题目链接:https://redpacket.kaaass.net/problem/61805daf-b454-4ee5-849f-a4db30919c9f

题目只有一个链接和一些怪话。点开链接会自动跳转到B站视频【官方 MV】Never Gonna Give You Up – Rick Astley

但是题目其实没有骗你“你KAAAsS哥什么时候骗过你”,确实点击就送,但是页面跳转的太快了~当访问题目URL时,浏览器首先会请求对应地址,在获得301或302等等响应时才会跳转到新的页面。所以这关使用cURL请求即可

$ curl -i https://redpacket.kaaass.net/api/stage1
HTTP/1.1 301 Moved Permanently
Date: Sat, 20 Feb 2021 03:27:19 GMT
Server: Apache
X-Powered-By: PHP/7.2.30
Access-Control-Allow-Origin: https://redpacket.kaaass.net
Upgrade: h2
Connection: Upgrade, close
Location: https://www.bilibili.com/video/BV1GJ411x7h7?autoplay=true
Vary: Accept-Encoding
Transfer-Encoding: chunked
Content-Type: text/html; charset=UTF-8

<p style="background: black;">Your red packet code: b3efda95-acf5-4242-97a0-f27160c4eb2f</p>

One more thing…

  • 本关通过数:34/300。唔,虽然比预想的要少,不过确实这个解法并不是很好想,解出人数少也是正常
  • 似乎有不少在分析页面源代码的,不过其实页面就是个普通的React APP,那些命名奇特的JavaScript文件只是编译产物
  • 还有不少请求在尝试过关URL,虽然规则已经说了过关是UUID,但是似乎还有不少人不信邪,比如/problem/Never-Gonna-Give-You-Up/problem/beipianle(之前打成/problem/beripianle,233)……

#2 一块五毛八

题目地址:https://redpacket.kaaass.net/problem/b3efda95-acf5-4242-97a0-f27160c4eb2f

题目给出了一个AR扫福服务器地址和部分服务器源码。打开网页可以看到,开红包就是请求源码接口的入口。

符合现代扁平化设计语言,但保留了超越深度层级的元素(选择文件),这与Material Design的设计理念不谋而合

服务端部分源码包含了这些部分:

  • train.py:训练模型的代码
  • model.mdl:模型训练结果
  • image/*.png:训练模型用数据集
  • server.py:服务器API

没错,这是一个机器学习主题的关卡。

# 检查福
for fu in fu_example:
    if compare(img, fu) < 900:
        break
else:
    # 不与任意一个福相似
    return {"status": "ERR", "info": "上传的图片不是福!"}
# 打分
score = calc_score(model, img)
# 计算金额
price = 0
for score_limit, cur_price in price_table:
    if score < score_limit:
        price = cur_price
        break
# 返回
return {"status": "OK",
        "price": price,
        "red_packet": red_packet_code if price > 100 else None}

阅读提供的源码,可以发现对传入的图片接口首先判断是否是福,其次根据图片生成一个分数,最后根据分数计算出最终红包的金额。返回Flag的条件是红包金额>100。所以目标很明显,就是在保证能通过福检测的情况下拼出使分数尽可能高的图片。

查看训练代码可以发现,打分模型使用的是Logistic回归来进行二分类。其中给出的四个福作为类别0,super_secret_fu.png作为类别1,但是没有给出。考虑到Logistic回归的预测函数

f(x)=\sigma(W^Tx+b)

由于Sigmoid函数是单调的,所以权重W就是图片向量的权,只要根据权调整图片就能拉高分数。而且由于super_secret_fu.png参与了训练过程,因此参数W就包含了它的信息。

超级秘密福
可视化参数W

之后保证福判断能通过就行了,我的方法是对照其中一个相似一点的福(因为判定方法是与任意福diff值小于900即可),我选择的是fu4.png。之后用各种方法放大参数中较大的部分,减少参数中为负的部分。可以通过代码在其他福字的基础上逐像素缩放,也可以手工进行修改。

我个人尝试过两种方式,一种是在一个福的基础上,每次加权(权值就是每个位置的W绝对值)随机选择一个像素,若对应W值为正赋1,否则赋0。还有一种就是用拉格朗日对偶求约束最优问题,但是效果不是很好,迭代完全不收敛。等我找到了不那么玄学的方法再贴出脚本吧。

我自己的Payload

One more thing…

  • 本关通过数:29/34。这关的通过率比我预想的要多一点,我的预期是70%的样子
  • 本关的红包领取状况堪称一绝。一共66.66的5人拼手气红包,第一位领到了51.22。很难不让后来人生草
  • 参加过去年西湖论剑的朋友可能觉得这题很像当时的一道Misc。确实这关的设计参考了那题的形式,不过由于更换了算法,因此重点在于把隐藏信息放在参数里面,Payload的构造也更加有技巧性。所以我个人觉得和那题基本上是没什么关系了
  • 福字是随手搜的
  • 本来这题是没有前端的,但是感觉没前端就只暴露个接口对这关的主题来说是完全没必要的。所以就用记事本xjb乱写精心设计了一个入口页,可以说是巧夺血压天工了

#3 佛曰

题目地址:https://redpacket.kaaass.net/problem/58eafedb-9762-4f97-8878-5ce13fea57a3

相信精通车技的朋友看到这题应该不会不知道这种加密方式。没错,这就是与佛论禅加密,所以打开工具,点击解密,然后就解完了……

才怪啦。解出来的应该是如下内容:

你成功解开了佛说的禅言,但是天上却传来声音:佛所说的话很深奥,你似乎没能完全参悟……乎没能完全参悟……没能完全参悟……能完全参悟……完全参悟……全参悟……参悟……悟……

提示:佛所言似乎有点冗余。但微言大义,要是将冗余所用的字眼排序后,不知能否得到佛的真谛的基……

没错,并没有得到预期的答案。但是根据提示,与佛论禅编码是有“冗余”的,而且是“字眼”。所以自然想到应该去查看与佛论禅的源码。GitHub上有多个与佛论禅加密的实现,这里贴一个原作者的

public static string Encode(string OriginalString)
{
    Random rand = new Random();
    byte[] originalBuffer = Encoding.Unicode.GetBytes(OriginalString);
    byte[] encodeBuffer = AES.AESEncrypt(originalBuffer, key, vector);
    string TudouString = String.Empty;
    for (int i = 0; i < encodeBuffer.Length; i++)
    {
        if (encodeBuffer[i] >= 0x80)
        {
            TudouString += TudouKeyWord[rand.Next(TudouKeyWord.Length)];
            TudouString += TudouChar[encodeBuffer[i] ^ 0x80];
        }
        else
        {
            TudouString += TudouChar[encodeBuffer[i]];
        }
    }

    return TudouString;
}

可以看到,在遇到大于0x80的字符时,程序会随机用一个TudouKeyWord中的字作为标志。这个随机正是我们要找的冗余信息,也给隐藏信息提供了可能。

那之后怎么做呢?还是看提示。根据提示,将“冗余字眼”“排序后”就能得到“基”。排序很简单,标志的字符是['冥', '奢', '梵', '呐', '俱', '哆', '怯', '諳', '罰', '侄', '缽', '皤'],排序后就是['侄', '俱', '冥', '呐', '哆', '奢', '怯', '梵', '皤', '缽', '罰', '諳']。但问题是,所谓“基”是什么?

其实这个“基”就是Base(Base N那个Base),换言之进制。而由于“冗余字眼”共有12个,因此这里指的就是12进制。于是提取原文本中的“冗余字眼”,按照12进制解码即可得到隐藏的信息。可写Python脚本如下

import re
from Crypto.Util.number import long_to_bytes

BYTEMARK = ['冥', '奢', '梵', '呐', '俱', '哆', '怯', '諳', '罰', '侄', '缽', '皤']

text = '佛曰:侄依咒薩俱尼奢那朋怯麼。奢三呐阿。冥藝三冥姪諳娑知般竟遮奢一是哆彌俱夜伊呐漫藝哆盡皤明侄心皤老耶哆三俱遮哆迦怯藝者怯瑟怯娑曳哆一輸耨俱除參苦缽得呐娑怯醯梵多地怯呼密集侄真亦羅死麼諳薩滅梵爍缽盧俱那呐瑟朋孕梵知迦爍罰曳槃侄數咒皤楞諳滅大室涅冥夷梵上伽奢故豆佛俱蘇離罰吉怯室缽楞哆姪盧缽悉缽盧冥諦羯世提姪缽殿哆逝諸上罰謹喝礙地怯耨想皤提大侄智亦冥那曰侄迦以亦迦皤亦伽梵者冥參恐爍缽所俱羯室阿若呐佛梵僧參謹俱伊若怯盧。都罰彌梵度侄喝有實侄菩奢都朋栗室哆勝那呐漫諸姪呐故度謹若梵所梵醯藐滅諳蒙缽夢麼大俱麼謹伽瑟遠上不怯佛哆顛訶奢倒侄礙藝提栗喝迦怯沙世梵礙勝明諳勝大沙缽有冥摩諦俱曳南若究侄室除得心阿皤菩有罰蒙怯怛俱豆室缽槃等罰等缽吉吉缽等侄死羯老冥死皤羯離梵以諳滅死梵曳諳地阿缽三實礙皤波梵曰諳礙諦至勝皤上奢真薩侄伊羅特缽喝梵波梵舍梵夢若神冥栗隸佛耨故俱無心娑侄大僧皤夷遠地呐得道侄羅爍諳他集呼實呐涅僧奢實諳悉'
hid = re.findall('(侄|俱|冥|呐|哆|奢|怯|梵|皤|缽|罰|諳)', text)
table = sorted(BYTEMARK)
nums = [table.index(ch) for ch in hid]
num = sum([a*(12**b) for a,b in zip(nums, range(len(nums)-1, -1, -1))])
print(long_to_bytes(num).decode())
我佛了

One more thing…

  • 本关通过数:0/29。无人解出此题确实很出乎我的意料,因为2小时就有人完成前2题,因此我一度认为今年的红包撑不过半天。看来提示确实很隐晦
  • 其实我至少看到了3人有正确的思路,但是不知道为什么他们最后没做出来。似乎都卡在12进制解码后结果不对,我还以为题目部署的时候出了问题
  • 提示里的那段“肥音”完全是为了凑字数。因为出题的时候发现要隐藏的数据有128位草稿雅言曰:“草,有128位啊,那得论成多长”,所以就嗯凑了一大堆字数,最后凑出了一个有129个标志的文本
  • 本来是想出与熊论道的,结果实在是搜不到源码

最后

新年解谜红包的第4年就是这样啦,各位觉得怎样呢?玩的开心嘛?今年出题的时候主要考虑到了去年的教训,因此从规则、题目格式、答案格式等多个方面完全革新了往年解谜红包的形式。虽然最后还是没有人成功收到大红包,但是因为前面关卡也有红包,所以希望整体过程不会太枯燥。解谜红包主要还是图一乐,今后我也会本着这个态度准备。此外还有个遗憾是由于今年平台复杂了许多,因此也没有时间准备惯例的隐藏红包了,明年它会回来吗?

最后给大家拜个晚年,祝大家新年快乐!

分享到

KAAAsS

喜欢二次元的程序员,喜欢发发教程,或者偶尔开坑。(←然而并不打算填)

相关日志

  1. 没有图片
  2. 没有图片
  3. 没有图片
  4. 没有图片
  5. 没有图片

评论

还没有评论。

在此评论中不能使用 HTML 标签。