Code Injection (Thread Injection)
- 우리가 원격에서 실행하고픈 코드가 있을 때, 이를 DLL 파일 형태로 만든 후 CreateRemoteThread() 인자로 LoadLibraryA() API 주소와 dll name 주소를 건네주어 DLL을 inject하고 DllMain에서 코드를 실행시킴
=> 흔적이 많이 남음(새로운 dll 로드되면 process explorer에서 즉시 확인 가능), 메모리 많이 차지
- 상대 프로세스에 독립실행 코드(Thread Procedure)과 데이터(Thread Parameter)만 삽입해 CreateRemoteThread()로 해당 thread를 실행시켜보자
=> 메모리 덜 차지, 흔적 거의 남지 않음, 별도의 DLL 파일 필요 없음, 규모 작음
CodeInjection.exe 소스코드
// CodeInjection.exe
#include "stdio.h"
#include "windows.h"
// Thread Parameter: Data to Inject
typedef struct _THREAD_PARAM {
FARPROC pFunc[2];
char szBuf[4][128];
} THREAD_PARAM, *PTHREAD_PARAM;
// LoadLibraryA, GetProcAddress, MessageBoxA
typedef HMODULE(WINAPI *PFLOADLIBRARYA)(LPCSTR lpLibFileName);
typedef FARPROC(WINAPI *PFGETPROCADDRESS)(HMODULE hModule, LPCSTR lpProcName);
typedef int(WINAPI *PFMESSAGEBOXA)(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType);
// Thread Procedure: Code to Inject
DWORD WINAPI ThreadProc(LPVOID lParam) {
PTHREAD_PARAM pParam = (PTHREAD_PARAM)lParam;
HMODULE hMod = NULL;
FARPROC pFunc = NULL;
// LoadLibraryA("user32.dll");
hMod = ((PFLOADLIBRARYA)pParam->pFunc[0])(pParam->szBuf[0]);
if(!hMod) return 1;
// GetProcAddress(hMod, "MessageBoxA");
pFunc = ((PFGETPROCADDRESS)pParam->pFunc[1])(hMod, pParam->szBuf[1]);
if(!pFunc) return 1;
// MessageBoxA(NULL, "This is code injection!!", "MINSEO", MB_OK);
((PFMESSAGEBOXA)pFunc)(NULL, pParam->szBuf[2], pParam->szBuf[3], MB_OK);
return 0;
}
BOOL InjectCode(DWORD dwPID) {
HMODULE hMod = NULL; // GetModuleHandleA 리턴값 저장
THREAD_PARAM param = {0,};
HANDLE hProcess = NULL; // OpenProcess 리턴값 저장
HANDLE hThread = NULL; // CreateRemoteThread 리턴값 저장
LPVOID pRemoteBuf[2] = {0,}; // VirtualAllocEx 리턴값 저장
DWORD dwSize = 0;
hMod = GetModuleHandleA("kernel32.dll");
// parameter setting (strcpy may be unsafe, use strcpy_s instead)
param.pFunc[0] = GetProcAddress(hMod, "LoadLibraryA");
param.pFunc[1] = GetProcAddress(hMod, "GetProcAddress");
strcpy(param.szBuf[0], "user32.dll");
strcpy(param.szBuf[1], "MessageBoxA");
strcpy(param.szBuf[2], "This is code injection!!");
strcpy(param.szBuf[3] , "MINSEO");
if( !(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)) ){
printf("OpenProcess() fail : err_code = %d\n", GetLastError());
return FALSE;
}
// Allocation for THREAD_PARAM
dwSize = sizeof(THREAD_PARAM);
if( !(pRemoteBuf[0] = VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE)) ) {
printf("VirtualAllocEx() fail : err_code = %d\n", GetLastError());
return FALSE;
}
if( !(WriteProcessMemory(hProcess, pRemoteBuf[0], (LPVOID)¶m, dwSize, NULL)) ) {
printf("WriteProcessMemory() fail : err_code = %d\n", GetLastError());
return FALSE;
}
// Allocation for ThreadProc
dwSize = (DWORD)InjectCode - (DWORD)ThreadProc;
if( !(pRemoteBuf[1] = VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE)) ) {
printf("VirtualAllocEx() fail : err_code = %d\n", GetLastError());
return FALSE;
}
if( !(WriteProcessMemory(hProcess, pRemoteBuf[1], (LPVOID)ThreadProc, dwSize, NULL)) ) {
printf("WriteProcessMemory() fail : err_code = %d\n", GetLastError());
return FALSE;
}
// Remote execution of code
if( !(hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pRemoteBuf[1], pRemoteBuf[0], 0, NULL)) ) {
printf("CreateRemoteThread() fail : err_code = %d\n", GetLastError());
return FALSE;
}
// Wait until Remote Thread terminates
// 메시지박스의 확인버튼을 누를때까지 끝나지 않는다
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
CloseHandle(hProcess);
return TRUE;
}
BOOL SetPrivilege(LPCTSTR lpszPrivilege, BOOL bEnablePrivilege) {
TOKEN_PRIVILEGES tp;
HANDLE hToken;
LUID luid;
if( !OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken) ) {
printf("OpenProcessToken error: %u\n", GetLastError());
return FALSE;
}
if( !LookupPrivilegeValue(NULL, lpszPrivilege, &luid) ) {
printf("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) ) {
printf("AdjustTokenPrivileges error: %u\n", GetLastError() );
return FALSE;
}
if( GetLastError() == ERROR_NOT_ALL_ASSIGNED ) {
printf("The token does not have the specified privilege. \n");
return FALSE;
}
return TRUE;
}
int main(int argc, char* argv[]) {
if(argc != 2) {
printf("USAGE: %s <PID> \n", argv[0]);
return 1;
}
if( !SetPrivilege(SE_DEBUG_NAME, TRUE) )
return 1;
DWORD dwPID = (DWORD)atol(argv[1]);
InjectCode(dwPID);
return 0;
}
- VirtualAllocEx, WriteProcessMemory 같은 Debug API 사용하므로 SetPrivilege 함수를 통해 디버그 권한 받아와야 함
- ThreadProc: 독립 실행 코드 (모든 data를 thread parameter에서 받아와서 사용)
- THREAD_PARAM: Thread의 파라미터, LoadLibraryA()와 GetProcAddress() 두 개의 API만 건네주면 모든 라이브러리의 함수 호출 가능
* 단 inject하고자 하는 프로세스가 kernel32.dll을 로딩해야 됨
Code Injection 디버깅
- Inject하고자 하는 프로세스를 실행하고(ex. calc.exe) ollydbg에 attach하고 F9(run)한다
- OllyDbg 옵션을 변경한다 (Events→Break on new thread)
- CodeInjection.exe <PID> 를 실행하면 해당 스레드 코드 시작 위치에서 멈춘다
추가 메모
- LPVOID 자료형
"void 포인터"를 나타내며, 메모리 주소를 가리키는 포인터 변수에 대한 일반적인 형식으로 사용
어떤 데이터 형식의 포인터이든 가리킬 수 있음
포인터에 대한 타입 캐스트를 피하고 포인터를 일반적으로 처리할 수 있도록 함
// defined in WinDef.h
typedef void *LPVOID;
- FARPROC 자료형
윈도우 프로그래밍에서 동적 링크 라이브러리(DLL)나 공유 객체 파일의 export function의 포인터를 나타냄
* WINAPI 매크로는 stdcall 호출 규약 의미
* FAR 매크로는 코드나 데이터가 저장된 메모리 세그먼트 의미 (32비트에서는 사용 x, 16비트와의 호환성 위해)
FARPROC 함수 포인터에 GetProcAddress로 구한 함수의 주소를 할당할 수 있음
함수의 시그니처(리턴타입, 파라미터)에 따라 함수 호출 전에 FARPROC 함수 포인터를 올바른 타입으로 캐스트 필요
// defined in windows.h
typedef INT_PTR (FAR WINAPI *FARPROC)();
- Ollydbg ThreadProc() 어셈 코드를 참고해 Inject code를 직접 만들 수 있지 않을까??
* ThreadProc()를 MS Visual C++를 이용해 빌드하는 방법
* 직접 어셈블리로 ThreadProc()을 구현해 WriteProcessMemory( byte code )
'security > 리버싱핵심원리' 카테고리의 다른 글
API 후킹 Tech Map (0) | 2023.03.25 |
---|---|
Code Injection - using assembly (0) | 2023.03.24 |
PE 패치를 이용한 DLL Injection (0) | 2023.03.20 |
EjectDll.exe 구현 (0) | 2023.03.20 |
SetWindowsHookEx()를 이용한 DLL Injection (0) | 2023.03.19 |