lhywk 님의 블로그

[Dreamhack] Stop before stops! 본문

Reversing/Dreamhack

[Dreamhack] Stop before stops!

lhywk 2026. 2. 3. 14:18

문제

무슨 뜻일까요? 궁금해진 카루가 프로그램을 분석해봅니다.
플래그를 알려주는 기능이 숨겨져 있었군요.

영문자, 숫자, 특수문자를 적절히 사용하여 플래그를 획득해 봅시다.

문제 풀이

바이너리 실행

문제에 주어진 바이너리를 실행하면 에러 문구가 출력됩니다.

IDA를 통해 소스코드를 분석해 보겠습니다.

int __fastcall main(int argc, const char **argv, const char **envp)
{
  __int64 v4; // rax
  __int64 v5; // rax
  __int64 v6; // rax
  __int64 v7; // rax
  int v8; // ebx
  __int64 v9; // rax
  unsigned int v10; // eax
  __int64 v11; // rax
  char v12; // bl
  char v13; // r12
  bool v14; // r13
  __int64 v15; // rax
  __int64 v16; // rax
  _DWORD v18[12]; // [rsp+10h] [rbp-28D0h] BYREF
  _QWORD v19[3]; // [rsp+40h] [rbp-28A0h] BYREF
  char v20; // [rsp+5Fh] [rbp-2881h] BYREF
  _BYTE v21[88]; // [rsp+60h] [rbp-2880h] BYREF
  _BYTE v22[8]; // [rsp+B8h] [rbp-2828h] BYREF
  _BYTE v23[5008]; // [rsp+C0h] [rbp-2820h] BYREF
  _BYTE v24[5008]; // [rsp+1450h] [rbp-1490h] BYREF
  _BYTE v25[32]; // [rsp+27E0h] [rbp-100h] BYREF
  _BYTE v26[32]; // [rsp+2800h] [rbp-E0h] BYREF
  _BYTE v27[32]; // [rsp+2820h] [rbp-C0h] BYREF
  _BYTE v28[47]; // [rsp+2840h] [rbp-A0h] BYREF
  char v29; // [rsp+286Fh] [rbp-71h] BYREF
  _BYTE v30[36]; // [rsp+2870h] [rbp-70h] BYREF
  unsigned int v31; // [rsp+2894h] [rbp-4Ch]
  __int64 v32; // [rsp+2898h] [rbp-48h]
  _QWORD *v33; // [rsp+28A0h] [rbp-40h]
  int v34; // [rsp+28ACh] [rbp-34h]
  const char *v35; // [rsp+28B0h] [rbp-30h]
  unsigned int *v36; // [rsp+28B8h] [rbp-28h]

  v35 = "`~!@$%^*+-_=:;?<>";
  if ( argc == 2 )
  {
    if ( strlen(argv[1]) == 24 && (unsigned __int8)std::operator==<char>(&vlkjbkldsajfksdkfl2[abi:cxx11], argv[1]) )
    {
      v4 = std::operator<<<std::char_traits<char>>(&std::cout, "Congratulations! The flag is: ");
      std::ostream::operator<<(v4, &std::endl<char,std::char_traits<char>>);
      v5 = std::operator<<<std::char_traits<char>>(&std::cout, "DH{");
      v6 = std::operator<<<char>(v5, &lkjasvhkjsldhkl[abi:cxx11]);
      v7 = std::operator<<<std::char_traits<char>>(v6, "}");
      std::ostream::operator<<(v7, &std::endl<char,std::char_traits<char>>);
      return 0;
    }
LABEL_10:
    v9 = std::operator<<<std::char_traits<char>>(&std::cout, "Argument error occured! Aborting...");
    std::ostream::operator<<(v9, &std::endl<char,std::char_traits<char>>);
    return 1;
  }
  if ( argc != 3 )
    goto LABEL_10;
  std::random_device::random_device((std::random_device *)v24);
  v10 = std::random_device::operator()(v24);
  std::mersenne_twister_engine<unsigned long,32ul,624ul,397ul,31ul,2567483615ul,11ul,4294967295ul,7ul,2636928640ul,15ul,4022730752ul,18ul,1812433253ul>::mersenne_twister_engine(
    v23,
    v10);
  std::uniform_int_distribution<int>::uniform_int_distribution(v22, 3, 7);
  v34 = std::uniform_int_distribution<int>::operator()<std::mersenne_twister_engine<unsigned long,32ul,624ul,397ul,31ul,2567483615ul,11ul,4294967295ul,7ul,2636928640ul,15ul,4022730752ul,18ul,1812433253ul>>(
          v22,
          v23);
  std::deque<char>::deque(v21);
  v20 = 0;
  v18[0] = 1;
  v18[1] = 2;
  v18[2] = 3;
  v18[3] = 4;
  v18[4] = 5;
  v18[5] = 6;
  v18[6] = 7;
  v18[7] = 8;
  v18[8] = 9;
  v18[9] = 10;
  v19[1] = 10;
  v19[0] = v18;
  v33 = v19;
  v36 = (unsigned int *)std::initializer_list<int>::begin(v19);
  v32 = std::initializer_list<int>::end(v33);
  while ( v36 != (unsigned int *)v32 )
  {
    v31 = *v36;
    if ( (int)v31 > v34 )
    {
      generate_random_string[abi:cxx11](v25);
      std::string::operator=(&vlkjbkldsajfksdkfl2[abi:cxx11], v25);
      std::string::~string(v25);
    }
    std::ostream::put((std::ostream *)&std::cout, 91);
    std::ios_base::width((std::ios_base *)&unk_D208, 2);
    std::ios::fill(&unk_D208, 48);
    v11 = std::ostream::operator<<(&std::cout, v31);
    std::operator<<<std::char_traits<char>>(v11, "] Input a character >> ");
    std::operator>><char,std::char_traits<char>>(&std::cin, &v20);
    if ( (unsigned __int64)std::deque<char>::size(v21) > 2 )
      std::deque<char>::pop_front(v21);
    std::deque<char>::push_back(v21, &v20);
    v12 = 0;
    v13 = 0;
    v14 = 0;
    if ( (unsigned __int64)std::deque<char>::size(v21) > 2 )
    {
      std::deque<char>::begin(v27, v21);
      std::deque<char>::end(v28, v21);
      std::allocator<char>::allocator(&v29);
      v12 = 1;
      std::string::basic_string<std::_Deque_iterator<char,char &,char *>,void>(v26, v27, v28, &v29);
      v13 = 1;
      if ( std::string::find(&charstr, v26, 0) != -1 )
        v14 = 1;
    }
    if ( v13 )
      std::string::~string(v26);
    if ( v12 )
      std::allocator<char>::~allocator(&v29);
    if ( v14 )
    {
      v15 = std::operator<<<std::char_traits<char>>(&std::cout, "\n##### Bruteforce attack is not allowed! Aborting...");
      std::ostream::operator<<(v15, &std::endl<char,std::char_traits<char>>);
      v8 = 1;
      goto LABEL_28;
    }
    std::string::basic_string(v30, &vlkjbkldsajfksdkfl2[abi:cxx11]);
    find_in_str((unsigned int)v20, v30);
    std::string::~string(v30);
    ++v36;
  }
  v16 = std::operator<<<std::char_traits<char>>(&std::cout, "\n##### Max input times reached! Shutting down...");
  std::ostream::operator<<(v16, &std::endl<char,std::char_traits<char>>);
  v8 = 0;
LABEL_28:
  std::deque<char>::~deque(v21);
  std::random_device::~random_device((std::random_device *)v24);
  return v8;
}

