-
23 DLL 인젝션리버싱 엔지니어링/리버싱 핵심 원리 2022. 4. 5. 01:45
DLL 인젝션
: 실행 중인 다른 프로세스에 특정 DLL 파일을 강제로 삽입하는 것
- 다른 프로세스에게 LoadLibrary() API를 스스로 호출하도록 명령해 사용자가 원하는 DLL을 로딩하는 것
- 일반 DLL 로딩과 다른 점은 로딩 대상이 되는 프로세스가 내 자신이냐 아니면 다른 프로세스냐 하는 것
- notepad 프로세스에 myhack.dll을 강제 삽입
- kernel32.dll 등과 같이 notepad.exe 프로세스 메모리에 대한 정당한 접근 권한을 가짐
- 사용자가 원하는 일 수행 가능
DLL 인젝션 활용 예
기능 개선 및 버그 패치
- 소스코드가 없거나 수정이 어려울 때 DLL 인젝션을 사용해 새로운 기능을 추가(PlugnIn 개념)하거나, 문제가 있는 코드와 데이터를 수정 가능
메시지 후킹
- 메시지 후킹(Message Hooking) 기능도 일종의 DLL 인젝션 기법
- 등록된 후킹 DLL을 OS에서 직접 인젝션시켜준다는 차이점 존재
API 후킹
- 후킹 함수를 DLL 형태로 만든 후 후킹을 원하는 프로세스에 간단히 인젝션하는 것만으로 API 후킹이 완성
- 삽입된 DLL은 해당 프로세스의 메모리에 대한 접근 권한을 갖고 있다는 특성을 활용
기타 응용 프로그램
- PC 사용자들을 감시, 관리하기 위한 애플리케이션에서 사용되기도 함
- 특정 프로그램의 실행 차단, 유해사이트 접속 차단, PC 사용 모니터링 프로그램 등
악성 코드
- DLL 인젝션 기법을 악성 코드에 적극적 활용
- 정상적인 프로세스에 몰래 숨어들어가 백도어 포트(Backdoor port)를 열어서 외부에서 접속을 시도하거나, 키로깅 기능으로 사용자의 개인 정보를 훔침
DLL 인젝션 구현 방법
DLL 인젝션 방법
- 원격 스레드 생성(CreateRemoteThread() API)
- 레지스트리 이용(AppInit_DLLs 값)
- 메시지 후킹(SetWindowsHookEx() API)
CreateRemoteThread()
실습 예제 myhack.dll
: notepad.exe 프로세스에 myhack.dll을 인젝션
실습 파일 복사
- 실습 파일(InjectDll.exe, myhack.dll)을 각자 작업 폴더에 복사
notepad.exe 프로그램 실행
- notepad.exe(메모장)을 실행한 후 Process Explorer를 실행해 프로세스의 PID를 알아냄
- notepad.exe 프로세스의 PID = 9416
Debug View 실행
- 시스템에서 실행되는 프로세스들이 출력하는 모든 디버그 문자열(Debug String)을 가로채 보여줌
- 다운 : https://docs.microsoft.com/en-us/sysinternals/downloads/debugview
myhack.dll 인젝션
- InjectDll.exe 프로그램은 DLL 인젝션 유틸리티
- InjectDll.exe 실행
- cmd 권리자 권한으로 실행
- 명령어 : InjectDll.exe [notepad.exe PID] [Inject할 DLL]
DLL 인젝션 확인
- DebugView 로그
- Process Explorer
- View - Show Lower Pane
- View - Lower Pane Views - DLLs
결과 확인
- index.html이 work 파일에 다운로드됨
- index.html 실행
예제 소스코드 분석
Myhack.cpp
: myhack.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; }
- DLL Main() : DLL이 로딩(DLL_PROCESS_ATTACH)될 때 디버그 문자열("<myhack.dll> Injection!!!")을 출력하고 스레드(ThreadProc)를 실행
- ThreadProc() : urlmon!URLDownloadToFile() API를 호출해 네이버 사이트의 index.html 파일을 다운받음
- notepad.exe 프로세스에 myhack.dll이 인젝션되면 결국 URLDownloadToFile() API가 호출
- DLL 인젝션이 발생하면 해당 DLLMain() 함수가 호출되기 때문
InjectDll.cpp
: myhack.dll 소스코드(myhack.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); 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 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() : 대상 프로세스(notepad.exe)로 하여금 스스로 LoadLibrary("myhack.dll") API를 호출하도록 명령하는 기능
- 대상 프로세스 핸들 구하기
- hProcess = OpenProcesss(PROCESS_ALL_ACCESS, FALSE, dwPID)
- OpenProcess() API를 이용해 PROCESS_ALL_ACCESS 권한의 notepad.exe 프로세스 핸들을 구함(dwPID 이용)
- 프로세스 핸들(hProcess)을 이용해 프로세스 제어 가능
- 대상 프로세스 메모리에 인젝션할 DLL 경로 써주기
- pRemoteBuf = VirtualAllocEx(hProcess, NULL, dwBufSize, MEM_COMMIT, PAGE_READWRITE);
- 프로세스에 로딩한 DLL 파일의 경로를 알려줘야함
- VirtyalAllocEX() API를 이용해 상대 프로세스 메모리 공간에 버퍼 할당
- 버퍼 크기는 DLL 파일 경로 문자열 길이로 지정(\0 포함)
- WriteProcessMemory(hProcess, pRemoteBuf, (LPVOID)szDllPath, dwBufSize, NULL);
- 할당받은 버퍼주소(pRemoteBuf)에 WriteProcessMemory() API를 이용해 DLL 경로 문자열을 써줌
- 상대방 프로세스의 메모리 공간에 쓰는 것
- LoadLibraryW() API 주소 구하기
- hMod = GetModuleHandle(L"kernel32.dll"); pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hMod, "LoadLibraryW");
- 위 코드는 InjectDll.exe에 로딩된 kernel32.dll의 LoadLibrary() API 주소
- notepad.exe에 로딩된 kernel32.dll의 LoadLibrary() API 시작 주소가 필요
- Windows 운영체제에서 kernel32.dll은 프로세스마다 같은 주소에 로딩됨
- 대상 프로세스에 원격 스레드(Remote Thread)를 실행함
- hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, pRemoteBuf, 0, NULL);
- pThreadProc : notepad.exe 프로세스 메모리 내의 LoadLibraryW() 주소
- pRemoteBuf : notepad.exe 프로세스 메모리내의 DLL 파일 경로 문자열 주소
- CreateRemoteThread() API : 다른 프로세스에게 스레드를 실행시켜주는 함수
- LoadLibraryW() 호출하도록 만드는 역할을 하도록 수정
- IpStartAddress (스레드 함수 주소): 'LoadLibrary() 주소'
- IpParameter(스레드 파라미터 주소) : 인젝션을 원하는 'DLL의 경로 문자열 주소'
- hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, pRemoteBuf, 0, NULL);
- 대상 프로세스 핸들 구하기
디버깅 방법
: DLL 파일이 인젝션되는 순간부터 디버깅할 수 있는 방법
notepad.exe 실행 후 OllyDbg를 통해 Attach
- Attach하면 일시적으로 멈춤 → 실행[F9]키
옵션 다이얼 로그의 Events 메뉴의 "Pause on new module(DLL)" 항목 체크
- 새로운 DLL이 로딩될 때마다 해당 DLL의 EP에서 멈춤
- DLL 인젝션이 될 때도 마찬가지로 해당 DLL의 EP에서 멈춤
myhack.dll 파일 notepad.exe 프로세스에 인젝션
- myhack.dll이 임포트하는 dll이 로딩될 때도 멈춤
- 따라서 myhack.dll의 EP코드가 나타날 때까지 실행[F9]
- myhack.dll 모듈의 EP 도착, 디버깅 진행하면 됨(Pause on new module(DLL) uncheck로 수정)
AppInit_DLLS
: 레지스트리(Registry) 이용
- Windows 운영체제에서 기본으로 제공하는 AppInit_DLLs, LoadAppInit_DLLs 레지스트리 항목 존재
- AppInit_DLLS 항목에 인젝션을 원하는 DLL 경로 문자열을 쓰고 LoaAppInit_DLLs 항목의 값을 1로 변경 후 재부팅하면, 실행되는 모든 프로세스에 해당 DLL을 인젝션
예제 소스코드 분석
Myhack2.cpp
// myhack2.cpp #include "windows.h" #include "tchar.h" #define DEF_CMD L"c:\\Program Files\\Internet Explorer\\iexplore.exe" #define DEF_ADDR L"http://www.naver.com" #define DEF_DST_PROC L"notepad.exe" BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) { TCHAR szCmd[MAX_PATH] = {0,}; TCHAR szPath[MAX_PATH] = {0,}; TCHAR *p = 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( !(p = _tcsrchr(szPath, '\\')) ) break; if( _tcsicmp(p+1, DEF_DST_PROC) ) break; wsprintf(szCmd, L"%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.exe'와 같다면 IE를 숨김 모드로 실행시켜 Naver 사이트에 접속
실습 예제
레지스트리 값 입력
regedit.exe 실행후 아래 경로로 이동
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows
AppInit_DLLs 값을 dll 경로로 편집
LoadAppInit_DLLs 항목 값을 1로 변경
SetWindowsHookEx()
: 메시지 후킹
- SetWindowsHookEx() API를 이용해 메시지 훅을 설치하면 OS에서 hook procedure를 담고있는 DLL을 프로세스에 강제로 인젝션
'리버싱 엔지니어링 > 리버싱 핵심 원리' 카테고리의 다른 글
24 DLL 이젝션 (0) 2022.04.05 22 악의적인 목적으로 사용되는 키로거 (0) 2022.04.01 21 Windows 메시지 후킹 (0) 2022.04.01 20 인라인 패치 실습 (0) 2022.03.30 19 UPack 디버깅 - OEP 찾기 (0) 2022.03.30