DOS 크랙 연습 적벽대전

개요

우연하게 삼국지를 주제로 한 DOS 게임 중 하나인 적벽대전을 다시 접하게 되었다. 당시에는 신경쓰지 않고 지나갔지만 해당 게임은 폭소시리즈와 삼국지 무장쟁패로 유명한 대만의 ‘Panda Entertaintment’에서 제작한 게임이다. 필자는 해당 게임을 구매하여 정품으로 보유하고 있었으나 현재에는 찾을 길이 없어 인터넷에서 다운로드한 대상으로 작업을 수행하였다. 대상 게임은 잘 알려져 있지 않아 국내에서는 거의 정보를 찾을 수 없지만 대만에서 1994년도에 발매된 게임으로 Wiki에서 확인된다. Panda Entertaintment는 현재 운영하지 않는걸로 보여지고 게임 또한 고전으로 실질적으로 저작권에 큰 영향을 받지 않는걸로 보여진다.

준비

  • DOSBox-x : 도스 프로그램 구동과 디버깅을 위하여 사용
    • Debugger 기능이 포함된 macOS용 바이너리를 사용하였다.
  • IDA Pro : 디스어셈블러, 도스 프로그램의 역공학을 위하여 사용
    • IDA를 사용하면 5분안에 분석이 완료될 정도로 너무나 간단하여 본문에서는 굳이 사용하지 않았다..
  • Sourcer : DOS용 디스어셈블러, 인터넷 검색 후 다운로드 한 프로그램을 사용
  • Ghidra : 오픈소스 공개 디스어셈블러, 도구 비교를 위해 사용
  • 010 Editor : 파일 에디터
  • 적벽대전 게임 (한글판)
    • SANRPG.EXE
    • filesize : 224476 bytes
    • md5 : 654fc9ab9aee09692dbe33d075ee83ba

목표 확인

크랙이 전혀 이루어지지 않은 대상을 찾아보려 하였으나 찾지 못하였다. 인터넷에서 일부 코드만 패치된 게임을 다운로드할 수 있었으며 해당 파일을 대상으로 작업을 수행하였다.

적벽대전 게임 타이틀 메뉴
적벽대전 게임 타이틀 메뉴

게임을 구동시에 [게임시작], [불러오기], [환경설정]의 메뉴를 클릭하면 패스워드 검증 화면이 표시된다. 입수한 파일의 경우 [시작하기]로 게임을 시작하면 패스워드 입력 창이 표시되지 않았다. 그러나 [불러오기][환경설정] 메뉴를 클릭하면 패스워드 입력 창이 표시된다. 또한 [시작하기]로 게임을 시작한 이후 메뉴로 다시 돌아와서 [불러오기][환경설정] 메뉴를 클릭하면 패스워드 입력 창이 나오지 않았다.

적벽대전 게임 패스워드 입력 화면
적벽대전 게임 패스워드 입력 화면

패스워드 입력에 실패하면 도스 터미널에 ‘password error.’ 문자열이 출력되고 게임이 종료된다.

분석

코드 예상

코드 분석을 하기 이전에 기존에 알게된 정보를 토대로 아래와 같이 정리를 해보았다.

  1. 누군가 [게임시작] 메뉴 클릭시에 나타나는 패스워드 입력만 크랙하였다.
  2. [게임시작] 메뉴 클릭 후에 다시 [불러오기][환경설정]을 실행시에는 패스워드를 묻지 않는다.
    • 크랙이 완료된 [게임시작] 메뉴를 실행하면 어디인가에 저장된 패스워드 검증 완료 스위치가 변경되어 더 이상 패스워드를 묻지 않게 된다. (예상)
  3. 패스워드 입력이 실패하면 ‘password error.’ 문구 출력 후 게임이 종료되는 루틴이 실행된다.

에러 출력 로직 분석

쉬운 접근 방법인 코드 예상의 3번 항목을 분석하여 어떠한 과정에 의하여 에러가 출력되는지를 확인해보도록 한다.

IDA를 사용하면 5분안에 크랙이 가능한 쉬운 샘플로서 이번에는 DOS 시절의 고전 디스어셈블러인 ‘SOURCER’를 사용해보았다. DOS 실행파일 분석이 가능하다면 디스어셈블러 선정은 크게 중요하지 않다. SOURCER는 제작사에 의하여 현재 유지보수가 되지않으며 인터넷에서 크랙된 도스 버전을 다운로드할 수 있다. SOURCER의 다운로드 방법과 설치는 여기에서 다루지 않는다.

SR.EXE로 SOURCER를 실행하면 분석할 파일명을 입력받는데 여기에 대상 파일 (SANRPG.EXE)의 경로를 입력한다.

SOURCER 실행 화면
SOURCER 실행 화면

