Análisis inverso de las funciones de cifrado y descifrado de cierto software de color [Primera prueba de ir al revés]

prefacio

Por casualidad vi un software de color mientras navegaba por Internet, lo que despertó mi interés por el análisis. En el proceso de análisis inverso, descubrí que la capa nativa estaba escrita en lenguaje go. No había tocado el reverso del lenguaje go antes, así que también aproveché esta La oportunidad de aprender el conocimiento relevante del lenguaje go reverso. Grabé el proceso principal de análisis de este software y lo compartiré con ustedes aquí.

análisis de capas java

Uso directamente httpcanary para capturar paquetes. Entre muchos paquetes, elijo el paquete con información de usuario en la url. Esta es la forma de obtener datos de usuario.

inserte la descripción de la imagen aquí

Mirando su cuerpo de solicitud y cuerpo de respuesta, encontré que el valor con el nombre de clave "datos" son datos cifrados, por lo que mi objetivo es encontrar la ubicación donde los datos se cifran y descifran, y restaurar su algoritmo.

请求体:
{"data":"5156526f6a634c4a744765506f684c746be324ec3592da9430b9ac8d07dab8c9183d56117c55bbb9001ded438ac2cd7776c25e4c57902ade91cbcd254747f54d2e0b91c47c54116ce1606ac59e355334d30cbf1f2794a64ef137d9593fbf03951044b189b716e7315af62e248195436b0a00ea7036626667eb03abe7ad73c417d3c6add6c2eaa502ea30f84fdb07cd70df8f10813bfcdab98d216704ba012ec30bbaffa3cdf2595e0cd8ce2d1f0c09d8d5d3803bcc8c2ac4c4ca0786b18bc96eb457f602b0724207ecca5ea0ff05e2e53ed777a1eb4805be967a490a30822b2942c3afb242865687ad3d1d07acc7ad90e75110f2f089747a541ba482f5ae6cb5c6f8a2ad2d2ceaab610c5b086364c9deec9b4c38f83f47c74f1644f5bc73204f6f048722b9ac976ed218cce50545fce2c5ceb9716970dd26f13efe9f83050bc7bb2a09e72360be358f5b17bf646b589cb3dc8e8f04806c9ffb9d9ca244141b73f2bca84dc361d12cf8fe197009592018bf174e86aa68b3f0eec6c823a217498d8a2bd57c495b2dc798f99caa601d69259fcd0004e8da0dd854d59daed89238fd2bc1ac2b2c69d8d14a639555c3497947641bb8076c69c8555eed606aac6eb742a2ca061c130bb4b1d034bb9a0b6afebe13e39cbe010343cd49971c76f5b83a6e1b69f45726a148c541da76eae511df3712dfbe26b119f3fd9fe104ada39e346c1025c110272863f5d55a3ab07accd38ed71f2fc46857d26177ebc0f2b7b82aa926c0c2a20efd9b0060a24b683e9beca8030561cc879b0d5a95fb6beec131b5e48f7b52fd6087fe8f2d927c2adad87f9aa2d50c857e6f3004729f7dfadc583918e7e4830f181bb185575254683e47bb3ab6bb829d5db789875160a2b6cce8e7f256e4ea08a80782dde53beaebf5f7720620a667649d3e44af","_ver":"v1","sign":"220676c5b98327c02d44c0b2252fd11a","timestamp":"1674027054"}
响应体:
{"errcode":0,"timestamp":1674026760,"data":"33F508647B1E3CFBBD09D0DEEAA5249F34BA0787663F026C278020170183093CEB9305656502A9E2D38054FA2C86773FD535EC53775A23713C7720BA47A7839E13AC9271E03C8E521F47E5EDF7BC0D8E6303761737FADE6BDF3D4DBBA2B618B2F7C311389CC6F371E41CC006D465206DB27B92841DD3D9D772CEE6A2345E8B58D224DA72B54597FE8223F189553F2E64A3F13384479DB64104786E6C0143ABB02632EEFB370787253DFC1FB8DA2A77F36AE3A1B1354D288C327D8E1D9622E2088C286B162AF045215B1630E58B9A0459D6EB351B713A35CD7AEF4A71956E4C6BFE19BCA9581B773936B412FAF0EFB204C052328C864C044AB49A716AAE33CDC127FA92A2B2475A351B4071C06D078AFF399C4195F99FC68CE222420CA2336D0756C7DD5F9E28C790BA884AFF28499A2A42DDDC92B7420BB17B7058CEB71B1A6810241FD54897F7B74954B027BED49AA6E332B633D13B0D30CF050D48F16FCDCAE92C7B0D6EA6B0427B1942D94D98B7C9C5E03E0875B8F4C6EFDA1338049ABA59F30FC1323B947A66B20DDF6546AFA8A4B4F1925E6891045F825E0AF32A27B6BDD56B52B7BBEDD070F5FFD8BC35F9473FB3246957B7CCBD4F823C90D6CB7B3A664393D4FE97D6323F07150A83CD31519C48D017E08E35E690C12A292461FD6E87E71249964A47F10EA45FAD61AE047CE860DC11E3AC86195FB0BE45762E4650EE0CA70ED1F4EABAAC56D26E0AB3C5C73561D95744E795121736B35A4CFAE4A62B20AC2A490373578719B3A3F2B8586D1CEA8A0924722721960F849F4909FCE0C9A6C684B9E2EE8AB0ABB99F756EC09D30E0F0A29897E5351E7CF10612A82954B662AFB10A95E14BCEEB7140CC805BBBF0C56BEFF5A609C4E80C379990956D6D4E9E8137F9CA6DA9E1D5BA6B88230AC0F4B5CDAD88E02F8CB45EBE23BEC53BF570C5B1F2D434256326ABCEF1C22FA5F738473545A8D924C01F96CF12775DD1F2715FE56E80737CE6F8771278CDE83C86DEF56B9C8736FEC2DC07308A4BE5F9019C433E83B4A3EBA69E2C5D993CAFD6C8B203F3BDE7CCD8EDCEA743865080FA061841AFABEAC7D543B3D4EBDBB2E7549DD1456C995D98ED7CB50303F70E48F850763D9F895969FB25EAF0A889696C4D73009E54800F28D5904B5593CF87210CC647A2DCB1113AEA0F73DCFDADC28BF7AD4FD6FCCE0D4A3F3B4242CCB29726A3781FF2D6C5D755E63F38E2F6424E7D69C67EA979322F7C9BB4A521EF566C46D31370BDBD8B26A95D82B24D0B340DA7A643647B7870A1C3507AB4FA459B225055A184BBEF0564CB052B02CC6C41D08EE03F907EC82A2A6C90F2437C5E1F7551CFD1D31E18AE065005C808E1991C0A9D3F6E86598C1333D9C4AE110060233DFE97622F8354DAAB0185313DFE15D3CCC99F89131B8DAC3DD0416C47693BBB55A5D707CCBC9B61E9594FCC4A0F626A828A55F0A3CB67ACCF30613D90342060FD7869A2EFB7149300D246716F1AB34D9DAA8F415657B754EFB80240646DE4C5264601BF959611252F4C1AAD1AFDD86FBB52D37C1A9DAF343FC9A791314BB18E3FE6B81B7555621EEB2A8CA408BF86D6261A2CFF6E5BB8873275923020ABEE93D56D3A4D195BBC4E40E652EFDA56D757A41ACA13BE26EF170674A9A6B113163B754C52592B691A969699D785B63E7A482A74FDC401CBA0FFD2AA7E81B47489CFE4D68B3AE7723A97F5EF421CD8FE9D64E2BE2F2A73F93828503D3175A4708E55961F6393826E4B56EDEF13AE1668066B90717AFDD33BF5385A34A6B847A717346874F2717A0F9DE5DF01D66C39E38194A3A441DAB4476881ED9B3206E3D5C8DFEF9EDA3793E53368819E09BF4F7A5E494EEFABED0EAEDDF5674B5182D327296B96D3195B152A1940D29DA9B8A81963EFE1031C20BCFFF6C9CEB1FD8CF04E8CA6DA45AA15FF8086C243A6782093","sign":"e32f75381d198788d65ecd7d23559f2f"}

