본문 바로가기

리버싱 핵심원리/03. DLL Injection

03-03. DLL Injection

1. DLL Injection

DLL Injection이란 실행 중인 다른 프로세스에 특정 DLL 파일을 강제로 삽입(Inject)하는 것이다. 실제로는 다른 프로세스에게 LoadLibrary() API를 호출하게 하여 사용자가 원하는 DLL을 로딩하는 것이다.

 

2. DLL Injection 구현 방법

  • 원격 스레드 생성(CreateRemoteThread() API 이용)

  • 레지스트리 이용(AppInit_DLLs 값 이용)

  • 메시지 후킹(SetWindowsHookEx() API 이용)

     

3. CreateRemoteThread()

 

3.1 InjectDll.cpp

#include "windows.h"
#include "tchar.h"

BOOL SetPrivilege(LPCTSTR lpszPrivilege, BOOL bEnablePrivilege) 
{
    TOKEN_PRIVILEGES tp;
    HANDLE hToken;
    LUID luid;

    if( !OpenProcessToken(GetCurrentProcess(),
                          TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, 
                          &hToken) )
    {
        _tprintf(L"OpenProcessToken error: %u\n", GetLastError());
        return FALSE;
    }

    if( !LookupPrivilegeValue(NULL,           // lookup privilege on local system
                              lpszPrivilege,  // privilege to lookup 
                              &luid) )        // receives LUID of privilege
    {
        _tprintf(L"LookupPrivilegeValue error: %u\n", GetLastError() ); 
        return FALSE; 
    }

    tp.PrivilegeCount = 1;
    tp.Privileges[0].Luid = luid;
    if( bEnablePrivilege )
        tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
    else
        tp.Privileges[0].Attributes = 0;

    // Enable the privilege or disable all privileges.
    if( !AdjustTokenPrivileges(hToken, 
                               FALSE, 
                               &tp, 
                               sizeof(TOKEN_PRIVILEGES), 
                               (PTOKEN_PRIVILEGES) NULL, 
                               (PDWORD) NULL) )
    { 
        _tprintf(L"AdjustTokenPrivileges error: %u\n", GetLastError() ); 
        return FALSE; 
    } 

    if( GetLastError() == ERROR_NOT_ALL_ASSIGNED )
    {
        _tprintf(L"The token does not have the specified privilege. \n");
        return FALSE;
    } 

    return TRUE;
}
BOOL InjectDll(DWORD dwPID, LPCTSTR szDllPath)
{
    HANDLE hProcess = NULL, hThread = NULL;
    HMODULE hMod = NULL;
    LPVOID pRemoteBuf = NULL;
    DWORD dwBufSize = (DWORD)(_tcslen(szDllPath) + 1) * sizeof(TCHAR);
    LPTHREAD_START_ROUTINE pThreadProc;

    // #1. dwPID 를 이용하여 대상 프로세스(notepad.exe)의 HANDLE을 구한다.
    if ( !(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)) )
    {
        _tprintf(L"OpenProcess(%d) failed!!! [%d]\n", dwPID, GetLastError());
        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)szDllPath, dwBufSize, NULL);

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

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

    CloseHandle(hThread);
    CloseHandle(hProcess);

    return TRUE;
}

int _tmain(int argc, TCHAR *argv[])
{
    if( argc != 3)
    {
        _tprintf(L"USAGE : %s <pid> <dll_path>\n", argv[0]);
        return 1;
    }

    // change privilege (Windows10 에서 제대로 작동하지 않아 주석 처리 하였다.
/* 
    if( !SetPrivilege(SE_DEBUG_NAME, TRUE) )
        return 1;
*/

    // inject dll
    if( InjectDll((DWORD)_tstol(argv[1]), (LPCTSTR)argv[2]) )
        _tprintf(L"InjectDll("%s") success!!!\n", argv[2]);
    else
        _tprintf(L"InjectDll("%s") failed!!!\n", argv[2]);

    return 0;
}

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

hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID);

OpenProcess() API를 이용하여 notepad.exe 프로세스의 핸들을 구한다.

 

3.1.2. 대상 프로세스의 메모리에 Injection할 Dll 경로 써주기

pRemoteBuf = VirtualAllocEx(hProcess, NULL, dwBufSize, MEM_COMMIT, PAGE_READWRITE); 
// dwBufSize = 경로 문자열 길이(NULL 포함)
// VirtualAllocEX()의 리턴값은 할당된 버퍼의 주소이다.

notepad.exe에 로딩할 DLL 파일의 경로를 알려줘야 한다. 이때 VirtualAllocEx() API를 이용하여 notepad.exe 프로세스의 메모리 공간에 버퍼를 할당해야 한다.

 

WriteProcessMemory(hProcess, pRemoteBuf, (LPVOID)szDllPath, dwBufSize, NULL);
// szDllPath = "C:\\myhack.dll")

앞에서 할당받은 버퍼 주소에 WriteProcessMemory() API를 이용하여 DLL 경로 문자열을 써준다.

 

3.1.3. LoadLibraryW() API 주소 구하기

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

