security/리버싱핵심원리

Code Injection

민사민서 2023. 3. 23. 15:54

Code Injection (Thread Injection)

- 우리가 원격에서 실행하고픈 코드가 있을 때, 이를 DLL 파일 형태로 만든 후 CreateRemoteThread() 인자로 LoadLibraryA() API 주소와 dll name 주소를 건네주어 DLL을 inject하고 DllMain에서 코드를 실행시킴

MsgBox.dll (DLL Injection)

    => 흔적이 많이 남음(새로운 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)&param, 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> 를 실행하면 해당 스레드 코드 시작 위치에서 멈춘다

THREAD_PARAM 구조체

 

추가 메모

 - 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