여러가지 실행파일 포맷이 있지만, 윈도우에서는 PE(Portable Executable File) 이라는 포맷을 사용한다.
PE 파일 형식은 파일에 담겨 다른 곳에 옮겨져(Portable)도 실행시킬 수 있도록(Executable) 규정한 형식이다.
리눅스에서 컴파일한 오브젝트 파일을 윈도우로 옮겨서 실행하면 실행이 안되는 이유도 이것 때문이다.
여기서는 PE 파일포맷에 대해서 간단하게 나마 살펴볼 것이다.

PE 구조

IMAGE_SECTION_HEADER 들의 배열로 이루어져 있다. IMAGE_SECTION_HEADER 들은 .text, .data, idata, .reloc 등의 구역(Section)의 시작 주소와 사이즈에 관한 정보가 들어 있다.
우선, .text 는 실제코드가 들어있다. .data 구역은 전역이나 정적 변수들이 적재되는 영역이다. 전역이나 정적 변수는 컴파일 할 때 사이즈가 결정되기 때문에 구역을 정확히 나눌 수 있는 것이다. 그리고 쭉 가다 보면 .idata 구역이 있다. 이 구역은 DLL 로부터 가져다 쓴 함수들의 실제 주소가 적혀있다.

섹션의 이름이 들어있다. 보통은 앞에 . 이 붙는다. .text, .idata 같은 식으로 말이다.

.text 섹션에는 일반적으로 실행되는 코드들이 들어 있다. 따라서 어셈블러나 컴파일러가 만드는 코드들이 들어가게 된다. 한 가지 DLL 의 파일을 실행할 때는 바로 그 주소를 쓰지 않고 다음과 같은 방식을 쓴다.

JMP  DWORD  PTR [XXXXXXXX]

여기서 주소는 .idata 에 있는 값으로 그 곳에 가면 실제 DLL 의 주소가 있다. 그런데 DLL 의 주소를 알아다 바로 그 주소로 CALL 을 쓰지않고 간접 주소 방식으로 주소를 한번 거쳐서 가져가게 되는 것일까.
잘 생각해보면 DLL 의 값은 고정되어 있지 않다. MessageBoxA 라는 함수를 썼다고 하면 DLL 의 값이 동적으로 로드되면서 다른 주소에 로드되면 CALL MessageBox 한 부분을 모두 찾아서 바뀐 주소로 바꾸어 주어야 한다. API 함수가 모두 DLL 안에 존재하므로 이 작업만도 엄청날 것이다.
그래서 DLL 의 주소는 .idata 에서 참고하도록 하고 PE 로더는 DLL 주소간에 매핑 테이블인 셈이다. 또 .idata 의 이런 기능 덕분에 .text 는 읽기 전용이 가능하다.

.data 섹션은 초기 데이터들이 있는 곳이다. 이 초기화 데이터들은 전역 변수와 정적 변수들이 컴파일하며 초기화 된다. 특히 문자열 같은 값들이 이곳에 존재한다. 지역 변수의 경우 쓰레드 스택에 저장되며 .data 영역에는 존재하지 않는다.

다른 DLL 로 부터 가져다 쓰는 함수들의 정보가 담겨 있다. 후에 함수 후킹에 유용하게 쓰이는 섹션이다. 이미 .text 에서 설명했다.

다른 모듈이 이 파일을 사용할 때 사용하도록 해놓은(Export) 함수의 리스트가 들어 있다. 이것은 이 파일이 DLL 일 때 외에는 거의 볼일이 없을 것이다. 간혹 Borland 컴파일러 자체 디버깅 목적으로 자신의 EXE 파일 자체의 함수를 이 곳에 넣고 외부에서 디버깅에 사용할 수 있도록 하는 경우가 있다.

peanalyzer.zip 에서 받아서 컴파일한 후에 PE 포맷의 파일을 지정해주면, 분석해주는 프로그램이다.

  • computer/rtcclab/pe_파일_포맷.txt
  • Last modified: 3 years ago
  • by likewind