콘텐츠로 이동

리버싱 핵심원리 1부 6장 정리

Note

본 게시글의 내용은 리버싱 핵심원리를 보며 복습 겸 정리하였습니다.

책의 내용과 일부 상이할 수 있고, 이해를 돕기 위해 강좌 형식의 말투와 적절한(?) 예시를 추가하였습니다.

본 게시글에서 사용되는 소스 코드와 파일, 프로그램은 리버싱 핵심원리에서 제공하는 파일과 언급되는 것들을 기반으로 하며, 일부 상이할 수 있습니다.

abex' crackme

abex' crackme는 리버스 엔지니어링의 패치 및 크랙 기술을 연습하기 위해 만들어진 프로그램입니다. 특정한 목표를 수행하도록 설계되어 있습니다. 국내 외 해외에서도 유명한 프로그램이기 때문에 다양한 풀이 방법이 존재합니다.

abex' 1st crackme

IMAGE1-1
이미지 1-1

프로그램을 실행하면 "Make me think your HD is CD-Rom." 문자열이 작성된 메시지 박스가 나타납니다. 내 컴퓨터의 하드디스크(HD)를 CD-Rom으로 인식하도록 만들라고 하네요. 확인 버튼도 눌러봅시다.

IMAGE1-2
이미지 1-2

"Nah... This is not a CD-ROM Drive!" 문자열이 작성된 메시지 박스가 나타나네요? 이 프로그램을 분석하기 위해 x64dbg로 열어봅시다.

디버깅 시작

IMAGE1-3
이미지 1-3

한 눈에 봐도 작성된 어셈블리 명령의 수가 적습니다. 이는 abex' 1st crackme가 VC++과 같은 도구가 아닌 직접 어셈블리어를 통해 만들었기 때문에 그렇습니다. 덕분에 Stub Code가 보이지도 않죠.

어셈블리어로 만들어진 프로그램은 그 자체가 디스어셈된 코드라 분석하기에도 용이합니다.

이미지 1-3을 자세히 보시면 "Ok, I really think that your HD is a CD-ROM! :p"라는 문자열이 확인됩니다. 내 하드디스크를 CD-ROM으로 인식시켰을 때 나타나는 성공 메시지 박스죠.

IMAGE1-4
이미지 1-4

이미지 1-4에서 호출되는 API 목록을 확인할 수 있습니다. Win32 API로, 윈도우즈 프로그래밍을 좀 해보신 분이라면 어떤 함수인지 아실겁니다.

GetDriveTypeA() 함수로 드라이브의 유형을 취득한 후 이를 CD-ROM으로 인식하도록 수정하면 우리가 원하는 성공 메시지 박스가 분명 나타날겁니다.

코드 분석

IMAGE1-5
이미지 1-5

abex' 1st crackme 프로그램에 작성된 어셈블리 명령은 크게 어렵지 않게 되어있습니다.

MessageBoxA()

IMAGE1-6
이미지 1-6

push 명령을 통해 MessageBoxA() 함수에 전달될 매개변수를 스택 메모리에 삽입(push)하고 있습니다. 함수 호츌 규약에 따라 매개변수는 역순으로 쌓입니다.

0x40100C 주소에 작성된 push 0HWND hWnd 매개변수를 나타냅니다. 여기서 0의 값이 삽입되고 있는데 NULL을 전달하는 것으로, 부모 창이 없음을 나타냅니다.

0x401007 주소에 작성된 push 402012LPCTSTR lpText 매개변수를 나타냅니다. 메시지 박스의 내용으로 출력될 문자열의 메모리 주소를 의미합니다.

0x401002 주소에 작성된 push 402000LPCTSTR lpCaption 매개변수를 나타냅니다. 메시지 박스의 제목으로 출력될 문자열의 메모리 주소를 의미합니다.

0x401000 주소에 작성된 push 0UINT uType 매개변수를 나타냅니다. 메시지 박스의 아이콘이나 버튼 유형을 나타내는데, 0의 값은 MB_OK로 확인 버튼만 출력하도록 합니다.

MessageBox.cpp
MessageBoxA(NULL, "Make me think your HD is a CD-Rom", "abex' 1st crackme", MB_OK);

어셈블리 명령을 C++ 언어로 복원하면 대략 위와 같다고 볼 수 있습니다.