Para encontrar la posición de empalme del valor de los datos, tengo las siguientes ideas:

  1. Busque la cadena de "datos" directamente en jadx y verifíquela con frida para ubicar la posición de empalme de los datos
  2. Use r0capture para ubicar la ubicación del software que envía y recibe paquetes, y luego encuentre la ubicación del empalme de parámetros retrocediendo
  3. Al conectar algunas funciones de empalme de parámetros de uso común, combinadas con el método de pila de llamadas para ubicar la posición del empalme de parámetros

Para este software, las tres ideas anteriores pueden obtener la posición de empalme del valor de los datos. Aquí solo hablaré sobre el tercer método, y los lectores pueden probar los otros dos métodos por sí mismos.

Principalmente engancho tres clases de uso común y filtro los resultados del enlace a través de "datos", el código del script es el siguiente:

function hook_tools() {
    
    
    let JSONObject = Java.use("com.alibaba.fastjson.JSONObject");
    JSONObject["put"].overload('java.lang.String', 'java.lang.Object').implementation = function () {
    
    
        if (arguments[0] == "data") {
    
    
            var value = arguments[1]
            value = Java.cast(value, Java.use("java.lang.String"))
            console.log("JSONObject put data --> " + value)
            console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
        }
        let ret = this.put(arguments[0], arguments[1]);
        return ret;
    };

    let HashMap = Java.use("java.util.HashMap")
    HashMap["put"].overload('java.lang.Object', 'java.lang.Object').implementation = function () {
    
    
        if (arguments[0] == "data") {
    
    
            var value = arguments[1]
            value = Java.cast(value, Java.use("java.lang.String"))
            console.log("HashMap put data --> " + value)
            console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
        }
        let ret = this.put(arguments[0], arguments[1]);
        return ret;
    }

    let org_JSONObject = Java.use("org.json.JSONObject")
    org_JSONObject["put"].overload('java.lang.String', 'java.lang.Object').implementation = function () {
    
    
        if (arguments[0] == "data") {
    
    
            var value = arguments[1]
            value = Java.cast(value, Java.use("java.lang.String"))
            console.log("org_JSONObject put data --> " + value)
            console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
        }
        let ret = this.put(arguments[0], arguments[1]);
        return ret;
    };
}

