千鋒教育-做有情懷、有良心、有品質(zhì)的職業(yè)教育機(jī)構(gòu)

手機(jī)站
千鋒教育

千鋒學(xué)習(xí)站 | 隨時(shí)隨地免費(fèi)學(xué)

千鋒教育

掃一掃進(jìn)入千鋒手機(jī)站

領(lǐng)取全套視頻
千鋒教育

關(guān)注千鋒學(xué)習(xí)站小程序
隨時(shí)隨地免費(fèi)學(xué)習(xí)課程

當(dāng)前位置:首頁  >  技術(shù)干貨  > 爬蟲之js逆向解析(滑塊驗(yàn)證碼)

爬蟲之js逆向解析(滑塊驗(yàn)證碼)

來源:千鋒教育
發(fā)布人:qyf
時(shí)間: 2022-09-19 17:44:53 1663580693

  為什么要做逆向

  動(dòng)態(tài)網(wǎng)頁爬蟲一般可分為兩種:Selenium爬取和接口爬取。兩種方式各有優(yōu)缺點(diǎn):前者我們己經(jīng)介紹了selenium的使用和驗(yàn)證碼、滑塊的使用,其雖然可以很好地處理網(wǎng)頁異步加載問題,但面對(duì)大型爬蟲任務(wù)時(shí),效率還是比較低的;后者雖然爬取速度較快,但請(qǐng)求參數(shù)很可能是動(dòng)態(tài)變化的,這時(shí)就需要利用一些前端的知識(shí),重新構(gòu)造參數(shù),整個(gè)過程通常稱為JS逆向。先來看一下簡(jiǎn)單的請(qǐng)求:

Picture

  但是往往在我們編寫爬蟲時(shí),可能會(huì)碰到以下兩種問題:

  • 所需要爬取的數(shù)據(jù)在網(wǎng)頁源代碼中并不存在;

  • 點(diǎn)擊下一頁跳轉(zhuǎn)頁面時(shí),網(wǎng)頁的URL 并沒與發(fā)生變化;

  造成這種問題原因是,你所正在爬取的頁面采取了動(dòng)態(tài)加載的方式

  動(dòng)態(tài)加載網(wǎng)頁其顯示的頁面則是經(jīng)過Javascript處理數(shù)據(jù)后生成的結(jié)果,可以發(fā)生改變。

  JavaScript是一種運(yùn)行在瀏覽器中的解釋型編程語言,JavaScript非常值得學(xué)習(xí),它既適合作為學(xué)習(xí)編程的入門語言,也適合當(dāng)作日常開發(fā)的工作語言。JavaScript可以收集用戶的跟蹤數(shù)據(jù),不需要重載頁面即可直接提交表單,可在頁面中嵌入多媒體文件,甚至可以運(yùn)行網(wǎng)頁游戲等。在很多看起來非常簡(jiǎn)單的頁面背后通常使用了許多JavaScript文件。比如:

Picture(1)

  這些數(shù)據(jù)的來源有多種,可能是經(jīng)過Javascript計(jì)算生成的,也可能是通過Ajax加載的。Ajax = Asynchronous JavaScript and XML(異步的 JavaScript 和 XML),其最大的優(yōu)點(diǎn)是在不重新加載整個(gè)頁面的情況下,可以與服務(wù)器交換數(shù)據(jù)并更新部分網(wǎng)頁的內(nèi)容。

  逆向工程

  對(duì)于動(dòng)態(tài)加載的網(wǎng)頁,我們想要獲取其網(wǎng)頁數(shù)據(jù),需要了解網(wǎng)頁是如何加載數(shù)據(jù)的,該過程就被成為逆向工程。

  對(duì)于使用了Ajax 請(qǐng)求技術(shù)的網(wǎng)頁,我們可以找到Ajax請(qǐng)求的具體鏈接,直接得到Ajax請(qǐng)求得到的數(shù)據(jù)。

  需要注意的是,構(gòu)造Ajax請(qǐng)求有兩種方式:

  • 原生的Ajax請(qǐng)求,會(huì)直接創(chuàng)建一個(gè)XMLHTTPRequest對(duì)象。

  • 調(diào)用jQuery的ajax()方法。一般情況下,$.ajax()會(huì)返回其創(chuàng)建的XMLHTTPRequest對(duì)象;但是,如果$.ajax()的dataType參數(shù)指定了為script或jsonp類型,$.ajax()不再返回其創(chuàng)建的XMLHTTPRequest對(duì)象。

  JQuery補(bǔ)充:

  在大型互聯(lián)網(wǎng)公司的不斷推廣下,JavaScript生態(tài)圈也在不斷的完善,各種類庫(kù)、API接口層出不窮。

  jQuery是一個(gè)快速、簡(jiǎn)潔的JavaScript框架,是繼Prototype之后又一個(gè)優(yōu)秀的JavaScript代碼庫(kù)(或JavaScript框架)。jQuery設(shè)計(jì)的宗旨是“Write Less, Do More”,即倡導(dǎo)寫更少的代碼,做更多的事情。

  對(duì)于這兩種方式,只要?jiǎng)?chuàng)建返回了XMLHTTPRequest對(duì)象,就可以通過Chrome瀏覽器的調(diào)試工具在NetWork窗口通過設(shè)置XHR過濾條件,直接篩選出Ajax請(qǐng)求的鏈接;如果是$.ajax()并且dataType指定了為script或jsonp,則無法通過這種方式篩選出來。

  案例分析

  這次搞得還是滑塊哦???,話不多說直接開搞數(shù)美滑塊,因?yàn)樾〖t書、蘑菇街、脈脈、斗魚等很多都用了數(shù)美的驗(yàn)證碼。整體難度還可以就是動(dòng)態(tài)參數(shù)有點(diǎn)東西的呢!

  數(shù)美驗(yàn)證碼官網(wǎng):https://www.ishumei.com/trial/captcha.html

