App爬蟲篇 - 破解移動端登錄,助力 Python 爬蟲

image

閱讀文本大概需要 10 分鐘。

目 標 場 景

在移動互聯網時代,很大一部分企業拋棄了傳統的網站,選擇將數據、服務整合到 App 端,因此 App 端無論是爬蟲還是反反爬都顯得尤為重要。

常見的 App 端的爬蟲方式是利用 Appium 和 Airtest 驅動手機打開應用,操作頁面,然后通過元素 ID 獲取元素的內容,又或者借助 mitmproxy 捕獲到請求的數據,最后將數據保存下來。

?如果要完成復雜的操作,加快爬蟲的效率,就必須破解 App 端的登錄,獲取一些關鍵的數據,直接模擬接口請求,達到快速高效地爬取數據的目的。

image

本篇文章的目的是帶大家「破解 App 端的登錄」這一操作。

ps:本文僅限技術交流,請勿用于其他用途。

準 備 工 作

在開始編寫腳本之前,需要做好如下準備工作

  • 待破解的 APK 應用,可去官網或者各大應用市場去下載,然后安裝應用到手機中

  • 反編譯工具,MAC OSX 推薦 Android Crack Tool 工具集,Win OS 可以使用 dex2jar 來反編譯 APK 應用包

  • 源碼瀏覽工具:jadx-gui

  • 抓包工具:Charles 或者 Fiddler

編 寫 腳 本

第 1 步,確保手機配置好代理之后,就可以利用 Charles 對「獲取****驗證碼」和「登錄」進行抓包操作,得到請求地址、請求參數和請求頭等數據。

image

第 2 步,對請求參數、請求頭中「沒有規律的數據」尋找生成的規律,并用 Python 代碼來生成這些數據。

首先,我們查看獲取驗證碼這一請求的參數,發現除了手機號碼外,參數 t 可以很容易想到是請求的時間戳,唯獨參數 token 在沒有其他網絡請求的情況下生成了。

def get_unix_time(type_13):
    """
    獲取時間戳
    :param type_13:10位、13位,是否是13位
    :return:
    """
    t = time.time()

    if type_13:

        millis = int(round(t * 1000))
    else:
        millis = int(t)

    return millis

所以,我們大膽猜測:這個 token 是 App 端通過一定的邏輯生成的;****同理,請求頭中 token 也是由 App 端生成

在我們多次發起獲取驗證碼的操作之后,我們得出一個規律:參數中的 token 保持不變,與請求時間沒有關系;請求頭的 token 會隨著時間的變化的也會發生變化。

我們利用 Android Crack Tool 對 APK 應用進行反編譯,得到源碼 Jar 包。

image

然后就可以使用 jadx-gui 工具打開源碼 Jar 包,通過請求地址中的「關鍵詞:login」搜索源碼,就能找到請求發送的位置。

image

由于應用源碼打包的時候混淆了代碼,因此,我們需要根據上面的搜索結果去定位參數初始化位置及實現邏輯。

image

逐步往上追溯應用源碼,可以找到按鈕點擊事件的監聽函數。

具體實現邏輯是把用戶輸入的手機函數傳給混淆后的函數 :b()

image

點擊查看函數 b() 的實現邏輯,會發現方法中對手機號碼進行了截取,獲取當前日期時間,進行字符串的「第一次拼接」操作。

image

對第一部分的拼接我們用 Python 代碼進行實現。

def __get_param_token(self, phone_num):
        """
        獲取參數Token
        :return: BNpK8SMDiV6jTU4DR99A9vYoN9e90yBd
        """
        today = datetime.date.today()
        formatted_today = today.strftime('%Y%m%d')

        formatted_day = today.strftime('%m%d')

        # 參數1  手機號碼|完整日期6位
        arg1 = phone_num + "|" + formatted_today
        
        # 手機號碼后4位+日期包含月、日
        # 參數2  64230704
        # 字符串轉為bytes
        arg2 = bytes(phone_num[7:] + formatted_day, encoding="utf8")

第一次拼接完成之后,我們發現又調用了一個函數 a(),參數為上面拼接生成的兩個變量。

函數 a() 的內部使用「DES + Base64」加密算法來進行第二步的處理。

image

加密的操作用 Python 可以很輕松的實現。

def encode(arg1, arg2):
    """
    加密
    :param arg1:11位手機號碼|完整日期 string
    :param arg2:手機后4位+日期4位  bytes
    :return:
    """
    des = DES.new(arg2, mode=DES.MODE_CBC, iv=bytearray([1, 2, 3, 4, 5, 6, 7, 8]))
    msg = des.encrypt(pad(arg1.encode(), DES.block_size))

    # 加密后的結果,bytes
    encode_result = base64.b64encode(msg)

    # 轉為string
    return str(encode_result, encoding='utf-8')

需要注意的是,b()函數的最后一行,對第二步生成的字符串進行了特殊字符的替換操作,生成 Token 之前需要對數據進行同樣的處理。

image

通過以上三步操作,就可以生成網絡請求中的參數 Token。

