本是同根生,相煎何太急-用Google语音识别API破解reCaptcha验证码

author:A胖

from:http://www.debasish.in/2014/04/attacking-audio-recaptcha-using-googles.html

0x00 背景

关于验证码和验证码破解的入门,请看:http://www.wooyun.org/drops/tips-141

什么是reCaptcha?

reCaptchas是由Google提供的基于云的验证码系统,通过结合程序生成的验证码和较难被OCR识别的图片,来帮助Google数字化一些书籍,报纸和街景里的门牌号等。

reCaptcha同时还有声音验证码的功能,用来给盲人提供服务。

0x01 细节

中心思想:

用Google的Web Speech API语音识别来破解它自己的reCaptcha声音验证码.

下面来看一下用来语音识别的API

Chrome浏览器内建了一个基于HTML5的语音输入API,通过它,用户可以通过麦克风输入语音,然后Chrome会识别成文字,这个功能在Android系统下也有。如果你不熟悉这个功能的话这里有个demo:
https://www.google.com/intl/en/chrome/demos/speech.html

我一直很好奇这个语音识别API是如何工作的,是通过浏览器本身识别的还是把音频发送到云端识别呢?

通过抓包发现,好像的确会把语音发送到云端,不过发送出去的数据是SSL加密过的。

于是我开始翻Chromium项目的源码,终于我找到了有意思的地方:

http://src.chromium.org/viewvc/chrome/trunk/src/content/browser/speech/

实现过程非常简单,首先从mic获取音频数据,然后发送到Google的语音识别Web服务,返回JSON格式的识别结果。 用来识别的Web API在这里:

https://www.google.com/speech-api/v1/recognize

比较重要的一点是这个API只接受flac格式的音频(无损格式,真是高大上)。

既然知道了原理,写一个利用这个识别API的程序就很简单了。

源代码:

研究了一下reCaptcha的语音验证码后,你会发现基本上有两种语音验证码,一种是非常简单的,没有加入很多噪音,语音也很清晰。另外一种是非常复杂的,故意加了很多噪音,连真人很难听出来。这种验证码里面估计加了很多嘶嘶的噪声,并且用很多人声作为干扰。

关于这个语音验证码的细节可以参考这里https://groups.google.com/forum/#!topic/recaptcha/lkCyM34zbJo

在这篇文章中我主要写了如何解决前一种验证码,虽然我为了破解后一种复杂的验证码也做了很多努力,但是实在是太困难了,即使是人类对于它的识别率也很低。

用户可以把recaptcha的语音验证码以mp3格式下载下来,但是Google语音识别接口只接受flac格式,所以我们需要对下载回来的mp3进行一些处理然后转换成flac再提交。

我们先手工验证一下这样行不行:

首先把recaptcha播放的音频下载成mp3文件。

然后用一个叫Audacity的音频编辑软件打开,如图

把第一个数字的声音复制到新窗口中,然后再重复一次,这样我们把第一位数字的声音复制成连续的两个相同声音。

比如这个验证码是76426,我们的目的是把7先分离出来,然后让7的语音重复两次。

最后把这段音频保存成wav格式,再转换成flac格式,然后提交到API。

很好,服务器成功识别了这段音频并且返回了正确的结果,下面就需要把这个过程自动化了。

在自动提交之前,我们需要了解一下数字音频是处理什么原理。

这个stackoverflow的问题是个很好的教程:

http://stackoverflow.com/questions/732699/how-is-audio-represented-with-numbers

把一个wav格式的文件用16进制编辑器打开:

用Python WAVE模块处理wav格式的音频:

wave模块提供了一个很方便接口用来处理wav格式:

getparams()函数返回一个元组,内容是关于这个wav文件的一些元数据,例如频道数量,采样宽度,采样率,帧数等等。

getnframes()返回这个wav文件有多少帧。

运行这个python程序后,会把sample.wav的每一帧用16进制表示然后print出来

从输出文件中我们可以看到,这个wav文件是单通道的,每个通道是2字节长,因为音频是16比特的,我们也可以用 getsampwidth()函数来判断通道宽度,getchannels() 可以用来确定音频是单声道还是立体声。

