CISCN 2021 西南赛区 Reverse Writeup

babyRe

主要加密代码如下

_BYTE *__fastcall encode(const char *a1)
{
  _BYTE *v2; // [rsp+18h] [rbp-28h]
  signed __int64 v3; // [rsp+20h] [rbp-20h]
  int v4; // [rsp+30h] [rbp-10h]
  int v5; // [rsp+34h] [rbp-Ch]
  __int64 v6; // [rsp+38h] [rbp-8h]

  v3 = strlen(a1);
  if ( v3 % 3 )
    v6 = 4 * (v3 / 3 + 1);
  else
    v6 = 4 * (v3 / 3);
  v2 = malloc(v6 + 1);
  v2[v6] = 0;
  v5 = 0;
  v4 = 0;
  while ( v5 < v6 - 2 )
  {
    v2[v5] = a3zanjvbmdZekol[(unsigned __int8)a1[v4] >> 2];
    v2[v5 + 1] = a3zanjvbmdZekol[(16 * a1[v4]) & 0x30 | ((unsigned __int8)a1[v4 + 1] >> 4)];
    v2[v5 + 2] = a3zanjvbmdZekol[(4 * a1[v4 + 1]) & 0x3C | ((unsigned __int8)a1[v4 + 2] >> 6)];
    v2[v5 + 3] = a3zanjvbmdZekol[a1[v4 + 2] & 0x3F];
    v4 += 3;
    v5 += 4;
  }
  if ( v3 % 3 == 1 )
  {
    v2[v5 - 2] = 61;
    v2[v5 - 1] = 61;
  }
  else if ( v3 % 3 == 2 )
  {
    v2[v5 - 1] = 61;
  }
  return v2;
}

容易看出是 base64 变种,编写脚本根据常量表进行替换即可正常解密

import base64

a="3ZAnJVbMd/zEkolRBDW4KUYT0ga1PF9j86qwuXHciCOfr2tLmexGhpSI+NQ5y7sv"
b="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="
s="gHe6gIrSlYUqkGPeg4KNo4Vql4g6g4UqgHgHl4JNonBhlbk+och="
c=""
for i in s:
    c=c+b[a.find(i)]
print(base64.b64decode(c))

一次脱单的机会

程序主要代码如下

      if ( *(float *)&dword_7FF705EB5638 <= 0.0 )
      {
        if ( IsDebuggerPresent() )
          goto LABEL_22;
        sub_7FF705EB1020("f");
        if ( IsDebuggerPresent() )
          goto LABEL_22;
        sub_7FF705EB1020("l");
        if ( IsDebuggerPresent() )
          goto LABEL_22;
        sub_7FF705EB1020("ag");
        sub_7FF705EB1020("{md5(");
        v8 = 0i64;
        v13[2] = _mm_load_si128((const __m128i *)&xmmword_7FF705EB35D0);
        v13[1] = _mm_load_si128((const __m128i *)&xmmword_7FF705EB35E0);
        v13[0] = _mm_load_si128((const __m128i *)&xmmword_7FF705EB35F0);
        v13[3] = v13[0];
        v13[4] = 0i64;
        if ( _mm_cvtsi128_si32(v13[0]) )
        {
          do
          {
            sub_7FF705EB1020("%c");
            ++v8;
          }
          while ( v13[0].m128i_i32[v8] );
        }
        if ( IsDebuggerPresent() )
          goto LABEL_22;
        sub_7FF705EB1020(")");
        if ( IsDebuggerPresent() )
          goto LABEL_22;
        sub_7FF705EB1020("}");
      }

在主函数开头下断点,然后修改 ip 跳过 IsDebuggerPresent,直接运行到中间输出的地方即可得到 flag

