Tiktok volcano version device registration generates device_id and iid

1, Foreword
Tiktok volcano volcano volcano is a small video. It is understood that the volcanic video has been upgraded to the tiktok version. It is necessary to upgrade its security measures. The author studied the equipment registration method of the flare volcano version, which is to generate device_. Tiktok can make nothing of it. It is possible to simulate the two ID generated by these two ID ID and the iid (install_id) process. It is proved effective when accessing the flare volcano version network interface, but it can not be used for other headlines applications.

2, Sample
Tiktok is very important when reverse or analysis, and the difference between the same app and different versions is also very great. This article uses the volcano version android:versionName=, 8.6.. 5″.

3, Reverse
static analysis
Static analysis is decompilation. There are many decompilations, from the basic Apktool tool to the powerful jadx, and the relatively powerful JEB. Sometimes Android studio can help with the analysis. GDA is also good. The author often uses jadx and JEB. If you want to do well, you must first sharpen your tools. This sentence is too suitable in the study of reverse analysis. No more nonsense. Let's start now.

Tiktok jadx opens the apk file of the flick of the volcano version, you can find AndroidManifest. first. XML file and record its package name package = "com ss. android. ugc. "Live", and then? I looked confused. I didn't know what to look at until I saw the work of my predecessors. I can know that there is a key api: https://log.snssdk.com/service/2/device_register/ , search this string first. You can try to search device_ Register or service / 2 / device_ Register / and other possible strings are OK. See what code you can find. When using jadx for specific operations, you need to select navigation - search text, and then wait a while, because it is decompiling, as shown in the following figure:
After decompiling, you can try to find the text: service/2/device_register /, a suspicious class was found:... Deviceregister b.a.URL_ DEVICE_ Register (), as shown in the figure below:
Click in and have a closer look. It is found that this is a function for constructing url list. It must be called somewhere. Then press the control key to find the use case of the function. It is found that there is only one place to call it except itself. Enter the function calling it and find that it is a function that fails to decompile normally, as shown in the following figure:
You can see a DeviceRegisterThread string coming into view, and then you can probably know that this class is more important. You can see the Thread class it inherits above, so this should be the sub Thread of the network part of the device registration request. Alas, in fact, at the beginning, the author found other places and focused on analyzing the device_ The local cache where the ID is located, because when there is a local cache, the local cache will be loaded first rather than initiating the network request. Later, it was found that there is no use after analyzing the local cache, but it is better to understand several nearby classes. Going further, let's continue to analyze the com. Com inherited from the Thread class ss. android. deviceregister. b. C. class A, this class can actually be analyzed from scratch. It will not be described in detail here. First, go through the normal decompilation of jadx, and then you can find that the decompilation failed function in the figure above is very important, which should be the part related to initiating network requests. However, the decompilation of jadx failed, and the pseudo code doesn't seem to be suitable for analysis, so I want to look at the smail code, but I found that the corresponding smail code of this class can't be found. It can only be said that it is a bug of jadx. What can we do? Then JEB can play.

The author used JEB2 before. This time, I searched the tool and found that JEB3 also came out. The JEB hyperlink mentioned above gives the address of JEB3. Although it seems to be a beta version, it does not affect the use. When using JEB2, I encountered a problem before, that is, when directly opening an apk installation package, especially when the installation package with large volume will prompt memory overflow and cannot be opened. The solution is to find the dex file to be analyzed, and then only open the dex file for analysis. Therefore, this time, the author also finds the dex file where the code to be analyzed above is located, and then analyzes the dex file separately. So the problem is, how to find the dex file where the code is located? At this time, it seems that some log information is output on the shell when jadx decompiles. You can see if this information can be helpful. Open the apk file again using jadx, and then find com. Com from the source directory ss. android. deviceregister. b. C. decompile class A, as shown in the figure below:
As can be seen from the above figure, the log outputs the DEX file of this class as classes4 dex. Then use JEB to open classes4 DEX, then find this class and analyze the important function above, as shown in the figure below:
This looks good. jeb's decompilation effect on anonymous internal classes is indeed better than jadx. Let's carefully analyze this function. jeb's renaming function is still very easy to use. The function after the author manually renames is shown in the following figure:
It looks more comfortable, but some jumps are still strange. After the author's manual rewriting, the code extraction is as follows:

private boolean is_successful_get_app_config_from_net(String args_json_string) {
    String response = null;
    Object args_bytes_clone;
    String url;
    int url_index;
    String default_response = null;
    boolean is_in_10min_from_last_request;
    long now_time;
    byte[] args_bytes;
    try {
        args_bytes = args_json_string.getBytes("UTF-8");
        now_time = System.currentTimeMillis();
        if(now_time - this.b.mLastGetAppConfigTime < 600000) {
            is_in_10min_from_last_request = true;
        }
        else {
            is_in_10min_from_last_request = false;
        }
    }
    catch(Throwable v0) {
        return false;
    }
 
    try {
        this.b.mLastGetAppConfigTime = now_time;
        String[] url_list = com.ss.android.deviceregister.b.a.URL_DEVICE_REGISTER();
        if(url_list == null) {
            throw new IllegalArgumentException("url is null");
        }
 
        int urls_length = url_list.length;
        url_index = 0;
        while(true) {
            if(url_index >= urls_length) {
                break;
            }
 
            url = url_list[url_index];
            args_bytes_clone = args_bytes.clone();
            if(StringUtils.isEmpty(url)) {
                ++url_index;
                continue;
            }
 
            url = NetUtil.addCommonParams(SemUtils.updateUrl(this.b.mContext, url), true);
            Logger.debug();
 
            try {
                if(!this.is_encrypt()) {
                    if(is_in_10min_from_last_request) {
                        url = url + "&config_retry=b";
                    }
 
                    response = NetworkClient.getDefault().post(url, args_bytes, true, "application/json; charset=utf-8", false);
                    break;
                }
 
                try {
                    response = NetUtil.sendEncryptLog(url, ((byte[])args_bytes_clone), this.b.mContext, is_in_10min_from_last_request);
                    break;
                }
                catch(RuntimeException v0_3) {
                    if(is_in_10min_from_last_request) {
                        try {
                            url = url + "&config_retry=b";
                            response = NetworkClient.getDefault().post(url, args_bytes, true, "application/json; charset=utf-8", false);
                            break;
                        }
                        catch(Throwable v0) {
                            try {
                                if(!this.b.shouldRetryWhenError(v0)) {
                                    throw v0;
                                }
                                ++url_index;
                                continue;
                            }
                            catch(Throwable v0) {
                                return false;
                            }
                        }
                    }
 
                    response = NetworkClient.getDefault().post(url, args_bytes, true, "application/json; charset=utf-8", false);
                    break;
                }
            }
            catch(Throwable v0) {
                try {
                    if(!this.b.shouldRetryWhenError(v0)) {
                        throw v0;
                    }
                    ++url_index;
                    continue;
                }
                catch(Throwable v0) {
                    return false;
                }
            }
        }
    }
    catch(Throwable v0) {
        return false;
    }
 
    if(response != null) {
        try {
            if(response.length() != 0) {
                this.parse_net_config(new JSONObject(response));
                return true;
            }
        }
        catch(Throwable v0) {
            return false;
        }
    }
 
    return false;
}

So you can basically understand what it's doing.

To put it simply, first get a list of url addresses registered with the device, and then traverse the access. If the access is successful, it ends. When accessing, first add public parameters, and then judge whether an encryption request is required. If an encryption request is required, call the encryption request function, and if not, call the ordinary request function, In addition, if you call the encryption request function and throw an exception, you will also call the ordinary request function and some retry parameters. At this time, when you think that the device registration requests found in the previous packet capture are encrypted, you can judge whether it is_ The function encrypt() returns true. If the function returns false, is it an unencrypted request? Well, that's all for the static analysis. Let's do a dynamic analysis, that is, try the effect when this function returns false.

dynamic analysis
Dynamic analysis is to analyze the app when it is running. It is more common to analyze the network request behavior of the app, the behavior of calling the system API, the behavior of accessing files, and so on. Since this paper studies the generation of device id, that is, a network interface during device registration, we can only analyze the network request behavior. Charles is the most commonly used network packet capturing tool. You need to configure an agent and install Charles's certificate on the mobile terminal. This part will not be described in detail.

As mentioned above, this paper mainly studies this interface: https://log.snssdk.com/service/2/device_register/ Under normal circumstances, the device will access this interface encrypted during registration. For example, after the app is newly installed or the app is cleaned up, it will access this interface when opening the app. The access is a POST request, the parameters are clear text, but the data is encrypted, as shown in the following figure:
Through the above static analysis, we can see that is_ The encrypt () function can control whether to encrypt the transmission, so you can think of using Xposed to perform the Hook function, so that the return value of the function is fixed to false.

Xposed is very powerful. It can inject code when other app s are running. The specific principle and tutorial will not be detailed here. The following author will post the code of hook, which is actually very short:`

findAndHookMethod("com.ss.android.deviceregister.b.a", param.classLoader, "isEncrypt", new XC_MethodHook() {
    @Override
    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
        boolean oldResult = (boolean) param.getResult();
        Log.i(TAG, "com.ss.android.deviceregister.b.a.isEncrypt(), oldResult=" + oldResult + ", newResult=" + false);
        param.setResult(false);
    }
});

Of course, the classes and methods that need hook still need to be found in jadx or jeb. The author has given it in the above code. How to write the hook module, then install it into the mobile phone, restart and take effect, etc. will not be repeated here.

Let's take a look at the hook, that is, is_ The encrypt() function returns the network request after false, as shown in the following figure:
You can see that the data part here has become clear text.

So far, the plaintext interface for device registration has been found, but looking at the work of the elder, he simulates the generated device by requesting registration in ciphertext_ id and iid cannot be used, so the author uses this plaintext registration method to simulate the generation of device id, which is similar to the code of the predecessors, and then uses the simulated generated device id to test the access to other interfaces, which seems to be available.

4, Summary
Overall, volcano volcano tiktok is much safer than the previous small video. Maybe it has some relations with the jitter. After all, it is tiktok product. It is not very difficult to reuse technology. And there is a certain anti decompile in the key code logic part, just like the function that I manually restore. This paper only makes a detailed reverse analysis for a small function, and does not involve the native layer. The confrontation estimation of that part is stronger, so let's do it first.

Technical exchange V:Dongjidao0705

Keywords: Java Android security

Added by hwmetzger on Wed, 22 Dec 2021 23:50:04 +0200