目录

  • 案例一二三
  • 四、验证码的逆向(⭐⭐⭐)
  • 1、案例11_普通的滑块验证码
  • 2、案例插曲_滑块验证码识别3种方法
  • (1)滑块缺口颜色为单一颜色时用rgb判断
  • (2)滑块缺口颜色非单一值用模板匹配
  • (3)滑块带有缺口的图与原始图进行比较
  • 3、案例12_jy的滑块验证码
  • (1)大概的流程了解
  • (2)乱序图片的还原逆向
  • (3)案例分析思路


案例一二三

四、验证码的逆向(⭐⭐⭐)

1、案例11_普通的滑块验证码
  • (1)案例网址:点击网址
  • java模拟识别滑动验证码 滑块验证码js逆向_ide


  • (2)案例反爬点:验证码缺口的识别,以及识别偏移量的js加密,cookie反爬
  • (3)案例分析:cookie只需要首次请求时把set-cookie拿到即可,slidevalue是滑块缺口加密,由于校验图片时是xhr请求,所以我们这里直接通过监听xhr断点,然后追溯堆栈找加密位置
  • 通过堆栈很快找到加密位置,和encode64()这个js函数相关,加密参数就是滑块距离,至此js逆向已完成,接下来我们只要识别滑块缺口就行
2、案例插曲_滑块验证码识别3种方法
  • 滑块识别分析的3种方法:
  • 滑块缺口是单一颜色用连续rgb像素点判断
  • 通过模板匹配cv2.matchTemplate()来确定缺口位置
  • 带有缺口的背景图和原始图进行对比
(1)滑块缺口颜色为单一颜色时用rgb判断
  • 滑块缺口是单一的颜色,此处的白色rgb为固定值,所以我们只需要判断当前像素点连续39个点和对角线连续39个点的rgb为固定值,选择39个像素点是因为滑块长宽差不多是40
  • 此处rgb固定值白色[245, 250, 255],即可定位到缺口x的位置 (凸出部分朝向会影响实际x的识别距离);当滑块凸出部分朝向左的时候,实际的缺口距离应当减去凸出来的部分,此处缺口应该是x-10
  • java模拟识别滑动验证码 滑块验证码js逆向_ide_02

    java模拟识别滑动验证码 滑块验证码js逆向_ide_03

  • 当然有的其它滑块,初始距离可能有个6~10的间隙,应当具体案例具体分析,此处初始距离为0,如果有间隙,我们识别出来的距离还应当减去初始间隙,如某验滑块
  • java模拟识别滑动验证码 滑块验证码js逆向_滑块_04

  • 滑块识别代码截图
  • 使用PIL.Image.open读取出来是RGB格式
  • 通过np.array(slider_image)以3通道按行展开读取rgb数组
  • 通过np.all()判断给定轴向上的所有元素是否都为True,如是否都为固定的rgb值
  • bulge_offset为凸出的宽度, 默认凸出只有上,左,下 3个方向,如果有朝向为左,实际距离应当减去凸出部分
  • 滑块识别代码截图,通过灰度后的判断,rgb不完全统一,但是灰度后的单一通道大于一个值
def get_slider_offset_method1(pic_content, fixed_rgb_l=250):
    """
    返回缺口距离
    :param pic_content: 滑块
    :param fixed_rgb_l: 单一固定的rgb,转灰度后只有一个通道,需要观察,此种缺口非单一固定rgb,但是rgb转灰度后都大于250
    :return:
    """
    slider_image = Image.open(BytesIO(pic_content))
    slider_image = slider_image.convert('L')
    array_img = np.array(slider_image)
    height, width = array_img.shape
    for y in range(height - 40):  # 从上往下
        for x in range(40, width - 40):  # 从左往右
            if np.all(array_img[y:y + 1, x:x + 39] >= fixed_rgb_l):  # 横向的连续39个元素点的rgb值大于250
                list_side = [array_img[y, x]]
                for side in range(1, 39):
                    list_side.append(array_img[y + side][x + side])
                return x
    return 0
(2)滑块缺口颜色非单一值用模板匹配
  • 当缺口部分颜色非单一值时,我们可以用cv2.matchTemplate()模板匹配方法来进行缺口识别,模板匹配的意思就是拿小图到大图里面去寻找和它差不多的地方,然后确定缺口
  • 通过cv2.matchTemplate()最后的效果如下,cv2.matchTemplata(img_big,img_temp,cv2.method): img_big:在该图上查找图像, img_temp:待查找的图像,模板图像, method: 模板匹配的方法
  • 滑块识别代码截图
  • 彩色图像是三通道(RGB),也就是Red,Green,Blue 三个通道
  • 灰度图是单通道,单通道中的每个像素的值介于0~255之间,代表黑色(0)到白色(255)之间的灰度程度
  • 黑白图像指的是二值图,每个像素非黑即白,黑是0,白是1,通常会用灰度图来表示二值图,黑是0,白是255
  • 使用cv2.imread(path, flag):读进来直接是BGR格式,flag=0(8位深度,1通道,灰度图),flag不填默认3通道
  • 使用cv2.cvtColor(slider_img, cv2.COLOR_BGR2GRAY):是颜色空间转换函数,slider_img是需要转换的图片,cv2.COLOR_BGR2GRAY转成灰度格式
  • cv2.Canny(img, minVal, maxVal, apertureSize=3):第一个参数是原图像,第二个和第三个参数分别是两个阈值,minVal 和 maxVal,来确定真实和潜在的边缘,apertureSize是sobel算子(卷积核)大小;调整minVal,检测出的边缘会增多,扩大apertureSize算子,会获得更多的细节,参考文档理解
  • 模板匹配cv2.matchTemplate(background_pic, slider_pic, cv2.TM_CCOEFF_NORMED):此处通过归一化相关系数法进行匹配
