点选验证码破解-网易云易盾

本文记录了我破解网易云易盾的点选验证码的详细全过程,供大家学习交流。

首先,我破解这类点选验证码的常用思路是:

  1. 用YOLO v3之类的目标检测模型来定位汉字位置
  2. 然后用普通的卷积网络做汉字的分类识别

这种方式在用YOLO v3做训练的时候只需要分一类,大概1000-2000的样本就能达到不错的定位效果,然后crop出每个汉字图片,最好标注个10w左右的样本用来训练分类模型。不过庆幸的是,易盾下方要求点击的汉字不是以图片形式展示的,可以省去繁琐的手动标注,利用程序自动标注。

另外一种方式是直接在YOLO v3中做汉字的分类,我并没有实践过,不好说两者的效果差异,就不误人子弟了。

在做这些之前,总得先把接口摸透吧。当然,基于Selenium也是一种方法,不过在资源占用和耗时方面会差很多。主要就两个接口:/get和/check。/get用于生成验证码,/check用于验证成功与否。

上图是/get接口的参数,id表示验证码类型(点击、滑块、点选),fp可置为空字符,token从上一个接口返回,至于cb,需要调试JS找到加密代码。然后/get主要返回了图片URL和要求依次点击的汉字:

__JSONP_qamfiw6_5({"data":{"bg":["http://necaptcha.nosdn.127.net/79b16aa001ae4dedbe6e1d32ea96eae7.jpg","http://nos.netease.com/necaptcha/79b16aa001ae4dedbe6e1d32ea96eae7.jpg"],"front":"连鉴距","type":3,"token":"b968894e1842472fb18196ed601d3419"},"error":0,"msg":"ok"});

再看/check接口:

主要多了acToken和data,acToken可置为空字符,data是对点击位置的坐标做JS加密而产生的字符串。

关于cb和data两个参数的JS加密部分可以直接看我项目中的JS文件,如果想要了解详细的破解流程可以联系我。

接下来开始进入正题。

YOLO v3

准备框架和环境

YOLO v3框架:https://github.com/qqwweee/keras-yolo3

训练环境:滴滴云P4 GPU云服务器

准备数据

请求易盾的/get接口获取足够的验证码图片,然后标注样本,我是自己用Tkinter写了一个非常粗糙的标注工具,统一使用20*20的框,也可以用LabelImg等开源的标注工具,不过个人感觉有点受限。

然后将训练样本数据写入txt,如下面这样的格式:

/home/dc2-user/projects/image/f6879824-12e2-11e9-a683-00f1f380f2db.jpg 27,31,67,71,0 85,64,125,104,0 174,95,214,135,0 172,30,212,70,0 252,23,292,63,0

使用框架

keras-yolo3是一个开源的YOLO v3训练框架。在该任务中,我用的是tiny版本,且不需要预训练权重,因此不用理会源码中无关的文件,只要准备好train.txt,看情况修改下train.py中的配置和超参数,运行即可。

训练结果

最终loss概稳定在20左右,直接上张测试图:

分类器

准备数据

前面提到过易盾下方要求点击的汉字不是以图片形式展示的,可以省去繁琐的手动标注。我的方案是请求/get接口,不断生成验证码,借助训练出来的定位模型,crop出检测到的汉字图片,将每个汉字的box中心点坐标组成列表,记为pre_points

groups = itertools.permutations(pre_points, 3)

将生成器groups中的所有组合情况依次通过/check接口检查是否验证成功,成功便将该组合按所要求的依次点击的汉字进行自动标注:

跑出了118000左右的样本之后结束,训练集与验证集比例9:1,共1966个汉字。

搭建模型

直接上模型代码:

def Conv2D_BN_Leaky(*args, **kwargs):
    conv_kwargs = {'use_bias': False, 'kernel_regularizer': l2(5e-3), 'padding': 'same'}
    conv_kwargs.update(kwargs)
    return compose(
        Conv2D(*args, **conv_kwargs),
        BatchNormalization(),
        LeakyReLU(alpha=0.1))


def _net(in_shape=(64, 64, 3), n_classes=1966):
    in_layer = Input(in_shape)
    x = Conv2D_BN_Leaky(32, (3, 3))(in_layer)
    x = Conv2D_BN_Leaky(32, (3, 3))(x)
    x = MaxPool2D(2, 2, padding='same')(x)
    x = Dropout(rate=0.3)(x)

    x = Conv2D_BN_Leaky(64, (3, 3))(x)
    x = Conv2D_BN_Leaky(64, (3, 3))(x)
    x = MaxPool2D(2, 2, padding='same')(x)
    x = Dropout(rate=0.3)(x)

    x = Conv2D_BN_Leaky(128, (3, 3))(x)
    x = Conv2D_BN_Leaky(128, (3, 3))(x)
    x = Conv2D_BN_Leaky(128, (3, 3))(x)
    x = MaxPool2D(2, 2, padding='same')(x)
    x = Dropout(rate=0.3)(x)

    x = Conv2D_BN_Leaky(256, (3, 3))(x)
    x = Conv2D_BN_Leaky(256, (3, 3))(x)
    x = Conv2D_BN_Leaky(256, (3, 3))(x)
    x = MaxPool2D(2, 2, padding='same')(x)
    x = Dropout(rate=0.3)(x)

    x = Flatten()(x)
    preds = Dense(n_classes, activation='softmax')(x)
    model = Model(in_layer, preds)
    model.compile(loss="categorical_crossentropy", optimizer=SGD(), metrics=["accuracy"])
    return model

