有次帮某人发spam,找人来填调查。写了个自动评论的脚本,但是发出一定数量的评论之后就会遭遇验证码,于是决定破解之。
思路也是一般的转化切割比对,成功率不是很高,不过重试几次也是可以用的。
虽然已经控制好了频率,不过最后还是被管理员发现了,直接封了帐号(好在是临时注册的),再注册再封,后来干脆封IP,

于是不得不给我的vps换了个ip(也好在是免费的),杯具。
闲话休说,言归正题。

首先是需要取得验证码的样本,以作训练特征之用。而要取得验证码,首先要模拟登录的请求:

usr = 'xx' psw = 'oo' 
resp = urllib2.urlopen('https://login.sina.com.cn/sso/login.php?username=%s&password=%s&returntype=TEXT' %
 (   usr, psw)) cookie = Cookie.SimpleCookie(resp.headers['set-cookie']) headers = {   'Referer': 'http://t.sina.com.cn',   'Cookie': cookie_header(cookie), 
  'User-Agent': 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.3pre) Gecko/20100405 Firefox/3.6.3plugin1', } def cookie_header(cookie):   
ret = ''   for v in cookie.values():     ret += "%s=%s; " % (v.key, v.value)   return ret

headers就是后续的请求中,需要传递的参数了。
取回一些验证码样本:

for i in xrange(100):  
 req_img = urllib2.Request('http://t.sina.com.cn/pincode/pin.php?lang=zh&r=%d&rule' % int(time() * 1000),     
headers = headers)   res_img = urllib2.urlopen(req_img)   f = open('xinlang_pincode/%d.png' % i, 'wb')   f.write(res_img.read())   f.close()

有一些验证码的回答是中文,中国首都什么的,这些不处理,直接返回失败。因为可以重复获取重新识别,不成问题的。下面是处理算术问题验证码的方法:
先进行变换处理:

from PIL import Image, ImageFilter, ImageEnhance file = 'xinlang_pincode/0.png'   im = Image.open(file) im = im.convert()   
enhancer = ImageEnhance.Brightness(im) im = enhancer.enhance(2.0) #加亮,效果见图1 enhancer = ImageEnhance.Contrast(im) im = enhancer.enhance(4) 
#提高对比度,效果见图2 im = im.convert('1') #二值化,效果见图3 im = im.filter(ImageFilter.MedianFilter) #中值去噪,效果见图4 im.show() 
#调用xv命令来显示图片,方便debug

图1:

图2:

图3:

图4:


这样处理过之后,图片背景中的色块被过滤掉,杂点也被过滤掉,而数字的形状也没有太大的损失。

下面是分解字符,也就是将每一个数字或者+-*等符号分解出来:

imim = im.load() WIDTH = 250 HEIGHT = 50   i = 0 has_start = False chars = [] while i < WIDTH:   all_none = True   for j in xrange(HEIGHT):     if imim[i, j] != 255:  
     all_none = False   if all_none:     if has_start:       end_x = i       has_start = False       char = im.crop((start_x, 0, end_x, HEIGHT))   
    char.show() #到这一步的效果见图5         charchar = char.load()       width = end_x - start_x       y1 = 0       y2 = HEIGHT - 1       all_none = True      
 while all_none:         for ii in xrange(width):           if charchar[ii, y1] != 255:             all_none = False         y1 += 1       all_none = True       while all_none:        
 for ii in xrange(width):           if charchar[ii, y2] != 255:             all_none = False         y2 -= 1       char = char.crop((0, y1 - 1, width, y2 + 2))      
 char = char.resize((20, 20)) #将图片缩放到统一的大小       char.show() #到这一步的效果见图6         chars.append(char)   else:     if not has_start:      
 start_x = i       has_start = True   i += 1

图5:字符被独立分割开

图6:字符上下两边的空白被去掉,且缩放到同一大小


这一步得到的chars是下面要用到的。

然后是训练,也就是形成特征库。特征库规模越大,识别率也越高。不过训练起来也挺累的,有几十上百条也就好了。至少0到9和+-*=等几个字符的特征都要有:
file = open('xinlang.img', 'a') for c in chars:   nstr = ''   im_loaded = c.load()   for x in range(20):     for y in range(20):       if im_loaded[x, y] == 255:      
   nstr += '0'       else:         nstr += '1'   c.show()   n = raw_input('? ')   file.write(nstr+':'+n+'\n') file.close()

这里的特征,就是直接把每一个像素的信息,用0和1组成的字符串表示。
训练的结果是一个文本文件,记录了对应的特征和字符,用于下面的比对。

比对函数:

pattern = [] for l in open('xinlang.img', 'r').read().split('\n'):   pattern.append(l.split(':')) del pattern[-1]   def what(img):   im = img.load()   nstr = ''  
 for x in xrange(20): #生成目标图像的特征字符串     for y in xrange(20):       if im[x, y] == 255:         nstr += '0'       else:         nstr += '1'   minmin = 400  
 res = None   for p in pattern:     cur = 0     for i in xrange(400):      
 if nstr[i] != p[0][i]: #比对每一个像素,如果不相同,则增加差异值         cur += 1     if cur < = minmin: #记录下差异值最小时所对应的字符      
 minmin = cur       res = p[1]   return res

最后测试一下:
for c in chars:   print what(c),

结果:

可以看到18+18这些字符可以成功识别。那为什么=和?识别不了呢?因为我没有训练这两个字符,而=和?都和数字2的特征最接近 -_-| ,

这个验证码还是挺好破解的,因为字符之间间距很大,而且没有旋转,没有扭曲,不需要多少变换就能得到可用的结果。像google的那种,就完全没法可想了。
Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