ibilibili 发表于 2025-2-17 16:03

52pj-25.6W&A题分析

## 前言

答题结束后,看了相关题解,尝试使用unidbg来打印trace并分析。

## 0x1还原vmcode

初始化ss,放入vmcode



注:双击byte_FEEE,shif+e拿出vmcode,待还原时用

分析vmcode使用逻辑



分析每个case操作



理清了每个opcode操作,还原vmcode为 伪码。

## 0x2分析trace还原意图

前言:虚拟机执行前分别lit了uid,0x1000,0x2000,还入栈了flag和一个字典,见下图。

注:这个字典在ida 32位so比较容易导出。



使用unidbg hook 虚拟机 switch(opcode) 打印trace,代码见附录1,以下为部分打印的trace



根据trace逐条分析静态 伪码 ,记录下意图,完整版见附录2



## 0x3反推flag

虚拟机运行完返回栈顶top_0



这个值必须=0x3EACFC04,check函数才返回0,flag正确



虚拟机在结束前执行了res-=1 res ^= 0xc15303fb,由以上分析,res在-=1前必须为0



由附录2完整伪码,res初始值为0,程序会对输入的flag进行格式校验,格式正确res |=0,然后用uid生成的这个数对 5个字符为一块 的四个块进行校验,res|=校验结果,也就是说对每个块的校验结果必须也都是0,对每个块的校验见附录2->sub_c003,分析得出爆破时间复杂度 $O(10^8)$,根据sub_c003写代码进行爆破,爆破代码见附录3。

## s前言

下文 为 上文分析引发的新的分析。

## 0x1s朴素算法的优化

通过afdm大佬"base36"提示,发现暴破代码line.24-34,实际上是在进行base36 decode。



即要找出一个正确的flag,实际上是找出来一个合适的5位36进制数a,使得a%b==0。

分析暴破代码可得,a ∈ [ 1<<0x19+1 ,36^5 ],b ∈ [ 0x05 , 0x17 + 0x13541 * 0xFF ]。

如果题目对于每个uid都至少存在一个flag,易得最大的a应为36^5 // b * b,这意味着我们可以直接算出这个a,O(n^5) ->O(1)。

优化完成,优化后flag计算代码见附录1s(同时实现了sub_c128,sub_c0b6,输入uid可立得flag)。

## 0x2s证明flag存在性

因为题放出前一定经过验证,每个uid至少存在一个flag是肯定的。

只是不太明白如何证明,我自己想了很长时间没想出来,于是让豆包给出了证明,分享如下:

由0x1s分析,flag存在 即 对于任意b,存在一个正整数k,使得a=kb。


##附录1、unidbg代码

