mtgsig2.1 版本之 a5 算法分析

本文由 简悦 SimpRead 转码, 原文地址 mp.weixin.qq.com

新年快乐~

搞过 mtgsig 的应该都知道,a0 代表算法版本,a1 可以理解为 mt 系每个 app 的标识码,此文章所用 app 和版本是 dp10.52.15

简单说一下怎么定位 a5 的加密入口以及算法还原

首先这里用的指令执行模拟框架还是 unidbg

补环境不再说了,直接放出完整调用代码

package com.xxx;
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Emulator;
import com.github.unidbg.Module;
import com.github.unidbg.debugger.Debugger;
import com.github.unidbg.file.FileResult;
import com.github.unidbg.file.IOResolver;
import com.github.unidbg.hook.hookzz.*;
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.api.ApplicationInfo;
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.wrapper.DvmInteger;
import com.github.unidbg.linux.android.dvm.wrapper.DvmLong;
import com.github.unidbg.linux.file.SimpleFileIO;
import com.github.unidbg.memory.Memory;
import com.github.unidbg.utils.Inspector;
import com.sun.jna.Pointer;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
public class Mtgsig extends AbstractJni implements IOResolver {
    private final AndroidEmulator emulator;
    private final VM vm;
    private final Module module;
    private static final String APP_PACKAGE_NAME = "com.xxx.v1";
    Mtgsig() {
        emulator = AndroidEmulatorBuilder.for32Bit().setProcessName(APP_PACKAGE_NAME).build(); // 创建模拟器实例,要模拟32位或者64位,在这里区分
        final Memory memory = emulator.getMemory(); // 模拟器的内存操作接口
        memory.setLibraryResolver(new AndroidResolver(23)); // 设置系统类库解析
        vm = emulator.createDalvikVM(new File("unidbg-android\\src\\test\\java\\com\\xxx\\xxx\xxx.apk")); // 创建Android虚拟机
        vm.setVerbose(true); // 设置是否打印Jni调用细节
        vm.setJni(this);
        DalvikModule dm = vm.loadLibrary(new File("unidbg-android\\src\\test\\java\\com\\xxx\\xxx\\xxx.so"), true);
        emulator.getSyscallHandler().addIOResolver(this);
        module = dm.getModule();
        dm.callJNI_OnLoad(emulator);
    }
    public static void main(String[] args) {
        Mtgsig test = new Mtgsig();
        test.HookByConsoleDebugger();
        test.hookrc4();
        test.hookrc4_key();
        test.hookbase64();
        test.hookbase64_ret();
        test.callInit();
        test.callMain();
    }
    public void callInit(){
        List<Object> list = new ArrayList<>(10);
        list.add(vm.getJNIEnv()); // 第一个参数是env
        list.add(0); // 第二个参数,实例方法是jobject,静态方法是jclazz,直接填0,一般用不到。
        list.add(1);
        ArrayObject myobject = new ArrayObject(null);
        vm.addLocalObject(myobject);
        // 完整的参数2
        list.add(vm.addLocalObject(myobject));
        module.callFunction(emulator, 0x4305, list.toArray());
    };
    public void callMain(){
        List<Object> list = new ArrayList<>(10);
        list.add(vm.getJNIEnv()); // 第一个参数是env
        list.add(0); // 第二个参数,实例方法是jobject,静态方法是jclazz,直接填0,一般用不到。
        list.add(2);
        StringObject input2_1 = new StringObject(vm, "4069cb78-e02b-45f6-9f0a-b34ddccf389c");
        String input="GET /localpush.bin cityid=4";
        ByteArray input2_2 = new ByteArray(vm, input.getBytes(StandardCharsets.UTF_8));
        DvmInteger input2_3 = DvmInteger.valueOf(vm, 2);
        vm.addLocalObject(input2_1);
        vm.addLocalObject(input2_2);
        vm.addLocalObject(input2_3);
        // 完整的参数2
        list.add(vm.addLocalObject(new ArrayObject(input2_1, input2_2, input2_3)));
        Number number = module.callFunction(emulator, 0x4305, list.toArray())[0];
        StringObject result = (StringObject) ((DvmObject[])((ArrayObject)vm.getObject(number.intValue())).getValue())[0];
        System.out.println(result.getValue());
    };
    public void hookrc4(){
        IHookZz hookZz = HookZz.getInstance(emulator); // 加载HookZz
        hookZz.wrap(module.base + 0x88F80 + 1, new WrapCallback<HookZzArm32RegisterContext>() {
            @Override
            // 方法执行前
            public void preCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {
                int length = ctx.getIntArg(3);
                Pointer out = ctx.getPointerArg(2);
                ctx.push(out);
                Inspector.inspect(ctx.getPointerArg(1).getByteArray(0, length),"rc4 input");
         };
            @Override
            // 方法执行后
            public void postCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {
                Pointer output = ctx.pop();
                byte[] outputhex = output.getByteArray(0, 193);
                Inspector.inspect(outputhex, "hook rc4 结果");
            }
        });
        hookZz.disable_arm_arm64_b_branch();
    }
    public void hookrc4_key(){
        IHookZz hookZz = HookZz.getInstance(emulator); // 加载HookZz
        hookZz.wrap(module.base + 0x88EEA + 1, new WrapCallback<HookZzArm32RegisterContext>() {
            @Override
            // 方法执行前
            public void preCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {
                int length = ctx.getIntArg(2);
                Pointer arg1 = ctx.getPointerArg(1);
                // ctx.push(ctx.getR2Pointer());
                Pointer out1 = ctx.getPointerArg(0);
                ctx.push(out1);
                Inspector.inspect(arg1.getByteArray(0, length),"rc4 key input");
            };
            @Override
            // 方法执行后
            public void postCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {
                Pointer output = ctx.pop();
                System.out.println(output);
                byte[] outputhex = output.getByteArray(0, 256);
                Inspector.inspect(outputhex, "hook rc4 key 结果");
            }
        });
        hookZz.disable_arm_arm64_b_branch();
    }
    public void hookbase64(){
        IHookZz hookZz = HookZz.getInstance(emulator); // 加载HookZz
        hookZz.wrap(module.base + 0xB468 + 1, new WrapCallback<HookZzArm32RegisterContext>() {
            @Override
            // 方法执行前
            public void preCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {
                int length = ctx.getIntArg(2);
                Pointer out = ctx.getPointerArg(0);
                ctx.push(out);
                Inspector.inspect(ctx.getPointerArg(1).getByteArray(0, length),"base64 input");
            };
            @Override
            // 方法执行后
            public void postCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {
            }
        });
        hookZz.disable_arm_arm64_b_branch();
    }
    public void hookbase64_ret(){
        IHookZz hookZz = HookZz.getInstance(emulator); // 加载HookZz
        hookZz.wrap(module.base + 0x8C8E0 + 1, new WrapCallback<HookZzArm32RegisterContext>() {
            @Override
            // 方法执行前
            public void preCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {
                int length = ctx.getIntArg(2);
                Pointer out = ctx.getPointerArg(1);
                ctx.push(out);
                Inspector.inspect(ctx.getPointerArg(1).getByteArray(0, length),"base64 结果");
            };
            @Override
            // 方法执行后
            public void postCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {
            }
        });
        hookZz.disable_arm_arm64_b_branch();
    }
    public void HookByConsoleDebugger(){
        Debugger debugger = emulator.attach();
        debugger.addBreakPoint(module.base+0x88EEA+1);//rc4 key
        debugger.addBreakPoint(module.base+0x88F80+1);//rc4
        //   debugger.addBreakPoint(module.base+0xB468);//base64
        //trace a5 baseb4的地址
        //emulator.traceWrite(0x40335300L,0x40335300L+0xc0L);
    }
    @Override
    public DvmObject<?> callStaticObjectMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
        switch (signature){
            case "com/meituan/android/common/mtguard/NBridge->getClassLoader()Ljava/lang/ClassLoader;":{
                return vm.resolveClass("java/lang/ClassLoader").newObject(signature);
            }
            case "java/lang/ClassLoader->main2(I[Ljava/lang/Object;)Ljava/lang/Object;":{
                int num = vaList.getIntArg(0);
                System.out.println("fuck Num:"+num);
                switch (num){
                    case 1:{
                        return new StringObject(vm, "com.dianping.v1");
                    }
                    case 4:{
                        return new StringObject(vm, "ms_com.dianping.v1.png");
                    }
                    case 5:{
                        return new StringObject(vm, "ppd_com.dianping.v1.xbt");
                    }
                    case 2:{
                        DvmObject<?> context = vm.resolveClass("android/content/Context").newObject(null);// context
                        return context;
                    }
                    case 6:{
                        return new StringObject(vm, "5.4.11");
                    }
                    case 51:{
                        return new StringObject(vm, "2");
                    }
                    case 3:{
                        DvmObject<?> context = vm.resolveClass("android/content/Context").newObject(null);// context
                        return context;
                    }
                    case 8:{
                        return new StringObject(vm, "");
                    }
                    case 40:{
                        return new StringObject(vm, "");
                    }
                }
                break;
            }
            case "java/lang/ClassLoader->main1(I[Ljava/lang/Object;)Ljava/lang/Object;":{
                int num = vaList.getIntArg(0);
                System.out.println("fuck Num:"+num);
                switch (num){
                    case 49:{
                        return new StringObject(vm, "5.1.12");
                    }
                }
                break;
            }
            case "java/lang/System->getProperty(Ljava/lang/String;)Ljava/lang/String;":{
                String propertyKey = vm.getObject(vaList.getObjectArg(0).hashCode()).getValue().toString();
                System.out.println(propertyKey);
                switch (propertyKey){
                    case "http.proxyHost":{
                        return new StringObject(vm, "");
                    }
                    case "https.proxyHost":{
                        return new StringObject(vm, "");
                    }
                }
                break;
            }
            case "android/os/SystemProperties->get(Ljava/lang/String;)Ljava/lang/String;":{
                String propertyKey = vm.getObject(vaList.getObjectArg(0).hashCode()).getValue().toString();
                System.out.println(propertyKey);
                switch (propertyKey){
                    case "ro.build.id":{
                        return new StringObject(vm, "OPR6.170623.010");
                    }
                    case "persist.sys.usb.config":{
                        return new StringObject(vm, "diag,serial_cdev,rmnet,adb");
                    }
                    case "sys.usb.config":{
                        return new StringObject(vm, "ptp,adb");
                    }
                    case "sys.usb.state":{
                        return new StringObject(vm, "ptp,adb");
                    }
                }
                break;
            }
            case "java/util/UUID->randomUUID()Ljava/util/UUID;":{
                return vm.resolveClass("java/util/UUID").newObject(UUID.randomUUID());
            }
            case "java/lang/Long->valueOf(J)Ljava/lang/Long;":{
                return DvmLong.valueOf(vm, vaList.getLongArg(0));
            }
            case "java/lang/String->format(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;":{
                return new StringObject(vm, String.format(vaList.getObjectArg(0).getValue().toString(), vaList.getIntArg(1)));
            }
        }
        return super.callStaticObjectMethodV(vm, dvmClass, signature, vaList);
    }
    @Override
    public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
        switch (signature){
            case "java/lang/ClassLoader->loadClass(Ljava/lang/String;)Ljava/lang/Class;":{
                return dvmObject.getObjectType();
            }
            case "android/content/pm/PackageManager->getApplicationInfo(Ljava/lang/String;I)Landroid/content/pm/ApplicationInfo;":{
                return new ApplicationInfo(vm);
            }
            case "java/util/UUID->toString()Ljava/lang/String;":{
                return new StringObject(vm, dvmObject.getValue().toString());
            }
        }
        return super.callObjectMethodV(vm, dvmObject, signature, vaList);
    }
    @Override
    public DvmObject<?> newObjectV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
        switch (signature){
            case "java/io/File-><init>(Ljava/lang/String;)V":{
                return vm.resolveClass("java/io/File").newObject(new File(vaList.getObjectArg(0).toString()));
            }
            case "java/lang/Integer-><init>(I)V":
                int input = vaList.getIntArg(0);
                return DvmInteger.valueOf(vm, input);
        }
        return super.newObjectV(vm, dvmClass, signature, vaList);
    }
    @Override
    public boolean callBooleanMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
        switch (signature){
            case "java/io/File->canRead()Z":{
                return false;
            }
        }
        return super.callBooleanMethodV(vm, dvmObject, signature, vaList);
    }
    @Override
    public DvmObject<?> getObjectField(BaseVM vm, DvmObject<?> dvmObject, String signature) {
        switch (signature){
            case "android/content/pm/ApplicationInfo->sourceDir:Ljava/lang/String;":{
                return new StringObject(vm, "/data/app/com.dianping.v1-BVuX2sFQfuNmRz85qE2NuA==/base.apk");
            }
        }
        return super.getObjectField(vm, dvmObject, signature);
    }
    @Override
    public int getStaticIntField(BaseVM vm, DvmClass dvmClass, String signature) {
        switch (signature){
            case "android/content/pm/PackageManager->GET_SIGNATURES:I":{
                return 64;
            }
        }
        return super.getStaticIntField(vm, dvmClass, signature);
    }
    @Override
    public int getIntField(BaseVM vm, DvmObject<?> dvmObject, String signature) {
        switch (signature){
            case "android/content/pm/PackageInfo->versionCode:I":{
                return 1100110203;
            }
        }
        return super.getIntField(vm, dvmObject, signature);
    }
    @Override
    public DvmObject<?> getStaticObjectField(BaseVM vm, DvmClass dvmClass, String signature) {
        switch (signature){
            case "android/os/Build->BRAND:Ljava/lang/String;":{
                return new StringObject(vm, "OPPO");//品牌
            }
            case "android/os/Build->TYPE:Ljava/lang/String;":{
                return new StringObject(vm, "user");
            }
            case "android/os/Build->HARDWARE:Ljava/lang/String;":{
                return new StringObject(vm, "OPM1.171019.011");//硬件
            }
            case "android/os/Build->MODEL:Ljava/lang/String;":{
                return new StringObject(vm, "OPPO R11st");//型号
            }
            case "android/os/Build->TAGS:Ljava/lang/String;":{
                return new StringObject(vm, "release-keys");
            }
            case "android/os/Build$VERSION->RELEASE:Ljava/lang/String;":{
                return new StringObject(vm, "9");
            }
        }
        return super.getStaticObjectField(vm, dvmClass, signature);
    }
    @Override
    public long callStaticLongMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
        switch (signature){
            case "java/lang/System->currentTimeMillis()J":{
                return System.currentTimeMillis();
            }
        }
        return super.callStaticLongMethodV(vm, dvmClass, signature, vaList);
    }
    @Override
    public FileResult resolve(Emulator emulator, String pathname, int oflags) {
                           这里成包名
        if (("/data/app/com.xxx.v1-BVuX2sFQfuNmRz85qE2NuA==/base.apk").equals(pathname)) {
            return FileResult.success(new SimpleFileIO(oflags, new File("unidbg-android\\src\\test\\java\\com\\xxx\\xxx\\base.apk"), pathname));
        }
        return null;
    }
}
mtgsig = {
      "a0":"2.1",
      "a1":"4069cb78-e02b-45f6-9f0a-b34ddccf389c",
      "a3":4,
      "a4":1641007666,
      "a5":"SWeM8hCznbxfdTxJtc0KHSzzKXqYFjfTl0UX+7wJk1DDJpB6VVJk/z/nOY4AgFnNPQA9BrwNNfDMYuaiAtIF65ZTLlIiSrFeSKTl1OZnpXzP1FvxE7IOT8lt9vqAkVjldJteCLcNt0hCVGHgpLtSixrX4nnLBa0xx6VDq8D7l+ZuDj6LwbwhfQrTywSliZrBFMNSw7QCBwufpLeZGln+I4Gg4xO4hB78x2QpnuGoGzCQ59Bvpfsb2DYwraeRb7Il",
      "a6":0,
      "a7":"5R12uldnp3hSXPr7QbCyuHdMCrky7li1vGm/mSt+YyAm7r5b5MqI/SlCC8BSeXZq7tAbvMxK3aXI7ZxNMosia7BaRbVILxHDVxFHrN2tRTI=",
      "a8":"DAD724885614A2FEFE4680911D321F89911FC9D29FA7F306DC6AEE1F",
      "a9":"90aa5e3cGg12eitjwSfAQq77re9bzPGk5gYYnXaseK0nggfQbPVktUMdgequOmVsVNUpcP1b0ceoAHDyorGl5cekKABAErse+EP0QwwIVhofmRHst0S59pj/B5h8htH+zosxunRrK0hNZJmvoh1xRBkKHq6jquk3GIKtPf7RXCXwXxN0yS3qqcdFZO2Wep4V8va5C/TkjB0YC6u4mJz/brXx3F/KYX3Z9PaGtyRGZpaRpgKBLxVUKJWA8dwT9MgNB466bfm3XrFyYnHw0sbrgDFaMM5rile4hvzHef+08TjJ3ycIOJT0kFLZOC4c3QVftWm31epg",
      "a10":"{}",
      "x0":1,
      "a2":"52516f82cfdd639bc05dbed46add0c59"
              }