function hook_java(){
    
    
    Java.perform(function(){
    
    
        hook_tools()
    })
}

function main(){
    
    
    hook_java()
}

setImmediate(main)

Después de que el software inyecte el script frida, los resultados de la ejecución son los siguientes:

inserte la descripción de la imagen aquí

Al comparar con el resultado de la captura de paquetes, se confirma que el resultado de frida hook es correcto, y combino la pila de llamadas para comenzar com.tencent.mm.network.d.q2el análisis desde la función.

inserte la descripción de la imagen aquí

De acuerdo con la pila de llamadas, la función q2 llamará a la función JSON.parseObject, por lo que el cuadro rojo en la figura anterior es el punto clave. Los parámetros de la función parseObject deben contener datos. Haga clic en la función o2 para observar

inserte la descripción de la imagen aquí

Esta función genera una cadena como valor de retorno a través de la función k, así que ingrese la función k para echar un vistazo

inserte la descripción de la imagen aquí

En la función k, puede ver la posición de asignación de los datos, que es el valor de retorno de e(encryptData).Utilizo la función frida hook e para verificarlo.

function observe_program(){
    
    
    let c = Java.use("com.szcx.lib.encrypt.c");
    c["e"].implementation = function (plainText) {
    
    
        console.log('e is called' + ', ' + 'plainText: ' + plainText);
        let ret = this.e(plainText);
        console.log('e ret value is ' + ret);
        return ret;
    };
}

function hook_java(){
    
    
    Java.perform(function(){
    
    
        observe_program()
    })
}

function main(){
    
    
    hook_java()
}

setImmediate(main)

inserte la descripción de la imagen aquí

Después de la verificación, la función e es la ubicación donde se genera el valor de los datos. Al mismo tiempo, el parámetro pasado por esta función es texto sin formato y el valor de retorno es texto cifrado. Ingresé la función e para observar

inserte la descripción de la imagen aquí

inserte la descripción de la imagen aquí

inserte la descripción de la imagen aquí