Loading이 완료되고 (옵션을 변경하지 않았다면) G 키를 누르면 SANRPG.LST 파일이 생성된다. 에디터로 SANRPG.LST를 열고 에러 문자열인 ‘password error’를 검색하면 아래와 같이 찾아진다.

50C7:0D90  70 61                db   70h, 61h
50C7:0D92  6E 64 61 31 36 2E            db  'nda16.pcx', 0
50C7:0D98  70 63 78 00
50C7:0D9C  70 61 73 73 77 6F            db  'password error.', 0
50C7:0DA2  72 64 20 65 72 72
50C7:0DA8  6F 72 2E 00
50C7:0DAC  70 61 73 73 77 6F            db  'password error.'
50C7:0DB2  72 64 20 65 72 72
50C7:0DB8  6F 72 2E

동일한 에러문구가 인접한 곳에 두 곳에 존재한다. 안타깝게도 SOURCER가 해당 문자열을 참조하는 코드를 찾아주지 못하였다. 문자열 주소인 ‘0D 9C’ (파일 상의 값은 ‘9C 0D’이지만 SOURCER가 주소 표기를 위해 반전)를 직접 검색하여 코드 내에 참조하는 곳이 있는지 확인 한다. 다행히도 검색 결과는 아래 한 군데 뿐이다.

37A4:14A6  83 3E 0AD4 00            cmp word ptr ds:[0AD4h],0   ; (2923:0AD4=8BE0h)
37A4:14AB  74 09                je  loc_1298        ; Jump if equal
37A4:14AD  A1 0AD4              mov ax,word ptr ds:[0AD4h]  ; (2923:0AD4=8BE0h)
37A4:14B0  3B 06 3A54               cmp ax,ds:data_141e     ; (2923:3A54=2D38h)
37A4:14B4  74 18                je  loc_1299        ; Jump if equal
37A4:14B6           loc_1298:                   ;  xref 37A4:14AB
37A4:14B6  9A 4711:0421             call    far ptr sub_447     ; (4711:0421)
37A4:14BB  3D 0001              cmp ax,1
37A4:14BE  74 0E                je  loc_1299        ; Jump if equal
37A4:14C0  1E                   push    ds
37A4:14C1  68 9C 0D 6A 08 9A            db   68h, 9Ch, 0Dh, 6Ah, 08h, 9Ah
37A4:14C7  02 00                db   02h, 00h
37A4:14C9  3BC3                 dw  seg_r
37A4:14CB  83 C4 06             db   83h,0C4h, 06h
37A4:14CE           loc_1299:                   ;  xref 37A4:14B4, 14BE
37A4:14CE  A1 3A54              mov ax,ds:data_141e     ; (2923:3A54=2D38h)
37A4:14D1  A3 0AD4              mov word ptr ds:[0AD4h],ax  ; (2923:0AD4=8BE0h)
37A4:14D4  E8 EBC5              call    sub_287         ; (009C)
37A4:14D7  B8 0013              mov ax,13h
37A4:14DA  CD 10                int 10h         ; Video display   ah=functn 00h
                                        ;  set display mode in al

37A4:14C1에 주소가 포함되어있지만 해당 구문이 SOURCER에 제대로 디스어셈블 되지 않았다. 0x68은 PUSH imm16 명령으로 해석하면 아래와 같다.

IA-32 Intel® Architecture Software Developer's Manual
IA-32 Intel® Architecture Software Developer’s Manual
37A4:14C1  push 0D9C

뒤의 코드를 마저 해석하면 아래와 같다.

37A4:14C4  push 8
37A4:14C6  call seg_r:0002 ; 3BC3:0002
37A4:14CB  add sp,6

16Bit DOS 프로그램은 Segment:Offset 형태의 메모리 주소가 사용된다. 시스템이 달라져도 Offset은 동일하지만 Segment는 환경에 따라 변경될 수 있다.

SOURCER를 기준으로 3BC3:0002에 위치하는 함수 코드는 에러 코드와 문자열을 인자로 전달 받고 에러 출력 후 프로그램을 종료하여 DOS로 복귀한다. 패스워드 입력을 실패하면 3BCD:0002 함수를 호출하여 ‘password error.’ 문자열을 출력 후 종료한다.

패스워드 검증 로직

에러 출력 구문으로 어떻게 도달하는지 위의 코드를 다시 확인한다.

37A4:14C0‘password error.’를 호출하기 위한 코드이다. 해당 코드의 위에는 3개의 점프 구문이 존재한다. 37A4:14B4 je loc_129937A4:14BE je loc_1299는 에러 출력을 건너띈다. 해당 부분에서 무조건 점프 시키면 패스워드 에러가 출력되지 않고 게임도 종료되지 않는다. 패스워드 크랙이 가능한 부분이다.

