1M流量撑爆你的浏览器——关于Gzip的那些事儿

1M流量撑爆你的浏览器——关于Gzip的那些事儿

WuMX——我又来撩群主了

本文正式版本首发于@sharecentre

我们先来个开幕雷击:点此下载(不要担心,不解压就不会出事的)

这个压缩包,电脑上有7z的同学可以打开看一下。


这里的计数单位是KB,经过换算后可以得出以下结果:

原大小=1GB

压缩后大小≈1MB

没错,你没看错,这个压缩包将1个G的大文件压缩到了1MB大小。那么,Gzip是怎么做到的?他又怎么来被我们加以利用呢?

关于Gzip

Gzip其实上是一个古董压缩软件,是GNU家知名软件之一,早在1992年就已被发布,使用的LZ77和哈夫曼算法被许多压缩格式沿用至今。

这么说吧,你现在还能在Linux(就是你们看那个很牛逼的系统,可以搞黑客还可以搞服务器)和网络上见到他的身影。像以.tar.gz为后缀的压缩归档都是Gzip压缩的。

关于GNU和.tar.gz
GNU是一个很牛逼的一个组织,他们专门打造开源软件。同样他们也做了一个很牛逼的系统:GNU。那么这时有人就该问了:这么牛逼我咋没听过咧?实际上,你们现在知道的那个Linux(还不是因为你们不用)里几乎九成的内置软件都是GNU旗下的,像Vim,Screen等,虽然他们没有花里胡哨的功能,但他们却构建了繁荣的Linux生态。GNU也算是Linux发展路上的那位默默铺路者,为操作系统的建设和完善打下了厚实的基础。.tar.gz算是Linux里面的常见格式了,在一些软件包里面经常见到他的身影(尤其是包管理器)。其实tar和gz是完全两个毫不相干的格式,tar是一个文件归档格式(就是把许多文件合并成一个),而Gzip是一个压缩软件。许多时候之所以把他俩安排到一起是因为Gzip太古老了,只能由一个文件生成与它相对应的压缩文件。tar的出现便解决了这个问题,tar将许多文件合并为1个文件,完美解决了Gzip不能压缩多个文件的问题。

Gzip使用的算法是LZ77和哈夫曼算法,两者都属于字典算法。

那么字典算法是什么呢?

让我冒着生命危险为你们举个例子,

我们现在要写一篇关于

ov


shirker


的商业小故事。

如果按正常方法写,文章是这样的:

Shirker|可莉小朋友在Youtube上看见了Uvuvwevwevwe Onyetenyevwe Ugwemuhwem Osas的视频,感到很有趣,于是Shirker|可莉小朋友联系了Uvuvwevwevwe Onyetenyevwe Ugwemuhwem Osas,想让Uvuvwevwevwe Onyetenyevwe Ugwemuhwem Osas来推广自己的频道,于是Shirker|可莉小朋友通过网络联系到了Uvuvwevwevwe Onyetenyevwe Ugwemuhwem Osas并告诉Uvuvwevwevwe Onyetenyevwe Ugwemuhwem Osas,如果Uvuvwevwevwe Onyetenyevwe Ugwemuhwem Osas在Youtube频道推广Shirker|可莉小朋友的频道,那么Uvuvwevwevwe Onyetenyevwe Ugwemuhwem Osas就能获得1000 USDT。Uvuvwevwevwe Onyetenyevwe Ugwemuhwem Osas欣然接受,于是Uvuvwevwevwe Onyetenyevwe Ugwemuhwem Osas和Shirker|可莉小朋友的交易成交了。

不说体积,光是看就感觉很思维爆炸,那么怎么压缩这句话,同时保持原意不变呢?

这时我们就可以使用字典

字典类似于代数,通过一个比较简短的字符来代替文章中出现频率较高且长度较长的字符串。说明白了就是给某些字符串换个别名。

那么我们给这篇文章做个字典。

用1,代表Uvuvwevwevwe Onyetenyevwe Ugwemuhwem Osas,

