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 << "}";
}
```
附录中的代码 `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");
}
```
即便加入额外的取值范围,跑起来也很快…
爱飞的猫 发表于 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 << "}";
} 勘误:文中0x2最后一部分,五个块 应该为 四个块。 勘误:文中0x2第二段unidbg hook while(1) 应为 hook switch(opcode)。 > 根据uid生成的数,动调从trace中拿,因为是固定的,没另外写程序生成
这个其实是与固定内容一起进行 CRC32 做了个简单哈希,完成后 `| 0x80808080`。
> 根据sub_c003写代码进行爆破
可以不爆破直接算,能秒出答案。可以看看其它 WP 关于这部分的说明 (base36 编码)。 本帖最后由 ibilibili 于 2025-2-18 07:48 编辑
爱飞的猫 发表于 2025-2-18 07:02
> 根据uid生成的数,动调从trace中拿,因为是固定的,没另外写程序生成
这个其实是与固定内容一起进 ...
> crc32
好的,学习了。
> 暴破
采用暴破的主要原因是暴破的程序比较朴素,好写,写错了调试时中间值可以跟trace作比较,暴破需要算4*36^5=10^8次,也是秒级的。 ibilibili 发表于 2025-2-17 21:35
勘误:文中0x2第二段unidbg hook while(1) 应为 hook switch(opcode)。
直接编著主题修改就行了,我帮你修改了上面的那个,并把图片上传本地,防止图床失效了,你自己修改下其他的吧。 Hmily 发表于 2025-2-19 15:57
直接编著主题修改就行了,我帮你修改了上面的那个,并把图片上传本地,防止图床失效了,你自己修改下其他 ...
好的谢谢H大。 学习VMP这种,请问怎么入手?VMP感觉太陌生了。。。。
页:
[1]
2