32

论如何再收一个新年解谜红包 – 2019篇

 5 years ago
source link: https://blog.kaaass.net/archives/1085?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

嘛,和去年一样,今年我又发了个新年解谜红包(不知道去年红包的同学可以康: 论如何正确收一个新年解谜红包 )。这次的题目也非常简洁,只有一张图片(右键另存为下载)。今年的红包比起去年的流程更短,但是每一关的难度都不小(尤其是Stage1)。不过,由于今年红包未领将会续两次,所以最长解谜时间可以是3天。那么接下来就是解谜全程啦~

Stage1 – 颜文字图片

Stage1可以说是最难的,但是提示也是最多的。首先看图。

2yquyiv.png!web 很多人一眼就看出,这个表情的眼睛是二维码的定位点。没错,而且这是一个非常关键的提示。而剩下的提示只能从文件本身入手了。随便丢进一个二进制文件查看器,在文件尾部发现另一个提示。

MfIvAnJ.png!web 在IEND块后有一个单词,“TALLER”。除此之外,还有一个比较隐蔽的提示就是,部分图片浏览器打不开这张图片。于是很多人就此卡关了……其实解题的方向很明确,就是PNG文件结构(后来我也补了这个hint)。

图片浏览器打不开的原因,其实是图片的第一个IHDR CHUNK的CRC校验失败了。

rMBzaaF.png!web IHDR CHUNK的数据段存放的其实是PNG图片的一些基本信息(长、宽、位深度等等),而结合“TALLER”的提示,很容易能想到是指图片的宽度,于是随便修改一个大一点的数值,然后一点点调整,就能看到之后的内容了。

BRRNVzn.png!web

可以看到,这是一个支离破碎的二维码。所以依照定位点的提示,简单的拼起来。

Vvieiue.png!web 于是你就得到一个扫不出的二维码23333。部分dalao看到扫不出来就放弃了这个思路,转而去思考如何用上另一只眼睛,于是就跑偏了。 (然而无论从中间的内容还是定位点间隔来看,另一只眼睛都没办法组成一个二维码)

还是要在图上找答案。既然可以确定是二维码,那就来找一下定位图形好了。

3Qfi2aB.png!web 可以看到,定位图形都是黑白的。而剩下区域的黑白点十分稀少,所以排除彩色块是杂色的可能。于是真正的答案就呼之欲出了——这是三通道的三个二维码结合在一起!理由很简单,重复的区域(定位图形)是黑白的。于是打开ps,把红、绿、蓝三个通道的二维码分开~

BvAJzqu.png!webUFzqeeQ.png!webbuaIV3m.png!web

分别扫描这三个二维码,得到:

红: 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之类的工具啦。

正常流程

打开调试工具发现,调试工具没办法正常使用。只要一打开调试工具,就会自动跳转到断点。

Bv6Njyz.png!web 这就意味着没办法进行断点调试和ajax请求查看之类的操作的。事实上,这一步就是为了掩盖ajax请求。虽然可以用诸如油猴脚本的方式屏蔽反调试(具体见“反调试浅析”节),但是事实上也可以直接对js脚本进行静态分析。用自带调试工具format一下。

r63Yvq6.png!web 这种魔性的十六进制命名其实看着还是很头大的,而且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

返回内容如下:

qAZFzej.png!web

这不是Brainfuck嘛,随手找个在线编译器运行一下得到:60014489。正好八位!然而领取失败???Naive了旁友!注意到HTTP状态码是 206 Partial Content,但是Content-Range却是bytes 0-176/176。附加Range请求头也不会返回更多。其实这是一个提示,Partial Content指的是你所看到的Brainfuck只是内容(Content)的一部分(Partial),选中这段文本就可以发现:

MzInA32.png!web

你可能是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编码、异或运算的性质,今年有流行的混淆库和反调试手段。当然最主要的是希望大家玩的开心,同时祝大家新年快乐~


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK