반응형

 

앞에서 DLL Injection 의 개념에 대해서 알아보았고 CreateRemoteThread() API 를 이용하여 DLL Injection 을 실제로 구현 해보았습니다.

이번에는 DLL Injection 의 또 다른 구현 방법들에 대해서 알아보도록 하겠습니다.




AppInit_DLLs


Windows 운영체제에서 기본으로 제공하는 레지스트리 키 중에서 AppInit_DLLs 란 것이 있습니다.

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows
  AppInit_DLLs


<Fig. 1>

AppInit_DLLs 값에 인젝션을 원하는 DLL 경로를 써준 후 재부팅하면, 이후 Windows 운영체제는 재부팅하면서 실행되는 모든 프로세스에 해당 DLL 을 인젝션 시켜줍니다. 너무 간단하면서도 너무 강력한 기능이지요.

간단한 실습을 해보겠습니다. 일단 아래 myhack2.cpp 소스를 보시죠.

// myhack2.cpp

#include "windows.h"

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

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
    char szCmd[MAX_PATH]  = {0,};
    char szPath[MAX_PATH] = {0,};
    char *szProcess = NULL;
    STARTUPINFO si = {0,};
    PROCESS_INFORMATION pi = {0,};

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

    switch( fdwReason )
    {
        case DLL_PROCESS_ATTACH :
            if( !GetModuleFileName( NULL, szPath, MAX_PATH ) )
                break;
   
            if( !(szProcess = strrchr(szPath, '\\')) )
                break;

            szProcess++;
            if( stricmp(szProcess, DEF_DST_PROC) )
                break;

            wsprintf(szCmd, "%s %s", DEF_CMD, DEF_ADDR);
            if( !CreateProcess(NULL, (LPTSTR)(LPCTSTR)szCmd,
                               NULL, NULL, FALSE, 
                               NORMAL_PRIORITY_CLASS,
                               NULL, NULL, &si, &pi) )
                break;

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

            break;
    }
  
    return TRUE;
}

소스 코드 내용은 간단합니다.
현재 자신을 로딩한 프로세스 이름이 "notepad" 라면 IE(Internet Explorer) 를 숨김모드로 실행시켜 Naver 사이트에 접속하게 됩니다. 목적에 따라서 다양한 업무를 수행할 수 있겠죠?

차례대로 따라해보겠습니다.

#1. 파일 복사

myhack2.dll


첨부된 파일을 적절한 위치에 복사합니다. (제 경우엔 C:\work\myhack2.dll)

#2. 레지스트리 값(AppInit_DLLs) 입력

regedit.exe 를 실행하여 아래와 같이 입력합니다. (myhack2.dll 의 전체경로를 입력해주세요.)

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows

<Fig. 2>

#3. 재부팅

재부팅이 완료되었으면 Process Explorer 를 이용해서 과연 myhack.dll 이 모든 프로세스에 인젝션 되었는지 확인해 보겠습니다.


<Fig. 3>

myhack2.dll 이 모든 프로세스에 성공적으로 인젝션 되었습니다.
인젝션된 myhack2.dll 은 아무 동작을 하고 있지 않습니다. (notepad 프로세스만 대상으로 한다는 걸 기억하세요.)

notepad 를 실행하면 아래 그림과 같이 IE 가 (숨김 속성으로) 실행되는걸 확인할 수 있습니다.


<Fig. 4>


* 주의!
AppInit_DLLs 레지스트리 키는 너무나 강력해서 모든 프로세스에 DLL 을 인젝션 시켜버립니다.
만약 인젝션되는 DLL 에 문제(버그)가 있다면 자칫 Windows 부팅이 안되는 상황이 발생할 수 있습니다.
이 경우 Window Preinstalled Environment 부팅 CD (예:BartPE)가 없다면 복구할 수 없습니다.
따라서 AppInit_Dlls 를 사용할 때는 사전에 철저한 테스트가 필요합니다.




SetWindowsHookEx() - message hook


SetWindowsHookEx() API 를 이용하여 메시지 훅을 설치하면 OS 에서 hook procedure 를 담고 있는 DLL 을 (윈도우를 가진) 프로세스에 강제로 인젝션 시켜줍니다.

이 또한 DLL Injection 의 한 기법입니다.