La función e finalmente llama a la función de cifrado de la capa nativa. Su primer parámetro es el texto sin formato a cifrar, y el segundo parámetro debe ser la clave. Al mismo tiempo, se puede encontrar que hay muchas funciones de descifrado en la clase. EncryptUtil a la que pertenece la función de encriptación Por el nombre Como puede ver, estas deberían ser las funciones de desencriptación. Por lo tanto, se puede adivinar que la clase EncryptUtil es la clase de herramienta de cifrado y descifrado central de este software, por lo que, siempre que estas funciones estén enganchadas, se puede realizar la captura de paquetes de este software.

Al escribir scripts de Frida, si conecta estas funciones nativas al mismo tiempo, el software se atascará, pero si conecta a las personas que llaman, puede resolver este problema. Solo conecté las dos funciones de encriptar y desencriptar, y otras funciones deberían ser lo mismo que La imagen está relacionada con el video, no le presto atención, el script de captura de paquetes es el siguiente:

function capture(){
    
    
    let c = Java.use("com.szcx.lib.encrypt.c");
    c["f"].implementation = function (plainText, encryptKey) {
    
    
        console.log('encrypt is called' + ', ' + 'plainText: ' + plainText + ', ' + 'encryptKey: ' + encryptKey);
        let ret = this.f(plainText, encryptKey);
        console.log('encrypt ret value is ' + ret);
        console.log("===========================================")
        console.log("===========================================")
        return ret;
    };

    c["b"].implementation = function (encrypted, encryptKey) {
    
    
        console.log('decrypt is called' + ', ' + 'encrypted: ' + encrypted + ', ' + 'encryptKey: ' + encryptKey);
        let ret = this.b(encrypted, encryptKey);
        console.log('decrypt ret value is ' + ret);
        console.log("===========================================")
        console.log("===========================================")
        return ret;
    };
}

function hook_java(){
    
    
    Java.perform(function(){
    
    
        capture()
    })
}

El efecto de captura de paquetes también es muy intuitivo. La siguiente figura muestra los datos descifrados del valor de datos en el cuerpo de respuesta al solicitar información de usuario.

inserte la descripción de la imagen aquí

Dentro de un círculo en el cuadro rojo está la información de membresía del usuario, siempre que cambie falso a verdadero, puede obtener privilegios de membresía, lectores, inténtelo usted mismo.

Después de encontrar las funciones de cifrado y descifrado de la capa java, no está terminada. Las dos funciones nativas, cifrar y descifrar, se analizarán a continuación, y sus algoritmos se restaurarán y ejecutarán sin conexión.

Análisis inverso de capa nativa

Antes de analizar la función de cifrado, primero escriba una función de llamada activa, pase el texto sin formato "123456", la clave es "BwcnBzRjN2U/MmZhYjRmND4xPjI+NWQwZWU0YmI2MWQ3YjAzKw8cEywsIS4BIg=" obtenida por el gancho, después de varias llamadas, se encuentra que bajo la misma entrada condiciones, el resultado cifrado está cambiando. El script de llamada activo es el siguiente:

function call(src) {
    
    
    Java.perform(function () {
    
    
        let EncryptUtil = Java.use("com.qq.lib.EncryptUtil");
        let pwd = 'BwcnBzRjN2U/MmZhYjRmND4xPjI+NWQwZWU0YmI2MWQ3YjAzKw8cEywsIS4BIg=='
        let ret = EncryptUtil.encrypt(src, pwd);
        console.log("encrypt ret --> " + ret)
    })
}

inserte la descripción de la imagen aquí

Use ida para analizar el archivo libsojm.so, busque cifrar en la tabla de exportación, puede juzgar que es una función registrada estáticamente

inserte la descripción de la imagen aquí

Mire el pseudocódigo de la función Java_com_qq_lib_EncryptUtil_encrypt, el efecto de descompilación es muy malo y los parámetros no se reconocen

inserte la descripción de la imagen aquí

Esto se debe a las diferentes convenciones de llamadas de funciones del lenguaje go. La convención de llamadas de funciones ATPCS se usa en la plataforma arm. Los parámetros 1~4 se almacenan en los registros R0~R3 respectivamente, y los parámetros restantes se colocan en la pila desde la derecha. a la izquierda y son La persona que llama implementa el balance de la pila, y el valor de retorno se almacena en R0. El lenguaje go usa la pila para pasar parámetros, y el valor de retorno se almacena debajo del último parámetro. Eche un vistazo a las instrucciones de ensamblaje.

