DLL(Dynamic Link Library)파일

Library는 함수, 데이터, 타입들을 여러가지 프로그래밍 요소들의 집합이다. 자주사용하는 함수를 미리 만들어 모아놓은게 Library라고 한다.

Library는 정적링크와 동적 링크 방식이 있는데 동적링크 Dynamic Link방식을 사용한게 DLL이다.

 

정적링크 방식을 사용하면 컴파일할때 라이브러리 코드를 실행파일에 복사하기 때문에 한번 컴파일 하게 되면 라이브러리파일 없이도 실행 가능한다.

 

동적링크 방식을 사용하면 실행파일 실행시 라이브러리에 실행파일이 연결된다. 실행파일에는 라이브러리에있는 함수의 정보만 포함되고 실제함수의 코드는 복사되지 않기 떄문에 실행파일이 작아진다. 또 필요할때 가져다 사용하기 떄문에 메모리 사용에 효율적이다.

 

▶ DLL injection

다른 프로세스에 특정 DLL파일을 강제 삽입하는것

DLL injection의 원리는 외부에서 다른 프로세스가 LoadLibrary() API를 호출하도록 하는 것이기 떄문에 일반적이 DLL loading과 마찬가지로 강제 삽입된 DLL의 DllMain()함수가 실행된다.

 

예를들면 notepad는 myhack.dll인 라이브러리를 호출하지 않지만 강제로 삽입할 수 있다. notepad.exe에 로딩된 myhack.dll은 notepad.exe프로세스에 이미 로딩된 DLL들과 마찬가지로 notepad.exe 프로세스 메로리에 대한 접근 권한을 갖는다. notepad에 사용자가 원한다면 통신기능을 추가하여 메신저로서의 기능을 하도록 할 수 있는 것이다.

 

▶ DLL injection사용하는 목적

프로세스에 삽입된 DLL파일은 프로세스 메모리에 대한 정당한 접근권한을 가지기 떄문에 사용자가 원하는 모든일을 수행할 수 있다. 이런점을 이용해 버그패치, 기능추가 등 다양한 곳에서 이용할 수 있지만 대부분 악의적인 용도인 악성코드로 사용된다.

 

// Myhack.cpp

#include "windows.h"
#include "tchar.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)
{
    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_PROCESS_ATTACH에서 디버그 문자열이 출력되고 CreateThread를 통해 ThreadProc이 실행된다.

ThreadProc내부에서는 URLDownloadToFile() API를 통해 index.html을 다운로드 한다. 해당 dll이 notepad.exe에 인젝션 되면 URLDownloadToFile() API가 호출되는 구조이다.

 

// 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 크기만큼 메모리를 할당한다.
    // DLL경로를 프로세스에 기록: 대상 프로세스 메로리에 인젝션할 DLL경로 적어주기
    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 주소를 구한다.
    // DLL로드 작업: LoadLibraryW() 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);
    WaitForSingleObject(hThread, INFINITE);	

    CloseHandle(hThread);
    CloseHandle(hProcess);

    return TRUE;
}

//tmain함수의 마지막 조건문에서 EjectDLL함수의 결과에 따라 success와 failed가 출력된다.
int _tmain(int argc, TCHAR *argv[])
{
    if( argc != 3)
    {
        _tprintf(L"USAGE : %s <pid> <dll_path>\n", argv[0]);
        return 1;
    }

    // change privilege
    if( !SetPrivilege(SE_DEBUG_NAME, TRUE) )
        return 1;

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

    return 0;
}

main()부분의 주요기능은 프로그램의 파라미터들에 대해 체크하고 injectDll()함수를 호출하는 것이다. InjectDll()함수가 DLL인젝션을 하는 주요 함수이다.

 


DLL Ejection

DLL인젝션과 반대 개념으로 프로세스에 강제로 삽입한 DLL을 뺴내는 기술이다. 동작 원리는 CreateRemoteThread API를 이용한 DLL인젝션 동작 원리와 같다.

 

대상 프로세스가 LoadLibrary() API를 호출하도록 하는 것이 DLL인젝션의 포인트라면 이젝션에서는 FreeLibrary()API를 호출한다.

 