전체 소스코드입니다.

여기서 플래그를 출력해 주는 곳의 소스코드를 분석하겠습니다.

if ( argc == 2 )
  {
    if ( strlen(argv[1]) == 24 && (unsigned __int8)std::operator==<char>(&vlkjbkldsajfksdkfl2[abi:cxx11], argv[1]) )
    {
      v4 = std::operator<<<std::char_traits<char>>(&std::cout, "Congratulations! The flag is: ");
      std::ostream::operator<<(v4, &std::endl<char,std::char_traits<char>>);
      v5 = std::operator<<<std::char_traits<char>>(&std::cout, "DH{");
      v6 = std::operator<<<char>(v5, &lkjasvhkjsldhkl[abi:cxx11]);
      v7 = std::operator<<<std::char_traits<char>>(v6, "}");
      std::ostream::operator<<(v7, &std::endl<char,std::char_traits<char>>);
      return 0;
    }
LABEL_10:
    v9 = std::operator<<<std::char_traits<char>>(&std::cout, "Argument error occured! Aborting...");
    std::ostream::operator<<(v9, &std::endl<char,std::char_traits<char>>);
    return 1;
  }

1. argc == 2 인자를 1개 받습니다.

2. if ( strlen(argv[1]) == 24 && (unsigned __int8)std::operator==<char>(&vlkjbkldsajfksdkfl2[abi:cxx11], argv[1]) ) 

