记一次QQ空间抓包(网页版 & 扫码登录)

3,328 阅读5分钟

2020/04/02 - 粉红的小小白 - 转载转发请标注来源 以下内容仅做学习交流使用,严禁非法用途

最近闲的无聊,于是随手对QQzone进行一次抓包分析

QQ空间

工作开始前我们需要准备的工具有:

工具名称 备注内容 下载链接
Chrome 谷歌浏览器,这里我们用于抓包 •点击下载
postman 一款接口调试工具 •点击下载
Python3 需要用到requests 和 PIL 库 •点击下载
VsCode 也可以使用PyCharm •点击下载

首先我们打开QQ空间网页版

QQ空间 qzone.qq.com

截图

然后我们打开F12调试,点击Network,找到二维码的来源

截图

获取二维码的接口 (ptqrshow) 地址 为:

https://ssl.ptlogin2.qq.com/ptqrshow?appid=549000912&e=2&l=M&s=3&d=72&v=4&t=0.17442452440865464&daid=5&pt_3rd_aid=0

经过多次获取该地址并对其进行分析,对比后发现:

  • 其中的get参数 &t 是随机变动的
  • 在python3中,我们可以使用random库构建一个类似的随机数,我们将他封装为一个函数t(),方便我们后面调用

写代码之前,我们先将必须用到的一些库放在首部导入,代码如下

#若没安装该库则使用pip安装
from PIL import Image # 图片处理
import requests # 发送网络请求
import time # 时间库
import random # 随机数
import json # json库,用来格式化

获取参数 &t 的代码如下

def t(): 
    return str(random.random())

接下来我们将获取二维码的过程封装成一个函数

def getQRC():  # 获取二维码
    url = 'https://ssl.ptlogin2.qq.com/ptqrshow?appid=549000912&e=2&l=M&s=3&d=72&v=4&t=' + t() + '&daid=5&pt_3rd_aid=0'
    res = requests.get(url)
    with open('QRC.png', 'wb') as f:
        f.write(res.content)
    Image.open('QRC.png').show() # 打开二维码图片
    return res.headers # 将请求结果返回,后面会用到

我们把二维码接口地址得到后,接下来我们来看它是怎么通过扫描验证码进行登录的
通过Network我们很容易发现,它会每隔一段时间向同一个接口发送请求,分析后得知,该接口用于判断用户是否已经扫码,然后登录

截图

接口 (ptqrlogin) 地址如下:

https://ssl.ptlogin2.qq.com/ptqrlogin?u1=https%3A%2F%2Fqzs.qq.com%2Fqzone%2Fv5%2Floginsucc.html%3Fpara%3Dizone&ptqrtoken=1889434358&ptredirect=0&h=1&t=1&g=1&from_ui=1&ptlang=2052&action=0-0-1585807008960&js_ver=20032614&js_type=1&login_sig=jhMZlTwCdTy5do0qJy2b2cRJocoxd-rzRYpb9-LszY8DCDM0WJtbcYicUjiNKGI3&pt_uistyle=40&aid=549000912&daid=5&

多次对比分析后发现,我们需要用到的参数有

  • &login_sig
  • &ptqrtoken
  • &action
&login_sig

该参数内容需要从以下接口的 header 里的 Set-Cookie 里获取,并且其参数内容由64位数字,字母和符号"-"构成

接口 (xlogin) 地址如下:

https://xui.ptlogin2.qq.com/cgi-bin/xlogin?proxy_url=https://qzs.qq.com/qzone/v6/portal/proxy.html&daid=5&&hide_title_bar=1&low_login=0&qlogin_auto_login=1&no_verifyimg=1&link_target=blank&appid=549000912&style=22&target=self&s_url=https://qzs.qzone.qq.com/qzone/v5/loginsucc.html?para=izone&pt_qr_app=手机QQ空间&pt_qr_link=http://z.qzone.com/download.html&self_regurl=https://qzs.qq.com/qzone/v6/reg/index.html&pt_qr_help_link=http://z.qzone.com/download.html&pt_no_auth=1

