x0FANSA-hk 님의 블로그

MJSEC) 4주차 - PE File format 예제 본문

MJSEC

MJSEC) 4주차 - PE File format 예제

x0fansa-hk 2026. 5. 1. 23:25

여기에서 이어집니다. 현재 글은 실전편입니다.

https://x0fansa-hk.tistory.com/8

 

MJSEC) 4주차 - PE File format 개념

우선 제가 만든 프로그램입니다. 이것을 아마 자주 참조하게 될 것입니다. 만약 소스코드가 궁금하시면 이 글 맨 아래에 제가 올린 링크를 확인해 주세요. 갑니다.1. PE파일이란? Windows운영체제에

x0fansa-hk.tistory.com

이번에는 x32dbg, x64dbg, HxD전부 있어야 편리할 것입니다.

시작해 봅시다.

1. notepad.exe의 PE Body분석

Windows XP의 32비트 notepad.exe를 사용하였습니다.

여기서 이번에 저희가 찾을 것은 INT와 IAT입니다.

접근순서를 다시 한번 생각해 볼게요.

INT

: PE시그니처를 찾습니다. → Optional Header를 찾습니다. → DataDirectory배열의 인덱스 1을 참조합니다. 8바이트 구조체이므로 시작주소에서 8바이트 건너뛰면 되겠죠?

PE시그니쳐가 0x00E0(224)에 위치함을 찾았습니다. 여기서 구조체 상으론 120바이트 뒤(PE시그니쳐 4바이트, File Header 20바이트, DataDirectory을 제외한 OptionalHeader 96바이트 - 주의: 32비트 프로그램이기에 32비트버전의 구조체를 참고해야 합니다.)에 DataDirectory배열의 시작주소가 있습니다.(더블포인터로 이해하면 편리합니다.) 1번째 인덱스이므로 1*8바이트 더하면 352(0x160)에 위치에 INT의 정보가 있을 것이에요. 여기에 있는 값인 0x00007604(16진수 리틀엔디안)의 주소로 점프해보겠습니다.

0x7604로 가니 함수의 이름이 저장되어있습니다. 옆의 Decoded text창을 보면 GetTextMetricsW, SetBkMode 등의 함수를 GDI32.dll에서 가져옴을 알 수 있습니다.

IAT

: 이전 블로그에서 다뤘듯, 이것은 직접 실행을 해야 저장이 됩니다. 따라서 단순히 파일을 열어보는 HxD에서는 확인이 불가능하고 x32dbg로 열어야 합니다. (그래서 열었습니다.)

역시 저희가 정리했던데로 따라가 보겠습니다. 우선 PE를 찾습니다. 덤프창을 보니 0x010000E0 여기 위치에서 PE시그니처를 찾을 수 있네요. x32dbg로 열었기 때문에 이건 실제로 실행이 되고 있는 상태이기에 IAT에 값이 할당이 되었을 것입니다. (그렇게 믿고)12번째 인덱스의 시작주소 바로 가보죠.

왼쪽 이미지의 덤프창을 확인해주세요~

Index계산: 0x010000E0+0d120+0d(12*8) - Memory에 올라온 상태여도 Header부분은 NULL패딩이 없기에 주소값이 구조체 크기와 매칭됩니다. 0x01000000을 더한 상태로 포인터 연산을 하면 되는 것이지요.

→ 계산상 0x010001B8에 12번째 인덱스의 시작주소가 있을거 같습니다.

덤프로 이동을 하니 0x010001B8 에 00 10 00 00 48 03 00 00이 있습니다.
이 있습니다. 직감상 잘 찾은거 같은 느낌이 듭니다. DataDirectory의 구조체는 주소 4바이트, 사이즈 4바이트인데 딱 엇비슷한 값을 가지기 때문이죠.

그래서 역시 한번 더 0x01001000으로 이동해봅시다.

뭔가 유의미한 값이 나옵니다. 그래서 역시 0x77DD6FEF로 가봅니다.

못가요. 해당 주소에 유의미한 값이 없습니다. 혹은 해당 notepad.exe에서 허락되지 않은 영역입니다.

왜 그럴까요? 아마 제가 이전 글에서 IAT의 특징이라고 적어놓은 곳을 보면 답이 있습니다.

아직 IAT에 DLL에서 함수를 저장해오는 기능을 수행하지 않은 상태입니다. 즉 실행된지 초창기라 아직 INT에 있는 함수명과 같은 함수의 주소를 IAT에 기록하지 않은 것이지요.

그러면 어떻게 하면 될까요? 간단합니다. 그냥 계속 기록할때까지 수행하게 하면 되는거에요. 다시 말해서 계속 수행하게 열심히 f7, f8, f9(이거 추천. 어차피 DLL이 로드될때 한번 걸리게 되있어요.)를 눌러줍시다. 톼톼톼

