반응형

지금까지 우리가 원하는 DLL 을 "실행중인" 프로세스에 강제로 Injection 시키는 방법에 대해서 알아봤습니다.

이번에는 접근 방법을 바꿔서 아예 대상 프로그램 "파일을 직접 수정"하여 DLL 을 강제로 로딩하도록 해보겠습니다.
이 방법은 한 번 적용해 놓으면 (별도의 Injection 과정없이) 프로세스가 시작할 때마다 원하는 DLL 을 로딩하게 만들 수 있습니다. 일종의 크랙이라고 생각하시면 됩니다.

목표는 notepad.exe 파일을 직접 수정하여 실행 시 myhack3.dll 을 로딩하도록 만드는 것입니다.
이를 위해서는 PE Header 를 자유자재로 다룰 수 있는 실력이 필요합니다.

IDT(Import Directory Table)와 기타 PE File Format 에 대해서는 아래의 글을 참조하시기 바랍니다.

- PE(Portable Executable) File Format (6) - PE Header



myhack3.dll


먼저 로딩 시킬 DLL 의 소스를 보겠습니다.

// myhack2.cpp
// ReverseCore

include "windows.h"

#define DEF_CMD  "c:\\Program Files\\Internet Explorer\\iexplore.exe"
#define DEF_ADDR "www.naver.com"

#ifdef __cplusplus
extern "C" {
#endif
__declspec(dllexport) BOOL start()
{
    char szCmd[MAX_PATH] = {0,};
    STARTUPINFO si = {0,};
    PROCESS_INFORMATION pi = {0,};

    si.cb = sizeof(STARTUPINFO);
    si.dwFlags = STARTF_USESHOWWINDOW;
    si.wShowWindow = SW_HIDE;

    // IE 를 hidden window 로 실행시켜 naver 에 접속합니다.
    wsprintf(szCmd, "%s %s", DEF_CMD, DEF_ADDR);
    if( !CreateProcess(NULL, (LPTSTR)(LPCTSTR)szCmd, 
                       NULL, NULL, FALSE,
                       NORMAL_PRIORITY_CLASS,
                       NULL, NULL, &si, &pi) )
        return FALSE;

    if( pi.hProcess != NULL )
        CloseHandle(pi.hProcess);

    return TRUE;
}
#ifdef __cplusplus
}
#endif

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
    switch( fdwReason )
    {
        case DLL_PROCESS_ATTACH :
            start();
            break;
    }
  
    return TRUE;
}

myhack3.dll


기능은 지난번과 마찬가지로 IE 를 hidden 속성으로 실행시켜 Naver 초기화면에 접속시키는 것입니다.

가장 눈에 띄는 것은 start() EXPORT 함수입니다.

PE 파일에서 어떤 DLL 을 IMPORT 한다는 것은 코드내에서 그 DLL 의 EXPORT 함수를 호출한다는 의미입니다. 그래서 myhack3.dll 파일은 형식적인 완전성을 위해 EXPORT 함수를 제공해야 합니다. (실제로 notepad.exe 내부에는 myhack3.dll!start() 함수를 호출하는 코드 같은건 존재하지 않습니다.)

프로그래밍에서 DLL 의 IMPORT 는 컴파일러/링커가 해주지만, 저는 이 작업을 PE View 와 Hex Editor 만을 가지고 수동으로 해보겠습니다. (제 환경은 WinXP SP3 KOR 버전입니다. OS 와 SP 버전에 따라서 방법이 약간 틀려질 수 있습니다.)



파일 변경 아이디어 

notepad.exe 파일이 실행될 때 자동으로 myhack3.dll 파일을 로딩하기 위해서는 PE Header 의 IMPORT DIRECTORY TABLE 에 myhack3.dll 을 (IMPORT 하도록) 추가해야 합니다.

notepad.exe 원본과 제가 수정한 notepad_hack.exe 를 첨부합니다.

notepad.exe

notepad_hack.exe


