Python anti crawler - Frida cracked an Android community token anti crawler

preface

Not much pressure. This Android community is Kuan. I thought about climbing this software before, but I forgot. I grabbed its package a few days ago and found a token verification in the request headers, which was decisively broken

Analysis process

Grab a bag first
You can see that there is a request header X-App-Token, which is authentication. As for X-App-Device, it should obtain your mobile phone information. Regardless of it, first look at the software source code and find the request method

1. jeb analysis

No reinforcement, no confusion, comfortable
Search keyword: X-App-Token

Obviously, we have found what we want. (jeb3.0 press tab to decompile)

The X-App-Token is the variable v2_1,v2_ 1 is returned by the getAS method in the AuthUtils class
Following up, we can find that this is a native method, and lib is a native lib

We can't use jeb analysis here. Let's see what parameter 2 deviceId is first

You can see that it is returned by the getDeviceID method in the SystemUtils class, and a context parameter is passed in
Let's go to the app's application hook
I found a method with the least amount of code, which can help us hook without breaking the original logic

hook code is very simple

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;
    }
})

Analyze so after getting deviceId

ida analysis

Unzip apk and get native lib So, open it with ida
If we know the method name and the number of parameters, we will search for the method name first

Press option+t in the Function Window to search for getAS. You can see that there is no hair

Then let's search in IDA View. The shortcut keys are the same
Found this. There are two parameters, but this is not a method. It cannot be decompiled with F5
To tell you the truth, I don't know what this DCB is But I saw this when I was rummaging through the Function Window looking for some method names that I could understand

As soon as I see, this method seems to have something to do with getAS, so I can easily F5

After a brief look at the code inside, I guess I'm looking for this method. Why this method instead of others

I analyzed the composition of the X-App-Token verification. It looks like this:

f2c29a109fde487e9350d3e6b881036a8513efac-09ea-3709-b214-95b366f1a1850x5d024391

I got my device ID before. I accidentally saw my device ID in it, and then I split it into this:

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

The first one is obviously md5 ciphertext, the second part is device id, and the last one is hexadecimal. I don't know what it is, but I saw this paragraph in the getAuthString code

This is a string splicing process, in which v82 is md5 ciphertext and the head of the string, followed by v43 (device id), string 0x and finally hex_time (this is the name I changed), so I can be sure that this method is what I want;

The above is the next analysis. The hexadecimal thing is the timestamp. We only need to analyze how md5 comes from. We know that md5 is v61, and the encryption code of v61 is here
The encrypted content is v58, which is a variable encoded by base64

I directly hook the md5 encryption class. There are three methods: md5 (which should be the constructor), update and finalize. The hook code is as follows

var JNI_LOAD_POINTER = Module.getExportByName('libnative-lib.so', 'JNI_OnLoad'); // First get JNI_ Address of onload method
// What is subtracted here is JNI from so_ Address of onload 0x31A04
var BASE_ADDR = parseInt(JNI_LOAD_POINTER) - parseInt('0x31A04'); // Running JNI with program_ The base address is obtained by subtracting the absolute address of onload from its relative address