```java
package com.kanxue.test2;
...
public class MainActivity {

    public static void main(String[] args) {
      long start = System.currentTimeMillis();
      MainActivity mainActivity = new MainActivity();
      System.out.println("load offset=" + (System.currentTimeMillis() - start) + "ms");
      mainActivity.crack();
    }

    private final AndroidEmulator emulator;
    private final VM vm;
    private final DalvikModule dm;
    private final Module module;

    private MainActivity() {
      emulator = AndroidEmulatorBuilder
                .for64Bit()
                .addBackendFactory(new Unicorn2Factory(true))
                .build();
      Memory memory = emulator.getMemory();
      LibraryResolver resolver = new AndroidResolver(23);
      memory.setLibraryResolver(resolver);

      vm = emulator.createDalvikVM();
      vm.setVerbose(true);
      dm = vm.loadLibrary(new File("unidbg-android/src/test/java/com/kanxue/test2/libnativelib.so"), false);
      dm.callJNI_OnLoad(emulator);
      module = dm.getModule();
    }

    private void crack() {
      // 加载 Java 类
      DvmClass nativeLibClass = vm.resolveClass("cn/afdm/152pojie/nativelib/NativeLib");
      // 创建 Java 对象实例
      DvmObject<?> obj = nativeLibClass.newObject(null);
      logIns();
      // 调用本地方法
      String signature = "check(ILjava/lang/String;)I";
      int res = obj.callJniMethodInt(emulator, signature, 781545, vm.addLocalObject(new StringObject(vm, "flag{BYTJC-CINDT-3ZB9A-B5Q4E}")));
      System.out.println(res);
    }

    public void logIns() {
      emulator.getBackend().hook_add_new(new CodeHook() {
            @Override
            public void onAttach(UnHook unHook) {}
            @Override
            public void detach() {}
            @Override
            public void hook(Backend backend, long address, int size, Object user) {
                if (address == module.base + 0x19548) {
                  long ss = (long) backend.reg_read(Arm64Const.UC_ARM64_REG_X0);
                  long sp = mem_read_word(backend, ss + 0x10002);
                  long eip = eip = (int) (long) backend.reg_read(Arm64Const.UC_ARM64_REG_W11)-1;
                  long top_0 = mem_read_dword(backend, ss + sp);
                  long top_1 = mem_read_dword(backend, ss + sp - 4);
                  long opcode = (int) (long) backend.reg_read(Arm64Const.UC_ARM64_REG_W14);
                  long x = (int) (long) backend.reg_read(Arm64Const.UC_ARM64_REG_W12);

                  System.out.printf("eip:%04X sp:%04X ", eip, sp);
                  if (opcode == 0x00) {
                        System.out.printf("XOR sp-=4 top_0=%x^%x=%x ", top_0, top_1, top_0 ^ top_1);
                  } else if (opcode == 0x01) {
                        System.out.printf("NEG top_0=-top_0=%x ", -top_0);
                  } else if (opcode == 0x02) {
                        System.out.printf("DROP sp-=%x ", x * 4);
                  } else if (opcode == 0x03) {
                        System.out.printf("NOP");
                  } else if (opcode == 0x04) {
                        System.out.printf("OR sp-=4 top_0=%x|%x=%x ", top_0, top_1, top_0 | top_1);
                  } else if (opcode == 0x05) {
                        System.out.printf("RET top_0=%x ", top_0);
                  } else if (opcode == 0x06) {
                        System.out.printf("NEQ sp-=4 top_0=%x!=%x=%x ", top_0, top_1, top_0 != top_1);
                  } else if (opcode == 0x07) {
                        long target = mem_read_dword(backend, ss + sp - 4L * x);
                        ;
                        System.out.printf("SWP swap(top_0,sp-4*%x) top_0=%x target=%x", x, top_0, target);
                  } else if (opcode == 0x08) {
                        System.out.printf("AND sp-=4 top_0=%x&%x=%x ", top_0, top_1, top_0 & top_1);
                  } else if (opcode == 0x09) {
                        System.out.printf("SHL <<%x top_0=%x>%x", x, top_0, top_0 << x);
                  } else if (opcode == 0x0A) {
                        System.out.printf("NOT top_0=%x->%x", top_0, ~top_0);
                  } else if (opcode == 0x0B) {
                        System.out.printf("-1");
                  } else if (opcode == 0x0C) {
                        System.out.printf("ADD sp-=4 top_0=%x+%x=%x ", top_0, top_1, top_0 + top_1);
                  } else if (opcode == 0x0D) {
                        System.out.printf("-1");
                  } else if (opcode == 0x0E) {
                        System.out.printf("JMP %x ", eip + x);
                  } else if (opcode == 0x0F) {
                        System.out.printf("HALT");
                  } else if (opcode == 0x10) {
                        System.out.printf("-1");
                  } else if (opcode == 0x11) {
                        System.out.printf("SHR >>%x top_0=%x>%x", x, top_0, top_0 >> x);
                  } else if (opcode == 0x12) {
                        System.out.printf("MOD sp-=4 top_0=%x mod %x=%x ", top_1, top_0, top_1 % top_0);
                  } else if (opcode == 0x13) {
                        System.out.printf("-1");
                  } else if (opcode == 0x14) {
                        System.out.printf("LOBYTE top_0=%x ", top_0 & 0xff);
                  } else if (opcode == 0x15) {
                        System.out.printf("MUL sp-=4 top_0=%x*%x=%x ", top_0, top_1, top_0 * top_1);
                  } else if (opcode == 0x16) {
                        System.out.printf("CALL %x", eip + 1 + x);
                  } else if (opcode == 0x17) {
                        System.out.printf("JE %x==%x=%x ", top_0, top_1, top_0 == top_1);
                  } else if (opcode == 0x18) {
                        System.out.printf("JNE %x!=%x=%x ", top_0, top_1, top_0 != top_1);
                  } else if (opcode == 0x19) {
                        System.out.printf("-1");
                  } else if (opcode == 0x1A) {
                        long target = mem_read_byte(backend, ss + top_0 + top_1);
                        System.out.printf("INDEX sp-=4 top_0=*(%x+%x)=%x ", top_0, top_1, target);
                  } else if (opcode == 0x1B) {
                        long target = mem_read_dword(backend, ss + sp - 4L * x);
                        System.out.printf("DUP sp+=4 top_0=%x from:%x ", target, sp - 4L * x);
                  } else if (opcode == 0x1C) {
                        System.out.printf("-1");
                  } else if (opcode == 0x1D) {
                        System.out.printf("LIT sp+=4 top_0=%x ", x);
                  } else if (opcode == 0x1E) {
                        System.out.printf("DEC top_0=%x-=1 ", top_0);
                  }
                  System.out.println();
                }
            }
      }, module.base + 19490, module.base + 0x1982C, null);
    }

    public long mem_read_byte(Backend backend, long address) {
      return mem_read_number(backend, address, 1);
    }

    public long mem_read_word(Backend backend, long address) {
      return mem_read_number(backend, address, 2);
    }

    public long mem_read_dword(Backend backend, long address) {
      return mem_read_number(backend, address, 4);
    }

    public long mem_read_number(Backend backend, long address, long size) {
      byte[] mem = backend.mem_read(address, size);
      long res = 0;
      for (int i = 0; i < size; i++) {
            res |= (long) (mem & 0xff) << (8 * i);
      }
      return res;
    }
}
```