37A4:14AB je loc_1298는 아래 37A4:14B4 점프 루틴 조건식을 건너띌지 여부를 결정한다. 37A4:14B4는 점프를 위한 조건으로 ds:[0AD4h]의 값이 0인지 비교한다. 해당 값이 0인 경우에는 패스워드 에러 출력를 건너띄고 loc_1299 대신 loc_1298을 실행한다.

37A4:14B6 loc_1298 4711:0421을 함수를 호출하는데 해당 함수의 결과가 1인 경우에는 loc_1299로 점프하고 1이 아닌 경우에는 ‘password error.’를 출력하고 게임이 종료된다. 호출되는 함수 4711:0421은 패스워드 입력과 관련된 함수로 추정할 수 있다. 실제로 해당 함수안에는 랜덤한 암호문 선택을 위한 것으로 추정되는 rand 함수와 암호문 데이터로 추정되는 code.pak 파일을 열람한다. 해당 함수를 분석하는 것도 흥미로워 보이지만 나중의 기회로 남겨두도록 한다.

검증로직은 아래와 같이 수행된다.

  1. ds:[0AD4h]를 검사하여
    • 0이면 패스워드 검사 루틴(3번)으로 이동
    • 0 이외의 값이면 아래의 2번 검증 루틴 수행
  2. ds[0AD4h]의 값이 ds:data_141e의 값과 일치하는지 확인
    • 일치하면 3번의 패스워드 검증 루틴 스킵 (게임 구동 후 패스워드 검사를 한번이라도 통과한 경우에는 재검증하지 않음)
    • 일치하지 않는 경우 패스워드 검증 루틴 수행
  3. 4711:0421 함수를 호출하여 패스워드 검증
    • 함수 호출 후 결과 값(ax)이 1과 같다면 패스워드 검사 성공으로 게임 실행
    • 결과 값(ax)이 1이 아닌 경우에는 ‘password error.’ 문자열 출력 후 게임 종료
  4. 패스워드 검사를 통과하면 게임 실행 중에 패스워드 검사가 다시 나타나지 않도록 ds[0AD4h]에 ds:data_141e의 값을 넣음

크랙 전략

다양한 크랙 방법이 존재한다. 대상 파일은 3번과 동일한 로직이 사용되는 패스워드 검증 결과 비교 코드인 37A4:179C의 코드를 검증이 실패 하는 경우에 점프하도록 ‘jne loc_1311’로 패치하였다. 크랙이 일부 완료된 샘플 바이너리는 [시작하기] 메뉴에서 표시되는 패스워드 검증에서 실패하는 경우에는 ‘jne loc_1311’ 코드에 의하여 ‘password error.’ 출력 없이 ‘loc_1311’로 점프하여 패스워드 검증을 우회하게 된다.

37A4:1784  83 3E 0AD4 00            cmp word ptr ds:[0AD4h],0   ; (2923:0AD4=8BE0h)
37A4:1789  74 09                je  loc_1310        ; Jump if equal
37A4:178B  A1 0AD4              mov ax,word ptr ds:[0AD4h]  ; (2923:0AD4=8BE0h)
37A4:178E  3B 06 3A54               cmp ax,ds:data_141e     ; (2923:3A54=2D38h)
37A4:1792  74 18                je  loc_1311        ; Jump if equal
37A4:1794           loc_1310:                   ;  xref 37A4:1789
37A4:1794  9A 4711:0421             call    far ptr sub_447     ; (4711:0421)
37A4:1799  3D 0001              cmp ax,1
37A4:179C  75 0E                jne loc_1311        ; Jump if not equal
37A4:179E  1E                   push    ds
37A4:179F  68 AC 0D 6A 08 9A            db   68h,0ACh, 0Dh, 6Ah, 08h, 9Ah
37A4:17A5  02 00                db   02h, 00h
37A4:17A7  3BC3                 dw  seg_r
37A4:17A9  83 C4 06             db   83h,0C4h, 06h
37A4:17AC           loc_1311:                   ;  xref 37A4:1792, 179C
37A4:17AC  A1 3A54              mov ax,ds:data_141e     ; (2923:3A54=2D38h)
37A4:17AF  A3 0AD4              mov word ptr ds:[0AD4h],ax  ; (2923:0AD4=8BE0h)
37A4:17B2  9A 32F8:0134             call    far ptr sub_174     ; (32F8:0134)
37A4:17B7  9A 3E42:01CC             call    far ptr sub_355     ; (3E42:01CC)

[불러오기][환경설정]은 위의 코드가 사용되지 않고 첫 번째 예제코드가 사용되기 때문에 동일하게 패치하는 과정이 필요하다.

2번 검증 로직을 수정하여 패스워드 검증 화면 자체가 보이지 않도록 크랙할 수 있다. ‘je’ 점프 로직을 ‘jmp’로 수정하여 무조건 점프하도록 ‘je’의 OPCode ‘0x74’를 jmp 명령의 OPCode는 ‘0xeb’로 수정한다.

