无扩散关联加密算法, python运行程序单字节爆破, patch汇编

运行

D:\TRY\holiday\recurrent\5_analgo\3>analgo
111111111111111111111111111111111111111111
wrong!

分析

为 go 语言程序, 显著的特征就是字符串由指针跟长度组成

// main.main
void __cdecl main_main()
{
  __int64 v0; // r14
  __int128 v1; // xmm15
  unsigned __int64 *v2; // rax
  __int64 v3; // rbx
  __int64 v4; // rdi
  _QWORD *v5; // rsi
  unsigned __int64 *_input; // rdx
  bool v7; // cl
  __int64 v8; // rax
  unsigned __int64 p_input; // rbx
  __int64 v10; // rax
  __int64 __input; // rax
  unsigned __int64 v12; // rcx
  __int64 v14; // [rsp+10h] [rbp-168h]
  __int64 v15; // [rsp+10h] [rbp-168h]
  __int64 v16; // [rsp+18h] [rbp-160h]
  __int64 v17; // [rsp+18h] [rbp-160h]
  __int128 v18; // [rsp+20h] [rbp-158h]
  char v19; // [rsp+90h] [rbp-E8h] BYREF
  __int64 cmpdata0; // [rsp+A5h] [rbp-D3h]
  __int64 cmpdata1; // [rsp+ADh] [rbp-CBh]
  __int64 cmpdata2; // [rsp+B5h] [rbp-C3h]
  __int64 cmpdata3; // [rsp+BDh] [rbp-BBh]
  __int64 cmpdata4; // [rsp+C5h] [rbp-B3h]
  __int16 cmpdata5; // [rsp+CDh] [rbp-ABh]
  _BYTE v26[105]; // [rsp+CFh] [rbp-A9h] BYREF
  unsigned __int64 *input; // [rsp+138h] [rbp-40h]
  RTYPE *v28; // [rsp+140h] [rbp-38h]
  char **v29; // [rsp+148h] [rbp-30h]
  RTYPE *v30; // [rsp+150h] [rbp-28h]
  char **v31; // [rsp+158h] [rbp-20h]
  _QWORD v32[2]; // [rsp+160h] [rbp-18h] BYREF

  while ( (unsigned __int64)&v19 <= *(_QWORD *)(v0 + 16) )
    runtime_morestack_noctxt();
  v14 = runtime_newobject();
  input = v2;
  *v2 = 0LL;
  v32[0] = &RTYPE__ptr_string;
  v32[1] = v2;
  v3 = qword_368E60;
  v4 = 2LL;
  v5 = v32;
  *(_QWORD *)&v18 = fmt_Fscanf(v14, v16);
  if ( v3 )
  {
    _input = input;
    v7 = 0;
  }
  else
  {
    _input = input;
    if ( input[1] == 42 )
    {
      regexp_MustCompile(v15, v17);
      v17 = *((_QWORD *)&v1 + 1);
      v18 = v1;
      v4 = 0LL;
      v5 = 0LL;
      v15 = regexp__ptr_Regexp_doExecute();
      v7 = v8 != 0;
      _input = input;
    }
    else
    {
      v7 = 0;
    }
  }
  if ( !v7 )
    goto LABEL_12;
  p_input = *_input;
  runtime_stringtoslicebyte(v4, (__int64)v5, (__int64)_input, _input[1]);// 切片函数
  *(_QWORD *)&v26[97] = v10;
  *(_QWORD *)v26 = 0LL;
  __input = ((__int64 (*)(void))loc_23DF6B)();
  qmemcpy(v26, ">>>>><<>>>><[[[.]+]<[-.+.].+.]].][.][[.+.]>[.<-.]<.][.][[.-,.<>>>>>>><<<<.].][[.[.]<.][.].-]].[.]", 97);// 将这个字符串的内容赋值到v26变量中, 长度为0x61, 根据main_anal中的操作来看这个类似于虚拟机字节码
  main_anal(__input, p_input, 0x2E5B2E5D5D2D2E5DLL, 97LL, v12);
  cmpdata0 = 0x9F061721E8B3DD38LL;
  cmpdata1 = 0x120D94C88B56A209LL;
  cmpdata2 = 0x8C0C2D3877E67EF3LL;
  cmpdata3 = 0x2507BAF4CF113DELL;
  cmpdata4 = 0x1B66F9F5A0238BCALL;
  cmpdata5 = 0x9727;
  if ( p_input == 42 ? runtime_memequal() : 0 ) // 关键判断
  {
    v30 = &RTYPE_string;
    v31 = &right;
    fmt_Fprintln(v15, v17, v18, *((__int64 *)&v18 + 1));
  }
  else
  {
LABEL_12:
    v28 = &RTYPE_string;
    v29 = &wrong;
    fmt_Fprintln(v15, v17, v18, *((__int64 *)&v18 + 1));
  }
}