##附录2、伪码分析结果

```asm
# 吾爱破解 2025 春节解题领红包之六 - 提示
#
# 王小明在网上找到一份通用固件反编译工具,
#   但它只能反编译前 0x200 字节。
#
# 以下为反编译的结果:

lb_C000: CALL   $+0x7f   # lb_C081
lb_C002: HALT

sub_c003{
//top_0=0x2000(参数三) top_1=0x1005(参数二) top_2=0x5,b,11,17(参数1)
lb_C003: LIT      0x00 //res(局部变量)
lb_C004: LIT      0x00 //计数(局部变量)
//while(计数!=参数1){
lb_C005: DUP //复制计数(局部变量)
lb_C006: DUP      0x06 //复制参数1 循环多少次 C056 sp:802C LIT sp+=4 top_0=5复制这两个是为了接下来判断
lb_C007: JE       $+0x14   # lb_C01D //跳出循环
}
lb_C009: SWP //交换top_01
// top_0=res top_1=计数
lb_C00A: LIT      0x24
lb_C00C: MUL //res*=24
取出base36值{
lb_C00D: DUP      0x03 //参数三 0x2000
lb_C00E: DUP      0x05 //复制参数二 0x1005
lb_C00F: DUP      0x03 //复制计数 0x0
lb_C010: INDEX //根据flag指针 获取(char)*(flag+指针)字符
lb_C011: INDEX //根据字符获取base36表的值 char_code+0x2000
}
//判断是否为0 , 0-9 A-Z不为0{
lb_C012: DUP //复制top_0
lb_C013: LIT      0x00 //如果索引结果等于0 退出
lb_C014: JE       $+0x25   # lb_C03B 走第二个ret
}
lb_C016: ADD //res+=当前base36值
lb_C017: DEC //res-=1
lb_C018: SWP //交换两个状态量 res和计数
//top_0=计数 top_1=res
lb_C019: LIT      0x01
lb_C01A: ADD //计数+=1
lb_C01B: JMP      $-0x18   # lb_C005 //向上跳转
//循环体结束
lb_C01D: DROP //res置顶
//top_0=res 8040
错误路径{
lb_C01E: DUP //复制res右移19位,如果为0跳转
lb_C01F: SHR      0x19
lb_C021: LIT      0x00 //入栈0用作判断
lb_C022: JE       $+0x1c   # lb_C040 //走第三个ret
}
lb_C024: LIT      0x01

lb_C025: ADD //res+=1
               
{lb_C026: DUP      0x05 //复制uid计算值低位byte 于lb_C055: LOBYTE 取低位byte 9d,df,94,9a
//push 0x13541 固定值
{{
lb_C027: LIT      0x35
lb_C029: LIT      0xda
lb_C02B: SHR      0x03
lb_C02C: SHR      0x04
lb_C02D: SHL      0x02
lb_C02E: SHL      0x06
lb_C02F: OR
}
lb_C030: SHL      0x02
lb_C031: SHL      0x06
lb_C032: LIT      0x41
lb_C034: OR
}
lb_C035: MUL //相乘
}
//top_0=计算数 top_1=res
lb_C036: DUP      0x04 //复制flag指针lb_C059: ADD //相加0x10005
lb_C037: LOBYTE //取低位,实际上是05,0b,11,17
lb_C038: ADD

//top_0=计算数
lb_C039: MOD //res mod 计算数must is 0
lb_C03A: RET      0x04

//以下两个ret都不能让res=0
lb_C03B: OR
lb_C03C: LIT      0x01
lb_C03D: OR
lb_C03E: OR
lb_C03F: RET      0x04
//top_0=res
lb_C040: LIT      0x01
lb_C041: OR
lb_C042: RET      0x04
}

sub_c043{
lb_C043: DUP      0x02 //复制0x1000
lb_C044: CALL   $+0x43   # lb_C089   //判断"flag{}"格式 ,符合返回0 0x8024
lb_C046: LIT      0x05
lb_C047: JMP      $+0x0b   # lb_C054

//此时top_0 是计数值 , top_1是校验结果
lb_C049: SWP //交换top01 将最终校验结果拿到栈顶

判断连接符,若正确结果不变{
//取出flag
lb_C04A: DUP      0x04 //复制0x1000
lb_C04B: DUP      0x02 //复制计数值
lb_C04C: INDEX

lb_C04D: LIT      0x2d
lb_C04F: NEQ //判断是不是'-'
lb_C050: OR //与最终结果or result or 0,其实就是不变

lb_C051: SWP //将计数值拿到栈顶
lb_C052: LIT      0x01
lb_C053: ADD //计数值+=1
}

//top_0=计数值,top_1=最终校验值
lb_C054: DUP      0x05 //复制根据uid生成的值 9a94df 注:lb_C064后边改变了这个值
lb_C055: LOBYTE //取低位byte df,94,9a,00

lb_C056: LIT      0x05 //下面call的参数
lb_C057: DUP      0x06 //复制0x1000
lb_C058: DUP      0x03 //复制计数值
lb_C059: ADD //相加 拿到flag字符指针
lb_C05A: DUP      0x06 //复制0x2000
//top_0=0x2000 top_1=0x1005 top_2=0x5
lb_C05B: CALL   $-0x5a   # lb_C003

lb_C05D: DUP      0x02 //复制校验格式返回值lb_C044: CALL0x8024 -》 复制最终结果
lb_C05E: OR //本轮call结果or
lb_C05F: SWP      0x02 //将最终结果存回0x8024
lb_C060: DROP

lb_C061: DUP      0x05 //复制lb_C082: CALL   $+0x32   # lb_C0B6 //根据uid计算一个值9a94df9d 0x8014
lb_C062: SHR      0x08 //右移八位
lb_C064: SWP      0x06 //结果存回0x8014
lb_C065: DROP

//top_0=计数值5,b,11,17
lb_C066: LIT      0x05
lb_C067: ADD //eip:C046 sp:8024 LIT sp+=4 top_0=5 //计数值+=5
//循环判断条件
lb_C068: DUP //复制栈顶
lb_C069: LIT      0x1c
lb_C06B: JNE      $-0x24   # lb_C049 //不相等向上跳转1c!=a 循环直到指针到28

//此时top_0 是计数值 , top_1是最终校验结果 于 8024

lb_C06D: DROP //删除计数值
//top_0=最终结果 must is 0
lb_C06E: DEC
//结果-=1
//push c15303fb
lb_C06F: LIT      0xfb
lb_C071: LIT      0x53
lb_C073: LIT      0xc1
lb_C075: SHL      0x05
lb_C076: SHL      0x03
lb_C077: OR
lb_C078: SHL      0x02
lb_C079: SHL      0x06
lb_C07A: LIT      0x03
lb_C07B: OR
lb_C07C: SHL      0x04
lb_C07D: SHL      0x04
lb_C07E: OR

lb_C07F: XOR
lb_C080: RET      0x03
//程序结束
top_0必须是0xffff ffff
}
//main
sub_c081{
lb_C081: DUP      0x03
lb_C082: CALL   $+0x32   # lb_C0B6 //根据uid计算一个值9a94df9d
lb_C084: DUP      0x03 //复制0x1000
lb_C085: DUP      0x03 //复制0x2000
lb_C086: CALL   $-0x45   # lb_C043
lb_C088: HALT//程序结束
}


lb_C089: LIT      0x00
lb_C08A: LIT      0x66
lb_C08C: DUP      0x03
lb_C08D: LIT      0x00
lb_C08E: INDEX
lb_C08F: XOR
lb_C090: OR
lb_C091: LIT      0x6c
lb_C093: DUP      0x03
lb_C094: LIT      0x01
lb_C095: INDEX
lb_C096: XOR
lb_C097: OR
lb_C098: LIT      0x61
lb_C09A: DUP      0x03
lb_C09B: LIT      0x02
lb_C09C: INDEX
lb_C09D: XOR
lb_C09E: OR
lb_C09F: LIT      0x67
lb_C0A1: DUP      0x03
lb_C0A2: LIT      0x03
lb_C0A3: INDEX
lb_C0A4: XOR
lb_C0A5: OR
lb_C0A6: LIT      0x7b
lb_C0A8: DUP      0x03
lb_C0A9: LIT      0x04
lb_C0AA: INDEX
lb_C0AB: XOR
lb_C0AC: OR
lb_C0AD: LIT      0x7d
lb_C0AF: DUP      0x03
lb_C0B0: LIT      0x1c
lb_C0B2: INDEX
lb_C0B3: XOR
lb_C0B4: OR
lb_C0B5: RET      0x01


//push 80808080 {
lb_C0B6: LIT      0x01
lb_C0B7: SHL      0x03
lb_C0B8: SHL      0x04
lb_C0B9: DUP
lb_C0BA: SHL      0x10
lb_C0BC: OR
lb_C0BD: DUP
lb_C0BE: SHL      0x05
lb_C0BF: SHL      0x03
lb_C0C0: OR
}
//push ffffffff 这个地址存放子函数返回值{
lb_C0C1: LIT      0x00
lb_C0C2: DEC
}
//push 0x32
lb_C0C3: LIT      0x32
lb_C0C5: CALL   $+0x61   # lb_C128

lb_C0C7: LIT      0x30
lb_C0C9: CALL   $+0x5d   # lb_C128

lb_C0CB: LIT      0x32
lb_C0CD: CALL   $+0x59   # lb_C128

lb_C0CF: LIT      0x35
lb_C0D1: CALL   $+0x55   # lb_C128

lb_C0D3: DUP      0x03 //复制uid
lb_C0D4: SHR      0x05
lb_C0D5: SHR      0x06
lb_C0D6: SHR      0x04
lb_C0D7: SHR      0x05
lb_C0D8: SHR      0x04 //top_item = 0 >>0x18
lb_C0D9: CALL   $+0x4d   # lb_C128

lb_C0DB: LIT      0x35
lb_C0DD: CALL   $+0x49   # lb_C128

lb_C0DF: LIT      0x32
lb_C0E1: CALL   $+0x45   # lb_C128

lb_C0E3: LIT      0x70
lb_C0E5: CALL   $+0x41   # lb_C128

lb_C0E7: LIT      0x6f
lb_C0E9: CALL   $+0x3d   # lb_C128

lb_C0EB: LIT      0x6a
lb_C0ED: CALL   $+0x39   # lb_C128

lb_C0EF: LIT      0x69
lb_C0F1: CALL   $+0x35   # lb_C128

lb_C0F3: LIT      0x65
lb_C0F5: CALL   $+0x31   # lb_C128

lb_C0F7: DUP      0x03 //复制uid
lb_C0F8: SHR      0x06
lb_C0F9: SHR      0x04
lb_C0FA: SHR      0x06 //top_item=b>>0x10
lb_C0FB: CALL   $+0x2b   # lb_C128

lb_C0FD: LIT      0x61
lb_C0FF: CALL   $+0x27   # lb_C128

lb_C101: LIT      0x66
lb_C103: CALL   $+0x23   # lb_C128

lb_C105: LIT      0x64
lb_C107: CALL   $+0x1f   # lb_C128

lb_C109: LIT      0x6d
lb_C10B: CALL   $+0x1b   # lb_C128

lb_C10D: DUP      0x03 //复制uid
lb_C10E: SHR      0x02
lb_C10F: SHR      0x06 //top_item=bec >>0x8
lb_C110: CALL   $+0x16   # lb_C128

lb_C112: LIT      0x32
lb_C114: CALL   $+0x12   # lb_C128
lb_C116: LIT      0x30
lb_C118: CALL   $+0x0e   # lb_C128
lb_C11A: LIT      0x32
lb_C11C: CALL   $+0x0a   # lb_C128
lb_C11E: LIT      0x35
lb_C120: CALL   $+0x06   # lb_C128
lb_C122: DUP      0x03
lb_C123: CALL   $+0x03   # lb_C128
lb_C125: NOT
lb_C126: OR
lb_C127: RET      0x01 //RET top_0=9a94df9d

sub_c128{
//push edb88320{ //poly
lb_C128: LIT      0x20
lb_C12A: LIT      0x83
lb_C12C: SHL      0x08
lb_C12E: OR
lb_C12F: LIT      0xb8
lb_C131: LIT      0xed
lb_C133: SHL      0x08
lb_C135: OR
lb_C136: SHL      0x10
lb_C138: OR
}
//push 0x07 i{
lb_C139: LIT      0x04
lb_C13A: LIT      0x03
lb_C13B: ADD
}
lb_C13C: DUP      0x04 //获取参数1,sp,ffffffff
lb_C13D: DUP      0x04 //获取参数2, 32
lb_C13E: LOBYTE //30
lb_C13F: XOR //res(局部变量) e52a41c2 8034
//循环7次{{
lb_C140: DUP //res(局部变量)
lb_C141: LIT      0x01
lb_C142: AND // &取低位
lb_C143: NEG //加负号

lb_C144: DUP      0x03//复制poly
lb_C145: AND // &
}
lb_C146: SWP //交换top01,结果存回res(局部变量)
lb_C147: SHR      //0x01
lb_C148: XOR //

//取i,循环减一
lb_C149: DUP      0x01
lb_C14A: DEC
lb_C14B: SWP      0x02
//i不等于0向上跳转
lb_C14C: LIT      0x00
lb_C14D: JNE      $-0x0f   # lb_C140
}
lb_C14F: SWP      0x02//结果存回poly
lb_C150: DROP   0x02
lb_C151: RET      0x02
}
...
```

