// EjectDll.exe
#include "windows.h"
#include "tlhelp32.h"
#include "tchar.h"
#define DEF_PROC_NAME L"notepad.exe"
#define DEF_DLL_NAME L"myhack.dll"
// process 이름 가지고 PID 찾기
DWORD FindProcessID(LPCTSTR szProcessName) {
DWORD dwPID = 0xFFFFFFFF;
HANDLE hSnapshot = INVALID_HANDLE_VALUE; // 시스템 스냅샷의 핸들
PROCESSENTRY32 pe; // 프로세스 정보 저장할 구조체
pe.dwSize = sizeof(PROCESSENTRY32); // 구조체 크기 초기화
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPALL, NULL); // 시스템 스냅샷 생성
Process32First(hSnapshot, &pe); // 스냅샷에 있는 첫번째 프로세스 정보를 pe에 저장
do {
if(!_tcsicmp(szProcessName, (LPCTSTR)pe.szExeFile)) { // szProcessName과 비교
dwPID = pe.th32ProcessID;
break;
}
} while(Process32Next(hSnapshot, &pe)); // 다음 프로세스 정보를 pe에 저장
CloseHandle(hSnapshot);
return dwPID;
}
// Debug API를 사용하기 위해 엑세스 토큰의 privilege 변경
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, lpszPrivilege, &luid)) {
_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;
if(!AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), (PTOKEN_PRIVILEGES)NULL, (PDWORD)NULL)) {
_tprintf(L"AdjuestTokenPrivileges 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;
}
// CreateRemoteThread를 이용해 대상 프로세스에서 dll eject
BOOL EjectDll(DWORD dwPID, LPCTSTR szDllName) {
HANDLE hSnapshot, hProcess, hThread;
HMODULE hModule = NULL;
MODULEENTRY32 me; // 모듈 정보를 저장할 구조체
LPTHREAD_START_ROUTINE pThreadProc;
BOOL bMore=FALSE, bFound=FALSE;
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwPID); // 프로세스의 모듈에 대한 스냅샷 생성
me.dwSize = sizeof(MODULEENTRY32); // 구조체 크기 초기화
bMore = Module32First(hSnapshot, &me);
for( ; bMore ; bMore = Module32Next(hSnapshot, &me)) {
// 모듈의 파일 이름과 실행 경로 둘 중 하나라도 szDllName과 일치하는지 확인
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;
}
int _tmain(int argc, TCHAR* argv[]) {
DWORD dwPID = 0xFFFFFFFF;
dwPID = FindProcessID(DEF_PROC_NAME);
if(dwPID == 0xFFFFFFFF) {
_tprintf(L"There is no %s process!\n", DEF_PROC_NAME);
return 1;
}
_tprintf(L"PID of \"%s\" is %d!\n", DEF_PROC_NAME, dwPID);
if(!SetPrivilege(SE_DEBUG_NAME, TRUE))
return 1;
if(EjectDll(dwPID, DEF_DLL_NAME))
_tprintf(L"EjectDll(%d, \"%s\") success!!\n", dwPID, DEF_DLL_NAME);
else
_tprintf(L"EjectDll(%d, \"%s\") failed!!\n", dwPID, DEF_DLL_NAME);
return 0;
}
1. tlhep32.h
- HANDLE CreateToolhelp32Snapshot(DWORD dwFlags, DWORD th32ProcessID)
* 지정된 프로세스에서 사용되는 힙, 모듈 및 스레드의 스냅샷을 만듭니다
* dwFlags:
* th32ProcessID: 스냅샷에 포함할 프로세스의 프로세스 식별자, NULL 혹은 0을 주면 모든 프로세스가 스냅샷에 포함됨
- BOOL Process32First([in] HANDLE hSnapshot, [in,out] LPPROCESSENTRY32 lppe)
* 시스템 스냅샷에서 발생한 첫 번째 프로세스에 대한 정보를 검색
* 프로세스 목록의 첫 번째 항목이 버퍼에 복사되었으면 TRUE 를 반환하고 그렇지 않으면 FALSE 를 반환
* 프로세스가 없거나 스냅샷에 프로세스 정보가 없는 경우 GetLastError에서 ERROR_NO_MORE_FILES 오류 값 반환
- BOOL Process32Next([in] HANDLE hSnapshot, [in,out] LPPROCESSENTRY32 lppe)
* 시스템 스냅샷에 기록된 다음 프로세스에 대한 정보를 검색
* 프로세스 목록의 다음 항목이 버퍼에 복사되었으면 TRUE 를 반환하고 그렇지 않으면 FALSE 를 반환
* 프로세스가 없거나 스냅샷에 프로세스 정보가 없는 경우 GetLastError에서 ERROR_NO_MORE_FILES 오류 값 반환
- Module32First, Module32Next도 동일 (두번째 인자가 MODULEENTRY 구조체 포인터)
- PROCESSENTRY32 구조체
DWORD dwSize | 구조체의 크기(바이트), dwSize를 초기화하지 않으면 Process32First가 실패 |
DWORD th32ProcessID | 프로세스 식별자 |
DWORD cntThreads | 프로세스에서 시작한 실행 스레드 수 |
DWORD th32ParentProcessID | 이 프로세스를 만든 프로세스의 식별자(부모 프로세스) |
CHAR szExeFile[MAX_PATH] | 프로세스에 대한 실행 파일의 이름 // CHAR 배열이라 (LPCTSTR) cast 필요 |
- MODULEENTRY32 구조체
DWORD dwSize | 구조체의 크기(바이트), dwSize를 초기화하지 않으면 Module32First가 실패 |
DWORD th32ProcessID | 모듈을 검사할 프로세스의 식별자 |
BYTE *modBaseAddr | 소유 프로세스의 컨텍스트에서 모듈의 기본 주소 |
DWORD modBaseSize | 모듈의 크기(바이트) |
HANDLE hModule | 소유 프로세스의 컨텍스트에서 모듈에 대한 핸들 |
char szModule[MAX_MODULE_NAME32+1] | 모듈 이름 |
char szExePath[MAX_PATH] | 모듈 경로 |
2. 코드 변경?
- szDllName comparison
// in EjectDll()
if(!_tcsicmp((LPCTSTR)me.szModule, szDllName) || !_tcsicmp((LPCTSTR)me.szExePath, szDllName)) {
bFound = TRUE;
break;
}
- me.szExePath (전체경로)와의 비교를 생략해도 Ejection 잘 동작하지만
- me.szModule (모듈명)과의 비교를 생략하면 Ejection 실패함
=> szDllName의 다양한 형태(전체경로 or 파일명)에 대응하기 위한 구현이라고 볼 수 있다
- DEF_PROC_NAME 과 DEF_DLL_NAME을 user input으로 받도록 구현
int _tmain(int argc, TCHAR* argv[]) {
if(argc != 3) {
_tprintf(L"USAGE: %s <Process Name> <Dll Name>\n", argv[0]);
_tprintf(L"You can give us either name or full path!\n");
return 1;
}
DWORD dwPID = 0xFFFFFFFF;
dwPID = FindProcessID(argv[1]);
if(dwPID == 0xFFFFFFFF) {
_tprintf(L"There is no %s process!\n", argv[1]);
return 1;
}
_tprintf(L"PID of \"%s\" is %d!\n", argv[1], dwPID);
if(!SetPrivilege(SE_DEBUG_NAME, TRUE))
return 1;
if(EjectDll(dwPID, argv[2]))
_tprintf(L"EjectDll(%d, \"%s\") success!!\n", dwPID, argv[2]);
else
_tprintf(L"EjectDll(%d, \"%s\") failed!!\n", dwPID, argv[2]);
return 0;
}
- argv[1]에는 Dll Ejection을 하고싶은 프로세스의 이름을 건네주어야 되지만
- argv[2]에는 Dll Name / Dll Path 둘 중 어느 것을 건네주어도 상관 없다 (Comparison 둘 다 고려)
3. 강제로 인젝션한 Dll에 대해서만 FreeLibrary()를 이용한 언로딩 가능하다던데?
EjectDll.exe를 사용하여 DLL을 제거할 때 FreeLibrary 함수를 호출합니다. 이 함수는 DLL의 참조 카운터를 감소시키고, 참조 카운터가 0이 되면 DLL을 메모리에서 언로드합니다. 그러나 이미 로드된 DLL이 PE 파일(실행 파일)에 의해 사용되고 있으면, 참조 카운터가 0보다 크기 때문에 FreeLibrary 호출로 DLL을 언로드할 수 없습니다.
1. 이미 로드된 DLL은 실행 파일에 의해 사용되고 있어 참조 카운터가 0이 아닙니다. (1보다 클 것)
2. FreeLibrary 함수는 참조 카운터가 0이 될 때까지 DLL을 언로드하지 않습니다.
3. 따라서 이미 로드된 DLL에 대해서는 FreeLibrary를 사용하여 언로드할 수 없습니다.
'security > 리버싱핵심원리' 카테고리의 다른 글
Code Injection (0) | 2023.03.23 |
---|---|
PE 패치를 이용한 DLL Injection (0) | 2023.03.20 |
SetWindowsHookEx()를 이용한 DLL Injection (0) | 2023.03.19 |
Registry를 이용한 DLL Injection (0) | 2023.03.18 |
CreateRemoteThread()를 이용한 DLL Injection (0) | 2023.03.18 |