그러다보면 어느 순간 cfgmgr32.dll이 로드가 되면서 덤프가 갱신이 될 것입니다. 보시다시피 0x01001000에 있는 값이 0x76F22660으로 바뀌었습니다. 즉 이시점부로 dll이 메모리에 올라왔고, 그 주소에서 저희가 얻을 함수의 시작주소는 0x76F22660에 있는 것입니다.

따라서 0x76F22660로 가줍시다.

그러면 이러한 간단한 코드가 나오는데 결론부터 말해서 0x76F22666에 있는 주소인 0x76F7435C(역시 리틀엔디안)으로 점프하면 됩니다. 이런식으로 만들어 놓은 이유는 바로 함수가 정의되는 곳을 얻어오자니 DLL파일의 데이터가 메모리 어디에 로드될지 모르니 일단 DLL파일의 헤더를 참조해서 해당 함수가 어디 있는지 가르키는 용도로 있는 것입니다.

0x76F7435C로 가 봅시다.

주석을 보면 옆에 RegQueryValueExW가 있습니다. 맞습니다. 여기가 RegQueryValueExW라는 함수의 주소를 저장하는 IAT입니다. 여기있는 advapi32.76F7431A를 참조한다면 함수 원형이 있는곳을 찾을 수 있겠죠?


2. kernel32.dll의 PE Body분석

windows를 사용하신다면 다음의 경로에서 복사해 올 수 있습니다.: C:\Windows\SysWOW64\kernel32.dll

여기서 이번에 저희가 찾을 것은 EAT입니다. 가져왔다고 치고 해보겠습니다.

 

 우선 몇비트 프로그램인지 확인해 봅시다. 이름으로 보기 너무 32비트처럼 생겼지만 확실하게 확인해봅시다. dll파일이기에 PE파일 형식을 따를거고, 32비트가 맞다면 Optional Header의 파라미터 Magic이 10B로 되어 있어야 합니다. 포인터 연산으로 빠르게 접근하면 PE 시그니처가 있는 곳에서 시그니처 4바이트, FileHeader 20바이트를 점프한다면 PE의 시작주소 이후 0x18만큼 점프를 하면 되겠죠?

그 결과 해당 위치에서 리틀엔디안으로 010B로 되어있음을 확인할 수 있습니다. 32비트 맞네요.

EAT

: 서론이 길었는데 저희가 이제 EAT를 찾아야 합니다. 역시 이전 글에서 정리한데로 접근을 해 봅시다. DataDirectory[0]에 EAT의 주소가 있다고 합니다. 그러므로 이번에도 PE를 기준으로 포인터 연산을 해 봅시다. 상단 이미지에서 PE의 위치가 0xF8(0d248)이고, 시그니처 0d4바이트, FileHeader 0d20바이트, OptionalHeader의 DataDirectory직전까지 96바이트(32비트 구조체로 참조) 를 더한다면 0x170(0d368)이고 이곳이 DataDirectory의 시작주소인데, EAT는 인덱스 0에 있기 때문에 (이중배열 시작주소가 [0]의 시작주소와 같은것처럼)여기부터 EAT와 관련된 주소입니다. 고로 0x170으로 점프합시다.

이번에도 0x170까지 확인을 했으면 역시 다시 동적으로 주소를 할당받아야 합니다. 따라서 다시 x32dbg를 켭시다.

이번에는 00BD0000에서 시작을 합니다. 메모리에 올려지므로써 구성은 같지만 위치가 조금씩 틀어졌겠죠? 


3. 4w_tester_x64 분석

제가 만든 예제파일입니다.

더보기
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <windows.h>

int main(void)
{
	srand((unsigned int)time(NULL));
	int X=rand()%100+1,Y=rand()%100+1;
	char strstack[16]="Hello, World!";
	char echoout[256];
	IMAGE_DOS_HEADER h1;
	IMAGE_NT_HEADERS h2;
	IMAGE_FILE_HEADER h3;
	IMAGE_OPTIONAL_HEADER h4;
	IMAGE_DATA_DIRECTORY h5;
	printf("=== PE Header Sizeof [bytes] ===\n"
		   "DOS Header: %d\n"
		   "NT Header: %d\n"
		   "File Header: %d\n"
		   "Optional Header: %d\n"
		   "DataDirectory: %d\n"
		   ,sizeof(h1),sizeof(h2),sizeof(h3),sizeof(h4),sizeof(h5));
	printf("\n=== Random CAL ===\n");
	printf("X: %d, Y: %d\n",X,Y);
	printf("ADD: %d\n"
		   "SUB: %d\n"
		   "MUL: %d\n"
		   "DIV: %d\n",X+Y,X-Y,X*Y,X/Y);
	printf("\n=== Default IO ===\n");
	printf("STR: %s\n",strstack);
	printf("Escape color code: \033[31mR\033[38mA\033[33mI\033[32mN\033[36mB\033[34mO\033[35mW\033[0m\n");
	printf("Input anything: ");
	scanf("%s",echoout);
	printf("ECHO: %s\n\n",echoout);
	while (getchar()!='\n'); 
    printf("\nPRESS ENTER TO EXIT...\n");
    getchar(); 
    return 0;
}

 

