Direct3D FPS

A Direct3D game where the win condition is reached by patching the check rather than playing it.

2026.08.18 Reversing original post

문제를 살펴보자. 파일을 실행시키면 게임이 나온다. 둘러보면 뜬금없이 카라 사진과 군고구마들이 돌아다닌다. 군고구마 를 가까이서 총으로 쏠수있고, 고구마랑 닿으면 hp 가 닳아서 리스폰 장소로 스폰된다. 코드를 한번보자.

Figure 1

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

Figure 2

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

Figure 3

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

Figure 4

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

Figure 5

고구마 인덱스 계산: 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 플러그인을 사용함).

Figure 6

구조체 주소를 계산하자! 구조체의 RVA 를 RAW 로 변환했다 (RVA to RAW 공식을 기억하자. RAW = RVA - Section.VirtualAddress + Section.PointerToRawData). HxD 에서 해당 오프셋을 저장해 파일로 저장해주었다!

RVA: 0x9184 ~ 0xF8A4
RAW: 0x6F84 ~ 0xD6A4

같이 xor 할 플래그도 찾아준다!

Figure 7

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