libsalt.so sign算法分析

 在开始这篇文章之前,请阅读一下注意事项

本文章仅用于学习研究,如有侵犯贵司权益,请联系告知,会立即做出下架删除处理。

本文章不针对任何网站,APP ,禁止用于商业,转载,违法等用途。

观看则默认同意本约定,未经允许禁止任何形式的转载,保留追究法律责任的权利,

Unidbg模板

package com;

import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Emulator;
import com.github.unidbg.LibraryResolver;
import com.github.unidbg.Module;
import com.github.unidbg.arm.backend.Unicorn2Factory;
import com.github.unidbg.debugger.Debugger;
import com.github.unidbg.file.FileResult;
import com.github.unidbg.file.IOResolver;
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.memory.Memory;
import com.github.unidbg.virtualmodule.android.AndroidModule;
import com.github.unidbg.virtualmodule.android.JniGraphics;
import com.github.unidbg.virtualmodule.android.MediaNdkModule;
import com.github.unidbg.virtualmodule.android.SystemProperties;

import java.io.File;
import java.util.ArrayList;
import java.util.List;


public class 模板 extends AbstractJni implements IOResolver {

    private static final String PackName = "APP 包名";
    private static final String AppPath = "传入 APP路径";
    private static final String[] SoName = {"SO名字或外部路径"}; // 可能会出现多个SO文件加载的情况
    private final AndroidEmulator emulator;
    private final VM vm;
    private final Module module;
    final Memory memory;


    @Override
    public FileResult resolve(Emulator emulator, String pathname, int oflags) {
        System.out.println("Load File: " + pathname);
        return null;
    }

    private static LibraryResolver createLibraryResolver() {
        return new AndroidResolver(23);
    }

    private static AndroidEmulator createARMEmulator() {
        return AndroidEmulatorBuilder.for64Bit()
                .setProcessName(PackName)
                .addBackendFactory(new Unicorn2Factory(false))
                .build();
    }

    模板 () {
        emulator = createARMEmulator();
        // 创建模拟器的内存映射
        emulator.getSyscallHandler().addIOResolver(this);
        // 获取模拟器的内存操作接口
        memory = emulator.getMemory();
        // 设置系统类库解析 23
        memory.setLibraryResolver(createLibraryResolver());
        // 创建Android虚拟机,传入APK,Unidbg可以替我们做部分签名校验的工作
        vm = emulator.createDalvikVM(new File(AppPath));
        // 虚拟模块部分
        new AndroidModule(emulator,vm).register(memory);
        new MediaNdkModule(emulator,vm).register(memory);
        new JniGraphics(emulator,vm).register(memory);
        // 设置JNI
        vm.setJni(this);
        // 打印日志
        vm.setVerbose(true);
        // 加载目标SO
        DalvikModule dm = vm.loadLibrary(SoName[0], true);
        //获取本SO模块的句柄,后续需要用它
        module = dm.getModule();
        // 调用JNI OnLoad
        dm.callJNI_OnLoad(emulator);
    };

    public static void main(String[] args) {
        模板 action = new 模板();
        action.Call();
    }

    public void Call () {
        List<Object> args = new ArrayList<>();
        args.add(vm.getJNIEnv()); // JNIEnv *
        args.add(0);
        args.add(vm.addLocalObject(new StringObject(vm, "123456"))); // 输入字符串
        args.add(vm.addLocalObject(new StringObject(vm, ""))); // 输入字符串
        Number result = module.callFunction(emulator, 0x236c, args.toArray());
        DvmObject<?> returnObj = vm.getObject(result.intValue());
        String encrypted = ((StringObject) returnObj).getValue();
        System.out.println("Encrypted String: " + encrypted);
    }

}

逻辑分析

从这章开始, 算法不会只用Unidbg 或者是 看伪C 进行分析

加密输出结果如下:


该 So 就是静态注册的方法 Java_com_nice_main_helpers_utils_NiceSignUtils_getSignRequest

静态注册的方式 Java_开头 完成静态注册

动态注册的方法 在 JNI_OnLoad 方法中完成的动态注册

第一步咱们进去函数后发现一个名为nice_sign_v3的函数 这个就非常可疑 咱们进去看下

进入nice_sign_v3函数之后我们看到了MD5加密 这里可能是加密的核心位置 我们去用unidbg hook一下他的入参

我们下面可以清晰的看到他的入参可以分为三个参数,v16=body,v20=设备did(md5加密过的),v11=随机16位字符串

上面代码可以看到 他进行两次nice_md5 那么我们去hook下 入参值

Nice_MD5

第一次 Nice_MD5的入参 我们可以通过图片看到 他的入参值就是v20的前半部分和后半部分颠倒位置

