Direct3D FPS
A Direct3D game where the win condition is reached by patching the check rather than playing it.
문제를 살펴보자. 파일을 실행시키면 게임이 나온다. 둘러보면 뜬금없이 카라 사진과 군고구마들이 돌아다닌다. 군고구마 를 가까이서 총으로 쏠수있고, 고구마랑 닿으면 hp 가 닳아서 리스폰 장소로 스폰된다. 코드를 한번보자.

( 화면에 자꾸 마우스 캡쳐링? 현상이 일어나는데, alt + tab 으로 피할수있다.) x64 디버거로 열어주었다. 메인 함수로 이동해 문자열 찾기를 해준다.

조건문을 패치해서 게임 클리어 메세지 창이 뜨게 했다 (JE —> JNE). 그렇니까 이상한 아스키코드 창이 나왔다. 아마 정답 값이 뜨는게 맞는데, 내가 패치를 통해 클리어 창이 뜨게해서 암호화가 안풀린듯 하다.

아까전 004F39E2 주소가 플래그 값의 주소인거같다.

메모리 덤프로 이동했다. Ctrl + R 따라가 준다. 여기 코드를 분석해준다. 이 게임은 고구마(적)를 쏴서 죽이는 FPS 고구마 하나하나가 구조체 고구마를 “죽일 때마다” 플래그 버퍼(fps.A97028)의 특정 바이트가 XOR로 갱신된다. 플래그 = “어떤 고구마를 어떤 순서로 몇 번 죽였는가”의 결과임. 00A933FF ~ 00A9343F 는 고구마가 피격되었을 때 호출되는 처리 루 틴이라는 걸 알수있다.

고구마 인덱스 계산: call fps.A93440 cmp eax,FFFFFFFF je fps.A9343E mov ecx,eax imul ecx,ecx,210 ; 고구마 구조체 크기 = 0x210
- fps.A93440 → 현재 맞은 고구마의 index 반환
- eax = -1 이면 무효
유효하면: ecx = 고구마 index * 0x210 고구마 구조체 배열 접근 --------------------------------------- 고구마 사망 시: 플래그 XOR mov dword ptr [ecx+A99194],0 ; 고구마 생존 여부 = dead mov cl, byte ptr [ecx+A99184] ; 고구마 고유 ID / 키 xor byte ptr [eax+A97028], cl ; 플래그 갱신 그럼 플래그는 언제 “완성”되나?
- 게임 클리어 조건 = 모든 고구마 사망
- 모든 고구마를 죽이면:
- 플래그 버퍼(A97028)가 최종 상태
- 그 상태가 MessageBox로 출력됨
즉: flag[i] = 구조체[i] ^ 고구마_Key[i] 코드를 보면, 모든 키가 “구조체 안에 이미 들어 있다”. 즉 구조체 전체를 다 뽑아보면 키 집합을 통째로 얻을 수 있다 ------------------ 이제 그럼 고구마 구조체를 전체를 어떻게 얻는가? 어셈블리어에서 구조체의 첫 복호화 주소는 4F9194 였다는걸 기억하 자. 이미지 베이스값을 생각해보면 상대 주소는 9194 다. 하지만 PE viewer 로 보면, Size of Raw Data 각 한참 작다. 즉 —> 고구마 구조체는 프로그램이 메모리에 올라온 후, 메모리에 기록된다.
원본 파일은 고구마 구조체의 대한 데이터가 없으니까, 고구마 구조체의 대한 데이터를 메모리에 저장된 상태에서 새 exe 를 만들어서, 구조체에 대한 데이터를 볼수있는 파일을 만들자 (64dbg 의 Syllica 플러그인을 사용함).

구조체 주소를 계산하자! 구조체의 RVA 를 RAW 로 변환했다 (RVA to RAW 공식을 기억하자. RAW = RVA - Section.VirtualAddress + Section.PointerToRawData). HxD 에서 해당 오프셋을 저장해 파일로 저장해주었다!
RVA: 0x9184 ~ 0xF8A4
RAW: 0x6F84 ~ 0xD6A4
같이 xor 할 플래그도 찾아준다!

64dbg 의 덤프창에서 주어주었다. 값은: 43 6B 66 6B 62 75 6C 69 07 25 25 29 70 17 34 39 F7 EB FA E8 B0 FD EB BC F0 A9 이다
------------------------------ 복호화 방식 flag[i] = 구조체[i] ^ 고구마_Key[i]
참고로 연산을 시킬때 중요한 부분들이 있다. 1. 실제로는 0x210 바이트 단위 구조체들이 연속으로 있는데 각 구조체의 첫 바이트만 키로 사용한다. 즉 고구마_key[i] = struct[i * 0x210].first_byte 2. 구조체 중에서 마지막 구조체는 계산에 서 제외 시킨다. 이유는 고구마 키 바이트 길이는 실제 flag 바이트 수 인데, 구조체 길이가 하나더 길어서, 마지막 구조체 는 flag와 1:1 매칭되지 않기 때문이다 그럼 닶은 Congratulation~ Game Clear! Password is Thr3EDPr0m