인자의 문자열 길이는 24자이어야 하고 vlkjbkldsajfksdkfl2 값과 같아야 합니다.

3. 조건을 만족할 경우 플래그를 출력합니다.

그럼 여기서 vlkjbkldsajfksdkfl2 값을 알아내야 합니다.

xref을 하여 따라가 보겠습니다.

sldkhjlhvfiuh 함수가 vlkjbkldsajfksdkfl2에 값을 채워 넣습니다.

sldkhjlhvfiuh 함수를 분석해 보겠습니다. 

6줄 라인에 salkdhvkhlklhkjfdhkjd::segment 변수 값을 읽어서 a1에 복사해서 넣습니다.

그 뒤 a1을 리턴합니다.

여기까지의 흐름은

1.  vlkjbkldsajfksdkfl2 값과 같으면 플래그를 출력한다.

2.  vlkjbkldsajfksdkfl2 값은 sldkhjlhvfiuh 함수가 넣는다.

3.  sldkhjlhvfiuh 함수는 salkdhvkhlklhkjfdhkjd::segment 변수 값을 리턴한다.

4. salkdhvkhlklhkjfdhkjd::segment 변수 값을 알아내야 한다.

62~63라인을 봤을 때 Getslifdhiuil3 함수의 반환값을 v8에 넣고 segment 값이 됩니다.

Getslifdhiuil3 함수를 분석해 보겠습니다.

24번 반복하면서 data[i] = data[i] ^ 87 - i 연산으로 암호화를 수행합니다.

암호화를 수행한 값을 역연산 하게 되면 최종적으로 우리가 알고 싶은 값을 얻어낼 수 있습니다.

data 배열의 값을 확인합니다.

byte_140003000 = bytearray([0x15, 0x35, 0x07, 0x11, 0x2B, 0x63, 0x05, 0x05, 0x2A, 0x71, 
  0x20, 0x0E, 0x76, 0x23, 0x73, 0x74, 0x14, 0x3E, 0x1A, 0x35, 
  0x33, 0x08, 0x16, 0x74, 0x00])

for i in range(0x18):
    byte_140003000[i] = byte_140003000[i] ^ 87 - i
    print(chr(byte_140003000[i]),end='')

XOR 암호화는 한번 더 연산을 수행하면 복호화되므로 파이썬으로 페이로드를 작성합니다.

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

[Dreamhack] legacyopt  (0) 2026.02.05
[Dreamhack] please, please, please  (0) 2026.02.04
[Dreamhack] rev-basic-5  (0) 2026.01.27
[Dreamhack] rev-basic-7  (0) 2026.01.27
[Dreamhack] flag printer  (0) 2026.01.26