(3)滑块带有缺口的图与原始图进行比较
  • 如果你同时有带缺口的图片,和不带缺口的背景图,此时可以通过判断两张图片的像素点rgb之差,设定一个阈值差,如果在阈值差内认为图片像素点一致,如果超过这个阈值差则认为是缺口位置
  • 具体代码实现:这里设置了阈值60为限制来判断缺口点,具体案例具体操作
from PIL import Image


def get_slider_offset_method3(pic_path, cut_pic_path, threshold=60):
    """比较两张图片的像素点RGB的绝对值是否小于阈值60,如果在阈值内则相同,反之不同"""
    pic_img = Image.open(pic_path)
    cut_img = Image.open(cut_pic_path)
    width, height = pic_img.size
    for x in range(40, width - 40):  # 从左往右
        for y in range(5, height - 10):  # 从上往下
            pixel1 = pic_img.load()[x, y]
            pixel2 = cut_img.load()[x, y]
            if abs(pixel1[0] - pixel2[0]) < threshold and abs(pixel1[1] - pixel2[1]) < threshold and abs(pixel1[2] - pixel2[2]) < threshold:
                continue
            else:
                return x
3、案例12_jy的滑块验证码
  • 案例网址 :aHR0cHM6Ly93d3cuZ2VldGVzdC5jb20vc2hvdw==
(1)大概的流程了解
  • 一般是两次get+ajax,第一次get+ajax获得challenge使其有效,第二次get+ajax即获取验证码与校验验证码
  • 每个应用网站都会对应一个唯一的id值即gt,在获取验证码之前会初始化并校验获得第一个有效challenge,携带有效的challenge后可以获得验证码
  • 验证码校验的过程会涉及到w参数,w参数逆向是关键,校验通过则返回validate值
  • 整个流程主要涉及如下4个请求包,具体网站具体分析
  • 第一个链接初始化获得challenge,gt值
  • 第二个链接ajax.php校验challenge,此时出现一次w值(可置空,可固定,可能要js生成),具体网站具体分析,本次案例此处w值不研究,直接置空
  • 第三个链接get.php携带有效challenge请求获得图片链接,该图片是个乱序图片,还需要还原;如果有返回challenge则取代前面的第一个challenge,同时请求图片的响应会返回c和s参数,在后面研究w参数加密会用到这两个
  • 第四个链接ajax.php校验图片,携带challenge,w值,w值的加密会涉及到轨迹信息,轨迹是一难点,随意生成的轨迹并不可用,需尽量趋向人为的轨迹曲线,以及前面的c和s参数校验通过则返回validate
  • 距离识别错误返回{“success”: 0, “message”: “fail”}
  • 距离正确但轨迹异常则返回{“success”: 0, “message”: “forbidden”}
  • 校验通过则返回{success: 1, message: “success”, validate: “f98af504ec3208dc19911b0de0b083c7”, score: “3”}
(2)乱序图片的还原逆向
  • 从网页拿到的图片是乱序的,还原后如下
    >>>>>>>>
  • 逆向分析:由于验证码图片是通过Canvas画布画出来的,所以我们这里直接通过监听Canvas下断点,然后就可以调试即可发现还原乱序代码的关键部分
  • getImageData(x,y,width,height):开始复制的左上角位置的 x 坐标、左上角位置的 y 坐标,width要复制的矩形区域的宽度、height要复制的矩形区域的高度
  • putImageData(imgData,x,y,dirtyX):将图像数据(从指定的 ImageData 对象)放回画布上,左上角位置的 x 坐标、左上角位置的 y 坐标,
  • 原图是260×160的图片,由乱序的2×26份的10×80小图片拼接而成,根据数组依次判断去乱序的图片里面去取这52份小图片然后依次拼接则形成原始图片
  • 还原代码:按照如上还原逻辑,代码如下
  • Image.new(mode, (width, height)):创建新图片
  • image.crop(x1, y1, x2, y2):左上角点到右下角点进行切割图片
  • image.paste(im, box):box为要粘贴到的区域,box类型为(x1, y1):将im左上角对齐(x1,y1)点,其余部分粘贴,超出部分抛弃
from PIL import Image