inserte la descripción de la imagen aquí

La convención de llamada de la función ATPS se sigue en la función Java_com_qq_lib_EncryptUtil_encrypt. La función crosscall2 llamada restaurará el entorno requerido por el tiempo de ejecución de Golang. El primer parámetro es un puntero a la interfaz de la función, el segundo parámetro es la dirección del parámetro y el tercer parámetro es el tamaño del parámetro. El puntero de la función apunta a _cgoexp_17c794619cba_Java_com_qq_lib_EncryptUtil_encrypt, establecí un punto de interrupción aquí para comenzar la depuración

inserte la descripción de la imagen aquí

inserte la descripción de la imagen aquí

inserte la descripción de la imagen aquí

inserte la descripción de la imagen aquí

inserte la descripción de la imagen aquí

Lo que se debe notar arriba es que la cadena en el lenguaje go no usa \0 como terminador. Es una estructura. La parte superior de la memoria es la primera dirección de la cadena, y la parte inferior es la longitud de la cadena. cadena cuya definición es la siguiente:

type StringHeader struct {
    
    
    Data uintptr            // 字符串首地址
    Len  int                // 字符串长度
}

A continuación, ingrese la función de cifrado analizada en el paso anterior para continuar con la depuración.

inserte la descripción de la imagen aquí

La función llamada en C3E3FD80 pasó en pwd y devolvió dos cadenas, una es una cadena "mIZUjjghGd" con una longitud de 0xA, y la otra es una cadena "4c7e?2fab4f4>1>2>5d0ee4bb61d7b03" con una longitud de 0x20, debe tenerse en cuenta aquí que las dos cadenas son continuas en la memoria, pero no son una cadena, porque la cadena en el lenguaje go no usa \0 como terminador, sino que tiene una longitud específica

inserte la descripción de la imagen aquí

Continúe bajando para depurar y descubra que una función pasa la entrada y las dos cadenas obtenidas anteriormente, y devuelve el texto cifrado cifrado después de la llamada.

inserte la descripción de la imagen aquí

Ingrese esta función para continuar con el análisis. En esta función, primero hay un ciclo for para hacer una ola de cálculos.

inserte la descripción de la imagen aquí

Después de la depuración, se encuentra que el bucle opera en la cadena "4c7e?2fab4f4>1>2>5d0ee4bb61d7b03", y se obtiene la nueva cadena "3d0b85afe3a3969592c7bb3ee16c0e74" con una longitud de 0x20 después de la operación

inserte la descripción de la imagen aquí

seguir depurando

inserte la descripción de la imagen aquí

La función en la figura anterior pasa dos cadenas y devuelve una nueva cadena "fc9e9a75e633ee4d317b08520b6c3ba9" después del procesamiento, continúe con la depuración

inserte la descripción de la imagen aquí

Esta función solo pasa un parámetro 0 x 10 y devuelve una cadena con una longitud de 0 x 10. Después de muchas veces de depuración, se encuentra que esta cadena se genera aleatoriamente, por lo que los resultados cifrados son diferentes bajo las mismas condiciones de entrada.

seguir depurando

inserte la descripción de la imagen aquí

Esta función pasa la cadena generada anteriormente "fc9e9a75e633ee4d317b08520b6c3ba9", y devuelve una matriz de bytes con una longitud de 0x20. Como dije anteriormente, esta es la clave del algoritmo de cifrado. Después de varias depuraciones, se confirma que esto no cambiará. , por lo que el resultado se puede usar directamente.

inserte la descripción de la imagen aquí

La función a la que salta la instrucción BLX R0 es una función encriptada. Se pasa el texto sin formato "123456" y se devuelve el resultado encriptado. Después de ingresarlo, descubrí que la lógica es bastante complicada. Utilicé el complemento Findcrypt para tratar de encontrar esto, de modo que los algoritmos de cifrado que se utilizan en la siguiente figura sean el resultado de la operación Findcrypt

inserte la descripción de la imagen aquí

