DLL ejection
다른 프로세스의 DLL을 Free하도록 만드는 것
CreateRemoteThread를 이용해 FreeLibrary API를 호출하게 하고 그 인자로 Free할 DLL의 주소를 넘겨주면된다.
// EjectDll.exe
#include "windows.h"
#include "tlhelp32.h"
#include "tchar.h"
#define DEF_PROC_NAME (L"notepad.exe")
#define DEF_DLL_NAME (L"Dll1.dll")
DWORD FindProcessID(LPCTSTR szProcessName)
{
DWORD dwPID = 0xFFFFFFFF;
HANDLE hSnapShot = INVALID_HANDLE_VALUE;
PROCESSENTRY32 pe;
// Get the snapshot of the system
pe.dwSize = sizeof(PROCESSENTRY32);
hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPALL, NULL);
// find process
Process32First(hSnapShot, &pe);
do
{
if (!_tcsicmp(szProcessName, (LPCTSTR)pe.szExeFile))
{
dwPID = pe.th32ProcessID;
break;
}
} while (Process32Next(hSnapShot, &pe));
CloseHandle(hSnapShot);
return dwPID;
}
BOOL EjectDll(DWORD dwPID, LPCTSTR szDllName)
{
BOOL bMore = FALSE, bFound = FALSE;
HANDLE hSnapshot, hProcess, hThread;
HMODULE hModule = NULL;
MODULEENTRY32 me = { sizeof(me) };
LPTHREAD_START_ROUTINE pThreadProc;
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwPID);
bMore = Module32First(hSnapshot, &me);
_tprintf(L"%p\n", me.modBaseAddr);
for (; bMore; bMore = Module32Next(hSnapshot, &me))
{
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");
_tprintf(L"%p\n", pThreadProc);
_tprintf(L"%p\n", me.modBaseAddr);
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;
// find process
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);
// eject dll
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;
}
코드 설명
이번에는 PID를 적지않고 Process의 이름으로 PID를 알아낸 다음(FindProcessID 함수)
그 PID와 DLL의 이름을 EjectDll 함수에 넘겨준다.
CreateToolhelp32Snapshot함수를 통해 메모장 프로세스에 로딩된 DLL 정보를 얻을 수 있는데
Module32Firest로 시작해서 Module32Next로 넘겨주면 me에 DLL의 정보를 담은 MOULEENTRY32 구조체가 나온다.
이 구조체 안에 모듈의 이름과 Free할 이름이 같으면 break하고
FreeLibrary의 주소를 알아낸 뒤,
CreateRemoteThread 함수로 FreeLibrary 함수의 주소와 me구조체 안의 DLL 시작 주소(me.modBaseAddr)을 인자로 넘겨주어 원격 스레드를 실행하면 메모장에 원격 스레드가 실행되면서 FreeLibrary함수에 me.modBaseAddr이 인자로 들어가며 Free된다.
DLL injection 중 원격 스레드를 이용하는 방법과 같다.
내 컴퓨터에선 FreeLibrary 함수의 주소는 0x00007FFF61A1CB10이었고
DLL이 로딩된 위치는 0x00007FFF56730000였다
PE 패치를 이용한 DLL 로딩
파일 자체를 수정해서 내가 원하는 DLL을 로딩하게 하는 방법.
#include "stdio.h"
#include "conio.h"
#include "windows.h"
void main() {
while (_getch() != 'q')
{
}
printf("stop");
}
이런 프로그램을 대충 만들었다.
이 프로그램에 Dll1.dll이 로딩되게 만들어 보겠다.
Stud_PE로 보면서 Hxd로 수정을 했다.
보면서 동시에 수정이 안되니까 복사해서 하나는 보는용도 하나는 수정하는 용도로 만들었다.
Stud_PE로 보니
IDT 위치 - RAW = 1ADC ~ 1B90여기에 있고 크기는 B4 였다 1개를 더 늘려야 하기때문에 C8크기가 필요한 상태인데 뒤에는 사용중이다.
다른곳에 할당해야한다.
마땅한 곳을 찾아보니 .reloc부분에 안쓰는 부분이 많다.
.reloc부분 RVA 6000이고 RAW는 2800이다.
virtual size와 raw size 를 보니 2830까지는 쓰고있다.
RAW기준 2840부터 IDT를 시작해야겠다.
OPTIONAL HEADER의 IDT를 시작 위치는 RAW기준 2840 즉 RVA 6040 , size는 C8로 바꾼다.
RAW 1ADC~ 1B90에 있는 내용을 2840에 채워준다.
맨 뒤에건 비어있기 떄문에 28E0부터
INT는 RAW 2960 -> RVA 6160, name은 RAW2930 -> RVA6130, IAT는 RAW 2940 -> RVA 6140으로 바꿔준다.
(마찬가지로 마지막 20바이트는 00으로 차있어야됨!)
그리고 2950에 ordinal 00 00 + 함수 이름(HookStart)를 적고
RAW 2930에 Dll1.dll을 적어준다.
그리고 RAW 2940(IAT)에 2950 -> 6150을 적어준다.
(2960)INT에도 6150을 적어줘야하지만 안적어도 IAT를 참조하여 한다고 했으니 안적어보겠다.
bound import table 도 00으로 바꿔줘야하는데 이미 0이어서 안바꿔도 된다.
이제 저장하고 Dll1.dll을 같은 디렉토리에 넣고 디버거로 실행해보면 Dll1.dll이 import되는걸 볼 수 있다.
FreeLibrary를 해보면 로딩시 import되는 dll은 free되지 않는다.
Code Injection
-DLL을 injection 하는 것이 아니라 코드를 Injection 함
VirtualAllocEx와 WriteProcessMemory함수로
프로세스의 readwrite 권한을 가진 메모리 1개와 excutable, read,write 권한을 가진메모리 1개를 할당해서
전자의 메모리엔 데이터 넣고 후자에는 코드를 넣어서
create Remote Thread 코드의 주소와 인자의 주소를 전달해서 실행시킴.
#include "windows.h"
#include "tlhelp32.h"
#include "tchar.h"
#include "stdio.h"
#define DEF_PROC_NAME (L"conso.exe")
#define DEF_DLL_NAME (L"C:\\Users\\a0102\\source\\repos\\Dll1\\x64\\Release\\Dll1.dll")
DWORD FindProcessID(LPCTSTR szProcessName)
{
DWORD dwPID = 0xFFFFFFFF;
HANDLE hSnapShot = INVALID_HANDLE_VALUE;
PROCESSENTRY32 pe;
// Get the snapshot of the system
pe.dwSize = sizeof(PROCESSENTRY32);
hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPALL, NULL);
// find process
Process32First(hSnapShot, &pe);
do
{
if (!_tcsicmp(szProcessName, (LPCTSTR)pe.szExeFile))
{
dwPID = pe.th32ProcessID;
break;
}
} while (Process32Next(hSnapShot, &pe));
CloseHandle(hSnapShot);
return dwPID;
}
typedef struct _THREAD_PARAM
{
FARPROC pFunc[2]; // LoadLibraryA(), GetProcAddress()
char szBuf[3][128]; // "user32.dll", "MessageBoxA", "http://www.reversecore.com", "ReverseCore"
} THREAD_PARAM, * PTHREAD_PARAM;
typedef HMODULE(WINAPI* PFLOADLIBRARYA)
(
LPCSTR lpLibFileName
);
typedef FARPROC(WINAPI* PFGETPROCADDRESS)
(
HMODULE hModule,
LPCSTR lpProcName
);
DWORD WINAPI ThreadProc(LPVOID lParam)
{
PTHREAD_PARAM pParam = (PTHREAD_PARAM)lParam;
HMODULE hMod = NULL;
FARPROC pFunc = NULL;
// LoadLibrary()
hMod = ((PFLOADLIBRARYA)pParam->pFunc[0])(pParam->szBuf[0]); // "user32.dll"
if (!hMod)
return 1;
// GetProcAddress()
pFunc = (FARPROC)((PFGETPROCADDRESS)pParam->pFunc[1])(hMod, pParam->szBuf[1]); // "MessageBoxA"
if (!pFunc)
return 1;
// LoadLibrary
((PFLOADLIBRARYA)pFunc)(pParam->szBuf[2]);
return 0;
}
BOOL InjectCode(DWORD dwPID)
{
HMODULE hMod = NULL;
THREAD_PARAM param = { 0, };
HANDLE hProcess = NULL;
HANDLE hThread = NULL;
LPVOID pRemoteBuf[2] = { 0, };
DWORD dwSize = 0;
hMod = GetModuleHandleA("kernel32.dll");
// set THREAD_PARAM
param.pFunc[0] = GetProcAddress(hMod, "LoadLibraryA");
param.pFunc[1] = GetProcAddress(hMod, "GetProcAddress");
strcpy_s(param.szBuf[0], "kernel32.dll");
strcpy_s(param.szBuf[1], "LoadLibraryA");
strcpy_s(param.szBuf[2], "Dll1.dll");
// Open Process
if (!(hProcess = OpenProcess(PROCESS_ALL_ACCESS, // dwDesiredAccess
FALSE, // bInheritHandle
dwPID))) // dwProcessId
{
printf("OpenProcess() fail : err_code = %d\n", GetLastError());
return FALSE;
}
// Allocation for THREAD_PARAM
dwSize = sizeof(THREAD_PARAM);
if (!(pRemoteBuf[0] = VirtualAllocEx(hProcess, // hProcess
NULL, // lpAddress
dwSize, // dwSize
MEM_COMMIT, // flAllocationType
PAGE_READWRITE))) // flProtect
{
printf("VirtualAllocEx() fail : err_code = %d\n", GetLastError());
return FALSE;
}
printf("%p\n", pRemoteBuf[0]);
if (!WriteProcessMemory(hProcess, // hProcess
pRemoteBuf[0], // lpBaseAddress
(LPVOID)¶m, // lpBuffer
dwSize, // nSize
NULL)) // [out] lpNumberOfBytesWritten
{
printf("WriteProcessMemory() fail : err_code = %d\n", GetLastError());
return FALSE;
}
// Allocation for ThreadProc()
dwSize = (DWORD)InjectCode - (DWORD)ThreadProc;
if (!(pRemoteBuf[1] = VirtualAllocEx(hProcess, // hProcess
NULL, // lpAddress
dwSize, // dwSize
MEM_COMMIT, // flAllocationType
PAGE_EXECUTE_READWRITE))) // flProtect
{
printf("VirtualAllocEx() fail : err_code = %d\n", GetLastError());
return FALSE;
}
if (!WriteProcessMemory(hProcess, // hProcess
pRemoteBuf[1], // lpBaseAddress
(LPVOID)ThreadProc, // lpBuffer
dwSize, // nSize
NULL)) // [out] lpNumberOfBytesWritten
{
printf("WriteProcessMemory() fail : err_code = %d\n", GetLastError());
return FALSE;
}
printf("%p\n", pRemoteBuf[1]);
if (!(hThread = CreateRemoteThread(hProcess, // hProcess
NULL, // lpThreadAttributes
0, // dwStackSize
(LPTHREAD_START_ROUTINE)pRemoteBuf[1], // dwStackSize
pRemoteBuf[0], // lpParameter
0, // dwCreationFlags
NULL))) // lpThreadId
{
printf("CreateRemoteThread() fail : err_code = %d\n", GetLastError());
return FALSE;
}
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
CloseHandle(hProcess);
return TRUE;
}
int main(int argc, char* argv[])
{
DWORD dwPID = 0;
// code injection
dwPID = (DWORD)FindProcessID(DEF_PROC_NAME);
printf("%d\n", dwPID);
InjectCode(dwPID);
return 0;
}
main
-코드를 보면 프로그램의 이름으로 PID를 찾고 InjectCode에 PID를 넘겨준다
InjectCode
-프로세스에 전달할 구조체를 만들어서 내용을 채운다(LoadLibraryA 함수 주소, GetProcAddress 함수 주소, 기타 string들)
-PID로 프로세스 핸들을 얻은 뒤, VirtualAllocEx 를 호출하여 대상 프로세스에 파라미터를 채울 메모리를 할당하고 주소를 얻는다.
- WriteProcessMemory로 할당한 메모리에 구조체를 쓴다.
- VirtualAllocEx 를 PAGE_EXECUTE_READWRITE 로 호출하여 대상 프로세스에서 실행할 코드를 채울 메모리를 할당하고 주소를 얻는다.
- WriteProcessMemory로 할당한 메모리에 함수 코드를 쓴다.
-CreateRemoteThread로 함수 주소에는 상대 메모리에서 코드를 위해 할당한 주소, 인자 주소에는 상대 메모리에서 구조체를 위해 할당한 주소를 넣고 실행한다.
실행시키면 메모리가 할당되었고 하나는 RW, 하나는 ERW로 할당되었다.
보면 아래는 구조체 메모리, 위에는 코드 메모리인데 잘 들어가 있는 것을 확인 할 수 있다.