android app nsign so 加密算法分析

android 逆向 28 / 37

推荐阅读

前言

charles 抓包

就是这个 nsign 参数

java 分析

全局搜索 "nsign" 关键词,随便定位一个,使用 jeb 打开这个类

nsign = v2; v2 = SignUtil.c 点进 SignUtil.c 看看

这里调用了 SignUtil.getSign0 函数

SignUtil.getSign0 是个 native 函数

往前翻,回到 nsign = v2 的位置,往上翻可以看到加载了 signutil.so 文件

frida hook

function getMapData(mapSet) {
    try {
        var result = {};
        var key_set = mapSet.keySet();
        var it = key_set.iterator();
        while (it.hasNext()) {
            var key_str = it.next().toString();
            result[key_str] = mapSet.get(key_str).toString();
        }
        return result
    } catch (error) {
        return mapSet
    }
}

function getProcessId() {
    var androidProcess = Java.use('android.os.Process');
    return 'processId: ' + androidProcess.myTid() + ' - ';
}

function printLog() {
    var result = '';

    for (var i = 0; i < arguments.length; i++) {
        result += arguments[i] + ' ';
    }

    console.log(getProcessId(), result)
}

Java.perform(function () {
    var SignUtil = Java.use('com.anjuke.mobile.sign.SignUtil');

    SignUtil.getSign0.implementation = function (a, b, c, d, e) {
        printLog('SignUtil.getSign0.a: ', a);
        printLog('SignUtil.getSign0.b: ', b);
        printLog('SignUtil.getSign0.c: ', c);
        printLog('SignUtil.getSign0.c: ', JSON.stringify(getMapData(c)));
        printLog('SignUtil.getSign0.d: ', d);
        printLog('SignUtil.getSign0.e: ', e);

        var res = this.getSign0(a, b, c, d, e);
        printLog('SignUtil.getSign0.res: ', res);

        return res;
    }
})

使用 frida hook com.anjuke.mobile.sign.SignUtil 这个类的输入输出

参数3,是个 mapvaluebyte[] 没打印出来,不过问题不大,根据 key 可以去 url params 里查找对应的 value

so 分析

前面是 md5 逻辑的分析,可以参考下上面的推荐阅读文章

md5 加密完之后,还有这个小逻辑,拼接上 1000 字符

接着还会拼接上这些计算的结果,最后才是最终的结果

unidbg

这个样本的逻辑比较简单,使用 frida hook so 也是足够了,不过博主还是比较喜欢使用 unidbg 辅助算法还原

@Override
public int callIntMethod(BaseVM vm, DvmObject<?> dvmObject, String signature, VarArg varArg) {
    switch (signature) {
        case "java/util/HashMap->size()I": {
            HashMap hashMap = (HashMap) dvmObject.getValue();
            return hashMap.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;": {
            HashMap map = (HashMap) dvmObject.getValue();
            return vm.resolveClass("java/util/Set").newObject(map.keySet());
        }
        case "java/util/Set->toArray()[Ljava/lang/Object;": {
            Set thisSet = (Set) dvmObject.getValue();
            StringObject[] obj = new StringObject[thisSet.size()];

            for (int i = 0; i < thisSet.size(); i++) {
                obj[i] = new StringObject(vm, (String) thisSet.toArray()[i]);
            }

            return new ArrayObject(obj);
        }
        case "java/util/HashMap->get(Ljava/lang/Object;)Ljava/lang/Object;": {
            HashMap map = (HashMap) dvmObject.getValue();
            Object key = varArg.getObjectArg(0).getValue();
            Object obj = map.get(key);
            return new ByteArray(vm, (byte[]) obj);
        }
    }

    return super.callObjectMethod(vm, dvmObject, signature, varArg);
}

大概需要补以上的这些环境

public void runGetSign0() {
    DvmClass SignUtil = vm.resolveClass("com/anjuke/mobile/sign/SignUtil");
    DvmObject<?> ret = SignUtil.callStaticJniMethodObject(
            emulator, "getSign0(Ljava/lang/String;Ljava/lang/String;Ljava/util/Map<Ljava/lang/String;[B>;>;Ljava/lang/String;I)Ljava/lang/String;",
            new StringObject(vm, "/xinfang/m/android/1.3/loupan/newlistv2/"),
            new StringObject(vm, "d41d8cd98f00b204e9800998ecf8427e"),
            vm.resolveClass("java/util/HashMap").newObject(getMapData()),
            new StringObject(vm, "ab4f9779-6108-430d-a083-338bbb5154db"),
            DvmInteger.valueOf(vm, 0)
    );
    System.out.println(ret.getValue().toString());
}

然后运行 getSign0 函数

过程很流畅,没啥问题,结果也是正常出来了

public void inlineHook() {
    final Pointer[] md5updater0 = {null};
    emulator.getBackend().hook_add_new(new CodeHook() {
        @Override
        public void hook(Backend backend, long address, int size, Object user) {
            if (address == (module.base + 0x11D4)) {
                Arm32RegisterContext ctx = emulator.getContext();

                md5updater0[0] = ctx.getR0Pointer();

                Inspector.inspect(md5updater0[0].getByteArray(0, ctx.getR2Int() + 10), " md5 update params r0 ");
                Inspector.inspect(ctx.getR1Pointer().getByteArray(0, ctx.getR2Int() + 10), " md5 update params r1 ");
                System.out.println("md5 update params r2 " + ctx.getR2Int());
            }
        }

        @Override
        public void onAttach(UnHook unHook) {
        }

        @Override
        public void detach() {
        }
    }, module.base + 0x11D4, module.base + 0x11D4, null);
}

使用 inline hook 一下 md5 update 函数

第一次 update 就是我们输入的数据

第二次 update800000...... 应该是填充的数据

第三次 update 是八个字节,刚好是明文的长度

结果验证

复制明文到 cyberchef 验证一下,结果是对的

使用 python 跑一下,结果也是正常出来了

1 条评论
本文作者:
本文链接: https://www.qinless.com/?p=1281
版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 qinless 的博客!
100

1 条评论

tofu

大佬 已加你好友vx

回复

发表评论

返回顶部