接下来我们调用接口 (xlogin) 后,在header内的Set-Cookie内发现了参数 &login_sig

截图

根据以上需求,我们可以将获取参数 &login_sig 的操作过程封装成函数 (如下),方便我们后面调用

def getLoginSig():
    url = 'https://xui.ptlogin2.qq.com/cgi-bin/xlogin?proxy_url=https://qzs.qq.com/qzone/v6/portal/proxy.html&daid=5&&hide_title_bar=1&low_login=0&qlogin_auto_login=1&no_verifyimg=1&link_target=blank&appid=549000912&style=22&target=self&s_url=https://qzs.qzone.qq.com/qzone/v5/loginsucc.html?para=izone&pt_qr_app=手机QQ空间&pt_qr_link=http://z.qzone.com/download.html&self_regurl=https://qzs.qq.com/qzone/v6/reg/index.html&pt_qr_help_link=http://z.qzone.com/download.html&pt_no_auth=1'
    headers = requests.get(url).headers
    start_index = headers['Set-Cookie'].find('pt_login_sig=') + 13
    return headers['Set-Cookie'][start_index:start_index + 64]
&ptqrtoken

该参数内容需要从获取二维码的接口 (ptqrshow) 中的header内获取,因为不同的二维码,该参数值都是不一样的,因此我们在第一步获取二维码并保存的时候,就需要将header返回,用于参数 &ptqrtoken 的获取 我们从获取二维码的接口 (ptqrshow) 的抓包信息来看,一个名为 &qrsig 的参数符合我们的要求

截图

于是我们将获取参数 &ptqrtoken 的过程封装成一个函数

def getQRCSig(headers):  # 将获取二维码接口的headers传进来
    return ptqrtoken(headers['Set-Cookie'].split(';')[0][6:])
    
def ptqrtoken(value):  # 构造ptqrttoken的加密方式
    i = 0
    e = 0
    n = len(value)
    for i in range(n):
        e = e + (e << 5)
        e = e + ord(value[i])
    return str(2147483647 & e)
&action

通过分析后得到,该参数内容为当前的时间戳乘以1000,并在首部加上字符串"0-0-",于是我们将获取参数 &action 的过程封装成一个函数

def getAction():  # 构造action参数
    import time
    return '0-0-' + str(int(time.time() * 1000))

然后我们现在回到接口 (ptqrlogin), 我们到现在已经获取了三个必须用到的参数,我们现在来构建函数 ifLogin() ,这个函数可以设置while循环,循环一次睡眠5秒,用来检测用户是否扫描成功以及是否登录成功

def ifLogin(ptqrtoken, loginSig):
    url = "https://ssl.ptlogin2.qq.com/ptqrlogin?u1=https://qzs.qzone.qq.com/qzone/v5/loginsucc.html?para=izone&ptqrtoken=" + ptqrtoken + "&ptredirect=0&h=1&t=1&g=1&from_ui=1&ptlang=2052&action=" + action() + "&js_ver=19112817&js_type=1&login_sig=" + loginSig + "&pt_uistyle=40&aid=549000912&daid=5&"
    return requests.get(url).text[7:-1].replace("'", "").split(',')

下面是该接口 (ptqrlogin) 在不同场景下返回的数据

  • 未扫描二维码时返回的数据

截图

  • 扫描二维码后返回的数据

截图

  • 二维码失效后返回的数据

截图

  • 扫描成功后返回的数据

截图

然后分析后得到,我们通过去除返回文本的前七个字符和倒数第一个字符,然后将剩下的文本分割成数组
数组第一个项可以作为我们判断的依据

  • 65 已失效
  • 66 未失效
  • 67 已扫描,但还未点击确认
  • 0   已经点击确认,并登录成功

注: 登录成功后返回的数据中,数组的第三项为跳转链接,最后一项为QQ网名

待更新