(블로그 방문자가 일 200명을 돌파한 기념으로... 이제부터 글쓸때 존대를 하기로 했습니다. ^^)
User32.dll은 프로세스에 로딩될 때 DllMain()에서 LoadAppInit_DLLs 레지스트리의 값을 체크한 뒤 만약 1이 설정되어 있다면 AppInit_DLLs 레지스트리에 적혀있는 DLL들(Space로 구분)을 로드합니다. 따라서, LoadAppInit_DLLs 레지스트리의 값을 1로 세팅한 뒤 AppInit_DLLs 레지스트리에 Injection하고자 하는 DLL을 적어놓으면 해당 DLL은 Windows에서 실행되는 모든 프로세스(정확히 말하면 User32.dll을 로드하는 모든 프로세스)에 Injection됩니다. DllMain에서부터 실행시킬 코드를 시작시키면 되겠습니다.
Microsoft에서 공식적으로 제공하는 Injection방법(?)이면서도... 이 방식의 Injection은 의외로 막기가 어렵다고 하네요...
DLL을 Injection했으면 다음 단계는 API Hooking이겠죠?
User Mode에서 API를 Hooking하기 위해서는 어떤 식이든 DLL을 Injection해야 합니다. 실행시키고자 하는 코드를 Target Process와 같은 공간에 집어넣지 않으면 Target Process가 실행시킬 수 없기 때문이죠. 시스템 프로그래머의 바이블이라 할 수 있는 Jeffrey Ritcher의 "Programming Application for Microsoft Windows 4th Edition" 22장을 보면 여러가지 DLL Injection하는 방법이 소개되어 있습니다.
1. Registry를 이용하는 방법 :
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows\LoadAppInit_DLLs
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows\AppInit_DLLs
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows\AppInit_DLLs
User32.dll은 프로세스에 로딩될 때 DllMain()에서 LoadAppInit_DLLs 레지스트리의 값을 체크한 뒤 만약 1이 설정되어 있다면 AppInit_DLLs 레지스트리에 적혀있는 DLL들(Space로 구분)을 로드합니다. 따라서, LoadAppInit_DLLs 레지스트리의 값을 1로 세팅한 뒤 AppInit_DLLs 레지스트리에 Injection하고자 하는 DLL을 적어놓으면 해당 DLL은 Windows에서 실행되는 모든 프로세스(정확히 말하면 User32.dll을 로드하는 모든 프로세스)에 Injection됩니다. DllMain에서부터 실행시킬 코드를 시작시키면 되겠습니다.
Injection할 Target 프로세스를 구분할 수 없다는 것과, 레지스트리를 삭제하더라도 시스템을 리부팅할때까지는 Inject된 DLL이 Eject되지 않는다는 등.. 불편한 점이 여러가지가 있고, 레지스트리에 심어놓기 때문에 티가 팍팍 난다는 등등... 여러가지 한계가 있죠.
요즘에도 이런 원시적으로 보이는 기술을 사용하는 곳이 있을까요? 네~ 있습니다. ^^
내 컴의 RegEdit를 열어서 저 위치를 찾아봤더니...
어엇! 벌써 뭔가 DLL이 등록되어 있군요? 무엇일까요? 네~ 바로 구글 데스크탑! 되겠습니다.
구글데스크탑의 DLL이 저 위치에 등록되어 있다는 걸 보면.. 구글데스크탑이 HDD의 파일 변경을 탐지하기 위해 API Hooking을 하는게 아닐까 하는 조심스러운 예상을 해 봅니다.
2. Windows MessageHook을 사용하는 방법
MessageHook을 하기 위해 Windows에서 제공하는 API가 있습니다. 보통 키로거나 CBT프로그램(토익볼때 사용하는...^^) 같은 프로그램들이 잘 쓰는 API 지요. 제푸리 씨는 다른 프로세스에 대해 Window Subclassing(윈도우 프로시져 재정의)을 하는 방법으로 소개하고 있습니다.
이 함수로 Hook을 Install하려면 후킹된 메시지를 핸들링할 메시지 핸들러를 지정해야 하는데, 이 핸들러는 세번째 인자인 핸들로 지정된 DLL에 들어있어야 합니다. 그리고서 이 API로 MessageHook을 걸면 세번째 인자의 DLL이 Target Process에 Injection됩니다. 그래야 Target 프로세스가 실행할 수 있기 때문이죠. Hook을 거는 프로세스에 있는 함수를 건네 봤자 Target Process에서는 보이지도 않으니.. ^^
그런데 일단 DLL이 Load되면, 지정된 메시지 프로시져 뿐만 아니라 해당 DLL에 들어있는 다른 함수을 포함한 DLL 전체가 Target Process에 Load되게 됩니다. 따라서 이 API는 Message Hook 외에도 DLL Injection을 하기 위한 용도로도 사용될 수 있습니다.
Microsoft에서 공식적으로 제공하는 Injection방법(?)이면서도... 이 방식의 Injection은 의외로 막기가 어렵다고 하네요...
http://blog.naver.com/wz1054.do?Redirect=Log&logNo=30028299734결정적인 단점이라면... Window가 없는 프로세스는 Hook할수 없다는거.
3. CreateRemoteThread 를 사용하는 방법
CreateRemoteThread 라는 API가 있습니다. 다른 프로세스에서 실행되는 Thread를 생성하는 방법인데요... 개인적으로는 도대체 왜 이런 API를 만들어놓았는지 이해가 되지 않습니다만... 어쨌든 가장 쉽게 DLL을 Injection할 수 있고, 이미 실행된 Process에 대해서도 Injection이 가능할 뿐만 아니라 당한 쪽에서 탐지하기도 쉽지 않기 때문에 가장 많이 쓰이는 방법입니다.
HANDLE WINAPI CreateRemoteThread( __in HANDLE hProcess, __in LPSECURITY_ATTRIBUTES lpThreadAttributes, __in SIZE_T dwStackSize, __in LPTHREAD_START_ROUTINE lpStartAddress, __in LPVOID lpParameter, __in DWORD dwCreationFlags, __out LPDWORD lpThreadId );
이 방법을 이용해 DLL을 Injection하는 방법의 핵심을 한마디로 요약하면 "CreateRemoteThread API를 이용하여 TargetProcess에서 LoadLibrary 를 실행시키는 것"입니다. 순서를 대략 요약하면 다음과 같습니다.
1. OpenProcess API를 사용해 Target Process의 핸들을 얻는다. 이때 CreateRemoteThread를 실행하기 위한 PROCESS_CREATE_THREAD 권한과 VirtualAllocEx를 위한 PROCESS_VM_OPERATION, 그리고 WriteProcessMemory를 실행하기 위한 PROCESS_VM_WRITE 권한을 지정하여 Open해야 한다.
2. VirtualAllocEx API를 사용해 Target Process에 DLL Name 혹은 Path를 기록할 메모리를 할당한다.
3. 위에서 할당한 Target Process의 메모리에 WriteProcessMemory API를 사용하여 DLL Name 혹은 Path 를 Write한다.
4. GetProcAddress 함수를 사용하여 LoadLibrary 함수의 주소를 확보한다.
5. CreateRemoteThread를 호출한다. 이때 1에서 할당한 핸들을 hProcess로, 2에서 할당한 주소를 lpParameter로, 4에서 확인한 LoadLibrary API의 주소를 lpStartAddress (Thread Entry Point)로 사용한다. 이때 Target Process에서 Kernel32.dll 이 로딩된 주소가 CreateRemoteThread를 실행시키는 프로세스의 Kernel32.dll과 동일하다고 가정한다. (Kernel32.dll은 가장 기본적인 dll이고 모든 프로세스에서 초기에 로딩되므로... 이 가정은 대부분 옳다.)
CreateRemoteThread API를 이용한 DLL Injection 막는 방법도 나와 있긴 합니다. ^^
CreateRemoteThread API를 이용해 Dll Injector를 만들어 보았습니다.
※ 이 프로그램은 MFC를 사용하여 VS2005에서 빌드되었습니다. 무슨말이냐 하며는... Side-by-Side 문제로 실행이 안될수도 있다는 뜻이죠. vcredist_x86.exe를 다운받아 실행해주시면 됩니다.
※ 뭐 이런 말 굳이 안써도 다 아시겠지만... 테스트용으로만 사용하시기 바라구요 어떠한 책임도 지지 않습니다.
코드가 궁금하신 분은... 제푸리 책 22장을 공부하세요. ^^
리버싱에 사용될 수 있는 프로그램을 배포하는 일은 삼가해야겠지만... 이정도야 저 유명한 책에 다 나와있는 수준이니... 별 문제 안되리라 봅니다.
4. DLL을 바꿔치거나 Path를 가로채는 방법
이 방법은 별게 아니구요... Target Process가 Attach하는 DLL을 확인해서 그중에 하나를 다른이름으로 백업 받아놓고, Injection할 DLL을 원 DLL의 이름으로 그 위치에 갖다놓는 방법입니다. 이때 백업받은 DLL에서 Export한 함수들을 Dependency Walker같은 걸로 확인해서... Injection한 DLL에 "Function Forwarder" 선언을 해주어 EXE가 정상적으로 실행되도록 해주는 것이 포인트입니다.
제푸리 아저씨는 이 방법을 "Injecting a DLL with a Trojan DLL"이라고 표현하고 있습니다. 적절한 표현인 것 같구요... 개인적으로는 가장 기발하다고 생각한 방법입니다. ^^
5. Target Process를 Debugging 모드로 실행시키는 방법
Debugging 모드로 다른 프로세스를 실행시키게 되면, Debuggee Process가 실제로 실행되기 전에 Debugger Process에게 제어권이 넘어오게 됩니다. Debuggee를 Hold 시켜놓은 상태에서 Entry Point 부근의 OP 코드를 백업받고, 내가 실행시킬 코드(아마도 LoadLibrary겠죠?)를 WriteProcessMemory하여 실행한 후, 다시 백업받은 OP코드를 실행시키고 나서 원래 흐름으로 돌려놓는 방법입니다.
몇년 전에 센세이션을 일으켰던, "해킹, 파괴의 광학" 이라는 책을 보면 이 방법이 자세하게 나와 있습니다.
6. CreateProcess를 이용하는 방법
CreateProcess API를 실행시킬 때 6번째 인자인 dwCreationFlags 에 "CREATE_SUSPENDED" 를 주게 되면 ChildProcess가 생성된 후 실행에 들어가기 전에 Suspend시킬 수 있습니다. 이 상태에서 위의 5번과 동일한 작업을 해주는 거죠. 내가 실행시킨 자식 프로세스에 대해서만 써먹을 수 있다는 단점이 있습니다.
음.. 참 여러가지 방법이 있지요? ^^
요즘에는 프로세스 하나 띄우는데 저절로 달라붙는 놈(?)들이 어찌나 많은지...
코딩을 할 때도 누가 내 코드를 보고 있다는 생각을 해야 할 정도입니다.
다음번에 시간날 때 API Hooking에 대해 한번 써보겠습니다. ^^
'C++' 카테고리의 다른 글
멀티스레드 프로그래밍(Multithread Programming)에 관한 고찰 (2) (10) | 2009.04.11 |
---|---|
멀티스레드 프로그래밍(Multithread Programming)에 관한 고찰 (1) (4) | 2009.03.24 |
UTF-8 String을 유니코드(UCS-16)로 전환하기 (0) | 2009.03.02 |
포인터 인자 검증하기 (Pointer Validation) (8) | 2009.03.02 |
CriticalSection에 관한 알쏭달쏭 퀴즈! (4) | 2009.02.05 |