前言

今天来分析一下某短视频系列的 sig3 加密参数,主要目的是探索学习app接口的加解密机制。仅供学习,禁止用作其他用途。未经允许禁止任何形式的转载,保留追究法律责任的权利。

逻辑分析

app样本:5Zac55Wq5YWN6LS555+t5Ymn

一. unidbg运行

package com.xx;
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Emulator;
import com.github.unidbg.Module;
import com.github.unidbg.arm.backend.Unicorn2Factory;
import com.github.unidbg.arm.context.RegisterContext;
import com.github.unidbg.debugger.BreakPointCallback;
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.linux.android.dvm.array.ArrayObject;
import com.github.unidbg.linux.android.dvm.array.ByteArray;
import com.github.unidbg.linux.android.dvm.api.AssetManager;
import com.github.unidbg.linux.android.dvm.wrapper.DvmBoolean;
import com.github.unidbg.linux.android.dvm.wrapper.DvmInteger;
import com.github.unidbg.linux.file.SimpleFileIO;
import com.github.unidbg.memory.Memory;
import com.github.unidbg.pointer.UnidbgPointer;
import com.github.unidbg.virtualmodule.android.AndroidModule;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;

import java.io.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;

import com.github.unidbg.virtualmodule.android.JniGraphics;


public class sign extends AbstractJni implements IOResolver{
    @Override
    public FileResult resolve(Emulator emulator, String pathname, int oflags) {
        return null;
    }
    private final AndroidEmulator emulator;
    private final VM vm;
    private final Module module;

    public sign() {
        // 创建模拟器实例,进程名建议依照实际进程名填写,可以规避针对进程名的校验
        emulator = AndroidEmulatorBuilder.for64Bit()
                .addBackendFactory(new Unicorn2Factory(true))
                .setProcessName("com.xx")// 模拟手机文件的根路径
                .build();
        // 获取模拟器的内存操作接口
        final Memory memory = emulator.getMemory();
        // 设置系统类库解析
        memory.setLibraryResolver(new AndroidResolver(23));
        // 创建Android虚拟机,传入APK,Unidbg可以替我们做部分签名校验的工作
        vm = emulator.createDalvikVM(new File("xx"));
        // 如果提示缺失依赖so
        vm.setJni(this);
        new AndroidModule(emulator, vm).register(memory);
        new JniGraphics(emulator, vm).register(memory);
        emulator.getSyscallHandler().addIOResolver(this);   //重定向io
        // 设置是否打印Jni调用细节
        vm.setVerbose(true);
        DalvikModule dm1 = vm.loadLibrary("kwsgmain", true);
        module = dm1.getModule();
        dm1.callJNI_OnLoad(emulator);

    public static void main(String[] args) throws FileNotFoundException {
        sign test = new sign();
        AndroidEmulator emulator = test.emulator;
        VM vm = test.vm;
        test.call_doCommandNative_init();
        test.get_NS_sig3();
    }

    public String get_NS_sig3(){
        DvmClass dvmClass = vm.resolveClass("com.kuaishou.android.security.internal.dispatch.JNICLibrary");
        String methodSign="doCommandNative(I[Ljava/lang/Object;)Ljava/lang/Object;";
        DvmObject<?> context = vm.resolveClass("com.kwai.theater.KSApplication").newObject(null);
        String aa = "coder7777";
        DvmObject<?> ret1 = dvmClass.callStaticJniMethodObject(
                emulator, methodSign, 10418,
                new ArrayObject(
                        new ArrayObject(new StringObject(vm, aa)),
                        new StringObject(vm, "d74f8f6d-951f-4ba0-bace-e5666ea0e323"),
                        DvmInteger.valueOf(vm, -1),
                        DvmBoolean.valueOf(vm, false),
                        context,
                        null,
                        DvmBoolean.valueOf(vm, false),
                        new StringObject(vm, "")
                ));
        System.out.println("QKING, initMain.ret-10418: " + ret1);
        return ret1.toString();
    }
    

    private void call_doCommandNative_init() {
        DvmClass dvmClass = vm.resolveClass("com.kuaishou.android.security.internal.dispatch.JNICLibrary");
        String methodSign="doCommandNative(I[Ljava/lang/Object;)Ljava/lang/Object;";
        DvmObject<?> context = vm.resolveClass("com.kwai.theater.KSApplication").newObject(null);  // 
        DvmObject<?> ret11 = dvmClass.callStaticJniMethodObject(
                emulator, methodSign, 10412,
                new ArrayObject(
                        null,
                        new StringObject(vm, "d74f8f6d-951f-4ba0-bace-e5666ea0e323"),
                        null,
                        null,
                        context,
                        null,
                        null
                ));
        System.out.println("QKING, call_doCommandNative_init: " + ret11);

    }
   
    @Override
    public void callStaticVoidMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
        switch (signature) {
            case "com/kuaishou/android/security/internal/common/ExceptionProxy->nativeReport(ILjava/lang/String;)V":
                return;
            }
        super.callStaticVoidMethodV(vm, dvmClass, signature, vaList);
    }

