안 쓰던 블로그
윈도우10 DLL 인젝션 구현 (CreateRemoteThread 사용) 본문
DLL 인젝션
DLL 인젝션이란 실행 중인 다른 프로세스에 특정 DLL 파일을 강제로 삽입하는 것이다
좀 더 자세하게 말하면 다른 프로세스에게 LoadLibrary() API를 스스로 호출하도록 명령하여 사용자가 원하는 DLL을 로딩하는 것이라고 할 수 있다
DLL 인젝션이 일반적인 DLL 로딩과 다른 점은, 로딩 대상이 되는 프로세스가 나 자신이냐 아니면 다른 프로세스냐 하는 점이다
예를 들어 공격자의 DLL 파일 hack.dll이 있다고 하면, notepad.exe 프로세스의 메모리 공간 어딘가로 강제 삽입을 하여 DLLMain()을 자동호출 시키는 식이다
(참고로 DLLMain()은 프로세스에 DLL이 로딩되면 자동으로 실행된다. 본래는 버그 수정, 기능 추가 등의 목적으로 사용된다. DLL에 대한 설명 참고 foxtrotin.tistory.com/331)
이처럼 notepad.exe 프로세스에 로딩된 hack.dll은 그 프로세스에 이미 로딩된 다른 DLL들(kernel32.dll, user32.dll 등)과 마찬가지로 notepad.exe 프로세스 메모리에 대한 정당한 접근 권한을 가진다
따라서 공격자가 원하는 어떤 일이라도 수행할 수 있게 된다(예를 들자면 notepad에 통신기능을 추가하여 메신저 기능을 구현한다던가)
DLL 인젝션 기법이 활용될 수 있는 사례는 다음과 같다
-기능 개선 및 버그 패치 (프로그램의 코드가 없거나 수정이 여의치 않을 때)
-메시지 후킹
-API 후킹
-기타 응용 프로그램. 예를 들면 특정 프로그램의 실행 차단, 유해 사이트 접속 차단, PC 사용 모니터링 등
실습
먼저 dll인젝션으로 프로세스에 심을 dll파일을 작성한다
#include <stdio.h>
#include <windows.h>
#pragma comment(lib, "myinjection.lib")
#define DEF_MYBLOG_ADDR ("http://foxtrotin.tistory.com/")
#define DEF_INDEX_PATH ("c:\\index.html")
DWORD WINAPI ThreadProc(LPVOID lParam)
{
URLDownloadToFile(NULL, DEF_MYBLOG_ADDR, DEF_INDEX_PATH, 0, NULL);
return 0;
}
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
HANDLE hThread = NULL;
switch (fdwReason)
{
case DLL_PROCESS_ATTACH:
hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
CloseHandle(hThread);
}
return TRUE;
}
DLL이 로딩될 때(DLL_PROCESS_ATTACH) 스레드(ThreadProc)를 실행한다
ThreadProc()은 myinjection의 URLDownloadToFile() 함수를 실행시켜서 foxtrotin.tistory.com을 index.html이름으로 다운받는다
앞서 프로세스에 DLL 인젝션이 발생하면 해당 DLL의 DllMain()함수가 호출된다고 했다. 따라서 notepad.exe 프로세스에 위의 dll이 인젝션되면 URLDownloadToFile()함수가 실행될 것이다
코드 작성이 완료되었으면 dll 파일을 빌드한다
이제 실제로 dll파일을 notepad.exe 프로세스에 인젝션 시킬 exe 프로그램을 작성한다
#include <stdio.h>
#include <windows.h>
#include <TlHelp32.h>
#include <tchar.h>
int main(int argc, char* argv[])
{
HANDLE hSnapshot, hProcess, hThread;
HMODULE hMod;
DWORD PPid = 0;
LPVOID DllBuf;
LPTHREAD_START_ROUTINE hModAddr;
LPCTSTR DllPath = "injection.dll";
// 프로세스 정보를 담을 구조체
PROCESSENTRY32 pe32 = { 0 };
pe32.dwSize = sizeof(PROCESSENTRY32);
// 프로세스 정보를 얻음
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPALL, 0);
if (Process32First(hSnapshot, &pe32))
{
do
{
//목표 프로세스 명와 같은 프로세스일 경우 PID를 얻음
if (!_tcsicmp(pe32.szExeFile, TEXT("notepad.exe")))
{
printf("[*] Find notepad.exe\n");
PPid = pe32.th32ProcessID;
printf("[*] Pid : %d\n\n", PPid);
break;
}
} while (Process32Next(hSnapshot, &pe32));
CloseHandle(hSnapshot);
}
// 얻은 PID를 이용해 목표 프로세스의 핸들을 구함
if (!(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, PPid)))
printf("OpenProcess Failed\n");
printf("- Handle : %x\n", hProcess);
// 목표 프로세스 메모리에 dll 크기만큼 메모리 할당
if (!(DllBuf = VirtualAllocEx(hProcess, NULL, lstrlen(DllPath), MEM_COMMIT, PAGE_READWRITE)))
printf("VirtualAllocEx Failed\n");
printf("- Virtual Memory : %x\n", DllBuf);
// 할당한 메모리에 hack.dll 경로를 씀
if (!(WriteProcessMemory(hProcess, DllBuf, (LPVOID)DllPath, lstrlen(DllPath) + 1, NULL)))
printf("WriteProcessMemory Failed\n");
// kernel32.dll 핸들 얻어옴
hMod = GetModuleHandle("kernel32.dll");
if(!hMod) printf("GetModuleHandle Failed, error code: %d\n", GetLastError());
printf("- kernel32.dll : %x\n", hMod);
// kernel32.dll의 LoadLibraryA() 주소를 구함
hModAddr = (LPTHREAD_START_ROUTINE)GetProcAddress(hMod, "LoadLibraryA");
if (!hModAddr) printf("GetModuleHandle Failed, error code: %d\n", GetLastError());
printf("- kernel32.LoadLibraryA : %x\n", hModAddr);
// 목표 프로세스에 스레드를 실행시킴
if (!(hThread = CreateRemoteThread(hProcess, NULL, 0, hModAddr, DllBuf, 0, NULL)))
printf("CreateRemoteThread Failed, error code: %d\n", GetLastError());
WaitForSingleObject(hThread, INFINITE);
printf("- Thread Handle : %x\n\n", hThread);
CloseHandle(hThread);
CloseHandle(hProcess);
system("pause");
return TRUE;
}
1. 대상 프로세스의 핸들 구하기
OpenProcess() API를 이용하여 notepad.exe의 프로세스 핸들을 구한다
이제 이 프로세스 핸들로 해당 프로세스를 제어할 수 있다
2. DLL 크기만큼 대상 프로세스에 메모리를 할당
대상 프로세스에게 로딩할 DLL 파일의 경로(문자열)를 알려주어야 한다
하지만 아무 메모리 공간에 쓸 수 없으니까 notepad.exe 메모리 공간에 버퍼를 할당한다
버퍼 크기는 NULL을 포함한 DLL 경로 문자열의 길이이다
3. 할당된 메모리에 인젝션 시킬 DLL 경로 쓰기
할당 받은 버퍼 주소에 WriteProcessMemory API를 이용하여 DLL 파일의 경로 문자열을 쓴다
이로써 대상 프로세스의 메모리 공간에 인젝션 시킬 DLL 파일의 경로가 생겼다
4. LoadLibraryA() API 주소 구하기
kernel32.dll의 LoadLibrary() API를 호출하기 위해 주소가 필요하다
다만 목표 프로세스인 notepad.exe에서 주소를 구하지 않고 현재 프로세스인 exe파일 에서 구한다
왜냐하면 윈도우 운영체제에서 kernel32.dll이 프로세스마다 같은 주소에 로딩되기 때문이다(고유 주소)
그러므로 kernel32.dll은 따로 로딩하지 않아도 로더가 강제로 로드 시키며, 무조건 로드되어 있다
즉, exe에 로딩된 kernel32.dll의 주소를 얻어와서 사용하여도 notepad.exe에 로딩된 것의 주소와 같기 때문에, 현재 프로세스의 kernel32.dll의 주소를 가져온다
그런 후에 LoadLibraryA()의 주소를 구한다
5. CreateRemoteThread()를 사용해 LoadLibrary("injection.dll")을 notepad.exe에서 실행
다른 프로세스에게 스레드를 실행시켜주는 함수인 CreateRemoteThread()로 LoadLibraryA() API를 호출하도록 명령한다
문제
이제 notepad를 실행 후 exe파일을 실행하면 홈페이지를 다운로드 받아야 정상인데.. 아무런 반응을 하지 않았다
windbg로 notepad.exe를 보니까 ntdll!KiRaiseUserExceptionDispatcher 에러가 나고 있었다
프로세스 익스플로러로 확인해 봐도 아예 인젝션이 되지 않았다
참고) 아래 코드는 위에서 사용한 코드랑 동작은 같은데 리버싱 핵심원리 책에서 나온 코드에 주석을 단 것이다. 안 쓰게 되었지만 아까워서..
#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))) //OpenProcess()API를 이용하여 PROCESS_ALL_ACCESS 권한의 notepad.exe프로세스 핸들을 구한다. 이때 프로그램 실행 파라미터로 넘어온 dwPID값을 사용한다
{
_tprintf(L"OpenProcess(%d) failed!!! [%d]\n", dwPID, GetLastError());
return FALSE;
}
//이 과정이 끝나면 PROCESS_ALL_ACCESS 권한을 얻게 되며, 프로세스 핸들 hProcess를 이용하여 대상 프로세스를 제어할 수 있다
// #2. 대상 프로세스(notepad.exe) 메모리에 szDllName 크기만큼 메모리를 할당한다.
pRemoteBuf = VirtualAllocEx(hProcess, NULL, dwBufSize, MEM_COMMIT, PAGE_READWRITE);
//대상 프로세스에게 로딩할 DLL 파일의 경로 문자열을 알려줘야 한다. 아무 메모리 공간에나 쓸 수 없으므로 VirtualAllocEX() API를 이용하여 대상 프로세스 메모리 공간에 버퍼를 할당한다
//버퍼 크기는 DLL 파일 경로 문자열 길이(Termination NULL을 포함)로 지정한다
//pRemoteBuf에는 할당된 버퍼 주소가 들어가게 된다. 이 주소는 내 프로세스인 Inject.exe의 메모리 주소가 아니라 hProcess가 가리키는 상대방 프로세스의 메모리 주소이다
// #3. 할당 받은 메모리에 myhack.dll 경로("c:\\Myhack.dll")를 쓴다.
WriteProcessMemory(hProcess, pRemoteBuf, (LPVOID)szDllPath, dwBufSize, NULL);
//앞에서 할당받은 버퍼 주소에 WriteProcessMemory() API를 이용하여 DLL 경로 문자열 work\\dummy.dll 을 써 준다. 이 역시 상대방 프로세스의 메모리 공간에 쓰는 것이다
//여기까지 하면 상대방 프로세스 메모리 공간에 인젝션할 DLL 파일의 경로를 써 준 것이다
// #4. LoadLibraryW() API 주소를 구한다.
hMod = GetModuleHandle(L"kernel32.dll");
pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hMod, "LoadLibraryW"); //notepad.exe프로세스에 로딩된 kernel32.dll의 LoadLibraryW() API의 시작 주소를 알아낸다
//LoadLibrary() API를 호출하기 위해 그 주소가 필요하다. 뒤에 W는 유니코드 문자열이라는 의미이다
//notepad.exe에 로딩된 kernel32.dll의 주소와 injectDLL.exe 프로세스에 로딩된 kernel32.dll주소가 같다면 성공, 아니면 메모리 참조 오류이다
//실제로 윈도우에서는 kernel32.dll은 프로세스마다 같은 주소에 로딩되지만 OS에 따라 아닐 수도 있다(ASLR 기능이 있다면 DLL로딩 주소가 랜덤이다)
// #5. notepad.exe 프로세스에 스레드를 실행
hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, pRemoteBuf, 0, NULL); //createRemoteThread() API는 다른 프로세스에게 스레드를 실행시켜주는 함수이다
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;
}
참고
ch4njun.tistory.com/144?category=711661
'CTF > Reversing' 카테고리의 다른 글
[리버싱] DLL과 32bit IAT 로딩 과정 (0) | 2020.10.08 |
---|---|
32bit EAT 로딩 과정 (0) | 2020.10.05 |
32bit IAT 로딩 과정 2 (0) | 2020.10.05 |
UPX 패킹된 프로그램 분석 및 툴없이 언패킹하기 (0) | 2020.10.01 |
VA, RVA, RAW 개념 (1) | 2020.09.29 |