본문 바로가기

security/rev

[TJCTF 2024] wt-two

rev 2번째 문제이고 자만하면 안되지만 풀었다는 것에 꽤 성장이 있었음을 느끼는 문제이다.

( 사실 그래도 진짜 쉬운 문제에 속한다. 슬럼프라 좋게 생각해도 될 것 같다. )

 

wt-two

 

사실 문제 이름과 설명에서는 전혀 추론을 못했고 바이너리 파일이 주어져서 ida로 열어보았다.

 

int __cdecl main(int argc, const char **argv, const char **envp)
{
  const char **num; // rdx
  char input_buf; // bl
  int flag_buf; // er12
  const char **n_plus; // rdx
  int v8; // eax
  const char **v9; // rdx
  int v10; // eax
  char input[64]; // [rsp+10h] [rbp-F0h] BYREF
  int flag[31]; // [rsp+50h] [rbp-B0h]
  _DWORD main_i[3]; // [rsp+CCh] [rbp-34h] BYREF
  int param1; // [rsp+D8h] [rbp-28h]
  _DWORD i[4]; // [rsp+DCh] [rbp-24h] BYREF
  int v16; // [rsp+ECh] [rbp-14h]

  if ( argc != 1 )
  {
    if ( argc == 3 )
    {
      param1 = *(_DWORD *)*argv;
      if ( !param1 || param1 == 1 )
        return 1;
      *(_QWORD *)&main_i[1] = malloc(4uLL);
      main_i[0] = param1 - 1;
      n_plus = (const char **)(unsigned int)(param1 - 1);
      **(_DWORD **)&main_i[1] = (_DWORD)n_plus;
      v16 = 0;
      v8 = main(3, (const char **)&main_i[1], n_plus);
      v16 += v8;
      v9 = (const char **)--main_i[0];
      **(_DWORD **)&main_i[1] = main_i[0];
      v10 = main(3, (const char **)&main_i[1], v9);
      return v10 + v16;
    }
LABEL_13:
    puts("Wrong...\n");
    return -1;
  }
  puts("Guess my flag!!\n");
  fgets(input, 49, _bss_start);
  if ( strlen(input) <= 0x1D )
    goto LABEL_13;
  *(_QWORD *)&i[1] = malloc(4uLL);
  flag[0] = 117;
  flag[1] = 107;
  flag[2] = 97;
  flag[3] = 119;
  flag[4] = 99;
  flag[5] = 115;
  flag[6] = 122;
  flag[7] = 97;
  flag[8] = 15;
  flag[9] = 67;
  flag[10] = 49;
  flag[11] = 245;
  flag[12] = 196;
  flag[13] = 269;
  flag[14] = 533;
  flag[15] = 948;
  flag[16] = 1618;
  flag[17] = 2679;
  flag[18] = 4154;
  flag[19] = 6658;
  flag[20] = 10915;
  flag[21] = 17756;
  flag[22] = 28613;
  flag[23] = 46360;
  flag[24] = 75060;
  flag[25] = 121457;
  flag[26] = 196390;
  flag[27] = 317717;
  flag[28] = 514246;
  flag[29] = 832085;
  for ( i[0] = 0; i[0] <= 29; ++i[0] )
  {
    num = (const char **)i[0];
    **(_DWORD **)&i[1] = i[0];
    input_buf = input[i[0]];
    flag_buf = flag[i[0]];
    if ( input_buf != ((unsigned __int8)flag_buf ^ (unsigned __int8)main(3, (const char **)&i[1], num)) )
      goto LABEL_13;
  }
  puts("Nice!!!");
  return 1;
}

 

main 코드는 위와 같다. 소스 값들을 주고 역계산을 하는 전형적인 쉬운 문제이지만 처음 풀어보는 매커니즘인 것 같다.