用0,代表Shirker|可莉小朋友。

好了,我们再用字典来处理文章看看:

0在Youtube上看见了1的视频,感到很有趣,于是0联系了1,想让1来推广自己的频道,于是0通过网络联系到了1并告诉1,如果1在Youtube频道推广0的频道,那么1就能获得1000 USDT。1欣然接受,于是1和0的交易成交了。

好吧我承认我写这篇二人转是纯粹找打,但是借用字典,就可以节省很多空间,避免了不必要的重复

我们读文章时只要按照字典上的内容将原文替换就可以了。压缩软件也是一样,在处理完压缩包后,压缩软件会将字典放在文件头部,解压时只要按照字典替换原文里的内容就可以还原出原来的文件了。


关于LZ77和哈夫曼算法
其实LZ77和哈夫曼算法都不算难,基本上小学文化水平都能看懂,你们如果感兴趣可以去整整。哈夫曼算法其实就是一个字典,把文章中出现的字符之类的按出现概率大小排起来然后生成一个二叉树,根据出现频率分配代号,为了节省空间,越常见的内容代号就越短。使用这种压缩只需要短短的几个字节就可以代替一大部分内容。LZ77相当于一个动态字典,它是用一个窗口从左往右挨次遍历看看有没有重复的东西(哪怕是一个字符也可以),就比如你第一位是个B,第三位还是个B,那么第三位就可以拿(2,1)来表示,2是偏移量,1是截取长度。这种东西有点类似于指针但实际上不是,因为指针是描述变量在内存中的绝对位置,而它指的是两个字符的相对位置。如果你想对LZ77进行深入研究,可以看看这个

准备作案

现在你们可以自己动手生成一个Gzip炸弹了。

你只需要一个Linux系统(随便哪个发行版都可以)和你的双手。

开始作案

打开你的Linux命令行,用你单身多年的右手输入这行代码:

dd if=/dev/zero bs=1M count=1024 | gzip > bomb.gzip

看看目录,你会发现多出一个小文件,大约1M,

这个文件就是我们的Gzip炸弹了。

如果你将这个文件打开,就会出现文章开头的那一壮观现象——1个G的一个超大文件被压缩进去了。

那肯定有人又要问了:

你这1G的文件都能压到1M,为什么我的就不行?

那么我们就得从这行代码入手来说说炸弹生成的原理了。

代码第一部分“dd if=/dev/zero bs=1M count=1024”生成的只是一个由1x10^9个0组成的空文件,虽然它和你硬盘里的小姐姐一样都占有1G的位置,但他实际上外强中干,什么也没有。

因为这个文件是由纯“0”构成的,所以压缩软件可以通过字典轻松将这个文件压缩到极小的大小,而小姐姐的信息要做到无损压缩是有极限的,一般由它的信息熵决定(感兴趣的可以去了解一下信息熵)。


那有人肯定又又又又要问了:

这空文件我直接用十亿个0不就表达了吗,为啥还要用字典?

你别说,还真有这种算法,名为

游程编码。

现在给你一串文字:

单看这个数字我就能感受到一股很臭的气息了,现在要求你把这串数字压到更短怎么办?

排开高级算法不说,你光看这个排列组合,就能非常明智的想出一个办法:解决反复

这串文字分开了可以是这样:

没错,你会发现每个小组重复了两遍,那么我们就可以把他变为:

再把中间那组压缩一下,就可以变成

这其实就是游程编码,把一段连续字符以次数来替代起到压缩效果。

有人要问了:你这666长度不变长了么,压缩了个寂寞。

实际上,真正的压缩结果不是这样的,压缩结果都以二进制表示,没有繁琐的符号,操作符也会尽可能的简短,真正实战中666压缩的确会更短。

但是游程编码有一个比较致命的缺点:数据一复杂起来,压缩大小不降反增。

这问题想想都知道,为了节省脑细胞就不多bb了。