##附录3、flag爆破代码

```cpp
#include <iostream>
#include <vector>
using namespace std;

unsigned char chart[] =
{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,...//0x2000的256字节
};

vector<unsigned char>dict;

unsigned int data1 = { 0x05,0x0b,0x11,0x17 };
unsigned int data2 = 0x9a94df9d; //根据uid生成的数,动调从trace中拿,因为是固定的,没另外写程序生成

void crake(unsigned int arg1, unsigned int arg2) {
      //for(auto c:dict)cout<<c<<endl;
      for (auto a : dict) {
                for (auto b : dict) {
                        for (auto c : dict) {
                              for (auto d : dict) {
                                        for (auto e : dict) {
                                                //if (a == 'B' && b == 'Y' && c == 'T' && d == 'J' && e == 'C')
                                                //      cout << "pause" << endl;
                                                int res = 0;
                                                res *= 0x24;
                                                res += chart - 1;
                                                res *= 0x24;
                                                res += chart - 1;
                                                res *= 0x24;
                                                res += chart - 1;
                                                res *= 0x24;
                                                res += chart - 1;
                                                res *= 0x24;
                                                res += chart - 1;

                                                if (res >> 0x19 == 0)continue;

                                                res += 1;

                                                int calcRes = arg2 * 0x13541 + arg1;
                                                if (res % calcRes == 0) {
                                                      cout << a << b << c << d << e;
                                                      return;
                                                }
                                        }
                              }
                        }
                }
      }
}

int main() {
      for (int i = '0'; i <= '9'; i++)dict.push_back(i);
      for (int i = 'A'; i <= 'Z'; i++)dict.push_back(i);
      cout << "flag{";
      for (int i = 0; i < 4; i++) {
                crake(data1, (data2 >> (8 * i)) & 0xff);
                if (i != 3)cout << '-';
      }
      cout << "}";
}
```

