TECH 으로 돌아가기
TECH HACKER NEWS 오늘 6분 읽기 25 READS

높이를 확인하지 않은 코드: 1985년 게임 '용병'의 좌표 검증 버그

높이를 확인하지 않은 코드: 1985년 게임 '용병'의 좌표 검증 버그
SOURCE IMAGE · HACKER NEWS

1985년 코모도어64(C64)로 나온 게임 '용병(Mercenary)'을 바이트 단위로 역분석한 기록이 공개됐다. 흥미로운 것은 게임을 시작하자마자 걸리는 이상 동작이다. 원본 코드를 명령어 그룹 단위까지 뜯어본 이 분석은 에뮬레이터 실행 결과와 시뮬레이터 계산을 서로 대조해 '바이트 정확도'를 확인했다고 밝히는데, 그 과정에서 드러난 버그의 성격이 오늘날 소프트웨어에서 흔히 보는 검증 누락 문제와 놀랄 만큼 닮아 있다.

메모리를 아끼려던 설계

당시 하드웨어의 제약은 코드 곳곳에 흔적을 남긴다. 이 게임은 렌더링 부담을 줄이려고 한 번에 건물 하나만 메모리에 올려 둔다. 플레이어가 서 있는 칸이 어느 건물을 활성화할지 결정하고, 건물 모델은 X나 Y 좌표의 상위 바이트가 바뀔 때만 로드된다($94FE-$952F). 또한 플레이어의 고도가 충분히 낮을 때만 그려지기 때문에, 건물은 해당 칸에 들어서는 순간 '툭 튀어나오듯' 나타난다. 고도 2048부터는 도로가 칸 중심을 잇는 선으로 대체되고, 고도 $200000에서는 13개의 도로가 그려진 고정 그림으로 바뀐다. 화면에 무엇을 그릴지가 철저히 좌표와 높이 값에 묶여 있는 구조다.

좌표는 같고 높이만 다른 두 지점

플레이어는 08-08 칸에 불시착하고, 그 바로 위 고도 64,997 지점에 콜로니 크래프트(Colony Craft)라는 모선이 떠 있다. 지상에는 승강기가 없어야 정상이다. 그런데 착륙 지점에서 약 14초 걸어가면 나오는, 한 변이 1,024단위인 눈에 보이지 않는 잔디밭이 E키에 대해 마치 모선 갑판의 승강기처럼 반응한다. 이유는 단순하다. 모선은 08-08 칸 바로 위에 떠 있으므로 갑판과 그 아래 잔디밭은 X, Y 좌표가 완전히 같고 오직 높이만 다르다. 그런데 E키의 판정 루틴은 칸 번호와 X, Y의 중간 바이트만 검사할 뿐 높이는 전혀 읽지 않는다. 승강기 목록의 8번 항목($B4B6/$B4BE)이 위치만으로 일치하는 것이다. 그 결과 갑판 아래 잔디밭이 승강기와 똑같이 작동한다.

E키를 누르면 플레이어는 모선 격납고인 8번 방으로 '내려간다'. 다시 올라올 때는 지표면에 도달하는 순간 $A312가 높이에 $40FF00을 더해 주어 갑판으로 정확히 나온다. 지하 방은 도시 어느 지점 아래에도 놓여 있지 않은 별도의 상자이므로, 승강대를 벗어나 걸으면 그대로 아래로 추락한다. 다만 격납고 8의 문 세 개는 모두 오브젝트 23으로 잠겨 있어 게임 초반에는 갈 수 있는 곳이 갑판과 격납고뿐이다. 사실상 유일한 탈출구는 모선 지붕에서 걸어 떨어지는 것이다. 확실하게 재현하려면 VICE 모니터에서 X, Y 좌표를 직접 지정한 뒤 E를 누르면 된다.

부분 일치가 만드는 함정

실무자 입장에서 주목할 지점은 이 버그가 '악의적 입력' 때문이 아니라 정상 최적화의 부산물이라는 점이다. 개발자는 높이 검사를 생략해 판정을 가볍게 만들었지만, 그 결과 좌표라는 부분 키(partial key)만으로 두 개체의 동일성을 판단하게 됐다. 서로 다른 두 객체가 검사되지 않는 한 축에서만 구별되는 상황은 오늘날 데이터베이스의 복합 키 누락, 캐시 키 충돌, 권한 검사에서 일부 필드만 대조하는 결함과 본질적으로 같은 구조다. 좌표를 정체성의 대리값으로 삼되 그 대리값이 유일성을 보장하지 못할 때 어떤 일이 벌어지는지를 보여 주는 교과서적 사례다.

같은 코드베이스에는 단순한 규칙이 예상 밖 결과로 이어지는 사례가 더 있다. 게임 시작과 동시에 팔야르 사령관 처남의 함선이 스스로 비행을 시작해 도시 서쪽 가장자리를 고도 32,769에서 남쪽으로 내려가다 남단에서 북단으로 순환한다. 이 함선은 최고 속도 11,475로 게임에서 가장 빠르지만 32,769단위 상공에는 설 수 없어 탑승(B키는 발밑 512단위 이내에서만 작동)이 불가능하다. 추락 중 CTRL+Q를 누르면 탈 수 없는 유령 다트가 무작위 칸에 나타나고, 자기 칸 밖에서 쏜 건물은 16패스 안에 그 칸에 들어가지 않는 한 파괴로 집계조차 되지 않는다. 모두 좌표·고도·타이머 같은 몇 개의 상태 변수와 그것을 언제 읽는지에 따라 갈리는 창발적 동작이다.

다만 이 분석은 한 명의 역분석 기록이며 게임 보존과 기술적 호기심의 영역에 가깝다는 점은 짚어 둘 필요가 있다. 코드에서 주소로도 참조되지 않는 돛대(오브젝트 50)의 용도처럼 여전히 미상인 부분이 남아 있고, 모든 수치와 프레임 값은 특정 에뮬레이터·시뮬레이터 실행에 근거한 것이다. 그럼에도 40년 전 8비트 기기의 메모리 절약 설계가 어떻게 눈에 보이지 않는 좌표 조작 창구를 열어 두었는지를 바이트 단위로 추적한 이 기록은, 검증의 완전성이라는 오래된 원칙이 하드웨어 세대를 넘어 유효하다는 사실을 조용히 상기시킨다.

SOURCE · HACKER NEWS
원문 전체 보기 → https://gamesexplained.com/c64/mercenary/
SHARE
NEXT · CHOOSE

변화를 읽었다면,
내가 만들 수익 구조를 고릅니다.

정보를 더 모으는 데서 멈추지 않고, 광고·외주·판매·중개·구독 중 내 상황에 맞는 출발점을 정해보세요.

21가지 수익 구조 살펴보기 →
처리 중...