ag{md5(女神早已不是女神

注意编码要选择 utf8,不要选 gbk

import hashlib

s=hashlib.md5('女神早已不是女神'.encode('utf8')).hexdigest()
print(s)

CryptoMachine

分析异常处理

程序启动时首先调用 TlsCallback 函数

```cpp
char *__stdcall TlsCallback_0(int a1, int a2, int a3)
{
  char *result; // eax
  DWORD flOldProtect; // [esp+8h] [ebp-Ch] BYREF
  LPVOID v5; // [esp+Ch] [ebp-8h]
  LPVOID lpAddress; // [esp+10h] [ebp-4h]

  if ( a2 == 1 )
  {
    lpAddress = &loc_4025DB;
    VirtualProtect(&loc_4025DB, 5u, 0x40u, &flOldProtect);
    *(_BYTE *)lpAddress = 0xE9;
    *(_DWORD *)((char *)lpAddress + 1) = (char *)sub_4021D0 - (char *)lpAddress - 5;// jmp sub_4021D0
    v5 = SEH_412700;
    VirtualProtect(SEH_412700, 1u, 0x40u, &flOldProtect);
    *(_DWORD *)v5 = 0x1B8;
    *((_WORD *)v5 + 2) = 0xC300;                // ret
    VirtualProtect(sub_4021D0, 0x30u, 0x40u, &flOldProtect);
    *((_BYTE *)sub_4021D0 + 38) = 0xE8;         // call unknown_libname_17
    *(_DWORD *)((char *)sub_4021D0 + 39) = (char *)unknown_libname_17 - (char *)((char *)sub_4021D0 + 38) - 5;
    *((_BYTE *)sub_4021D0 + 43) = 0xE9;
    result = (char *)sub_4021D0 + 43;
    *((_DWORD *)sub_4021D0 + 11) = (_BYTE *)lpAddress - ((char *)sub_4021D0 + 43);// jmp lpAddress
  }
  return result;
}

通过 SMC 操作修改 __scrt_common_main_seh 函数,使程序在进入主函数前调用 sub_4021D0 函数

.text:004021D0 sub_4021D0 proc near                    ; DATA XREF: TlsCallback_0+39↓o
.text:004021D0                                         ; TlsCallback_0+86↓o ...
.text:004021D0 push    offset main
.text:004021D5 push    large dword ptr fs:0
.text:004021DC mov     large fs:0, esp
.text:004021E3 push    offset sub_4021A0
.text:004021E8 push    large dword ptr fs:0
.text:004021EF mov     large fs:0, esp
.text:004021F6 call    _register_thread_local_exe_atexit_callback ; Microsoft VisualC universal runtime
.text:004021FB jmp     loc_4025E0
.text:004021FB sub_4021D0 endp

sub_4021D0 函数在 SEH 链中添加虚拟机入口

进入主函数,首先将需要加密的 flag.docx 读入到 block,随后创建 output 文件用来存放加密后的密文

  Buffer = 0;
  nNumberOfBytesToRead = 0;
  CreateFileA(FileName, 0x80000000, 1u, 0, 3u, 0, 0);
  stack = malloc(0x100u);
  memset(stack, 0, 0x100u);
  ReadFile(0, &Buffer, 0, &nNumberOfBytesToRead, 0);
  Block = malloc(0x3000u);
  memset(Block, 0, 0x3000u);
  v3 = (int (__stdcall *)(const char *, unsigned int, int, _DWORD))sub_401820(aCsgbpNdlk);
  v4 = (void (__stdcall *)(int, void *, int, int *, _DWORD))sub_401820(aRdcgbljb);
  nNumberOfBytesToRead = sub_401820(aWskwacokm);
  v5 = v3("flag.docx", 0x80000000, 1, 0);
  outfile = ((int (__stdcall *)(const char *, int, int, _DWORD, int, _DWORD, _DWORD))v3)(
              "output",
              0x40000000,
              1,
              0,
              2,
              0,
              0);
  v4(v5, Block, 12288, &blocklen, 0);
  MEMORY[0x44D000] = 0;

MEMORY[0x44D000] = 0 会触发 SEH 调用虚拟机

  reg = envp[46];
  if ( *reg == 0xB4900D8B )
  {
    envp[0x2E] = (_DWORD *)((char *)envp[0x2E] - 3);
    v23 = (int (__cdecl *)(_DWORD))sub_401820(aSdvvjmgileooiu);
    v15 = v23(sub_4018C0);
    result = 0;
  }
  else if ( envp[46] < (_DWORD *)dword_41A8D8 || (unsigned int)envp[46] > 0x41AA5C )
  {
    result = 1;
  }

第一次进入虚拟机时会调用 SetUnhandledExceptionFilter(sub_4018C0) 注册 UnhandledException 异常处理函数 sub_4018C0

第二次进入虚拟机时会返回 1 抛出 UnhandledException 异常进行反调试,在调试状态下不会执行 sub_4018C0 导致程序退出

int __cdecl sub_4018C0(int a1)
{
  if ( **(_DWORD **)(*(_DWORD *)(a1 + 4) + 184) == 0x8B0000C6 )
    *(_DWORD *)(*(_DWORD *)(a1 + 4) + 184) = dword_41A8D8;
  return -1;
}

sub_4018C0 对虚拟机的状态进行初始化,将 ip 指向虚拟机的指令段,由于处理器无法识别虚拟机指令,所以返回时会触发异常再次进入虚拟机

这里把内存中 envp[46] 的值手动修改成 0x0041A8D8 即可正常调试虚拟机

分析虚拟机

虚拟机主要代码如下

    switch ( *reg )
    {
      case 1:
        v26 = 0;
        envp[46] = reg + 1;                     // nop
        break;
      case 0xA0:
        mem[reg[1]] = reg[2];                   // mem(a1)=a2
        envp[46] = reg + 3;
        break;
      case 0xB0:
        v22 = mem[reg[1]];
        ++sp_;
        *((_DWORD *)stack + sp_) = v22;         // push(mem(a1))
        envp[46] = reg + 2;
        break;
      case 0xB5:
        mem[reg[1]] = *((_DWORD *)stack + sp_--);// mem(a1)=pop()
        envp[46] = reg + 2;
        break;
      case 0xC0:
        m1 = mem[reg[1]];                       // mem(0)=cmp(mem(a1),mem(a2))
        m2 = mem[reg[2]];
        mem[0] = 0;
        if ( m1 <= m2 )
        {
          if ( m1 == m2 )
          {
            mem[0] |= 2u;                       // 010 mem(a1)==mem(a2)
          }
          else if ( m1 < m2 )
          {
            mem[0] |= 1u;                       // 001 mem(a1)<mem(a2)
          }
        }
        else
        {
          mem[0] |= 4u;                         // 100 mem(a1)>mem(a2)
        }
        envp[46] = reg + 3;
        break;
      case 0xD0:
        mem[reg[1]] ^= mem[reg[2]];             // mem(a1)^=mem(a2)
        envp[46] = reg + 3;
        break;
      case 0xD1:
        mem[reg[1]] += mem[reg[2]];             // mem(a1)+=mem(a2)
        envp[46] = reg + 3;
        break;
      case 0xD2:
        mem[reg[1]] -= mem[reg[2]];             // mem(a1)-=mem(a2)
        envp[46] = reg + 3;
        break;
      case 0xD3:
        ++mem[reg[1]];                          // mem(a1)++
        envp[46] = reg + 2;
        break;
      case 0xD4:
        --mem[reg[1]];                          // mem(a1)--
        envp[46] = reg + 2;
        break;
      case 0xE0:
        envp[46] = (_DWORD *)(4 * reg[1] + 0x41A8D8);// jmp(a1)
        break;
      case 0xE1:
        if ( (mem[0] & 2) != 0 )
          envp[46] = (_DWORD *)(4 * reg[1] + 4303064);// jeq
        else
          envp[46] = reg + 2;
        break;
      case 0xE2:
        if ( (mem[0] & 2) != 0 )                // jne
          envp[46] = reg + 2;
        else
          envp[46] = (_DWORD *)(4 * reg[1] + 4303064);
        break;
      case 0xE3:
        if ( (mem[0] & 4) != 0 )
          envp[46] = (_DWORD *)(4 * reg[1] + 4303064);// ja
        else
          envp[46] = reg + 2;
        break;
      case 0xE4:
        if ( (mem[0] & 6) != 0 )
          envp[46] = (_DWORD *)(4 * reg[1] + 4303064);// jae
        else
          envp[46] = reg + 2;
        break;
      case 0xE5:
        if ( (mem[0] & 1) != 0 )
          envp[46] = (_DWORD *)(4 * reg[1] + 4303064);// jb
        else
          envp[46] = reg + 2;
        break;
      case 0xE6:
        if ( (mem[0] & 3) != 0 )
          envp[46] = (_DWORD *)(4 * reg[1] + 4303064);// jbe
        else
          envp[46] = reg + 2;
        break;
      case 0xF0:
        v4 = sub_401810(0);                     // random
        v5 = rand();
        srand(v5 + v4);
        v6 = (unsigned __int8)rand();
        v7 = ((unsigned __int8)rand() << 8) | v6;
        v8 = ((unsigned __int8)rand() << 16) | v7;
        mem[0] = ((unsigned __int8)rand() << 24) | v8;// mem(0)=rand()
        envp[46] = reg + 1;
        break;
      case 0xF1:
        mem[4] = (int)stack;                    // mem(4)=stack
        envp[46] = reg + 1;
        break;
      case 0xF2:
        mem[4] = (int)Block;                    // mem(4)=block
        envp[46] = reg + 1;
        break;
      case 0xF3:
        WriteFile = (void (__cdecl *)(int, int, int, char *, _DWORD))sub_401820(aWskwacokm);
        buf = mem[reg[1]];
        len = mem[reg[2]];
        WriteFile(outfile, buf, len, v14, 0);   // write(outfile,mem(a1),mem(a2))
        envp[46] = reg + 3;
        break;
      case 0xF4:
        v16 = mem[reg[1]];
        hCrypto = (PINFORMATIONCARD_CRYPTO_HANDLE)mem[reg[2]];
        v17 = mem[reg[3]];
        encrypt(hCrypto, v9, v10, v11, v12, v13);
        envp[46] = reg + 4;
        break;
      case 0xF6:
        mem[0] = blocklen;                      // mem(0)=len
        envp[46] = reg + 1;
        break;
      case 0:
        free(stack);                            // exit
        free(Block);
        _loaddll(0);
        break;
    }

简单分析可以看出是使用栈和寄存器的虚拟机

用 IDA 的 Export Data 功能导出虚拟机指令,再编写脚本进行翻译

#include <cstdio>

unsigned char str[] =
{
  0xA0, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0xA0, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 
  0x04, 0x00, 0x00, 0x00, 0xA0, 0x00, 0x00, 0x00, 0x01, 0x00, 
  0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0xB0, 0x00, 0x00, 0x00, 
  0x02, 0x00, 0x00, 0x00, 0xA0, 0x00, 0x00, 0x00, 0x02, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA0, 0x00, 0x00, 0x00, 
  0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xC0, 0x00, 
  0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 
  0xE4, 0x00, 0x00, 0x00, 0x1D, 0x00, 0x00, 0x00, 0xF0, 0x00, 
  0x00, 0x00, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0xD3, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0xE0, 0x00, 
  0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0xF1, 0x00, 0x00, 0x00, 
  0xD1, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 
  0x00, 0x00, 0xB0, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 
  0xB5, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0xB5, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB5, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0xB5, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0xB5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0xB5, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0xF2, 0x00, 
  0x00, 0x00, 0xD1, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 
  0x02, 0x00, 0x00, 0x00, 0xF4, 0x00, 0x00, 0x00, 0x04, 0x00, 
  0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 
  0xF3, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 
  0x00, 0x00, 0xF3, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 
  0x01, 0x00, 0x00, 0x00, 0xD1, 0x00, 0x00, 0x00, 0x02, 0x00, 
  0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xF6, 0x00, 0x00, 0x00, 
  0xD1, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 
  0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0xD2, 0x00, 0x00, 0x00, 0x02, 0x00, 
  0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xE5, 0x00, 0x00, 0x00, 
  0x09, 0x00, 0x00, 0x00, 0xF6, 0x00, 0x00, 0x00, 0xD2, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 
  0xD1, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 
  0x00, 0x00, 0xF4, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 
  0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF3, 0x00, 
  0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 
  0xF3, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};

int main(){
    unsigned int *op_=(unsigned int *)str;
    unsigned int *op=op_;
    while(op-op_<sizeof(str)/4){
        printf("%d: ",op-op_);
        switch(*op){
            case 0:
                printf("exit\n");
                op++;
                break;
            case 1:
                printf("nop\n");
                op++;
                break;
            case 0xA0:
                printf("mem(%d)=%d\n",*(op+1),*(op+2));
                op+=3;
                break;
            case 0xB0:
                printf("push(mem(%d))\n",*(op+1));
                op+=2;
                break;
            case 0xB5:
                printf("mem(%d)=pop()\n",*(op+1));
                op+=2;
                break;
            case 0xC0:
                printf("mem(0)=cmp(mem(%d),mem(%d))\n",*(op+1),*(op+2));
                op+=3;
                break;
            case 0xD0:
                printf("mem(%d)^=mem(%d)\n",*(op+1),*(op+2));
                op+=3;
                break;
            case 0xD1:
                printf("mem(%d)+=mem(%d)\n",*(op+1),*(op+2));
                op+=3;
                break;
            case 0xD2:
                printf("mem(%d)-=mem(%d)\n",*(op+1),*(op+2));
                op+=3;
                break;
            case 0xD3:
                printf("mem(%d)++\n",*(op+1));
                op+=2;
                break;
            case 0xD4:
                printf("mem(%d)--\n",*(op+1));
                op+=2;
                break;
            case 0xE0:
                printf("jmp %d\n",*(op+1));
                op+=2;
                break;
            case 0xE1:
                printf("jeq %d\n",*(op+1));
                op+=2;
                break;
            case 0xE2:
                printf("jne %d\n",*(op+1));
                op+=2;
                break;
            case 0xE3:
                printf("ja %d\n",*(op+1));
                op+=2;
                break;
            case 0xE4:
                printf("jae %d\n",*(op+1));
                op+=2;
                break;
            case 0xE5:
                printf("jb %d\n",*(op+1));
                op+=2;
                break;
            case 0xE6:
                printf("jbe %d\n",*(op+1));
                op+=2;
                break;
            case 0xF0:
                printf("mem(0)=rand()\n");
                op+=1;
                break;
            case 0xF1:
                printf("mem(4)=stack\n");
                op+=1;
                break;
            case 0xF2:
                printf("mem(4)=block()\n");
                op+=1;
                break;
            case 0xF3:
                printf("write(outfile,mem(%d),mem(%d))\n",*(op+1),*(op+2));
                op+=3;
                break;
            case 0xF4:
                printf("encrypt(mem(%d),mem(%d),mem(%d))\n",*(op+1),*(op+2),*(op+3));
                op+=4;
                break;
            case 0xF6:
                printf("mem(0)=blocklen\n");
                op+=1;
                break;
        }
    }
}

翻译结果如下

0: mem(2)=0
3: mem(3)=4
6: mem(1)=16
9: push(mem(2))
11: mem(2)=0
14: mem(4)=4
17: mem(0)=cmp(mem(2),mem(4))
20: jae 29
22: mem(0)=rand()
23: push(mem(0))
25: mem(2)++
27: jmp 17
29: mem(4)=stack
30: mem(4)+=mem(3)
33: push(mem(4))
35: mem(5)=pop()
37: mem(0)=pop()
39: mem(0)=pop()
41: mem(0)=pop()
43: mem(0)=pop()
45: mem(2)=pop()
47: mem(4)=block()
48: mem(4)+=mem(2)
51: encrypt(mem(4),mem(5),mem(1))
55: write(outfile,mem(5),mem(1))
58: write(outfile,mem(4),mem(1))
61: mem(2)+=mem(1)
64: mem(0)=blocklen
65: mem(2)+=mem(1)
68: mem(0)=cmp(mem(2),mem(0))
71: mem(2)-=mem(1)
74: jb 9
76: mem(0)=blocklen
77: mem(0)-=mem(2)
80: mem(4)+=mem(1)
83: encrypt(mem(4),mem(5),mem(0))
87: write(outfile,mem(5),mem(1))
90: write(outfile,mem(4),mem(1))
93: exit

进一步化简得到伪代码

offset=0
do{
    stack[4:20]=random();
    msg=block()+offset
    key=&stack[4]
    encrypt(msg,key,16)
    write(outfile,key,16)
    write(outfile,msg,16)
    offset+=16
}while(offset<blocklen)
remain=blocklen-offset
msg+=16
encrypt(msg,key,remain)
write(outfile,key,16)
write(outfile,msg,16)
exit

可以看出程序的算法是每次随机生成 128 位的密钥,然后用 encrypt 函数加密明文中 128 位的数据,再将密钥和密文一起输出到文件

分析加密算法

encrypt 函数的主要代码如下

    do
    {
      v17 = 0;
      v18 = *(int *)((char *)&dword_418A24 + v16);
      v42 = *(int *)((char *)&dword_418A2C + v16);
      v40 = *(int *)((char *)&dword_418A28 + v16);
      v41 = *(int *)((char *)&dword_418A20 + v16);
      v39 = v18;
      v43 = (int)&v49 + v16;
      v19 = (char *)&v49 + v16;
      do
      {
        v20 = sub_401250(v18, v35.m128i_i32[v17]);
        v21 = sub_401250(v42, v37.m128i_i32[v17]) ^ v20;
        v22 = sub_401250(v41, *((_DWORD *)&v34 + v17)) ^ v21;
        v23 = sub_401250(v40, v36.m128i_i32[v17]);
        v18 = v39;
        v19 += 4;
        ++v17;
        *((_DWORD *)v19 - 1) = v23 ^ v22;
      }
      while ( v17 < 4 );
      v16 = v48 + 16;
      v48 = v16;
    }
    while ( v16 < 64 );

分析算法特征

  1. dword_418A24 处的内容为 [2,3,1,1],对应 AES 中列混合的系数

  2. sub_401250 中多次出现异或 0x1B 的操作,0x1B 对应二进制 0b00011011,对应 AES 中 \(GF(2^8)\) 上乘法规定的的不可约多项式 \(m(x)=x^8+x^4+x^3+x^1+1\)

  3. encrypt 中第三个参数为 16,对应 AES 中块的大小 16 字节(128位)

虽然程序中没有静态的 sbox 表可供 FindCrypt 插件查询算法,但是通过以上三点可以推断出这里的 encrypt 函数即为 AES 加密算法

编写解密脚本

解密脚本如下

from Crypto.Cipher import AES

with open('output','rb') as f1:
    with open('output.docx','wb') as f2:
        while(1):
            k=f1.read(16)
            c=f1.read(16)
            cipher=AES.new(k,AES.MODE_ECB)
            msg=cipher.decrypt(c)
            f2.write(msg)

打开 output.docx 即可得到 flag

posted @ 2021-06-05 23:55  Byaidu  阅读(115)  评论(0编辑  收藏  举报