Code Injection 실습 예제인 CodeInjection.exe 의 소스 코드(CodeInjection.cpp)를 살펴보도록 하겠습니다.
이전 강좌의 내용은 아래 링크를 참조하시기 바랍니다.
CodeInjection.cpp
* 참고!
CodeInjection.cpp 는 Visual C++ 2008 Express Edition 으로 개발되었으며 Windows 7 32bit 환경에서 테스트 되었습니다. 또한 Visual C++ 의 코드 최적화 기능을 사용하지 않고 빌드 하였습니다. (/Od)
아래 소개되는 코드들은 설명의 편의를 위하여 에러 처리 부분을 생략하였습니다. 완전한 코드는 첨부된 CodeInjection.cpp 파일을 참고하시기 바랍니다.
main()
먼저 main() 함수를 살펴 보겠습니다.
int main(int argc, char *argv[])
{
DWORD dwPID = 0;
if( argc != 2 )
{
printf("\n USAGE : %s <pid>\n", argv[0]);
return 1;
}
// code injection
dwPID = (DWORD)atol(argv[1]);
InjectCode(dwPID);
return 0;
}
<코드 1 – main() 함수>
main() 함수의 역할은 InjectCode() 함수를 호출하는 것입니다. 이때 함수 파라미터로 상대방 프로세스의 PID 값을 넘겨줍니다.
ThreadProc()
이제 상대방 프로세스에 인젝션 시킬 코드(스레드 함수)를 살펴보겠습니다.
// Thread Parameter
typedef struct _THREAD_PARAM
{
FARPROC pFunc[2]; // LoadLibraryA(), GetProcAddress()
char szBuf[4][128]; // "user32.dll", "MessageBoxA",
// "www.reversecore.com", "ReverseCore"
} THREAD_PARAM, *PTHREAD_PARAM;
// LoadLibraryA()
typedef HMODULE (WINAPI *PFLOADLIBRARYA)
(
LPCSTR lpLibFileName
);
// GetProcAddress()
typedef FARPROC (WINAPI *PFGETPROCADDRESS)
(
HMODULE hModule,
LPCSTR lpProcName
);
// MessageBoxA()
typedef int (WINAPI *PFMESSAGEBOXA)
(
HWND hWnd,
LPCSTR lpText,
LPCSTR lpCaption,
UINT uType
);
// Thread Procedure
DWORD WINAPI ThreadProc(LPVOID lParam)
{
PTHREAD_PARAM pParam = (PTHREAD_PARAM)lParam;
HMODULE hMod = NULL;
FARPROC pFunc = NULL;
// LoadLibrary(“user32.dll”)
// pParam->pFunc[0] = kernel32!LoadLibraryA()
// pParam->szBuf[0] = “user32.dll”
hMod = ((PFLOADLIBRARYA)pParam->pFunc[0])(pParam->szBuf[0]);
// GetProcAddress(“MessageBoxA”)
// pParam->pFunc[1] = kernel32!GetProcAddress()
// pParam->szBuf[1] = “MessageBoxA”
pFunc = (FARPROC)((PFGETPROCADDRESS)pParam->pFunc[1])(hMod, pParam->szBuf[1]);
// MessageBoxA(NULL, “www.reversecore.com”, “ReverseCore”, MB_OK)
// pParam->pFunc[1] = kernel32!GetProcAddress()
// pParam->szBuf[1] = “MessageBoxA”
((PFMESSAGEBOXA)pFunc)(NULL, pParam->szBuf[2], pParam->szBuf[3], MB_OK);
return 0;
}
<코드 2 – ThreadProc() 함수>
위 코드에서 실제로 인젝션 되는 부분은 ThreadProc() 함수입니다. 그 위의 typedef 문은 C 언어 문법을 위한 것이므로 인젝션 시킬 필요가 없습니다.
ThreadProc() 의 코드는 함수 포인터를 많이 사용해서 얼핏 복잡하게 보이지만 사실 내용은 아래와 같이 간단합니다.
hMod = LoadLibraryA(“user32.dll”);
pFunc = GetProcAddress(hMod, “MessageBoxA”);
pFunc(“www.reversecore.com”, “ReverseCore”);
<코드 2> 의 주석을 참조하시면 ThreadProc() 의 코드는 쉽게 이해가 가실 것입니다.
중요한 것은 ThreadProc() 코드의 개념입니다.
Code Injection 기법의 핵심은 독립 실행 코드를 인젝션 시키는 것입니다.
그러기 위해서 코드와 (코드에서 참조하는) 데이터를 같이 인젝션 시키는 것입니다. 그리고 인젝션 시키는 코드에서 역시 인젝션 시킨 데이터를 정확히 참조할 수 있도록 해야 합니다.
위 ThreadProc() 함수를 보시면 직접 API 를 호출 하지 않습니다. 또한 문자열도 직접 정의해서 사용하지 않습니다. 전부 스레드 파라미터로 넘어온 THREAD_PARAM 구조체에서 가져다 사용하고 있습니다.
만약 일반적인 프로그램이라면 ThreadProc() 의 코드는 아래와 같이 간단히 작성할 수 있습니다.
DWORD WINAPI ThreadProc(LPVOID lParam)
{
MessageBoxA(NULL, "www.reversecore.com", "ReverseCore", MB_OK);
return 0;
}
<코드 3 – 일반적인 프로그램에서의 ThreadProc()>
위 <코드 3>을 빌드하여 생성된 파일을 디버거로 보면 아래 그림과 같습니다.
<Fig. 1>
위 그림의 코드(10001000 ~ 10001018 영역)를 다른 프로세스에 그대로 인젝션 시킨다면 정상적으로 실행되지 않습니다. 그 이유는 코드에서 사용되는 10009290, 1000929C, 100080F0 주소의 내용이 상대방 프로세스에는 없기 때문입니다.
따라서 저 주소에 해당하는 문자열과 API 주소를 같이 인젝션 시켜야 합니다. 또한 <Fig. 1>의 코드 역시 그 인젝션된 데이터의 주소를 정확히 참조하도록 프로그래밍 되어야 합니다.
이와 같은 조건을 만족시키기 위해서 <코드 2> 의 ThreadProc() 함수는 THREAD_PARAM 구조체를 이용해서 2 개의 API 주소와 4 개의 문자열 데이터를 받아들입니다. 2 개의 API 는 바로 “LoadLibraryA()” 와 “GetProcAddress()” 입니다. 이 2 개의 API 만 있으면 모든 라이브러리의 함수를 호출 할 수 있습니다.
* 참고 사항
1. 위 실습 예제의 경우에 LoadLibraryA() 와 GetProcAddress() 의 주소 말고 MessageBoxA() 의 주소를 직접 전달하여 사용해도 됩니다. 하지만 정석은 LoadLibraryA() 와 GetProcAddress() 만을 전달한 후 이를 이용해서 필요한 DLL 을 로딩시켜 원하는 함수 주소를 직접 구하는 것입니다. 이 방식의 장점은 해당 라이브러리를 프로세스에 정확히 로딩시킨다는 것입니다. 가령 notepad.exe 프로세스에 윈도우 소켓 API 인 ws2_32!connect() 의 주소를 넘겨주면 에러가 발생할 것입니다. (notepad.exe 에 기본적으로 ws2_32.dll 이 로딩되지 않았으니까요.)
2. 대부분의 유저 모드 프로세스는 kernel32.dll 을 로딩하므로 LoadLibraryA(), GetProcAddress() 의 주소를 직접 넘기는 것은 크게 무리가 없습니다. 단, kernel32.dll 을 로딩하지 않는 시스템 프로세스(예: smss.exe)도 있으니 사전에 꼭 확인하시기 바랍니다.
3. Kernel32.dll 같은 시스템 라이브러리는 OS 가 부팅되어 있는 상태에서는 모든 프로세스에서 동일한 주소에 로딩되어 있습니다. OS 버전이 틀리거나 재부팅(Vista, 7 의 경우)을 하거나 하면 같은 모듈이라도 로딩 주소는 틀려집니다.
위 <코드 2> 의 내용을 디버거로 살펴보면 아래 그림과 같습니다.
<Fig. 2>
위의 <Fig. 2> 의 코드를 보시면 모든 중요한 데이터는 스레드 파라미터인 pParam 으로 받아서 사용하는 것을 알 수 있습니다. 즉, <Fig. 2> 의 ThreadProc() 함수는 독립 실행 코드라고 말 할 수 있습니다. 위의 <Fig. 2> 와 앞에서 소개한 <Fig. 1> 를 비교해 보시면 그 차이점을 확인하실 수 있습니다.
* 참고!
Visual C++ 2008 Express Edition 에서 프로젝트의 [Release/Debug] 모드와 [최적화] 옵션에 따라서 CodeInjection.cpp 파일은 <Fig. 2> 와는 다른 형태로 빌드 될 수 있습니다. 위 실습 예제는 Release 모드에서 최적화 옵션 사용 안함(/Od)으로 빌드 하였습니다.
InjectCode()
아래는 Code Injection 기법의 핵심인 InjectCode() 함수입니다.
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], "user32.dll");
strcpy_s(param.szBuf[1], "MessageBoxA");
strcpy_s(param.szBuf[2], "www.reversecore.com");
strcpy_s(param.szBuf[3], "ReverseCore");
// Open Process
hProcess = OpenProcess(PROCESS_ALL_ACCESS, // dwDesiredAccess
FALSE, // bInheritHandle
dwPID); // dwProcessId
// Allocation for THREAD_PARAM
dwSize = sizeof(THREAD_PARAM);
pRemoteBuf[0] = VirtualAllocEx(hProcess, // hProcess
NULL, // lpAddress
dwSize, // dwSize
MEM_COMMIT, // flAllocationType
PAGE_READWRITE); // flProtect
WriteProcessMemory(hProcess, // hProcess
pRemoteBuf[0], // lpBaseAddress
(LPVOID)¶m, // lpBuffer
dwSize, // nSize
NULL); // [out] lpNumberOfBytesWritten
// Allocation for ThreadProc()
dwSize = (DWORD)InjectCode - (DWORD)ThreadProc;
pRemoteBuf[1] = VirtualAllocEx(hProcess, // hProcess
NULL, // lpAddress
dwSize, // dwSize
MEM_COMMIT, // flAllocationType
PAGE_EXECUTE_READWRITE); // flProtect
WriteProcessMemory(hProcess, // hProcess
pRemoteBuf[1], // lpBaseAddress
(LPVOID)ThreadProc, // lpBuffer
dwSize, // nSize
NULL); // [out] lpNumberOfBytesWritten
hThread = CreateRemoteThread(hProcess, // hProcess
NULL, // lpThreadAttributes
0, // dwStackSize
(LPTHREAD_START_ROUTINE)pRemoteBuf[1],
pRemoteBuf[0], // lpParameter
0, // dwCreationFlags
NULL); // lpThreadId
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
CloseHandle(hProcess);
return TRUE;
}
<코드 4 - InjectCode() 함수>
위 코드는 DLL Injection 코드와 매우 유사합니다.
InjectCode() 함수의 앞 부분은 THREAD_PARAM 구조체 변수를 세팅하고 있습니다. 이 값들은 상대방 프로세스에 인젝션 되어 ThreadProc() 스레드 함수에 파라미터로 전달될 것입니다.
* 참고!
Windows 7 에서 모든 프로세스에 로딩된 kernel32.dll 의 주소가 동일하므로 CodeInjection.exe 프로세스에서 구한 API(“LoadLibraryA”, “GetProcAddress”) 주소와 notepad.exe 프로세스에서 구한 API(“LoadLibraryA”, “GetProcAddress”) 주소가 서로 동일하다는 것을 기억하시기 바랍니다.
그리고 API 함수 호출이 이어지는데요, 핵심 API 함수들의 호출 흐름만 살펴보면 아래와 같습니다.
OpenProcess()
// data : THREAD_PARAM
VirtualAllocEx()
WriteProcessMemory()
// code : ThreadProc()
VirtualAllocEx()
WriteProcessMemory()
CreateRemoteThread()
위 코드의 핵심은 상대방 프로세스에 data 와 code 를 각각 메모리 할당하고 인젝션 시켜 준다는 것입니다.
마지막으로 CreateRemoteThread() API 를 이용해서 원격 스레드를 실행시킵니다.
+---+
이로써 Code Injection 기법을 이용한 실습 예제 소스코드에 대한 설명을 마치도록 하겠습니다.
설명의 편의성을 위하여 매우 기초적인 실습 예제를 소개하였습니다. Code Injection 의 개념을 이해하시는데 어려움이 없을 거라 생각됩니다. 위 개념을 이해하셨다면 다양한 아이디어로 자신만의 Code Injection 기법을 연습해 보시기 바랍니다
* 참고!
제가 Code Injection 기법을 구현할 때 인젝션 시킬 코드 부분은 어셈블리 언어로 프로그래밍 합니다. 복잡한 것은 MASM 을 사용하고, 간단한 것은 OllyDbg 의 “Assemble” 명령을 사용합니다. (단축키 [Space]) 이렇게 만들어진 Hex 코드 버퍼를 위 InjectCode() 함수 내에서 상대방 프로세스에 인젝션 시켜줍니다. 이러한 방법은 좀 더 직관적인 인젝션 코드를 만드는데 도움이 됩니다. Code Injection 마지막 강좌에서 간단히 실습해보도록 하겠습니다.
다음 강좌에서는 Code Injection 기법을 디버깅 하는 방법에 대해서 알아보도록 하겠습니다.
ReverseCore
위 글이 도움이 되셨다면 추천(VIEW ON) 부탁 드려요~
'study' 카테고리의 다른 글
| 어셈블리 언어를 이용한 Code Injection (4) (0) | 2010/07/04 |
|---|---|
| 어셈블리 언어를 이용한 Code Injection (3) (2) | 2010/06/30 |
| 어셈블리 언어를 이용한 Code Injection (2) (2) | 2010/06/28 |
| 어셈블리 언어를 이용한 Code Injection (1) (0) | 2010/06/27 |
| Code Injection 기법 (3) (7) | 2010/06/24 |
| Code Injection 기법 (2) (14) | 2010/06/23 |
| Code Injection 기법 (1) (8) | 2010/06/22 |
| Advanced Global API Hooking – IE 접속 제어 (4) (27) | 2010/05/17 |
| Advanced Global API Hooking – IE 접속 제어 (3) (10) | 2010/05/07 |
| Advanced Global API Hooking – IE 접속 제어 (2) (10) | 2010/04/25 |
| Advanced Global API Hooking – IE 접속 제어 (1) (21) | 2010/03/29 |
API,
API Hooking,
Code Injection,
CreateRemoteThread,
GetProcAddress,
GetThreadContext,
it,
LoadLibrary,
MessageBox,
OpenProcess,
Reverse Code Engineering,
Reverse Engineering,
ReverseCore,
Reversing,
SetThreadContext,
Thread Injection,
VirtualAllocEx,
WriteProcessMemory,
리버스 엔지니어링,
리버싱,
소프트웨어 역공학,
후킹
CodeInjection.zip