接下来对每帧进行解码,这个16进制编码实际上是小端序保存的(little-endian),所以还需要对这段python程序做一些修改,并且利用struct模块把每帧的值转换成带符号的整数。

修改完毕后再次运行,输出内容差不多这样:

这样是不是更明白了?下面用python的matplotlib画图模块把这些数值画出来:

这个图实际上就是声音的波形图

进一步自动化:

下面这段python程序通过音量不同把音频文件分割成多个音频文件,相当于图片验证码识别中的图片分割步骤。

当这个文件被分割成多份之后我们可以简单的把他们转换成flac格式然后把每个文件单独发送到Google语音识别API进行识别。

视频已翻墙下载回来:

Solving reCaptcha Audio Challenge using Google Web Speech API Demo

现在我们已经解决了简单的音频验证码,我们再来尝试一下复杂的。

这个图片是用前面的程序画出来的复杂语音验证码的波形图:

从图里我们可以看到,这段音频中一直存在一个恒定的噪声,就是中间横的蓝色的那条,对于这样的噪声我们可以用标准的离散傅里叶变换,通过快速傅里叶变换fast Fourier transform(挂在高树上的注意了!)来解决。

回到多年前校园中的数字信号处理这门课,让我们在纯洁的正弦波s(t)=sint(w*t)上叠加一个白噪声,S(t)=S(t+n), F为S的傅里叶变换,把频率高于和低于w的F值设为0,噪声就被这样过滤掉了。

比如这张图里,正弦波的频谱域被分离了出来,只要把多余频率切掉,再逆变换回去就相当于过滤掉部分噪音了。其实自己写这样的过滤器实在太蛋疼了,Python有不少音频处理库并且自带降噪滤镜。

但是就像识别图形验证码一样,噪音(相当于图片里的干扰线和噪点)并不是破解语音验证码的难点,对于计算机来说,最难的部分还是分割,在复杂的语音验证码里,除了主要的人声之外,背景中还有2,3个人在念叨各种东西,并且音量和主要的声音差不多,无法通过音量分离,这样的手段即使对于人类也很难识别的出。

我把目前的代码放在了https://github.com/debasishm89/hack_audio_captcha

这些代码还很原始,有很大改进的余地。

0x02 结论

我把这个问题报给了Google安全团队,他们说这个东西就是这样设计的(苦逼的作者),如果系统怀疑对方不是人是机器的时候会自动提升到高难度验证码,目前Google不打算改进这个设计。

评论

RainSlide2016-01-09 15:44:16

flac only?!Google一定玩gzip了,只是gzip玩得开心不如直接录制ape开心。

daeerk2014-12-11 14:36:43

估计是B吧

bowman2014-11-05 18:06:48

我不告诉bat中的一家reCaptcha有39%的正确率,基本上两次能搞定一次

从容2014-06-26 23:09:13

试试才知道~

核攻击2014-05-17 10:23:40

真他娘的猥琐的方法……

xiadaomike2014-05-01 16:26:59

我比较好奇Terminal配色名是?

地狱星星2014-05-01 16:26:31

牛呀

橘子2014-04-30 22:54:07

大赞!求转~

Anonymous2014-04-30 13:28:00

我是小白,完全看不懂啊~

Mody2014-04-30 09:50:15

[email protected]

高斯2014-04-30 04:45:21

sint(w*t)?不是sin(w*t)?

xiaojiaoben2014-04-29 18:59:15

使用机器学习可以分割两种人声

计算姬2014-04-29 17:50:41

高大上啊,自己攻自己啊。

lucky2014-04-29 16:56:43

真是好的想法

fffonion2014-04-29 13:55:57

感觉标题应该是“python音频处理教程:一”……
【其实google也可以识别非flac格式的,只要改下content-type

Jim叔叔2014-04-29 13:48:49

高数大牛啊。。。如果你是正弦,我就是余弦,我们是傅里叶变换的一对基。

杰克弗里曼2014-04-29 13:37:24

厉害,谷歌这是给自己下套了

xyang2014-04-29 13:30:03

我去

livers2014-04-29 13:25:18

这个老外真淫荡啊

  • N/A