2009. 8. 1. 14:18
인터넷을 뒤지다가 같은 제목의 글이 맨 앞에 나오길래 패스했었는데, 읽어보니 내 쪽이 더 간단한거 같아서 글로 남길만한 가치가 있을 거 같아 다시 쓰기로 했다 -_-..

차례
  • 소개
  • 소스코드
  • 설명
  • 참고
소개

어떤 프로그램을 해킹하려 할 때는, 그 프로그램의 메모리에 접근하는 것이 필수적이다. 옛날 DOS와 같은 메모리 보호 기능이 없던 OS에서는 어렵지 않게 해당 프로그램의 메모리에 접근할 수 있었지만, 요즘과 같이 멀티태스킹과 메모리 보호가 기본인 상황에서는 그렇게 접근하는 것이 쉽지 않다. 그래서 자주 이용되는 방법이 디버거를 사용하는 방법이다.

하지만 이 디버거를 사용하는 경우에는 상당히 번거로운 것이 사실이다. 간단한 작업만 하면 되는데 그 커다란 디버거를 불러와야한다거나, 디버깅 API를 사용하려면 여러가지로 준비할 것이 많다거나, 해당 프로그램에서 디버거 디텍션 루틴을 넣어놨다면 또 복잡해지기도 해서 좀 더 간단한 방법을 사용할 수 있다면 쓰는 편이 낫다.

여기서 소개하려는 방법은 CreateRemoteThread()라는 API 함수를 통해 타겟 프로세스에 내가 만들어둔 DLL을 주입시키는 방법이다. DLL은 별도의 프로그램이지만, LoadLibrary 함수 등을 통해서 동적으로 링크되면 해당 프로세스의 주소 공간 내에 매핑되어 마치 처음부터 원래 프로그램과 같이 실행된 것 처럼 메모리 공간을 헤집을 수 있다는 특징이 있다. 매번 디버깅 API를 통해 메모리에 접근하는 것이 아니라, 이러한 부분을 DLL로 만들어서 주입함으로써 더 간단하게 타겟 프로세스를 주무를 수 있다.

소스코드
hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID);
if(hProc == NULL)
{
    MessageBox(hWnd, TEXT("Failed to inject DLL!\nCannot open the process"), NULL, MB_ICONERROR);
    break;
}
pRemoteDll = VirtualAllocEx(hProc, NULL, sizeof(char) * MAX_PATH, MEM_COMMIT, PAGE_READWRITE);
if(!pRemoteDll)
{
    CloseHandle(hProc);
    MessageBox(hWnd, TEXT("Failed to inject DLL!\nCannot allocate memory in target process"), NULL, MB_ICONERROR);
    break;
}
if(!WriteProcessMemory(hProc, pRemoteDll, szDllName, sizeof(szDllName), NULL))
{
    VirtualFreeEx(hProc, pRemoteDll, 0, MEM_RELEASE);
    CloseHandle(hProc);
    MessageBox(hWnd, TEXT("Failed to inject DLL!\nCannot write memory in target process"), NULL, MB_ICONERROR);
    break;
}
hKernel32 = LoadLibrary(TEXT("KERNEL32.DLL"));
pfnLoadLibrary = (LPTHREAD_START_ROUTINE)GetProcAddress(hKernel32, TEXT("LoadLibraryA"));

hThread = CreateRemoteThread(hProc, NULL, 0, pfnLoadLibrary, pRemoteDll, 0, NULL);
if(!hThread)
{
    VirtualFreeEx(hProc, pRemoteDll, 0, MEM_RELEASE);
    CloseHandle(hProc);
    FreeLibrary(hKernel32);
    MessageBox(hWnd, TEXT("Failed to inject DLL!\nCannot create remote thread"), NULL, MB_ICONERROR);
    break;
}
WaitForSingleObject(hThread, INFINITE);

VirtualFreeEx(hProc, pRemoteDll, 0, MEM_RELEASE);
CloseHandle(hProc);
FreeLibrary(hKernel32);
if(GetExitCodeThread(hThread, &dwRet))
{
    TCHAR out[1024];
    _stprintf(out, TEXT("Injection finished.\nExit code: 0x%08lX"), dwRet);
    MessageBox(hWnd, out, TEXT("Finished"), MB_ICONINFORMATION);
}
설명

