Python反反爬蟲 - Frida破解某安卓社區(qū)token反爬蟲

前言

不多逼逼玲躯,這個安卓社區(qū)就是酷安据德,以前想過要爬這軟件鳄乏,但是都忘了,幾天前抓了下它的包棘利,發(fā)現(xiàn)請求 headers 里有一個 token 驗證橱野,果斷就給破了

分析過程

先抓個包

image

可以看到其中有個請求頭 X-App-Token,這就是驗證善玫,至于X-App-Device這玩意兒應該是獲取你手機信息的水援,不管它,先看看軟件源代碼茅郎,找到請求方法

1蜗元、jeb分析

沒加固,好像也沒混淆系冗,舒服

image

搜索關(guān)鍵字:X-App-Token

image

很明顯找到了我們要的東西了奕扣,(jeb3.0按tab鍵反編譯)

image

這個 X-App-Token 是變量 v2_1,v2_1是掌敬,一個AuthUtils類里的getAS方法返回的

image
image

跟進可以發(fā)現(xiàn)這是一個native方法惯豆,lib是native-lib

image

到這里就沒法用jeb分析了,我們先看看參數(shù)2 deviceId 是個什么玩意兒

可以看到是一個 SystemUtils 類里的 getDeviceID方法返回的奔害,傳入一個context參數(shù)

image

我們?nèi)pp的application hook這個方法

image

我找到一個代碼量最少的方法楷兽,這樣可以幫助我們不破壞原邏輯的情況下hook

image

hook的代碼很簡單

Java.perform(function() {
    var CoolMarket = Java.use('com.coolapk.market.CoolMarketApplication');
    CoolMarket.onLog.implementation = function() {
        
        var deviceId = Java.use('com.coolapk.market.util.SystemUtils').getDeviceId(this);
        console.log('Device Id: ', deviceId);

        var app_token = Java.use('com.coolapk.market.util.AuthUtils').getAS(this, deviceId);
        console.log('App Token: ', app_token);

        console.log('----------');
        return 1;
    }
})

拿到 deviceId 后分析so

ida分析

解壓apk拿到 native-lib.so,用ida打開

image

我們已知方法名和參數(shù)個數(shù)华临,那么就先搜索方法名

Function Window 按 option+t 搜索 getAS芯杀,可以看到,毛都沒有

image

image

那我們就到 IDA View 里搜索雅潭,快捷鍵一樣

image

找到了這個瘪匿,參數(shù)是兩個,但這個不是方法寻馏,沒法 F5 反編譯

image

不瞞你們,這個DCB是個什么玩意兒我也不知道.
但是我在 Function Window 瞎翻想找一些我能看得懂的方法名時看到了這個

image

我一看核偿,這個方法好像和 getAS 有點關(guān)系就順手 F5 了

簡單看了看里面的代碼,我估計我要找的是這個方法诚欠,為什么是這個方法而不是其他的

我分析了 X-App-Token 這個驗證的組成,它長這樣:f2c29a109fde487e9350d3e6b881036a8513efac-09ea-3709-b214-95b366f1a1850x5d024391

我之前就獲取到了我的 Device Id漾岳,我無意間看到了我的 device id就在里面轰绵,然后我把它拆分成了這樣:

  • f2c29a109fde487e9350d3e6b881036a
  • 8513efac-09ea-3709-b214-95b366f1a185
  • 0x5d024391

第一項很明顯是md5密文,第二部分就是device id尼荆,最后是一個十六進制左腔,不知道什么玩意兒,但我在那個 getAuthString 代碼里看到了這段

image

這是一個字符串拼接的過程捅儒,其中 v82 是md5密文液样,也是字符串的頭部振亮,后面接著是 v43(device id)、字符串0x鞭莽、最后是 hex_time (這是我改了后的命名)坊秸,所以我就能確定這個方法就是我想要的;