PE View 를 이용하여 notepad.exe 의 IDT 의 주소를 확인해 보겠습니다. IMAGE_OPTIONAL_HEADER 의 IMPORT Table RVA 값이 바로 IDT 의 RVA 입니다.


<Fig. 1>

위 그림에서 IDT 주소는 RVA 로 7604 입니다. 이를 PE View 로 따라가 보겠습니다. (PE View 의 주소 보기 옵션을 "RVA" 로 해주세요.)



<Fig. 2>

IDT 는 <Fig. 2> 와 같이 IMAGE_IMPORT_DESCRIPTOR(이하 IID) 구조체 배열로 되어 있으며, 마지막은 NULL 구조체로 끝납니다. IMPORT 하는 DLL 파일 하나당 IID 구조체 하나가 필요합니다. (IID 구조체 하나의 크기는 14h bytes)

PE View 의 주소 보기 옵션을 "File Offset" 으로 변환하면 IDT 의 file offset 은 6A04 입니다.
Hex editor 를 이용해 6A04 주소를 찾으면 아래 그림과 같습니다.


<Fig. 3>

IDT 는 file offset 으로 6A04 ~ 6ACC 범위에 있으며, 전체 크기는 C8 입니다.
마지막 NULL 구조체를 포함하여 총 10개의 IID 구조체가 있습니다. 

처음에 전 IDT 마지막에 myhack3.dll 을 위한 IID 구조체를 추가하려 했습니다만, 더 이상 추가할 공간이 없습니다. (마지막 NULL 구조체는 꼭 필요하거든요.)

그래서 Hex editor 로 notepad.exe 의 빈 영역(사용되지 않는 영역)을 찾아 보았습니다.
<Fig. 4> 에서 보이는 것처럼 마침 파일 끝부분에 알맞은 크기가 비어 있네요.

* 만약 파일에 빈 영역이 보이지 않는다면 파일 끝에 새로운 섹션을 추가하거나, 또는 마지막 섹션의 크기를 늘리는 방법을 사용해야 합니다.


<Fig. 4>

바로 이 위치(file offset = 10710, RVA = 13310)에 새로운 IID 를 만들것입니다.

주의 하실 점은 저 영역(file:10710 ~ 107FF, RVA:13310 ~ 133FF)이 과연 메모리에 로딩되는 영역인지 확인하는 것입니다.
파일에 존재한다고 해서 반드시 메모리에 로딩되는 것은 아닙니다. 섹션 헤더에 명시된 영역만 메모리에 로딩됩니다.


notepad 의 마지막 섹션 헤더(".rsrc") 를 살펴보겠습니다.


<Fig. 5>

RVA = B000 이고 VirtualSize = 8304 이므로 메모리에 로딩되는 영역은 RVA = 13304 까지입니다.
하지만 <Fig. 4> 의 파란색 영역(RVA:13310 ~ 133FF)은 포함되지 않는 영역입니다.

그렇다면 이부분은 메모리에 로딩되지 않을까요?
실제로는 메모리에 로딩됩니다. 이유는 notepad 에서 IMAGE_OPTIONAL_HEADER 의 SectionAlignment 값이 1000 이기 때문에 마지막 섹션(".rsrc")헤더에 명시된 위치(RVA=13304)는 실제로 14000 까지 확장됩니다. (기본 단위의 배수)

디버거에서 확인해 보겠습니다.

<Fig. 6>

위 그림을 보시면 notepad 프로세스 메모리에서 마지막 섹션(".rsrc")의 VirtualSize 값이 9000 으로 확장된 것을 보실 수 있습니다. <Fig. 5> 의 실제 VirtualSize 값은 8304 였으나 SectionAlignment (1000) 의 배수인 9000 으로 자동 확장된 것입니다.

따라서 RVA = 13310 위치에 새로운 IID 를 만드는 것은 문제가 없습니다.



IDT(Import Directory Table) 변경


