python验证码识别之Discuz(一)

python就是这么一个语言:你越是使用,越能体会它的方便与强大。
`
在写python爬虫脚本用法总结的时候曾经提到过验证码的处理问题,Discuz验证码当时就说了google的验证码要凉拌,但是某些验证码,尽管它看起来可能很复杂,
但是却没有想象的那么难破,我就有破解成功的例子。不过那个例子并不是Discuz,当时我还觉得Discuz的验证码挺花哨的,如图:
`
前一阵simplecd的论坛老有机器人注册进来灌水一通,不甚其扰,但是也说明了discuz的验证码可能被别人吃透了,所以才会机器人猖獗,我改了验证码方式以后果然机器人就绝迹了。
`
机器人泛滥就说明这个注册机制有漏洞,那么Discuz的验证码肯定是有漏洞的,于是本人就试着也破解一下好了。

一、问题拆分

验证码破解其实可以分为两部分,第一步就是单纯的图像识别,根据图像给出验证码,第二步就是和服务器的互动。理论上来说第二步无非就是一些体力活,抓包分析验证的流程就行了,不过事实上有些网站的服务器端和浏览器端的通信搞得太复杂,经常会把我这种html三脚猫给绕晕了,所以为了简化问题,先不考虑第二步,解决了第一步再说。
`

二、验证码复杂度分析

Discuz的默认gif验证码看起来挺复杂,但是实际上却是纸老虎。
这个验证码看上去相当nice:
`
1.gif动画方式,只有一帧会停顿比较久
2.杂乱的背景,有随机的画线,有背景文字,大量色块
3.随机旋转和一定程度的宽度缩放
`
但是对计算机来说,这些都不是问题:
`
1.只要找出停顿最久的帧就可以了,gif格式本身也不怎么复杂,更何况python还有PIL这种非常方便的类库可以用,解决1几乎不费力气
`
2.背景的杂乱确实会造成一定影响,不过因为Discuz对动画都采用了相同的背景,所以消除背景噪音也不是那么困难:跟其他噪音帧比较一下就能消除噪音了
`
3.旋转和缩放对我来说确实有点麻烦,那也只是因为没学过计算机图形学,不知道有啥好的算法来反旋转而已。注意:我是说不知道好的算法,事实上烂的直观的算法我还是能想出那么几个的,效率不高那也至少对付它够用了
`
其实归根到底,好的验证码还是得像google的验证码那样扭曲才行,旋转也就那么几种角度可以选择,只要能把字母分隔开来就好对付得很,扭曲就复杂得多了,几乎不可能反扭曲,更何况google的验证码变态得连人都很难认出来。
`

三、算法

`
1.读取图片(PIL)
`
2.选取延迟最久的帧(PIL)
–2.1 获取每一帧的duration信息(Image.seek,Image.info['duration'])
–2.2 duration最大的那一帧即是
`
seccode1
3.获取验证色块的代码(0-255)
–3.1 统计一帧中相同色块的出现频率按高到低排序
–3.2 把验证帧和噪音帧的频率做比较,去除相同的部分(即噪音色块)
`
4.对每一个字母进行旋转与缩放
–4.1 选择一个顶点,做射线
–4.2 找到和字母相切的两条射线
–4.3 把图像旋转射线与水平线的偏离角度(Image.rotate)
–4.4 缩放到固定大小
`
5.把处理后字母和特征库进行比较
–5.1 用最简单的距离比较法
–5.2 特征库就多找几幅图搞定
`

四、代码(实现到去噪这一步)

实际实现时,发现Discuz很狡猾地把同一个颜色用了不同的代码来记,算法第三步实现起来还要比较调色板颜色,比较麻烦,这里用了另一个基于距离的去躁方式:算出相同色块的中心点,如果有点在规定的半径之外就是噪声
`

DIAMETER = 50
def isnoise(points):
    '''Determine if the points are noisy points'''
    # center point
    center = [0,0]
    for point in points:
        center[0]+=point[0]
        center[1]+=point[1]
    center[0]/=len(points)
    center[1]/=len(points)
 
    # point out of distance: noise
    for point in points:
        distance = (point[0]-center[0])*(point[0]-center[0])+(point[1]-center[1])*(point[1]-center[1])
        if distance > DIAMETER*DIAMETER/4:
            return True
 
    return False

`
载入图像是很简单的:

    import Image
    DURATION = 1000
 
    im = Image.open(fname)
    frames = []
    key = 0
    for i in range(10):
        im.seek(i)
        frames.append(im.load())
        if im.info['duration']>DURATION:
            key = i
    frame = frames[key]

`
载入后统计不同颜色的出现频率:

    WIDTH = 100
    HEIGHT = 50
 
    d = {}
    for i in xrange(WIDTH):
        for j in xrange(HEIGHT):
            k = frames[key][i,j]
            if d.has_key(k):
                d[k][0] += 1
                d[k].append((i,j))
            else:
                d[k] = [1,(i,j)]
    topd = sorted(d.items(),cmp=lambda x,y:cmp(y[1][0],x[1][0]))