// MD5::MD5
Java.perform(function() {
    //  Then use the base address + the relative address of the method to hook to get the absolute address
    var hookpointer = '0x' + parseInt(BASE_ADDR + parseInt('0x32168')).toString(16) // Get the address of the hook method to be
    var pointer = new NativePointer(hookpointer) // Build nativepoint based on method address
    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] -> [Before method call]')
                console.log('Parameter 1: {0} => {1}'.format(arg0, Memory.readCString(arg0))) // Memory.readCString is to read the address as a string, similar to readUtf8String, readUtf16String, etc
                console.log('Parameter 2: {0} => {1}'.format(arg1, Memory.readCString(arg1)))
                console.log('\n')
            },
            onLeave: function(retval) {
                console.log('\n')
                console.log('=====> [MD5::MD5] -> [After method call]:')
                console.log('Return value: ', retval)
                console.log('Parameter 1: {0} => {1}'.format(arg0, Memory.readCString(arg0)))
                console.log('Parameter 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) // Get the address of the hook method to be
    var pointer = new NativePointer(hookpointer) // Build nativepoint based on method address
    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] -> [Before method call]')
                console.log('Parameter 1: {0} => {1}'.format(arg0, Memory.readCString(arg0)))
                console.log('Parameter 2: {0} => {1}'.format(arg1, Memory.readCString(arg1)))
                console.log('Parameter 3: {0} => {1}'.format(arg2, Memory.readCString(arg2)))

                console.log('\n')
            },
            onLeave: function(retval) {
                console.log('\n')
                console.log('=====> [MD5::update] -> [After method call]:')
                console.log('Return value: ', retval)
                console.log('Parameter 1: {0} => {1}'.format(arg0, Memory.readCString(arg0)))
                console.log('Parameter 2: {0} => {1}'.format(arg1, Memory.readCString(arg1)))
                console.log('Parameter 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) // Get the address of the hook method to be
    var pointer = new NativePointer(hookpointer) // Build nativepoint based on method address
    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] -> [Before method call]')
                console.log('Parameter 1: {0} => {1}'.format(arg0, Memory.readCString(arg0)))
                console.log('Parameter 2: {0} => {1}'.format(arg1, Memory.readCString(arg1)))
                console.log('Parameter 3: {0} => {1}'.format(arg2, Memory.readCString(arg2)))
                console.log('Parameter 4: {0} => {1}'.format(arg3, Memory.readCString(arg3)))
                console.log('\n')
            },
            onLeave: function(retval) {
                console.log('\n')
                console.log('=====> [MD5::finalize] -> [After method call]:')
                console.log('Return value: ', retval)
                console.log('Parameter 1: {0} => {1}'.format(arg0, Memory.readCString(arg0)))
                console.log('Parameter 2: {0} => {1}'.format(arg1, Memory.readCString(arg1)))
                console.log('Parameter 3: {0} => {1}'.format(arg2, Memory.readCString(arg2)))
                console.log('Parameter 4: {0} => {1}'.format(arg3, Memory.readCString(arg3)))
                console.log('\n')
            }
        }   
    )
})

After running, the base64 encoded content is obtained:

dG9rZW46Ly9jb20uY29vbGFway5tYXJrZXQvYzY3ZWY1OTQzNzg0ZDA5NzUwZGNmYmIzMTAyMGYwYWI/MzgyMzIxNWQ5MWQyOWQ5ODg3ZWJjMDVmMGQ3ZmQzMGQkODUxM2VmYWMtMDllYS0zNzA5LWIyMTQtOTViMzY2ZjFhMTg1JmNvbS5jb29sYXBrLm1hcmtldA==

After decoding:

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

After I see this code
The above decoded content can be divided into:

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

Just go to the origin of md5 encryption in the second part. Continue to analyze and find the place of encryption
According to the line drawn in this figure, you can clearly know that this md5 is the time stamp, and you can also see that this is the time stamp in the output of my hook
as for token://com.coolapk.market/c67ef5943784d09750dcfbb31020f0ab? This is the same

conclusion

token://com.coolapk.market/c67ef5943784d09750dcfbb31020f0ab? +md5 encrypted timestamp + $+ device ID + & + com coolapk. Market (package name), get the first part after md5 encryption

The origin of token is: the first part + deivce id + 0x + time stamp after hexadecimal conversion

Simple test code:

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)

    # Timestamp encryption
    md5_t = hashlib.md5(str(t).encode('utf-8')).hexdigest()

    # I don't know what the hell string splicing is
    a = 'token://com.coolapk.market/c67ef5943784d09750dcfbb31020f0ab?{}${}&com.coolapk.market' \
        .format(md5_t, DEVICE_ID)

    # I don't know what the ghost string is. The spliced string is encrypted again
    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()

Finally, thank you for reading. Each of your likes, comments and sharing is our greatest encouragement. Refill ~

If you have any questions, please discuss them in the comment area!

Keywords: Python Android Back-end crawler

Added by stevehaysom on Sat, 29 Jan 2022 12:14:06 +0200