上面說的也就是接下來要分析的澎怒,其中那個十六進制的東西就是時間戳褒搔,我們只需要分析出md5是怎么來的就行了,我們知道m(xù)d5是 v61喷面,v61 的加密代碼在這

image

加密的內(nèi)容是v58星瘾,v58是一個經(jīng)過base64編碼后的變量

image

我懶得去看它是什么了,我直接hook了md5加密類惧辈,有三個方法琳状,一個是md5(應該是構(gòu)造方法把)、update咬像、finalize算撮,hook 代碼如下


// 這里算基址是學的四哥的
var JNI_LOAD_POINTER = Module.getExportByName('libnative-lib.so', 'JNI_OnLoad'); // 首先拿到 JNI_OnLoad方法的地址
// 這里減去的是從so中得到的JNI_OnLoad的地址 0x31A04
var BASE_ADDR = parseInt(JNI_LOAD_POINTER) - parseInt('0x31A04'); // 用程序運行中JNI_OnLoad的絕對地址減去它的相對地址得到基址

// MD5::MD5
Java.perform(function() {
    //  然后用基址 + 要hook的方法的相對地址就得到了絕對地址
    var hookpointer = '0x' + parseInt(BASE_ADDR + parseInt('0x32168')).toString(16) // 獲取要hook方法的地址
    var pointer = new NativePointer(hookpointer) // 根據(jù)方法地址構(gòu)建NativePointer
    console.log('[MD5::MD5] hook pointer: ', pointer)
    
    var arg0, arg1, arg2, arg3
    Interceptor.attach(pointer, {
            onEnter: function(args) {
                arg0 = args[0]
                arg1 = args[1]
                console.log('\n')
                console.log('=====> [MD5::MD5] -> [方法調(diào)用前]')
                console.log('參數(shù)1: {0} => {1}'.format(arg0, Memory.readCString(arg0))) // Memory.readCString 是讀取地址為字符串,類似的還有readUtf8String县昂、readUtf16String等
                console.log('參數(shù)2: {0} => {1}'.format(arg1, Memory.readCString(arg1)))
                console.log('\n')
            },
            onLeave: function(retval) {
                console.log('\n')
                console.log('=====> [MD5::MD5] -> [方法調(diào)用后]:')
                console.log('返回值: ', retval)
                console.log('參數(shù)1: {0} => {1}'.format(arg0, Memory.readCString(arg0)))
                console.log('參數(shù)2: {0} => {1}'.format(arg1, Memory.readCString(arg1)))
                console.log('\n')
            }
        }   
    )
})



// MD5::update
Java.perform(function() {
    var hookpointer = '0x' + parseInt(BASE_ADDR + parseInt('0x329AC')).toString(16) // 獲取要hook方法的地址
    var pointer = new NativePointer(hookpointer) // 根據(jù)方法地址構(gòu)建NativePointer
    console.log('[MD5::update] hook pointer: ', pointer)
    
    var arg0, arg1, arg2, arg3
    Interceptor.attach(pointer, {
            onEnter: function(args) {
                arg0 = args[0]
                arg1 = args[1]
                arg2 = args[2]
                console.log('\n')
                console.log('=====> [MD5::update] -> [方法調(diào)用前]')
                console.log('參數(shù)1: {0} => {1}'.format(arg0, Memory.readCString(arg0)))
                console.log('參數(shù)2: {0} => {1}'.format(arg1, Memory.readCString(arg1)))
                console.log('參數(shù)3: {0} => {1}'.format(arg2, Memory.readCString(arg2)))

                console.log('\n')
            },
            onLeave: function(retval) {
                console.log('\n')
                console.log('=====> [MD5::update] -> [方法調(diào)用后]:')
                console.log('返回值: ', retval)
                console.log('參數(shù)1: {0} => {1}'.format(arg0, Memory.readCString(arg0)))
                console.log('參數(shù)2: {0} => {1}'.format(arg1, Memory.readCString(arg1)))
                console.log('參數(shù)3: {0} => {1}'.format(arg2, Memory.readCString(arg2)))
                console.log('\n')
            }
        }   
    )
})