`
然后就是把噪音标为0,其他保留,因为只有四个非噪音,只要找到4个就break返回

    for i in xrange(WIDTH):
        for j in xrange(HEIGHT):
            frame[i,j]=0
 
    found = 0
    for (k,v) in topd:
        if not isnoise(v[1:]):
            for point in v[1:]:
                frame[point[0],point[1]]=k
            found += 1
        else:
#           print k,'is noise'
            pass
        if found == 4:
            break
    return frame

`
最后是print函数,打印出来看看啥样子了,也可以用Image.show函数,但是我还是喜欢print出来

def printframe(frame,code=-1):
    for i in xrange(WIDTH):
        for j in xrange(HEIGHT):
            if (code == -1 and frame[i,j] !=0) or (code != -1 and frame[i,j]==code) :
                print '*',
            else:
                print '-',
        print

`
最后给个到这一步的代码打包,需要安装PIL
seccode1.tar.gz

This entry was posted in Discuz, python, 编程 and tagged , , , . Bookmark the permalink.

14 Responses to python验证码识别之Discuz(一)

  1. 他这个没有扭曲,只有旋转,其实旋转的角度也是在一定范围内的,完全可以多抓几个样本,每个角度都建立特征库,一对比,就出来了,比切线算法效率还要高一点。
    验证码如果有随机扭曲和连接,基本破解无望。
    但是现在很多打码站,qq,google等验证码都有人接活,一组验证码不到2分钱,如果再适当扣量,打一组验证码一分钱都不要。
    这年头还是手机验证管用点。

  2. yegong says:

    曾经破国discuz的验证码,那个时候还不是gif的动画
    discuz的字母右下侧有一个阴影
    好玩的是,那个阴影的颜色正好和当前颜色互补…
    所以你去噪的部分可以做得更简单一些
    把旋转还原的话,也可以借助阴影来定位
    不过这样的方法不通用,也没多少意思

    • observer says:

      这一版的discuz验证码似乎没有阴影了,我那基于距离的去噪已经过于简单了,后来发现其实这个去噪算法很不通用,碰上复杂一点的立即歇菜,我有时间要重写一下,这个系列估计会拖很长。

  3. bones7456 says:

    自己写OCR引擎也太累了吧,看看这个:
    http://li2z.cn/2009/12/29/tesseract-ocr/
    这样的gif取出帧以后,再交给它,应该问题不大。

    BTW: 这里的文章我都挺喜欢的,交个朋友吧,我加了贵站的友链了,如果可以加我的Gtalk或者msn吧,呵呵!

    • observer says:

      Gtalk怎么加@@
      多谢提供tesseract,不过我测了一下这个东东在对付Discuz这种验证码不太准,毕竟ocr是为了从扫描的图片提取文本,用来对付有旋转的文本就很吃力,应用不同啊。而且我也想自己实现一下简单的图片识别看看,所以还是会自己做。

  4. tyz says:

    如果碰到一个字符有好几种颜色的,比如图片上有随机的反色区域时,应该就很难取出来了

    算法的第4部分,如果这样做的话,顶点会比较难找,对于B和M这样的字符,你可以用水平线和垂直线找到,但C这种圆形的就没那么容易了。

    而且如果旋转算法比识别算法还慢的话,还不如直接用识别好了。这个旋转也不复杂,顶多正负45度,就以5度为一个单位的话,增加的特征库也很有限。

    不过我怀疑discuz的破解应该属于暴力破解,因为那个验证码输进去后会有正确和错误的提示,如果那个函数没有次数限制的话那就。。。具体还要看看代码是怎么写的

    我不懂图像处理和python,所以以上多为扯淡。。。

    • observer says:

      好建议,我也发现圆形图形旋转起来太难了,不如就多弄几个特征,不过5度好像比较不准,可能得2-3度才行,一下子要增加40倍的运算量@@

  5. py says:

    把文字分布在gif的各帧里面,靠快速移动造成的视觉残留做验证码,这样就难很多了吧……

  6. 个人经验,验证码识别最困难的在两点,一个是去噪,一个是切块。只要卡住其中一步,基本就自动识别无望了。
    如果没有粘连,即便再随机扭曲,他也是有一个限度的,大不了多采点样本,进行模糊识别,最终还是可以自动识别的。
    google,qq,淘宝的验证码,基本没有噪点,也不花俏,看着蛮简单,但是他随机扭曲粘连在一起,无法正确切块,就无法自动识别。

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="">