第一次 Nice_MD5的入参 的结果
颠倒前: 66e465b27596fe73ac148ce8993c9eaf
颠倒后: ac148ce8993c9eaf66e465b27596fe73

第二次 Nice_MD5的入参 我们可以通过图片看到 他的入参值就是v11+第一次 Nice_MD5的入参结果+8a5f746c1c9c99c0b458e1ed510845e5

拼接的规则
[zflq7vua9alemnwb] + [66e465b27596fe73ac148ce8993c9eaf] + [8a5f746c1c9c99c0b458e1ed510845e5]
第二次 Nice_MD5的入参 的结果
颠倒前: b56ebb1f9af72cfed5eaacb028eb05cd
颠倒后: d5eaacb028eb05cdb56ebb1f9af72cfe

然后对第二次 Nice_MD5的入参 的结果进行前半部分和后半部分颠倒位置

明文处理部分以及nice_sha1

第一步 我们通过图片可以看到几个函数

cJSON_Parse,sub_25B0,nice_sha1

通过代码可以看到 入参的明文数据传入

cJSON_Parse进行JSON解析操作得到了v38

sub_25B0函数传入了v38得到v40的结果 那么我

们现在去hook下sub_25B0返回的结果

v40 = sub_25B0返回的结果入下:

通过结果可以看到 我们的明文参数从JSON格式变成了application/x-www-form-urlencoded格式并进行升降排序

v40 = "block_version=0&device_model=MI 9&items=config=&post=&proto_version=0.1"

根据下面图片我们可以看到v40进行了一系列运算给了v21 下面我们先去HOOK下 nice_sha1 之前的v21长什么样子

根据下面图片我们可以看到这个时候v21已经变成乱码和第二次 Nice_MD5的入参 前半部分和后半部分颠倒位置

从上面代码我们呢可以看到v21[46]=0 就代表他可能是在这个位置做了截断

接下来我们就用py把这个nice_sha1加密之前部分先给他还原了 看看加密出来的参数一样不

通过上面图片我们可以清晰看到一模一样

下面我们就进行nice_sha1操作对比下结果

                                         unidbg nice_sha1加密结果

                                                              py运行结果

通过上面图片我们可以清晰看到一模一样

下面我们就进行nice_sha1操作对比下结果

unidbg nice_sha1加密结果

                                                                    py运行结果

我们通过图片对比发现 nice_sha1加密已经对比上了 但是和开头的结果还是不一样 我们继续去看代码

我们可以看到他从v48+8的位置才开始获取 然后又进行了一次前半部分和后半部分颠倒位置

我们现在用py写个代码试试看看

我们发现已经和开头已经对上了

这里我就不再详细写Python 怎么一步一步复现了,直接附上写好的py代码

现在再回头看整个算法 难么? 不难 !

import hashlib


def process_strings(v40, v67):
    v21 = bytearray(1024)
    v41 = 0
    if len(v40) < 2:
        v46 = 32
    else:
        v42_idx = 1
        v43 = 0x2000000000
        loop_count = len(v40) >> 1
        while v41 < loop_count:
            if v42_idx - 1 >= len(v40) or v42_idx >= len(v40):
                break
            v44 = ord(v40[v42_idx - 1])
            v45 = ord(v40[v42_idx])
            combined = (v44 & 0xF0) | (v45 & 0xF)
            v21[v41] = combined
            v41 += 1
            v42_idx += 2
            v43 += 0x100000000
        v46 = v43 >> 32
    v67_bytes = v67.encode('utf-8')
    for i in range(32):
        if i < len(v67_bytes):
            v21[v41 + i] = v67_bytes[i]
        else:
            v21[v41 + i] = 0
    if v46 < len(v21):
        v21[v46] = 0
    else:
        pass
    print("Output:", v21)
    result = v21.split(b'\x00', 1)[0]
    return result
def sha1_bytes(data: bytes) -> str:
    hash_obj = hashlib.sha1(data)
    return hash_obj.digest().hex()[8:]
def swap(s):
    n = len(s)
    mid = n // 2
    return s[mid:] + s[:mid]

v40 = "block_version=0&device_model=MI 9&items=config=&post=&proto_version=0.1"
v67 = "d5eaacb028eb05cdb56ebb1f9af72cfe"
output = process_strings(v40, v67)
print("Output:", output)
digest_bytes = sha1_bytes(output)
print("Final Output (bytes):", digest_bytes)
print("Final Output (bytes):", swap(digest_bytes))

 

末尾

后续 也会推出一系列的文章,包括但不限于 魔改算法,VMP 系列,小众算法

 

通过私信联系获取加群方式

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值