Packet capture analysis and application of 91 short video encryption algorithm

Posted by dfowler on Sat, 08 Jan 2022 09:26:57 +0100

Target APP: 91 short video

I posted an article before Ant accelerator brush invitation According to the article, this APP is basically the same as the encryption algorithm of ant accelerator. It is not available for download.

0x00 tool preparation

Tool installation package and configured lightning simulator system backup Download: Password: 4vpf

For details such as the introduction to the use of each tool, please refer to their respective documents or Baidu.

0x01 ideal analysis process

Create a new simulator, install the target APP and tools, and do not open the target APP first.

1. Using ProxyDroid to solve the problem of missing packages

Open ProxyDroid, fill in the proxy server IP and port according to the example below, and select HTTP as the protocol (even if HTTPS is used, HTTP is also selected)

You can choose whether to use global agent or sub application agent. Here, you can directly use global agent.

The proxy server IP and port are the agents in Fiddler. The default port is 8888. The IP is the LAN IP. There is an icon shown below in the upper right corner of Fiddler. You can hover over it to display the LAN address, generally 192.168.

2. Automatic hook encryption algorithm and hash with inspectage

Open inspection and select the target APP. The interface is as follows.

Forward port 8008 to the computer according to the prompt in the figure, that is, open cmd in the simulator running directory, and then enter:

adb forward tcp:8008 tcp:8008

It opens in the computer browser You can see the inspectage web page interface.

Click on the setting to turn off all unnecessary items, leaving only Crypto and Hash, and turn on automatic refresh.

3. Start analysis