    @Override
    public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
        switch (signature) {
            case "com/kwai/theater/KSApplication->getPackageCodePath()Ljava/lang/String;": {
                return new StringObject(vm, "/data/app/~~DsTsX9Czu7IXbyf7Kgqq9w==/com.kwai.theater-Vd-G-su_DD7b_HtgBaAt5w==/base.apk");
            }
            case "com/kwai/theater/KSApplication->getPackageName()Ljava/lang/String;": {
                return new StringObject(vm, "com.kwai.theater");
            }
            case "com/kwai/theater/KSApplication->getAssets()Landroid/content/res/AssetManager;": {
                return new AssetManager(vm, signature);
            }
            case "com/kwai/theater/KSApplication->getPackageManager()Landroid/content/pm/PackageManager;": {
                return vm.resolveClass("android/content/pm/PackageManager").newObject(signature);
            }
        }
        return super.callObjectMethodV(vm, dvmObject, signature, vaList);
    }

    @Override
    public DvmObject<?> callStaticObjectMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
        switch (signature) {
            case "com/kuaishou/android/security/internal/common/ExceptionProxy->getProcessName(Landroid/content/Context;)Ljava/lang/String;":
                return new StringObject(vm, "com.kwai.theater");
            case "com/kuaishou/android/security/internal/common/ExceptionProxy->getThreadByName(Ljava/lang/String;)Ljava/lang/String;":
                String threadName = String.valueOf(vaList.getIntArg(0));
                return new StringObject(vm, threadName);
                    }
            return super.callStaticObjectMethodV(vm, dvmClass, signature, vaList);
    }
  1. 正常出结果,这块就没什么好讲的了,网上有很多份,唯一要注意的就是要先初始化一下
    在这里插入图片描述

  2. 出结果后,固定入参 ”coder7777“,结果每次都不一样,猜测是有时间戳或者随机数,我们来测试一下,首先固定时间戳:
    在这里插入图片描述
    此时发现入参固定,时间戳固定,结果一致,结果为42530000d2f6651b0a0a09085baf1cd100b40776171b1503,更改参数和时间戳也看不出什么了,咱们接着往下走。

  3. so去花
    跟着大佬学习肯定不会错,我这里也是看着龙哥的文章去的花,具体过程就不分析了,文章里面或者其他资料都很多,跟着照做就可以实现了。

https://www.yuque.com/lilac-2hqvv/zfho3g/issny5?#%20%E3%80%8A%E8%8A%B1%E6%8C%87%E4%BB%A4%E5%A4%84%E7%90%86%EF%BC%88%E4%B8%80%EF%BC%89%E3%80%8B

二. trace分析

        try {
            emulator.traceCode(module.base, module.base + module.size).setRedirect(new PrintStream(filePath +"traceCodeCar_0x20694.log"));
        }catch (IOException e) {
            throw new IllegalStateException(e);
        }
  1. 先trace一份代码出来,首先 根据结果”42530000d2f6651b0a0a09085baf1cd100b40776171b1503“查找,看看是哪里生成的,咱们在trace里面搜索一下 0x1b(不要搜索0x03这些结果比较多)。搜索发现这里会比较像:
    在这里插入图片描述
    unidbg在这里断一下,看看x21
    在这里插入图片描述
    看样子结果是我们想要的,然后打开ida看下流程,快捷键”x“和打断点配合,最终确认如下
    在这里插入图片描述
    上面就是v460 生成的地方,我们在这个地方打上断点:
    在这里插入图片描述
    我们python实现一下异或操作 注意共执行23轮,然后第二十四位不变:
    在这里插入图片描述
    也没问题,结果正是 42530000d2f6651b0a0a09085baf1cd100b40776171b1503

  2. 现在我们就要分析 41510100d5f0601f0100000054a111dd13a61666000d0003 的来源了,这里我们先测试下
    只修改明文:

 41510100d5f0601f0100000054a111dd13a61666000d0003  原文
 41510100d5f0601f0100000061193a8d13a61666000d00a5 修改后

13-16位和最后一位变化

只修改时间戳:

  41510100d5f0601f0100000054a111dd13a61666000d0003  原文
  415101000e9525310100000054a111dd12a61666000d004f 修改后

5-8处字节发生变化 17-19和最后一个字节发生变化。

两者均修改

 41510100d5f0601f0100000054a111dd13a61666000d0003  原文
 415101000e9525310100000061193a8d12a61666000d00f1 修改后

5-8处字节发生变化,13~20处字节发生变化,和最后一个字节变化

三.明文分析

                                       crc32算法
  1. 由以上可知,13-16位和明文”coder7777“有关,13-16位是 dd11a154, 咱们trace里面搜索一下:在这里插入图片描述在0x1620处,我们在这里打个断点:
    在这里插入图片描述
    mx0是密文,mx1=0x30是长度,根据网上资料可以,这里用的是crc32算法,我们来测试一下:
    在这里插入图片描述
    得到结果 ”dd11a154’ 且没有魔改

    									aes-cbc加密
    