자세한 설명은 제 글을 참고하시기 바랍니다.

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


다음번에는 실행파일의 PE Header 정보를 조작하여 원하는 DLL 파일을 로딩시키는 방법에 대해서 알아보겠습니다. (엄밀히 말하면 DLL Injection 이라고 할 수 는 없습니다만, 리버싱에서 즐겨 사용되는 방법이기에 같이 소개합니다.)


Dll Injection - 다른 프로세스에 침투하기 (4)


ReverseCore


반응형
반응형

DLL Injection 구현 방법


몇 가지 구현 방법이 있습니다.

그 중에서 가장 유명한 방법이 CreateRemoteThread() API 를 이용하는 방법입니다.

이 방법은 윈도우즈 프로그래밍 서적의 바이블인 Jeffrey Richter Programming Applications for Microsoft Windows 에 소개된 내용입니다.

일단 소스 코드를 보겠습니다. (엔지니어에게는 백 마디 설명 보다는 역시 소스 코드를 한번 보는게 낫죠.)

먼저 Injection 시킬 myhack.dll 소스 코드입니다.

myhack.cpp


// myhack.cpp

#include "stdio.h"
#include "windows.h"

#pragma comment(lib, "urlmon.lib")

#define DEF_NAVER_ADDR ("http://www.naver.com/index.html")
#define DEF_INDEX_PATH ("c:\\index.html")

DWORD WINAPI ThreadProc(LPVOID lParam)
{
    URLDownloadToFile(NULL, DEF_NAVER_ADDR, DEF_INDEX_PATH, 0, NULL);
    return 0;
}

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
    HANDLE hThread = NULL;

    switch( fdwReason )
    {
        case DLL_PROCESS_ATTACH :
            hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
            CloseHandle(hThread);
            break;
    }
  
    return TRUE;
}

아주 간단한 코드입니다.

DllMain() 을 보시면 DLL 이 로딩(DLL_PROCESS_ATTACH)될 때 스레드(ThreadProc)를 실행합니다.

ThreadProc() 의 내용은 urlmon.dll 의 URLDownloadToFile() 함수를 실행시켜서 네이버 초기화면(index.html)을 다운받습니다.

프로세스에 DLL Injection 이 발생하면 해당 DLL 의 DllMain() 함수가 호출된다고 이전 포스트에서 설명드렸습니다. 따라서 notepad.exe 프로세스에 myhack.dll 이 Injection 되면 결국 URLDownloadToFile() 함수가 실행될 것입니다.

* DLLMain() 에서 직접 URLDownloadToFile() 을 호출하면 간혹 hang 이 걸리는 경우가 있어서, 별도의 스레드를 생성하여 호출하도록 프로그래밍 하였습니다.



이제 myhack.dll 을 notepad.exe 프로세스에 Injection 시켜줄 프로그램(InjectDll.exe)의 소스코드를 보시겠습니다.

InjectDll.cpp


// InjectDll.exe

#include "stdio.h"

#include "windows.h"
#include "tlhelp32.h"

#define DEF_PROC_NAME ("notepad.exe")
#define DEF_DLL_PATH ("c:\\myhack.dll")

DWORD FindProcessID(LPCTSTR szProcessName);
BOOL InjectDll(DWORD dwPID, LPCTSTR szDllName);

int main(int argc, char* argv[])
{
    DWORD dwPID = 0xFFFFFFFF;
 
    // find process
    dwPID = FindProcessID(DEF_PROC_NAME);
    if( dwPID == 0xFFFFFFFF )
    {
        printf("There is no <%s> process!\n", DEF_PROC_NAME);
        return 1;
    }

    // inject dll
    InjectDll(dwPID, DEF_DLL_PATH); 

    return 0;
}

DWORD FindProcessID(LPCTSTR szProcessName)
{
    DWORD dwPID = 0xFFFFFFFF;
    HANDLE hSnapShot = INVALID_HANDLE_VALUE;
    PROCESSENTRY32 pe;

    // Get the snapshot of the system
    pe.dwSize = sizeof( PROCESSENTRY32 );
    hSnapShot = CreateToolhelp32Snapshot( TH32CS_SNAPALL, NULL );

    // find process
    Process32First(hSnapShot, &pe);
    do
    {
        if(!_stricmp(szProcessName, pe.szExeFile))
        {
            dwPID = pe.th32ProcessID;
            break;
        }
    }
    while(Process32Next(hSnapShot, &pe));

    CloseHandle(hSnapShot);

    return dwPID;
}

