반응형

API Hooking 에 대한 강좌입니다.
User 에서 API Hooking의 다양한 기법들에 대해서 자세히 설명하도록 하겠습니다.



후킹 (Hooking)

리버싱에서 후킹은 정보를 가로채고, 실행 흐름을 변경하고, 원래와는 다른 기능을 제공하게 하는 기술입니다.

후킹의 전체 프로세스는 아래와 같습니다.

- 디스어셈블러/디버거를 이용하여 프로그램의 구조와 동작원리를 파악
- 버그 수정 또는 기능 개선에 필요한 Hook 코드를 개발 [1]
- 실행 파일과 프로세스 메모리를 자유롭게 조작하여 Hook 코드 설치

위와 같은 일련의 작업들은 그야말로 리버스 엔지니어링 기술의 핵심(Core) 이라고 할 수 있습니다.

그래서 전 후킹을 "리버싱의 꽃" 이라고 부릅니다.

여러가지 후킹 기술이 있지만 그중에서도 API 를 후킹하는 기술을 API Hooking 이라고 하고, User mode 후킹 중에서 메시지 후킹[2]과 함께 가장 널리 사용되는 기술입니다.


[1] 프로그램 소스가 있다면 대부분의 경우 후킹은 필요하지 않을 것입니다. 하지만 특수한 상황(소스 코드가 없거나, 소스 코드의 수정이 여의치 않은 상황)에서는 후킹 기술이 요긴하게 사용될 수 있습니다.

[2] 메시지 후킹(Message Hooking) 에 대한 내용은 제 글을 참고하시기 바랍니다.

- Windows Message Hooking (윈도우 메시지 후킹)



API (Application Programming Interface)


API Hooking 을 설명하려면 먼저 API(Application Programming Interface) 에 대해서 짚고 넘어가야 합니다.

Windows OS 에서는 사용자 어플리케이션이 시스템 자원(메모리, 파일, 네트워크, 비디오, 사운드, 기타)을 사용하고 싶을 때 직접 할 수 있는 방법이 없습니다. 왜냐하면 그것들은 OS 가 직접 관리하며, 여러 가지 이유(안정성, 보안, 효율, 기타)로 사용자 어플리케이션의 직접적인 접근을 막아놓았기 때문입니다.

이럴 때 사용자 어플리케이션은 시스템 커널에게 요청해야 합니다. 요청 방법이 바로 MS 에서 제공한 Win32 API 를 이용하는 것입니다. (API 는 해당 OS 제작사에서 제공합니다.)

즉, API 함수 없이는 어떤 의미 있는 프로그램을 만들어 낼 수 없습니다.

아래 그림은 32 bit Windows OS 의 프로세스 메모리를 간략히 나타낸 것입니다.


<Fig. 1>

실제 어플리케이션 코드를 실행 시키기 위해 많은 시스템 라이브러리(DLL) 들이 로딩됩니다. [3] 모든 프로세스에는 기본적으로 kernel32.dll 이 로딩되구요, kernel32.dll 은 ntdll.dll 을 로딩합니다. (참고로 GUI 어플리케이션에서는 user32.dll 과 gdi32.dll 또한 필수 라이브러리 입니다.)

ntdll.dll 의 역할이 바로 유저 모드 어플리케이션의 코드에서 발생하는 시스템 자원에 대한 접근을 커널 모드에게 요청 하는 것입니다.


간단한 를 들어보겠습니다.
notepad.exe 에서 c:\abc.txt 라는 파일을 열고자 합니다.
코드에서는 msvcrt!fopen() API 를 호출합니다. 그 이후의 API 호출 흐름을 보면 아래와 같습니다.

msvcrt!fopen()
 
kernel32!CreateFileW()
    ntdll!ZwCreateFile()
      ntdll!KiFastSystemCall()
        SYSENTER                     // Intel IA-32 Op Code
          => 커널 모드 진입


일반적인 시스템 자원을 사용하는 API 는 kernel32.dll 과 ntdll.dll 을 타고 가다가 결국 SYSENTER 명령을 통해 커널 모드로 진입하게 됩니다.


[3] 'DLL 로딩(loading)' 이라는 용어보다는 'DLL 매핑(mapping)' 이라는 용어가 더 정확한 표현입니다. Windows 운영체제는 DLL 을 최초 한번만 메모리에 적재(loading) 하고, 그 이후부터는 프로세스에게 매핑(mapping) 시켜주는 메커니즘을 사용합니다.



