re OORM
肝了一下午,拿了个一血,开心hh
如果patch或者插桩之类的方法,代替模拟执行的话,可能速度会更快,不过这题规模还能接受,就用模拟执行跑了,这个方便一点,切个片段出来就能直接执行
需要注意的是避免用unicorn的单步,指定until会快很多;然后unicorn不知道为什么emu_start多次后会报一个OS Error什么访问-1指针异常啥的,得隔一定次数重新new一个Uc
main
main:
输入100个hex
然后每一个hex,转int,接着按位拆分,存进QWORD[400]数组unk_214EE0,unk_214260中,低位在数组低地址
接着轮询800个函数,总共需要执行800次
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | times = 0; do { for ( i = 0LL; i != 400; ++i ) { keyA = keyAs_2135E0[i]; if ( runA ) { ++times; funcs_A_211CA0[i](keyA, input_in_bits_A_214EE0[i]); keyAs_2135E0[i] = 0LL; } keyB = keyBs_212960[i]; if ( keyB ) { ++times; funcs_B_211020[i](keyB, input_in_bits_B_214260[i]); keyBs_212960[i] = 0LL; } } } while ( times <= 799 ); |
最后需要dword_212940 == 40
800个函数分析
那800个函数的定义:void (__fastcall *)(__int64 key, __int64 input_bit)
keyAs_2135E0、keyBs_212960初始化地方sub_6160,好像是什么PHT里引用了这个函数
keyAs、keyBs分别初始了20个非0元素,dump出这两个数组
funcs_A[0](key, input_bit)大致逻辑,根据keyAs[0]和input_bit[0],给keyAs[20]赋值:
1 2 3 4 5 6 | void funcs_A_0(__int64 key, __int64 input_bit) { x = input_bit | (key<< 1); y = hashA0(x); // 48 89 3D B7 C5 mov cs:keyAs_2135E0+0A0h, rdi keyAs[20] = y;} |
funcs_A[1](key, input_bit)大致逻辑,根据keyAs[1]和input_bit[1],给keyAs[21]赋值:
1 2 3 4 5 6 | void funcs_A_1(__int64 key, __int64 input_bit) { x = input_bit | (key<< 1); y = hashA1(x); // 48 89 3D FF BA mov cs:keyAs_2135E0+0A8h, rdi keyAs[21] = y;} |
funcs_A[399](key, input_bit)大致逻辑,根据keyAs[399]和input_bit[399],给dwCheck_212940赋值:
1 2 3 4 5 6 | void funcs_A_399(__int64 key, __int64 input_bit) { x = input_bit | (key<< 1); y = hashA399(x); if ( y == 21961 || y == 27098 ) ++dwCheck_212940;} |
fA_0~fA_19 生成 fA_20~fA_39 的key
然后fA_20~fA_39 生成 fA_40~fA59 的key
直到fA_380~fA_399 是检验
整体大概是这么个顺序:
fA_0 ? fA_20 … fA_360,fA_380校验
fA_1 ? fA_21 … fA_361,fA_381校验
…
fB_0 ? fB_1 … fB_18,fB_19校验
fB_20 ? fB_21 … fB_38,fB_39校验
…
相当于输入是20*20的值只能为0、1的矩阵
fA系列函数是做列校验
fB系列函数是做行校验
模拟执行爆破+剪枝
使用fA_0、fA_20、...、fA_380爆破第一列,有很多结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 | [0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1] 32766[0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1] 32766[0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1] 32766[0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1] 32766[0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1] 32766[0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1] 32766[0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1] 32766[0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0] 3090...[1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1] 32766[1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0] 3090[1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0] 3090... |
模拟执行fA爆破列的脚本(使用fB爆破行的脚本也差不多):
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 | from capstone import *from unicorn import *from unicorn.x86_const import *from elftools.elf.elffile import ELFFilekeyAs = [9644, 31494, 15772, ..., 0, 0, 0, ...]f = open('main', 'rb')elff = ELFFile(f)def rva_to_offset(elff, rva): for segment in elff.iter_sections(): if rva >= segment['p_vaddr'] and rva < segment['p_vaddr'] + segment['p_memsz']: return rva - segment['p_vaddr'] + segment['p_offset'] raise ValueError('RVA not within any segment') def read_elf_content_by_rva(elff, rva, size): for segment in elff.iter_segments(): # 检查RVA是否在当前段的范围内 if rva >= segment['p_vaddr'] and rva < segment['p_vaddr'] + segment['p_memsz']: foa = rva - segment['p_vaddr'] content = segment.data()[foa : foa + size] return content# 收集函数的地址funcs_A = [int.from_bytes(read_elf_content_by_rva(elff, 0x211CA0 + i * 8, 8), 'little') for i in range(400)]funcs_A.append(0x106430)endAs = [ 28866, 31618, 34242, ...]tyAs = [ 39, 39, 39, ..., (35, 32766, 3090), (35, 6485, 4159), (35, 14535, 24449), ...]def sim2(uc: Uc, i, j, key, input_bit): # func arg idx = i + j * 20 uc.reg_write(UC_X86_REG_RDI, key) uc.reg_write(UC_X86_REG_RSI, input_bit) uc.emu_start(funcs_A[idx], endAs[idx], 0, 0) if j != 19: return uc.reg_read(tyAs[idx]), 0, 0 else: return uc.reg_read(tyAs[idx][0]), tyAs[idx][1], tyAs[idx][2]uc = Uc(UC_ARCH_X86, UC_MODE_64)# codefor segment in elff.iter_segments(): if segment['p_vaddr'] == 0x6000: uc.mem_map(segment['p_vaddr'], segment['p_memsz']//0x1000*0x1000 + 0x1000) uc.mem_write(segment['p_vaddr'], segment.data())# 执行funcA[i + j * 20](key, 0) 和 funcA[i + j * 20](key, 1)def dfsA(i, j, key) -> bool: global path, uc if j == 3: # unicron的bug,得隔一定次数重新创建一个Uc uc = Uc(UC_ARCH_X86, UC_MODE_64) for segment in elff.iter_segments(): if segment['p_vaddr'] == 0x6000: uc.mem_map(segment['p_vaddr'], segment['p_memsz']//0x1000*0x1000 + 0x1000) uc.mem_write(segment['p_vaddr'], segment.data()) if j == 6: print(path) if j == 19: new_key, t0, t1 = sim2(uc, i, j, key, 0) if new_key == t0 or new_key == t1: print('path', path + [0], new_key) new_key, t0, t1 = sim2(uc, i, j, key, 1) if new_key == t0 or new_key == t1: print('path', path + [1], new_key) else: new_key, _, _ = sim2(uc, i, j, key, 0) path.append(0) dfsA(i, j + 1, new_key) path.pop() new_key, _, _ = sim2(uc, i, j, key, 1) path.append(1) dfsA(i, j + 1, new_key) path.pop()path = []dfsA(0, 0, keyAs[0])f.close() |
爆破列校验发现有多解,检查列的所有解,如果某一位在该列的所有解中全为0或1可以确认该位为0或1,一次列验证确认结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | _, _, _, _, _, _, _, _, _, _, _, _, _, 1, _, 1, 1, _, _, __, 1, _, 1, 1, _, _, _, _, _, 1, _, _, 1, _, 1, 1, _, _, __, _, _, 1, _, _, 1, 1, _, _, 1, _, _, 0, _, 0, 1, _, _, __, _, _, 1, _, 1, _, 1, _, _, 1, _, _, 1, _, 1, 0, _, _, __, 1, _, _, 1, 1, _, _, 1, _, 1, _, _, 1, _, 1, 1, _, _, 1_, _, 1, _, 1, 1, _, _, _, _, 1, _, 1, 0, _, 1, 0, _, _, __, _, _, 1, 1, 1, _, _, _, _, 1, _, 1, 1, _, 0, 1, _, _, __, 1, _, 1, 1, 1, _, 1, _, _, 1, _, 1, 1, 1, 1, 1, _, 1, _1, 1, _, 1, 1, 1, 1, 1, _, _, 1, _, _, 1, 1, 1, 1, _, _, 1_, 1, 1, _, 1, 1, 1, 1, _, _, 1, _, _, 1, 1, 1, 1, _, _, 1_, 1, 1, _, _, 1, 1, 1, _, 1, _, 1, _, 1, 1, 1, 1, _, _, 1_, _, 1, 1, _, _, 1, _, _, 1, _, 1, _, 1, _, 1, 1, _, _, 1_, _, 1, 1, 1, _, 1, _, _, 1, 1, 1, _, 0, _, 0, 1, _, 1, 1_, 1, 1, 1, 1, _, 1, _, _, 1, _, _, _, 1, _, 1, 0, _, 1, 1_, 1, 1, 1, 1, _, 1, _, _, 1, _, _, _, 1, _, 1, 1, _, _, 11, 1, 1, 1, _, _, _, _, _, _, 1, _, _, 1, _, 1, 1, _, _, 1_, 1, 1, 1, _, _, _, _, 1, _, 1, _, _, 1, _, 0, 1, _, _, 1_, 1, 1, _, 1, _, _, 1, 1, _, _, _, _, 1, 1, 1, 1, _, _, 1_, 1, _, _, 1, _, _, _, _, _, _, _, _, 1, _, 1, 1, _, _, __, _, _, _, _, _, _, _, _, _, _, _, _, 1, _, 1, 1, _, _, _ |
然后用这个确认的结果,对爆破做剪枝加速,继续跑一次行验证、一次列验证、一次行验证(每次跑完验证都按前面的思路更新这个确认的结果),就出来完整结果了