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