Description

Category: Reversing

Source: PlaidCTF 2019

Points: 50

Author: Jisoon Park(js00n.park)

Description:

Let's do this together. You do know how to count, don't you?

Write-up

main() 함수를 보면 decimal string 형태의 flag를 1씩 증가시키면서 입력받은 값이 flag와 동일한지 확인 후 check_flag()로 원했던 flag 값 까지 도달했는지를 확인하고 있다.

[...]

  while ( 1 )
  {
    incr_flag();
    printf("> ");
    fflush(stdout);
    fgets(s, 30, stdin);
    if ( s[0] && s[strlen(s) - 1] <= 31 )
      s[strlen(s) - 1] = 0;
    if ( strcmp(s, flag_buf) )
    {
      printf("No, the correct number is %s.\n", flag_buf);
      puts("But I believe in you. Let's try again sometime!");
      exit(1);
    }
    v4 = get_compliment();
    puts(v4);
    check_flag();

[...]

check_flag() 함수가 핵심인 것 같다. 좀 더 들여다 보자.

[...]

  for ( i = 0; ; ++i )
  {
    if ( i > 19 )
    {
      printf("PCTF{% raw %}{%s}{% endraw %}\n", flag_buf);
      exit(0);
    }
    v0 = flag_buf[i];
    v1 = v0 & 3;
    v2 = (v0 >> 2) & 3;
    v3 = (v0 >> 4) & 0xF;
    LODWORD(v4) = rol(v1 + 0xA55AA55AA559LL, 2);
    v5 = v4;
    LODWORD(v6) = rol(v2 - v4 + 0xA55AA55AA559LL, 13);
    v7 = v6;
    LODWORD(v8) = rol(v3 - v6 + 0xA55AA55AA559LL, 17);
    v9 = v8;
    v10 = v7 ^ v8 ^ v5;
    LODWORD(v11) = rol((v7 & ~(v7 ^ v8 ^ v5) | v8 & (v7 ^ v8 ^ v5)) + v5 + v3 + 68453106630LL, 3);
    v12 = v11;
    LODWORD(v13) = rol((v9 & ~v11 | v10 & v11) + v7 + v1 + 68453106630LL, 11);
    v14 = v13;
    LODWORD(v15) = rol((v12 & ~v9 | v13 & v9) + v10 + v2 + 68453106630LL, 19);
    v16 = v15;
    LODWORD(v17) = rol((v14 ^ v9 ^ v15) + v12 + v2 + 201504941903014LL, 5);
    v18 = v17;
    LODWORD(v19) = rol((v16 ^ v17 ^ v14) + v9 + v1 + 201504941903014LL, 7);
    v20 = v19;
    LODWORD(v21) = rol((v18 ^ v14 ^ v19) + v16 + v3 + 201504941903014LL, 23);
    v22 = (unsigned int)((unsigned __int64)(v20 + v21 + v18 + v14) >> 32) ^ (unsigned __int64)(v20 + v21 + v18 + v14);
    v23 = (v22 >> 16) ^ v22;
    result = (unsigned __int8)(BYTE1(v23) ^ v23);
    if ( *((_BYTE *)check_buf + i) != (BYTE1(v23) ^ (unsigned __int8)v23) )
      break;
  }

[...]

대부분의 변수가 64bit 정수로 구성되어 있고, rol()을 이용한 계산을 거친 결과가 마지막 if문의 조건을 만족해야 한다.

unsigned __int64 __cdecl rol(unsigned __int64 a1, char sh)
{
  int high; // edx
  int low; // eax
  int v4; // ebx
  __int16 v5; // si
  unsigned __int64 v6; // rax
  unsigned __int64 result; // rax

  high = a1 << sh >> 32;
  low = (_DWORD)a1 << sh;
  if ( sh & 0x20 )
  {
    high = (_DWORD)a1 << sh;
    low = 0;
  }
  v4 = low;
  v5 = high;
  v6 = a1 >> ((48 - sh) & 0x1F);
  if ( (48 - sh) & 0x20 )
  {
    LODWORD(v6) = HIDWORD(v6);
    WORD2(v6) = 0;
  }
  LODWORD(v6) = v6 | v4;
  HIDWORD(result) = (unsigned __int16)(WORD2(v6) | v5);
  return result;
}

rol() 함수는 rotate left 동작을 하는데, rotate left를 임의로 구현했더니 동작 결과가 조금 달라서 주어진 rol() 함수를 그대로 이용해야 했다.

IDA가 decompile 해준 check_flag() 함수와 rol() 함수를 그대로 python으로 옮겼더니 제대로 동작하지 않았다. 64bit 연산이 적용된 경우에 IDA의 decompile이 부정확한 경우가 있어서, assembly 코드를 보고 python 함수를 구현했다.(code)

Flag : PCTF{2052419606511006177}

이렇게 해서 풀긴 풀었는데, 풀고 나서 다시 생각해보니 flag_buf에 들어있는 값들이 복잡해 보이지만 사실 딱 10종류이다.

check_flag() 함수를 자세히 보면 flag의 각 자리수를 검사하는데, 자리수 간에 의존성이 없으니 flag_buf에 있는 10가지 숫자가 0부터 9까지의 숫자에 1:1로 대응된다.

그냥 gdb에서 1부터 10까지 넣어가면서 if문의 비교 값들을 확인해봤으면 금방 풀 수 있었을텐데 assembly 코드를 한땀한땀 보느라 너무 많은 시간을 허비했다. 다른팀들 풀이 시간을 보니 20분 정도만에 풀었던데 아마도 그렇게 푼 것 같다.

'writeups > Reversing' 카테고리의 다른 글

Easy Crack Me  (0) 2019.11.26
Elementary  (0) 2019.11.26
ELF Crumble  (0) 2019.11.26
Super Secure Vault  (0) 2019.11.26
Feed_me  (0) 2019.11.25

+ Recent posts