// MD5::finalize
Java.perform(function() {
    var hookpointer = '0x' + parseInt(BASE_ADDR + parseInt('0x321C4')).toString(16) // 獲取要hook方法的地址
    var pointer = new NativePointer(hookpointer) // 根據(jù)方法地址構(gòu)建NativePointer
    console.log('[MD5::finalize] hook pointer: ', pointer)
    var arg0, arg1, arg2, arg3
    Interceptor.attach(pointer, {
            onEnter: function(args) {
                arg0 = args[0]
                arg1 = args[1]
                arg2 = args[2]
                arg3 = args[3]
                console.log('\n')
                console.log('=====> [MD5::finalize] -> [方法調(diào)用前]')
                console.log('參數(shù)1: {0} => {1}'.format(arg0, Memory.readCString(arg0)))
                console.log('參數(shù)2: {0} => {1}'.format(arg1, Memory.readCString(arg1)))
                console.log('參數(shù)3: {0} => {1}'.format(arg2, Memory.readCString(arg2)))
                console.log('參數(shù)4: {0} => {1}'.format(arg3, Memory.readCString(arg3)))
                console.log('\n')
            },
            onLeave: function(retval) {
                console.log('\n')
                console.log('=====> [MD5::finalize] -> [方法調(diào)用后]:')
                console.log('返回值: ', retval)
                console.log('參數(shù)1: {0} => {1}'.format(arg0, Memory.readCString(arg0)))
                console.log('參數(shù)2: {0} => {1}'.format(arg1, Memory.readCString(arg1)))
                console.log('參數(shù)3: {0} => {1}'.format(arg2, Memory.readCString(arg2)))
                console.log('參數(shù)4: {0} => {1}'.format(arg3, Memory.readCString(arg3)))
                console.log('\n')
            }
        }   
    )
})

運行后得到了那個base64編碼過的內(nèi)容:

dG9rZW46Ly9jb20uY29vbGFway5tYXJrZXQvYzY3ZWY1OTQzNzg0ZDA5NzUwZGNmYmIzMTAyMGYwYWI/MzgyMzIxNWQ5MWQyOWQ5ODg3ZWJjMDVmMGQ3ZmQzMGQkODUxM2VmYWMtMDllYS0zNzA5LWIyMTQtOTViMzY2ZjFhMTg1JmNvbS5jb29sYXBrLm1hcmtldA==

經(jīng)過解碼后:

token://com.coolapk.market/c67ef5943784d09750dcfbb31020f0ab?3823215d91d29d9887ebc05f0d7fd30d$8513efac-09ea-3709-b214-95b366f1a185&com.coolapk.market

在我看到這段代碼后

image

上面解碼后的內(nèi)容可以拆分為:

據(jù)我分析肮柜,只需要的到第二部分的md5加密的來歷就行了,繼續(xù)分析倒彰,找到了加密的地方

image

根據(jù)這圖的畫線审洞,可以明確的知道這md5就是時間戳,在我hook的輸出中也可以看到這個就是時間戳

image
image

至于 token://com.coolapk.market/c67ef5943784d09750dcfbb31020f0ab? 這個是不變的

結(jié)論

token://com.coolapk.market/c67ef5943784d09750dcfbb31020f0ab? + md5加密后的時間戳 + $ + device id + & + com.coolapk.market(包名)待讳,將其md5加密后得到 第一部分

token的來歷就是:第一部分 + deivce id + 0x + 十六進制轉(zhuǎn)換后的時間戳

簡單的測試代碼:

import requests
import time
import hashlib
import base64


DEVICE_ID = "8513efac-09ea-3709-b214-95b366f1a185"