API Hooking


API Hooking 이란 Win32 API 호출을 중간에서 가로채서 제어권을 얻어내는 것입니다.

API Hooking 의 이점은 다음과 같습니다.

- API 호출 전/후에 사용자의 훅 코드(Hook Code)를 실행시킬 수 있습니다.
- API 에 넘어온 파라미터 혹은 API 함수의 리턴값을 엿보거나 조작 할 수 있습니다.
- API 호출 자체를 취소시키거나 사용자 코드로 실행 흐름을 변경시킬 수 있습니다.

이해를 돕기 위해 아래 그림을 봐주시기 바랍니다.

먼저 정상적인 API 호출입니다.


<Fig. 2>

코드 영역 주소에서 CreateFile() API 를 호출하였습니다. [4]

CreateFile() API 는 kernel32.dll 에서 서비스(export) 하므로 kernel32.dll 영역의 CreateFile() API 가 실행되고 정상적으로 리턴합니다.

[4] 실제 kernel32 에서 서비스되는 API 이름은 CreateFileA() 와 CreateFileW() 입니다. 프로그래밍할 때 CreateFile() 만 써주면 컴파일 시에 적절히 CreateFileA()/CreateFileW() 중에서 결정됩니다. 여기서는 설명의 편의상 CreateFile() 로 하였습니다.


다음은 kernel32!CreateFile() 가 후킹된 경우입니다.


<Fig. 3>

사용자가 DLL Injection 기술로 hook.dll 을 프로세스 메모리 공간에 침투 시킵니다. 그리고 kernel32!CreateFile() 를 hook!MyCreateFile() 로 후킹하였습니다. (후킹 함수 설치 방법은 DLL Injection 말고도 더 있습니다.)

이제부터 해당 프로세스에서 CreateFile() API 가 호출 될 때마다 kernel32!CreateFile() 이 호출 되는 것이 아니라, hook!MyCreateFile() 이 호출 됩니다.

후킹 함수(MyCreateFile)와 원본 함수(CreateFile)의 호출 순서는 경우에 따라 달라집니다.
입력된 파라미터를 조작하고 싶을 때는 후킹 함수가 먼저 실행되고 나중에 원본 함수가 실행됩니다. 또한 API 리턴값을 조작하고 싶을 때는 원본 함수가 먼저 실행된 후 후킹 함수가 나중에 실행됩니다.

이 외에도 후킹 목적에 따라서 원본 함수 호출 전/후에 후킹 함수를 실행시키거나 원본 함수를 아예 호출하지 않는 등의 여러 가지 변형이 가능합니다. 따라서 후킹 목적과 상황에 따라서 적절히 사용해 주시면 되겠습니다.

이것이 바로 API Hooking 의 기본 개념입니다.

API Hooking 을 구현하는 방법은 다양합니다. 하지만 후킹의 기본 개념은 변하지 않습니다.

위의 개념을 잘 이해하시면 이후에 설명 드리는 구현 방법에 대해서도 쉽게 이해하실 수 있습니다.


+---+

다음 포스트로 이어집니다.

API Hooking - Tech Map


ReverseCore

반응형
반응형


IAT (Import Address Table)


PE Header 를 처음 배울때 최대 장벽은 IAT(Import Address Table) 입니다.

IAT 에는 Windows 운영체제의 핵심 개념인 process, memory, DLL 구조 등에 대한 내용이 함축되어 있습니다.
즉, IAT 만 잘 이해해도 Windows 운영체제의 근간을 이해한다고 할 수 있습니다.

IAT 란 쉽게 말해서 프로그램이 어떤 라이브러리에서 어떤 함수를 사용하고 있는지를 기술한 테이블 입니다.



DLL (Dynamic Linked Library)


IAT 를 설명하기 앞서 Windows OS 의 근간을 이루는 DLL(Dynamic Linked Library) 개념을 짚고 넘어가야 합니다.
(뭐든지 이유를 알면 이해하기 쉬운 법이지요...)

DLL 을 우리말로 '동적 연결 라이브러리' 라고 하는데요, 그 이유를 알아 보겠습니다.

16 bit DOS 시절에는 DLL 개념이 없었습니다. 그냥 'Library' 만 존재하였습니다.

