15 分钟用 ML 破解一个验证码系统

2,613 阅读9分钟
原文链接: python.jobbole.com

人人都恨验证码——那些恼人的图片,显示着你在登陆某网站前得输入的文本。设计验证码的目的是,通过验证你是真实的人来避免电脑自动填充表格。但是随着深度学习和计算机视觉的兴起,现在验证码常常易被攻破。

我拜读了 Adrian Rosebrock 写的《Deep Learning for Computer Vision with Python》。在书中,Adrian 描述了他是怎样用机器学习绕过纽约 E-ZPass 网站上的验证码:

1*4q8hCiIh1amCf2e9fJBQGg

Adrian 无法接触到该应用生成验证码的源代码。为了攻破该系统,他不得不下载数百张示例图片,并手动处理它们来训练他自己的系统。

但是如果我们想攻破的是一个开源验证码系统,我们确实能接触到源代码该怎么办呢?

我访问了 WordPress.org 的插件频道,并搜索了“验证码”。第一条搜索结果是 Really Simple CAPTCHA,并且有超过一百万次的活跃安装:

1*3qc-gFTRWmleRXomopMAUQ

最好的一点是,它是开源的!既然我们已经有了生成验证码的源代码,那它应该挺容易被攻破的。为了让这件事更有挑战性,让我们给自己规定个时限吧。我们能在 15 分钟内完全攻破这个验证码系统吗?来试试吧!

重要说明:这绝不是对 Really Simple CAPTCHA 插件或对其作者的批评。该插件作者自己说它已经不再安全了,建议使用其他插件。这仅仅是一次好玩又迅速的技术挑战。但是如果你是那剩余的一百多万用户之一,也许你应该改用其他插件 :)

挑战

为了构思一个攻击计划,来看看 Really Simple CAPTCHA 会生成什么样的图片。在示例网站上,我们看到了以下图片:

1*WDu1xgEEQuP3tqgC-cPmzA