这里再看下“b7332938d71fbd84260aabaf3ecd0e6e164c729106057925b51710d697f5e06ced13be5d336bf29ad77fbad8cb814cad”是怎么来的。还是根据网上资料,初步认为是aes-cbc,咱们只需要验证是不是即可。
上文的b73329xxxxx所在的位置是 0x404e44c0,咱们这里trace追踪一下

 emulator.traceWrite(0x404e44c0,0x404e44c0+0x30);

在这里插入图片描述
这里看到调用的地方是在ilbc里面,0x1d62c是结束调用的地方,咱们在 0x1d62c 前面的位置打个断点
在这里插入图片描述
可以看到是在 0x404d31b0 这个位置,我们在trace一下:

emulator.traceWrite(0x404d31b0,0x404d31b0+0x30);

在这里插入图片描述
可以看到是在0x25b30,ida分析一下,这里大概就是aes算法的地方了,再具体的就没必要去分析了,网上的资料很多,例如如何知道是aes,且是cbc模式,这里就不细说了。
咱们hook一下入参:
在这里插入图片描述

这里一直入参为 33c4d4f0f3316a554af23888c78af4b44050d6c952572c743e047af26c1d8c3e
结果为 b7332938d71fbd84260aabaf3ecd0e6e164c729106057925b51710d697f5e06ced13be5d336bf29ad77fbad8cb814cad
加密方式为 aes-cbc.
咱们直接上dfa:

        debugger.addBreakPoint(1073741824 + 0x24B1C, new BreakPointCallback() {
            int count = 1;
            int count2 = 0;
            @Override
            public boolean onHit(Emulator<?> emulator, long address) {
                byte[] data= {0x1};
                if (count % 9 == 0) {
                    data[0] = (byte) randint(0,0xff);
                    emulator.getBackend().mem_write(0x404d3150 + count2 * 0x10 + randint(0, 15), data);
                }
                count++;
                if (count == 11) {
                    count = 1;
                    count2++;
                }
                return true;
            }
        });

运行10次以上,得到一个原文和多个错误密文,然后使用phoenixAES算出第10轮秘钥
在这里插入图片描述
算出后再用 Stark 算出主密钥
在这里插入图片描述
这里算出密钥后,验证一下:
在这里插入图片描述
结果正确,说明咱们aes密钥找对了

										hmac-sha256		

最后一步了,找出 33c4d4f0f3316a554af23888c78af4b44050d6c952572c743e047af26c1d8c3e 生成的位置,由hook入参可知,33c4xxx存在 0x404d8340,咱们trace一下

emulator.traceWrite(0x404d8340,0x404d8340+0x20);

在这里插入图片描述
是在0x1ce50,此时已经生成结果,对上一个函数进行hook
在这里插入图片描述
在这里插入图片描述
hmac的特征值就是0x36和0x5c,经过验证
在这里插入图片描述
所以这个就是密钥,python验证一下
在这里插入图片描述
ok,这里也没问题,至此明文分析介绍

四.时间戳分析

这个就比较简单了,网上也有很多说明,就是时间戳的16进制,验证一下
在这里插入图片描述
也没问题,然后5-8字节发生的变化,这个是随机数,使用了时间戳作为种子的随机数,这里就不多分析了,知道结果即可。

五.最后一位分析

前面所有字节均已分析完毕,还有最后一位,回到咱们最开始分析的地方

 v370 = 0LL;
 DWORD1(v460[1]) = (qword_644D0 >> 57) & 2 | ((qword_644D0 & 0x2000000000000000LL) != 0) | (qword_644D0 >> 58) & 4 | (qword_644D0 >> 53) & 0x10 | (qword_644D0 >> 54) & 0x20 | (qword_644D0 >> 44) & 0x40 | 0xD00 | (v368 << 24);
              do
              {
                *(v460 + v370) ^= v368 ^ v370;
                ++v370;
              }

这里循环了23轮,异或得到最终结果。我们在这里hook一下,并根据代码分析发现
在这里插入图片描述
最后一位就是(qword_644D0 >> 57) & 2 | ((qword_644D0 & 0x2000000000000000LL) != 0) | (qword_644D0 >> 58) & 4 | (qword_644D0 >> 53) & 0x10 | (qword_644D0 >> 54) & 0x20 | (qword_644D0 >> 44) & 0x40 | 0xD00 | (v368 << 24)计算得到的,python验证一下
在这里插入图片描述
至此,所以数据均已分析完成

总结

1.前四位固定值
2.5-8处字节又时间戳为种子的随机数生成
3.9-12固定值
4.13-16由明文生成,采用 CRC32 + AES-CBC + HMAC-SHA256 生成
5.17-20 时间戳生成
6.21-23 固定值
6.第24位由前23位求和取反算出
7.前六步生成的20位密文异或得到最终加密参数

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