지뢰 찾기.exe 다운로드 : https://down10.software/download-minesweeper/download/
Process Hacker2 : https://sourceforge.net/projects/processhacker/
Process Explorer : https://download.sysinternals.com/files/ProcessExplorer.zip
DLL 파일 빌드 환경 : Visual Studio 2019
실습 환경 : Windows 11 x64
hack.dll 만들기(WinAPI)
hack.dll은 대상 프로세스에 dll 파일이 인젝션되면 DllMain()이 호출되고 DllMain이 어떤 이유로 호출이 되었는지를 나타내는 fdwReason 변수의 값이 DLL_PROCESS_ATTACH(프로세스 시작 또는 LoadLibrary 호출의 결과로 DLL이 현재 프로세스에 로딩될 때의 값)이면 CreateThread()로 ShowMessage 라는 스레드를 생성하고, 이 스레드는 인젝션에 성공했다는 메시지창을 띄운다.
스레드를 사용해서 호출하는 이유는 직접적으로 호출할 때는 간혹 Hang이 걸리는 경우가 있기 때문이기도 하고, 다른 사람들이 짠 코드를 참고해봐도 스레드를 생성해서 하는 경우가 많기 때문이다.
dllheader.h 헤더 파일
// dllheader.h
#pragma once
#ifndef DLLHEADER_H
#define DLLHEADER_H
#include <stdio.h>
#include <Windows.h>
#include <tchar.h>
#endif
// CREATEDLL_EXPORTS라는 전처리기가 정의되어 있는지 확인
// 동일한 *.h 파일을 dll 생성 프로젝트와 dll 호출 프로젝트에서 사용하기 위해서이다
#ifdef CREATEDLL_EXPORTS
// CREATEDLL_EXPORTS가 정의되어 있을때
// __declspec(dllexport) 키워드가 붙은 함수는 DLL 외부로 노출되므로, DLL을 사용해 __declspec(dllexport) 키워드가 붙은 함수를 호출할 수 있다는 의미이다.
// __declspec(dllexport) 키워드가 없는 함수는 정의되어 있어도 DLL 외부에서 호출할 수 없다.
#define MYMATH_DECLSPEC __declspec(dllexport)
#else
// CREATEDLL_EXPORTS가 정의되어 있지 않을 때
// __declspec(dllimport) 키워드는 DLL에 정의된 함수를 호출하기 위해 사용된다.
// 즉, DLL에서 __declspec(dllexport)로 정의된 함수를 __declspec(dllimport)로 호출한다는 것이다.
#define MYMATH_DECLSPEC __declspec(dllimport)
#endif
// Name Mangling : 컴파일러가 함수의 이름을 일정한 규칙을 가지고 변형하는 것
// DLL을 사용하여 명시적 링킹(Explicit Linking)을 할 경우 호출할 함수의 이름을 알아야 하는데,
// 함수의 이름이 DLL로 생성되면서 맹글링 되면 호출하기가 어려우므로 extern "C" 키워드를 붙여 네임 맹글링을 수행하지 않겠다고 정의하는 것이다.
extern "C" MYMATH_DECLSPEC DWORD WINAPI ShowMessage(LPVOID lParam);
//extern "C" MYMATH_DECLSPEC BOOL WINAPI DllMain(HINSTANCE hinstDll, DWORD fdwReason, LPVOID lpvReserved)
dll_main.c 파일
#include "dllheader.h"
DWORD WINAPI ShowMessage(LPVOID lParam)
{
MessageBox(NULL, _T("DLL Injection Success"), _T("MessageBox"), MB_OK);
return 0;
}
BOOL WINAPI DllMain(HINSTANCE hinstDll, DWORD fdwReason, LPVOID lpvReserved)
{
HANDLE hThread = NULL;
switch (fdwReason)
{
case DLL_PROCESS_ATTACH:
OutputDebugString(_T("<지뢰찾기.dll> Injection!!!"));
hThread = CreateThread(NULL, 0, ShowMessage, NULL, 0, NULL);
CloseHandle(hThread);
break;
}
return TRUE;
}
위의 코드는 헤더 파일과 DllMain() 소스 파일이다.
Visual Studio에서 빌드하여 .dll 파일을 만드는 방법은 아래의 링크에서 확인한다.
https://sean.tistory.com/294?category=899305
빌드가 안되거나 빌드가 어려운 환경이라면 아래의 파일을 다운로드한다.
Process Hacker를 이용해 수동으로 프로세스에 DLL Injection하고, Process Explorer에서 확인
먼저, 지뢰찾기.exe를 실행한다.
그 다음 Process Hacker을 실행해 지뢰찾기.exe 프로세스의 PID를 확인해보면 위의 사진에서와 같이 18032이다.
지뢰찾기.exe 프로세스에서 오른쪽 마우스 -> Miscellaneous -> Inject DLL...
그리고 위에서 빌드했던 DLL 파일인 hack.dll 파일을 선택한다.
그러면 위와 같이 "DLL Injection Success" 메시지창이 뜬다.
그리고 process explorer에서 지뢰찾기.exe 프로세스에 로드된 DLL 파일 목록을 보면 hack.dll이 로드되어 있다.
hack.dll 파일을 Injection 하는 프로그램 만들기(WinAPI)
위에서는 Process Hacker를 이용해 수동으로 hack.dll 파일을 Injection 했는데, 목표로 하는 프로세스에 hack.dll 파일을 인젝션하는 프로그램을 만든다.
코드의 동작 로직
1. CreateToolhelp32Snapshot으로 현재 시스템의 모든 프로세스 및 스레드와 th32ProcessID에 지정된 프로세스의 힙 및 모듈을 포함하여 스냅샷을 생성한다.
2. Process32First와 Process32Next로 1번에서 생성된 스냅샷에서 프로세스 정보를 가져와 PROCESSENTRY32 구조체에 담는다.
3. 목표 프로세스 이름과 같은 프로세스일 경우 PID를 얻는다.
(멀티 바이트 환경인지 유니코드 환경인지에 따라 자동으로 변환되도록 strcmp()가 아니라 _tcsicmp()를 사용한다.)
4. 얻은 PID를 이용해 OpenProcess() 함수로 해당 프로세스의 핸들을 얻는다.
5. VirtualAllocEx로 해당 프로세스에 hack.dll 경로 길이만큼 메모리를 할당한다.
6. WriteProcessMemory()를 사용해 dll 경로를 써준다.
(이때 5번에서 할당한 메모리 주소가 2번째 인자로 들어간다.)
7. GetModuleHandle()과 GetProcAddress()를 이용해 kernel32.dll의 LoadLibraryA() 함수의 주소를 구한다.
(대상 프로세스가 아닌 현재 프로세스에서 구하는 이유는 Windows에서 kernel32.dll이 프로세스마다 같은 주소에 로딩되기 때문이고, kernel32.dll 같은 파일들을 로드를 안하더라도 로더가 알아서 강제로 로드시키기 때문에 무조건 로드가 되는 dll 파일이다.)
8.CreateRemoteThread() 함수의
첫 번째 인자는 목표로 하는 대상 프로세스의 핸들,
네번째 인자(스레드 함수 주소) 부분은 LoadLibrary 주소,
마지막으로 다섯 번째 인자(스레드 파라미터 주소) 부분은 hack.dll 경로를 써준 메모리 주소를 넘긴다
위의 로직대로 되고 나면 목표로 하는 대상 프로세스에서 LoadLibrary("hack.dll의 경로") 스레드가 실행되게 된다.
아래의 코드를 Visual Studio에서 새 프로젝트를 만들어 InjectDll.c 파일에 입력하고 컴파일한 뒤 실행하면
위에서 Process Hacker을 이용해서 DLL Injection을 수행한 것처럼 메시지 창이 뜰 것이다.
InjectDll.c
#include <stdio.h>
#include <Windows.h>
#include <tlhelp32.h>
#include <tchar.h>
BOOL GetProcessPid(HANDLE* hProcessSnap, DWORD* PID, PROCESSENTRY32* pe32, LPCTSTR* DllPath);
BOOL InjectDll(HANDLE hProcess, DWORD PID, LPCTSTR* DllPath);
int main(void)
{
HANDLE hProcessSnap, hProcess = 0;
PROCESSENTRY32 pe32 = { 0 }; // 프로세스 정보가 담길 구조체
DWORD PID;
LPCTSTR DllPath = (LPCTSTR)"C:\\hack.dll";
GetProcessPid(&hProcessSnap, &PID, &pe32, &DllPath); // 프로세스의 핸들 얻어오기
InjectDll(hProcess, PID, &DllPath);
}
BOOL GetProcessPid(HANDLE* hProcessSnap, DWORD* PID, PROCESSENTRY32* pe32, LPCTSTR* DllPath)
{
// 시스템에서 현재 실행 중인 프로세스의 정보를 얻어 와서 스냅샷을 만든다.
hProcessSnap = (HANDLE*)CreateToolhelp32Snapshot(TH32CS_SNAPALL, 0);
if (hProcessSnap == INVALID_HANDLE_VALUE)
{
_tprintf(_T("CreateToolhelp32Snapshot (of processes)"));
return(FALSE);
}
// Set the size of the structure before using it.
pe32->dwSize = sizeof(PROCESSENTRY32);
// Retrieve information about the first process,
// and exit if unsuccessful
if (Process32First(hProcessSnap, pe32))
{
do
{
if (!_tcsicmp(pe32->szExeFile, _T("지뢰찾기.exe")))
{
*PID = pe32->th32ProcessID;
_tprintf(_T("[*] Process Name : %s\n"), pe32->szExeFile);
_tprintf(_T("[*] PID is : %u\n\n"), *PID);
break;
}
} while (Process32Next(hProcessSnap, pe32));
CloseHandle(hProcessSnap);
}
else
{
_tprintf(_T("Process32First error! Error Code is : %d\n"), GetLastError()); // show cause of failure
CloseHandle(hProcessSnap); // clean the snapshot object
return(FALSE);
}
}
BOOL InjectDll(HANDLE hProcess, DWORD PID, LPCTSTR* DllPath)
{
HANDLE hThread;
LPVOID pRemoteBuf;
HMODULE hMod = 0;
LPTHREAD_START_ROUTINE pThreadProc;
// #1. PID 를 이용하여 대상 프로세스의 HANDLE을 구한다.
if (!(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, PID)))
{
_tprintf(_T("OpenProcess(%d) failed!!! [%d]\n"), PID, GetLastError());
return FALSE;
}
// #2. 대상 프로세스 메모리에 DllPath 크기만큼 메모리를 할당한다.
if (!(pRemoteBuf = VirtualAllocEx(hProcess, NULL, lstrlen(*DllPath) + 1, MEM_COMMIT, PAGE_READWRITE)))
_tprintf(_T("VirtualAllocEx() Failed!!\n"));
else _tprintf(_T("-> Virtual Memory is : %x\n"), (unsigned int)pRemoteBuf);
// #3. 할당 받은 메모리에 지뢰찾기_ex.dll 경로를 쓴다.
if (!(WriteProcessMemory(hProcess, pRemoteBuf, (LPVOID)*DllPath, lstrlen(*DllPath) + 1, NULL)))
_tprintf(_T("WriteProcessMemory() failed!!\n"));
// #4. KERNEL 핸들 구하기
if (!(hMod = GetModuleHandle(_T("Kernel32.dll"))))
_tprintf(_T("GetModuleHandle() Failed!!, Error Code is : %d\n"), GetLastError());
else _tprintf(_T("-> KERNEL.dll memory is : %x\n"), (unsigned int)hMod);
// #5. LoadLibraryA() API 주소를 구한다.
if (!(pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hMod, "LoadLibraryA")))
_tprintf(_T("GetProcAddress() Failed!!, Error Code is : %d\n"), GetLastError());
else _tprintf(_T("-> KERNEL32.LoadLibraryW : % x\n"), pThreadProc);
// #6. notepad.exe 프로세스에 스레드를 실행
if (!(hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, pRemoteBuf, 0, NULL)))
_tprintf(_T("CreateRemoteThread() Failed!!, Error Code is : %d\n"), GetLastError());
WaitForSingleObject(hThread, INFINITE);
_tprintf(_T("-> Thread Handle is : %x\n\n"), hThread);
}
CreateThread()와 CreateRemoteThread()의 차이점
위 두 함수 모두 Windows에서 스레드의 탄생을 책임지고 있는 함수로, 스레드를 생성하는 역할이다.
차이점은 아래와 같다.
CreateThread : 호출한 프로세스에서 동작하는 스레드를 생성한다.
CreateRemoteThread : 파라미터로 넘어온 프로세스에서 수행되는 스레드를 생성한다.
실제로는 Windows 내부에서 CreateThread의 경우 GetCurrentThread를 사용해 CreateRemoteThread를 호출하는 것으로 구현이 되어 있기 때문에 CreateThread는 CreateRemoteThread의 서브셋이라고 보면 된다고 한다.
지뢰 찾기.exe DLL Injection 실습 원본 글 : https://liveyourit.tistory.com/114?category=873508
프로세스에 대한 스냅샷을 만들고 프로세스 보기 관련 Document : https://docs.microsoft.com/ko-kr/windows/win32/toolhelp/taking-a-snapshot-and-viewing-processes
프로세스 정보 추출 관련 블로그 글 : https://sosal.kr/629
DllMain() 함수 Document : https://docs.microsoft.com/ko-kr/windows/win32/dlls/dllmain
Visual Studio에서 DLL 프로젝트로 DLL 파일을 생성하고 해당 DLL을 불러오는지에 관련된 글 : https://luckygg.tistory.com/278
PROCESSENTRY32 구조 Document : https://docs.microsoft.com/en-us/windows/win32/api/tlhelp32/ns-tlhelp32-processentry32
Windows handle에 관련된 글 : https://m.blog.naver.com/PostView.nhn?isHttpsRedirect=true&blogId=tipsware&logNo=221065382244&proxyReferer=
Windows에서 스레드 다루는 블로그 글 : http://www.jiniya.net/wp/archives/7194
'Reversing > 실습' 카테고리의 다른 글
stolen_bytes_pespin.exe 언패킹 (0) | 2023.09.09 |
---|---|
[Reversing 실습] 지뢰찾기.exe DLL Ejection (0) | 2022.11.09 |
Windows 7 64bit에서 32bit notepad.exe PE 재배치(Base Relocation Table) (0) | 2022.06.19 |
Windows 7 64bit에서 HxD로 32bit notepad.exe PE 파일 EP 영역 확인 (0) | 2022.06.19 |
UPX packer로 notepad.exe 실행 압축한 파일 분석 (0) | 2022.06.13 |