패치를 위한 코드를 찾기 위하여 파일 에디터에서 ‘3B 06 54 3A 74 18’을 검색하여 해당 부분을 ‘3B 06 54 3A EB 18’로 수정하면 크랙이 완료된다. 동일한 코드가 [시작하기][불러오기], [환경설정]에서 사용되기 때문에 두 군데의 수정이 필요하다. 해당 코드를 수정하더라도 패스워드 검증이 최소 한 번 수행되지 않은 경우에는 검증로직 1번에서 ds:[0AD4h]의 값이 0인 경우 검증로직 3번의 패스워드 검증 루틴으로 점프(jz, 74 09)하기 때문에 수정된 2번의 로직이 실행되지 않는다. 1번의 로직은 에디터에서 수정된 곳의 바로 위에 인접해 있다. 점프를 수행하지 않기 위하여 아무 명령을 수행하지 않는 NOP (0x90)으로 두 바이트를 수정한다.

크랙이 완료된 바이너리를 실행하여 패스워드 검증 부분에서 DOSBox Debugger로 코드를 확인한 그림은 아래와 같다.

크랙 완료된 적벽대전 패스워드 검증 코드
크랙 완료된 적벽대전 패스워드 검증 코드

Ghidra로 16Bits DOS Game Binary 분석

SOURCER와 DOSBox Debugger가 생각만큼 성능이 뛰어나지 않아 보인다. 1990년대가 아닌 2020년대에는 성능이 훨씬 좋은 IDA Pro가 있기 때문에 크랙이 매우 쉽게 느껴진다. IDA Pro가 상용이라 부담스럽다면 아직은 부족해보이지만 Ghidra가 대안이 될 수 있어보인다.

위의 패스워드 검증 로직을 Ghidra에서 확인한 결과는 아래와 같다.

1e81:14b0 3b 06 54 3a     CMP        AX,word ptr [DAT_37a4_3a54]                      = FFFFh
1e81:14b4 74 18           JZ         LAB_1e81_14ce
                             LAB_1e81_14b6                                   XREF[1]:     1e81:14ab(j)  
1e81:14b6 9a 21 04        CALLF      FUN_2dee_0421                                    undefined FUN_2dee_0421()
                 ee 2d
1e81:14bb 3d 01 00        CMP        AX,0x1
1e81:14be 74 0e           JZ         LAB_1e81_14ce
1e81:14c0 1e              PUSH       DS
1e81:14c1 68 9c 0d        PUSH       0xd9c
1e81:14c4 6a 08           PUSH       0x8
1e81:14c6 9a 02 00        CALLF      FUN_22a0_0002                                    undefined FUN_22a0_0002(undefine
                 a0 22
1e81:14cb 83 c4 06        ADD        SP,0x6
                             LAB_1e81_14ce                                   XREF[2]:     1e81:14b4(j), 1e81:14be(j)  
1e81:14ce a1 54 3a        MOV        AX,[DAT_37a4_3a54]                               = FFFFh
1e81:14d1 a3 d4 0a        MOV        [DAT_37a4_0ad4],AX
1e81:14d4 e8 c5 eb        CALL       FUN_1e81_009c                                    undefined FUN_1e81_009c()
1e81:14d7 b8 13 00        MOV        AX,0x13
1e81:14da cd 10           INT        0x10

Ghidra 또한 ‘password error.’ 문자열의 레퍼런스가 자동으로 찾아지지는 않는다. 그러나 SOURCER에서 디스어셈블되지 않은 구문이 정상적으로 해석되고 있으며 부족하지만 C언어로 디컴파일된 결과를 함께 확인할 수 있다.

기타

특이하게도 게임에 사용된 배경화면의 이미지(Padna 로고, 오프닝, 메뉴 화면)가 PCX 파일 포맷으로 그대로 드러나 있다. 해당 파일의 검증 절차가 없기 때문에 과거에 이미지 파일을 편집하여 게임을 구동해보았던 기억이 난다. (이미지 파일을 당시 어떤 프로그램으로 편집했었는지는 기억이 가물하다.)

정품 구매한 게임이지만 게임 자체의 재미는 별로였던걸로 기억한다.

마치며

가급적 자세히 써보고자 하였지만 아주 간단한 샘플임에도 생각 보다 글의 분량이 많아지다보니 많은 부분을 생략하였다. 이런거 까지 쓸 필요가 있을까라는 내용도 있어 글의 방향을 잡기가 어려운 점이 있다. 가급적 재미겸 기록의 보존 목적으로 글을 이어가려고 하는데 다음번에는 조금 더 직관적으로 단순하게 써볼까 한다.

댓글 달기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다