문자열 길이나 if문 경계 값을 봐도 flag의 길이가 30인 것은 바로 알 수 있다.

 

  for ( i[0] = 0; i[0] <= 29; ++i[0] )
  {
    num = (const char **)i[0];
    **(_DWORD **)&i[1] = i[0];
    input_buf = input[i[0]];
    flag_buf = flag[i[0]];
    if ( input_buf != ((unsigned __int8)flag_buf ^ (unsigned __int8)main(3, (const char **)&i[1], num)) )
      goto LABEL_13;
  }

 

이후 이 for문 부터가 중요해지는데 i[0]를 보고 헷갈렸는데 사실은 배열이 아닌 별의미 없는 반복 index로 활용한다.

input 문자열과 flag 문자열에 어떤 값을 xor한 값을 하나씩 확인하며 다를 경우 goto LABEL_13으로 fail 부분으로 보내버린다. ( 참고로 LABEL_13 이렇게 코드 앞에 있어도 goto문이 아니면 실행이 안된다. ) 

결국 어떤 값은 따지고 보면 main(3, index, index)이다.

 

  if ( argc != 1 )
  {
    if ( argc == 3 )
    {
      param1 = *(_DWORD *)*argv;
      if ( !param1 || param1 == 1 )
        return 1;
      *(_QWORD *)&main_i[1] = malloc(4uLL);
      main_i[0] = param1 - 1;
      n_plus = (const char **)(unsigned int)(param1 - 1);
      **(_DWORD **)&main_i[1] = (_DWORD)n_plus;
      v16 = 0;
      v8 = main(3, (const char **)&main_i[1], n_plus);
      v16 += v8;
      v9 = (const char **)--main_i[0];
      **(_DWORD **)&main_i[1] = main_i[0];
      v10 = main(3, (const char **)&main_i[1], v9);
      return v10 + v16;
    }

 

argc에 3을 쥐어주고 main을 돌리기 때문에 if문 안쪽 계산까지 실행되는 것을 알 수 있다.

if문 안쪽 계산을 결국 따지고 보면 main(3, a, b)일 때, a가 0,1일 경우 1을 반환하고 아닐 경우는

main(3, a-1, b-1), main(3, a-2, b-2) 이렇게 재귀호출을 두 번씩 하는 것을 볼 수 있다.

argc를 경계로 값 생성을 위해 main을 반복시키는 구조이다. 곱씹을수록 재밌는 구조인 것 같다.

 

#include <stdio.h>

int func(int num1, int num2){
    if(num1 == 0 || num1 == 1)
        return 1;
    else{
        int n1 = func(num1-1, num2-1);
        int n2 = func(num1-2, num2-1);
        return n1+n2;
    }
}

int main(){
    int flag[30]={ 117, 107, 97, 119, 99, 115, 122, 97, 15, 67, 49, 245, 196, 269, 533,
        948, 1618, 2679, 4154, 6658, 10915, 17756, 28613, 46360, 75060, 121457, 196390,
        317717, 514246, 832085 };
    
    for(int i=0; i<30; i++){
        printf("%c", flag[i]^func(i,i));
    }
}

 

파악한 매커니즘을 거꾸로 이용하는 decrypt를 c코드로 짜봤다. 풀었을 때는 뿌듯했는데 막상 보니까 짧아서 엄청 쉬웠던 문제인가 싶기도 하다...

 

flag

 

매커니즘은 파악했는데 또 decrypt 코드가 작동 안되서 못 풀 수도 있다고 생각했는데 1트만에 되서 놀랐다. 어쨋든 기분은 좋다.

'security > rev' 카테고리의 다른 글

[TJCTF 2024] guess-what  (0) 2024.05.18
[MireaCTF] c1ng3 checker 풀이  (0) 2024.05.01
[Dreamhack] ez_rev 풀이  (0) 2024.04.30
[Dreamhack] rev-basic-3  (0) 2024.04.30
[Dreamhack] rev-basic-2  (0) 2024.04.30