Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

FRIDAがAndroidコミュニティトークンの検証をクラック

More than 1 year has passed since last update.

(ヒント:日本語はすべてGoogle翻訳からのものです)

まえがき(前言)

这个社区是酷安,以前想过要爬(spider)这软件,但是都忘了,几天前抓了下它的包,发现请求 headers 里有一个 token 验证,果断就给破了

このコミュニティはCoolapkで、以前このソフトウェアを入手することを考えていましたが、それを忘れていました。数日前にそのパッケージを入手し、リクエスト** headers トークン**の検証があることがわかりました。 そうです。

分析プロセス(分析过程)

最初にバッグをキャプチャします(先抓个包)

zhuabao.jpg

確認に使用されるリクエストヘッダー X-App-Token があることがわかります。X-App-Device に関しては、携帯電話情報を取得するために、最初に見てください。 ソフトウェアのソースコードを見て、リクエストメソッドを見つけます
可以看到其中有个请求头 X-App-Token,这就是验证,至于X-App-Device这玩意儿应该是获取你手机信息的,不管它,先看看软件源代码,找到请求方法

1、jeb分析(jeb分析)

補強なし、混乱していないようです
没加固,好像也没混淆,舒服

jeb_analysis_home.jpg

検索キーワード(搜索关键字):X-App-Token

jeb_search_keyword_x-app-token.jpg

明らかに私たちが望むものを見つけました。(jeb3.0タブを押して逆コンパイルします)
很明显找到了我们要的东西了,(jeb3.0按tab键反编译)

jeb_have_fond-x-app-token.jpg

この X-App-Token は変数v2_1であり、AuthUtils クラスの getAS メソッドによって返されます。
这个 X-App-Token 是变量 v2_1,v2_1是一个AuthUtils类里的getAS方法返回的

jeb_lock_value.jpg

jeb_value_from.jpg

フォローアップはこれがネイティブメソッドであることがわかります、libはnative-libです。
跟进可以发现这是一个native方法,lib是native-lib

ここではjebで分析することはできません。パラメータdeviceIdを見てみましょう。
到这里就没法用jeb分析了,我们先看看参数 deviceId 是个什么玩意儿

SystemUtils クラスの getDeviceID メソッドによって返されることがわかります。
可以看到是一个 SystemUtils 类里的 getDeviceID方法返回的,传入一个context参数

jeb_lock_deviceid_from.jpg

APPのApplicationにアクセスしてメソッドを見つけ、それをHOOKしてみましょう
我们去app的application hook这个方法

jeb_find_application_class.jpg

コードの量が最も少ないメソッドを見つけたので、元のロジックを壊さずにHOOKを実行できます。
我找到一个代码量最少的方法,这样可以帮助我们不破坏原逻辑的情况下hook

jeb_find_hook_func.jpg

HOOKコードは簡単です
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ファイルを分析します
拿到 deviceId 后分析so

2、IDA分析(IDA分析)

apkを解凍し、native-lib.so を取得し、idaで開きます
解压apk拿到 native-lib.so,用ida打开

ida_open_so.jpg

メソッド名とパラメーターの数がわかっているので、最初にメソッド名を検索します。
我们已知方法名和参数个数,那么就先搜索方法名

Function Window で option+t を押して getAS を検索すると、何もないことがわかります。
Function Window 按 option+t 搜索 getAS,可以看到,毛都没有

ida_search_keyword_getAS.jpg

ida_not_found.jpg

次に、ショートカットと同じ IDA View で検索します
那我们就到 IDA View 里搜索,快捷键一样

ida_ida_view_search.jpg

これが見つかりました、パラメータは2つですが、これはメソッドではなく、F5逆コンパイルではありません
找到了这个,参数是两个,但这个不是方法,没法 F5 反编译

ida_ida_view_have_found.jpg

このDCBが何なのかわかりません。
这个DCB是个什么玩意儿我也不知道.

しかし、私は Function Window でよくわかる方法名を探していたところ、これを見つけた
但是我在 Function Window 瞎翻想找一些我能看得懂的方法名时看到了这个

ida_func_win_search_again.jpg