예를 들면 C 언어에서 printf() 함수를 사용할 때 컴파일러는 C 라이브러리에서
해당 함수의 binary 코드를 그대로 가져와서 프로그램에 삽입(포함)시켜 버렸습니다.
즉, 실행 파일내에 printf() 함수의 바이너리 코드를 가지고 있는 것입니다.

Windows OS 에서는 Multi-Tasking 을 지원하기 때문에 이러한 라이브러리 포함 방식이 비효율적이 되어 버렸습니다.

32 bit Windows 환경을 제대로 지원하기 위해 기본적으로 매우 많은 라이브러리 함수(process, memory, window, message, etc)를 사용해야 합니다.

여러 프로그램이 동시에 실행되야 하는 상황에서 모든 프로그램마다 위와 같이 동일한 라이브러리가 포함되어서 실행된다면
심각한 메모리 낭비를 불러오게 됩니다. (물론 디스크 공간의 낭비도 무시할 수 없지요.)

그래서 Windows OS 설계자들은 (필요에 의해) 아래와 같은 DLL 개념을 고안해 내었습니다.

"프로그램내에 라이브러리를 포함시키지 말고 별도의 파일(DLL)로 구성하여 필요할 때마다 불러쓰자."
"일단 한번 로딩된 DLL 의 코드, 리소스는 Memory Mapping 기술로 여러 Process 에서 공유해 쓰자."
"라이브러리가 업데이트 되었을때 해당 DLL 파일만 교체하면 되니 쉽고 편해서 좋다."


실제 DLL 로딩 방식은 2가지 입니다.
프로그램내에서 사용되는 순간에 로딩하고 사용이 끝나면 메모리에서 해제 시키는 방법(Explicit Linking)
프로그램 시작할 때 같이 로딩되어 프로그램 종료 할 때 메모리에서 해제되는 방법(Implicit Linking)이 있습니다.

IAT 는 바로 Implicit Linking 에 대한 매카니즘을 제공하는 역할을 합니다.

IAT 의 확인을 위해 OllyDbg notepad.exe 를 열어보겠습니다.
아래 그림은 kernel32.dll CreateFileW 를 호출하는 코드입니다.


CreateFileW 를 호출할 때 직접 호출하지 않고 01001104 주소에 있는 값을 가져와서 호출합니다.
(모든 API 호출은 이런 방식으로 되어 있습니다.)

01001104 주소는 notepad.exe 의 ".text" 섹션 메모리 영역입니다. (더 정확히는 IAT 메모리 영역입니다.)
01001104 주소의 값은 7C8107F0 이며, 
7C8107F0 주소가 바로 notepad.exe 프로세스 메모리에 로딩된 kernel32.dll 의 CreateFileW 함수 주소입니다.

여기서 한가지 의문이 생깁니다.
"그냥 CALL 7C8107F0 이라고 하면 더 편하고 좋지 않나요?"
컴파일러가 CALL 7C8107F0 이라고 정확히 써줬다면 더 좋지 않냐는 의문이 들 수 있습니다만,
그건 바로 위에서 설명 드렸던 DOS 시절의 방식입니다.

notepad.exe 제작자가 프로그램을 컴파일(생성)하는 순간에는 이 notepad.exe 프로그램이
어떤 Windows(9X, 2K, XP, Vista, etc), 어떤 언어(KOR, ENG, JPN, etc), 어떤 Service Pack 에서
실행 될 지 도저히 알 수 없습니다.

위에서 열거한 모든 환경에서 kernel32.dll 의 버전이 틀려지고, CreateFileW 함수의 위치(주소)가 틀려집니다.
모든 환경에서 CreateFileW 함수 호출을 보장하기 위해서 컴파일러는 CreateFileW 의 실제 주소가 저장될 위치(01001104)를
준비하고 CALL DWORD PTR DS:[1001104] 형식의 명령어를 적어두기만 합니다.


파일이 실행되는 순간 PE Loader 가 01001104 의 위치에 CreateFileW 의 주소를 입력해줍니다.

또 다른 이유는 DLL Relocation 때문입니다.
일반적인 DLL 파일의 ImageBase 값은 10000000h 입니다.