首先由 *(_QWORD *)&v18 = fmt_Fscanf(v14, v16); 进行输入, 随后检查长度, 并进行切片.

进入加密函数, 需要关注前面的 memcpy 操作的字符串, 跟加密函数高度相关

  qmemcpy(v26, ">>>>><<>>>><[[[.]+]<[-.+.].+.]].][.][[.+.]>[.<-.]<.][.][[.-,.<>>>>>>><<<<.].][[.[.]<.][.].-]].[.]", 97);// 将这个字符串的内容赋值到v26变量中, 长度为0x61, 根据main_anal中的操作来看这个类似于虚拟机字节码
  main_anal(__input, p_input, 0x2E5B2E5D5D2D2E5DLL, 97LL, v12);

main_anal

// main.anal
__int64 __fastcall main_anal(__int64 __input, unsigned __int64 _len, __int64 num, __int64 len, unsigned __int64 a5)
{
  __int64 result; // rax
  __int64 v6; // rbx
  __int64 v7; // r14
  unsigned __int64 len_sub_1; // r9
  unsigned __int64 v9; // rcx
  char v10; // dl
  int v11; // r10d
  unsigned __int8 _IP; // r11
  int v13; // r12d
  unsigned __int64 _len_sub_1; // r11
  unsigned __int64 __len; // r9
  __int64 v16; // rax
  __int64 v17; // rsi
  __int64 v18; // r8
  __int64 v19; // rdx
  __int64 v20; // rax
  __int64 v21; // rcx
  unsigned __int64 v22; // rsi
  unsigned __int64 v23; // r15
  __int64 v24; // r8
  __int64 v25; // rax
  unsigned __int64 v26; // rcx
  unsigned __int64 v27; // rcx
  int v28; // edx
  __int64 v29; // rax
  unsigned __int64 v30; // rcx
  __int64 v31; // rsi
  unsigned __int64 v32; // r15
  __int64 v33; // r8
  __int64 v34; // rax
  unsigned __int64 v35; // rcx
  unsigned __int64 v36; // r9
  unsigned __int64 v37; // rsi
  unsigned __int64 v38; // r13
  __int64 v39; // r8
  __int64 v40; // rax
  unsigned __int64 v41; // rcx
  __int64 v42; // [rsp-42h] [rbp-D8h]
  char v43; // [rsp+1h] [rbp-95h]
  unsigned __int8 v44; // [rsp+2h] [rbp-94h]
  unsigned __int8 v45; // [rsp+2h] [rbp-94h]
  char v46; // [rsp+3h] [rbp-93h]
  unsigned __int8 v47; // [rsp+4h] [rbp-92h]
  char v48; // [rsp+5h] [rbp-91h]
  unsigned __int8 v49; // [rsp+5h] [rbp-91h]
  unsigned __int64 v50; // [rsp+Eh] [rbp-88h]
  unsigned __int64 v51; // [rsp+16h] [rbp-80h]
  unsigned __int64 v52; // [rsp+1Eh] [rbp-78h]
  unsigned __int64 v53; // [rsp+26h] [rbp-70h]
  unsigned __int64 v54; // [rsp+2Eh] [rbp-68h]
  unsigned __int64 v55; // [rsp+3Eh] [rbp-58h] BYREF
  unsigned __int64 v56; // [rsp+46h] [rbp-50h]
  unsigned __int64 v57; // [rsp+4Eh] [rbp-48h]
  unsigned __int64 v58; // [rsp+56h] [rbp-40h]
  unsigned __int64 v59; // [rsp+5Eh] [rbp-38h]
  __int64 v60; // [rsp+66h] [rbp-30h]
  __int64 v61; // [rsp+6Eh] [rbp-28h]
  __int64 v62; // [rsp+76h] [rbp-20h]
  __int64 v63; // [rsp+7Eh] [rbp-18h]
  __int64 v64; // [rsp+86h] [rbp-10h]
  __int64 v65; // [rsp+9Eh] [rbp+8h]
  __int64 v66; // [rsp+9Eh] [rbp+8h]
  __int64 v67; // [rsp+A6h] [rbp+10h]
  __int64 v68; // [rsp+AEh] [rbp+18h]
  unsigned __int64 v69; // [rsp+C6h] [rbp+30h]

  while ( (unsigned __int64)&v55 <= *(_QWORD *)(v7 + 16) )
  {
    v66 = result;
    v68 = len;
    v69 = a5;
    runtime_morestack_noctxt();
    result = v66;
    len = v68;
    a5 = v69;
  }
  v67 = v6;
  v65 = result;
  v46 = 0;
  v57 = 0LL;
  len_sub_1 = _len - 1;
  LODWORD(v9) = 0;
  v10 = 1;
  v11 = 0;
  while ( v6 > (unsigned __int8)v9 )
  {
    _IP = *(_BYTE *)(result + (unsigned __int8)v9);// 更新IP
    if ( v46 == _IP )
      JUMPOUT(0x292E1ALL);
    v46 = *(_BYTE *)(result + (unsigned __int8)v9);
    v57 = 0LL;
    v44 = v11;
    v43 = v10;
    v50 = len_sub_1;
    v13 = v9 + 1;
    v47 = v9 + 1;
    if ( _IP > '.' )
    {
      if ( _IP > '>' )
      {
        if ( _IP == '[' )
        {
          if ( v10 )
          {
            _len_sub_1 = len_sub_1 + 1;
            v59 = len_sub_1 + 1;
            if ( (__int64)_len > (__int64)(len_sub_1 + 1) )
            {
              if ( a5 < len_sub_1 + 2 )
                runtime_panicSliceAcap();
              if ( _len < _len_sub_1 )
                runtime_panicSliceB();
              v31 = _len - len_sub_1;
              v54 = len_sub_1 + v31 + 1;
              v32 = a5;
              v33 = __input + (_len_sub_1 & ((__int64)(1 - (a5 - len_sub_1)) >> 63));
              if ( v32 < v54 )
              {
                v64 = v33;
                v58 = v31;
                runtime_growslice(v42);
                __input = v34;
                v32 = v35;
              }
              v58 = v32;
              v62 = __input;
              runtime_memmove();
              if ( v54 <= v59 )
                runtime_panicIndex();
              __input = v62;
              v11 = v44;
              *(_BYTE *)(v50 + v62 + 1) = v44;
              result = v65;
              v10 = v43;
              v6 = v67;
              _len_sub_1 = v59;
              v13 = v47;
              __len = v54;
              a5 = v58;
            }
            else
            {
              __len = _len + 1;
              if ( a5 < _len + 1 )
              {
                runtime_growslice(v42);
                __len = __input + 1;
                v10 = v43;
                v6 = v67;
                v11 = v44;
                _len_sub_1 = v59;
                v13 = v47;
                __input = v29;
                a5 = v30;
                result = v65;
              }
              *(_BYTE *)(__input + _len) = v11;
            }
          }
          else
          {
            _len_sub_1 = len_sub_1 + ((1LL << v57) & -(__int64)(v57 < 0x40));
            __len = _len;
          }
        }
        else
        {
          if ( _IP != ']' )
            goto LABEL_67;
          if ( v10 )
          {
            if ( len_sub_1 >= _len )
              runtime_panicIndex();
            v45 = *(_BYTE *)(len_sub_1 + __input);
            v37 = _len - len_sub_1;
            v53 = len_sub_1 + v37 - 1;
            v38 = a5;
            v39 = __input + ((len_sub_1 + 1) & ((__int64)(1 - (a5 - len_sub_1)) >> 63));
            if ( v38 < v53 )
            {
              v64 = v39;
              v59 = v37;
              runtime_growslice(v42);
              __input = v40;
              v38 = v41;
            }
            v61 = __input;
            runtime_memmove();
            v36 = v50 - 1;
            result = v65;
            v10 = v43;
            v6 = v67;
            v13 = v47;
            _len = v53;
            a5 = v38;
            __input = v61;
            v11 = v45;
          }
          else
          {
            v36 = len_sub_1 - (-(__int64)(v57 < 0x40) & (1LL << v57));
          }
          _len_sub_1 = v36;
          __len = _len;
        }
      }
      else
      {
        if ( _IP != '<' )
        {
          if ( _IP == '>' )
            JUMPOUT(0x29328FLL);
LABEL_67:
          __len = _len;
          _len_sub_1 = v50;
          goto LABEL_3;
        }
        if ( v10 )
        {
          v11 -= v57 < 0x20 ? 1 << v57 : 0;
        }
        else
        {
          if ( len_sub_1 >= _len )
            runtime_panicIndex();
          *(_BYTE *)(__input + len_sub_1) = *(_BYTE *)(len_sub_1 + __input) - (v57 < 0x20 ? 1 << v57 : 0);
        }
        __len = _len;
        _len_sub_1 = v50;
      }
    }
    else
    {
      if ( _IP > ',' )
      {
        if ( _IP != '-' )
        {
          v10 ^= 1u;
          __len = _len;
          _len_sub_1 = v50;
          goto LABEL_3;
        }
        if ( len_sub_1 >= _len )
          runtime_panicIndex();
        v49 = *(_BYTE *)(len_sub_1 + __input);
        v22 = _len - len_sub_1;
        v51 = len_sub_1 + v22 - 1;
        v23 = a5;
        v24 = __input + ((len_sub_1 + 1) & ((__int64)(1 - (a5 - len_sub_1)) >> 63));
        if ( v23 < v51 )
        {
          v64 = v24;
          v59 = v22;
          runtime_growslice(v42);
          __input = v25;
          v23 = v26;
        }
        v60 = __input;
        runtime_memmove();
        if ( v43 )
        {
          if ( v44 )
          {
            v27 = v51;
            goto LABEL_34;
          }
          v27 = v51;
LABEL_35:
          v28 = v47;
        }
        else
        {
          v27 = v51;
          if ( v51 <= v50 - 1 )
            runtime_panicIndex();
          if ( !*(_BYTE *)(v50 + v60 - 1) )
            goto LABEL_35;
LABEL_34:
          v28 = v49;
        }
        v6 = v67;
        v13 = v28;
        a5 = v23;
        __len = v27;
        __input = v60;
        _len_sub_1 = v50 - 1;
        v10 = v43;
        v11 = v44;
        result = v65;
        goto LABEL_3;
      }
      if ( _IP == '+' )
      {
        v48 = v9;
        _len_sub_1 = len_sub_1 + 1;
        v59 = len_sub_1 + 1;
        if ( (__int64)_len > (__int64)(len_sub_1 + 1) )
        {
          if ( a5 < len_sub_1 + 2 )
            runtime_panicSliceAcap();
          if ( _len < _len_sub_1 )
            runtime_panicSliceB();
          v55 = a5;
          v17 = _len - len_sub_1;
          v52 = len_sub_1 + v17 + 1;
          v18 = __input + (_len_sub_1 & ((__int64)(1 - (a5 - len_sub_1)) >> 63));
          v19 = v55;
          if ( v55 < v52 )
          {
            v64 = v18;
            v58 = v17;
            runtime_growslice(v42);
            __input = v20;
            v19 = v21;
          }
          v56 = v19;
          v63 = __input;
          runtime_memmove();
          if ( v52 <= v59 )
            runtime_panicIndex();
          __input = v63;
          *(_BYTE *)(v50 + v63 + 1) = v48;
          result = v65;
          v10 = v43;
          v6 = v67;
          v11 = v44;
          _len_sub_1 = v59;
          v13 = v47;
          __len = v52;
          a5 = v56;
        }
        else
        {
          __len = _len + 1;
          if ( a5 < _len + 1 )
          {
            runtime_growslice(v42);
            __len = __input + 1;
            v10 = v43;
            v6 = v67;
            v11 = v44;
            _len_sub_1 = v59;
            v13 = v47;
            __input = v16;
            a5 = v9;
            result = v65;
            LOBYTE(v9) = v48;
          }
          *(_BYTE *)(__input + _len) = v9;
        }
      }
      else
      {
        if ( _IP != ',' )
          goto LABEL_67;
        if ( len_sub_1 >= _len )
          runtime_panicIndex();
        if ( _len <= len_sub_1 + 2 )
          runtime_panicIndex();
        *(_BYTE *)(__input + len_sub_1) = (-63 * *(_BYTE *)(len_sub_1 + __input + 2)) ^ *(_BYTE *)(len_sub_1 + __input);
        __len = _len;
        _len_sub_1 = v50;
      }
    }
LABEL_3:
    LODWORD(v9) = v13;                          // 结合上面的v13 = v9 + 1, 可以看成v9++
    _len = __len;
    len_sub_1 = _len_sub_1;
  }




  return result;
}
/* Orphan comments:
这里是针对">"的语句>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
v57初始化为0, 如果v57 < 32, 则返回1<<v57, 否则返回0
*/