見ると、この方法はgetASと少し関係があるようでF5になっている
我一看,这个方法好像和 getAS 有点关系就顺手 F5 了

コードを見てみました私が探しているのはこの方法だと思います
简单看了看里面的代码,我估计我要找的是这个方法,为什么是这个方法而不是其他的

実はこの方法は動的に登録されていて、これは私が以前書いた文章なのでその時はまだ分からなかった

私が分析した x—アプリ—トークン この検証の構成、のようにそれは f2c29a109fde487e9350d3e6b881036a8513efac -9 ea−3709−b214−95 b366f1a1850x5d024391
我分析了 X-App-Token 这个验证的组成,它长这样:f2c29a109fde487e9350d3e6b881036a8513efac-09ea-3709-b214-95b366f1a1850x5d024391

私はこれまでにDevice Idを入手していたのですが、ふと私のDevice Idが入っているのを見て、それを次のように切り分けました。
我之前就获取到了我的 Device Id,我无意间看到了我的 device id就在里面,然后我把它拆分成了这样

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

第一項は明らかにmd5暗号文で、第二部分はdevice idで、最後は十六進数で何か分からないのですが、私はそのgetAuthStringコードの中にこれを見ました
第一项很明显是md5密文,第二部分就是device id,最后是一个十六进制,不知道什么玩意儿,但我在那个 getAuthString 代码里看到了这段

ida_code_string_splice_1.jpg

これは文字列のスティッチングのプロセスで、v82はmd5暗号文であり、文字列の先頭でもあり、後ろにはv43 (device id)、文字列0x、最後にhex_time(これは私が変更した名前)が続くので、この方法が私の意図したものであることを確認することができます。
这是一个字符串拼接的过程,其中 v82 是md5密文,也是字符串的头部,后面接着是 v43(device id)、字符串0x、最后是 hex_time (这是我改了后的命名),所以我就能确定这个方法就是我想要的;

上で述べた、つまりこれから分析するのですが、そのうちの十六進数のものがタイムスタンプなのです。md5がどのように来たかを分析するだけで済みます。md5はv61であり、v61の暗号化コードはここにあります
上面说的也就是接下来要分析的,其中那个十六进制的东西就是时间戳,我们只需要分析出md5是怎么来的就行了,我们知道md5是 v61v61 的加密代码在这

ida_code_md5_1.jpg

暗号化の内容はv58であり,v58はbase64符号化された変数である
加密的内容是v58,v58是一个经过base64编码后的变量

ida_code_base64_1.jpg

私はそれが何か見るのがおっくうで、私は直接md5の暗号化類をhookして、3つの方法があって、1つはmd5(構造方法であるべきです)、update、finalize、hookのコードは次の通りですか
我懒得去看它是什么了,我直接hook了md5加密类,有三个方法,一个是md5(应该是构造方法把)、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) // メソッドアドレスに基づいて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] -> [メソッド呼び出し前]')
                console.log('パラメータ1: {0} => {1}'.format(arg0, Memory.readCString(arg0))) // Memory.readCStringはリードアドレスが文字列であり,同様にreadUtf8String, readUtf16Stringなどがある
                console.log('パラメータ2: {0} => {1}'.format(arg1, Memory.readCString(arg1)))
                console.log('\n')
            },
            onLeave: function(retval) {
                console.log('\n')
                console.log('=====> [MD5::MD5] -> [メソッド呼び出し後]:')
                console.log('戻り値: ', retval)
                console.log('パラメータ1: {0} => {1}'.format(arg0, Memory.readCString(arg0)))
                console.log('パラメータ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) // メソッドアドレスに基づいて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] -> [メソッド呼び出し前]')
                console.log('パラメータ1: {0} => {1}'.format(arg0, Memory.readCString(arg0)))
                console.log('パラメータ2: {0} => {1}'.format(arg1, Memory.readCString(arg1)))
                console.log('パラメータ3: {0} => {1}'.format(arg2, Memory.readCString(arg2)))

                console.log('\n')
            },
            onLeave: function(retval) {
                console.log('\n')
                console.log('=====> [MD5::update] -> [メソッド呼び出し後]:')
                console.log('戻り値: ', retval)
                console.log('パラメータ1: {0} => {1}'.format(arg0, Memory.readCString(arg0)))
                console.log('パラメータ2: {0} => {1}'.format(arg1, Memory.readCString(arg1)))
                console.log('パラメータ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) // メソッドアドレスに基づいて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] -> [メソッド呼び出し前]')
                console.log('パラメータ1: {0} => {1}'.format(arg0, Memory.readCString(arg0)))
                console.log('パラメータ2: {0} => {1}'.format(arg1, Memory.readCString(arg1)))
                console.log('パラメータ3: {0} => {1}'.format(arg2, Memory.readCString(arg2)))
                console.log('パラメータ4: {0} => {1}'.format(arg3, Memory.readCString(arg3)))
                console.log('\n')
            },
            onLeave: function(retval) {
                console.log('\n')
                console.log('=====> [MD5::finalize] -> [メソッド呼び出し後]:')
                console.log('戻り値: ', retval)
                console.log('パラメータ1: {0} => {1}'.format(arg0, Memory.readCString(arg0)))
                console.log('パラメータ2: {0} => {1}'.format(arg1, Memory.readCString(arg1)))
                console.log('パラメータ3: {0} => {1}'.format(arg2, Memory.readCString(arg2)))
                console.log('パラメータ4: {0} => {1}'.format(arg3, Memory.readCString(arg3)))
                console.log('\n')
            }
        }   
    )
})