同樣的方式,針對請求中的 Token,我們通過查詢 token 關鍵字查詢源碼。

image

通過觀察,我們發現類 e 中的 b()函數的功能就是往請求中添加請求頭,繼續查看函數 b() 的實現類,發現這個類也全部被混淆了。

image

如果你細心一點,一定會發現當前實現類的包名是 Okhttp3,我們可以從 Github 下載 Okhttp3 的源碼,然后進行對比,就能很清晰的知道里面的實現邏輯了。

ps:okhttp 是 Android 使用很多一個網絡請求庫。

image

通過對比沒有混淆過的代碼,可以很容易的編寫出生成請求頭中 Token 的邏輯。

def __get_head_token(self, method, url, data):
        """
        獲取請求頭Token
         分為Get和Post請求方式
        :param method: 請求方式
        :param url: 請求URL
        :param data: Post請求中的參數
        :return:
        """
        today = datetime.date.today()
        formatted_today = today.strftime('%Y%m%d')

        if method == Method.GET:
            # 請求的URL的query部分
            query_content = url.split('?')[1]
        else:
            query_content = urlencode(data)

        print('query_content:' + query_content)

        # 根據反編譯后的源碼增加對應的邏輯
        token_pro = query_content + "|" + formatted_today + '|zxw'

        # MD5計算
        token = md5(token_pro)

        return token

至此,這一步就完成兩個 Token 的生成。

第 3 步就可以利用 Python「模擬發起一個請求」,來獲取手機驗證碼了。

def get_code(self, timestamp):
    """
    獲取驗證碼
    :return:
    """

    # 1.1 獲取參數Token,與日期有關
    self.param_token = self.__get_param_token(self.phone)
    print("parm_token:" + self.param_token)

    # 1.2 獲取請求頭Token,與時間有關
    url = self.code_url.format(self.phone, timestamp, self.param_token)

    # 獲取請求頭中的Token
    self.head_token = self.__get_head_token(Method.GET, url, None)

    print('head_token【獲取驗證碼】:' + self.head_token)

    # 2.獲取手機驗證碼的URL
    get_code_url = self.code_url.format(self.phone, timestamp, self.param_token)

    # 3.修改Head中的token
    HEADERS['token'] = self.head_token

    print(get_code_url)

    # 4.發起【獲取驗證碼】的請求
    resp = requests.get(get_code_url, headers=HEADERS)

    print('==' * 60)
    print(resp.text)

同理,后面的登錄請求也是先通過抓包,使用上面生成的 Token 邏輯去修改請求頭中 Token,然后模擬請求,就可以正常登錄了。

def login(self, code, timestamp):
    """
    登錄
    :return:
    """

    # 修改參數
    self.login_params['loginCode'] = code
    self.login_params['t'] = timestamp

    # 請求token
    # url = self.code_url.format(self.phone, timestamp, self.param_token)
    self.head_token = self.__get_head_token(Method.POST, None, self.login_params)

    print('head_token【登錄】:' + self.head_token)

    HEADERS['token'] = self.head_token

    # 登錄
    resp = requests.post(self.login_url, data=self.login_params, headers=HEADERS)

    print(resp.text)

結 果 結 論

通過模擬獲取驗證碼的請求,等待手機收到驗證碼之后,輸入驗證碼,然后再模擬登錄的請求,就可以獲取登錄成功后的令牌。

image

由于驗證碼是由服務器產生的,這里沒法獲取生成邏輯,但是針對安卓手機可以監聽通知欄消息元素,拿到短信驗證碼進行自動填入,就不需要人工輸入了。

image

拿到登錄令牌之后,理論上 App 上頁面的各類網絡請求都可以利用 Python 去模擬,后面提供的源碼包含了一個完整搶票的流程。

本文首發于公眾號「 AirPython 」,關注公眾號后,回復「 App登錄 」即可獲得所有源碼。

如果你覺得文章還不錯,請大家點贊分享下。你的肯定是我最大的鼓勵和支持。

推薦閱讀:

10萬+的短視頻被批量生產了,Python表示不服

薅羊毛 | 揭秘閑魚方案,一部手機,實現隨時隨地薅羊毛

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,363評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,497評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,305評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,962評論 1 311
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,727評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,193評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,257評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,411評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,945評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,777評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,978評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,519評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,216評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,657評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,960評論 2 373

推薦閱讀更多精彩內容

  • 點擊查看原文 Web SDK 開發手冊 SDK 概述 網易云信 SDK 為 Web 應用提供一個完善的 IM 系統...
    layjoy閱讀 13,833評論 0 15
  • 這個世界最強悍的是自己,最脆弱的還是自己, 或許我比我想象中的更堅強———— 當所有人都離開了,我依然堅守崗位;當...
    獨愛秋寂閱讀 179評論 0 0
  • 西元一千九百九十年,黑吉遼、津冀鄂、湘蒙瓊、陜徽浙十二省學子,凡二十五人,匯集長春,求學吉大,謂之九零考古文博。同...
    九哥的歌閱讀 702評論 0 2