总体看就是一个 while 循环中包含了 switch, 跟暑假复现题 gocode 非常相似, 前面 memcpy 的">>..."字符串就是字节码, 用来操作加密行为. 看了小组的 WP 是叫做brainfuck 代码.

根据不同的输入可以发现是无扩散关联的加密方式, 即各个字节加密互相不受影响, 这种加密可以采用爆破破解.

但是程序本身是无法直接爆破加密的, 因为它会在关键判断出验证长度

.text:0000000000293A42 48 83 F9 2A                                         cmp     rcx, 2Ah ; '*'  ; len
.text:0000000000293A46 74 04                                               jz      short loc_293A4C

直接修改指令 cmp rcx, 2Ah​ 会导致判断一直为错误, 因为 rcx 是中的值是字符串长度无法改变, 而后面的验证函数又是通过 rcx 来确定检验长度的, 所以需要修改 rcx ,而不是后面的直接数.

修改指令为 mov ecx, 1​ , 修改后续直接数相当于修改 ecx .

另一个问题: cmp 指令会影响下一个 jz 结果, 修改后 jz 会一直跳转到"wrong", 需要修改为 jmp 绝对跳转.

两处修改会覆盖掉后面的一点内容, 但都是无用指令, 所以覆盖掉也没有关系.

使用 Python 爆破


import subprocess
import string

# fla111111111111111111111111111111111111111
def patcher(size):
    data = bytearray(open("analgo.exe", "rb").read())
    data[0xB3043] = size
    open("analgo.exe", "wb").write(data)


def try_one(t):
    pipe = subprocess.Popen("./analgo.exe", bufsize=2, stdout=subprocess.PIPE, stdin=subprocess.PIPE)
    pipe.stdin.write(t)
    return pipe.stdout.read()


data = list('f11111111111111111111111111111111111111111\n')
for i in range(1, 0x2A):
    patcher(i)
    for j in string.printable:
        data[i] = j
        if b'right!\n' == try_one(bytes("".join(data).encode("ascii"))):
            break
    print("".join(data))

flag

flag{568a3cdd-77e1-4c42-9fee-127e27a5744e}