【安卓逆向】某xx冰城加密 sign分析与学习

发布于 2024-09-06  50 次阅读


今天分析的app是 6Jyc6Zuq5Yaw5Z+OXzIuMi42

抓包

很明显的有一个sign字段

反编译

由于新版本有360壳,此处使用旧版本2.2.6

打开jeb反编译 全局搜索sign

可以定位到

定位到 d.a可以得到

package com.mxbc.mxsa.network.feima;

import android.text.TextUtils;
import android.util.Base64;
import com.alibaba.fastjson.a;
import com.meituan.robust.ChangeQuickRedirect;
import com.meituan.robust.PatchProxy;
import com.meituan.robust.PatchProxyResult;
import com.mxbc.mxsa.base.service.common.SerializableService;
import com.mxbc.mxsa.base.utils.s;
import com.mxbc.mxsa.network.c;
import com.mxbc.mxsa.network.f;
import com.mxbc.service.e;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeSet;

public class d {
    private static String a = "";
    private static String b = "";
    private static String c = "";
    public static ChangeQuickRedirect changeQuickRedirect;

    static {
    }

    private static String a() {
        PatchProxyResult patchProxyResult0 = PatchProxy.proxy(new Object[0], null, d.changeQuickRedirect, true, 0x10A2, new Class[0], String.class);
        if(patchProxyResult0.isSupported) {
            return (String)patchProxyResult0.result;
        }

        if(c.a()) {
            d.b = (String)((SerializableService)e.a(SerializableService.class)).deserializeBase64(f.a(true));
            return "";
        }

        d.a = (String)((SerializableService)e.a(SerializableService.class)).deserializeBase64(f.a(false));
        return "";
    }

    public static String a(String s) {
        PatchProxyResult patchProxyResult0 = PatchProxy.proxy(new Object[]{s}, null, d.changeQuickRedirect, true, 0x10A3, new Class[]{String.class}, String.class);
        if(patchProxyResult0.isSupported) {
            return (String)patchProxyResult0.result;
        }

        try {
            PKCS8EncodedKeySpec pKCS8EncodedKeySpec0 = new PKCS8EncodedKeySpec(Base64.decode(d.a(), 2));
            PrivateKey privateKey0 = KeyFactory.getInstance("RSA").generatePrivate(pKCS8EncodedKeySpec0);
            Signature signature0 = Signature.getInstance("SHA256withRSA");
            signature0.initSign(privateKey0);
            signature0.update(s.getBytes());
            return Base64.encodeToString(signature0.sign(), 2);
        }
        catch(Exception exception0) {
            exception0.printStackTrace();
            return "";
        }
    }

    public static String a(Map map0) {
        PatchProxyResult patchProxyResult0 = PatchProxy.proxy(new Object[]{map0}, null, d.changeQuickRedirect, true, 4260, new Class[]{Map.class}, String.class);
        if(patchProxyResult0.isSupported) {
            return (String)patchProxyResult0.result;
        }

        HashMap hashMap0 = new HashMap(map0);
        TreeSet treeSet0 = new TreeSet(hashMap0.keySet());
        StringBuilder stringBuilder0 = new StringBuilder();
        for(Object object0: treeSet0) {
            String s = (String)object0;
            Object object1 = hashMap0.get(s);
            String s1 = ((object1 instanceof Byte)) || ((object1 instanceof Short)) || ((object1 instanceof Integer)) || ((object1 instanceof Long)) || ((object1 instanceof Float)) || ((object1 instanceof Double)) || ((object1 instanceof Boolean)) || ((object1 instanceof Character)) || ((object1 instanceof String)) ? String.valueOf(object1) : a.toJSONString(hashMap0.get(s));
            if(TextUtils.isEmpty(s1)) {
                continue;
            }

            stringBuilder0.append(s);
            stringBuilder0.append('=');
            stringBuilder0.append(s1);
            stringBuilder0.append('&');
        }

        if(stringBuilder0.length() > 0) {
            stringBuilder0.deleteCharAt(stringBuilder0.length() - 1);
        }

        if(com.mxbc.mxsa.base.utils.e.a().b()) {
            s.d("NetRequest", stringBuilder0.toString());
        }

        return d.b(stringBuilder0.toString()).replace("\n", "").replace("\r", "");
    }