Picture(2)

  數(shù)美滑塊的驗(yàn)證碼主要的難點(diǎn)有以下幾點(diǎn):

  request的請(qǐng)求參數(shù),是動(dòng)態(tài)變化的。名稱是動(dòng)態(tài)變化,加密的密鑰也是動(dòng)態(tài)變化的,這就有點(diǎn)難搞了

  每天小版本更新的頻率1-2次,必須得能夠?qū)崿F(xiàn)完全自動(dòng)化,否則人工很難及時(shí)的調(diào)整驗(yàn)證碼的參數(shù),來不及。

  js里的混淆的變量也是動(dòng)態(tài)變化的

  驗(yàn)證碼注冊(cè)

Picture(3)

  先看一下register

Picture(4)

  下圖是響應(yīng)結(jié)果:bg和fg是驗(yàn)證碼圖片地址 https://castatic.fengkongcloud.com/bg

Picture(5)

  計(jì)算滑塊位置

  根據(jù)上一步可以得到驗(yàn)證圖片的地址。

  驗(yàn)證碼圖片:https://castatic.fengkongcloud.com/crb/set-000006/v2/07ee613eeb1b43bed7daa24c7b288ea0bg.jpg

  滑塊圖片:https://castatic.fengkongcloud.com/crb/set-000006/v2/07ee613eeb1b43bed7daa24c7b288ea0fg.png

  使用opencv查找并匹配圖像模板中的滑塊。

  需要注意的是,這里是以原圖計(jì)算的,而頁面上的圖片大小只有(300,150),(應(yīng)用不同的產(chǎn)品可能大小也不同)

  所以需要按比例進(jìn)行縮小或者放大。

  驗(yàn)證

  對(duì)應(yīng)的api地址是:https://captcha.fengkongcloud.com/ca/v2/fverify?...

Picture(6)

  查詢字符串參數(shù):