def get_app_token():
    t = int(time.time())
    hex_t = hex(t)

    # 時間戳加密
    md5_t = hashlib.md5(str(t).encode('utf-8')).hexdigest()

    # 不知道什么鬼字符串拼接
    a = 'token://com.coolapk.market/c67ef5943784d09750dcfbb31020f0ab?{}${}&com.coolapk.market' \
        .format(md5_t, DEVICE_ID)

    # 不知道什么鬼字符串拼接 后的字符串再次加密
    md5_a = hashlib.md5(base64.b64encode(a.encode('utf-8'))).hexdigest()

    token = '{}{}{}'.format(md5_a, DEVICE_ID, hex_t)
    print(token)
    return token


def request():
    url = "https://api.coolapk.com/v6/main/indexV8?page=1"
    headers = {
        "User-Agent": "Dalvik/2.1.0 (Linux; U; Android 9; MI 8 SE MIUI/9.5.9) (#Build; Xiaomi; MI 8 SE; PKQ1.181121.001; 9) +CoolMarket/9.2.2-1905301"
    }
    headers = {
        "User-Agent": "Dalvik/2.1.0 (Linux; U; Android 9; MI 8 SE MIUI/9.5.9) (#Build; Xiaomi; MI 8 SE; PKQ1.181121.001; 9) +CoolMarket/9.2.2-1905301",
        "X-App-Id": "com.coolapk.market",
        "X-Requested-With": "XMLHttpRequest",
        "X-Sdk-Int": "28",
        "X-Sdk-Locale": "zh-CN",
        "X-Api-Version": "9",
        "X-App-Version": "9.2.2",
        "X-App-Code": "1903501",
        "X-App-Device": "QRTBCOgkUTgsTat9WYphFI7kWbvFWaYByO1YjOCdjOxAjOxEkOFJjODlDI7ATNxMjM5MTOxcjMwAjN0AyOxEjNwgDNxITM2kDMzcTOgsTZzkTZlJ2MwUDNhJ2MyYzM",
        "Host": "api.coolapk.com",
        "X-Dark-Mode": "0",
        "X-App-Token": get_app_token(),
    }

    resp = requests.get(url, headers=headers)
    print(resp.text)


if __name__ == '__main__':
    request()


最后

代碼啥的都提交到Github了芒澜,CoolapkTokenCrack

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末创淡,一起剝皮案震驚了整個濱河市痴晦,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌琳彩,老刑警劉巖碧浊,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異劳较,居然都是意外死亡驹止,警方通過查閱死者的電腦和手機浩聋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來幢哨,“玉大人赡勘,你說我怎么就攤上這事±塘” “怎么了闸与?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長岸售。 經(jīng)常有香客問我践樱,道長,這世上最難降的妖魔是什么凸丸? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任拷邢,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己潮剪,他們只是感情好,可當我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布环肘。 她就那樣靜靜地躺著,像睡著了一般集灌。 火紅的嫁衣襯著肌膚如雪悔雹。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天欣喧,我揣著相機與錄音腌零,去河邊找鬼。 笑死唆阿,一個胖子當著我的面吹牛益涧,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播驯鳖,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼饰躲,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了臼隔?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤妄壶,失蹤者是張志新(化名)和其女友劉穎摔握,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體丁寄,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡氨淌,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年泊愧,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片盛正。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡删咱,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出豪筝,到底是詐尸還是另有隱情痰滋,我是刑警寧澤,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布续崖,位于F島的核電站敲街,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏严望。R本人自食惡果不足惜多艇,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望像吻。 院中可真熱鬧峻黍,春花似錦、人聲如沸拨匆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽涮雷。三九已至阵面,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間洪鸭,已是汗流浹背样刷。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留览爵,地道東北人置鼻。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓,卻偏偏與公主長得像蜓竹,于是被迫代替她去往敵國和親箕母。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,037評論 2 355

推薦閱讀更多精彩內(nèi)容