BOOL InjectDll(DWORD dwPID, LPCTSTR szDllName)
{
    HANDLE hProcess, hThread;
    HMODULE hMod;
    LPVOID pRemoteBuf;
    DWORD dwBufSize = lstrlen(szDllName) + 1;
    LPTHREAD_START_ROUTINE pThreadProc;

    // #1. dwPID 를 이용하여 대상 프로세스(notepad.exe)의 HANDLE을 구함
    if ( !(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)) )
        return FALSE;

    // #2. 대상 프로세스(notepad.exe) 메모리에 szDllName 크기만큼 메모리를 할당
    pRemoteBuf = VirtualAllocEx(hProcess, NULL, dwBufSize, MEM_COMMIT, PAGE_READWRITE);

    // #3. 할당 받은 메모리에 myhack.dll 경로("c:\\myhack.dll")를 씀
    WriteProcessMemory(hProcess, pRemoteBuf, (LPVOID)szDllName, dwBufSize, NULL);

    // #4. LoadLibraryA() API 주소를 구함
    hMod = GetModuleHandle("kernel32.dll");
    pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hMod, "LoadLibraryA");

    // #5. notepad.exe 프로세스에 스레드를 실행
    hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, pRemoteBuf, 0, NULL);
    WaitForSingleObject(hThread, INFINITE); 

    CloseHandle(hThread);
    CloseHandle(hProcess);

    return TRUE;
}

역시 코드가 간결합니다. (편의상 예외처리, 리턴값 체크 등의 코드는 생략하였습니다.)

main() 함수에서는 2개의 서브 함수를 호출하고 있습니다.

FindProcessID(DEF_PROC_NAME) 함수는 프로세스 이름으로 PID(Process ID) 를 구해주는 함수입니다. (설명은 생략합니다.) 그리고 InjectDll(dwPID, DEF_DLL_PATH) 함수가 바로 DLL Injection 을 해주는 핵심 함수입니다.

InjectDll() 함수를 자세히 살펴보겠습니다.

InjectDll() 함수는 대상 프로세스(notepad.exe)로 하여금 스스로 LoadLibrary("myhack.dll") API 를 호출하도록 명령하는 기능을 가지고 있습니다.

#1. 대상 프로세스 핸들 구하기

hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID))

OpenProcess() API 를 이용해서 notepad.exe 의 프로세스 핸들을 구합니다. (이때 미리 구해놓은 PID 를 사용함)
이 프로세스 핸들(hProcess)을 이용해서 해당 프로세스(notepad.exe)를 제어할 수 있습니다.

#2-3. 대상 프로세스 메모리에 Injection 시킬 DLL 경로를 써주기

pRemoteBuf = VirtualAllocEx(hProcess, NULL, dwBufSize, MEM_COMMIT, PAGE_READWRITE);

대상 프로세스(notepad.exe)에게 로딩할 DLL 파일의 경로(문자열)를 알려줘야 합니다.
아무 메모리 공간에 쓸 수 없으므로 VirtualAllocEx() API 를 이용하여 대상 프로세스(notepad.exe) 메모리 공간에 버퍼를 할당합니다. 버퍼 크기는 DLL 경로 문자열 길이(NULL 포함)입니다. 

* 주의! 
VirtualAllocEx() 함수의 리턴값(pRemoteBuf)은 할당된 버퍼 주소입니다. 이 주소는 내 프로세스(Inject.exe)의 메모리 주소가 아니라 hProcess 핸들이 가리키는 대상 프로세스(notepad.exe)의 메모리 주소라는것을 꼭 기억하시기 바랍니다.


WriteProcessMemory(hProcess, pRemoteBuf, (LPVOID)szDllName, dwBufSize, NULL);

할당 받은 버퍼 주소(pRemoteBuf)에 WriteProcessMemory() API 를 이용하여 DLL 경로 문자열("C:\\myhack.dll")을 써줍니다.

이로써 대상 프로세스(notepad.exe) 메모리 공간에 Injection 시킬 DLL 파일의 경로가 생겼습니다.

