app逆向--安居客参数分析

声明

本文章中所有内容仅供学习交流,不可用于任何商业用途和非法用途,否则后果自负,如有侵权,请联系作者立即删除!由于本人水平有限,如有理解或者描述不准确的地方,还望各位大佬指教!!

前言

今天我们要分析的app是安居客。样本下载

2025安居客v17.17.5老旧历史版本安装包官方免费下载_豌豆荚

java层分析

第一步抓包分析。通过抓包发现nsign是加密的。

使用jadx打开,搜索nsign

跟到最后发现是so层的

我们先逐个参数分析一下

参数1是url的path

参数2是请求头转成字节然后经过a函数处理

我们把a函数翻译成Python

def byte_to_str(bArr: bytes):
    cArr = "0123456789abcdef"
    digest = md5(bArr).digest()
    sb = []
    for byte in digest:
        sb.append(cArr[(byte & 240) >> 4])
        sb.append(cArr[byte & 15])
    return ''.join(sb)
print(byte_to_str('{"condition":{"commId":"730320","key":"","value":""},"pageNum":1,"pageSize":15}'.encode("utf-8")))

参数3是params,把value转成了字节

参数3是uuid

参数4是参数2的长度

hook一下入参和结果看看

str=/ajkspec/esf/news/cross/community/comments, bArr=123,34,99,111,110,100,105,116,105,111,110,34,58,123,34,99,111,109,109,73,100,34,58,34,55,51,48,
51,50,48,34,44,34,107,101,121,34,58,34,34,44,34,118,97,108,117,101,34,58,34,34,125,44,34,112,97,103,101,78,117,109,34,58,49,44,34,112,97,103,101,83,105,122,101,34,58,49,5
3,125, 
map={"app":"a-ajk","_guid":"74d1a658-d258-4fc0-a5b8-41582f01d04d","version_code":"322347","m":"Android-MI 9","uuid":"89749bd1-f403-4ac4-823b-fdd1dcbee7fa-KEe4VfU",
"openudid":"guest6c99fa49-a4d0-4e9b-824c-ab94eb041e68-rSOeUfU","manufacturer":"Xiaomi","o":"cepheus-user 11 RKQ1.200826.002 V12.5.6.0.RFACNXM release-keys","qtime":"20250
315164940","gmid":"guest6c99fa49-a4d0-4e9b-824c-ab94eb041e68-rSOeUfU","cv":"17.17.5","v":"11","58clientid":"8d8f7ddfb3d4767c754156d4f77383cc","ajk_city_id":"18","from":"mobile","pm":"b276","cid":""},
str2=4126b0de-0645-4ca7-8e59-9de4f5cd01fc
result=100043f8420e90baf10e220bafdd3c2fd42301a80011004f4126b0de

so分析

进入so层看看 这里应该就是加密的主流程了,接下来就用unidbg进行算法辅助分析,算法比较简单也可以直接frida进行hook

package com.anjuke.mobile.sign;

import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Module;
import com.github.unidbg.arm.backend.Unicorn2Factory;
import com.github.unidbg.debugger.Debugger;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.*;
import com.github.unidbg.linux.android.dvm.array.ArrayObject;
import com.github.unidbg.linux.android.dvm.array.ByteArray;
import com.github.unidbg.linux.android.dvm.jni.ProxyDvmObject;
import com.github.unidbg.memory.Memory;

import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.*;

public class nsign extends AbstractJni {
    private final AndroidEmulator emulator;
    private final VM vm;
    private final Module module;

    public nsign() {
        emulator = AndroidEmulatorBuilder
                .for64Bit()
                .addBackendFactory(new Unicorn2Factory(true))
                .setProcessName("com.anjuke.android.app")
                .build();
        Memory memory = emulator.getMemory();
        memory.setLibraryResolver(new AndroidResolver(23));
        vm = emulator.createDalvikVM(new File("E:\\apk\\安居客\\anjuke_17.17.5.apk"));
        vm.setJni(this);
        vm.setVerbose(true);
        DalvikModule dm = vm.loadLibrary(new File("E:\\apk\\安居客\\libsignutil.so"), true);
        module = dm.getModule();
        dm.callJNI_OnLoad(emulator);
    }

    public void callSign() {
        List<Object> list = new ArrayList<Object>();
        list.add(vm.getJNIEnv());   // 第一个参数是env
        list.add(0); // 第⼆个参数,实例⽅法是jobject,静态⽅法是jclass,直接填0,⼀般⽤不到。
        list.add(vm.addLocalObject(new StringObject(vm, "/ajkspec/esf/news/cross/community/comments")));
        list.add(vm.addLocalObject(new StringObject(vm, "e17228f712db5be45a57987ca1730c1d")));
        Map<String, String> paramMap = new HashMap<>() {{
            put("app", "a-ajk");
            put("_guid", "74d1a658-d258-4fc0-a5b8-41582f01d04d");
            put("version_code", "322347");
            put("m", "Android-MI 9");
            put("uuid", "89749bd1-f403-4ac4-823b-fdd1dcbee7fa-KEe4VfU");
            put("openudid", "guest6c99fa49-a4d0-4e9b-824c-ab94eb041e68-rSOeUfU");
            put("manufacturer", "Xiaomi");
            put("o", "cepheus-user 11 RKQ1.200826.002 V12.5.6.0.RFACNXM release-keys");
            put("qtime", "20250315164940");
            put("gmid", "guest6c99fa49-a4d0-4e9b-824c-ab94eb041e68-rSOeUfU");
            put("cv", "17.17.5");
            put("v", "11");
            put("58clientid", "8d8f7ddfb3d4767c754156d4f77383cc");
            put("ajk_city_id", "18");
            put("from", "mobile");
            put("pm", "b276");
            put("cid", "");
        }};
        Map<String, byte[]> map = new HashMap<>();
        for (String key : paramMap.keySet()) {
            map.put(key, paramMap.get(key).getBytes(StandardCharsets.UTF_8));
        }
        list.add(vm.addLocalObject(ProxyDvmObject.createObject(vm, map)));
        list.add(vm.addLocalObject(new StringObject(vm, "3b8ed218-5dd5-4190-b367-bbb83abdca60")));
        list.add(79);
        Number number = module.callFunction(emulator, 0x18F0, list.toArray());
        String result = vm.getObject(number.intValue()).getValue().toString();
        System.out.println("result=" + result);
    }

    @Override
    public int callIntMethod(BaseVM vm, DvmObject<?> dvmObject, String signature, VarArg varArg) {
        switch (signature) {
            case "java/util/HashMap->size()I":
                Map<?, ?> map = (Map<?, ?>) dvmObject.getValue();
                return map.size();
        }
        return super.callIntMethod(vm, dvmObject, signature, varArg);
    }

    @Override
    public DvmObject<?> callObjectMethod(BaseVM vm, DvmObject<?> dvmObject, String signature, VarArg varArg) {
        switch (signature) {
            case "java/util/HashMap->keySet()Ljava/util/Set;":
                Map<?, ?> map = (Map<?, ?>) dvmObject.getValue();
                return vm.resolveClass("java/util/Set").newObject(map.keySet());
            case "java/util/Set->toArray()[Ljava/lang/Object;":
                Set<?> set = (Set<?>) dvmObject.getValue();
                Object[] array = set.toArray();
                DvmObject<?>[] objects = new DvmObject[array.length];
                for (int i = 0; i < array.length; i++) {
                    if (array[i] instanceof String) {
                        // 如果元素是字符串,包装为 StringObject
                        objects[i] = new StringObject(vm, (String) array[i]);
                    }
                }
                return new ArrayObject(objects);
            case "java/util/HashMap->get(Ljava/lang/Object;)Ljava/lang/Object;":
                HashMap<?, ?> map1 = (HashMap<?, ?>) dvmObject.getValue();
                Object key = varArg.getObjectArg(0).getValue();
                Object value = map1.get(key);
                if (value instanceof byte[]) {
                    return new ByteArray(vm, (byte[]) value);
                }
        }
        return super.callObjectMethod(vm, dvmObject, signature, varArg);
    }

    public static void main(String[] args) {
        nsign nsign = new nsign();
        nsign.callSign();
    }
}

我们hook一下get_sign这个函数的入参和出参

这正是结果的前半段部分,那么结果就是:get_sign的结果+未知的10字节

我们跟进去get_sign函数

对md5进行hook

可以看到参数1就是加盐+path+str1+map,对其进行md5加密

1000 43f8420e90baf10e220bafdd3c2fd423 01a8 0011 004f 3b8ed218
1000 43f8920e90baf10e220bafdd3c2fd423

可以看到第3个字节的第一个字符串不同

回去看伪代码发现最后一行进行了处理 v37[*v37 & 0xF] = *v37;

*v37是v37的第一个字节,既v37[0] = '4',对应的ASCll值为0x34,0x34&0x0F=0x04,既v37[4] = '4',所以9变成了4

前面四个是固定字符串,可以写死,接下来就只剩下后面的10字节了

我们回到so看看伪代码

这部分转成python就是

def get_hex_char(value):
    hex_chars = "0123456789abcdef"
    char1 = hex_chars[value >> 12]
    char2 = hex_chars[(value >> 8) & 0xF]
    char3 = hex_chars[(value >> 4) & 0x0F]
    char4 = hex_chars[value & 0xF]
    return char1 + char2 + char3 + char4

最后四个字节就是uuid的前八位

到此整个算法就分析完成了。

结言

这是比较简单的app了,适合新手入门分析so,这里的源码已经放在了星球,欢迎大家跟我一起讨论,同时星球会丢更多的一些辅助工具。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值