우리는 notepad.exe 프로세서의 로딩된 kernel32.dll의 LoadLibraryW() API의 주소를 알아내야 한다. 왜냐하면 notepad.exe 프로세스가 LoadLibaryW() API를 실행시켜 myhack.dll을 불러내야 하기 때문이다. 하지만 코드를 보면 InjectDll.exe 프로세스에 로딩된 kernel32.dll에서 LoadLibraryW() API의 시작주소를 얻어내고 있다. 언뜻보면 두 주소가 다르니 참조 오류가 발생할 것 같지만 실제로는 문제가 생기지 않는다.

 

실제로는 Windows 운영체제에서 kernel32.dll은 프로세스마다 같은 주소에 로딩된다.

 

이 방법은 제프리 리쳐의 『Windows via C/C++』에 소개된 이후로 Dll 인젝션의 기법으로 널리 이용되고 있다. 따라서 InjectDll.exe 프로세스에 임포트된 LoadLibraryW() 주소와 notepad.exe 프로세스에 임포트된 LoadLibraryW()의 주소는 동일하다.

 

3.1.4. 대상 프로세스에 원격 스레드를 실행

hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, pRemoteBuf, 0, NULL);
// pThreadProc = notepad.exe 프로세스 메모리 내의 LoadLibraryW() 주소
// pRemoteBuf = notepad.exe 프로세스 메모리내의 "C:\\myhack.dll" 문자열 주소

마지막으로 notepad.exe가 LoadLibraryW() API를 호출하도록 하면 된다. 하지만 Windows에서는 그러한 기능을 제공하는 API가 없다. 그래서 편법으로 CreateRemoteThread() API를 이용한다. (이게 Dll Injection의 정석이다.) CreateRemoteThread() 는 다른 프로세스에게 스래드를 실행시키게 하는 함수이다. 잠시 구조를 살펴보면

HANDLE CreateRemoteThread(
  HANDLE                 hProcess,
  LPSECURITY_ATTRIBUTES  lpThreadAttributes,
  SIZE_T                 dwStackSize,
  LPTHREAD_START_ROUTINE lpStartAddress,
  LPVOID                 lpParameter,
  DWORD                  dwCreationFlags,
  LPDWORD                lpThreadId
);

lpStartAddress와 lpParameter는 각각 스레드 함수 주소와 스레드 파라미터 주소이다. 중요한것은 이 주소들이 대상 프로세스(hProcess)의 메모리 내의 공간의 주소이어야 한다는 것이다. (그래야 해당 프로세스에서 값들을 인식할 수 있다.)

그럼 왜 갑자기 이 함수가 나온 것이냐라고 하면 lpStartAddress에 들어가는 스레드 함수의 형태를 보면 힌트를 얻을 수 있다.

DWORD WINAPI ThreadProc(
  _In_ LPVOID lpParameter
);

두 함수 모두 4byte 파라미터를 하나 받고, 4byte 값을 리턴한다. 즉 두 함수의 형태가 동일하다.(다시 말하면 두 함수의 Type이 동일하기 때문에 LoadLibaryW() API가 LPTHREAD_START_ROUTINE Type으로 Type Casting이 가능하다는 이야기이다.)

그래서 LoadLibraryW()를 스레드 함수로 설정하고 CreateRemoteThread() API를 실행시키면 notepad.exe에 새로운 스레드가 생성되고 그 스레드가 LoadLibraryW() API가 된다.

 

3.2. myhack.cpp

#include "windows.h"
#include "tchar.h"
#include <stdio.h>
#pragma comment(lib, "urlmon.lib")

#define DEF_URL         (L"http://www.naver.com/index.html")
#define DEF_FILE_NAME   (L"index.html")

HMODULE g_hMod = NULL;

DWORD WINAPI ThreadProc(LPVOID lParam)
{
    TCHAR szPath[_MAX_PATH] = {0,};

    if( !GetModuleFileName( g_hMod, szPath, MAX_PATH ) )
        return FALSE;

    TCHAR *p = _tcsrchr( szPath, '\\' );
    if( !p )
        return FALSE;

    _tcscpy_s(p+1, _MAX_PATH, DEF_FILE_NAME);

    URLDownloadToFile(NULL, DEF_URL, szPath, 0, NULL);

    return 0;
}

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
    OutputDebugString(L"<myhack.dll");

    HANDLE hThread = NULL;

    g_hMod = (HMODULE)hinstDLL;

    switch( fdwReason )
    {
    case DLL_PROCESS_ATTACH : 
        OutputDebugString(L"<myhack.dll> Injection!!!");
        hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
        CloseHandle(hThread);
        break;
    }

    return TRUE;
}

DllMain()을 보면 DLL이 로딩될 때, 디버그 문자열("myhack.dll Injection!!")을 출력하고 스레드(ThreadProc())을 실행한다.
ThreadProc()의 내용을 보면 urlmonURLDownloadToFile() API를 호출하여 네이버 사이트의 index.html을 다운받는다.

 


모든 코드와 설명의 출처는 https://reversecore.com/ 에 있습니다.

 

'리버싱 핵심원리 > 03. DLL Injection' 카테고리의 다른 글

03-04. DLL Ejection  (1) 2020.08.10
03-01. Windows 메세지 후킹  (0) 2020.08.10