Position
The serial depends on the position of each character of the name, so the keygen has to invert that mapping.
** 취미로 푸는 워게임 풀이기 때문에, 전문성이나 깊이있는 이해도가 떨어 질수있음. 개인기록용으로 올리는거기 때문 에, 전문성을 원하는거면 다른 보안 블로그의 풀이를 찾아보기 바람.
파일은 두가지가 있다. 하나는 실행 파일이고 하나는 워게임의 instruction 을 담은 노트이다.

IDA/ x32dbg 에서 실행 파일을 연후 좌측 Symbols(또는 Modules/Functions) 창을 확인하고 메인 함수로 들어간다 (DLL 말고 exe). x32dbg 에서도 충분히 문제 풀이를 할수있지만, 이번에는 소스코드를 보고 싶기 때문에 아이다를 사용 한다 (F5). 32디버거로 소스 코드를 보고싶으면 단순히 어셈블리어를 ai 를 통해 하면된다. 문자열 찾기에서 “wrong” 같 은 문자열을 찾아 들어간다. 코드를 디버깅해보면 코드는 네임을 먼저 얻고 특정 calculation 을 통해 시리얼 키값을 만들 어낸다. 그후 유저에게 시리얼 키값을 인풋으로 받아낸후, calculation 을 통해 만들어넨 시리얼 키값과 유저인풋을 비교 해 correct / wrong 여부를 결정해낸다.

어떤식으로 네임으로 시리얼 키값을 만들어 내는지 분석하고, 그에 맞는 특정 시리얼 키에 맞을수있는 네임을 알아내는 파 이썬코드를 짜기위해 F5 으로 소스 코드를 본다. 확인해보면, 대략 구조는 name[0], name[1]을 사용해서 serial[0][4] 까지 생성하고 name[2], name[3]을 사용해서 serial[6][10]을 생성하는 구조이다.
밑에 코드는 참고로 원본 코드가 너 무 길기 때문에 연산 방법을 알기 위해 불필요해 보이는 부분을 정리한 코드다 int __stdcall sub_401740(int a1) { int i = 0, j; wchar_t s[4]; // 원래 v50 wchar_t t[11]; // 원래 v51 wchar_t tmp[4]; // 원래 v52 // GetWindowTextW(a1 + 304, &v50) GetInput1(s); // 길이 4 // 길이 체크 if (*(DWORD *)(s - 12) != 4) return 0; // a~z 체크 for (i = 0; i < 4; i++) { if (s[i] < ‘a’ || s[i] > ‘z’) return 0; } // 중복 문자 체크 for
(i = 0; i < 4; i++) { for (j = 0; j < 4; j++) { if (i != j && s[i] == s[j]) return 0; } } // GetWindowTextW(a1 + 420,
GetInput2(t); // 길이 11
if (*(DWORD *)(t - 12) != 11 || t[5] != '-')
return 0;
// === s[0], s[1] 기반 ===
if (t[0] != '0' + (((s[0] & 1) ? 6 : 5) + ((s[1] & 4) ? 2 : 1))) return 0;
if (t[1] != '0' + (((s[0] & 8) ? 6 : 5) + ((s[1] & 8) ? 2 : 1))) return 0;
if (t[2] != '0' + (((s[0] & 2) ? 6 : 5) + ((s[1] & 16) ? 2 : 1))) return 0;
if (t[3] != '0' + (((s[0] & 4) ? 6 : 5) + ((s[1] & 1) ? 2 : 1))) return 0;
if (t[4] != '0' + (((s[0] & 16) ? 6 : 5) + ((s[1] & 2) ? 2 : 1))) return 0;
// === s[2], s[3] 기반 ===
if (t[6] != '0' + (((s[2] & 1) ? 6 : 5) + ((s[3] & 4) ? 2 : 1))) return 0;
if (t[7] != '0' + (((s[2] & 8) ? 6 : 5) + ((s[3] & 8) ? 2 : 1))) return 0;
if (t[8] != '0' + (((s[2] & 2) ? 6 : 5) + ((s[3] & 16) ? 2 : 1))) return 0;
if (t[9] != '0' + (((s[2] & 4) ? 6 : 5) + ((s[3] & 1) ? 2 : 1))) return 0;
if (t[10] != '0' + (((s[2] & 16) ? 6 : 5) + ((s[3] & 2) ? 2 : 1))) return 0;
return 1;
디컴파일 되서 만들어낸 소스코드를 보면서 디버깅 하면 훨씬 이해가 쉽다. 밑에는 그 결과들이다:
Name = c0 c1 c2 c3
- 길이: 4
- 문자: ‘a’ ~ ‘z’
- 중복 불가
숫자 생성 규칙:
4글자 소문자 이름 → 각 문자 비트 분해 → 숫자 10개 생성 → XXXXX-YYYYY 키 생성
이 결과들이 앞에서 나온 값과 더해져 사용됩니다. 참고로 세 번째, 네 번째 문자도 생성도 동일 구조. Ai 에게 위에 분석들 과 함께, 특정 키젠에 따라 가능한 p 로 끝나는 네임 리스트를 얻는 파이썬 코드를 요구한다. 아래는 그 결과 값이다:
import string import itertools SERIAL = "76876-77776" def check(name: str, serial: str) -> bool: # name
constraints (원본 v50 검사) if len(name) != 4: return False if any(c < ‘a’ or c > ‘z’ for c in name): return False if len(set(name)) != 4: return False # serial constraints (원본 v51 검사) if len(serial) != 11 or serial[5] != ’-’: return False s = [ord(c) for c in name] t = serial # === s[0], s[1] 기반 === if int(t[0]) != ((6 if (s[0] & 1) else 5) + (2 if
(s[1] & 4) else 1)): return False if int(t[1]) != ((6 if (s[0] & 8) else 5) + (2 if (s[1] & 8) else 1)): return False if
int(t[2]) != ((6 if (s[0] & 2) else 5) + (2 if (s[1] & 16) else 1)): return False if int(t[3]) != ((6 if (s[0] & 4) else 5)
+ (2 if (s[1] & 1) else 1)): return False if int(t[4]) != ((6 if (s[0] & 16) else 5)+ (2 if (s[1] & 2) else 1)): return
False # === s[2], s[3] 기반 === if int(t[6]) != ((6 if (s[2] & 1) else 5) + (2 if (s[3] & 4) else 1)): return False if
int(t[7]) != ((6 if (s[2] & 8) else 5) + (2 if (s[3] & 8) else 1)): return False if int(t[8]) != ((6 if (s[2] & 2) else 5) +
(2 if (s[3] & 16) else 1)): return False if int(t[9]) != ((6 if (s[2] & 4) else 5) + (2 if (s[3] & 1) else 1)): return
False if int(t[10]) != ((6 if (s[2] & 16) else 5)+ (2 if (s[3] & 2) else 1)): return False return True results = []
letters = string.ascii_lowercase for a, b, c in itertools.product(letters, repeat=3): name = a + b + c + “p” # 끝이
p if len(set(name)) != 4: continue if check(name, SERIAL): results.append(name) print("Possible passwords
ending with ‘p’:”) for r in results: print(r) print(“\nTotal found:”, len(results)) 이렇게 만든 코드를 사용하면,
- bump
- cqmp
- ftmp
라는 모든 가능한 p 로 끝나는 name 이 나온다. 여기서 auth 인증되는 FlAG 값은 Bump 다.