##附录1s、flag计算代码
```cpp
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

const int N = 36 * 36 * 36 * 36 * 36;

unsigned char chart[] =
{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x0A,0x10, 0x15, 0x21, 0x13, 0x0C, 0x04, 0x11, 0x1C, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x0F, 0x20, 0x0D, 0x02,0x23, 0x06, 0x1B, 0x14, 0x0E, 0x01, 0x16, 0x19, 0x08, 0x12,0x1F, 0x17, 0x24, 0x0B, 0x1E, 0x07, 0x1A, 0x05, 0x18, 0x1D,0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};

vector< unsigned char>dict;
vector<pair< unsignedint, unsigned char>> reChart;

void sub_c128(unsigned int& arg1, unsigned int arg2) {
      unsigned int poly = 0xedb88320;
      unsigned int res = (arg2 & 0xff) ^ arg1;
      for (int i = 0; i < 8; i++) {
                res = -(int)(res & 1) & poly ^ (res >> 1);
      }
      arg1 = res;
}

unsigned int sub_c0b6(unsigned int uid) {
      unsigned int res = 0xffffffff;
      sub_c128(res, 0x32), sub_c128(res, 0x30), sub_c128(res, 0x32), sub_c128(res, 0x35);
      sub_c128(res, uid >> 0x18);
      sub_c128(res, 0x35), sub_c128(res, 0x32), sub_c128(res, 0x70), sub_c128(res, 0x6f), sub_c128(res, 0x6a), sub_c128(res, 0x69), sub_c128(res, 0x65);
      sub_c128(res, uid >> 0x10);
      sub_c128(res, 0x61), sub_c128(res, 0x66), sub_c128(res, 0x64), sub_c128(res, 0x6d);
      sub_c128(res, uid >> 0x8);
      sub_c128(res, 0x32), sub_c128(res, 0x30), sub_c128(res, 0x32), sub_c128(res, 0x35);
      sub_c128(res, uid);
      res = ~res | 0x80808080;
      return res;
}

void crake(unsigned int arg1, unsigned int arg2) {
      unsigned int calcRes = arg2 * 0x13541 + arg1;
      string res;
      for (unsigned int n = N / calcRes * calcRes - 1; n; n /= 36)res.push_back(reChart.second);
      reverse(res.begin(), res.end()), cout << res;
      return;
}

int main() {
      unsigned int uid; cin >> uid;
      unsigned int calcRes_uid = sub_c0b6(uid);
      for (int i = '0'; i <= '9'; i++)dict.push_back(i);
      for (int i = 'A'; i <= 'Z'; i++)dict.push_back(i);
      for (auto i : dict)reChart.push_back(make_pair(chart - 1, i));
      sort(reChart.begin(), reChart.end());
      cout << "flag{";
      for (int i = 0; i < 4; i++) {
                crake(5 + i * 6, (calcRes_uid >> (8 * i)) & 0xff);
                if (i != 3)cout << '-';
      }
      cout << "}";
}
```