* 참고
Win32 프로그래밍을 처음 배울 때 분명 다른 프로세스의 메모리에 읽고 쓰는 일이 어렵다고(혹은 불가능하다고) 배웠습니다. 하지만 실제로는 다른 프로세스의 메모리 공간에 접근을 못하면 운영체제도 답답해 집니다. (예를 들어 다른 프로세스 메모리에 접근 할 수 없다면 디버거 제작이 불가능해지지요.) 그래서 Windows 운영체제는 Debug API 를 제공하여 다른 프로세스 메모리 공간에 접근 할 수 있도록 하였습니다. 대표적인 Debug API 가 바로 위에서 소개해 드린 VirtualAllocEx(), VirtualFreeEx(), WriteProcessMemory(), ReadProcessMemory() 등이 있습니다.

#4. LoadLibraryA() API 주소를 구하기

hMod = GetModuleHandle("kernel32.dll");
pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hMod, "LoadLibraryA");


LoadLibrary() API 를 호출시키기 위해 그 주소가 필요합니다.
(LoadLibraryA() 는 LoadLibrary() 의 ASCII 문자열 버전입니다.)

위 코드의 의미를 잘 생각해봐야 합니다.
우리는 분명 notepad.exe 에 로딩된 kernel32.dll 의 LoadLibraryA() API 의 시작 주소를 알아내야 합니다.
하지만 위 코드는 InjectDll.exe 에 로딩된 kernel32.dll 의 LoadLibraryA() API 의 시작 주소를 얻어내고 있습니다.

notepad.exe 에 로딩된 kernel32.dll 과 InjectDll.exe 에 로딩된 kernel32.dll 의 메모리 시작 위치(ImageBase)가 동일 하다면 위 코드는 문제가 없습니다.

일반적인 DLL 파일의 ImageBase 는 0x10000000 으로 설정되기 때문에 a.dll 과 b.dll 을 차례대로 로딩하면 a.dll 은 정상적으로 0x1000000 주소에 로딩이 되겠지만 b.dll 은 자신이 원하는 0x10000000 주소에 로딩되지 못하고 다른 비어 있는 주소 공간에 로딩됩니다. 즉, DLL Relocation 이 발생하는 것입니다. (a.dll 이 같은 주소에 이미 로딩되어 있기 때문입니다.)

만약 kernel32.dll 이 프로세스마다 다른 주소에 로딩된다면 위 코드는 잘못된 것입니다.
하지만 실제 Winodows 운영체제에서 kernel32.dll 은 프로세스마다 같은 주소에 로딩됩니다.

어째서 그런걸까요?

PE View 를 통해서 Windows 운영체제의 핵심 DLL 파일들의 ImageBase 값을 조사해 봤습니다.
(Windows XP SP3 KOR 버전입니다. Windows 업데이트 상태에 따라서 아래 값들은 달라질 수 있습니다.)

DLL file        ImageBase       SizeOfImage
--------------------------------------------
msvcrt.dll      77BC0000        00058000
user32.dll      77CF0000        00090000
gdi32.dll       77E20000        00049000
advapi32.dll    77F50000        000A8000
kernel32.dll    7C7D0000        00130000
shell32.dll     7D5A0000        007FD000
...


Microsoft 에서 친절하게 OS 핵심 DLL 파일들의 ImageBase 값을 이쁘게 정리해놨습니다.
즉, 자신들끼리 절대로 겹치지 않고 따라서 DLL Relocation 이 발생하지 않습니다.

Dll Injection 기법은 위와같이 OS 핵심 DLL 들은 자신만의 고유한 주소에 로딩된는 것을 보장해주는 Windows 특성을 이용한 것입니다. (이 특성이 Windows 보안 취약점으로 이용되기도 합니다.)

따라서 InjectDll.exe 프로세스에 import 된 LoadLibraryA() 주소와 notepad.exe 프로세스에 import 된 LoadLibraryA() 주소는 동일합니다.

* 참고!
모든 Windows 프로세스는 kernel32.dll 을 로딩합니다.
PE Header 를 조작하여 IAT 에서 kernel32.dll 항목을 제거해버려도 loader 가 강제로 kernel32.dll 을 로딩시켜버립니다. (XP 부터 해당됨. 2000 에서는 실행불가.)


#5. 대상 프로세스에 스레드를 실행 시킴

