리버싱 핵심원리

[리버싱 핵심원리] 24장 - DLL 이젝션

ghkdlxld 2023. 5. 11. 20:44

24.1 DLL 이젝션 동작원리

DLL 인젝션과 DLL 이젝션은 서로 반대되는 개념이다.

DLL 인젝션은 대상 프로세스로 하여금 LoadLibrary() API를 호출하도록 만드는 것이었다면, DLL 이젝션은 대상 프로세스로 하여금 FreeLibrary() API를 호출하도록 만드는 것이다.

즉, CreateRemoteThread()의 IpStartAddress 파라미터에 FreeLibrary() API 주소를 넘겨주고, IpParameter 파라미터에 이젝션할 DLL의 HANDLE을 넘겨주는 것이다.

 

24.2 DLL 이젝션 구현

(EjectDll 소스코드에서 핵심 함수인 EjectDll의 코드)

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

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

위 코드가 대상 프로세스가 스스로 FreeLibrary() API를 호출하도록 만드는 역할을 수행한다.

 

1. 프로세스에 로딩된 DLL 정보 구하기

CreateToolhelp32Snapshot() API를 이용해 프로세스에 로딩된 모듈(DLL)의 정보를 얻을 수 있다. 이 정보를 Module32First() / Module32Next() 함수에 넘겨 MODULEENTRY32 구조체에 해당 모듈의 정보가 세팅한다. 

typedef struct tagMODULEENTRY32W {
  DWORD   dwSize;
  DWORD   th32ModuleID;
  DWORD   th32ProcessID;
  DWORD   GlblcntUsage;
  DWORD   ProccntUsage;
  BYTE    *modBaseAddr;
  DWORD   modBaseSize;
  HMODULE hModule;
  WCHAR   szModule[MAX_MODULE_NAME32 + 1];
  WCHAR   szExePath[MAX_PATH];
} MODULEENTRY32W;

szModule = DLL 이름 (for 루프에서 이 값과 이젝션을 원하는 DLL 파일 이름을 비교해 정확한 모듈 정보를 찾을 수 있다.)

modBaseAddr = 해당 DLL이 로딩된 주소(프로세스 가상 메모리)

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

hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID);

프로세스 ID(dwPID)를 이용해 대상 프로세스의 프로세스 핸들을 구한다.

3. FreeLibrary() API 주소 구하기

hModule = GetModuleHandle(L"kernel32.dll");
pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hModule, "FreeLibrary");

23장에서 LoadLibrary() API를 호출하기 위해서 해당 API의 주소를 구한 것처럼 FreeLibrary의 주소를 구한다. 해당 API 또한 kerenl32.dll의 API이기 때문에 notepad.exe 프로세스에 로딩된 주소를 굳이 찾지 않아도 EjectDll.exe 프로세스에 로딩된 kernel32.dll의 주소를 사용해도 된다. (FreeLibrary 주소는 모든 프로세스에 대해 동일하다!)

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

 hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc //pThreadProc = 스레드 함수 = FreeLibrary()
								, me.modBaseAddr,0, NULL);

해당 과정도 23장과 동일하게 CreateRemoteThread() API를 이용한다. 23장과 다른 점은 pThreadProc() 주소가 LoadLibrary()의 주소가 아닌 FreeLibrary() 주소라는 것이다.

24.3 DLL 이젝션 실습

해당 실습은 로딩된 DLL 파일을 제거하는 것이기 때문에 먼저 DLL 인젝션을 시켜줘야 한다. 23장의 실습과 동일하게 인젝션을 진행하면 된다.

성공적으로 인젝션이 되었다.

이후 인젝션된 dll 파일을 이젝션하는 실습을 진행한다. cmd 창을 켜서 다음과 같이 입력하면 된다. (PID는 기존 인젝션에서 사용한 값과 동일하다... Notepad를 재시작한 게 아니라면....)

위 사진에서 myhack.dll이 인젝션된 프로세스를 찾을 수 없는 것으로 보아 이젝션이 성공적으로 실행된 것을 알 수 있다.

전체적으로 DLL 이젝션은 DLL 인젝션과 비슷하지만, 로딩된 DLL 파일을 없앤다는 점에서 차이가 있다.

 

 

출처 : 리버싱 핵심원리 (이승원, 인사이트)