爱飞的猫 发表于 2025-3-1 08:11

附录中的代码 `sub_c0b6` 就是 `crc32`…

```c
inline static uint32_t crc32b(const unsigned char *message, const size_t n)
{
    uint32_t crc = 0xFFFFFFFF;
    for (size_t i = 0; i < n; i++)
    {
      crc = crc ^ (uint32_t)(uint8_t)*message++;
      for (int j = 7; j >= 0; j--)
      { // Do eight times.
            const uint32_t mask = -(crc & 1);
            crc = (crc >> 1) ^ (0xEDB88320 & mask);
      }
    }
    return ~crc;
}

uint32_t crc32_uid(uint32_t value)
{
    //               0               1
    //               0123456789abcdef012345
    char buffer[] = "2025_52pojie_afdm_2025";
    ((uint8_t*)buffer) = (value >> 24) & 0xFF;
    ((uint8_t*)buffer) = (value >> 16) & 0xFF;
    ((uint8_t*)buffer) = (value >> 8) & 0xFF;
    ((uint8_t*)buffer) = value & 0xFF;

    return crc32b(buffer, sizeof(buffer)) | 0x80808080;
}
```

答案一定有解,因为我有暴力跑过都能得到解(uid 每一段的取值范围都是 `0x80-0xFF`,`offset` 取最大值 23):

