首页
社区
课程
招聘
[原创]libsalt.so sign 算法分析
发表于: 2025-8-10 23:29 996

[原创]libsalt.so sign 算法分析

2025-8-10 23:29
996

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

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

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

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

Unidbg 模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
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

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

nice_sign_v3

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

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


Nice_MD5

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

1
2
颠倒前: 66e465b27596fe73ac148ce8993c9eaf
颠倒后: ac148ce8993c9eaf66e465b27596fe73

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

1
[zflq7vua9alemnwb] + [66e465b27596fe73ac148ce8993c9eaf] + [8a5f746c1c9c99c0b458e1ed510845e5]
1
2
颠倒前: 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 格式并进行升降排序

1
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 操作对比下结果

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

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

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

我们发现已经和开头已经对上了 下面我们贴出部分的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
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))

算法还原

在开始关键的算法还原的时候 我们在去整理一下全部的思路 这里我用一张逻辑图去表示出来

:::success
这里 我就避免一些不必要的情况,大家都是交流学习的 没必要搞事情 没有任何恶意哈 保命 我i就不贴出来完整的算法了总体难度评 2 分 ⭐ ⭐

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

:::


末尾

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

欢迎加 V:ass7752321 会给大家拉一个交流群 互相交流一下思路


[培训]Windows内核深度攻防:从Hook技术到Rootkit实战!

收藏
免费 3
支持
分享
最新回复 (2)
雪    币: 1506
活跃值: (3878)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
2
可以老铁666
2025-8-11 10:44
0
雪    币: 1920
活跃值: (1881)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
3
大佬怎么加群呢
2025-10-5 11:56
0
游客
登录 | 注册 方可回帖
返回