Viendo que tiene las características del algoritmo de encriptación AES, ¿cómo verificarlo aquí? La función sub_CC7BC es un usuario de varias matrices AES, por lo que puede establecer puntos de interrupción en las funciones BLX R0 y sub_CC7BC anteriores. Si el punto de interrupción en la función sub_CC7BC se activa después de que se activa el punto de interrupción en BLX R0, entonces esto se puede determinar. El algoritmo de cifrado es probablemente el algoritmo de cifrado AES. Después de la depuración, se verifica que las suposiciones anteriores son ciertas, por lo que lo siguiente es determinar qué modo del algoritmo de cifrado AES se utiliza.

En el proceso de depuración anterior, obtuve una matriz de bytes con una longitud de 0x20 y una cadena aleatoria. La matriz de bytes puede ser la clave, y la cadena aleatoria puede ser la iv. Pruébelo en cyberchef.

inserte la descripción de la imagen aquí

Después de intentarlo, se puede determinar que este algoritmo de cifrado es el estándar AES-256-CFB.

Después de que AES cifra el texto sin formato, el BLX R0 continúa con la depuración

inserte la descripción de la imagen aquí

La función de esta función es empalmar el resultado cifrado con AES con una cadena generada aleatoriamente.

Luego, depure y encuentre una operación de bucle, el pseudocódigo es el siguiente:

inserte la descripción de la imagen aquí

Lo que hace esta operación de bucle también es muy simple, que es convertir la forma hexadecimal de las cadenas empalmadas en una cadena.

La cadena obtenida a través de la operación cíclica anterior es el resultado final, y hasta ahora se han analizado todos los procesos de cifrado. Para resumir, primero pase el pwd entrante a través de una serie de operaciones para obtener una matriz de bytes con una longitud de 0x20, y luego genere una cadena aleatoria con una longitud de 0x10, use la matriz de bytes como clave y la cadena aleatoria como iv para hacer el texto sin formato La operación de encriptación de AES-256-CFB, y luego la cadena aleatoria y el resultado encriptado AES se empalman, y finalmente su hexadecimal se convierte en una cadena para completar todo el proceso de encriptación.

De manera similar, el proceso de descifrado debe ser la operación inversa del proceso de cifrado. Primero, podemos tomar cada byte del texto cifrado como un hexadecimal, cada dos hexadecimales son un byte, y los primeros 0x10 bytes son iv, y conocemos la clave, solo necesitamos realizar la operación de descifrado AES en el texto cifrado para obtener el texto sin formato.

Ejecutar sin conexión

Uso python para restaurar los algoritmos de cifrado y descifrado, y uso la biblioteca de solicitudes para enviar solicitudes de red, el código completo se proporciona a continuación

import json
import random
from Cryptodome.Cipher import AES
import time
import hashlib
import requests

def generate_random_str(len):
    table = b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
    arr = []
    for i in range(0x10):
        index = random.randint(0, 51)
        arr.append(table[index])
    return str(bytes(arr), encoding = "utf-8")

def aes_encrypt(text, iv):
    key = [0xA7, 0x91, 0x4B, 0x68, 0x21, 0x10, 0x16, 0x5D, 0xDB, 0x0B, 0xA7, 0xCF, 0x4E, 0xAC, 0xBE, 0xFC, 0x1C, 0xC2, 0x1F, 0x56, 0xF3, 0x41, 0xB8, 0x2A, 0x89, 0x0A, 0xCA, 0xED, 0x2C, 0xC3, 0x4A, 0x75]
    key = bytes(key)
    mode = AES.MODE_CFB
    cryptos = AES.new(key, mode, iv.encode(),segment_size=128)
    cipher_text = cryptos.encrypt(text.encode())
    return list(cipher_text)

def custom_encrypt(text,random_str,aes_encrypt_list):
    abc_table = b"0123456789abcdef"
    v20 = 0
    v21 = 0
    v31 = 0x10 + len(text)
    arr = []
    for i in range(v31 * 2):
        arr.append(0)
    table = random_str.encode('utf-8') + bytes(aes_encrypt_list)
    while (v20 < v31):
        v23 = table[v20]
        arr[v21] = abc_table[v23 >> 4]
        arr[v21 + 1] = abc_table[v23 & 0xF]
        v20 = v20 + 1
        v21 = v21 + 2
    return str(bytes(arr), encoding="utf-8")