模型参考了VGGNet和keras-yolo3。在我训练过程中,起初模型深度较浅,出现严重的过拟合现象,所以除了BN层之外,还特意加上了Dropout和L2,但效果并不明显,后来考虑可能是模型的特征提取和泛化能力太差,多加了几个卷积层之后,训练结果还不错。

训练结果


跑了50个epoch,耗时约两个小时。经测试,在测试集上的Top-1错误率为10.0%

小结

最后将模型和代码进行整合。经测试,验证码整体识别成功率为72.0%,还有较大提升空间。

项目代码发布于Github:https://github.com/supervipcard/163dun-crack目前暂不打算公开h5模型文件,代码仅供参考。

点赞 分享

发表评论

共有 21 条评论

  • 回复

    xiaopang

    2019-06-07 14:00

    请问crop出来以后怎么自动标注,crop出来的图片怎么跟下面的文字对应起来,没明白,感谢

    • 回复

      超级会员卡 [博主] xiaopang

      2019-06-08 12:31

      比如说yolo检测出来4个字的坐标,这四个坐标四选三的所有组合情况逐一拿去验证,验证成功的那一组crop出来的图就对应着下面的3个字

    • 回复

      xiaopang 超级会员卡 [博主]

      2019-06-08 17:32

      嗯 我已经在试了 为什么全部组合都试了 全是返回错误的,头大了

    • 回复

      超级会员卡 [博主] xiaopang

      2019-06-08 18:18

      可能是验证接口没请求对?或者位置检测不对?排错下。

    • 回复

      xiaopang 超级会员卡 [博主]

      2019-06-08 20:23

      我发现博主 js不带轨迹的 这样也可以吗 我先跑下你原始的

    • 回复

      xiaopang xiaopang

      2019-06-08 20:39

      果然 用博主原来代码是可以的 厉害了

    • 回复

      xiaopang xiaopang

      2019-06-11 10:51

      已经搞了10w个图片,但是用自己的电脑跑简直要命啊, 1070显卡 一次要6个小时,做500次

    • 回复

      超级会员卡 [博主] xiaopang

      2019-06-11 11:42

      你的模型结构是怎样的?

    • 回复

      xiaopang 超级会员卡 [博主]

      2019-06-11 11:43

      用的不是tiny,默认的啊,怎么会这么慢

    • 回复

      超级会员卡 [博主] xiaopang

      2019-06-11 11:49

      用yolo定位和分类一起做了么,那估计一个step这耗时也差不多了

    • 回复

      xiaopang 超级会员卡 [博主]

      2019-06-11 11:51

      一开始定位和分类一起做,但是手动标注了2000个,速度还可以,效果不行,然后按博主的做,现在想要不要开个gpu训练下,还是找找其他方法

    • 回复

      xiaopang 超级会员卡 [博主]

      2019-06-11 19:55

      阿里云上开了p100 一个step也要2个多小时,这样可搞不起啊,为什么跟博主差距那么大, 难道以为我用的不是tiny的原因吗,博主是直接跑了 还是改了模型

    • 回复

      超级会员卡 [博主] xiaopang

      2019-06-11 20:03

      yolo只负责定位的话注意是单类别的,样本数1k+就够了,最好再换成tiny试试。

    • 回复

      xiaopang 超级会员卡 [博主]

      2019-06-12 10:36

      已经换成tiny 10w样本,一个step1个小时,博主你训练花了多久?

    • 回复

      超级会员卡 [博主] xiaopang

      2019-06-12 10:46

      样本数太大了,跑这么久很正常,我好像是2k样本一分钟左右吧

    • 回复

      xiaopang 超级会员卡 [博主]

      2019-06-12 10:56

      你不是说118000的训练,这是识别 ,不是定位

    • 回复

      超级会员卡 [博主] xiaopang

      2019-06-12 11:06

      你训练汉字识别也用yolo跑?这种分类任务自己搭几层cnn跑就可以了

    • 回复

      xiaopang 超级会员卡 [博主]

      2019-06-12 11:35

      我刚入门不懂啊,自己摸索,能不能学习下博主的训练代码

    • 回复

      xiaopang 超级会员卡 [博主]

      2019-06-12 11:37

      我也发现是我这个问题了,已经在尝试了,另外尝试下能不能yolo一步到位,直接定位识别

  • 回复

    wusj

    2019-03-20 17:18

    已解决,node.js安装问题,感谢!
  • 回复

    wusj

    2019-03-19 19:40

    在data = self.core.call('get_data', self.token, points),会报"execjs._exceptions.ProgramError: TypeError: 对象不支持此属性或方法",博主会出现吗?