好了,所以验证码似乎是四个字母。在 PHP 源代码中对其进行验证:

	public function __construct() {
		/* Characters available in images */
		$this->chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';
 
		/* Length of a word in an image */
		$this->char_length = 4;
 
		/* Array of fonts. Randomly picked up per character */
		$this->fonts = array(
			dirname( __FILE__ ) . '/gentium/GenBkBasR.ttf',
			dirname( __FILE__ ) . '/gentium/GenBkBasI.ttf',
			dirname( __FILE__ ) . '/gentium/GenBkBasBI.ttf',
			dirname( __FILE__ ) . '/gentium/GenBkBasB.ttf',
		);

没错,它用四种不同字体的随机组合来生成四个字母的验证码。并且可以看到,它在代码中从未使用 O 或者 I,以此避免用户混淆。总共有 32 个可能的字母和数字需要我们识别。没问题!

计时:2 分钟

工具

在进行下一步前,提一下我们要用来解决问题的工具:

Python 3 

Python 是一种有趣的编程语言,它有大量的机器学习和计算机视觉库。

OpenCV 

OpenCV 是一种流行的计算机视觉和图片处理框架。我们要使用 OpenCV 来处理验证码图片。由于它有 Python API,所以我们可以直接从 Python 中使用它。

Keras

Keras 是用 Python 编写的深度学习框架。它使得定义、训练和用最少的代码使用深度神经网络容易实现。

TensorFlow 

TensorFlow 是 Google 的机器学习库。我们会用 Keras 编程,但是 Keras 并没有真正实现神经网络的逻辑本身,而是在幕后使用 Google 的 TensorFlow 库来挑起重担。

好了,回到我们的挑战吧!

创造我们的数据集

为了训练任何机器学习系统,我们需要训练数据。为了攻破一个验证码系统,我们想要像这样的训练数据:

1*dC78m_XpcZmuaV_GlnAh5g

鉴于我们有 WordPress 插件的源代码,我们可以调整它,一起保存 10,000 张验证码图片及分别对应的答案。

经过几分钟对代码的攻击,并添加了一个简单的 for 循环之后,我有了一个训练数据的文件夹——10,000 个 PNG 文件,文件名为对应的正确答案:

1*leaOO0EYbgKVl7MEhflIFA

这是唯一一个我不会给你示例代码的部分。我们做这个是为了教育,我不希望你们真去黑 WordPress 网站。但是,我最后会给你生成的这10,000 张图片,这样你就能重复我的结果了。

计时:5 分钟

简化问题

既然有了训练数据,就可以直接用它来训练神经网络了:

1*4ScTIDYJ6rPCAtopRulzOg

有了足够的训练数据,这个方法可能会有用——但是我们可以使问题更简化来解决。问题越简单,要解决它需要的训练数据就越少,需要的计算能力也越低。毕竟我们只有 15 分钟!

幸运的是,验证码图片总是由仅仅四个字母组成。如果我们能想办法把图片分开,使得每个字母都在单独的图片中,这样我们只需要训练神经网络一次识别一个字母:

1*4ScTIDYJ6rPCAtopRulzOg

我没有时间去浏览 10,000 张训练图片并在 Photoshop 中手动把它们拆分开。这得花掉好几天的时间,而我只剩下 10 分钟了。我们还不能把图片分成相等大小的四块,因为该验证码插件把字母随机摆放在不同的水平位置上以防止这一做法:

1*yyfjNSCKt8IvY7JqANnOZg

幸运的是,我们仍然可以自动处理。在图像处理中,常常需要检测有相同颜色的像素块。这些连续像素块周围的界限被称为轮廓。OpenCV 中有一个 LndContours() 函数,可以被用来检测这些连续区域。

所以我们用一个未经处理的验证码图片开始:

接下来把该图片转换成纯黑白(这叫做 thresholding),这样容易找到连续区域:

接着,使用 OpenCV 的 LndContours() 函数来检测该图片中包含相同颜色像素块的不同部分:

接下来就是简单地把每个区域存成不同的图片文件。鉴于我们知道每张图片都应该包含从左到右的四个字母,我们可以利用这一点在保存的同时给字母标记。只要我们是按顺序保存的,我们就应该能保存好每个图片字母及其对应的字母名。

但是等等——我看到一个问题!有时验证码中有像这样重叠的字母:

这意味着我们会把两个字母分离成一个区域:

如果不处理这个问题,会创造出糟糕的训练数据。我们得解决这个问题,这样就不会意外地教机器把两个重叠的字母识别成一个字母了。

一个简单的方法是,如果一个轮廓区域比它的高度更宽,这意味着很可能有两个字母重叠在一起了。在这种情况下,我们可以把重叠的字母从中间拆分成两个,并将其看作两个不同的字母:

既然我们找到拆分出单个字母的方法了,就对所有验证码图片进行该操作。目标是收集每个字母的不同变体。我们可以将每个字母保存在各自对应的文件夹中,以保持条理。

在我分离出所有字母后,我的 W 文件夹长这样:

计时:10 分钟

构建并训练神经系统

由于我们只需要识别单个字母和数字的图片,我们不需要非常复杂的神经网络结构。识别字母要比识别像猫狗这样复杂的图片容易得多。

我们要使用简单的卷积神经网络结构,有两层卷积层以及两层完全连接层:

如果你想要了解更多神经网络的工作,以及为什么它们是图片识别的理想工具,请参考 Adrian 的书或者我之前的文章

定义该神经网络结构,只需要使用 Keras 的几行代码:

# Build the neural network!
model = Sequential()
 
# First convolutional layer with max pooling
model.add(Conv2D(20, (5, 5), padding="same", input_shape=(20, 20, 1), activation="relu"))
model.add(MaxPooling2D(pool_size=(2, 2), strides=(2, 2)))
 
# Second convolutional layer with max pooling
model.add(Conv2D(50, (5, 5), padding="same", activation="relu"))
model.add(MaxPooling2D(pool_size=(2, 2), strides=(2, 2)))
 
# Hidden layer with 500 nodes
model.add(Flatten())
model.add(Dense(500, activation="relu"))
 
# Output layer with 32 nodes (one for each possible letter/number we predict)
model.add(Dense(32, activation="softmax"))
 
# Ask Keras to build the TensorFlow model behind the scenes
model.compile(loss="categorical_crossentropy", optimizer="adam", metrics=["accuracy"])

现在我们可以训练它了!

# Train the neural network
model.fit(X_train, Y_train, validation_data=(X_test, Y_test), batch_size=32, epochs=10, verbose=1)

在 10 通过了训练数据集后,我们达到了几乎 100% 的正确率。此时,我们应该能随时自动绕过这个验证码了!我们成功了!

计时:15 分钟(好险!)

使用训练后的模型来处理验证码

既然有了一个训练后的神经网络,利用它来攻破真实的验证码要很容易了:

  • 1.从一个使用 WordPress 插件的网站上下载一张验证码图片。
  • 2.使用文章中生成训练数据集的方法,把该验证码图片拆分成四张字母图片。
  • 3.用神经网络对每张字母图片分别作预测。
  • 4.用四个预测字母作为验证码的答案。
  • 5.狂欢!

在破解验证码时,我们的模型看起来是这样:

1*L6scb0B2nCirZ_ZLyuONPg

或者从命令来看:

1*7RE-Ql6jaDu1jCfi0OgtTw

来试试吧!

如果你想自己试试,你可以从这里找到代码( http://t.cn/R8yFJiN )。它包含 10,000 张示例图片和文章中每一步的所有代码。参考文件 README.md 中的运行指导。

但是如果你想了解每一行代码都做了什么,我强烈建议你看看《 Deep Learning for Computer Vision with Python。该书覆盖了更多的细节,而且有大量的详细示例。这本书是我目前见过的唯一一本既包含了运行原理,又包含了如何在现实生活中用其来解决复杂问题的书。去看看吧!

3 赞 4 收藏 评论

关于作者:精算狗

简介还没来得及写 :) 个人主页 · 我的文章 · 18 ·