notepad.exe 를 복사하셔서 이름을 notepad_hack.exe 로 변경해주세요.
이제부터 notepad_hack.exe 파일을 가지고 변경 작업을 진행합니다.

#1. IMPORT Table 의 RVA 값 변경

PE View 를 기준으로 설명드리겠습니다.
IMAGE_OPTIONAL_HEADER 의 IMPORT Table 구조체 멤버는 IDT 의 위치와 크기를 알려줍니다. <Fig. 1> 을 보시면 현재 IMPORT Table 의 RVA 값은 7604 입니다. Hex editor 에서 이 값을 새로운 IDT 의 RVA 값인 13310 으로 변경합니다. 그리고 Size 값을 기존 C8 에 14 (IID 구조체 크기) 를 더한 값인 DC 로 변경합니다. (Size 멤버는 수정 하지 않아도 됩니다.)


<Fig. 7>

PE View 로 확인하면 아래 그림과 같습니다.

<Fig. 8>

이제 notepad 실행 시 PE Loader 는 IDT 가 RVA = 13310 에 존재한다고 간주합니다.

#2. BOUND IMPORT TABLE 사용 안함


PE HEADER 에서 BOUND IMPORT TABLE 은 DLL 로딩 속도를 조금 향상시킬 수 있는 기법입니다. 이 값을 그냥 놔둬도 문제 없는 경우가 많지만, 제가 테스트할 때는 myhack3.dll 이 정상동작하지 않았습니다. (편하게 작업하시려면 이 값을 무조건 0 으로 밀어버리시기 바랍니다.)

Hex editor 를 이용해서 BOUND IMPORT Table 영역(RVA, Size)을 0 으로 밀어버립니다.

<Fig. 9>

PE View 로 확인해 볼까요?

<Fig. 10>

#3. 새로운 IDT 생성

기존 IDT (file = 6A04 ~ 6ACB, RVA = 7604 ~ 76CB) 를 복사하여 새로운 위치(file = 10710, RVA = 13310)에 붙여넣고, 나머지 지저분한 데이타들은 깔끔하게 NULL 로 밀어버립니다.

<Fig. 11>

이 상태에서 아래와 같이 myhack3.dll 을 위한 IID 를 구성하여 새로운 IDT 끝에 추가시킵니다.
(각 멤버의 데이타에 대해서는 아래에서 따로 설명드리겠습니다.)

typedef struct _IMAGE_IMPORT_DESCRIPTOR {
    union {
        DWORD   Characteristics;            
        DWORD   OriginalFirstThunk;       // INT(Import Name Table) => 8750
    };

    DWORD   TimeDateStamp;                // => FFFFFFFF

    DWORD   ForwarderChain;               // => FFFFFFFF
    DWORD   Name;                         // library name => 8760
    DWORD   FirstThunk;                   // IAT(Import Address Table) => 1348
} IMAGE_IMPORT_DESCRIPTOR;


아래는 Hex editor 로 작업한 그림입니다. (IDT 마지막에 myhack3.dll 을 위한 IID 를 추가)

<Fig. 12>

#4. IID(IMAGE_IMPORT_DESCRIPTOR) 구성

앞 단계에서 추가한 IID 구조체 멤버들이 정상적으로 동작하기 위해서 적절한 세팅이 필요합니다.

먼저 INT(Import Name Table) 와 Name 멤버 설명입니다. 
이 값들은 각각 RVA = 8750 (file = 7B50), RVA = 8760 (file = 7B60) 입니다.

참고로 이 위치는 첫번째 섹션(".text")의 끝부분으로써 빈 영역입니다. 제가 편의상 이 영역을 선정한 것이며 다른 위치에 선정하셔도 상관 없습니다.

일단 Hex Editor 로 RVA = 8750 (file = 7B50) 위치로 가서 아래 그림과 같이 값을 입력합니다.

<Fig. 13>

위 그림에 나타난 값들의 의미를 설명드리겠습니다.

