QQ模擬登入實現之四兩撥千斤(基於V8引擎)

wyzsk發表於2020-08-19
作者: iv4n · 2016/03/14 10:54

0x00 概述


很多時候,我們需要模擬QQ自動登入的場景,比如爬取QQ頁面的時候,我們需要登入,當然,還有其它的需求就不方便說了。

比較簡單的帳號登入,基本上都是傳送一個請求包, 最多再偽造一下UserAgent,加一個驗證碼,就能搞定。

然而當我看到鵝廠的的登入介面時,內心是崩潰的,加密的過程讓我有點驚慌失措。

我們訪問一下QQ的登入頁面:

http://ui.ptlogin2.qq.com/cgi-bin/login?hide_title_bar=0&low_login=0&qlogin_auto_login=1&no_verifyimg=1&link_target=blank&appid=636014201&target=self&s_url=http%3A//www.qq.com/qq2012/loginSuccess.htm

通常會看到如下頁面

p1

簡單的分析一下這個登入頁面,這裡QQ支援3種登陸方式:

  • 手機QQ二維碼登入
  • 使用者名稱密碼登入
  • 客戶端快速登入

登入頁面展示的邏輯如下:

p2

從自動化的角度來考慮,第一種方式直接排除,需要手機互動;第三種方式,需要有客戶端支援,在win下面也是不錯的方案;第二種方式最普遍,也是比較實用,本文重點講解這種登陸方式的自動化。

0x01 使用者名稱密碼登入流程分析


要想實現模擬登入,我們得先搞清楚登入的流程。

所以,我們先來簡單看一下使用者名稱密碼登入的流程。

PS: 個人習慣把QQ的這個登入頁面叫做登入元件。

0x02 整體流程


p3

流程說明

1.元件載入

元件載入會做一些準備工作,這裡不做詳解,只講一個重要的點:

載入成功後會生成一個長度64的字串簽名:login_sig,後續每一步都需要返回這個簽名做校驗。簽名的字串通常如下:

#!bash
V0VRhNIHGyezVzO7YgH82MmYj78KF6csGHq3330UXWDa79ZSUPy6J84RwcBzbFaQ

2.登入檢測

簡單看了下,主要做了2件事情:一個是使用者名稱校驗,一個是登入環境校驗。

  • 使用者名稱校驗:使用者名稱如果不存在,則校驗不透過;
  • 登入環境校驗:風控系統會檢測登入環境是否異常如同一IP多個帳號登入失敗,則校驗不透過;

校驗不透過的策略可能不同,彈圖片驗證碼是比較通用的策略;

校驗透過會返回一個JSON串,其中3個返回值比較重要,這裡說明下:

  1. 一個長度為4的校驗碼verifycode,驗證碼。校驗碼verifycode是以歎號!開頭,後面是3位大寫字母,形式如下:

    #!bash
    !QWE
    
  2. 一個長度為32的16進位制格式的鹽salt。 鹽salt,看了下,其實就是uin(qq號碼)的16進位制,形式如下:

    #!bash
    \x00\x00\x00\x00\x6b\x68\x90\xfb
    
  3. 一個長度為112的session pt_verifysession_v1,校驗session。session pt_verifysession_v1,形式如下:

    #!bash
    69a55c643beecaf5580394c80e9a0f8e800d8c0f3cab6a95ba77e39703e80b83ba2bde15d54558120e782a26f815a3ff97fdfb46ae92db6d
    

3.登入

上述流程正常後,就進入了登入。這裡主要是對使用者的密碼進行特定加密,然後和前面兩步獲取的必要引數一同提交登入,進行帳號和密碼校驗。

登入成功會返回一個回撥URL和植入認證cookie superkey。

這裡補充說明下:

QQ的密碼的處理流程比較複雜,關鍵是,除了一些標準密碼處理方法(MD5,SALT ,RSA),還有TX自帶的TEA演算法;

當然,在登入的JavaScript程式碼裡面都有具體實現,感興趣的同學可以研究下;

整個流程還是蠻有意思的,後續有機會可以給大家分享一下如何自己實現TX的密碼加密流程:)

0x03 自動登入實現


自動登入實現的幾種方案

透過上面的流程,我們可以看到,自動登入實現的難點在於加密的密碼如何獲取,這裡提供幾種登入方案:

  • 方案一:當然,最普遍的自動化實現方案,所有流程自己實現,每次請求的資料都自己生成,難點在於加密密碼的生成。相對複雜,需要熟悉演算法,但是效率高;
  • 方案二:最簡單暴力的方式,直接呼叫瀏覽器引擎,模擬人工輸入使用者名稱密碼提交表單。簡單,但部署稍微複雜,效率低;
  • 方案三:如果覺得上述方法太過於暴力,可以選一個折中的方案,我們只在密碼生成的時候,使用JavaScript引擎,呼叫登入元件中的Encrypt演算法對密碼進行加密,其他流程仍然自動化實現。 簡單,並且效率比方案二好;