즉, CreateRemoteThread()의 lpStartAddress 파라미터에 FreeLibrary() API 주소를 넘겨주고, lpParameter 파라미터에 이젝션 할 DLL의 핸들을 넘겨주면 된다.

또한, Windows에는 Reference Count 라는 것이 있는데, LoadLibrary("a.dll")을 10번 호출하면 a.dll에 대한 참조 카운트도 10이 되어서 나중에 a.dll을 해제할 때 FreeLibrary()를 10번 호출 해야 한다.

 

// EjectDll.exe

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

#define DEF_PROC_NAME	(L"notepad.exe")
#define DEF_DLL_NAME	(L"myhack.dll")

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(!_tcsicmp(szProcessName, (LPCTSTR)pe.szExeFile))
        {
            dwPID = pe.th32ProcessID;
            break;
        }
    }
    while(Process32Next(hSnapShot, &pe));

    CloseHandle(hSnapShot);

    return dwPID;
}

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;
}

//EjecDll()함수는 FreeLibrary()API를 호출하도록 만드는 역할을 한다.
BOOL EjectDll(DWORD dwPID, LPCTSTR szDllName)
{
    BOOL bMore = FALSE, bFound = FALSE;
    HANDLE hSnapshot, hProcess, hThread;
    HMODULE hModule = NULL;
    MODULEENTRY32 me = { sizeof(me) };
    LPTHREAD_START_ROUTINE pThreadProc;

    // dwPID = notepad 프로세스 ID
    // TH32CS_SNAPMODULE 파라미터를 이용해서 notepad 프로세스에 로딩된 DLL 이름을 얻음
    hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwPID);
    //CreateToolhelp32snapshot()API를 이용하면 프로세스에 로딩된 DLL정보를 얻을수 있다. 
    //이렇게 구한 hSnapshot핸들을 Module32First()/Module32Next함수에 넘기면 MODULEENTRY32구조체에 해당 모듈의 정보가 세팅된다.

    bMore = Module32First(hSnapshot, &me);
    for( ; bMore ; bMore = Module32Next(hSnapshot, &me) )
    {
        if( !_tcsicmp((LPCTSTR)me.szModule, szDllName) || 
            !_tcsicmp((LPCTSTR)me.szExePath, szDllName) )
        {
            bFound = TRUE;
            break;
        }
    }

    if( !bFound )
    {
        CloseHandle(hSnapshot);
        return FALSE;
    }

    if ( !(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)) )
    {
        _tprintf(L"OpenProcess(%d) failed!!! [%d]\n", dwPID, GetLastError());
        return FALSE;
    }

    hModule = GetModuleHandle(L"kernel32.dll");
    pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hModule, "FreeLibrary");
    hThread = CreateRemoteThread(hProcess, NULL, 0, 
                                 pThreadProc, me.modBaseAddr, 
                                 0, NULL);
    WaitForSingleObject(hThread, INFINITE);	

    CloseHandle(hThread);
    CloseHandle(hProcess);
    CloseHandle(hSnapshot);

    return TRUE;
}

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

    _tprintf(L"PID of \"%s\" is %d\n", DEF_PROC_NAME, dwPID);

    // change privilege
    if( !SetPrivilege(SE_DEBUG_NAME, TRUE) )
        return 1;

    // eject dll
    if( EjectDll(dwPID, DEF_DLL_NAME) )
        _tprintf(L"EjectDll(%d, \"%s\") success!!!\n", dwPID, DEF_DLL_NAME);
    else
        _tprintf(L"EjectDll(%d, \"%s\") failed!!!\n", dwPID, DEF_DLL_NAME);

    return 0;
}

 

 

 

참고

https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=ilikebigmac&logNo=221470285183

https://hackersstudy.tistory.com/75

https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=ilikebigmac&logNo=221470036755

https://kyumoonhan.tistory.com/125

https://maple19out.tistory.com/37

'보안 > 리버싱' 카테고리의 다른 글

LAB 6-4 실습  (0) 2021.11.20
LAB 6-3 실습  (0) 2021.11.20
[리버싱] 드림핵 rev-basic-8 풀이  (0) 2021.11.13
[리버싱] 드림핵 rev-basic-7 풀이  (0) 2021.11.13
IAT, EAT 로딩 과정  (0) 2021.10.02

+ Recent posts