```c
void self_check()
{
    bool ok = true;
    for (int i = 0x80; i <= 255; i++)
    {
      char temp_text = {0};
      auto solved_value = solve((uint8_t)i, 23);
      encode_base36(temp_text, solved_value);
      if (strlen(temp_text) != 5)
      {
            printf("failed to encode %d --> %s\n", i, temp_text);
            ok = false;
      }
    }
    assert(ok && "self check failed");
}
```

即便加入额外的取值范围,跑起来也很快…

ibilibili 发表于 2025-2-18 15:15

爱飞的猫 发表于 2025-2-18 07:02
> 根据uid生成的数,动调从trace中拿,因为是固定的,没另外写程序生成

这个其实是与固定内容一起进 ...

重新看了一下看懂了,
这是优化后的反推flag代码,

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

unsigned char chart[] =
{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x0A,
0x10, 0x15, 0x21, 0x13, 0x0C, 0x04, 0x11, 0x1C, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x0F, 0x20, 0x0D, 0x02,
0x23, 0x06, 0x1B, 0x14, 0x0E, 0x01, 0x16, 0x19, 0x08, 0x12,
0x1F, 0x17, 0x24, 0x0B, 0x1E, 0x07, 0x1A, 0x05, 0x18, 0x1D,
0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};

typedef pair<unsigned int, unsigned int> PII;