모든 준비는 끝났고 마지막으로 notepad.exe 로 하여금 LoadLibraryA() API 를 호출하도록 명령만 내리면 됩니다. 하지만 Windows 에서는 그런 API 를 제공하지 않습니다.

그래서 편법(?)으로 CreateRemoteThread() API 를 사용합니다.
(편법이라기 보다는 DLL Injection 의 정석이라고 말 할 수 있지요.)

CreateRemoteThread() API 는 다른 프로세스에게 스레드를 실행시켜주는 함수입니다.

HANDLE WINAPI CreateRemoteThread(
  __in   HANDLE                   hProcess,             // 프로세스 핸들
  __in   LPSECURITY_ATTRIBUTES    lpThreadAttributes,
  __in   SIZE_T                   dwStackSize,
  __in   LPTHREAD_START_ROUTINE   lpStartAddress,       // 스레드 함수 주소
  __in   LPVOID                   lpParameter,          // 스레드 파라미터 주소

  __in   DWORD                    dwCreationFlags,
  __out  LPDWORD                  lpThreadId
);


첫번째 파라미터인 hProcess 만 빼면 일반적으로 사용되는 CreateThread() 함수와 다 똑같습니다.

hProcess 파라미터가 바로 스레드를 실행시킬 프로세스의 핸들입니다.
lpStartAddress 와 lpParameter 파라미터는 각각 스레드 함수 주소와 스레드 파라미터 주소입니다.
중요한건 이 주소들이 대상 프로세스의 가상 메모리 공간의 주소이어야 한다는 것입니다. (그래야 그 프로세스에서 인식을 할 수 있겠죠.)

좀 어리둥절 하시죠?
다른 프로세스에 DLL 을 injection 시키는데 스레드가 무슨 상관일까요?

스레드 함수 ThreadProc() 과 LoadLibrary() API 를 보시면 힌트를 얻을 수 있습니다.

DWORD WINAPI ThreadProc(
  __in  LPVOID           lpParameter
);

HMODULE WINAPI LoadLibrary(
  __in  LPCTSTR          lpFileName
);


두 함수 모두 4 byte 파라미터를 받고, 4 byte 값을 리턴하지요.
바로 여기서 아이디어를 얻은 것입니다.

CreateRemoteThread() 를 호출해서 4 번째 파라미터 lpStartAddress 에 "LoadLibrary() 주소"를 주고, 5 번째 파라미터 lpParameter 에 원하는 "DLL 의 경로" 문자열을 주면 됩니다. (반드시 대상 프로세스의 가상 메모리 공간에의 주소이여야 합니다.)

우린 이미 위에서 다 준비해놨지요. 편안하게 호출해주면 됩니다.

hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, pRemoteBuf, 0, NULL);

  pThreadProc = notepad.exe 의 LoadLibraryA() 주소
  pRemoteBuf = notepad.exe 의 "c:\\myhack.dll" 문자열 주소


CreateRemoteThread() 는 스레드를 생성하는 것이 아니라 실제로는 LoadLibraryA() 를 호출 시키는 것입니다.



+---+

CreateRemoteThread() 를 이용한 Dll Injection 기법에 대한 설명을 마치겠습니다.

처음에는 잘 이해가 되지 않을 수 있습니다. 설명을 다시 차근차근 읽어보시고 직접 실습해 보세요.

다음 번에는 다른 Dll Injection 기법들과 한번 Injection 된 Dll 을 꺼내는 (Ejection) 방법에 대해서 설명드리도록 하겠습니다.


Dll Injection - 다른 프로세스에 침투하기 (3)


ReverseCore

반응형
반응형

DLL Injection 에 대해서 자세히 알아보겠습니다.

DLL Injection 기법은 다른 프로세스에 침투하는 가장 쉽고 강력한 방법입니다.



DLL Injection 이란?


다른 프로세스에 특정 DLL 파일을 강제로 삽입시키는 것입니다.

더 정확히 표현하면 다른 프로세스에게 LoadLibrary() API 를 호출하도록 명령하여 내가 원하는 DLL 을 loading 시키는 것입니다.

따라서 DLL Injection 이 일반적인 DLL loading 과 다른점은 loading 대상이 되는 프로세스가 내 자신이냐 아니면 다른 프로세스냐 하는 것입니다.