INT 는 쉽게 말해서 RVA 배열인데요, 배열의 각 원소는 Import 하는 함수의 Ordinal(2 bytes) + Func Name String 구조체의 주소(RVA) 를 나타내며 배열의 끝은 NULL 입니다. 

<Fig. 13> 에서 INT 에는 1 개의 원소가 있고 그 값은 RVA 8770 입니다. RVA 8770 으로 가보면 Import 하려는 함수의 Ordinal (2 bytes) 와 함수 이름 문자열이 나타납니다.

Name 은 Import 하는 함수를 제공하는 DLL 파일의 이름 문자열 입니다. "myhack3.dll" 이라고 보이시죠?

마지막으로 IID 의 IAT(Import Address Table) 멤버에 대해 설명드리겠습니다.

#3 에서 IAT 값을 RVA = 1348 (file = 748) 로 세팅하였습니다. 참고로 이 위치는 원래 notepad 의 IAT 영역의 끝 부분입니다.
(INT 데이타와 IAT 데이타는 각각 뭉쳐서 존재합니다.)


<Fig. 14>

IAT 역시 RVA 배열입니다. 각 원소는 INT 와 같은 값을 가져도 좋고 NULL 을 가져도 좋습니다. INT 가 정확하다면 IAT 는 다른 값을 가져도 상관없습니다. 어차피 실행 시에 PE Loader 에 의해 메모리 상에서 실제 함수 주소로 덮어써집니다. (간혹 NULL 을 넣었을때 에러는 발생하지 않지만 DLL 이 정상 동작하지 않을 때가 있습니다. 확실한 건 INT 와 같은 값을 써주면 정확히 동작합니다.)

* 반드시 IAT 를 기존 IAT 영역에 쓸 필요는 없습니다만, 프로세스에 따라서 혹은 DLL 에 따라서 정상 동작하지 않는 경우가 있습니다. 세심한 테스트가 필요한 부분입니다.

여기까지 제대로 따라 하셨으면 작업끝입니다.



검증 (Test)


새로 만든 notepad_hack.exe 파일을 PE View 로 열어보겠습니다.


<Fig. 15>

마지막 섹션(".rsrc")로 IDT 가 옮겨졌으며, myhack3.dll 을 IMPORT 시키기 위한 IID 구조체가 정상적으로 세팅된 것을 확인 하실 수 있습니다.


<Fig. 16>

<Fig. 16> 에서 추가된 INT 도 확인 하실 수 있습니다.

notepad_hack.exe 와 myhack3.dll 파일을 같은 폴더에 넣고 notepad_hack.exe 를 실행해 보겠습니다.
Process Explorer 로 확인하면 아래 그림과 같습니다.


<Fig. 17>

notepad_hack.exe 에 myhack3.dll 이 로딩되어서 IE 가 정확하게 실행되는 화면을 보실 수 있습니다.

성공입니다!



Epilogue


설명이 길어서 자칫 복잡하게 느끼실 수 있습니다.

Import Directory Table 에 내가 원하는 dll 을 추가시켜서 실행 시 자동으로 로딩하게 한다는 기본 원리만 확실히 이해하시고, 관련된 PE Header 를 참고하시면 쉽게 하실 수 있으실 겁니다.

단, OS 와 SP 버전에 따라서 PE Loader 의 구현이 조금씩 다를 수 있으니 처음 하시는 분들께서는 약간의 시행 착오를 거치셔야 합니다. 또한 대상 프로세스와 DLL 파일의 프로그래밍에 따라서 약간씩 방법을 달리 적용해야 하는 경우도 있습니다.

그러나 위의 원리를 이해하신다면 어렵지 않게 해내실 걸로 생각합니다.


+---+


다음번에는 프로세스에 Injection 된 DLL 을 강제로 꺼내는 기법인 DLL Ejection 에 대해서 알아보도록 하겠습니다.


ReverseCore



반응형

+ Recent posts