上面是 undbg 调用出来的 mtgsig,会发现每次调用 a2, a4, a5, a7 都会不一样, 这样很难分析,所以我们第一件事先修改 undbg 中的时间获取函数,让函数返回固定值,看看有什么效果(并不是所有案例都可以)此图放错应修改 gettimeofday,可以在 src/main/java/com/github/unidbg/unix/UnixSyscallHandler.java 中修改

这样修改后每次调用 a2, a4, a5, a7 都是固定的

然后我们看 a5 这种密文,一看就是 base64 输出方式,那就 hook 住 base64 的函数验证下

**怎么找 base64 编码函数?
**

老方法:ida 打开 shift + f12 搜索 ABCDE

双击第一个结果

快捷键 x 查看交叉引用

双击第一个结果

f5

int __fastcall sub_B468(int a1, int a2, signed int a3)
{
  int v3; // r4
  int v4; // r0
  _BYTE *v5; // r6
  int v6; // r5
  int v7; // r2
  int v8; // r0
  int v9; // r4
  int v10; // r2
  char v11; // r1
  int v12; // r1
  int v13; // r0
  _DWORD *v14; // r0
  int v16; // [sp+4h] [bp-24h]
  signed int v17; // [sp+8h] [bp-20h]
  int v18; // [sp+Ch] [bp-1Ch]
  int v19; // [sp+10h] [bp-18h]
  char v20; // [sp+14h] [bp-14h]
  int v21; // [sp+18h] [bp-10h]
  v3 = a1;
  if ( !a2 || !a3 )
  {
    v12 = sub_370C(_stack_chk_guard) + 132;
    v13 = v3;
LABEL_13:
    sub_8C608(v13, v12);
    return _stack_chk_guard - v21;
  }
  v19 = a2;
  v18 = a1;
  v17 = a3;
  v4 = sub_98230(a3 + 2, 3u);
  v5 = malloc((4 * v4 | 1) + 1);
  if ( !v5 )
  {
    v12 = sub_370C(0) + 132;
    v13 = v3;
    goto LABEL_13;
  }
  v6 = 0;
  sub_2A514();
  v7 = v17;
  v16 = v5;
  v8 = v19;
  if ( v17 >= 3 )
  {
    v6 = 0;
    do
    {
      *v5 = aAbcdefghijklmn[*(v8 + v6) >> 2];
      v5[1] = aAbcdefghijklmn[(*(v8 + v6 + 1) >> 4) | 16 * *(v8 + v6) & 0x30];
      v5[2] = aAbcdefghijklmn[(*(v8 + v6 + 2) >> 6) | 4 * *(v8 + v6 + 1) & 0x3C];
      v5[3] = aAbcdefghijklmn[*(v8 + v6 + 2) & 0x3F];
      v8 = v19;
      v5 += 4;
      v6 += 3;
    }
    while ( v6 < v17 - 2 );
    v7 = v17;
  }
  if ( v6 >= v7 )
  {
    v14 = v3;
  }
  else
  {
    v9 = v7;
    *v5 = aAbcdefghijklmn[*(v8 + v6) >> 2];
    v10 = 16 * *(v8 + v6) & 0x30;
    if ( v6 == v9 - 1 )
    {
      v5[1] = aAbcdefghijklmn[v10];
      v11 = 61;
    }
    else
    {
      v5[1] = aAbcdefghijklmn[(*(v8 + v6 + 1) >> 4) | v10];
      v11 = aAbcdefghijklmn[4 * *(v8 + v6 + 1) & 0x3C];
    }
    v14 = v18;
    v5[3] = 61;
    v5[2] = v11;
    v5 += 4;
  }
  *v5 = 0;
  sub_8C8E0(v14, v16, &v5[~v16 + 1], &v20);
  free(v16);
  return _stack_chk_guard - v21;
}