예를 들어 어떤 프로그램이 a.dll 과 b.dll 을 사용한다고 했을때,
PE Loader는 먼저 a.dll 을 ImageBase 값인 메모리 10000000h 에 잘 로딩합니다.
그 다음 b.dll 을 ImageBase 값인 메모리 10000000h 에 로딩하려고 봤더니, 이미 그 주소는 a.dll 이 사용하고 있었습니다.
그래서 PE Loader 는 다른 비어있는 메모리 공간(ex:3E000000h) 을 찾아서 b.dll 을 로딩시켜 줍니다.

이것이 DLL Relocation 이며 실제 주소를 하드코딩 할 수 없는 이유입니다.
또한 PE Header 에서 주소를 나타낼때 VA 를 쓰지 못하고 RVA 를 쓰는 이유이기도 합니다.

* DLL 은 PE Header 에 명시된 ImageBase 에 로딩된다고 보장할 수 없습니다.
 
반면에 process 생성 주체가 되는 EXE 파일은 자신의 ImageBase 에 정확히 로딩되지요. 
  (자신만의 가상 메모리 공간을 가지기 때문입니다.)


이것은 매우 중요한 설명입니다. 다시 한번 잘 읽어보시기 바랍니다.

이제 IAT 의 역할을 이해할 수 있으실 겁니다.
(아래에서 설명드릴 IAT 구조가 왜 이리 복잡해야 하는지에 대해서도 약간 이해가 되실 겁니다.)



IMAGE_IMPORT_DESCRIPTOR


PE 파일은 자신이 어떤 라이브러리를 Import 하고 있는지 IMAGE_IMPORT_DESCRIPTOR 구조체에 명시하고 있습니다.

* Import : library 한테서 서비스(함수)를 제공 받는 일
* Export : library 입장에서 다른 PE 파일에게 서비스(함수)를 제공 하는 일


IMAGE_IMPORT_DESCRIPTOR 구조체는 아래와 같습니다.

typedef struct _IMAGE_IMPORT_DESCRIPTOR {
    union {
        DWORD   Characteristics;            
        DWORD   OriginalFirstThunk;       // INT(Import Name Table) address (RVA)
    };
    DWORD   TimeDateStamp;
    DWORD   ForwarderChain; 
    DWORD   Name;                         // library name string address (RVA)
    DWORD   FirstThunk;                   // IAT(Import Address Table) address (RVA)
} IMAGE_IMPORT_DESCRIPTOR;

typedef struct _IMAGE_IMPORT_BY_NAME {
    WORD    Hint;                         // ordinal
    BYTE    Name[1];                      // function name string

} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;

* 출처 : Microsoft 의 Visual C++ 에서 제공하는 winnt.h

일반적인 프로그램에서는 여러 개의 Library 를 Import 하기 때문에 
Library 의 갯수 만큼 위 구조체의 배열 형식으로 존재하게 되며, 구조체 배열의 마지막은 NULL 구조체로 끝나게 됩니다.

IMAGE_IMPORT_DESCRIPTOR 구조체에서 중요한 멤버는 아래와 같습니다. (전부 RVA 값을 가집니다.)

  • OriginalFirstThunk : INT(Import Name Table) 의 주소(RVA)
  • Name : Library 이름 문자열의 주소(RVA)
  • FirstThunk : IAT(Import Address Table) 의 주소(RVA)

* PE Header 에서 'Table' 이라고 하면 '배열' 을 뜻합니다.
* INT 와 IAT 는 long type (4 byte 자료형) 배열이고 NULL 로 끝납니다. (크기가 따로 명시되어 있지 않습니다.)
* INT 에서 각 원소의 값은 IMAGE_IMPORT_BY_NAME 구조체 주소값을 가지고 있습니다.
   (IAT 도 같은 값을 가지는 경우가 있습니다.)
* INT 와 IAT 의 크기는 같아야 합니다.


아래 그림은 notepad.exe 의 kernel32.dll 에 대한 IMAGE_IMPORT_DESCRIPTOR 구조를 표시하고 있습니다.


<Fig. IAT 구조>

PE Loader
가 Import 함수 주소를 IAT 에 입력하는 기본적인 순서를 설명드리겠습니다.

