Contents

KCTF 2021秋季赛 第七题 声名远扬

Contents

文章发布至看雪《KCTF 2021秋季赛 第七题 声名远扬》

起点

  • 32位,无保护,ida搜索字符串没有看到错误输出,没有明显的函数处理过程
  • 发现Duilib写的,下载源码,编译lib,https://github.com/duilib/duilib,pct,sigmake制作签名文件,应用之~
  • 识别了500多个还可以了,阅读源码,跟踪按钮事件,CNotifyPump::NotifyPump->CNotifyPump::LoopDispatch,进而跟踪到按钮消息处理函数sub_89D2D0。
  • 分析出大致流程如下:
int __userpurge sub_89D2D0@<eax>(int a1@<ecx>, int a2@<ebx>, int a3@<edi>, int a4@<esi>, int a5)
{
  v13 = a1;
  result = a1;
  if ( *(_DWORD *)(a1 + 1384) == *(_DWORD *)(a5 + 136) )
  {
    //...
    v10 = sub_89E530(a1a, a2a);                    //编码函数
    //...
    sub_89D600(&v15, a2, a3, a4, v7, (int)v16);    //校验函数
    (*(void (__thiscall **)(_DWORD, char *))(**(_DWORD **)(v13 + 1388) + 44))(*(_DWORD *)(v13 + 1388), v16);    //结果提示
  }
  return result;
}