실행화면

 

4w_tester.c
0.00MB
4w_tester_x32.exe
1.21MB
4w_tester_x64.exe
0.32MB

 

여기서 저희는 "INT와 IAT x64dbg에서 찾아보기, 프로세스에서 dll파일을 가져올 때, 어떤 과정이 일어나는지 정리."를 목표로 이 파일을 다룰 것입니다. 그러므로 일단 x64dbg로 4w_tester_x64를 엽시다.

x64dbg로 실행시킨 직후 모습. 현재 파일은 0x400000주소에 올라왔음이 확인가능하다.

 

 앞에서 처럼 포인터 연산을 해서(PE로부터 4+20+112+8바이트) 가봅시다. 여기서 0x400110에서 0x8000이 적혀 있습니다. 여기서 RVA와 RAW보정을 해야 합니다. 일단 0x8000이 있는 섹션을 찾습니다. 0x8000이 ASCII 기준 Section에서 섹션의 시작(.text등이 있는 곳의 주소)에서 8+4바이트에 있는 것을 찾으면 됩니다.

 

.idata안에 0x8000이 있습니다. 이때 VirtualSize는 0x818, VirtualAddress(RVA)는 0x8000입니다. 이를 가지고 해당 구조체에ㅓ 추가 4+4바이트 뒤(SizeOfRawData+PointerToRawData)를 봅니다.

0x3800이 있습니다. 이것으로 메모리의 0x8000주소는 파일상의 0x3800위치에 맵핑되어있습니다. 그래서 거기로 이동하여 또 4바이트를 추출하면 0x803C입니다.

RVA: 0x803C

섹션 시작 RVA: 0x8000

섹션 시작 RAW: 0x3800이므로

0x803C-0x8000+0x3800=0x383C입니다. 이동합니다.

0x83CC가 있습니다. 여기가 이제 RVA입니다. 여기에서 다시계산하면 코드 텍스트가 나올 것입니다.

RVA: 0x83CC
섹션 시작 RVA: 0x8000
섹션 시작 RAW: 0x3800
계산: 0x83CC - 0x8000 + 0x3800 = 0x3BCC

실제이름

 

IMAGE_SECTION_HEADER
IMAGE_IMPORT_DESCRIPTOR
IMAGE_THUNK_DATA
IMAGE_IMPORT_BY_NAME 이 헤더들을 참고해야합

 

 


4. 간단한 질문

Q1. IAT를 제대로 찾았다면, INT와 같은 값들이 저장되어 있을텐데, 이유는?
A1. IAT는 프로그램을 실행하고 DLL이 로드될 때 동적으로 함수주소를 받아오는 구조입니다. 따라서 IAT를 찾았어도 현재 실행상태가 아니라면 IAT가 엉뚱한 값으로 초기화되있을텐데, 그중 대표적으로 INT와 똑같은 값들로 저장되어있습니다.(꼭그런건 아니에요. 거의 그렇다는거지)

 

Q2. INT와 IAT x64dbg에서 찾아보기, 프로세스에서 dll파일을 가져올 때, 어떤 과정이 일어나는지 정리.

A2. 제가 만든 프로그램 분석에서 이를 위주로 설명해보았습니다.

Q3. HxD에서 정적으로 뜯@어보는거랑 x64dbg에서 실행해 프로세스가 로드 후 구조가 어떻게 달라지는가?

A3. 첫번째, 두번째 질문과 비슷한데 이 프로그램이 무슨 함수를 쓰는구나 라는것을 구체적으로 참조해서 함수구조까지 볼 수 있습니다. FILE상태일때와 Memory상태일때가 서로 어긋나는 것이죠.


느낀점: 포인터로 해석하면(쪼금) 할만합니다. 포인터를 꼭 배우시길...

'MJSEC' 카테고리의 다른 글

MJSEC) 4주차 - PE File format 개념  (1) 2026.04.30
MJSEC) 3주차 - 스택프레임 / 함수호출규약  (0) 2026.04.30
MJSEC) 1-2주차 통합 과제  (0) 2026.04.30
MJSEC) 신입 부원 과제  (0) 2026.04.30