1. IID 의 Name 멤버를 읽어서 라이브러리의 이름 문자열("kernel32.dll")을 얻습니다.
2. 해당 라이브러리("kernel32.dll")를 로딩합니다.
3. IID 의 OriginalFirstThunk 멤버를 읽어서 INT 주소를 얻습니다.
4. INT 에서 배열의 값을 하나씩 읽어 해당 IMAGE_IMPORT_BY_NAME 주소(RVA)를 얻습니다.
5. IMAGE_IMPORT_BY_NAME 의 Hint(ordinal) 또는 Name 항목을 이용하여 해당 함수("GetCurrentThreadId")의 시작 주소를 얻습니다.
6. IID 의 FirstThunk(IAT) 멤버를 읽어서 IAT 주소를 얻습니다.
7. 해당 IAT 배열 값에 위에서 구한 함수 주소를 입력합니다.
8. INT 가 끝날때까지 (NULL 을 만날때까지) 위 4 ~ 7 과정을 반복합니다.


위 그림에서는 INT 와 IAT 의 각 원소가 동시에 같은 주소를 가리키고 있지만 그렇지 않은 경우도 많습니다.
(변칙적인 PE 파일에 대해서는 향후 많은 파일을 접해보면서 하나씩 배워 나가야 합니다.)



notepad.exe 를 이용한 실습


실제로 notepad.exe 를 대상으로 하나씩 살펴 보겠습니다.

그런데 실제 IMAGE_IMPORT_DESCRIPTOR 구조체 배열은 PE 파일의 어느 곳에 존재할까요?
PE Header 가 아닌 PE Body 에 위치합니다.

그곳을 찾아가기 위한 정보는 역시 PE Header 에 있습니다.
바로 IMAGE_OPTIONAL_HEADER32.DataDirectory[1].VirtualAddress 값이 
실제 IMAGE_IMPORT_DESCRIPTOR 구조체 배열의 시작 주소 입니다. (RVA 값입니다.)

IMAGE_IMPORT_DESCRIPTOR 구조체 배열을 다른 용어로는 IMPORT Directory Table 이라고도 합니다.
(위 용어를 전부 알아두셔야 남들과 의사소통이 원활해 집니다.)

IMAGE_OPTIONAL_HEADER32.DataDirectory[1] 구조체 값은 아래와 같습니다.
(첫번째 4 byte 가 VirtualAddress, 두번째 4 byte 가 Size 멤버입니다.)


 offset   value   description
----------------------------------------------
...
00000158 00000000 RVA  of EXPORT Directory

0000015C 00000000 size of EXPORT Directory

00000160 00007604 RVA  of IMPORT Directory
00000164 000000C8 size of IMPORT Directory

00000168 0000B000 RVA  of RESOURCE Directory
0000016C 00008304 size of RESOURCE Directory
...

* 위 구조체에 대해 궁금하신 분들께서는 IMAGE_OPTIONAL_HEADER 설명 을 참고하시기 바랍니다.
* DataDirectory 구조체에서 Size 멤버는 중요하지 않습니다. (PE Loader 에서 사용되지 않는 값입니다.)

위 그림에서 보듯이 RVA 가 7604h 이니까 File Offset 은 6A04h 입니다.
파일에서 6A04h 를 보면 아래 그림과 같습니다.


그림에서 파란색으로 표시된 부분이 전부 IMAGE_IMPORT_DESCRIPTOR 구조체 배열이고,
빨간 테두리로 되어 있는 부분은 구조체 배열의 첫번째 원소입니다. 
(참고로 배열의 마지막은 NULL 구조체로 되어 있는 것도 확인 할 수 있습니다.)

빨간 테두리의 IMAGE_IMPORT_DESCRIPTOR 구조체를 각 멤버별로 살펴보겠습니다.

OriginalFirstThunk (INT) = 7990h (file offset : 6D90h)
TimeDateStamp            = FFFFFFFFh
ForwarderChain           = FFFFFFFFh

Name                     = 7AACh (file offset : 6EACh)
FirstThunk (IAT)         = 12C4h (file offset : 6C4h)

우리는 IAT 를 공부하는 입장이기 때문에 hex editor 를 이용하여 하나하나 따라가도록 하겠습니다.
(편의를 위해 위 구조체 값(RVA) 를 미리 file offset 으로 변환해 놓았습니다.)

* RVA 를 file offset 으로 변환하는 방법에 대해서는 IMAGE_SECTION_HEADER 설명을 참고하세요.

그럼 순서대로 진행해 볼까요?


