PE
파일은 Portable Executable
파일로, 실행 파일을 의미한다. 실행 파일의 구조는 다음과 같다.
출처: https://0xrick.github.io/win-internals/pe2/
PEview
라는 도구를 이용해 CrackMe
1번 파일을 열어보게 되면 파일에 담겨있는 정보를 구조에 맞춰 보기 쉽게 나눠준다.
DOS_HEADER
에는 파일의 시그니처와 EXE Header
의 Offset
정보가 담겨있다.
- 파일 시그니처란 파일 내용을 식별하거나 확인하는 데 사용할 수 있는 파일 형식에 따른 고유의 포맷을 의미한다.
EXE Header
의 Offset
값인 0x100
으로 가보면
PE
라고 명시하고 있는 것을 볼 수 있다.
실행 파일은 무조건 MZ
, PE
를 가지며 이 중 하나라도 손상된다면 실행되지 않는다.
DOS_Stub
은 MS-DOS
프로그램 코드이다. 파일마다 어떤 운영체제에서 실행해야 하는지, 도스 환경에서 실행할 수 있는지 불가능한지 등의 정보를 나타낸다.
현재 이 파일은 Win32
환경에서 실행해야 한다고 명시되어있다.
NT HEADERS
는 파일의 시그니처와 파일 헤더, 옵셔널 헤더가 있다.
파일 헤더에서는 섹션의 개수 등의 정보를 포함하고 있다.
옵셔널 헤더는 중요한 정보가 많은데, 첫 번째로 Image Base
는 프로그램이 메모리상에 올라갔을 때 위치하는 주소이다. Address of Entry Point
는 메모리 상에서 헤더 등을 제외하고 실제 프로그램의 내용이 위치한 곳의 주소인데, Image Base
를 기준으로 한 상대주소이다. 즉 이 프로그램의 실제 주소는 0x400000
에 0x1000
을 더한 0x401000
이 되는 것이다.
다음은 섹션 헤더이다. 섹션 헤더에는 각 섹션에 대한 정보가 들어있으며, 해당 섹션이 메모리에 로드될 때 변하는 정보들이 들어있다.
여기서 중요한 것이 Offset
과 RVA
, 그리고 VA
이다.
Offset
은 파일에서의 실제 위치를 뜻하고 RVA
는 메모리 상의 상대주소, 마지막으로 VA
는 메모리 상의 절대주소를 뜻한다.
이 헤더는 SECTION CODE
의 헤더이다. Name
은 섹션의 이름이고 Virtual Size
는 해당 섹션이 메모리에서 차지하는 크기이다. Size of Raw Data
는 파일에서 해당 섹션의 실제 크기를 의미하고 Pointer to Raw Data
는 파일에서 해당 섹션의 시작 Offset
을 의미한다.
이러한 정보를 바탕으로 디버거를 통해 각종 정보를 확인해보자.
수업 당시 실습은 ollydbg
로 진행하였지만 다른 디버거도 사용해보고 싶어서 x64dbg
로 진행해보기로 했다.
단축키는 ollydbg
와 거의 유사한 것 같아서 따로 알아보지 않고 진행했다.
우선 CrackMe
1번을 로드했다.
불러온 후 RUN
을 한번 눌러주면 해당 파일의 엔트리 포인트로 이동시켜준다.
이제 각 위치를 살펴보자.
우선 이 파일의 이미지 베이스가 0x400000
인지 확인해보자.
해당 주소를 확인해보니 정확하게 파일의 시작 지점이 들어가 있는 것을 볼 수 있다. 엔트리 포인트가 0x1000
이었고, 0x401000
을 확인해보면
파일의 진짜 데이터들이 들어가 있는 것을 확인할 수 있다.
여기서 한 가지 주의할 점은 엔트리 포인트가 코드의 메인은 아니라는 점이다. 메인이 아니더라도 엔트리 포인트일 수 있고, 이번 경우에는 우연히 둘이 일치한 것 뿐이다.
프로그램을 한 줄씩 실행시키면서 가보자.
윈도우 API인 MessageBoxA
를 호출한 후 정상적으로 메시지 박스가 잘 나온다.
크랙미 1번은 하드 디스크를 CD-ROM으로 위장시키는 문제이다. GetDriveTypeA
를 통해 현재 디스크의 타입을 반환한다.
https://learn.microsoft.com/ko-kr/windows/win32/api/fileapi/nf-fileapi-getdrivetypea
위 링크를 참조하면 해당 API의 정보가 나오는데, 해당 함수가 호출되면 반환값은
다음 중 하나이다.
해당 함수가 호출된 이후 반환값은 EAX
레지스터에 저장되기 때문에 값을 살펴보면
3이 들어가 있고 이는
DRIVE_FIXED
3
드라이브에 고정 미디어가 있습니다. 예를 들어 하드 디스크 드라이브 또는 플래시 드라이브입니다.
을 의미한다.
구조를 보면
EAX
와 ESI
에 특정 연산을 진행한 이후 비교했을 때 둘이 같다면 0x40103D
로 점프하는 구문이다.
점프하는 위치에는
CD-ROM
이 맞다는 메시지박스를 띄우는 구문이 존재하고 현재 프로그램은
EAX
와 ESI
가 같지 않기 때문에 점프를 하지 않게 된다. 즉, 만약 CD-ROM
에 해당하는 값으로 리턴이 된다면 점프를 하는 것으로 보인다. 이 문제를 풀기 위해서는 해당 점프 구문의 JE
를 JNE
로만 바꿔주면 된다.
해당 구문을 바꾼 후 실행해보자.
정상적으로 잘 우회한 모습이다. 이제 실제 파일을 크랙해보자.
아까 위에서 파일의 실제 Offset
과 메모리 상의 주소는 다르다고 했었는데, 현재 메모리상의 절대주소, 즉 VA
가 0x401026
이다. 위에서 본 이 파일의 Image Base
가 0x400000
이었기 때문이 해당 주소의 RVA
는 0x1026
이다.
이제 RVA
를 Offset
으로 변환시켜주면 실제 파일에서 저 구문의 위치를 알 수 있게 되는데, 섹션 헤더를 보면
RVA
가 0x1000
이고 Virtual Size
가 0x1000
이므로 0x1000
부터 0x1000
만큼의 영역, 즉 0x1000 ~ 0x1fff
를 의미한다.
0x1026
은 이 영역에 해당하기 때문에 Pointer to Raw Data
와 RVA
의 차이를 구한 후 해당 값을 이용해 계산하면 된다.
차이가 0xA00
이므로 0x1026
에서 0xA00
를 빼준다.
0x626
이 해당 구문의 파일에서의 실제 Offset
임을 알 수 있다. 그렇다면 이제 HxD
를 이용해서 해당 파일을 크랙해보자.
실제로 잘 있는 것을 볼 수 있다. 이제 저 값을 0x74
에서 0x75
로 바꾼 후 저장해보자.
성공적으로 크랙을 완료하였다.