CodeEngn Reversing Advance_Level10

이름을 입력하면 시리얼을 이름에 맞추어 생성하는 것 같다. 키젠을 만들거나 브루트포스를 할 수 있겠다.

PEiD가 감지한 패킹은 없다. 브루트포스 하는 스크립트를 짜봤다. 대소문자 구분이 사실상 없기 때문에 소문자만 포함했다.

import os, sys
import subprocess
 
testChar = ''
success = ''
 
result = ""
 
words = range(0x30,0x39) + range(0x61,0x7A) 
counter = 0
 
for i in words :
    for j in words :
        for k in words:
            for n in words:
                test = chr(i) + chr(j) + chr(k) + chr(n) + '\n' + "WWWCCCJJJRRR" + '\n'
                theproc = subprocess.Popen('10.exe', 
                                       shell = True, 
                                       stdin=subprocess.PIPE,
                                       stdout=subprocess.PIPE,
                                       stderr=subprocess.PIPE)
 
                msg = test.encode()
                stdout_value, stderr_value = theproc.communicate(msg)
      
                result = str(stdout_value)
                
                if counter % 0x100 == 0:
                    print(chr(i) + chr(j) + chr(k) + chr(n))
                
                counter +=1
                
                
                #find flag
                if "Good" in result:
                    print ("name is :" + chr(i) + chr(j) + chr(k) + chr(n) + " " + result)
                    sys.stdout.flush()
                    exit()

IDA의 헥스레이를 이용해 디컴파일한다. v11 변수에 성공 여부가 저장된다. 이는 check_serial의 리턴값이다.

check_serial의 첫 번째 인자는 이름, 두 번째 인자는 시리얼이다. check_serial 함수를 디컴파일 하면 온갖 string 할당문 때문에 알아보기 힘들다. 그래서 check_serial 함수의 수도코드를 수정하여 읽기 쉽게 한 후 로직을 분석했다.

  //---문자열 할당--- 
  std::string::string(
    &seriallist,
    "AJXGRFV6BKOW3Y9TM4S2ZU I70H5Q81PDECLNAJXGRFV6BKOW3Y9TM4S2ZU I70H5Q81PDECLNAJXGRFV6BKOW3Y9TM4S2ZU I70H5Q81PDECLN",
    (int)&name_loc);
    
  std::string::string(&name_loc, (char *)&unk_443070, (int)&serial_loc);
  std::string::string(&serial_loc, (char *)&unk_443070, (int)&temp2);
 
 
  //---이름과 시리얼을 대문자로 변환---
  temp_ptr1 = &temp2;
  std::string::string(&temp1, name);
  StringToUpper((int)temp_ptr1, (std::string *)&temp1);
  std::string::operator=(name, &temp2);
 
  temp_ptr2 = &temp1;
  std::string::string(&temp2, serial);
  StringToUpper((int)temp_ptr2, (std::string *)&temp2);
  std::string::operator=(serial, &temp1);
 
 
  //---시리얼 체크---
  result = 1;
  for ( i = 0; i <= 3; ++i )
  {
    //이름의 길이로 계산
    namelen= std::string::length(name);
    *(double *)&v7 = (long double)((namelen>> 2) + i * (namelen>> 2));
    v3 = floor(*(double *)&v7); 
    v15 = (signed __int64)(v3 - 1.0);
    //계산한 숫자번째의 글자 선택
    v4 = (char *)std::string::at(name, v15);
    std::string::operator=(&name_loc, *v4);
 
    //각 글자마다 3글자의 시리얼 체크
    for ( j = 3 * i; 3 * i + 3 > j; ++j )
    {
 
      v5 = (char *)std::string::at(serial, j);
      std::string::operator=(&serial_loc, *v5);
 
      std::string::string(&temp1, &serial_loc);
      serial_char = (const std::string *)&temp1;
 
      std::string::string(&temp2, &seriallist);
 
      // 시리얼의 j번째 값이 seriallist에서 2번째로 나타나는 인덱스 구함
      seriallist_idx = stringFindSecond((std::string *)&temp2, serial_char);
 
      std::string::string(&temp1, &name_loc);
      name_char = (const std::string *)&temp1;
      
      // 앞에서 찾은 이름 값이 seriallist에서 2번째로 나타나는 인덱스 구함
      std::string::string(&temp2, &seriallist);
      v21 = stringFindSecond((std::string *)&temp2, name_char);
 
      // name[index]의 값이 seriallist에서 2번쨰로 나타나는 인덱스와
      // serial에서 j번째 값이 seriallist에서 2번째로 나타난 인덱스
      // 의 차이가 5 이하여야 한다.
      if ( std::abs(seriallist_idx - v21) > 5 )
        result = 0;
    }
  }
 
  return result;
}

이 조건이라면 이름과 키를 같게 한다면 무조건 통과할 수 있다. 다른 글자를 섞으려면 이름 한 글자 당 세 글자의 시리얼을 쓰면 쉽다. WWWCCCJJJRRR은 3글자씩 동일하기에, name이 각 글자와 같다면(wcjr) j를 인덱스로 하는 for문을 쉽게 통과한다.

하지만 문제에는 조건이 있다. 일단 4글자이며 숫자, 알파벳 순서로 우선 순위가 있다. 이는 seriallist가 단순히 같은 패턴의 반복인 것을 이용하면 쉽게 해결할 수 있다. 다음 패턴으로 반복된다.

AJXGRFV6BKOW3Y9TM4S2ZU I70H5Q81PDECLNAJXGRFV6BKOW3Y9TM4S2ZU I70H5Q81PDECLNAJXGRFV6BKOW3Y9TM4S2ZU I70H5Q81PDECLN”

AJXGRFV6BKOW3Y9TM4S2ZU와 I70H5Q81PDECLN가 반복되기에, w, c, j, r과 5바이트 내에서 낮은 알파벳을 선택하면 된다.

W 근처의 가장 높은 우선순위는 3, C 근처는 1, J 근처는 a, r 근처는 6이다.

따라서 31a6이 플래그이다.


tags: writeup, reversing, pe file, windows, x86asm, bruteforce, keygen