実行後にそのbase64コードされた内容が得られる:
运行后得到了那个base64编码过的内容:

dG9rZW46Ly9jb20uY29vbGFway5tYXJrZXQvYzY3ZWY1OTQzNzg0ZDA5NzUwZGNmYmIzMTAyMGYwYWI/MzgyMzIxNWQ5MWQyOWQ5ODg3ZWJjMDVmMGQ3ZmQzMGQkODUxM2VmYWMtMDllYS0zNzA5LWIyMTQtOTViMzY2ZjFhMTg1JmNvbS5jb29sYXBrLm1hcmtldA==

復号後:
经过解码后:

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

このコードによる:
根据这段代码

ida_code_string_splice_2.jpg

以上の復号化されたコンテンツは、以下のように分割される。
上面解码后的内容可以拆分为:

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

私の分析によると、第二部分のmd5の暗号化の素性だけが必要で、分析を続けて、暗号化の場所を見つけた
据我分析,只需要的到第二部分的md5加密的来历就行了,继续分析,找到了加密的地方

ida_code_md5_2.jpg

この図の線を引くことにより,このmd5がタイムスタンプであることが明確に分かるようになり,これがタイムスタンプであることが私hookの出力でも分かるようになった
根据这图的画线,可以明确的知道这md5就是时间戳,在我hook的输出中也可以看到这个就是时间戳

ida_code_draw_line_analysis.jpg

terminal_print_1.jpg

token://com.coolapk.market/c67ef5943784d09750dcfbb31020f0ab? についてはこれは不変である
至于 token://com.coolapk.market/c67ef5943784d09750dcfbb31020f0ab? 这个是不变的

3、结论(结论)

token://com.coolapk.market/c67ef5943784d09750dcfbb31020f0ab? + md5暗号化されたタイムスタンプ + $ + device id + & + com.coolapk.market(カバンの人),そのmd5を暗号化して第一部を得る
token://com.coolapk.market/c67ef5943784d09750dcfbb31020f0ab? + md5加密后的时间戳 + $ + device id + & + com.coolapk.market(包名),将其md5加密后得到 第一部分

パッケージ名のtokenの由来は、第一部+ deivce id + 0x +十六進変換されたタイムスタンプである
token的来历就是:第一部分 + deivce id + 0x + 十六进制转换后的时间戳

簡単なテストコード:
简单的测试代码:

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
原文: Python反反爬虫 - Frida破解某安卓社区token反爬虫

My blog: 2h0n9
E-Mail: zckuna@163.com

見てくれてありがとう!!!
感谢观看!!!
Thanks for watching!!!

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away