这函数里有 base64 编码流程,看上去没魔改,编码的结果是放在 v5,v5=v16,v16 传给 sub_bc8e0, 但还是要验证下 base64 魔改没,a5 走不走这个函数也要验证

所以 hook 一把梭

hook sub_B468 和 sub_8C8E0

    public static void main(String[] args) {
        Mtgsig test = new Mtgsig();
        test.hookbase64();
        test.hookbase64_ret();
        test.callInit();      
        test.callMain();
    }

结果在运行日志里找到了 a5 的密文

可以看到 base64 的入参是乱码,所以肯定是某种算法加密后的字节流,

把这个入参经过标准 base64 编码之后发现和 unidbg 算出来的结果一致,说明没有魔改

所以下一步我们要顺藤摸瓜,找到这个某算法

定位某算法

方法很多,我用 trace,因为 unidbg 的地址随机化是关闭的,也就是说,你模拟执行一千次,加密结果存放的内存地址是不会变的,所以可以监控某个地址块的读写操作,从而找到操作地址的地方

console debugger 在 0xB468 处打个断点

    public void HookByConsoleDebugger(){
        Debugger debugger = emulator.attach();
        debugger.addBreakPoint(module.base+0xB468);
    }

可以看到 r1 寄存器存放的是某算法加密后的字节流,r2 是长度,那么我们 trace 这个地址的写入,看是哪个地址操作了这个地址

    public void HookByConsoleDebugger(){
        Debugger debugger = emulator.attach();
        debugger.addBreakPoint(module.base+0xB468);
        //trace a5 baseb4 输入的地址
        emulator.traceWrite(0x40335300L,0x40335300L+0xc0L);
    }