아래 그림을 봐주세요.


<Fig. 1>

notepad 프로세스에 myhack.dll 을 강제로 삽입시켰습니다.
(원래 notepad 는 myhack.dll 을 로딩하지 않습니다.)

notepad 프로세스에 로딩된 myhack.dll 은 notepad 프로세스 메모리에 대한 (정당한) 접근권한이 생겼기 때문에 사용자가 원하는 어떤 일이라도 수행할 수 있습니다. (예: notepad 에 통신기능을 추가하여 메신저나 텍스트 웹브라우저 등으로 바꿔버릴 수도 있습니다.)



DLL Injection 사용 목적


LoadLibrary() API 를 이용해서 어떤 DLL 을 로딩시키면 해당 DLL 의 DllMain() 함수가 실행됩니다.

DLL Injection 의 동작원리는 외부에서 다른 프로세스로 하여금 LoadLibrary() API 를 호출하도록 만드는 것이기 때문에 (일반적인 DLL loading 과 마찬가지로) 강제 삽입된 DLL 의 DllMain() 함수가 실행됩니다.

또한 삽입된 DLL 은 해당 프로세스의 메모리에 대한 접근권한을 갖기 때문에 사용자가 원하는 다양한 일(버그 패치, 기능 추가, 기타)을 수행 할 수 있습니다.

그런데 문제는 대부분 악의적인 용도로 사용된다는 것입니다.

- 악성 코드

정상적인 프로세스(winlogon.exe, services.exe, svchost.exe, explorer.exe, etc)에 몰래 숨어들어가 악의적인 짓들을 합니다. 다른 악성 파일을 다운받거나, 몰래 백도어를 열어두고 외부에서 접속하거나, 키로깅 등의 나쁜짓을 하는 것이죠.

정상 프로세스 내부에서 벌어지는 일이라서 일반적인 PC 사용자들은 눈치채기 어렵습니다.
만약 explorer.exe 가 80 포트로 어떤 사이트에 접속시도를 한다고 생각해보세요.
일반인들은 그냥 정상 프로세스의 정상적인 동작으로 생각하기 쉽습니다.

- 유해 프로그램, 사이트 차단 프로그램

부모님들께서 아이들의 건전한 PC 사용을 위해 설치하는 '프로세스 차단 프로그램' 등이 있습니다.
주로 게임 프로그램과 성인 사이트 접속 등을 방지하는 역할을 하지요.

아이들 입장에서야 원수(?)같은 프로그램이라서 미친듯이 해당 프로세스를 종료하려 듭니다.
DLL Injection 기법으로 정상 프로세스에 살짝 숨어들어간다면 들키지도 않고, 프로세스 강제 종료에도 안전하게 됩니다. (Windows 핵심 프로세스를 종료하면 시스템이 같이 종료되기 때문에 결국 '차단' 하려는 목적을 달성하는 셈이지요.)

- 기능 개선 및 (버그) 패치 

만약 어떤 프로그램의 소스 코드가 없거나 수정이 여의치 않을 때 DLL Injection 을 사용하여 전혀 새로운 기능을 추가하거나 (PlugIn 개념) 문제가 있는 코드, 데이타를 수정할 수 있습니다.
일단 프로세스 메모리에 침투한 DLL 은 해당 프로세스의 메모리에 자유롭게 접근 할 수 있기 때문에 코드, 데이타를 고칠 수 있습니다.
저도 예전에 인터넷에서 다운받은 헥사 에디터에 버그가 있길래 DLL Injection 기법을 이용해서 해당 버그 코드를 간단히 패치해서 사용한 적이 있습니다. (그냥 다른 헥사 에디터를 다운 받아도 써도 되지만 고쳐 쓰고 싶은 마음에... ^^)

- API Hooking

다음번에 설명드릴 주제인데요, API Hooking 에 DLL Injection 기법이 많이 사용됩니다.
정상적인 API 호출을 중간에서 후킹하여 제가 원하는 기능을 추가 (혹은 기존 기능을 제거)하는 목적으로 사용됩니다.

이 역시 삽입된 DLL 은 해당 프로세스의 메모리에 대한 접근 권한을 가지고 있다는 특성을 잘 활용한 것입니다.



DLL Injection 간단 실습