GetDriveTypeA()

IMAGE1-7
이미지 1-7

push 명령으로 GetDriveTypeA() 함수에 전달될 매개변수 LPCTSTR lpRootPathName을 스택 메모리에 삽입하고 있습니다. "C:\\"의 문자열이 확인되는 것으로 보아 C 드라이브를 대상으로 하고 있음을 알 수 있습니다.

GetDriveTypeA() 함수 호출 시 반환값으로 DRIVE_FIXEDeax 레지스터에 저장됩니다. DRIVE_FIXED는 3이라는 상수 값을 갖고 있으며, 고정식 미디어 장치임을 의미합니다. 설정에 따라 달라질 수 있지만 대부분의 하드디스크의 경우 이 값이 반환됩니다.

기타

IMAGE1-8
이미지 1-8

값을 1씩 증가 또는 감소시키는 incdec 명령이 확인되고, 중간에 jmp 명령이 확왼됩니다.

inc esi 명령이 3개 작성되어 있고, dec eax 명령은 2개 작성되어 있습니다. 위 명령을 모두 실행하면 esi의 값은 3이 되고, eax의 값은 1이됩니다.(1)

  1. 제 PC의 경우 esi 레지스터의 값으로 401003이 최종적으로 저장되었습니다. PC에 따라 값이 다를 수 있습니다.

중간에 jmp 명령이 끼어있는데 무시하셔도 되는 명령입니다. 왜냐하면 바로 그 아래로 이동하라는 점프(이동) 명령인데 있으나 마나한 분석을 방해하는 코드이기 때문입니다. 위와 같은 jmp 명령의 경우 코드 분석을 방해하기 위해 추가된 반동분자 같은 코드라 보시면 되겠습니다.

조건

IMAGE1-9
이미지 1-9

cmp는 Compare의 약자로, eaxesi 레지스터의 값을 서로 비교하는 명령입니다. 그 아래에 je 명령이 작성되어 있는데, eaxesi 레지스터의 값을 서로 비교하여 두 값이 같으면(Jump if Equal) 0x40103D 주소로 이동하라는 뜻입니다. 값이 서로 같지 않으면 그 아래에 있는 명령을 실행합니다.

esi 레지스터에 3, eax 레지스터에 1이 저장되어 있고 두 값은 서로 다르기 때문에 0x40103D 주소로 이동하지 않고 그 아래에 있는 명령을 실행합니다.

실패 MessageBoxA()

IMAGE1-10
이미지 1-10

MessageBoxA와 같이 push 명령을 통해 매개변수를 스택 메모리에 삽입한 후 호출하여 전달하고 있습니다. 여기서 호출되는 MessageBoxA()는 CD-ROM으로 인식 실패 시 나타나는 메시지 박스입니다.

0x40103B 주소에 있는 jmp는 실패 메시지 박스를 띄운 후 0x401050으로 이동하여 ExitProcess() 호출 후 프로그램을 종료합니다.

성공 MessageBoxA()

IMAGE1-11
이미지 1-11

MessageBoxA와 같이 push 명령을 통해 매개변수를 스택 메모리에 삽입한 후 호출하여 전달하고 있습니다. 여기서 호출되는 MessageBoxA()는 CD-ROM으로 인식 성공 시 나타나는 메시지 박스입니다.

성공 메시지 박스를 띄운 후 ExitProcess()를 호출하여 프로그램을 종료합니다.

패치

방법

성공 메시지 박스를 호출하도록 하는 방법은 여러가지가 있습니다.

단순히 jmp 명령을 이용해 바로 성공 메시지 박스를 호출하거나, 일부 명령을 nop으로 바꾸어 실행하지 않거나 등 매우 다양한 방법들이 존재합니다. 이중 어느 방법을 사용할지는 본인의 선택입니다. 본 게시글에선 jmp 명령을 이용해 바로 성공 메시지 박스를 나타내도록 하겠습니다.

jmp

IMAGE1-12
이미지 1-12

0x401026 주소에 있는 je 40103D 명령을 jmp 40103D로 변경합니다.

이렇게 변경하면 GetDriveTypeA()로 함수의 결과를 받든 말든, cmp로 비교를 하든 말든 상관없이 바로 성공 메시지 박스를 나타나도록 할 수 있습니다.