image = Image.open('./luanxu.png')
standard_img = Image.new("RGBA", (260, 160))
position = [39, 38, 48, 49, 41, 40, 46, 47, 35, 34, 50, 51, 33, 32, 28, 29, 27, 26, 36, 37, 31, 30, 44, 45, 43, 42, 12, 13, 23, 22, 14, 15, 21, 20, 8, 9, 25, 24, 6, 7, 3, 2, 0, 1, 11, 10, 4, 5, 19, 18, 16, 17]
s, u = 80, 10
for c in range(52):
    a = position[c] % 26 * 12 + 1
    b = s if position[c] > 25 else 0
    im = image.crop(box=(a, b, a + 10, b + 80))
    standard_img.paste(im, box=(c % 26 * 10, 80 if c > 25 else 0))
standard_img.save("./restore_bg.png")
(3)案例分析思路
  • 目标研究参数,第4个链接的w参数,即校验图片的那个w参数,主要会涉及rsa+aes加密,aes的key参数是随机生成的,会通过rsa加密传给服务器
  • 确定了目标研究参数w参数,可以采用调用栈回溯的方法,这里由于js文件做了一个unicode混淆字符串加密,所以我们直接通过搜索w的unicode码"\u0077":来寻找加密位置并打断点, w=_+s,所以只要研究s和_即可
  • 我们先研究s参数,打入断点进入psgR这个函数
  • s参数:发现最后返回的是r参数,而这个r参数的加密方式本质是个rsa加密,rsa加密需要两个参数:公钥+待加密文本,那只需要调试进去找到公钥即可(此处省略,自行调试)
  • s参数:那待加密文本即aes_key是怎么生成的,我们可以进入t[MIDa(753)]这个函数逐步看一下,一下就发现了加密逻辑,就是4个S4()函数组成
  • s参数:用python还原rsa加密,传入公钥和待加密文本aes_key即可
import rsa
from binascii import b2a_hex


def rsa_encrypt_text(key, _text: str):
    """
    RSA加密
    :param key: 公钥的参数
    :param _text: 待加密的明文
    :return: 加密后的数据
    """
    e = int('010001', 16)
    n = int(key, 16)
    pub_key = rsa.PublicKey(e=e, n=n)
    return b2a_hex(rsa.encrypt(_text.encode(), pub_key)).decode()
  • _参数:_参数和u参数有关,先看u参数,这个u参数分解下,传入了带加密文本o参数(传入前进行了字典转字符串操作即JSON.stringify())和前面随机生成的aes_key,其中这个o参数我们后面再分析
  • _参数的u参数:深入u参数加密函数ee[rndd(365)]再具体分析,对aes的加密结果进行了一个转数组的操作
  • _参数的u参数:继续深入看aes加密是CBC加密模式,所以只要3个参数:key,iv,待加密文本,key就是前面所说的aes_key,待加密文本是传进来的(后面具体分析),iv参数调试找到为"0000000000000000",js代码实现,那u参数已研究完
  • _参数:是对前面的u参数又做了一层加密,这个加密也比较好扣,省略

  • w参数:w=_+s,前面_和s参数合成即可,至此w参数加密已完成研究,细节部分继续往后看
  • w参数>_参数>u参数>o参数分析:可变参数分析,我们着重看下aa参数和userresponse参数
  • aa:对轨迹和响应的c和s参数进行了加密
  • ep:涉及v和tm,v是js文件的版本,tm和window[“performance”][“timing”]相关,可以根据Performance.timing 时间产生的先后顺序以及时间间隔,用当前的时间戳减去相应的值来模拟
  • imgload:图片加载时间,随机生成
  • passtime:轨迹滑动总时长
  • rp:对gt、challenge、passtime进行了md5加密
  • userresponse:对滑块距离和challenge进行了加密
  • its5:随机参数影响不大
  • aa参数:aa参数unicode码"\u0061\u0061",我们发现是上一个函数传过来的,也就是现在的t值
  • aa参数:往前回溯看上一个函数,发现对应的是f值,也就是aa现在就是f值,只要看f值是怎么生成的,此处打断点,重新调试,可见f是一个函数的返回结果,传入的三个参数分别对应着:轨迹加密、响应的c、s参数
  • aa参数r[LsRU(1063)][LsRU(1047)]()就是对轨迹加密的,其中r[LsRU(1063)]就是传入的数组轨迹,进入SOQd就可以看具体的轨迹加密逻辑,扣完SOQd()这个函数,轨迹加密就有了
  • aa参数的轨迹: 轨迹生成传入,此处需注意你的轨迹是叠加传入的值,还是已经做了减法的值,这涉及到你要不要再加个e函数进行差减;轨迹生成可网上自行查找
  • 如我传的xyt_list是这种格式(x和t已经做了差):[[28,34,0],[1,0,45],[3,0,63],[4,-1,8],[4,0,9],[3,0,7],[4,0,9]…
  • 而非这种格式(x和t都是累加的):[[-31,-19,0],[0,0,0],[1,0,36],[2,0,44],[2,0,52],[4,0,61],[6,0,69],[8,0,77],[14,0,85],[17,0,92],[21,1,99],[25,2,106],…
  • userresponse参数:U函数加密结果返回而得,传入滑块距离和challenge,去掉冗余代码,U函数可以直接扣出来
  • rp参数:对gt、challenge、passtime进行了md5加密
  • 最终效果