注意地址是 long 类型,不加 L 是 trace 不了的

然后运行

有结果了,后边的地址就是我们要的东西

ida g 跳到 0x88fdc

int __fastcall sub_88F80(int result, _BYTE *a2, _BYTE *a3, signed int a4)
{
  int v4; // r4
  int v5; // r5
  char v6; // ST0C_1
  _BYTE *v7; // [sp+0h] [bp-28h]
  _BYTE *v8; // [sp+4h] [bp-24h]
  if ( a4 >= 1 )
  {
    v8 = (result + 257);
    v7 = (result + 256);
    do
    {
      LOBYTE(v4) = *v7 + 1;
      *v7 = v4;
      v4 = v4;
      LOBYTE(v5) = *v8 + *(result + v4);
      *v8 = v5;
      v6 = *(result + v4);
      v5 = v5;
      *(result + v4) = *(result + v5);
      *(result + v5) = v6;
      *a3 = *a2 ^ *(result + ((*(result + *v8) + *(result + *v7)) & 0xFF));
      --a4;
      ++a3;
      ++a2;
    }
    while ( a4 );
  }
  return result;
}

特征很明显,有经验的一看就知道是 rc4(boss 直聘那篇也是这个

hook

    public static void main(String[] args) {
        Mtgsig test = new Mtgsig();
        test.HookByConsoleDebugger();
        test.hookrc4();
        test.callInit();
        test.callMain();
    }

结果

入参开头是 789c, 是 zlib 压缩算法特征

import zlib
print(zlib.decompress(bytes.fromhex("789c9591bb0e83300c45ffc5338e72f382f22d594a2b550ca59dba20febd7696ba4895e88038d738768e5869028db45642a5b11257ea2a05c3d1706adc303744e3625a7ac343e3d4f8f4390a6f7a60f72298ae6838d9a66c3ed8d5514f88898ef7dd3f466c94f880121b253ee6c4568a7f5af14e6bdba8a329d0087945f5a229c90fbb3ceeee3a9f97e7bcdcdc0bda94a50cef7270c89a8b66782f4ff0512bbd8c29494a7d2945f2b0cba75d869711d92587b600f88e722b0ab4bd0143e08309")).decode())

现在明文知道了 还要知道 rc4 的 key,key 一般在另一个函数里,通过 key 生成 S 盒

sub_88F80 按 x 交叉引用 可以到这里

int __fastcall sub_88EEA(int a1, int key, int key_len, int a4)
{
  int ret; // r6
  int v5; // r0
  int v6; // r5
  int v7; // r4
  int v8; // r1
  int v9; // r0
  int v10; // r1
  int result; // r0
  int v12; // r4
  int v13; // r5
  int v14; // ST00_4
  int v15; // r1
  int v16; // r1
  int key1; // [sp+8h] [bp-18h]
  int v18; // [sp+Ch] [bp-14h]
  ret = a1;
  v5 = 0;
  do
  {
    *(ret + v5) = v5;
    ++v5;
  }
  while ( 256 != v5 );
  key1 = key;
  *(ret + 257) = 0;
  *(ret + 256) = 0;
  v18 = key_len;
  if ( a4 )
  {
    v6 = 0;
    v7 = 0;
    do
    {
      sub_982CC(v6, key_len);
      v9 = *(key1 + v8);
      v10 = *(ret + v6);
      key_len = v18;
      v7 = (v6 + v7 + v10 + v9) & 0xFF;
      *(ret + v6) = *(ret + v7);
      *(ret + v7) = v10;
      ++v6;
      result = 256;
    }
    while ( 256 != v6 );
  }
  else
  {
    v12 = 0;
    LOBYTE(result) = 0;
    do
    {
      v13 = *(ret + v12);
      v14 = result + v13;
      sub_982CC(v12, key_len);
      result = v14 + *(key1 + v15);
      v16 = (v14 + *(key1 + v15)) & 0xFF;
      *(ret + v12) = *(ret + v16);
      key_len = v18;
      *(ret + v16) = v13;
      ++v12;
    }
    while ( 256 != v12 );
  }
  return result;
}

hook sub_88EEA

key 是 mtgsig[‘a1’] + mtgsig[‘a3’] + mtgsig[‘a4’]

打开逆向之友,看看是否魔改了

oh shit 标准加密出来结果不对,经过验证,确实是魔改了

魔改的是密钥生成 S 的部分,huok 这个函数的返回值就是 S 盒,可以发现和标准算法生成的 S 盒不一样

两个分支,因为我之前搞过 1.5 和 2.0 的, 我发现之前版本是只有下面的分支,下面的是标准 rc4 

现在因为 a4=1 所以走的上面的分支

然后经过我野兽般的眼神和嗅觉,呵,不就这里多 + 了个变量吗

好了 知道了

先搞一份 rc4 py 源码,rc4 原理这个作者也讲的很好

https://www.biaodianfu.com/rc4.html
import base64
import zlib
MOD = 256
def KSA(key):
    key_length = len(key)
    S = list(range(MOD)) 
    j=0
    for i in range(MOD):
        j = (j + S[i] + key[i % key_length]) % MOD
        S[i], S[j] = S[j], S[i] 
    return S
def PRGA(S):
    i = 0
    j = 0
    while True:
        i = (i + 1) % MOD
        j = (j + S[i]) % MOD
        S[i], S[j] = S[j], S[i]  # swap values
        K = S[(S[i] + S[j]) % MOD]
        yield K
def get_keystream(key):
    S = KSA(key)
    return PRGA(S)
def encrypt_logic(key, text)->bytes:
    if isinstance(key, bytes):
        key = [c for c in key]
    else:
        key = [ord(c) for c in key]
    keystream = get_keystream(key)
    res = []
    for c in text:
        val = c ^ next(keystream)
        res.append(val)
    return bytes(res)
def encrypt(key, plaintext)->bytes:
    if isinstance(plaintext,bytes):
        plaintext = [c for c in plaintext]
    else:
        plaintext = [ord(c) for c in plaintext]
    return encrypt_logic(key, plaintext)
def decrypt(key, ciphertext)->bytes:
    ciphertext = base64.b64decode(ciphertext.encode())
    res = encrypt_logic(key, ciphertext)
    return res

改秘钥 key 生成 S 盒的部分

def KSA(key):
    key_length = len(key)
    S = list(range(MOD)) 
    j=0
    for i in range(MOD):
        #多加了个 i 看到没???
        j = (i(我是混进来的) + j + S[i] + key[i % key_length]) % MOD
        S[i], S[j] = S[j], S[i] 
    return S

完整 py 代码

# -*- coding: utf-8 -*-
import base64
import zlib
MOD = 256
def KSA(key):
    key_length = len(key)
    # create the array "S"
    S = list(range(MOD))  # [0,1,2, ... , 255]
    j=0
    for i in range(MOD):
      #  print(hex(key[i % key_length]))
        j = (i + j + S[i] + key[i % key_length]) % MOD
        S[i], S[j] = S[j], S[i]  # swap values
    return S
def PRGA(S):
    i = 0
    j = 0
    while True:
        i = (i + 1) % MOD
        j = (j + S[i]) % MOD
        S[i], S[j] = S[j], S[i]  # swap values
        K = S[(S[i] + S[j]) % MOD]
        yield K
def get_keystream(key):
    S = KSA(key)
    return PRGA(S)
def encrypt_logic(key, text)->bytes:
    if isinstance(key, bytes):
        key = [c for c in key]
    else:
        key = [ord(c) for c in key]
    keystream = get_keystream(key)
    res = []
    for c in text:
        val = c ^ next(keystream)
        res.append(val)
    return bytes(res)
def encrypt(key, plaintext)->bytes:
    if isinstance(plaintext,bytes):
        plaintext = [c for c in plaintext]
    else:
        plaintext = [ord(c) for c in plaintext]
    return encrypt_logic(key, plaintext)
def decrypt(key, ciphertext)->bytes:
    ciphertext = base64.b64decode(ciphertext.encode())
    res = encrypt_logic(key, ciphertext)
    return res
def a5_decrypt(mtgsig):
    xina5key = f"{mtgsig['a1']}{mtgsig['a3']}{mtgsig['a4']}"
    a5_str = decrypt(xina5key, mtgsig['a5'])
    a5_str = zlib.decompress(a5_str).decode()
    print(a5_str)
    return a5_str
def a5_encrypt(mtgsig):
    xina5key = f"{mtgsig['a1']}{mtgsig['a3']}{mtgsig['a4']}"
    a5_str =r'{"b1":"{\"1\":\"-\",\"2\":\"-\",\"3\":\"-\",\"4\":\"\",\"5\":\"1\",\"6\":\"-\",\"7\":\"-\",\"8\":\"4\",\"9\":\"\",\"10\":\"-\",\"11\":\"-\",\"12\":\"\",\"13\":\"\",\"14\":\"-\",\"15\":\"\",\"16\":\"-\",\"33\":{\"0\":0,\"1\":\"-\",\"2\":\"-\",\"3\":\"-\",\"4\":\"-\",\"5\":\"-\",\"6\":\"-\",\"7\":\"-\",\"8\":\"-\",\"9\":\"-\",\"10\":\"-\",\"11\":\"-\",\"12\":\"-\",\"13\":\"-\",\"14\":\"-\",\"15\":\"-\",\"16\":\"-\"}}","b2":1,"b3":0,"b4":"com.dianping.v1","b5":"10.52.15","b6":"1100110203","b7":1641007666,"b8":1641007666,"b9":1641007666,"b10":"5.4.11","b11":"5.4.11","b12":"2"}'
    a5_str = zlib.compress(a5_str.encode())
    a5 = encrypt(xina5key,a5_str)
  #  hexdump(a5)
    print(base64.b64encode(a5).decode())
    return base64.b64encode(a5).decode()
def main():
    a5_decrypt(mtgsig)
    a5_encrypt(mtgsig)
if __name__ == '__main__':
    mtgsig = {"a0":"2.1",
              "a1":"4069cb78-e02b-45f6-9f0a-b34ddccf389c",
              "a3":4,
              "a4":1641007666,
              "a5":"SWeM8hCznbxfdTxJtc0KHSzzKXqYFjfTl0UX+7wJk1DDJpB6VVJk/z/nOY4AgFnNPQA9BrwNNfDMYuaiAtIF65ZTLlIiSrFeSKTl1OZnpXzP1FvxE7IOT8lt9vqAkVjldJteCLcNt0hCVGHgpLtSixrX4nnLBa0xx6VDq8D7l+ZuDj6LwbwhfQrTywSliZrBFMNSw7QCBwufpLeZGln+I4Gg4xO4hB78x2QpnuGoGzCQ59Bvpfsb2DYwraeRb7Il",
              "a6":0,
              "a7":"5R12uldnp3hSXPr7QbCyuHdMCrky7li1vGm/mSt+YyAm7r5b5MqI/SlCC8BSeXZq7tAbvMxK3aXI7ZxNMosia7BaRbVILxHDVxFHrN2tRTI=",
              "a8":"DAD724885614A2FEFE4680911D321F89911FC9D29FA7F306DC6AEE1F",
              "a9":"90aa5e3cGg12eitjwSfAQq77re9bzPGk5gYYnXaseK0nggfQbPVktUMdgequOmVsVNUpcP1b0ceoAHDyorGl5cekKABAErse+EP0QwwIVhofmRHst0S59pj/B5h8htH+zosxunRrK0hNZJmvoh1xRBkKHq6jquk3GIKtPf7RXCXwXxN0yS3qqcdFZO2Wep4V8va5C/TkjB0YC6u4mJz/brXx3F/KYX3Z9PaGtyRGZpaRpgKBLxVUKJWA8dwT9MgNB466bfm3XrFyYnHw0sbrgDFaMM5rile4hvzHef+08TjJ3ycIOJT0kFLZOC4c3QVftWm31epg",
              "a10":"{}",
              "x0":1,
              "a2":"52516f82cfdd639bc05dbed46add0c59"
              }
    main()

a5 完结,下篇讲讲 a9  a9 也是魔改了标准算法,应该是 Blowfish 或者 Twofishs 算法,不过因为对这算法不熟悉,愣是不知道魔改了哪里,一气之下 (走投无路

) 撸了一遍汇编,后面有空再研究研究算法

不过汇编更 6 了 

翻译到三分之一我就觉得这样好傻逼 一部分简单指令可以自动化转成 py 代码,然后就加快了速度~

哦还有 a2,a2 还是跟 mtgsig2.0 的一样

上一页
下一页