Ghidra Study
PE(Portable Executable) 포맷 실행 파일은 윈도우에서 이용되는 형식으로 윈도우에서 .exe, .dll 파일이 메모리에 로드뒈어 실행되기 위한 정보를 담고 있다.
PE 포맷 헤더에 담고 있는 정보에 대해서 알아보자.

PE 포맷 헤더의 구조는 위 사진과 같다.
프로세스 메모리 시작 주소, 코드 및 데이터의 저장 위치, 아키텍처 종류 (x86, x64, ARM), 함수 테이블 정보 등 실행을 위한 다양한 정보를 담고 있다.
IMAGE_DOS_HEADER 구조체로 정의되며 PE 파일을 분석할 때 가장 먼저 마주치게 된다.
모든 x64 윈도우 환경에서 PE 파일은 이 헤더로 시작한다. 리버스 엔지니어링에 중요한 필드 2개가 존재한다.

PEView 프로그램을 활용
4D 5A(MZ)인지 확인해야 한다. 만약 시그니처가 다르다면 OS는 이를 실행파일로 간주하지 않는다.IMAGE_NT_HEADER 구조체로 위 사진 처럼 PE(0x50, 0x45, 0x00, 0x00)이라는 값의 서명을 가지고 있다.

IMAGE_NT_HEADER 구조체는 IMAGE_FILE_HEADER, IMAGE_OPTIONAL_HEADER로 구성돼 있다.

위 사진은 File Header가 담고 있는 정보이다. 여기서 중요하게 볼 정보가 몇가지 있다.


Optional Header는 가장 방대하면서도 실제 프로그램이 메모리에서 어떻게 동작하는지를 결정하는 핵심 정보를 담고 있다.
Magic: 파일이 32비트인지, 64비트인지 최종적으로 확정 짓는 값 (0x1B = 32bit, 0x20B = 64bit)
AddressOfEntryPoint(EP): 리버싱의 시작점으로 프로그램이 메모리에 로드된 후, 가장 처음으로 실행될 코드의 주소를 가리킨다.
ImageBase: 파일이 가상메모리의 어느 주소에 로드되는지 나타내는 정보 (.exe = 0x00400000, .dll = 0x10000000)
SectionAlignment & FileAlignment: 파일이 하드디스크에 있을 때와 메모리에 올라갔을 때의 데이터 배치 간격
섹션이 하드디스크에 있을 때와 가상 메모리에 있을 때 차지해야 하는 청크의 크기를 결정한다.
이 둘을 다르게 하는 이유는 하드디스크와 가상 메모리(RAM)의 관리 효율성의 차이 때문이다.
IMAGE_SECTION_HEADR 구조체로 정의되며, 섹션 테이블에는 각 섹션 이름과 주소 정보가 저장되어 있다.

파일 조작이나 메모리 조작, 통신 등 대부분의 프로그램 동작에는 운영체제가 제공하는 라이브러리를 이용한다. 윈도우 실행 형식인 PE 포맷의 경우 윈도우 API가 라이브러리로 제공된다. 라이브러리 링크 방법에는 정적 링크와 동적 링크가 있다.
#include를 이용하여 라이브러리를 선언, printf 함수 호출 -> printf 함수 구현부가 실행파일 내부에 존재DLL 파일에서 찾아서 사용
PE 포맷에서 DLL 이 내보내는(Export) 함수를 임포트하여 사용. IAT(Import Address Table)에 임포트된 함수의 주소를 저장, 코드 내에서 함수를 호출할 때 IAT에 접근하여 함수의 주소로 이동.
Import와 반대되는 개념으로 앞서 실행파일에서 DLL의 함수를 사용하기 위해서는 DLL 파일에서 함수를 Export 해줘야 한다.
#include <windows.h>
#include <iostream>
using namespace std;
typedef int (*Add)(int, int);
int main() {
HMODULE hDLL = LoadLibraryW(L"add.dll");
if (hDLL == NULL) {
cerr << "DLL 로드 실패" << endl;
return 1;
}
Add AddNum = (Add)GetProcAddress(hDLL, "Add");
if (AddNum == NULL) {
cerr << "Add 함수 로드 실패" << endl;
FreeLibrary(hDLL);
return 1;
}
int result = AddNum(5, 10);
cout << result << endl;
FreeLibrary(hDLL);
return 0;
}
#include <windows.h>
extern "C" __declspec(dllexport) int Add(int a, int b) {
return a + b;
}
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
switch(ul_reason_for_call) {
case DLL_PROCESS_ATTACH:
break;
case DLL_PROCESS_DETACH:
break;
}
return true;
}
C++ 코드로 함수를 import, export 하는 코드를 작성해 주었다.
dll 파일을 생성하는 방법은
gcc -shared -o add.dll Export.cpp
싱행하는 방법은
g++ -o Import.exe Import.cpp
이렇게 컴파일하여 실행파일을 생성하였다.
생성한 실행파일을 분석하여 실제로 dll 파일이 동적 링크가 되는지 확인해 보았다.
PEView에서 볼 수 있는 ImportTable은 정적 링크된 함수들이기 때문에 코드를 실행하여 확인하여야 한다.
이를 위해 IDA를 사용해 주었다.

LoadLibrary이후에 BreakPoint를 걸고 실행하게 되면

직접 작성한 add.dll 파일이 로드된 것을 확인할 수 있다.