notepad.exe 프로세스에 myhack.dll 을 injection 시키도록 하겠습니다.
=> myhack.dll 은 www.naver.com/index.html 을 다운받도록 프로그래밍 하였습니다.

myhack.dll

InjectDll.exe


1) InjectDll.exe 와 myhack.dll 을 다운받아 C:\ 에 복사합니다.
2) 메모장(notepad.exe) 을 실행합니다.
3) InjectDll.exe 를 실행합니다.

Process Explorer 를 이용하여 notepad.exe 프로세스에 Injection 된 myhack.dll 을 확인해 보겠습니다.


<Fig. 2>

notepad.exe 프로세스 메모리 공간에 myhack.dll 이 들어와 있는게 보이시죠?

C:\index.html (네이버 초기화면 html) 파일이 정상적으로 생겼는지 볼까요?


<Fig. 3>

index.html 파일을 메모장으로 열어보겠습니다.


<Fig. 4>

정상적으로 notepad.exe 프로세스에 myhack.dll 이 Injection 되어 http://www.naver.com/index.html 파일을 받아왔습니다.

이와 같이 다른 프로세스에 침투하여 원하는 동작을 마음껏 수행할 수 있는 Dll Injection 기법의 원리와 구현 방법에 대해서 알아보겠습니다.


Dll Injection - 다른 프로세스에 침투하기 (2)


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) 에 대해서 공부해 보겠습니다.



반응형
반응형

#0. prologue

분야를 막론하고 기술자(특히 엔지니어)들은 자신만의 작업환경과 손에 익은 도구(장비)가 있습니다.

기술자(특히 엔지니어) 란 특정 업무를 위해서 필요한 도구를 아주 능숙하게 다룰 줄 아는 사람들입니다.

같은 도구를 사용하더라도 기술자의 능력에 따라서 전혀 다른 결과를 보여줍니다.
(더 나아가서 필요한 도구를 직접 만들어 내기도 합니다.)

또한 기술자들은 각자 자신만의 도구를 가지고 있으며
한번 손에 익힌 도구를 되도록 오래 쓰고 왠만해서는 바꾸려 하지 않습니다.
(바꿀 때도 될 수 있으면 같은 회사의 후속 제품으로 바꾸려고 하지요.)

남의 도구, 남의 작업환경에서 일을 하면 아무래도 불편하다고 생각합니다.

즉, 자신만의 작업환경과 자신만의 도구가 갖춰져야 그 기술자의 진정한 실력이 100% 발휘 된다고 할 수 있습니다.



#1. Reverse Code Engineer (Reverser)

리버서들은 어떨까요?

리버서도 역시 IT 엔지니어 범주에 들어가기 때문에 위에서 언급한 일반적인 기술자의 성향과 다를 바가 없습니다.

리버싱을 하기 위한 도구의 종류만도 수십 가지가 넘으며, 각 종류별로 다양한 제품들이 존재합니다.
또한 IT 분야의 특성상 계속해서 새로운 도구가 개발되고 있습니다.

도구의 종류만도 아래와 같이 매우 많습니다. (언급하지 못한 것도 많을 것입니다.)

disassembler
debugger - PE, script, etc
development tool - assembly, C/C++, etc
editor(viewer) - text, hex, resource, retistry, string, PE, etc
monitoring tool - process, file, registry, network, message, etc
memory dump
classifier
calculator - hex, binary
compare tool - text, hex
packer/unpacker
encoder/decoder
virtual machine
decompiler - VB, Delphi, etc
emulator
...



#2. 좋은 분석 도구 선택의 5 가지 기준

아래에 ReverseCore 만의 도구 선택 기준(가이드)을 제시합니다. (참고만 하세요~)


첫째, 도구 개수를 최소화시킨다.

남들이 쓴다고 해서 기능을 알지도 못하는 도구를 잔뜩 가지고 있어봐야 도움이 되지 않습니다.
자신에게 필요한 도구만을 각 종류별로 하나씩만 사용하는 것이 좋습니다.

자신의 실력에 맞는 것만을 고르고 차츰 하나씩 늘려나가시면 됩니다.

또한 중복된 기능의 도구들은 하나로 정리하는 것이 좋습니다.


둘째, 도구는 기능이 단순하고 사용방법이 편리한 것이 좋다.

