安洵杯2019 Crackme

  • main函数:

  • int __cdecl __noreturn main_0(int argc, const char **argv, const char **envp)
    {
      _BYTE v3[6]; // [esp-4h] [ebp-D0h]
    
      printf("please Input the flag:\n");
      scanf_s("%s", &input_string);
      MessageBoxW(0, L"Exception", L"Warning", 0);
      *(_DWORD *)v3 = sub_A3100F;
      MEMORY[0] = 1;
      sub_A31136(*(int *)&v3[2]);
    }
    
  • 看到汇编中注册了SEH函数:

  • push    offset sub_A3100F
    push    large dword ptr fs:0
    mov     large fs:0, esp
    
  • 跟进

  • char __cdecl sub_A319F0(int a1, unsigned int *a2, _BYTE *a3)
    {
      int v3; // eax
      char result; // al
      int v5[38]; // [esp+D0h] [ebp-A4h] BYREF
      unsigned int v6; // [esp+168h] [ebp-Ch]
    
      v6 = 0;
      j_memset(v5, 0, 0x90u);
      v5[0] = _byteswap_ulong(*a2);
      v5[1] = _byteswap_ulong(a2[1]);
      v5[2] = _byteswap_ulong(a2[2]);
      v5[3] = _byteswap_ulong(a2[3]);
      while ( v6 < 0x20 )
      {
        v3 = sub_A31700(v5[v6], v5[v6 + 1], v5[v6 + 2], v5[v6 + 3], *(_DWORD *)(a1 + 4 * v6));
        v5[v6++ + 4] = v3;
      }
      *a3 = HIBYTE(v5[35]);
      a3[1] = BYTE2(v5[35]);
      a3[2] = BYTE1(v5[35]);
      a3[3] = v5[35];
      a3[4] = HIBYTE(v5[34]);
      a3[5] = BYTE2(v5[34]);
      a3[6] = BYTE1(v5[34]);
      a3[7] = v5[34];
      a3[8] = HIBYTE(v5[33]);
      a3[9] = BYTE2(v5[33]);
      a3[10] = BYTE1(v5[33]);
      a3[11] = v5[33];
      a3[12] = HIBYTE(v5[32]);
      a3[13] = BYTE2(v5[32]);
      a3[14] = BYTE1(v5[32]);
      result = v5[32];
      a3[15] = v5[32];
      return result;
    }
    unsigned int __cdecl sub_581700(int a1, int a2, int a3, int a4, int a5)
    {
      return a1 ^ sub_581760(a5 ^ a4 ^ a3 ^ a2);
    }
    int __cdecl sub_581760(int a1)
    {
      unsigned int v2; // [esp+D0h] [ebp-2Ch]
      unsigned int v3; // [esp+F4h] [ebp-8h]
    
      LOBYTE(v2) = sub_5819A0(HIBYTE(a1));
      BYTE1(v2) = sub_5819A0(BYTE2(a1));
      BYTE2(v2) = sub_5819A0(BYTE1(a1));
      HIBYTE(v2) = sub_5819A0(a1);
      v3 = _byteswap_ulong(v2);
      return ((v3 >> 8) | (v3 << 24)) ^ ((v3 >> 14) | (v3 << 18)) ^ ((v3 >> 22) | (v3 << 10)) ^ v3 ^ ((v3 >> 30) | (4 * v3));
    }
    
  • 其实是个SM4加密

  • 需要找到key的值

  • 交叉引用到Handler函数

  • int __stdcall Handler_0(_DWORD **a1)
    {
      unsigned int v2[5]; // [esp+D0h] [ebp-18h] BYREF
    
      if ( **a1 == 0xC0000005 )
      {
        qmemcpy(v2, "where_are_u_now?", 16);
        sub_581172(key, v2);
        SetUnhandledExceptionFilter(TopLevelExceptionFilter);
      }
      return 0;
    }
    
    unsigned int __cdecl sub_A31F50(int a1, unsigned int *a2)
    {
      unsigned int result; // eax
      unsigned int v3; // [esp+D0h] [ebp-B8h]
      unsigned int v4; // [esp+DCh] [ebp-ACh]
      unsigned int v5; // [esp+E0h] [ebp-A8h]
      unsigned int v6; // [esp+E4h] [ebp-A4h]
      int v7[35]; // [esp+E8h] [ebp-A0h]
      unsigned int v8; // [esp+174h] [ebp-14h]
      unsigned int v9; // [esp+178h] [ebp-10h]
      unsigned int v10; // [esp+17Ch] [ebp-Ch]
      unsigned int v11; // [esp+180h] [ebp-8h]
    
      v3 = 0;
      v8 = _byteswap_ulong(*a2);
      v9 = _byteswap_ulong(a2[1]);
      v10 = _byteswap_ulong(a2[2]);
      v11 = _byteswap_ulong(a2[3]);
      v4 = v8 ^ 0xA3B1BAC6;
      v5 = dword_A37A68[1] ^ v9;
      v6 = dword_A37A68[2] ^ v10;
      result = 12;
      v7[0] = dword_A37A68[3] ^ v11;
      while ( v3 < 0x20 )
      {
        v7[v3 + 1] = *(&v4 + v3) ^ sub_A314E0(dword_A37A78[v3] ^ v7[v3] ^ v7[v3 - 1] ^ *(&v5 + v3));
        *(_DWORD *)(a1 + 4 * v3) = v7[v3 + 1];
        result = ++v3;
      }
      return result;
    }
    
  • 可以直接动调出来key(0xA3B1BAC6 SM4一个特征值)

  • 还有一个UEH

  • int __cdecl sub_582C30(_DWORD *a1)
    {
      int result; // eax
      char v2; // [esp+D3h] [ebp-11h]
      size_t i; // [esp+DCh] [ebp-8h]
    
      result = (int)a1;
      if ( *(_DWORD *)*a1 == 0xC0000005 )
      {
        for ( i = 0; i < j_strlen(Str2); i += 2 )
        {
          v2 = Str2[i];
          Str2[i] = Str2[i + 1];
          Str2[i + 1] = v2;
        }
        Str1 = sub_58126C(output_string);
        *(_DWORD *)(a1[1] + 176) = *(_DWORD *)(*a1 + 20);//eax
        *(_DWORD *)(a1[1] + 164) = *(_DWORD *)(*a1 + 24);//ebx
        *(_DWORD *)(a1[1] + 172) = *(_DWORD *)(*a1 + 28);//ecx
        *(_DWORD *)(a1[1] + 168) = *(_DWORD *)(*a1 + 32);//edx
        *(_DWORD *)(a1[1] + 156) = *(_DWORD *)(*a1 + 36);//edi
        *(_DWORD *)(a1[1] + 160) = *(_DWORD *)(*a1 + 40);//esi
        *(_DWORD *)(a1[1] + 184) = sub_581136;//eip
        return -1;
      }
      return result;
    }
    _BYTE *__cdecl sub_583090(char *Str)
    {
      int k; // [esp+E4h] [ebp-5Ch]
      int v3; // [esp+F0h] [ebp-50h]
      int j; // [esp+FCh] [ebp-44h]
      int v5; // [esp+108h] [ebp-38h]
      signed int i; // [esp+114h] [ebp-2Ch]
      _BYTE *v7; // [esp+120h] [ebp-20h]
      signed int v8; // [esp+12Ch] [ebp-14h]
      int v9; // [esp+138h] [ebp-8h]
    
      v5 = 0;
      v8 = j_strlen(Str);
      if ( v8 % 3 )
        v9 = 4 * (v8 / 3) + 4;
      else
        v9 = 4 * (v8 / 3);
      v7 = malloc(__CFADD__(v9, 1) ? -1 : v9 + 1);
      v7[v9] = 0;
      for ( i = 0; i < v8; i += 3 )
      {
        v3 = 0;
        for ( j = 0; j < 3; ++j )
          v3 |= (unsigned __int8)Str[j + i] << (8 * (2 - j));
        for ( k = 0; k < 4; ++k )
        {
          if ( k >= 4 - (i + 3 - v8) && i + 3 > v8 )
            v7[v5] = 33;
          else
            v7[v5] = ::Str[sub_5810FF((v3 >> (6 * (3 - k))) & 0x3F)];
          ++v5;
        }
      }
      return v7;
    }
    
    int __cdecl sub_582760(int a1)
    {
      return (a1 + 24) % 64;
    }
    
  • 对str2进行处理,将sm4产生的输出进行处理,发现是一个魔改的base64

  • (其中对表进行凯撒加密

  • UEH里面还修改了eip为sub_581136,进行最后的字符串比较

  • 最后result=-1,异常返回继续执行

关于hadler0是怎么调用的

  • int __stdcall sub_582AB0(int a1, int a2, int a3, int a4)
    {
      size_t i; // [esp+D8h] [ebp-8h]
    
      for ( i = 0; i < j_strlen(Str); ++i )
      {
        if ( Str[i] <= 122 && Str[i] >= 97 )
        {
          Str[i] -= 32;
        }
        else if ( Str[i] <= 90 && Str[i] >= 65 )
        {
          Str[i] += 32;
        }
      }
      MessageBoxA(0, "hooked", "successed", 0);
      AddVectoredExceptionHandler(0, Handler);
      return 0;
    }
    
  • 交叉引用到上面这个函数

  • 发现其对base64码表进行变换,并注册VEH,从而调用Handler

  • 再交叉引用

  • int __cdecl sub_5827B0(int a1, char *String2, int a3)
    {
      DWORD LastError; // eax
      int flOldProtect[3]; // [esp+D0h] [ebp-90h] BYREF
      void *v6[3]; // [esp+DCh] [ebp-84h] BYREF
      LPCVOID lpAddress; // [esp+E8h] [ebp-78h]
      struct _MEMORY_BASIC_INFORMATION Buffer; // [esp+F4h] [ebp-6Ch] BYREF
      LPVOID lpBaseAddress; // [esp+118h] [ebp-48h]
      int v10; // [esp+124h] [ebp-3Ch]
      char *String1; // [esp+130h] [ebp-30h]
      int i; // [esp+13Ch] [ebp-24h]
      int v13; // [esp+148h] [ebp-18h]
      int v14; // [esp+154h] [ebp-Ch]
    
      v14 = a1;
      v13 = a1 + *(_DWORD *)(a1 + 60) + 24;
      for ( i = *(_DWORD *)(v13 + 104) + a1; i; i += 20 )
      {
        String1 = (char *)GetModuleHandleW(0) + *(_DWORD *)(i + 12);
        if ( !stricmp(String1, String2) )
          break;
      }
      if ( !i )
        return 0;
      v10 = *(_DWORD *)(i + 16) + a1;
      if ( !v10 )
        return 0;
      while ( 1 )
      {
        if ( !*(_DWORD *)v10 )
          return 0;
        lpBaseAddress = (LPVOID)v10;
        if ( *(_DWORD *)v10 == a3 )
          break;
        v10 += 4;
      }
      lpAddress = (LPCVOID)(v10 >> 12 << 12);
      VirtualQuery(lpAddress, &Buffer, 0x3E8u);
      VirtualProtect((LPVOID)lpAddress, Buffer.RegionSize, 0x40u, &Buffer.Protect);
      v6[0] = sub_581023;
      if ( WriteProcessMemory((HANDLE)0xFFFFFFFF, lpBaseAddress, v6, 4u, 0) )
      {
        VirtualProtect(Buffer.BaseAddress, Buffer.RegionSize, Buffer.Protect, (PDWORD)flOldProtect);
        return 1;
      }
      else
      {
        LastError = GetLastError();
        printf("%d\n", LastError);
        return 0;
      }
    }//找到 user32.dll 对应的 IMAGE_IMPORT_DESCRIPTOR 结构体地址,然后找到 MessageBoxW 对应的 IMAGE_THUNK_DATA 结构体地址,用VirtualProtect修改页属性为可写,用WriteProcessMemory将IMAGE_THUNK_DATA字段覆写为sub_581023函数地址
    
  • int sub_581E40()
    {
      HMODULE ModuleHandleW; // eax
    
      ModuleHandleW = GetModuleHandleW(0);
      sub_58114A((int)ModuleHandleW, "User32.dll", "MessageBoxW");
      return 0;
    }
    
  • 其实就是IAT hook,将MessageBoxW的IAT地址替换为了sub_581023的函数地址,该函数完成了VEH的注册

  • 再往上交叉,就找到一个rdata区的数据

  • .rdata:00587618 dd offset sub_581235
    
  • // write access to const memory has been detected, the output may be wrong!
    int __tmainCRTStartup()
    {
      int v1; // [esp+18h] [ebp-24h]
      signed __int32 v2; // [esp+1Ch] [ebp-20h]
      signed __int32 v3; // [esp+20h] [ebp-1Ch]
    
      v2 = *(_DWORD *)(j__NtCurrentTeb() + 4);
      v1 = 0;
      while ( 1 )
      {
        v3 = _InterlockedCompareExchange(dword_58A6EC, v2, 0);
        if ( !v3 )
          break;
        if ( v3 == v2 )
        {
          v1 = 1;
          break;
        }
      }
      if ( dword_58A6FC == 1 )
      {
        j__amsg_exit(31);
        goto LABEL_13;
      }
      if ( dword_58A6FC )
      {
        dword_58A2DC = 1;
        goto LABEL_13;
      }
      dword_58A6FC = 1;
      if ( !j__initterm_e((_PIFV *)&First, (_PIFV *)&Last) )
      {
    LABEL_13:
        if ( dword_58A6FC == 1 )
        {
          j__initterm((_PVFV *)&dword_587000, (_PVFV *)&dword_587208);
          dword_58A6FC = 2;
        }
        if ( dword_58A6FC != 2
          && CrtDbgReportW(
               2,
               L"f:\\dd\\vctools\\crt\\crtw32\\dllstuff\\crtexe.c",
               553,
               0,
               L"%s",
               L"__native_startup_state == __initialized") == 1 )
        {
          __debugbreak();
        }
        if ( !v1 )
          _InterlockedExchange(dword_58A6EC, 0);
        if ( dword_58A714 )
        {
          if ( j___IsNonwritableInCurrentImage(&dword_58A714) )
            dword_58A714(0, 2, 0);
        }
        CrtSetCheckCount(1);
        _initenv = envp;
        main(argc, (const char **)argv, (const char **)envp);
      }
      return 255;
    }
    
  • j__initterm_e函数调用了那个数据,且对全局/静态C++类的构造函数进行了初始化

  • 再往上就到了start

异常处理的注册

  1. 有个调用链:start -> tmainCRTStartUp->initterm_e->IAThook ,修改了MessageBox中的IAT表

  2. 这时main函数里会调用Messageboxw,就调用了注册VEH的函数,并对码表进行变换

  3. 在main中注册了SEH

  4. 在VEH的handler中注册了UEH

异常处理的回调

  1. Windows异常处理:用户态发生异常->调试器-VEH--SEH--UEH
  2. 在main中内存写异常,各级异常处理的返回状态都未完成处理,那么会进行上述的完整回调
  3. VEH对sm4进行初始,得到key
  4. SEH对输入进行sm4加密,
  5. UEH进行base64魔改加密,最后比较
  • 最后写下脚本

  • import sm4
    key = sm4.SM4Key(b"where_are_u_now?")
    print key.decrypt(sm4_encoded)
    
  • flag{SM4foRExcepioN?!}

一些小tips

  1. initterm:用于访问函数指针表并将其初始化的内部方法。

    第一个指针位于表中的起始位置,第二个指针位于结束位置。

  2. GetModuleHandleW:

    HMODULE GetModuleHandleW(
      [in, optional] LPCWSTR lpModuleName//加载的模块的名称 
    );
    
  3. HMODULE LoadLibraryA(
      [in] LPCSTR lpLibFileName
    );将指定的模块加载到调用进程的地址空间中
    
  4. FARPROC GetProcAddress(
      [in] HMODULE hModule,
      [in] LPCSTR  lpProcName
    );从指定的动态链接库 (DLL) 检索导出函数 (也称为过程) 或变量的地址。
    
    BOOL WriteProcessMemory(将数据写入到指定进程中的内存区域。
      [in]  HANDLE  hProcess,
      [in]  LPVOID  lpBaseAddress,
      [in]  LPCVOID lpBuffer,
      [in]  SIZE_T  nSize,
      [out] SIZE_T  *lpNumberOfBytesWritten
    );
    
    PVOID AddVectoredExceptionHandler(
      ULONG                       First,
      PVECTORED_EXCEPTION_HANDLER Handler
    );注册向量化异常处理程序。
    Firse:处理程序的调用顺序。 如果参数为非零值,则处理程序是要调用的第一个处理程序。 如果 参数为零,则处理程序是最后一个要调用的处理程序。
    Handler:指向要调用的处理程序的指针。 
    
    LPTOP_LEVEL_EXCEPTION_FILTER SetUnhandledExceptionFilter(
      [in] LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter//指向顶级异常筛选器函数的指针,每当 UnhandledExceptionFilter 函数获得控制权且进程未调试时,将调用该函数。
    );
    使应用程序能够取代进程每个线程的顶级异常处理程序。
    调用此函数后,如果在未调试的进程中发生异常,并且异常会将其引入未经处理的异常筛选器,该筛选器将调用 由 lpTopLevelExceptionFilter 参数指定的异常筛选器函数。
    
    LONG UnhandledExceptionFilter(
      [in] _EXCEPTION_POINTERS *ExceptionInfo//指向 EXCEPTION_POINTERS 结构的指针,该结构指定异常和异常时处理器上下文的说明。
    );应用程序定义的函数,它将未经处理的异常传递给调试器(如果正在调试进程)。 否则,它会选择显示 应用程序错误消息 框,并导致执行异常处理程序。 只能从异常处理程序的筛选器表达式内调用此函数。
    
    typedef struct _EXCEPTION_POINTERS {
      PEXCEPTION_RECORD ExceptionRecord;
      PCONTEXT          ContextRecord;
    } EXCEPTION_POINTERS, *PEXCEPTION_POINTERS;
    ExceptionRecord:指向 EXCEPTION_RECORD 结构的指针,该结构包含与计算机无关的异常说明。
    ContextRecord:指向 CONTEXT 结构的指针,该结构包含异常时处理器状态的特定于处理器的说明。
    

总结:

很全面的一道题,知识点明确