具體實現

所以我們選第三種方案,不用深入具體的密碼加密流程和演算法,同時實現成本也比較低。

下面我們用python進行一個簡單的登入實現:

我們直接使用V8引擎,呼叫JavaScript中的Encryption方法進行密碼加密是非常簡單的:

#!python
def tx_pwd_encode_by_js(self, pwd, salt, verifycode):
    """
    呼叫V8引擎,直接執行TX的登陸JS中的加密方法,不用自己實現其中演算法。
    """
    # pwd, salt, verifycode, undefined
    with PyV8.JSContext() as ctxt:
        with open("qq.login.encrypt.js") as jsfile:
            ctxt.eval(jsfile.read())
            encrypt_pwd = ctxt.eval("window.$pt.Encryption.getEncryption('%s', '%s', '%s', undefined)"
                             %(pwd, salt, verifycode) )
            return encrypt_pwd

其他的就是體力活了,按照登陸的流程一步一步來,參考如下:

首先,我們第一步先載入元件,獲取簽名,參考程式碼:

#!python
def get_signature(self):
    """
    step 1, load web login iframe and get a login signature
    """
    params = {
        'no_verifyimg': 1,
        "appid": self.appid,
        "s_url": self.urlSuccess,
    }
    params = urllib.urlencode(params)
    url = "%s?%s" %(self.urlRaw, params)
    r = self.session.get(url)
    if 200 != r.status_code:
        error_msg = "[Get signature error] %s %s" %(r.status_code, url)
        return [False, error_msg]
    else:
        self.login_sig = self.session.cookies['pt_login_sig']
        return [True, ""]

獲取了login_sig後,我們進行第二步,進行登入檢測:

#!python
def check_login(self):
    '''
    step 2: get verifycode and pt_verifysession_v1.
    TX will check username and the login's environment is safe
      '''
    params = {
        "uin": self.uin,
        "appid": self.appid,
        "pt_tea": 1,
        "pt_vcode": 1,
        "js_ver": 10151,
        "js_type": 1,
        "login_sig": self.login_sig,
        "u1": self.urlSuccess,
    }
    params = urllib.urlencode(params)
    url = "%s?%s" %(self.urlCheck, params)
    r = self.session.get(url)
    if 200 != r.status_code:
        error_msg = "[Get verifycode error] %s %s" %(r.status_code, url)
        return [False, error_msg]
    else:
        v = re.findall('\'(.*?)\'', r.text)
        self.check_code = v[0]
        if self.check_code != '0':
            error_msg = "[Verifycode not 0] %s %s" %(self.check_code, url)
            return [False, error_msg]
        self.verifycode = v[1]
        self.salt = v[2]
        self.pt_verifysession_v1 = v[3]
        return [True, ""]

檢測成功後,我們就可以進行直接登陸,登陸流程程式碼參考如下:

#!python
def login(self):
    '''
    step 3: login and get cookie.
    TX will check encrypt(password)
        '''
    encrypt_pwd  =  self.tx_pwd_encode_by_js(self.pwd, self.salt, self.verifycode)

    if not self.pt_verifysession_v1:
        self.pt_verifysession_v1 = self.session.cookies['ptvfsession']
    params = {
        'u': self.uin,
        'verifycode': self.verifycode,
        'pt_vcode_v1': 0,
        'pt_verifysession_v1': self.pt_verifysession_v1,
        'p': encrypt_pwd,
        'pt_randsalt': 0,
        'u1': self.urlSuccess,
        'ptredirect': 0,
        'h': 1,
        't': 1,
        'g': 1,
        'from_ui': 1,
        'ptlang': 2052,
        'action': self.action,
        'js_ver': 10143,
        'js_type': 1,
        'aid': self.appid,
        'daid': 5,
        'login_sig': self.login_sig,
    }
    params = urllib.urlencode(params)
    url = "%s?%s" %(self.urlLogin, params)
    r = self.session.get(url)
    if 200 != r.status_code:
        error_msg = "[Login error] %s %s" %(r.status_code, url)
        return [False, error_msg]
    else:
        v = re.findall('\'(.*?)\'', r.text)
        if v[0] != '0':
            error_msg = "[Login Faild] %s %s" %(url, v[4])
            return [False, error_msg]
        self.nick = v[5]
        return [True, ""]

show me the code程式碼傳送門:
https://github.com/LeoHuang2015/qqloginjs

0x04 總結


透過這種方式,只需要使用JS引擎,呼叫JS的加密方法即可生成加密的密碼,不需要深入研究TX密碼加密的流程和演算法,比較簡單方便。

本文章來源於烏雲知識庫,此映象為了方便大家學習研究,文章版權歸烏雲知識庫!

相關文章