Picture(7)

  params參數(shù)里的 dv,qe,ou,cf等等,都經(jīng)過了DES加密,

  破解方式分析

  打開控制臺(tái)多看幾遍請(qǐng)求過程,我們基本就明白請(qǐng)求步驟了。具體的分析過程就不再贅述。

  所攜帶的請(qǐng)求參數(shù)如下:

  該接口返回的js參數(shù),是下一步需要請(qǐng)求的目標(biāo)。

  提取js參數(shù)

  js地址:https://castatic.fengkongcloud.com/pr/auto-build/v1.0.3-144/captcha-sdk.min.js

  需要提取該js中的參數(shù)名,會(huì)在最后驗(yàn)證的時(shí)候使用(注:一般情況下參數(shù)名不會(huì)變),但是這些請(qǐng)求參數(shù)都是變化的。

  獲取js的response,搜索上面的參數(shù)我們沒有找到,但是發(fā)現(xiàn)了倒序的名字

  通過查看調(diào)用棧,打斷點(diǎn),一層層分析,發(fā)現(xiàn)js做了ob混淆。

  JS混淆有很多種,這里舉幾個(gè):UglifyJS,JScrambler,jsbeautifier.org,JSDetox,obfuscator.io 等,像下面的代碼就是ob混淆。

  開頭定義了一個(gè)大數(shù)組,然后對(duì)這個(gè)大數(shù)組里的內(nèi)容進(jìn)行位移,再定義一個(gè)解密函數(shù)。后面大部分的值都調(diào)用了這個(gè)解密函數(shù),以達(dá)到混淆的效果。如果想還原可以使用ob混淆還原工具:https://github.com/DingZaiHub/ob-decrypt

  當(dāng)然不進(jìn)行混淆還原也可以通過斷點(diǎn)很快的定位到具體的函數(shù)加密的位置

  再次請(qǐng)求走到這里,而這里是一部分的參數(shù)的加密,先進(jìn)去看下它是怎么加密的

  進(jìn)來了走到這可以看到是DES加密,參數(shù)分別是加密的密碼,要加密的參數(shù),后面兩個(gè)是數(shù)字呢就是模式選擇了,1,0是加密,0,0是解密,在這里是加密。

  我們輸出在console中輸出一下,這四個(gè)參數(shù)看一下

  那么問題來了,這個(gè)密碼"b64ccadf"哪來的呢,別急,我們重新再來一遍!很快我們又進(jìn)來走到這,_0x1c2865是什么怎么是亂碼的呢?

  console輸出一下看看

Picture(8)

  密碼搞到了,加密方式也曉得了,然后參數(shù)一個(gè)一個(gè)整過去就Ok了。

  返回結(jié)果response:

