使用gan破解极验滑动验证码
本文提供一个使用GAN破解滑动验证码的思路,完整代码由于利益关系,没有办法公开。
极验的滑动验证码原理
捕捉滑动轨迹,通过分类器判断动作是人做的还是机器做的。
极验是没有办法捕获我们完整的动作的,它会在几个时间点,捕捉滑动块的位置,生成一个序列发送给服务端做检验。这也为我们提供了破解的思路。
破解方法
破解方法很直观,我们生成一组滑动块位置,然后以0.3秒的间隔依次滑动过去,骗过极验的后台。
1,获取原始图片
极验验证码的原始图片会和带缺口的图片一起传过来,但是并不会显示给用户,虽然它就在页面上,但是被设置为不显示。
获取方法也很简单:
仔细分析极验的验证码,你会发现,在标签里面有一个叫geetest_canvas_fullbg 的cavas非常奇怪,鼠标放上去会有完整的图片展示出来。
废话就不多说了,这就是我们要的原始图片。
2,获取带缺口的图片
3,计算滑动距离
获取原始图片和缺口图片以后,就可以开始计算需要滑动的距离。这里,计算距离方法如下:
依次遍历图片的每一个像素,找到原始图片和带缺口图片差异较大的点。这些点都有可能师缺口位置的点
def get_distance(image1, image2, left=57, threshold=60, deviation=6):
i = 0
for i in range(left, image1.size[0] - 40):
for j in range(image1.size[1] - 40):
rgb1 = image1.load()[i, j]
rgb2 = image2.load()[i, j]
res1 = abs(rgb1[0] - rgb2[0])
res2 = abs(rgb1[1] - rgb2[1])
res3 = abs(rgb1[2] - rgb2[2])
if not (res1 < threshold and res2 < threshold and res3 < threshold):
# image2.crop((i,j,i+40,j+40)).save('tmp_cod1.png')
# image1.crop((i,j,i+40,j+40)).save('tmp_cod2.png')
if is_sub_cod(i, j, image1, image2, threshold)://有可能为缺口,需要进一步确认
tmp_imge = image2.crop((i, j, i + 40, j + 40))
tmp_imge.save('tmp_cod.png')
print(i)
return i
break
logging.debug("未识别出验证码中的不同位置, 或图片定位出现异常")
print(i)
return 260
确定了候选点以后,需要进行进一步的确认,确认方法如下:
def is_sub_cod(left, top, image1, image2, threshold):
count = 0
all = 40 * 40
for i in range(left, left + 40):
for j in range(top, top + 40):
rgb1 = image1.load()[i, j]
rgb2 = image2.load()[i, j]
res1 = abs(rgb1[0] - rgb2[0])
res2 = abs(rgb1[1] - rgb2[1])
res3 = abs(rgb1[2] - rgb2[2])
if not (res1 < threshold and res2 < threshold and res3 < threshold):
count += 1
print("count is {0}".format(count))
if count / all >= 0.6:
return True
else:
return False
到了这里,我们就可以获得需要滑动的距离了。
如果你直接使用这个距离,模拟浏览器操作,你会悲惨的发现,验证通过不了。
极验的后端会识别出哪些不是人进行的操作。接下来,我们就尝试破解行为验证。
4,随机生成数据,产生训练样本
为了能够训练模型,首先我们需要获得训练数据。这里,训练数据的获得有两种方法:
- 人为拖动滑块,记录滑块的位置点。
- 随机生成一些点,尝试进行极验的验证,记录验证成功和失败的操作。
我们这里采用第二种方法。
这里会有一个问题,就是尝试次数过多的化,会被网站封锁ip,所以这里提示大家一个好方法:
申请极验的试用,然后在本地进行数据采集,就不会有被封ip的风险了。
5,训练生成模型
有了数据以后,我们就可以训练模型了。使用tesorflow构建一个gan生成神经网络:
在构建模型时候,主要踩到坑如下:
- 不需要使用rnn,因为我们最终生成的序列长度不会超过10位,事实上大部分都只有2-3位,多的部分用0补齐
- 不要在判别或者生成器最后使用sigmod函数,如果使用了,那么你的模型很可能不会收敛
- 不要使用批量训练,批量计算时会将梯度平均化,会导致训练结果全都是一个值
- 构建生成模型:
Gh1 = tf.nn.leaky_relu(tf.matmul(self.Z, G_W1z) + tf.matmul(self.Y, G_W1y) + G_b1)
Gh2 = tf.nn.leaky_relu(tf.matmul(Gh1, G_W2) + G_b2)
Gh3 = tf.nn.leaky_relu(tf.matmul(Gh2, G_W3) + G_b3)
self.G = tf.matmul(Gh3, G_W4) + G_b4
- 构建判别模型
Dh1 = tf.nn.leaky_relu(tf.matmul(self.X, D_W1x) + tf.matmul(self.Y, D_W1y) + D_b1)
Dh2 = tf.nn.leaky_relu(tf.matmul(Dh1, D_W2) + D_b2)
Dh3 = tf.nn.leaky_relu(tf.matmul(Dh2, D_W3) + D_b3)
self.D = tf.matmul(Dh3, D_W4) + D_b4
- 判别生成的样本
DGh1 = tf.nn.leaky_relu(tf.matmul(self.G, D_W1x) + tf.matmul(self.Y, D_W1y) + D_b1)
DGh2 = tf.nn.leaky_relu(tf.matmul(DGh1, D_W2) + D_b2)
DGh3 = tf.nn.leaky_relu(tf.matmul(DGh2, D_W3) + D_b3)
self.DG = tf.matmul(DGh3, D_W4) + D_b4
- 构建训练器
self.D_solver = tf.train.AdamOptimizer(learning_rate=0.1).minimize(self.Dloss, var_list=theta_D)
self.G_solver = tf.train.AdamOptimizer(learning_rate=0.01).minimize(self.Gloss, var_list=theta_G)
- 训练模型
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
counter = 0
g_count = 0
d_count = 0
start_dis = True
for e in range(epochs):
# new_state = sess.run(model.initial_state)
loss = 0
for x, y in get_batches(*read_data(data_path), batch_size)://这里一定要一条一条数据的训练,血的教训
counter += 1
start = time.time()
feed = {
model.X: x,
model.Y: y,
model.Z: sample_Z(batch_size, 100)
}
if start_dis:
_, d_loss_crr, d, dg, g_x, g_restract, t_x, t_y, g_y = sess.run(
[model.D_solver, model.Dloss, model.D, model.DG,
model.G_x, model.G_restrct, model.X, model.Y, model.G_y],
feed_dict=feed)
d_count += 1
if d_count < 10:
pass
elif d_loss_crr < 0.1 or d_count > 100:
start_dis = False
g_count = 0
end = time.time()
if (d_count %1 ==0):
print('Epoch:{}/{}..'.format(e + 1, epochs),
'Training Step:{}...'.format(counter),
'Discriminater loss:{:.4f}...'.format(d_loss_crr),
'{:.4f} sec/batch'.format((end - start)),
'd_count is {}'.format(d_count))
else:
_, g_loss_crr = sess.run([model.G_solver, model.Gloss],
feed_dict=feed)
g_count += 1
if g_loss_crr < 1 or g_count > 100:
start_dis = True
d_count = 0
saver.save(sess, 'checkpoints2/i{}.ckpt'.format(counter))
6,使用生成模型,进行验证码破解
使用生成的数据进行破解比较简单,就不细说了。