여기서 사용한 핵심 API는 다음과 같다.
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
);
LPVOID WINAPI VirtualAllocEx(
__in HANDLE hProcess,
__in_opt LPVOID lpAddress,
__in SIZE_T dwSize,
__in DWORD flAllocationType,
__in DWORD flProtect
);
CreateRemoteThread()는, 원래는 “다른 프로세스에 스레드를 생성할 때” 사용하는 함수다. 맨 앞에서 지정해주는 hProcess로 대상 프로세스를 지정해 줄 수 있다. 그런데 여기서, 스레드의 시작 주소를 지정해주는 lpStartAddress를 살펴보면 재밌는 사실을 하나 발견할 수 있다.

LPTHREAD_START_ROUTINE의 정의는 다음과 비슷하다.
DWORD WINAPI ThreadProc(
__in LPVOID lpParameter
);
그리고 DLL를 불러오는 함수인 LoadLibrary의 정의는 다음과 같다.
HMODULE WINAPI LoadLibrary(
__in LPCTSTR lpFileName
);
보다시피, 타입만 약간 다르고 실질적인 함수 호출에 있어서는 두 함수가 서로 똑같다는 것을 알 수 있다. 만약 타겟 프로세스 내의 LoadLibrary() 주소를 알 수 있다면 CreateRemoteThread()에 스레드 시작 주소로 LoadLibrary()를 넘겨주어 어렵지 않게 DLL을 주입할 수 있다는 것이 된다. 그리고 또 하나의 희소식은, Windows는 빠른 동적 링크를 위해서 각 DLL마다 선호하는 Base Address가 기록되어 있으며 충돌하지 않는 한 Base Address는 항상 고정되어 있다는 것이다. 따라서, 제일 먼저 로드되기 마련인 KERNEL32.DLL의 base address는 어떤 프로세스가 됐든 특별한 일 없이는 전부 똑같다는 말이 된다!

그러면 VirtualAllocEx()는 왜 필요할까? 바로 LoadLibrary()의 패러미터를 넘기기 위해 필요하다. LoadLibrary()는 고맙게도 시작 주소가 모든 프로세스마다 동일하지만 패러미터로 넘겨줄 DLL의 이름은 타겟 프로세스 내에서 찾아야 한다. 당연히, 이쪽의 주소를 넘겨주게 되면 해당 프로세스 내에서는 쓰레기 값이 참조되거나, 심지어는 잘못된 메모리 접근이 발생할 수도 있다. 그래서, 패러미터로 넘겨줄 DLL의 path를 넘기기 위해 VirtualAllocEx()로 타겟 프로세스 내에 메모리를 할당하고, 반환된 그 주소를 CreateRemoteThread()의 lpParameter로 넘겨주게 되면 LoadLibrary()의 스레드가 동작하면서 DLL을 주입시켜주게 되는 것이다. 주입과 동시에 실행되어야 할 코드들은 DLL 내의 DllMain() 함수 내에 집어넣으면 수행되게 된다. 구체적인 것은 MSDN의 DllMain() 항목을 참조하는 것이 좋을 것이다.

주의할 점은, 이 방법이 100% 동작하지 않을 수 있다.

참고

  • 해킹, 파괴의 광학 (와이미디어, 김성우 저) - 복잡한 방법밖에 실려있지 않지만, 상당한 참고가 될 것이다.
꽤 예전에 배워서 구현한 파일인데, 그러다보니 참조했던 곳이 다 사라져버렸다 -_-;;
여튼, 나중에 소개할지도 모르는 DirectShow 기반의 간단한 스트리밍 서비스 뚫기 등에서도 API 후킹에 많이 써먹었던 매우 유용한 툴이라, 많은 사람들이 유용하게 쓸 수 있으리라 믿는다.

'크래킹, 크랙미' 카테고리의 다른 글

AD free NateOn  (0) 2010.05.18
COM Object 생성 추적하기, COMTracer  (0) 2009.10.05
DLL injection with CreateRemoteThread()  (0) 2009.08.01
CBsearch Crack  (15) 2009.06.20
abexcrackme2  (0) 2007.02.17