• [Flare-on 2019] Reloadered

    2020. 1. 21.

    by. ugonfor

    머리가 나쁘면 손과 시간이 고생한다는 것을 뼈저리게 느끼게 해준 문제...

    #이상한게 왜 디버거로 볼때는 이상한 값이라도, 키를 체크하는 과정을 통과하고 flag를 출력해주는 데, 그냥 실행해 보면 가끔씩만 되는걸까...

    처음에는 멍청하게 프로세스를 켜서 brute force를 할려고 했는 데, 그냥 그 알고리즘을 가져와서 돌리는 게 백배 천배 빠르다는 것을 시간을 많이 할애한 후에 알게됨 ㅋㅋ..

    딱봐도 뭔가 이상함. 문제에서 의도한 값인거 같긴 한데, flare-on.com이 아님

    게다가 딱봐도 수상해 보이는 값이 전혀 다른 곳에 있음....


    이때 쯤 새로 들은 힌트: 1. relocation table이 있을 것이다.

    pe 재배치에 대해서 열심히 찾아보았다. 나뭇잎을 찾아서 보기도 하였다... 근데 너무 갈피가 안 잡혀서 고민하던 중, 힌트를 얻은 것이 두 개 있었다.

    하나는 가설이었는 데, pe재배치를 어떻게 하든, 결국에는 코드부분에 나타나게 될 것이다. 즉 디버거로 따라가다보면 코드를 볼 수 있겠지...

    나머지 하나는 디버거로 시작할때와 그냥 시작할 때, 출력값이 차이난다는 것이다.

    위 파워쉘이 단순히 실행한 것이고, 아래 cmd창이 디버거로 실행한 것이다.

    디버거로 실행하면 reloaderd가 나타난다.

    처음에는 단순 에러인가 하고 생각했는 데, 그럴리가 없다... 안티 디버깅이 되서 디버거로 볼때와, 단순 실행할 때의 코드가 다른 것이다. → 승완선배가 언급해주심

    그래서 열심히 찾아봄...

    특히 중간에 nop으로 쌓여 있는 부분이 의심스러웠는 데, 그부분에 reference를 둔 문자열도 존재하는 데 단순 코드로 보면 nop였기 때문에 매우 의심스러웠다 ㅇㅇ...

    나는 entrypoint부터 call하는 부분들을 중심으로 차근차근 다 분석을 해보았는 데, 다끝나고 보니까 그냥 nop에다가 hardwarebreakpoint를 걸었으면 되지 않았나 싶다...

    여튼 call하는 부분을 열심히 찾아보니 call esi가 하나 있더라... 그리고 esi로 들어가면 nop로 쌓여있던 부분이 다 다른 코드값으로 바뀌어서 real_main이 실행되더라...

    그 코드를 수도코드로 만든 것이 다음과 같다.

    int __usercall real_main@<eax>(int a1@<ebx>, int a2, const char **a3, const char **a4)
    {
      unsigned __int64 v4; // rax
      unsigned __int64 v5; // kr08_8
      unsigned int v7; // esi
      signed __int64 v22; // kr18_8
      bool v23; // zf
      HMODULE v24; // esi
      unsigned int v25; // edx
      int v26; // eax
      unsigned int i; // ecx
      unsigned int j; // esi
      char *v30; // esi
      FILE *v31; // eax
      int v32; // eax
      int v33; // eax
      unsigned int v34; // edi
      unsigned int k; // ecx
      unsigned int v36; // eax
      unsigned int v37; // eax
      int v38; // [esp-10h] [ebp-1C4h]
      int v39; // [esp+Ch] [ebp-1A8h]
      unsigned int v40; // [esp+Ch] [ebp-1A8h]
      int v41; // [esp+10h] [ebp-1A4h]
      unsigned int v42; // [esp+14h] [ebp-1A0h]
      unsigned int v43; // [esp+18h] [ebp-19Ch]
      unsigned __int8 v44; // [esp+1Ch] [ebp-198h]
      unsigned int v45; // [esp+1Ch] [ebp-198h]
      unsigned int v46; // [esp+24h] [ebp-190h]
      unsigned int v47; // [esp+28h] [ebp-18Ch]
      unsigned int v48; // [esp+30h] [ebp-184h]
      int v49; // [esp+34h] [ebp-180h]
      int v50; // [esp+44h] [ebp-170h]
      int v51; // [esp+48h] [ebp-16Ch]
      int v52; // [esp+4Ch] [ebp-168h]
      int v53; // [esp+50h] [ebp-164h]
      int v54; // [esp+54h] [ebp-160h]
      int v55; // [esp+58h] [ebp-15Ch]
      int v56; // [esp+5Ch] [ebp-158h]
      int v57; // [esp+60h] [ebp-154h]
      __int128 v58; // [esp+64h] [ebp-150h]
      __int128 v59; // [esp+74h] [ebp-140h]
      __int128 v60; // [esp+84h] [ebp-130h]
      char v61[8]; // [esp+94h] [ebp-120h]
      char Str[260]; // [esp+ACh] [ebp-108h]
    
      v58 = xmmword_132A8;
      strcpy(v61, "~64*");
      v59 = xmmword_132B8;
      v47 = 0;
      v60 = xmmword_132C8;
      do
      {
        v39 = 1000;
        v42 = 0;
        v4 = __rdtsc();
        v43 = 0;
        do
        {
          v5 = v4;
          v4 = __rdtsc();
          v42 = (v4 - v5 + __PAIR64__(v42, v43)) >> 32;
          v43 += v4 - v5;
          --v39;
        }
        while ( v39 );
        v41 = 1000;
        v44 = 0;
        _RAX = __rdtsc();
        v46 = 0;
        v7 = _RAX;
        v40 = v44;
        do
        {
          LODWORD(_RAX) = 1;
          v45 = HIDWORD(_RAX);
          v48 = v7;
          v38 = a1;
          __asm { cpuid }
          _EAX = 2;
          __asm { cpuid }
          _EAX = 3;
          __asm { cpuid }
          a1 = v38;
          v49 = _EAX;
          _RAX = __rdtsc();
          v7 = _RAX;
          v22 = _RAX - __PAIR64__(v45, v48) + __PAIR64__(v46, v40);
          v40 += _RAX - v48;
          v23 = v41-- == 1;
          v46 = HIDWORD(v22);
        }
        while ( !v23 );
        if ( v22 / 1000 - __SPAIR64__(v42, v43) / 1000 > 7000 || v49 == 305419896 )
          return sub_11000(real_main, 0x3A5u);
        ++v47;
      }
      while ( v47 < 0x3E8 );
      v24 = GetModuleHandleA(0);
      v50 = 1568;
      v51 = 2330;
      v25 = 0;
      v52 = 2732;
      v53 = 2734;
      v54 = 3087;
      v55 = 3589;
      v56 = 3686;
      v57 = 4077;
      do
      {
        if ( *((unsigned __int8 *)sub_11000 + (_DWORD)v24 + v25) == (((unsigned __int8)(v25 + 66) - v25) ^ 0x8E) )
        {
          v26 = 0;
          while ( v25 != *(&v50 + v26) )
          {
            if ( (unsigned int)++v26 >= 8 )
              return sub_11000(real_main, 0x3A5u);
          }
        }
        ++v25;
      }
      while ( v25 < 0x13CA );
      if ( NtCurrentPeb()->BeingDebugged )
        return sub_11000(real_main, 0x3A5u);
      for ( i = 0; i < 0x539; ++i )
      {
        for ( j = 0; j < 0x34; ++j )
        {
          if ( !(i % 3) || !(i % 7) )
            *((_BYTE *)&v58 + j) ^= i;
        }
      }
      v30 = (char *)calloc(0xEu, 1u);
      printf("Enter key: ");
      v31 = _acrt_iob_func(0);
      fgets(v30, 14, v31);
      v30[13] = 0;
      v32 = 0;
      if ( *v30 )
      {
        do
          ++v32;
        while ( v30[v32] );
      }
      if ( v30[v32 - 1] == 10 )
      {
        v33 = 0;
        if ( *v30 )
        {
          do
            ++v33;
          while ( v30[v33] );
        }
        v30[v33 - 1] = 0;
      }
      v34 = 0;
      if ( *v30 )
      {
        do
          ++v34;
        while ( v30[v34] );
      }
      for ( k = 0; k < 0x35; ++k )
      {
        Str[k] = *((_BYTE *)&v58 + k) ^ v30[k % v34];
        v36 = k;
      }
      Str[v36] = 0;
      v37 = v36 + 1;
      if ( v37 < 0x100 )
      {
        Str[v37] = 0;
        if ( !strstr(Str, byte_132E0) )
        {
          printf("\nERROR: Wrong key!\n");
          exit(0);
        }
        printf("Here is your prize:\n\n\t%s\n", Str);
        exit(0);
      }
      __report_rangecheckfailure();
      return main(a2, a3, a4);
    }

    이거를 알기 쉽게 RENAME 하였다.

    int __usercall real_main@<eax>(int a1@<ebx>, int a2, const char **a3, const char **a4)
    {
      unsigned __int64 v4; // rax
      unsigned __int64 v5; // kr08_8
      unsigned int v7; // esi
      signed __int64 v22; // kr18_8
      bool v23; // zf
      HMODULE v24; // esi
      unsigned int v25; // edx
      int v26; // eax
      unsigned int i; // ecx
      unsigned int j; // esi
      char *string; // esi
      FILE *v31; // eax
      int length; // eax
      int length_2; // eax
      unsigned int length_3; // edi
      unsigned int k; // ecx
      unsigned int v36; // eax
      unsigned int v37; // eax
      int v38; // [esp-10h] [ebp-1C4h]
      int v39; // [esp+Ch] [ebp-1A8h]
      unsigned int v40; // [esp+Ch] [ebp-1A8h]
      int v41; // [esp+10h] [ebp-1A4h]
      unsigned int v42; // [esp+14h] [ebp-1A0h]
      unsigned int v43; // [esp+18h] [ebp-19Ch]
      unsigned __int8 v44; // [esp+1Ch] [ebp-198h]
      unsigned int v45; // [esp+1Ch] [ebp-198h]
      unsigned int v46; // [esp+24h] [ebp-190h]
      unsigned int v47; // [esp+28h] [ebp-18Ch]
      unsigned int v48; // [esp+30h] [ebp-184h]
      int v49; // [esp+34h] [ebp-180h]
      int v50; // [esp+44h] [ebp-170h]
      int v51; // [esp+48h] [ebp-16Ch]
      int v52; // [esp+4Ch] [ebp-168h]
      int v53; // [esp+50h] [ebp-164h]
      int v54; // [esp+54h] [ebp-160h]
      int v55; // [esp+58h] [ebp-15Ch]
      int v56; // [esp+5Ch] [ebp-158h]
      int v57; // [esp+60h] [ebp-154h]
      __int128 v58; // [esp+64h] [ebp-150h]
      __int128 v59; // [esp+74h] [ebp-140h]
      __int128 v60; // [esp+84h] [ebp-130h]
      char v61[8]; // [esp+94h] [ebp-120h]
      char Str[260]; // [esp+ACh] [ebp-108h]
    
      v58 = xmmword_132A8;
      strcpy(v61, "~64*");
      v59 = xmmword_132B8;
      v47 = 0;
      v60 = xmmword_132C8;
      do
      {
        v39 = 1000;
        v42 = 0;
        v4 = __rdtsc();
        v43 = 0;
        do
        {
          v5 = v4;
          v4 = __rdtsc();
          v42 = (v4 - v5 + __PAIR64__(v42, v43)) >> 32;
          v43 += v4 - v5;
          --v39;
        }
        while ( v39 );
        v41 = 1000;
        v44 = 0;
        _RAX = __rdtsc();
        v46 = 0;
        v7 = _RAX;
        v40 = v44;
        do
        {
          LODWORD(_RAX) = 1;
          v45 = HIDWORD(_RAX);
          v48 = v7;
          v38 = a1;
          __asm { cpuid }
          _EAX = 2;
          __asm { cpuid }
          _EAX = 3;
          __asm { cpuid }
          a1 = v38;
          v49 = _EAX;
          _RAX = __rdtsc();
          v7 = _RAX;
          v22 = _RAX - __PAIR64__(v45, v48) + __PAIR64__(v46, v40);
          v40 += _RAX - v48;
          v23 = v41-- == 1;
          v46 = HIDWORD(v22);
        }
        while ( !v23 );
        if ( v22 / 1000 - __SPAIR64__(v42, v43) / 1000 > 7000 || v49 == 0x12345678 )
          return protect_(real_main, 0x3A5u);
        ++v47;
      }
      while ( v47 < 0x3E8 );
      v24 = GetModuleHandleA(0);
      v50 = 1568;
      v51 = 2330;
      v25 = 0;
      v52 = 2732;
      v53 = 2734;
      v54 = 3087;
      v55 = 3589;
      v56 = 3686;
      v57 = 4077;
      do
      {
        if ( *((unsigned __int8 *)protect_ + (_DWORD)v24 + v25) == (((unsigned __int8)(v25 + 0x42) - v25) ^ 0x8E) )
        {
          v26 = 0;
          while ( v25 != *(&v50 + v26) )
          {
            if ( (unsigned int)++v26 >= 8 )
              return protect_(real_main, 0x3A5u);
          }
        }
        ++v25;
      }
      while ( v25 < 0x13CA );
      if ( NtCurrentPeb()->BeingDebugged )
        return protect_(real_main, 0x3A5u);
      for ( i = 0; i < 0x539; ++i )
      {
        for ( j = 0; j < 0x34; ++j )
        {
          if ( !(i % 3) || !(i % 7) )
            *((_BYTE *)&v58 + j) ^= i;
        }
      }
      string = (char *)calloc(0xEu, 1u);
      printf("Enter key: ");
      v31 = _acrt_iob_func(0);
      fgets(string, 14, v31);
      string[13] = 0;
      length = 0;
      if ( *string )
      {
        do
          ++length;
        while ( string[length] );
      }
      if ( string[length - 1] == '\n' )
      {
        length_2 = 0;
        if ( *string )
        {
          do
            ++length_2;
          while ( string[length_2] );
        }
        string[length_2 - 1] = 0;
      }
      length_3 = 0;
      if ( *string )
      {
        do
          ++length_3;
        while ( string[length_3] );
      }
      for ( k = 0; k < 0x35; ++k )
      {
        Str[k] = *((_BYTE *)&v58 + k) ^ string[k % length_3];
        v36 = k;
      }
      Str[v36] = 0;
      v37 = v36 + 1;
      if ( v37 < 0x100 )
      {
        Str[v37] = 0;
        if ( !strstr(Str, "@flare-on.com") )
        {
          printf("\nERROR: Wrong key!\n");
          exit(0);
        }
        printf("Here is your prize:\n\n\t%s\n", Str);
        exit(0);
      }
      __report_rangecheckfailure();
      return main(a2, a3, a4);
    }

    전체적인 함수는 순서가 다음처럼 되어있는 데,

    다음 부분을 기준으로 함수가 입력값을 받는 것으로 보이므로 무시해도 상관없을 것이다.

    아마 그 위의 과정은 디버거로 보고있는 지 확인하는 과정이라 예상된다.

    위에 코드에서 중간에 NtCurrentPeb()→BeingDebugged가 있는 걸 보니, 이 부분에서 디버깅중인지 확인하는 듯 하다.

    어셈블리로는 브레이크 포인트를 걸은 부분이 그 부분이다. 이 부분의 ZF를 조작해줘서 통과하게 만들고 디버깅을 계속 진행했다.

    이부분에서 나중에 비교해야 하는 값과 특정 작업을 거치기에 그냥 거치도록 진행시켜주었다.

    그리고 아이다 꺼짐.... ㅜㅜ

    결국에 우리가 주시해야하는 부분은 이만큼이다.

    입력값을 주기전까지는 저절로 진행이 되니 우리가 fgets에서 값을 주고 이를 비교해서 flag를 출력하는 것이기 때문이다.

    여기서 보면 fakeflag에서는 값을 알려줬는 데, 여기서는 그런 것이 없어서 잠깐 고민했다.

    금방 답을 찾을 수 있었는 데, @flare-on.com의 길이가 13글자였다. 그리고 입력받는값이 14개, string의 길이가 13개였다. xor하는 값이 @flare-on.com인지 확인을 하는 과정으로 flag를 찾으면 된다고 생각했다.

      string = (char *)calloc(0xEu, 1u);
      printf("Enter key: ");
      v31 = _acrt_iob_func(0);
      fgets(string, 14, v31);
      string[13] = 0;
      length = 0;
      if ( *string )
      {
        do
          ++length;
        while ( string[length] );
      }
      if ( string[length - 1] == '\n' )
      {
        length_2 = 0;
        if ( *string )
        {
          do
            ++length_2;
          while ( string[length_2] );
        }
        string[length_2 - 1] = 0;
      }
      length_3 = 0;
      if ( *string )
      {
        do
          ++length_3;
        while ( string[length_3] );
      }
      for ( k = 0; k < 0x35; ++k )
      {
        Str[k] = *((_BYTE *)&v58 + k) ^ string[k % length_3];
        v36 = k;
      }
      Str[v36] = 0;
      v37 = v36 + 1;
      if ( v37 < 0x100 )
      {
        Str[v37] = 0;
        if ( !strstr(Str, "@flare-on.com") )
        {
          printf("\nERROR: Wrong key!\n");
          exit(0);
        }
        printf("Here is your prize:\n\n\t%s\n", Str);
        exit(0);
      }
      __report_rangecheckfailure();
      return main(a2, a3, a4);
    }

    참고로, xor하는 값의 경우에는 &v58이 스택주소인데, 스택에서 값을 0x35개 가져왔다. 그리고 그 값을 아래 파이썬 스크립트에서는 secret이라 함.

    이를 파이썬 스크립트로 짰고, 실행해보니, 3HeadedMonkey 가 나오더라...

    solution

    Key = [0] * 14
    flareon = "@flare-on.com"
    secret = 'z\x17\x084\x171;%[\x18.:\x15V\x0e\x11>\r\x11;$!1\x06<&|<\r$\x16:\x14y\x01:\x18ZXs.\t\x00\x16\x00I"\x01@\x08\n\x14'
    secret_13 = secret[-14:-1]
    
    for j in range(13):
        for i in range(0,0x80):
            if i ^ ord(secret_13[j]) == ord(flareon[j]):
                Key[j] = i
                break
    
    for i in range(len(Key)):
        print(chr(Key[i]),end='')

    flag:

    PS C:\Users\ryuhy\Desktop\flare-on\Flare-On6_Challenges\9 - reloadered> & '.\reloaderd - 복사본.exe'
    Enter key: 3HeadedMonkey
    Here is your prize:
    
            I_mUsT_h4vE_leFt_it_iN_mY_OthEr_p4nTs?!@flare-on.com

    캬 😂


    이번 단원에서는

    __security_init_cookie() 함수와 __scrt_common_main_seh() 함수 에 대해 알아볼 수 있었음.

    security init cookie 는 윈도우 버전의 카나리인듯하고, __scrt_common_main_seh()는 main함수를 시작하는 건데, 중간에 Call esi가 있음. 그래서 정적분석 되지 않는 부분이 실행되기도 하는 듯

    'Writeup > Wargame_Writeup' 카테고리의 다른 글

    HackCTF j0n9hyun's secret  (0) 2020.01.23
    HackCTF Unexploitable #2  (0) 2020.01.23
    [Flare-on 2019] Snake  (0) 2020.01.21
    [Flare-on 2019] Demo  (0) 2020.01.21
    [Flare-on 2019] DnsChess  (0) 2020.01.21

    댓글