Picture(9)

  message = success,riskLevel=PASS 說明驗(yàn)證通過

  完整代碼

  """

  數(shù)美滑塊驗(yàn)證碼破解驗(yàn)證

  """

  import base64

  import json

  import random

  import re

  import time

  from io import BytesIO

  import cv2

  import numpy as np

  import requests

  from pyDes import des, ECB

  CAPTCHA_DISPLAY_WIDTH = 310

  CAPTCHA_DISPLAY_HEIGHT = 155

  p = {}

  def pad(b):

  """

  塊填充

  """

  block_size = 8

  while len(b) % block_size:

  b += b'\0'

  return b

  def split_args(s):

  """

  分割js參數(shù)

  """

  r = []

  a = ''

  i = 0

  while i < len(s):

  c = s[i]

  if c == ',' and (a[0] != '\'' or len(a) >= 2 and a[-1] == '\''):

  r.append(a)

  a = ''

  elif c:

  a += c

  i += 1

  r.append(a)

  return r

  def find_arg_names(script):

  """

  通過js解析出參數(shù)名

  """

  names = {}

  a = []

  for r in re.findall(r'function\((.*?)\)', script):

  if len(r.split(',')) > 100:

  a = split_args(r)

  break

  r = re.search(r';\)(.*?)\(}', script[::-1]).group(1)

  v = split_args(r[::-1])

  d = r'{%s}' % ''.join([((',' if i else '') + '\'k{}\':([_x0-9a-z]*)'.format(i + 1)) for i in range(15)])

  k = []

  r = re.search(d, script)

  for i in range(15):

  k.append(r.group(i + 1))

  n = int(v[a.index(re.search(r'arguments;.*?,(.*?)\);', script).group(1))], base=16)

  for i in range(n // 2):

  v[i], v[n - 1 - i] = v[n - 1 - i], v[i]

  for i, b in enumerate(k):

  t = v[a.index(b)].strip('\'')

  names['k{}'.format(i + 1)] = t if len(t) > 2 else t[::-1]

  return names

  def get_encrypt_content(message, key, flag):

  """

  接口參數(shù)的加密、解密

  """

  des_obj = des(key.encode(), mode=ECB)

  if flag:

  content = pad(str(message).replace(' ', '').encode())

  return base64.b64encode(des_obj.encrypt(content)).decode('utf-8')

  else:

  return des_obj.decrypt(base64.b64decode(message)).decode('utf-8')

  def get_random_ge(distance):

  """

  生成隨機(jī)的軌跡

  """

  ge = []

  y = 0

  v = 0

  t = 1

  current = 0

  mid = distance * 3 / 4

  exceed = 20

  z = t

  ge.append([0, 0, 1])

  while current < (distance + exceed):

  if current < mid / 2:

  a = 15

  elif current < mid:

  a = 20

  else:

  a = -30

  a /= 2

  v0 = v

  s = v0 * t + 0.5 * a * (t * t)

  current += int(s)

  v = v0 + a * t

  y += random.randint(-5, 5)

  z += 100 + random.randint(0, 10)

  ge.append([min(current, (distance + exceed)), y, z])

  while exceed > 0:

  exceed -= random.randint(0, 5)

  y += random.randint(-5, 5)

  z += 100 + random.randint(0, 10)

  ge.append([min(current, (distance + exceed)), y, z])

  return ge

  def make_mouse_action_args(distance):

  """

  生成鼠標(biāo)行為相關(guān)的參數(shù)

  """

  ge = get_random_ge(distance)

  args = {

  p['k']['k5']: round(distance / CAPTCHA_DISPLAY_WIDTH, 2),

  p['k']['k6']: get_random_ge(distance),

  p['k']['k7']: ge[-1][-1] + random.randint(0, 100),

  p['k']['k8']: CAPTCHA_DISPLAY_WIDTH,

  p['k']['k9']: CAPTCHA_DISPLAY_HEIGHT,

  p['k']['k11']: 1,

  p['k']['k12']: 0,

  p['k']['k13']: -1,

  'act.os': 'android'

  }

  return args

  def get_distance(fg, bg):

  """

  計(jì)算滑動(dòng)距離

  """

  target = cv2.imdecode(np.asarray(bytearray(fg.read()), dtype=np.uint8), 0)

  template = cv2.imdecode(np.asarray(bytearray(bg.read()), dtype=np.uint8), 0)

  result = cv2.matchTemplate(target, template, cv2.TM_CCORR_NORMED)

  _, distance = np.unravel_index(result.argmax(), result.shape)

  return distance

  def update_protocol(protocol_num, js_uri):

  """

  更新協(xié)議

  """

  global p

  r = requests.get(js_uri, verify=False)

  names = find_arg_names(r.text)

  p = {

  'i': protocol_num,

  'k': names

  }

  def conf_captcha(organization):

  """

  獲取驗(yàn)證碼設(shè)置

  """

  url = 'https://captcha.fengkongcloud.com/ca/v1/conf'

  args = {

  'organization': organization,

  'model': 'slide',

  'sdkver': '1.1.3',

  'rversion': '1.0.3',

  'appId': 'default',

  'lang': 'zh-cn',

  'channel': 'YingYongBao',

  'callback': 'sm_{}'.format(int(time.time() * 1000))

  }

  r = requests.get(url, params=args, verify=False)

  resp = json.loads(re.search(r'{}\((.*)\)'.format(args['callback']), r.text).group(1))

  return resp

  def register_captcha(organization):

  """

  注冊(cè)驗(yàn)證碼

  """

  url = 'https://captcha.fengkongcloud.com/ca/v1/register'

  args = {

  'organization': organization,

  'channel': 'DEFAULT',

  'lang': 'zh-cn',

  'model': 'slide',

  'appId': 'default',

  'sdkver': '1.1.3',

  'data': '{}',

  'rversion': '1.0.3',

  'callback': 'sm_{}'.format(int(time.time() * 1000))

  }

  r = requests.get(url, params=args, verify=False)

  resp = json.loads(re.search(r'{}\((.*)\)'.format(args['callback']), r.text).group(1))

  return resp

  def verify_captcha(organization, rid, key, distance):

  """

  提交驗(yàn)證

  """

  url = 'https://captcha.fengkongcloud.com/ca/v2/fverify'

  args = {

  'organization': organization,

  p['k']['k1']: 'default',

  p['k']['k2']: 'YingYongBao',

  p['k']['k3']: 'zh-cn',

  'rid': rid,

  'rversion': '1.0.3',

  'sdkver': '1.1.3',

  'protocol': p['i'],

  'ostype': 'web',

  'callback': 'sm_{}'.format(int(time.time() * 1000))

  }

  args.update(make_mouse_action_args(distance))

  key = get_encrypt_content(key, 'sshummei', 0)

  for k, v in args.items():

  if len(k) == 2:

  args[k] = get_encrypt_content(v, key, 1)

  print(args)

  r = requests.get(url, params=args, verify=False)

  resp = json.loads(re.search(r'{}\((.*)\)'.format(args['callback']), r.text).group(1))

  return resp

  def get_verify(organization):

  """

  進(jìn)行驗(yàn)證

  """

  resp = conf_captcha(organization)

  protocol_num = re.search(r'build/v1.0.3-(.*?)/captcha-sdk.min.js', resp['detail']['js']).group(1)

  if not p.get('id') or protocol_num != p['i']:

  update_protocol(protocol_num, ''.join(['https://', resp['detail']['domains'][0], resp['detail']['js']]))

  resp = register_captcha(organization)

  rid = resp['detail']['rid']

  key = resp['detail']['k']

  domain = resp['detail']['domains'][0]

  fg_uri = resp['detail']['fg']

  bg_uri = resp['detail']['bg']

  fg_url = ''.join(['http://', domain, fg_uri])

  bg_url = ''.join(['http://', domain, bg_uri])

  r = requests.get(fg_url, verify=False)

  fg = BytesIO(r.content)

  r = requests.get(bg_url, verify=False)

  bg = BytesIO(r.content)

  distance = get_distance(fg, bg)

  print(distance)

  r = verify_captcha(organization, rid, key, int(distance / 600 * 310))

  return rid, r

  def test():

  organization = 'RlokQwRlVjUrTUlkIqOg'

  # rid是驗(yàn)證過程中響應(yīng)的標(biāo)示,r是最后提交驗(yàn)證返回的響應(yīng)

  rid, r = get_verify(organization)

  print(rid, r)

  # riskLevel為PASS說明驗(yàn)證通過

  if r['riskLevel'] == 'PASS':

  # 具體可抓包查看,接口:/api/sns/v1/system_service/slide_captcha_check

  pass

  if __name__ == '__main__':

  test()

  大家用同樣的方法趕快試一試小紅書,蘑菇街等網(wǎng)站登陸吧!

tags:
聲明:本站稿件版權(quán)均屬千鋒教育所有,未經(jīng)許可不得擅自轉(zhuǎn)載。
10年以上業(yè)內(nèi)強(qiáng)師集結(jié),手把手帶你蛻變精英
請(qǐng)您保持通訊暢通,專屬學(xué)習(xí)老師24小時(shí)內(nèi)將與您1V1溝通
免費(fèi)領(lǐng)取
今日已有369人領(lǐng)取成功
劉同學(xué) 138****2860 剛剛成功領(lǐng)取
王同學(xué) 131****2015 剛剛成功領(lǐng)取
張同學(xué) 133****4652 剛剛成功領(lǐng)取
李同學(xué) 135****8607 剛剛成功領(lǐng)取
楊同學(xué) 132****5667 剛剛成功領(lǐng)取
岳同學(xué) 134****6652 剛剛成功領(lǐng)取
梁同學(xué) 157****2950 剛剛成功領(lǐng)取
劉同學(xué) 189****1015 剛剛成功領(lǐng)取
張同學(xué) 155****4678 剛剛成功領(lǐng)取
鄒同學(xué) 139****2907 剛剛成功領(lǐng)取
董同學(xué) 138****2867 剛剛成功領(lǐng)取
周同學(xué) 136****3602 剛剛成功領(lǐng)取
相關(guān)推薦HOT
Visual Studio Online和GitHub有什么區(qū)別?

1.定位不同Visual Studio Online,現(xiàn)更名為Visual Studio Codespaces,是微軟提供的一款在線開發(fā)環(huán)境,允許開發(fā)者在云端進(jìn)行編程和調(diào)試工作。而...詳情>>

2023-10-15 00:21:42
什么是域控制器?

一、域控制器的定義域控制器是指在Windows Server操作系統(tǒng)中部署Active Directory服務(wù)的服務(wù)器。Active Directory是微軟公司開發(fā)的目錄服務(wù),用...詳情>>

2023-10-15 00:10:28
深度學(xué)習(xí)模型權(quán)重h5、weights、ckpt、pth有什么區(qū)別?

1.來源框架不同h5格式通常用于Keras和TensorFlow框架,weights用于Darknet框架,ckpt是TensorFlow框架的一種格式,而pth則主要用于PyTorch框架...詳情>>

2023-10-15 00:05:17
大數(shù)據(jù)測(cè)試工程師需要具備哪些技能?

一、理解大數(shù)據(jù)概念大數(shù)據(jù)測(cè)試工程師需要理解大數(shù)據(jù)的基本概念和原理,如分布式存儲(chǔ)、MapReduce、實(shí)時(shí)計(jì)算等。他們還需要了解如何處理大規(guī)模的...詳情>>

2023-10-14 23:43:03
為什么SpringBoot的 jar 可以直接運(yùn)行?

一、JAR文件的結(jié)構(gòu)與執(zhí)行方式Spring Boot的JAR包是Java Archive的縮寫,它是一種壓縮文件格式,可以將Java項(xiàng)目的類文件、資源文件以及依賴庫(kù)等...詳情>>

2023-10-14 23:01:49
快速通道