Gzip在数据算法这的确做了个比较好的取舍,因为现实世界中的数据千变万化,复杂度极高,使用字典等算法将更占优势,虽然这样的空文件用字典压缩完可能需要1M多,但在大多数情况下字典算法的确更省空间。

通过强大的算法,我们可以将一长串低复杂度的数据压缩到很小,可想而知极小的数据也有可能压缩着极大的数据。

我就拿和Gzip差不多的zip炸弹举个例子:

2003年,曾就爆出过一个名为42的zip,它体积只有42KB,但实际解压完有4.5PB那么大。
同时期还有个名为droste的zip,一旦解压就会无限套娃。一个压缩包里面再套一个,会永无止境的套下去。

这种恐怖袭击,你电脑受得了吗?

当时,就有病毒随身携带这种压缩包,这种压缩包解压时体积很大,就会把杀毒软件卡住,然后病毒就可以溜去搞事情了,可想而知危害巨大。不过快20年过去了,现在安全软件早已识破了这些手段,能够自动忽略这种压缩包。

不过20年过去了,Gzip炸弹还是没有变!

到底是哪里没有变呢?

这下我们得来一项实战了:

炸你的浏览器和防爬虫

我们先从一个网络小技术说起。

现在服务器的带宽非常宝贵,价格是居高不下。网站运营者为了节省带宽,只能将网页资源进行精简和压缩,以提高网站的载客量。为了解决这一问题,一个http的新技术便横空出世:

http压缩。

http压缩,慕名思议就是将http传输的信息进行压缩,一般服务器只要开启了http压缩,带宽资源就能降低到原来的40%~60%。一般使用http压缩后,传输时内容都会在 一端自动压缩然后在另一端自动解压。

但我现在就告诉你,http压缩就用的是Gzip。

诶你说nm巧不巧,就恰好用的Gzip。

那你现在又想问了:Gzip炸弹能不能搞上去?

你说呢?当然行!这技术被人家用了这么多年仍然乐此不疲。Gzip没有变就是因为他的反爬虫效果实在是太好了

没错,当客户端发来一个http请求时。你可以从服务器直接向客户端返回一个gz包,然后告诉他:我这个http响应是拿Gzip压缩过的你拿到后得解压一下,然后客户端就会傻呵呵地解压你的gz包,然后他的内存就爆炸啦(因为客户端对服务器响应的处理都是在内存内进行的,所以就····)

本着促进网络安全的要求,我们可以将其用于防爬虫上,在爬虫发来请求时,我们直接往回丢个1M的Gzip并告诉他:这个响应是压缩过的,你解压一下。

然后呢?

爬虫收到响应后,程序的内部模块就会处理响应,Gzip炸弹就会迅速占满爬虫的内存,系统就会杀掉高占用进程,如果幸运的话,爬虫还可能会死机,总之就是花了1M不到的流量,就可以把一个烦人的网络爬虫整废。

太TM的刺激啦!!!

现在我们来实践一下。

我们使用Node.js写一个模拟爬虫(其实真实的爬虫并不是这样,为了省事就写成了这个样子,其实两者原理都差不多)。

//client.js
const axios = require('axios')
const count = 10

for(let i=0;i<count;i++){
    axios('http://localhost:8080');
}

很简单,连续请求这个服务器10次。

现在我们来写个对抗爬虫的小服务器。

//server.js
const http = require('http')
const fs = require('fs')

const boom = fs.readFileSync('./bomb.gzip')//读取本地的Gzip炸弹

http.createServer((req,res)=>{
    res.setHeader('content-encoding','gzip');//设定Gzip返回标头
    res.writeHead(200);//StatusCode
    res.end(boom)//返回Boom
}).listen(8080)

没错,看设定返回标头的那一行,服务器在返回的header上加了Gzip的content-encoding。此时客户端在拿到时就会接到Gzip编码的响应,此时客户端解码时内存就会溢出。

至于为什么我要使用pm2启动client是因为可以更直观的看见client的内存占用,如果直接使用node client的话只会看见过了一段时间后弹出一个killed