1. 라이브러리 이름 (Name)

Name 멤버를 따라가면 쉽게 구할 수 있습니다. (RVA : 7AACh -> file offset : 6EACh)


2. OriginalFirstThunk - INT(Import Name Table)

OriginalFirstThunk 멤버를 따라 갑니다. (RVA : 7990h -> file offset : 6D90h)


위 그림이 INT 입니다. 주소 배열 형태로 되어 있습니다. (배열의 끝은 NULL 로 되어 있습니다.)
주소값 하나 하나가 각각의 IMAGE_IMPORT_BY_NAME 구조체를 가리키고 있습니다. (<Fig. IAT 구조> 참고)

배열의 첫번째 값인 7A7Ah (RVA) 를 따라가 볼까요?

3. IMAGE_IMPORT_BY_NAME

RVA 값 7A7Ah 는 file offset 으로 6E7Ah 입니다.


앞에 2 byte 는 Hint (ordinal) 로써 라이브러리에서 함수의 고유번호 입니다.
ordinal 뒤로 "PageSetupDlgW" 함수 이름 문자열이 보이시죠? (문자열 마지막은 '\0' - C 언어와 동일)

여기까지 정리하면 INT 는 "함수 이름 주소 배열" 인데 첫번째 원소가 가리키는 함수 이름은 "PageSetupDlgW" 였습니다.

이제 IAT 에 해당 함수가 실제 메모리에 매핑된 주소를 얻어서 (GetProcAddress API 참고) IAT 에 입력하면 됩니다.

4. FirstThunk - IAT (Import Address Table)

IAT 의 RVA 값은 12C4h 이고 file offset 으로는 6C4h 입니다.


위 그림이 "comdlg32.dll" 라이브러리에 해당하는 IAT 입니다.
INT 와 마찬가지로 주소 배열 형태로 되어 있으며 배열의 끝은 NULL 입니다.

IAT 의 첫번째 원소값은 이미 76324906h 로 하드 코딩되어 있습니다.
notepad.exe 파일이 메모리에 로딩될 때 이 값은 위 3번에서 구한 정확한 주소값으로 대체 됩니다.

* 사실 제 시스템(Windows XP SP3) 에서 76324906h 주소는 comdlg32.dll!PageSetupDlgW 함수의 정확한 주소값입니다.
* MS 가 서비스팩을 배포하면서 관련 시스템 파일을 재빌드 할때 이미 정확한 주소를 하드 코딩 한것입니다.
  (일반적인 DLL 은 IAT 에 실제 주소가 하드 코딩되어 있지 않고, INT 와 같은 값을 가지는 경우가 많습니다.)
* 참고로 일반적인 DLL 파일은 ImageBase 가 10000000h 으로 되어 있어서 보통 DLL relocation 이 발생하지만,
   Windows 시스템 DLL 파일들(kernel32, user32, gdi32, etc)은 고유의 ImageBase 가 있어서 
   DLL relocation 이 발생하지 않습니다.


OllyDbg 를 이용해서 notepad.exe 의 IAT 를 확인해 보겠습니다.


notepad.exe 의 ImageBase 값은 01000000h 입니다.
따라서 comdlg32.dll!PageSetupDlgW 함수의 IAT 주소는 010012C4h 이며 76324906h 로 정확한 값이 들어와 있습니다.

* XP SP3 notepad.exe 를 다른 OS (2000, Vista, etc) 혹은 다른 ServicePack(SP1, SP2) 에서 실행하면,
  010012C4h 주소에는 다른 값이 세팅됩니다. (그 OS 혹은 ServicePack 에 있는 comdlg32.dll!PageSetupDlgW 의 주소)


해당 주소(76324906h)로 가면 아래와 같이 comdlg32.dll 의 PageSetupDlgW 함수 시작이 나타납니다.




이상으로 IAT(Import Address Table) 에 대한 기본 설명을 마치겠습니다.

IAT 는 Windows 리버싱에서 중요한 개념이기 때문에 반드시 잘 익혀두셔야 합니다.

향후 변칙적인 IAT 를 가지는 PE Patch 을 볼 때 IAT 를 다시 한번 살펴볼 기회가 있을것입니다.

다음번에는 PE Header 설명의 마지막으로 EAT(Export Address Table) 에 대해서 공부해 보겠습니다.



반응형

+ Recent posts