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

(ヒント：日本語はすべてGoogle翻訳からのものです)
## まえがき(前言)
> <del>这个社区是酷安，以前想过要爬(spider)这软件，但是都忘了，几天前抓了下它的包，发现请求 **headers** 里有一个 **token** 验证，果断就给破了<del>

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

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

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

![zhuabao.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317662/34cbd1bc-a01a-4c38-84d3-91b0c92e9aa1.jpeg)

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

#### 1、jeb分析(jeb分析)
補強なし、混乱していないようです
<del>没加固，好像也没混淆，舒服<del>

![jeb_analysis_home.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317662/9a8c4cdc-fcb8-8c36-6a53-2052f0d7b376.jpeg)

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

![jeb_search_keyword_x-app-token.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317662/39fa55de-e9c8-0b18-1b26-0fc281edcc85.jpeg)

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

![jeb_have_fond-x-app-token.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317662/0a443706-441f-97d2-cdd5-6b5896b55bd3.jpeg)

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

![jeb_lock_value.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317662/a79a4235-bcfd-22d7-c748-178ffb40ccd8.jpeg)

![jeb_value_from.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317662/546b9b22-1ad1-e925-2895-ba55b2f94c6c.jpeg)

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

![](images/jeb_native_func.jpg)

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

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

![jeb_lock_deviceid_from.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317662/d00a0882-15c6-9788-2a96-e2a3c0fb43bb.jpeg)

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

![jeb_find_application_class.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317662/2d531d4b-a0a9-7379-3483-2bc1af8d7a08.jpeg)

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

![jeb_find_hook_func.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317662/4325ba45-5e43-08c3-4b80-58f0bd204048.jpeg)

HOOKコードは簡単です
<del>hook的代码很简单<del>

```javascript
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ファイルを分析します
<del>拿到 deviceId 后分析so<del>

#### 2、IDA分析(IDA分析)
apkを解凍し、**native-lib.so** を取得し、idaで開きます
<del>解压apk拿到 **native-lib.so**，用ida打开<del>

![ida_open_so.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317662/ea30ed63-36e0-ff80-a280-1fec32a78434.jpeg)

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

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

![ida_search_keyword_getAS.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317662/8c9f7db6-9a68-2cdc-778c-0f29316d15bb.jpeg)

![ida_not_found.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317662/0ceb493a-a477-7d06-6529-8392ec04967f.jpeg)

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

![ida_ida_view_search.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317662/b7e58817-f54a-2445-5dfe-83efc65bb3d5.jpeg)

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

![ida_ida_view_have_found.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317662/d05e734b-bb6a-0e54-1636-337d2973eb29.jpeg)

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

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

![ida_func_win_search_again.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317662/fbc77688-1563-1924-36e1-85c827cf9d0a.jpeg)

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

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

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

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

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

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

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

![ida_code_string_splice_1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317662/890e5283-663d-2225-c1e2-fa3a51cf3060.jpeg)

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

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

![ida_code_md5_1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317662/89a509b7-d1a8-df52-bdb4-f6cddcb0d2a4.jpeg)

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

![ida_code_base64_1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317662/46b3dc72-66df-3ddf-e29a-2959ba61c08f.jpeg)

私はそれが何か見るのがおっくうで、私は直接md5の暗号化類をhookして、3つの方法があって、1つはmd5(構造方法であるべきです)、update、finalize、hookのコードは次の通りですか
<del>我懒得去看它是什么了，我直接hook了md5加密类，有三个方法，一个是md5(应该是构造方法把)、update、finalize，hook 代码如下<del>

```javascript

// ベースアドレスを計算する。
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コードされた内容が得られる:
<del>运行后得到了那个base64编码过的内容：<del>
> dG9rZW46Ly9jb20uY29vbGFway5tYXJrZXQvYzY3ZWY1OTQzNzg0ZDA5NzUwZGNmYmIzMTAyMGYwYWI/MzgyMzIxNWQ5MWQyOWQ5ODg3ZWJjMDVmMGQ3ZmQzMGQkODUxM2VmYWMtMDllYS0zNzA5LWIyMTQtOTViMzY2ZjFhMTg1JmNvbS5jb29sYXBrLm1hcmtldA==

復号後:
<del>经过解码后：<del>
> token://com.coolapk.market/c67ef5943784d09750dcfbb31020f0ab?3823215d91d29d9887ebc05f0d7fd30d$8513efac-09ea-3709-b214-95b366f1a185&com.coolapk.market

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

![ida_code_string_splice_2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317662/c3ee7b43-49c4-7d33-38cc-e21ece83ee0a.jpeg)

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

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

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

![ida_code_md5_2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317662/c3e87f3a-5883-5ddd-b5be-d59dfd56c8b5.jpeg)

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

![ida_code_draw_line_analysis.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317662/cb886d09-56bc-47a0-6e4a-c9f4b03e267c.jpeg)

![terminal_print_1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317662/2f83c1b0-5106-3e13-e85c-ce7d6803181a.jpeg)

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

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

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

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

```python
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](https://github.com/ZCKun/CoolapkTokenCrack)
原文: [Python反反爬虫 - Frida破解某安卓社区token反爬虫](http://www.2h0n9.com/2019/06/14/python-anti-anti-crawl-frida-coolapk-token-crack-19-06-14/)

My blog: [2h0n9](http://2h0n9.com/)
E-Mail: zckuna@163.com

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