论如何再收一个新年解谜红包 – 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行,从头阅读十分麻烦。于是,要从其他地方找突破口。这里有几种可能的方式。

  1. 如果通过抓包工具或其他手段发现了ajax请求,那么可以在代码中直接搜索XMLHttpRequest。只出现于340行。
  2. 既然每次刷新页面,那串字符内容发生了变化,那么,那串字符要么是ajax请求来了,要么就是随机生成的。从ajax请求的分析可以看出,请求的url的参数是一串随机的字符串(比如url:redpacket.kaaass.net/20tannyaocyuu/ronn.php?suttann=815abb03af71b),于是可以断定js中存在随机生成代码。于是搜索Math或random,找到316行的函数_0x242600。

而且页面逻辑那么简单,肯定没有300来行,所以可以判断真正的逻辑就是293-353行。简单说明下逻辑:

  1. 调用_0xf07168函数生成请求参数。
  2. 使用_0x4e01df发送ajax请求。
  3. 将返回的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状态码是 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编码、异或运算的性质,今年有流行的混淆库和反调试手段。当然最主要的是希望大家玩的开心,同时祝大家新年快乐~

我来评几句
登录后评论

已发表评论数()

相关站点

+订阅
热门文章