def decrypt(text):
    arr = []
    arr_reHex = []
    text_bytes = text.encode()
    for i in text_bytes:
        arr.append(int(str(chr(i)), 16))
    for i in range(0, len(arr) // 2):
        arr_reHex.append((arr[i * 2] << 4) | arr[i * 2 + 1])
    arr_iv = arr_reHex[0:0x10]
    arr_cipher = arr_reHex[0x10:]
    key = [0xA7, 0x91, 0x4B, 0x68, 0x21, 0x10, 0x16, 0x5D, 0xDB, 0x0B, 0xA7, 0xCF, 0x4E, 0xAC, 0xBE, 0xFC, 0x1C, 0xC2, 0x1F, 0x56, 0xF3, 0x41, 0xB8, 0x2A, 0x89, 0x0A, 0xCA, 0xED, 0x2C, 0xC3, 0x4A, 0x75]
    key = bytes(key)
    mode = AES.MODE_CFB
    cryptos = AES.new(key, mode, bytes(arr_iv), segment_size=128)
    plain_text = cryptos.decrypt(bytes(arr_cipher))
    return str(plain_text, encoding="utf-8")

def generate_sign(data):
    appKey = "81d7beac44a86f4337f534ec93328370"
    timestamp = str(int(time.time()))
    text = "_ver=v1&data=" + data + "&timestamp=" + timestamp + appKey
    text_sha = hashlib.sha256(text.encode('utf-8')).hexdigest()
    text_md5 = hashlib.md5(text_sha.encode('utf-8')).hexdigest()
    return text_md5

def get_userInfo(data, sign):
    headers = {
    
    
        'User-Agent': 'okhttp-okgo/jeasonlzy',
        'token': 'F39FED66EB36A4B94B308114826D0DBCAFDCAFA186F0938BD5CCF4117B28D586E6780DE54474D93BA0C8EC44B1FA1797F245AB0F648DFC62B60184916108DEDF2F0A490D5475059855ACEE4FCDCE2E91ED2BA128F68135896D8D8192DAC604BEDDE68FA864'
    }
    j = json.dumps({
    
    'data': data, '_ver': 'v1', "sign": sign, "timestamp": str(int(time.time()))})
    r = requests.post("http://api50.fiftymvapi.com:8080/api.php/api/user/userinfo",headers=headers , data=j)
    return r

if __name__ == '__main__':
    text = '{"system_build_id":"a1000","system_iid":"f7767b9f58a4ce56ba2b5ea559fbbdbc","app_status":"9001A7FD9DDFE91CDA376F4EB9DD0E2FC915ADA4:2","system_version":"5.7.0","system_build_aff":"","bundle_id":"jp.fihvv.vrdrrh","system_app_type":"local","new_player":"fx","system_oauth_id":"ee916e21e1eda4fa5bf5dc522e088f20","system_oauth_type":"android","system_token":"B8B8D7BF07295EF62A008193126FA3D0C34AFC437ED0E4DDE00E83D4F81647364884A6B1BCED057E77384252F5F7AFF92E243C7E6C266ED02D388212B291AC775F5F7857B9E51DF19A4E24E455AC2AA664141F3701D4F31A7F96B921C589BE83827EBB367C"}'
    random_str = generate_random_str(0x10)
    aes_encrypt_list = aes_encrypt(text, random_str)
    custom_encrypt_str = custom_encrypt(text,random_str,aes_encrypt_list)

    sign = generate_sign(custom_encrypt_str)

    r = get_userInfo(custom_encrypt_str, sign)

    if r.status_code == requests.codes.ok:
       print("请求userinfo成功")
       print("响应体:")
       print(r.text)
       print("data解密数据:")
       print(decrypt(r.json()["data"]))
    else:
        print("请求userinfo失败")

Visualización del efecto de ejecución:

inserte la descripción de la imagen aquí

apéndice

Enlace de software: https://pan.baidu.com/s/16DZ3ReL2kMwjI4tAFE39qw?pwd=7xve

Supongo que te gusta

Origin blog.csdn.net/weixin_56039202/article/details/128743295
Recomendado
Clasificación