看看吧,爬虫的内存一路高涨,最后被系统杀掉。

如果你用浏览器访问http://localhost:8080的话,会看见一段时间后报内存溢出的错误。因为现在的浏览器是一个沙盒环境,对每一个网页环境都做了,因此Gzip炸弹也只会让当前页面崩溃而不影响其他标签页和操作系统。

压缩炸弹并不是Gzip仅有的,像我前面提及的两个zip也是压缩炸弹,他们的压缩体积更小,同时危害力更大。同学们以后如果碰到了奇怪的压缩包千万不要鲁莽直接解压,尤其是telegram上的一些奇怪压缩包。他们可能不是zip炸弹,但也有可能是病毒一类东西,我们一定先要先检查好压缩包内容,再进行解压。

好了,说了这么多,我相信大家都对Gzip炸弹的原理和利用有了一定的了解。同时我也想劝告大家一句话:

——The End——

想要看港仔女装照吗?

想要每天来点乐子吗?

想要找有趣的小伙伴吗?

@sharecentre,港仔女装,趣闻逸事,大佬时评看不完,还有超多好玩的小伙伴等着你!没钱的技术宅也可以写写豆腐块文章投稿,投稿一经采纳就会有150-250RMB的丰厚稿酬,快快来投稿吧!


白嫖党欢迎关注@sharecentrepro,里面有丰富的白嫖高速节点,还有机场主不定期发福利!Github超1.5k star的免费订阅项目也在这里,快快来关注!!!


买机场总是摸不着头脑?来@speedcentre!使用prprcloud旗下专业测速产品进行流解,延迟等多项测试,每天都会发新机场测评,不定时还会挑选最划算的机场,让你不再犯选择困难症,快来关注吧!


P.S.

其实这篇文章一共有三版,其中版是在Vuepress上完成的,但由于甲方要求,所以第三版改到了Telegraph上面。

这篇文章之所以要拿Gzip而不是zip来举例子,是因为Gzip是http专用的压缩格式,因为这类压缩在web领域非常常见而且杀人不见血,所以就专门把它拿出来说了一遍。

前两版说的用处实际上是防CC攻击而不是防爬虫。这一版之所以要改成防爬虫是因为CC攻击一般是由编程语言内置的标准库或基础的http(s)库发出的,一般最多只会反复获取响应内容来消耗服务器带宽而不会去解码,原来那两版中是用axios做的CC肉鸡,拿vue或nodejs写过程序的人都知道axios是一个比较好的http扩展库,由于其设计目的就是用来更好更简单地获取服务器的响应,所以axios就会自动判断响应标头进行解码,对于要用api的程序员就比较友好(在Chrome环境中,一个XHR可能需要数行代码,而axios只用一行就够了)。而CC肉鸡可能根本就不需要这么多,他们为了节省服务器资源就会用最基础的方法来榨干目标服务器,所以我就把防CC去掉了。(当然也不排除有些人拿axios之类的库写肉鸡)爬虫的目的就是为了获取目标服务器的内容,无论是py还是js写的爬虫,都需要对服务器响应进行解析,这时必定就会触发Gzip炸弹,因此这种说法也比较可行。

这一版的防爬虫,我为了省事就把前两版的CC肉鸡代码搬了过来换了个说法,但是这样可能不太严谨。axios是基于promise的http库,他的方法都是异步的,因为我没有将方法调用放在async函数并给axios方法前面加await,所以这个循环就是一个高并发,每次循环都是创建一个任务就过去了,并不会等待方法的完成,他的行为也就更像肉鸡。真正的爬虫都会拿到响应并分析响应的,所以一般用同步方法获取内容再进行分析(不过即使是用同步方法的爬虫也在劫难逃,而且这种反爬虫方法也有人做过落地实践了,效果还不错,所以我把他搬出来说了)

这篇文章也只是业余制作,技术含量比较低,并且吹水内容比较多,仅供大家图一乐并给我抽老婆攒点钱,还请各位大佬不吝指教。

Report Page