Blue Water CTF 2024 re OORM wp 模拟执行爆破+剪枝

科技   2024-11-02 17:59   上海  




re OORM


如果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次。


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]赋值:


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]赋值:


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赋值:


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爆破第一列,有很多结果:


[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爆破行的脚本也差不多):


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, _, 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, _, _, _


然后用这个确认的结果,对爆破做剪枝加速,继续跑一次行验证、一次列验证、一次行验证(每次跑完验证都按前面的思路更新这个确认的结果),就出来完整结果了。




看雪ID:wx_御史神风

https://bbs.kanxue.com/user-home-1000123.htm

*本文为看雪论坛优秀文章,由 wx_御史神风 原创,转载请注明来自看雪社区


# 往期推荐

1、PWN入门-SROP拜师

2、一种apc注入型的Gamarue病毒的变种

3、野蛮fuzz:提升性能

4、关于安卓注入几种方式的讨论,开源注入模块实现

5、2024年KCTF水泊梁山-反混淆





球分享

球点赞

球在看



点击阅读原文查看更多

看雪学苑
致力于移动与安全研究的开发者社区,看雪学院(kanxue.com)官方微信公众帐号。
 最新文章