文章目录隐藏
注意:题目中出现的链接需要替换后才能访问redpacket.kaaass.net=>redpacket.kaaass.net/archived/2019/。
嘛,和去年一样,今年我又发了个新年解谜红包(不知道去年红包的同学可以康:论如何正确收一个新年解谜红包)。这次的题目也非常简洁,只有一张图片(右键另存为下载)。今年的红包比起去年的流程更短,但是每一关的难度都不小(尤其是Stage1)。不过,由于今年红包未领将会续两次,所以最长解谜时间可以是3天。那么接下来就是解谜全程啦~
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/。
一打开页面,先闪过了一些字,然后变成了类似“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行,从头阅读十分麻烦。于是,要从其他地方找突破口。这里有几种可能的方式。
而且页面逻辑那么简单,肯定没有300来行,所以可以判断真正的逻辑就是293-353行。简单说明下逻辑:
于是我们来分析下ajax的返回。比如对于http://redpacket.kaaass.net/20tannyaocyuu/ronn.php?suttann=815abb03af71b,将返回:
{
"t": "1549800860",
"i": "415c90a1be0659c317b6fc13f4",
"m": "280e4d25e5e203e5d5a1125b88612a27",
"e": "a67e6dd453bd1fa8ded097121b481308"
}
不难发现
所以,实际上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;
}
理所当然,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。
其实这种反调试是混淆工具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函数。
返回内容如下:
这不是Brainfuck嘛,随手找个在线编译器运行一下得到:60014489。正好八位!然而领取失败???Naive了旁友!注意到HTTP状态码是206 Partial Content,但是Content-Range却是bytes 0-176/176。附加Range请求头也不会返回更多。其实这是一个提示,Partial Content指的是你所看到的Brainfuck只是内容(Content)的一部分(Partial),选中这段文本就可以发现:
你可能是Postman的受害者。左Postman,右Notepad++
这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编码、异或运算的性质,今年有流行的混淆库和反调试手段。当然最主要的是希望大家玩的开心,同时祝大家新年快乐~