vector<PII> reChart;

vector<unsigned char>dict;

unsigned int data1 = { 0x05,0x0b,0x11,0x17 };
unsigned int data2 = 0x9a94df9d;

void crake(unsigned int arg1, unsigned int arg2) {
        unsigned int calcRes = arg2 * 0x13541 + arg1;
        unsigned int n = (36 * 36 * 36 * 36 * 36 / calcRes) * calcRes - 1;

        vector<char> tmp;
        while (n) {
                tmp.push_back((char)reChart.second);
                n /= 36;
        }
        reverse(tmp.begin(), tmp.end());
        for (auto c : tmp) cout << c;
        return;
}

int main() {
        for (int i = '0'; i <= '9'; i++)dict.push_back(i);
        for (int i = 'A'; i <= 'Z'; i++)dict.push_back(i);

        for (auto i : dict)reChart.push_back(make_pair<unsigned int, unsigned int>(chart - 1, i));
        sort(reChart.begin(), reChart.end());

        cout << "flag{";
        for (int i = 0; i < 4; i++) {
                crake(data1, (data2 >> (8 * i)) & 0xff);
                if (i != 3)cout << '-';
        }
        cout << "}";
}

ibilibili 发表于 2025-2-17 16:22

勘误:文中0x2最后一部分,五个块 应该为 四个块。

ibilibili 发表于 2025-2-17 21:35

勘误:文中0x2第二段unidbg hook while(1) 应为 hook switch(opcode)。

爱飞的猫 发表于 2025-2-18 07:02

> 根据uid生成的数,动调从trace中拿,因为是固定的,没另外写程序生成

这个其实是与固定内容一起进行 CRC32 做了个简单哈希,完成后 `| 0x80808080`。

> 根据sub_c003写代码进行爆破

可以不爆破直接算,能秒出答案。可以看看其它 WP 关于这部分的说明 (base36 编码)。

ibilibili 发表于 2025-2-18 07:37

本帖最后由 ibilibili 于 2025-2-18 07:48 编辑

爱飞的猫 发表于 2025-2-18 07:02
> 根据uid生成的数,动调从trace中拿,因为是固定的,没另外写程序生成

这个其实是与固定内容一起进 ...
> crc32
好的,学习了。
> 暴破
采用暴破的主要原因是暴破的程序比较朴素,好写,写错了调试时中间值可以跟trace作比较,暴破需要算4*36^5=10^8次,也是秒级的。

Hmily 发表于 2025-2-19 15:57

ibilibili 发表于 2025-2-17 21:35
勘误:文中0x2第二段unidbg hook while(1) 应为 hook switch(opcode)。

直接编著主题修改就行了,我帮你修改了上面的那个,并把图片上传本地,防止图床失效了,你自己修改下其他的吧。

ibilibili 发表于 2025-2-19 19:51

Hmily 发表于 2025-2-19 15:57
直接编著主题修改就行了,我帮你修改了上面的那个,并把图片上传本地,防止图床失效了,你自己修改下其他 ...

好的谢谢H大。

fightingBird 发表于 2025-2-25 15:50

学习VMP这种,请问怎么入手?VMP感觉太陌生了。。。。
页: [1] 2
查看完整版本: 52pj-25.6W&A题分析