文章目录[隐藏]
注意:题目中出现的链接需要替换后才能访问redpacket.kaaass.net=>redpacket.kaaass.net/archived/2019/。
嘛,和去年一样,今年我又发了个新年解谜红包(不知道去年红包的同学可以康:论如何正确收一个新年解谜红包)。这次的题目也非常简洁,只有一张图片(右键另存为下载)。今年的红包比起去年的流程更短,但是每一关的难度都不小(尤其是Stage1)。不过,由于今年红包未领将会续两次,所以最长解谜时间可以是3天。那么接下来就是解谜全程啦~
Stage1 – 颜文字图片
Stage1可以说是最难的,但是提示也是最多的。首先看图。
很多人一眼就看出,这个表情的眼睛是二维码的定位点。没错,而且这是一个非常关键的提示。而剩下的提示只能从文件本身入手了。随便丢进一个二进制文件查看器,在文件尾部发现另一个提示。
在IEND块后有一个单词,“TALLER”。除此之外,还有一个比较隐蔽的提示就是,部分图片浏览器打不开这张图片。于是很多人就此卡关了……其实解题的方向很明确,就是PNG文件结构(后来我也补了这个hint)。
图片浏览器打不开的原因,其实是图片的第一个IHDR CHUNK的CRC校验失败了。
IHDR CHUNK的数据段存放的其实是PNG图片的一些基本信息(长、宽、位深度等等),而结合“TALLER”的提示,很容易能想到是指图片的宽度,于是随便修改一个大一点的数值,然后一点点调整,就能看到之后的内容了。
可以看到,这是一个支离破碎的二维码。所以依照定位点的提示,简单的拼起来。
于是你就得到一个扫不出的二维码23333。部分dalao看到扫不出来就放弃了这个思路,转而去思考如何用上另一只眼睛,于是就跑偏了。(然而无论从中间的内容还是定位点间隔来看,另一只眼睛都没办法组成一个二维码)
还是要在图上找答案。既然可以确定是二维码,那就来找一下定位图形好了。
可以看到,定位图形都是黑白的。而剩下区域的黑白点十分稀少,所以排除彩色块是杂色的可能。于是真正的答案就呼之欲出了——这是三通道的三个二维码结合在一起!理由很简单,重复的区域(定位图形)是黑白的。于是打开ps,把红、绿、蓝三个通道的二维码分开~
分别扫描这三个二维码,得到:
红: YW5ueWFvY3l1dS8=
绿: cmVkcGFja2V0Lmth
蓝: YWFzcy5uZXQvMjB0
没错,这是base64。解码得到:“annyaocyuu/”、“redpacket.ka”、“aass.net/20t”。于是拼接得到下一关的链接:redpacket.kaaass.net/20tannyaocyuu/。
有趣的事情
- 之所以把二维码拆开来,是因为想不到不拆的话怎么隐藏其余部分
- IEND之后实际上增加了一个空字节,之后才是TALLER。由于IEND的CRC校验的最后一字节是82,所以增加了一个空字节以保证字母T能正常显示
- 去年最后一关是图片(文件解谜),而今年第一关就是图片的原因是,去年由于过早暴露关卡地址,导致奇怪的请求较多,日志分析起来很麻烦
Stage2 – 寻找共鸣者
一打开页面,先闪过了一些字,然后变成了类似“Not you, 37ece3d5410533901a1a40590f46d9a3.”的内容。每次刷新时后面的内容都会变。查看源代码可以看到页面使用了js脚本index.min.js,而且原本内容是:
Man, you should have javascript-supporting!
KAAAsS留:这次出太难了,红包就直接丢在这吧b547de608dd0f2bbd61919a854510263。如果继续玩的话,原来的最后一关我发了个100的。
这个红包过期且被领了,故只续其它红包啦~
另外,注意到网页的标题是“Find the ECHOES!”。
简单版红包
由于第一个访问该页面的请求已经是题目发出的次日,且离续红包只有半小时了。于是,考虑到第一关已经很不简单了,我就临时把原定最后的红包发在这里,且最后一关的红包改发100的。不过这个红包也不是随便发的,也有一个简单的谜题。
b547de608dd0f2bbd61919a854510263看着像md5,实际上也就是红包码的md5。当然我也是不可能直接让破一个md5,那可太鬼畜了。真正的红包码藏在COOKIE里。而且每一次请求其实都会返回Set-Cookie头,所以要怪只能怪没用Postman之类的工具啦。
正常流程
打开调试工具发现,调试工具没办法正常使用。只要一打开调试工具,就会自动跳转到断点。
这就意味着没办法进行断点调试和ajax请求查看之类的操作的。事实上,这一步就是为了掩盖ajax请求。虽然可以用诸如油猴脚本的方式屏蔽反调试(具体见“反调试浅析”节),但是事实上也可以直接对js脚本进行静态分析。用自带调试工具format一下。
这种魔性的十六进制命名其实看着还是很头大的,而且format后文件有整整383行,从头阅读十分麻烦。于是,要从其他地方找突破口。这里有几种可能的方式。
- 如果通过抓包工具或其他手段发现了ajax请求,那么可以在代码中直接搜索XMLHttpRequest。只出现于340行。
- 既然每次刷新页面,那串字符内容发生了变化,那么,那串字符要么是ajax请求来了,要么就是随机生成的。从ajax请求的分析可以看出,请求的url的参数是一串随机的字符串(比如url:redpacket.kaaass.net/20tannyaocyuu/ronn.php?suttann=815abb03af71b),于是可以断定js中存在随机生成代码。于是搜索Math或random,找到316行的函数_0x242600。
而且页面逻辑那么简单,肯定没有300来行,所以可以判断真正的逻辑就是293-353行。简单说明下逻辑:
- 调用_0xf07168函数生成请求参数。
- 使用_0x4e01df发送ajax请求。
- 将返回的json中,字段“m”的字符串,与随机字符串的md5进行比较。若相同,显示“I think it’s you.”。若不同,显示“Not you, …….”。
于是我们来分析下ajax的返回。比如对于http://redpacket.kaaass.net/20tannyaocyuu/ronn.php?suttann=815abb03af71b,将返回:
{ "t": "1549800860", "i": "415c90a1be0659c317b6fc13f4", "m": "280e4d25e5e203e5d5a1125b88612a27", "e": "a67e6dd453bd1fa8ded097121b481308" }
不难发现
- t是当前时间戳
- i是一个字符串,内容和传入的参数suttann有关
- m是i的md5值
- e是i的md5值
所以,实际上js部分的逻辑可以看作是在判断随机字符串(suttann)和i是否相同。另外,还有个梗提示。网页标题“Find the ECHOES!”,ECHOES是《不吉波普不笑》的共鸣者,只会重复别人说的话,ECHOES是回声,即返回和输入相同。
所以我们要找到这样一个“不动点”字符串。事实上,suttann是十六进制数。线索其实不少,结合suttann的生成代码:
// 内容是"0123456789abcdef" let _0x18f6e3 = _0x4a1f('0x14'); // 生成请求参数 let _0xf07168 = ()=>{ let _0x1f40ba = {} , _0x2f85d0 = '' , _0x387ebf = _0x4a1f('0x37'); // 内容是"hexof13length" for (let _0x2a8809 in _0x387ebf) { _0x2f85d0 += _0x18f6e3[_0x242600(0x0, 0xf)]; // _0x242600是生成随机数 } _0x1f40ba[_0x4a1f('0x38')] = _0x2f85d0; // _0x4a1f('0x38') => "suttann" return _0x1f40ba; }
- 生成字符串每一位只能是0123456789abcdef
- 生成时的foreach循环遍历的字符串为hexof13length
- 注意到i的长度和suttann有关,且suttann越大i越长。
理所当然,i也可以猜测是数字。于是请求多组,转为十进制不难发现,
i = suttann^{2} + suttann
所以很简单,对于自然数suttann,唯一使suttann=i的解既是0。于是带上0000000000000请求,发现返回结果多了一个seq。
{ "t": "1549802094", "i": "0000000000000", "m": "4aad0d9ff11812ebdd5e376fdbef6222", "e": "0f251631cda7e0d6fc2b5a3c75bd07ca", "seq": "4836331" }
多次请求发现,seq的内容随时间变化。而且仔细看tag,连起来是“time seq”,即“时间序列”。于是按时间顺序列出seq的内容:
4836282
4836283
4836286
4836291
4836298
redpacket.kaaass.net/kibounohana/?passwd=
4836318
4836331……(循环)
其实就是一个很简单的数列找规律,得到下一关链接redpacket.kaaass.net/kibounohana/?passwd=4836307。
有趣的事情
- 简单红包关,log有二三十条带b547de608dd0f2bbd61919a854510263的请求
- 原先返回的i是固定13位显示(长于13位就截断),然而由于随机字符串的值都很大,加上十六进制数的提示很隐蔽,所以难度特别高。于是我就改成返回完整数字了
- stage2总共收到2679次请求,来自19个不同ip地址。其中,光是0000000000000的就有484次
- 当然少不了比如suttann=2333333333333这类的请求啦
- 有位dalao劫持了ajax,强行返回了一个一样的md5,然后卡关了……???
- 混淆使用的是javascript-obfuscator/javascript-obfuscator,强烈推荐
反调试浅析
其实这种反调试是混淆工具javascript-obfuscator/javascript-obfuscator自带的功能之一。代码大致如下:
(function() { (function a() { try { (function b(i) { if (('' + (i / i)).length !== 1 || i % 20 === 0) { (function() {}).constructor('debugger')() } else { debugger } b(++i) })(0) } catch (e) { setTimeout(a, 5000) } })() })();
主要是利用debugger触发断点调试。最简单的方法就是用油猴脚本替换掉window.setTimeout函数。
Stage3 – Partial Content
返回内容如下:
这不是Brainfuck嘛,随手找个在线编译器运行一下得到:60014489。正好八位!然而领取失败???Naive了旁友!注意到HTTP状态码是
这Brainfuck里还夹带了私货!(还有一个提示就是Brainfuck的缩进)有经验的老司机应该看出来了。没错,这一段Brainfuck里穿插了Whitespace,另一个魔性的语言。
但是把这一段东西丢到Whitespace编译器,却没有任何的反应。其实这段Whitespace还需要一个输入,那就是Brainfuck编码的60014489。输入之后就返回了正确的红包码。至此,就是2019新年解谜红包的全部流程啦。
一些数据
算上追加,总共3处红包,总共被领取了4次。最欧的33.36/50,最非的4.21/100,甚至是同一个人。
Stage1就询问我的人来看,很多人想到二维码拼接之后的处理方式。Stage2有19个不同IP请求,Stage3则是4个。Stage2的大部分请求都是简单红包失效后,所以很可惜错过了那个红包。
隐藏红包
秉承去年的良好传统,今年的解谜红包也是附带隐藏红包的。不过今年的入口依旧魔性。和去年一样,有兴趣的dalao可以试着找找,解法隐藏回复可见~
后记
发解谜红包最主要还是想搞个有意思的活动,消磨一下无聊的假期。当然还有一点很重要,就是强调“深究=>查文档”,并加入一些实用的技巧。去年的HTTP状态码是鼓励查文档,今年的PNG文件格式、二维码也同样是鼓励深究这些平常经常接触事物的本质,然后查阅文档。实用的技巧上,去年有虾米音乐的url编码、异或运算的性质,今年有流行的混淆库和反调试手段。当然最主要的是希望大家玩的开心,同时祝大家新年快乐~
哈哈哈哈哈哈“同一个人”来报道了
噗,话说友链嘛
嗯,我已经安排上了。
在第一关就卡关的咸鱼表示无比害怕(捂脸)
后面的内容能看懂已经很庆幸了QWQ
这次红包活动让我明白了
Win10自带的图片浏览器是不会校验IHDR的CRC的
vscode的图片浏览器是会校验的(逃
第一关就卡了
咱来翻答案了2333
出来看神仙