    private static String b(String s) {
        PatchProxyResult patchProxyResult0 = PatchProxy.proxy(new Object[]{s}, null, d.changeQuickRedirect, true, 0x10A5, new Class[]{String.class}, String.class);
        if(patchProxyResult0.isSupported) {
            return (String)patchProxyResult0.result;
        }

        try {
            d.c = (String)((SerializableService)e.a(SerializableService.class)).deserializeBase64(f.b(false));
            PKCS8EncodedKeySpec pKCS8EncodedKeySpec0 = new PKCS8EncodedKeySpec(Base64.decode("", 2));
            PrivateKey privateKey0 = KeyFactory.getInstance("RSA").generatePrivate(pKCS8EncodedKeySpec0);
            Signature signature0 = Signature.getInstance("SHA256withRSA");
            signature0.initSign(privateKey0);
            signature0.update(s.getBytes());
            return Base64.encodeToString(signature0.sign(), 8);
        }
        catch(Exception exception0) {
            exception0.printStackTrace();
            return "";
        }
    }
}

大概意思就是通过a将传入的参数序列化组成字符串再用b进行SHA256withRSA加密,最后再用base64编码一下

objection动态调试

objection -g com.wudaokou.hippo explore
android hooking watch class_method com.mxbc.mxsa.network.feima.d.b --dump-args --d
ump-return --dump-backtrace

很明显是相同的

进一步hook

android hooking watch class_method android.util.Base64.decode --dump-args --dump-r
eturn --dump-backtrace

整合下代码

import base64
import time

import requests
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.backends import default_backend


def sign(str):
    private_key_pem = """-----BEGIN PRIVATE KEY-----
xxxxxxxxxxxxxxxxxx
-----END PRIVATE KEY-----"""

    # 直接加载PEM格式的私钥
    private_key = serialization.load_pem_private_key(
        private_key_pem.encode('utf-8'),  # 将字符串转换为字节
        password=None,  # 私钥未加密,不需要密码
        backend=default_backend()
    )

    signature = private_key.sign(
        str.encode('utf-8'),
        padding.PKCS1v15(),
        hashes.SHA256()
    )


    return base64.b64encode(signature).decode().replace("/", "_").replace("+", "-")


def create_str_before_sign(e):
    # 获取字典的键,并按照字母顺序排序
    sorted_keys = sorted(e.keys())

    # 构建最终的查询字符串
    result = ""
    for n, key in enumerate(sorted_keys):
        # 检查值是否存在,或者是否为0
        if e[key] or e[key] == 0:
            # 如果不是第一个键值对,前面加上 & 符号
            result += ("&" if n > 0 else "") + f"{key}={e[key]}"

    return result.replace("'", '"').replace(" ", "")


if __name__ == "__main__":
    url = "https://mxsa.mxbc.net/api/v1/adinfo/list"
    t = int(time.time() * 1000)
    json_data = {
        "areaId": "",
        "t": t,
        # "t": 1725575959740,
        "appId": "ba660596c1d911ebabac005056883e3e",
        "adPlaceCodeList": [
            "HomeFloatBanner", "HomeMagnet"
        ]
    }

    print(create_str_before_sign(json_data))

    json_data["sign"] = sign(create_str_before_sign(json_data))

    print(json_data)

    headers = {
        'User-Agent': "okhttp/4.4.1",
        'Connection': "Keep-Alive",
        'Accept-Encoding': "gzip",
        'app': "mxbc",
        'appchannel': "huawei",
        'appversion': "2.2.6",
        'Access-Token': "",
        'Content-Type': "application/json; charset=UTF-8"
    }
    response = requests.post(url, json=json_data, headers=headers)

    print(response.json())