실력이 늘어날 수록 사용해야 할 도구의 개수도 늘어납니다.
기능이 단순하고 인터페이스가 직관적일 수록 사용하기에 편리합니다.

여기서 기능이 단순하다는 말은 리버싱 도구치고는 단순하다는 말입니다.
(Windows 의 계산기, 메모장 수준을 생각하시면 안됩니다. ^^)

따라서 아무리 단순한 리버싱 도구를 하나 익히는 데만도 어느 정도 시간이 필요합니다.


셋째, 기능을 철저히 익힌다.

아무리 좋은 도구라도 사용할 줄 모르면 무용지물 입니다.

자신이 이미 가지고 있는 도구에서 제공되는 기능인데도,
그걸 알지 못하고 다른 도구를 찾는 분들이 많습니다.

일단 도구를 선택한 후에는 제공되는 메뉴얼을 한번 정독해보시는 것이 좋습니다.
자주 사용하는 기능의 단축키 정도는 외워 두시면 작업이 훨씬 수월합니다.
(단축키를 잘 쓰면 확실히 자타공인 전문가처럼 느껴집니다.)


넷째, 꾸준히 업데이트 한다.

리버싱도 IT 범주에 속하기 때문에 기술 발전 속도가 매우 빠릅니다.

새로운 기술에 대응하기 위해 도구들도 빠르게 변화하기 때문에
사용하는 도구의 업데이트는 매우 중요한 일입니다.

따라서 꾸준히 업데이트를 지원하는 도구를 선택하는 것이 좋습니다.


다섯째, 도구의 핵심 동작 원리를 이해한다.

도구를 더 잘 사용하기 위해서 동작 원리를 이해하는 것이 좋습니다.
더 나아가 테스트용 프로토타입을 만들 수 있다면 금상첨화 입니다.

이 부분을 간과하시는 분들이 매우 많습니다.
하지만 높은 수준의 리버싱 실력을 쌓기 위해서는 필수적인 사항입니다.

예를 들어 debugger 의 동작원리를 이해하고 있으면,
anti-debugging 기법을 잘 회피할 수 있습니다.

원리를 이해하지 못하고 도구에만 의존하면,
간단한 트릭도 해결하지 못하고 다른 도구를 찾아 나서야 합니다.
소위 말하는 "도구의 노예"가 되는 것이지요. (이것만은 꼭 경계해야 겠습니다.)



#3. epilogue

debug.exe 라는 프로그램을 아시나요?
MS-DOS 시절부터 존재하던 16-bit debugger 입니다. (XP 에도 존재합니다.)

커맨드 창에서 debug.exe 를 실행하고 '?' 명령으로 도움말을 보겠습니다.


위에 보이는 명령어가 전부입니다.

단순하지요?

제가 아는 어떤 분이 debug.exe 로 16 bit DOS 프로그램을 분석하시는 걸 본 적이 있습니다.

뭔가를 실행시키더니 키보드를 다다다다 두들기면서 화면이 번쩍 번쩍 넘어가는데,
바로 옆에서 지켜보면서도 무슨 작업을 하는지 도저히 알 수 없었습니다.
(눈이 그분의 작업 속도를 따라가지 못한 거죠.)

처음에는 실행되는 프로그램이 debug.exe 인지 조차도 몰랐었습니다.
(전 그때 당시 이미 debug.exe 를 한달 정도 써본 경험이 있었음에도 불구하고 말이죠.)

그리고는 다 됐다면서 결과를 넘겨 주시더군요.
그때 그분이 사용하신 프로그램이 debug.exe 라는 걸 알고 충격에 휩쌓였었죠.

'저 단순한 debug.exe 만으로 이 일을 이렇게 빨리 해냈단 말인가?' 하고 말이죠.

그 이후로 제가 어떤 도구를 고를 때 나름대로의 기준을 세우게 되었습니다.

그리고 하나의 깨달음을 얻었습니다.

"평범한 도구라도 극한까지 연마하면 천하에 다시 없는 비범한 도구가 된다."

마치 무림고수(武林高手)가 수련에 수련을 거듭해서 검(劍)을 버리고
결국에는 초(草)/목(木)/죽(竹)/석(石)을 모두 검처럼 사용할 수 있듯이 말이죠.


여러분은 어떻게 생각하시나요? ^^


반응형

+ Recent posts