ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [리버싱 핵심원리 study] 23장 DLL 인젝션
    Reverse Engineering 2021. 1. 6. 03:29

    0. 들어가기 전

    이번 실습은 진행하는 데 있어서 아래와 같은 오류들로 인해 발목을 잡혔다.

    오류 1

    구글링을 해본 결과 간단한 원인은 권한이 없어 관리자 권한으로 cmd를 실행해줘야만 정상적으로 DLL 인젝션 명령이 실행되는 것이었다. 관련 내용을 아래 블로그에서 잘 다루는 것 같아 참고해보면 좋을 것 같다.

     

     

    SetPrivilege Function & Access Token Privilege

    # Introduce SetPrivilege 함수는 주로 Injector에서 많이 사용되고 Debugger와 Cheat Engine등의 Debugging을 수행하는 프로그램에 쓰일 것 같은 함수입니다. 해당 프로세스의 사용자 엑세스 토큰의(Access Token) 특

    holi4m.github.io

     

    또한 DLL 인젝션을 하는 방법 중 책에서 다루는 두 번째 방법인 레지스트리를 이용한 방법은 windows 10 64bit 환경에서는 잘 작동하지 않아 virtualbox로 windows 7 32bit 환경에서 진행하였다. 위와 같은 내용을 바탕으로 이번 공부에 대한 내용을 정리해보겠다.

     

    1. DLL 인젝션

    DLL 인젝션은 현재 실행 중인 프로세스에 특정 DLL 파일을 강제로 삽입하는 것이다. 구체적으로는 해당 프로세스가 LoadLibrary() API를 호출하여 특정 DLL을 로딩하게 만든다. 이번 실습에서는 notepad.exe에 myhack.dll이라는 DLL을 강제로 삽입하여 사용자가 원하는 동작을 하도록 수행한다. LoadLibrary() API를 이용해 특정 DLL을 로딩하면 해당 DLL의 DLLMain() 함수가 실행되고, 해당 DLL은 프로세스의 메모리에 대한 접근 권한을 갖기 때문에 사용자가 원하는 다양한 일을 수행할 수 있다.

     

    이번 실습에서는

    (1) 원격 스레드 생성(CreateRemoteThread() API 사용)

    (2) 레지스트리 이용(AppInit_DLLs 값 수정)

    (3) 메시지 후킹(SetWindowsHookEx() API 사용)

    세 가지 방법을 이용하여 DLL 인젝션을 구현한다.

     

    2. CreateRemoteThread()

    실습 예제인 myhack.dll과 InjectDll.exe를 적당한 작업 폴더에 위치시킨 후, notepad.exe(32-bit)를 실행한다. 인젝션된 myhack.dll은 인터넷에 접속하여 http://www.naver.com/index.html파일을 다운로드하도록 프로그래밍되어 있다. 우선 Process Explorer를 통해 notepad.exe의 PID를 알아내자.

    Process Explorer

    이후에 DebugView 프로그램을 실행시켜놓자. DebugView는 프로세스들이 출력하는 디버깅 문자열을 보여주는 유틸리티로, 인젝션에 성공하면 해당 내용이 출력될 것이다.

    Debugview

    이제 cmd로 InjectDll.exe가 있는 폴더에서 다음과 같은 명령어를 입력해보자. (단, 관리자 권한으로 실행되어야 함)

    dll injection
    DLL injection 성공!

    위와 같이 13052 프로세스에 myhack.dll이 인젝션 되었다는 문구를 확인할 수 있다. Process Explorer에도 아래와 같이 notepad.exe에 myhack.dll이 인젝션 된 것을 확인할 수 있다.

    process explorer

    그렇다면 myhack.dll에서 정말 index.html 파일을 다운로드 하도록 했는지 폴더로 가서 확인해보자.

    index.html1
    index.html2

    위와 같이 정상적으로 파일을 다운로드 받은 것을 확인할 수 있다.

     

    - 소스코드 분석

    /*** myhack.cpp ***/
    #include "Windows.h"
    #include "tchar.h"
    
    #pragma comment(lib, "urlmon.lib")
    
    #define DEF_URL (L"https://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];
    	memset(szPath, 0, _MAX_PATH);
    
    	if (!GetModuleFileName(g_hMod, szPath, _MAX_PATH))
    		return FALSE;
    	TCHAR* p = _tcschr(szPath, '\\');
    	if (!p)
    		return FALSE;
    
    	_tcscpy_s(p + 1, lstrlen(DEF_FILE_NAME), 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 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) 메모리에 szDllPath 크기만큼 메모리를 할당한다.
    	pRemoteBuf = VirtualAllocEx(hProcess, NULL, dwBufSize, MEM_COMMIT, PAGE_READWRITE);
    
    	//(*3)할당 받은 메모리에 myhack.dll 경로를 쓴다.
    	WriteProcessMemory(hProcess, pRemoteBuf, (LPVOID)szDllPath, dwBufSize, NULL);
    
    	//(*4)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;
    }
    
    int _tmain(int argc, TCHAR* argv[]) {
    	if (argc != 3) {
    		_tprintf(L"USAGE : %s pid dll_path\n", argv[0]);
    		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() 함수를 호출하여 대상 프로세스(notepad.exe)로 하여금 스스로 LoadLibrary("myhack.dll") API를 호출하도록 한다. InjectDll() 함수를 구체적으로 살펴보자.

     

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

     

    (*2) notepad.exe의 메모리에 인젝션할 DLL 경로를 적어준다. 이때 아무 곳에나 적을 수는 없으므로, VirtualAllocEx() API를 이용하여 메모리 공간에 버퍼를 할당한다.

     

    (*3) WriteProcessMemory() API를 통해 DLL 경로 문자열을 (*2)에서 구한 메모리 공간에 적어준다. 이때 메모리 공간은 상대 프로세스(notepad.exe)의 공간이다.

     

    (*4) LoadLibraryW() API의 주소를 구하기 위해 kernel32.dll 모듈로부터 GetProcAddress()를 이용해 주소를 구한다. 여기서 kernel32.dll은 사실 InjectDll.exe 프로세스에 로딩된 라이브러리이지만, Windows 운영체제에서 kernel32.dll이 프로세스마다 같은 주소에 로딩된다는 특징을 이용하여 결국 InjectDll.exe의 kernel32.dll == notepad.exe의 kernel32.dll이 되는 것이다. 

     

    (*5) 대상 프로세스에 원격 스레드(Remote Thread)를 실행한다. 이 때 pThreadProc=>LoadLibraryW(), pRemoteBuf=>"......\myhack.dll" 이므로 LoadLibraryW("myhack.dll")이 실행된다. 

     

    책의 내용과 별개로 이번 실습이 흥미로워 보다 다양한 환경에서 해당 소스를 컴파일 해보고 실행해보았다. Windows 10 64bit 환경 기준 다음과 같은 결과를 보였다.

    (target / program) 32bit (InjectDll.exe / myhack.dll) 64bit (InjectDll.exe / myhack.dll)
    32bit notepad injection 성공 injection 실패
    64bit notepad injection 실패 injection 성공

    32bit / 64bit에 따라 import 하는 dll 종류가 다른가...? 정확한 원인은 이후 공부해보면서 분석해보겠다.

     

    3. AppInit_DLLs

    이 방법은 레지스트리(Registry)를 이용한 방법이다. (Windows 10 64bit 환경에서는 적용이 안되어 Windows 7 32bit 환경에서 진행하였다.) Windows 운영체제에서 기본으로 제공하는 AppInit_DLLs와 LoadAppInit_DLLs라는 이름의 레지스트리 항목이 있다.

     

    레지스트리 편집기

    AppInits_DLLs 항목에 인젝션을 원하는 DLL 경로 문자를 쓰고, LoadAppInit_DLLs 항목의 값을 1로 변경한 후 재부팅하면, 실행되는 모든 프로세스에 해당 DLL이 인젝션 된다. 위 원리는 User32.dll이 프로세스에 로딩될 때 AppInit_DLLs 항목을 읽어서 값이 존재하면 LoadLibrary() API를 이용하여 사용자 DLL을 로딩하기 때문이다. 예제 파일인 myhack2.dll의 경로를 이용하여 값을 편집하고 재부팅을 한 후, Process Explorer로 확인해보자.

     

    myhack2.dll

    위와 같이 myhack2.dll이 프로세스에 인젝션된 것을 확인할 수 있다. 또한 notepad.exe를 실행하면 아래와 같이 IE가 숨김 속성으로 작동하는 것을 확인할 수 있다.

    myhack2.dll

    반응형

    댓글

Designed by Tistory.