-
-
[原创] Blue Water CTF 2024 re OORM wp 模拟执行爆破+剪枝
-
发表于: 3天前 1104
-
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 ELFFile keyAs = [ 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) # code 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()) # 执行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 , _, _, 1 1 , 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 , _, _, _ |
然后用这个确认的结果,对爆破做剪枝加速,继续跑一次行验证、一次列验证、一次行验证(每次跑完验证都按前面的思路更新这个确认的结果),就出来完整结果了