分析

  • 关键代码1(编码函数),一个简单的类似base64的编码,这里我想的是直接通过ida脚本去把表提出来。
  for ( i = 0; ; i += v11 )
  {
    v2 = get_len(a2);
    v11 = v2 - i;
    v27 = '@';
    Src = *(_BYTE *)sub_89E970(v30, (int)&v27);    //从编码表中替换
    v26 = 64;
    v32 = *(_BYTE *)sub_89E970(v30, (int)&v26);
    v25 = 64;
    v33 = *(_BYTE *)sub_89E970(v30, (int)&v25);
    v24 = 0x40;
    v34 = *(_BYTE *)sub_89E970(v30, (int)&v24);
    if ( !v11 )
      break;
    if ( v11 == 1 )
    {
      v23 = ((int)*(unsigned __int8 *)char_at(i) >> 2) & 0x3F;    //char_at是std::string.at
      Src = *(_BYTE *)sub_89E970(v30, (int)&v23);
      v22 = (16 * *(_BYTE *)char_at(i)) & 0x3F;
      v32 = *(_BYTE *)sub_89E970(v30, (int)&v22);
      v21 = 64;
      v33 = *(_BYTE *)sub_89E970(v30, (int)&v21);
      v20 = 64;
      v34 = *(_BYTE *)sub_89E970(v30, (int)&v20);
    }
    else if ( v11 == 2 )
    {
      v19 = ((int)*(unsigned __int8 *)char_at(i) >> 2) & 0x3F;
      Src = *(_BYTE *)sub_89E970(v30, (int)&v19);
      v3 = 16 * *(_BYTE *)char_at(i);
      v18 = (((int)*(unsigned __int8 *)char_at(i + 1) >> 4) | v3) & 0x3F;
      v32 = *(_BYTE *)sub_89E970(v30, (int)&v18);
      v17 = (4 * *(_BYTE *)char_at(i + 1)) & 0x3F;
      v33 = *(_BYTE *)sub_89E970(v30, (int)&v17);
      v16 = 64;
      v34 = *(_BYTE *)sub_89E970(v30, (int)&v16);
    }
    else
    {
      v15 = ((int)*(unsigned __int8 *)char_at(i) >> 2) & 0x3F;
      Src = *(_BYTE *)sub_89E970(v30, (int)&v15);
      v4 = (16 * *(_BYTE *)char_at(i)) & 0x3F;
      v14 = (((int)*(unsigned __int8 *)char_at(i + 1) >> 4) & 0x3F | v4) & 0x3F;
      v32 = *(_BYTE *)sub_89E970(v30, (int)&v14);
      v5 = 4 * *(_BYTE *)char_at(i + 1);
      v13 = (((int)*(unsigned __int8 *)char_at(i + 2) >> 6) | v5) & 0x3F;
      v33 = *(_BYTE *)sub_89E970(v30, (int)&v13);
      v12 = *(_BYTE *)char_at(i + 2) & 0x3F;
      v34 = *(_BYTE *)sub_89E970(v30, (int)&v12);
      v11 = 3;
    }
  • 写个调试脚本如下:
//大致流程是这样不过调试的时候连续执行step_over我再ida里一直没成功没时间看了有知道的兄弟请告知.
//因为本身循环次数不多通过条件断点输出和多次run脚本把表打了出来
import time
for i in range(64):
	edx = idc.get_reg_value("EBP")-0x39
	idc.patch_dbg_byte(edx,i)
	idc.set_reg_value(0x0089E5AF,'EIP')
	idaapi.step_over()
	idaapi.step_over()
	idaapi.step_over()
	idaapi.step_over()
	time.sleep(0.1)
	eax = idc.get_reg_value("EAX")
	code = idc.read_dbg_byte(eax)
	print(hex(code), end='')
//编码表[0x70,0x72,0x76,0x6f,0x39,0x43,0x48,0x53,0x4a,0x4f,0x63,0x50,0x49,0x62,0x36,0x78,0x52,0x56,0x55,0x58,0x51,0x7a,0x30,0x71,0x42,0x47,0x44,0x45,0x37,0x32,0x4c,0x4e,0x5a,0x64,0x75,0x61,0x65,0x66,0x59,0x54,0x35,0x4b,0x5f,0x38,0x2d,0x34,0x46,0x41,0x68,0x6c,0x69,0x6d,0x6a,0x6b,0x6e,0x67,0x74,0x31,0x79,0x4d,0x57,0x73,0x33,0x77,0x21]
  • 关键代码2(校验函数),0089D8A2:call fword ptr [ebp+var_C] 校验代码放在了x64里执行,所以直接上windbg
  • 看了看代码,一个简单的比较,bp 00895606 下断点,da rbp-30打印输出,直接拿最后的编码比较
  • GYldGg-iIoJlPX9hPXpjPqfdEY21B01TBTzeGqfKNR!!

求解

  • 写了个简单的z3求解
from z3 import *
table = [0x70,0x72,0x76,0x6f,0x39,0x43,0x48,0x53,0x4a,0x4f,0x63,0x50,0x49,0x62,0x36,0x78,0x52,0x56,0x55,0x58,0x51,0x7a,0x30,0x71,0x42,0x47,0x44,0x45,0x37,0x32,0x4c,0x4e,0x5a,0x64,0x75,0x61,0x65,0x66,0x59,0x54,0x35,0x4b,0x5f,0x38,0x2d,0x34,0x46,0x41,0x68,0x6c,0x69,0x6d,0x6a,0x6b,0x6e,0x67,0x74,0x31,0x79,0x4d,0x57,0x73,0x33,0x77,0x21]
A = Array('A', IntSort(), IntSort())
i = 0
for elem in table:
  A = Store(A, i, elem)
  i = i + 1
permute = lambda k:Select(A, BV2Int(k))
flag_len = 31
x = [BitVec('u%d'%i,8) for i in range(x_len)]
s = Solver()

i = 0
v = 0
xxx = []
while True:
    k = x_len - i
    if (k == 0):
        break
    if k == 1:
        pos = (x[i] >> 2) & 0x3f
        m0 = permute(pos)
        pos = (16 * x[i]) & 0x3f
        m1 = permute(pos)
        pos = BitVecVal(0x40, 8)
        m2 = permute(pos)
        m3 = permute(pos)
    elif k == 2:
        pos = (x[i] >> 2) & 0x3f
        m0 = permute(pos)
        pos = ((x[i+1] >> 4) | (16 * x[i])) & 0x3f
        m1 = permute(pos)
        pos = (4 * x[i+1]) & 0x3f
        m2 = permute(pos)    
        pos = BitVecVal(0x40, 8)
        m3 = permute(pos)
    else:
        pos = (x[i] >> 2) & 0x3f
        m0 = permute(pos)
        pos = ((x[i+1] >> 4) & 0x3f | (16 * x[i])) & 0x3f
        m1 = permute(pos)       
        pos = ((x[i+2] >> 6) | (4 * x[i+1])) & 0x3f
        m2 = permute(pos)
        pos = x[i+2] & 0x3f
        m3 = permute(pos)
        k = 3
    xxx += [m0,m1,m2,m3]
    i += k

cip = 'GYldGg-iIoJlPX9hPXpjPqfdEY21B01TBTzeGqfKNR!!'
j = 0
for t in xxx:
    s.add(t==ord(cip[j]))
    j+=1
res = s.check()
if res == z3.sat:
    print(s.model())
    model = s.model()
    flag = ''
    for i in range(0,flag_len):
        flag += chr(model[x[i]].as_long().real)
    print(flag)
else:
    print('oh,no')
  • 最后得到:flag{2021-10-04-yangyangbudeyi}