After the tools are configured, open the target APP until the invitation code is successfully bound. (or stop in the open screen advertisement, check the Crypto of inspectage, and turn off the automatic refresh after the results are displayed, because I don't know why the records are automatically cleared here, but there will be a registered package to use just after startup.)

The analysis method is to find the corresponding encryption and decryption input and output in the inspection according to the packet parameters in Fiddler.

  1. Click the package that binds the invitation code. When binding the invitation code, look at Fiddler to determine which package it is. If nothing happens, the target domain name is the penultimate one in fiddler.

    You can see that there are three sending parameters, a timestamp, a HEX format data, and an MD5 sign.
  2. Search for the invitation code you entered in the Crypto of inspectage. If you don't find it, click the folded encrypted data and search again. The plaintext is encrypted and the base64 is decrypted.

    It can be determined here:

    • The encryption algorithm is AES,
    • key is h3PV8o444kNybrx77icyiriQ2q0uTjqUSsFRfaynkT8 = (base64 code),
    • iv is DrRMfzwgpjgI1sIjfW8aXw = = (base64 encoding),
    • The decryption mode is AES/CFB/NoPadding,
    • The format of encrypted data is {mod":"user","build_id":"a1000","token":"","oauth_id":"xxxxx","oauth_type":"android","aff":"xxxxx","app_status":"xxxx:2","version":"4.5.5","apiV2":"v2","app_type":"local","code":"invitation "}
  3. Use the encrypted results HEX encoding conversion Convert to HEX, and find the upper pair after removing the first 32 characters in and data. According to the experience of the previous article, these 32 characters should be IV. Convert the just obtained IV, DrRMfzwgpjgI1sIjfW8aXw = = to HEX, and it is found that it is exactly equal to the first 32 bits of data.

    It can be determined here that the transmitted data is iv spliced with the HEX code of the encrypted data.
  4. Using the same method, go to the sign found in the hash interface, and you can see that it is a long string of hashes for MD5 encryption.
  5. Continue to search this long string of hash es and find that it is exactly the result of the following SHA256 encryption.

    The format of encrypted string is data={}xtamp={}132f1537f85sjdpcm59f7e318b9epa51
  6. At this time, I took it to verify SHA256 and found that the result was not right. Take a closer look and find that the timestamp is not fully displayed. It is estimated that there is a problem. Manually complete it as data = {} & timestamp = {} 132f1537f85sjdpcm59f7e318b9epa51. Verify again and find that there is no problem.
  7. The encrypted data is almost clear, and the decryption of the returned data should be the same. Decrypt the previously obtained key and iv and find that they are incorrect. At this time, according to the previous experience, it is speculated that the first 32 bits of the returned data are iv, and the later is the data to be decrypted. It is found that this is the case.

    Since various AES encryption and decryption tools fix the input and output formats, few of them support HEX or base64 encoding and key and iv, it is recommended to write code verification or use other tools, or manually convert the encoding and then use the tool for verification. Here is to convert the data to be decrypted [HEX to base64] [], convert the key and iv to HEX, and then use the tool for verification
  8. Here, I only saw the package with the invitation code. Now I'll look at the registered package. After all, I can't brush the invitation without registering. According to the sequence in Fiddler, decrypt and view the contracted data in turn. As a result, no registration words are found, and multiple data are exactly the same. So you can guess that either you didn't catch the registration package, or these data packages completed the registration automatically.

The analysis part can end here. There are key, iv, encryption and decryption data template and sign template. You can write code directly.

4. Why is it all speculation?

Seeing this, I wonder how it is all guesses. Can you guess these by direct analysis?

It's hard to guess.

Because the actual analysis process is not so smooth, the intermediate decompilation looked at the source code. At the same time, combined with the experience of analyzing ant accelerator last time, many things can be seen at a glance. The encryption of the two apps is basically the same.

If you really don't understand how to guess, take a look at the previous article Ant accelerator brush invitation There is a decompile analysis part in the article.

0x02 actual analysis process

Lightning 3, Android 5, is used here because Android 7 is troublesome to install certificates. The green version comes from the website of paidaxing simulator.

At the beginning, grab the package, open Fiddler, set the agent in the simulator, install the certificate and open the APP.

A meal of operation is as fierce as a tiger. There is nothing in the result, and the APP can be opened and used normally. At this time, you can guess that APP disables the agent.

General anti bag grabbing measures shall be understood by yourself. When the packet cannot be caught, if the APP flashes back or cannot access the network, the agent may be detected; If the APP is used normally, the agent should be disabled.

Here, you can use the packet capture tools on the mobile terminal such as the packet capture wizard and little yellow bird, or use the agent tool to forward the traffic to Fiddler. Because the mobile terminal is inconvenient to operate, you can use ProxyDroid to forward the traffic here.

Other tools can be used, such as dragon, Postern and sockdroid. However, I recommend ProxyDroid. The proxy mode is complete and the setting process is simple and clear.

After using the proxy tool, I still didn't catch the package I wanted. Something's wrong. I began to doubt that it's not HTTP protocol. Maybe ws or tcp was used.

There is no clue here, so I can only look at the results of inspection, and then decompile (without shelling) to find the algorithm. I found it for a long time. At this time, you can use frida to get the results, but I still want to try to catch the bag.

After that, they tried to add agents with inspection and socks, but failed.

Finally, I accidentally found that when I opened the APP for the first time after the new simulator was built, I could catch the package with ProxyDroid. After that, I tested it many times. No matter what posture, I couldn't catch the package except the first time. Did I use tcp except the first time?

However, fortunately, the packet is still caught. At this time, the requested packet can be spliced by combining the encryption algorithm and hash found in hook in inspectage. See the above for details.

I still can't figure out why. I hope someone who knows can solve my doubts.

Python code

# -*- encoding: utf-8 -*-
@File    :
@Time    :   2022 January 4, 2014:24:32 Tuesday
@Author  :   erma0
@Version :   1.0
@Link    :
@Desc    :   91 Short video brush invitation

import requests
import time
import json
from base64 import b64decode
from hashlib import sha256, md5
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
# from Crypto.Hash import SHA256, MD5  # Same as hashlib Library

class Aff(object):
    91 Short video brush invitation
    def __init__(self, aff: str = "gcKyA"):
        self.aff = aff
        self.oauth_id = ''
        self.timestamp = ''
        self.url = ''
        # self.url = ''
        self.headers = {  # You can add header or not
            'Accept-Language': 'zh-CN,zh;q=0.8',
            'Mozilla/5.0 (Linux; U; Android 5.1.1; zh-cn; M973Q Build/LMY49I) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30',
            'Content-Type': 'application/x-www-form-urlencoded'
        # self.b64key = 'h3PV8o444kNybrx77icyiriQ2q0uTjqUSsFRfaynkT8='
        # self.b64iv = 'DrRMfzwgpjgI1sIjfW8aXw=='
        self.key = b64decode('h3PV8o444kNybrx77icyiriQ2q0uTjqUSsFRfaynkT8=')
        self.iv = b64decode('DrRMfzwgpjgI1sIjfW8aXw==')

    def get_timestamp(long: int = 10):
        Take the timestamp, 10 bits by default
        return str(time.time_ns())[:long]

    def decrypt(self, data: str):
        aes decrypt
        ct_iv = bytes.fromhex(data[:32])
        ct_bytes = bytes.fromhex(data[32:])
        ciper =, AES.MODE_CFB, iv=ct_iv, segment_size=128)
        # In CFB mode, iv specifies that the block size is 128 (the default is 8, and a multiple of 8 needs to be filled in. It seems that the AES standard block size is 128, which has nothing to do with the key size 128 / 192 / 256)
        plaintext = ciper.decrypt(ct_bytes)
        return plaintext.decode()

    def encrypt(self, data: str):
        aes encryption
        ciper =, AES.MODE_CFB, iv=self.iv, segment_size=128)
        ct_bytes = self.iv + ciper.encrypt(data.encode())  # iv + combination of encryption results
        return ct_bytes.hex().upper()  # hex coding

    def get_sign(self):
        generate sign
        template = 'data={}&timestamp={}132f1537f85sjdpcm59f7e318b9epa51'.format(
            self.encrypt_data, self.timestamp)
        # sha256
        sha = sha256()
        res = sha.hexdigest()
        # md5
        m = md5()
        res = m.hexdigest()
        return res

    def request(self, d: dict):
        Request packet
        plaintext = {
            "build_id": "a1000",
            "token": "",
            "oauth_type": "android",
            "app_status": "A72B8E7B0E661AAEEB5280AAC3993DC6F4A2D8C0:2",
            "version": "4.5.5",
            "apiV2": "v2",
            "app_type": "local"
        self.timestamp = self.get_timestamp(10)
        self.encrypt_data = self.encrypt(json.dumps(d, separators=(',', ':')))
        sign = self.get_sign()
        data = {"timestamp": self.timestamp, "data": self.encrypt_data, "sign": sign}
        res =, data=data, headers=self.headers)
        resj = res.json()
        res = self.decrypt(resj.get('data'))
        return res

    def get_user(self):
        Generate new user
        # Take random md5
        m = md5()
        oauth_id = m.hexdigest()

        data = {"mod": "system", "oauth_id": oauth_id, "code": "index"}
        self.oauth_id = oauth_id

    def invite(self):
        Brush invitation, invitation code: self.aff
        data = {"mod": "user", "oauth_id": self.oauth_id, "aff": self.aff, "code": "invitation"}

if __name__ == "__main__":
    aff = Aff('gcKyA')
    # data = 'x'
    # print(aff.decrypt(data))

Topics: Python Encryption Fiddler