dll 인젝션은 내가 원하는 타깃 프로세스에 특정한 DLL 파일을 강제로 삽입해서 실행시키는 것이다.
기술적으로 설명하면 이런 말이다.
타깃 프로세스가 LoadLibrary( ) API를 스스로 호출하도록 명령한다. dll 인젝션의 경우, 삽입된 dll은 타깃 프로세스의 메모리에 대한 접근 권한이 있다는 특성을 잘 기억하고 있으면 좋다.
DLL 인젝션을 공부할 때 미리 알아둬야 하는 개념이 있다.
프로세스에 DLL이 로드되면은 자동으로 DllMain( )이라는 함수가 실행된다.
DllMain()에는 그리고 dll이 프로세스에 로드될 때, 로드가 해제될 때 등등의 경우마다 특정한 동작을 실행하게 된다. 여기다가 원하는 코드를 추가할 수도 있고.생각해보자. 우리가 지금 하는 dll 인젝션은 프로세스에 dll이 로드되는 거니까 PROCESS_ATTACH의 경우이다.
DLL 인젝션의 순기능은 기본적으로 기능을 개선하거나 버그를 패치할 때 쓰는 것이다. 소스코드를 직접 수정하는게 힘들 경우엔 DLL 인젝션으로 문제가 생긴 코드나 데이터를 수정하면 되겠지. 아니면 완전 새로운 기능을 넣어주는 Plug-In도 가능하겠지.
운영체제 차원에서 메시지 후킹하는 것도 있겠어.
뭐 자기관리 차원에서 스마트폰 화면 차단 같은 것도 있을 것이고 게임이나 주식 거래 프로그램 등에서 실행 차단, 유해사이트 차단 같은 것도 dll 인젝션이다.
DLL 인젝션을 하는 방법은 크게 세 갈래이다.
- Create Remote Thread API (원격스레드생성)
- AppInit_DLLs (레지스트리 이용)
- Set Windows Hook Ex API (메시지후킹)
하나씩 해볼거야.
우선 한번 그냥 어떻게 되는 건지 한번 보자.
먼저 notepad의 PID를 알아내자. process explorer쓰던지 뭘 쓰던지 자유.
19756이래.
그 다음엔 Debug View라는 툴을 써야해. 시스템에서 실행되는 프로세스들이 디버그 문자들을 막 쏟아내고 있을텐데 그걸 모두 가로채서 보여주는 툴이야.
다운받아서 실행할게. 뭐 이런거 나와. 이건 캡쳐도구를 펼치니까 바로 생긴 메시지야.
인젝션 프로그램을 실행시킬거야.
InjectDll.exe를 이용해서 노트패드에 myhack.dll을 넣을거야.
개빡치네;;
*cmd를 관리자 권한으로 실행해야 합니다 여러분.
모르겠으면 cmd를 ctrl+Shift+Enter로 실행하세요
그럼 이렇게 나옵니다.
DebugView에도 이렇게 잘 나옵니다.
이 메시지가 뭐냐면
- InjectDll.exe가 노트패드에 myhack.dll 삽입
- 노트패드에서 DllMain( )함수 실행
- DllMain( )함수의 OutputDebugString( ) API가 실행된다.
그래서 dll 문자열이 나온거야.
process explorer에도 로드된 myhack.dll이 보인다.
자, myhack.dll의 기능은 naver사이트에서 index.html을 다운받는거다. 잘 들어가있고 한번 브라우저로 들어가보면 요런 어설픈 네이버가 나온다. 성공
낱낱이 뜯어보자.
먼저 myhack.dll을 먼저 뜯어보자. cpp로 작성되었다.
뭐라는거야 ㅡㅡ
핵심은 바로 두번째 밑에 있는 DllMain을 보는거다.
.
Dll이 로드된다 = DLL_PROCESS_ATTACH였지.
그러면 OutputDebugString으로 "myhack.dll injection!!"이걸 출력해.
그 다음 ThreadProc이라는 스레드를 실행하라고 나오네.
ThreadProc은 위에 정의되어있다
URLDownloadToFile : url에 가서 file을 다운받으라고 하네.
이제 myhack.dll을 넣어주는 injector가 되어주는 InjectDll.exe를 보자.
앞뒤로 긴 내용들이 있지만 중요한건 눈에 보이는 이 부분이다.
핵심은 결국 타깃 프로세스인 notepad.exe가 스스로 LoadLibrary("myhack.dll") API를 호출하게 만드는 것
단계별로 보자구
hProcess = OpenProcess(이러한 권한으로, FALSE, 얘를 가져와)
이런 구조이다.
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)
PROCESS_ALL_ACCESS 권한의 notepad.exe 프로세스의 dwPID를 구한다. 이렇게 프로세스 핸들을 얻어내면 notepad.exe를 제어할 수 있다.
근데 내가 궁금한건 여기서 대체 뭘로 notepad.exe라는걸 알아내는거야? 다른 프로세스들도 여러개 실행되고 있을텐데
pRemoteBuf = VirtualAllocEx(hProcess, NULL, dwBufSize, MEM_COMMIT, PAGE_READWRITE);
노트패드한테 dll파일 문자열을 알려줘야 할거 아냐. 그리고 노트패드의 메모리엔 그 문자열을 적어놓을 공간도 있어야 할 것이고.
그래서 Virtual Alloc Ex라는 API로 DLL 파일 경로 문자열 길이(myhack.dll이겠지, 근데 terminating Null도 포함해)만큼의 메모리 버퍼를 할당해준다.
이 pRemoteBuf의 리턴값은 그 할당해준 버퍼의 주소야.
노트패드의 메모리 주소겠지? 항상 타깃의 메모리주소라는 걸 잊지말고.
WriteProcessMemory(타깃 프로세스, 타깃 버퍼가 될 메모리 주소, (LPVOID)szDllPath, dwBufSize, NULL);
요런 구조야.
WriteProcessMemory(hProcess, pRemoteBuf, (LPVOID)szDllPath, dwBufSize, NULL);
노트패드가 LoadLibrary( ) API를 호출하는 주소를 알아야해. 근데 윈도우 운영체제에선 사실 굳이 노트패드에서 이걸 알아내지 않아도 돼. 일단 코드 봐봐
hMod = GetModuleHandle(L"kernel32.dll");
pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hMod, "LoadLibraryW");
지금 hMod는 노트패드가 아니라 그냥 자기가 적혀있는 InjectDll.exe 프로세스에 로드된 kernel32.dll에서 LoadLibrary( ) api의 시작주소를 구하고 있지
(정확히는 LoadLibraryW( ), W가 왜 붙냐면 이건 LoadLibrary( )의 유니코드 문자열 버전이야)
자 이렇게 하는 이유는 노트패드나, injectdll.exe나 kernel32.dll의 주소가 동일하기 때문이다.
이건 윈도우 운영체제의 특징이자 보안 취약점이긴 한데 핵심적인 dll들은 자시난의 고유한 주소에 로드되는 것이 보장된다. 뭐 전에 배운 ASLR(Address Space Layout Randomization)이런 걸 크게 생각할 필요 없다.
윈도우에선 여러 프로세스가 있어도 프로세스마다 kernel32.dl은 같은 주소에 로드된다. 왜냐면 이미 ImageBase값을 지정해줬거든.
난 다 와놓고 여기가 어렵더라.
hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, pRemoteBuf, 0, NULL);
hThread = CreateRemoteThread(타깃프로세스, NULL, 0, 스레드 함수 주소, 스레드 함수의 파라미터 주소, 0, NULL);
이런 구조다. 스레드 함수 주소랑 스레드 파라미터 주소 모두 InjectDll.exe의 메모리가 아니라 타깃인 notepad의 메모리주소인거 기억하고 있지?
자 그럼 저걸 실제 실행하게 되면 어떻게 되냐면
hThread
= CreateRemoteThread(메모장 dwPID, NULL, 0, LoadLibraryW 주소, LoadLibraryW에 넣을 myhack.dll 문자열 주소, 0, NULL);
요런 느낌이야.
설명을 길게 해야한다. 우선 CreateRemoteThread를 좀 볼게.
원래 하고싶은건 notepad.exe가 자신의 LoadLibraryW( ) API를 호출하게 명령하고 싶은거야. 하지만 윈도우에 그런 명령을 해주는 건 없어.
그래서 DLL인젝션에선 CreateRemoteThread라는 api를 쓴대.
이건 다른 프로세스에게 스레드를 실행시켜주는 함수야. 이게 난 처음에 들었을 때 별 생각 없었는데 저 굵은 볼드체가 어색해야 해.
생각해봐.
다른 프로세스의 DLL 인젝션해주기 = 다른 프로세스에 스레드 실행시키기 ????
뭔가 동치의 느낌은 아니라는 것이다. 다른 스레드 함수가 힌트가 된다고 하네.
스레드 함수 ThreadProc이나 LoadLibrary 같은 API를 볼까.
DWORD WINAPI ThreadProc(
_in LPVOID IpParameter
);
HMODULE WINAPI Loadl_ibrary(
__in LPCTSTR IpFileName
);
둘 다 꼴이 같다. 4바이트 파라미터 입력 받고, 4바이트 리턴하고.
근데 이게 CreateRemoteThread를 써야한다는 거랑 뭔 상관이야,,,,?
알면 좀 알려주셈.
한번 디버깅해보자. 이번에도 지난번처럼 Break on user dll 해두면 돼.
뭐가 맞을지 몰라서 사용자 DLL로드, 시스템 DLL 로드 둘 다 브레이크 걸어뒀어.
그럼 이제 실행할 때 새 dll로드될때마다 멈추겠지. myhack.dll 될 때까지 해보자.
왜 안되냐
또 하나의 방법은 Registry를 이용하는 것이다. 윈도우 운영체제에선 기본적으로 AppInit_DLLs와 LoadAppInit_DLLs라는 레지스트리 항목이 있다.
AppInit_DLLs 항목에 인젝션 하고싶은 DLL 이름 문자열 경로를 쓰고 LoadAppInit_DLLs 항목의 값을 1로 변경하고서 재부팅해주면 모든 프로세스에 해당 dll을 인젝션해준다. 쉽고 강하다.
정확한 원리는 어떤 프로세스가 실행될 때 User32.dll이 로드된다.
그럼 AppInit_DLLs 항목을 읽는다. 그 값이 존재한다고 나오면 LoadLibrary( ) API로 사용자 dll을 로드한다.
그래서 user32.dll을 로드해야만 인젝션되긴 한다. 예컨대 windows XP에선 LoadAppInit_DLLs 항목이 무시되거덩
우선 여기서 인젝션할 dll은 myhack2.dll이다. 내용은 그냥 나를 로딩한 프로세스가 notepad면 Internet Explorer를 숨김 모드로 실행시켜서 네이버에 접속하란 것이다.
레지스트리 편집기 툴이 필요하다. 암거나 상관없지만 regedit.exe면 되지 않을까. 그리고 이 경로로 이동해라
HKEY_LOCAI__MACHINE\SOFTWARE\Microsoft\Windows NT\Cu rrentVersion\Windows
그리고 AppInit_Dlls 항목에 myhack2.dll이 들어있는 전체 경로 이름을 넣어준다.
그리고 LoadAppInit_Dlls 항목은 1로 바꿔준다.
시스템을 재부팅해주고 Process Explorer 실행해서 보면 모든 프로세스에 myhack2.dll이 들어있다. 물론 user32.dll을 로딩한 경우에 한해서다.
이제 notepad를 실행하면 IE가 숨김 속성으로 실행된다.
자 이제 생각해보면 얘가 얼마나 강력한 기능인지 느껴진다. 그럼 만약 버그가 있는 dll을 인젝션하면 윈도우 재부팅 과정에서 부팅 자체가 안되는 경우도 만들 수 있다.
마지막 세번째 방법은 메시지 후킹을 이용한 것이다. SetWindowsHookEx( ) API를 이용하면 어떻게 됐어.
os에서 hook procedure를 담은 프로세스한테 강제로 인젝션했자나.
까먹었으면 지난 포스트 다시 봐.
dll을 넣었으면 뺄 줄도 알아야지.
DLL을 삽입할 땐 타깃 프로세스가 LoadLibrary( )를 호출하게 했다. 그럼 이젝션할 땐 타깃 프로세스가 FreeLibrary(타깃 dll)을 실행하게 하면 된다.
그걸 해주는게 CreateRemoteThread()였으니까 이번에도 그걸 이용하자.
즉 첫번째 인자인 hProcess가 타깃 프로세스
네번째 인자인 lpStartAddress가 FreeLibrary( ),
다섯번째 인자인 lpParameter가 이젝션할 dll 핸들이면 되겠네.
** 사실 윈도우엔 참조 카운트가 있어서 같은 dll이 n번 호출됐으면 freelibrary 이젝션도 n번 해줘야 한다.
EjectDll.cpp 이게 코드가 좀 길다...젤 중요한 EjectDll( ) 함수를 들여보자.
초반에 hSnapshot 부분을 보자.
CreateToolhelp32Snapshot이라는 api를 이용하면 프로세스에 로드되어 있는 DLL의 정보를 구할 수 있다.
그렇게 구한 dll 핸들을 Module32First( )라는 함수에 넘겨준다. 또는 Module32Next( )에 넘겨줘도 되고.
근데 왜 여기선 왜 Process32Next나 Process32First라고 나오는 걸까
암튼 그러면 그 DLL 핸들 정보가 MODULE ENTRY 32라는 구조체에 저장된다. 이 구조체를 간단하게 보면 이런 구조야.
여기서 modBaseAddr이 해당 dll이 로드된 프로세스 가상 메모리 주소고 szModule이 dll 이름을 가진 멤버다.
자 그럼 szModule에 dll 이름이 있으니까 밑에 for 루프를 돌면 내가 이젝션 원하는 dll과 szModule이 같은게 나올때까지 반복한다.
근데 나 이게 이해안돼. 이미 내가 이젝션하고 싶은걸 뽑았다면서 왜 for를 돌지. 아 sz에는 이 프로세스가 가진 모든 dll을 뽑아낸건가.
for 루프가 끝나고 밑에 보면 익숙하죠?
인젝션할 때처럼 이걸로 프로세스 dwPID를 구한다.
이번에도 타깃 프로세스가 아니라 이 EjectDll.exe에 임포트된 kernel32.dll을 통해서 FreeLibrary( )주소를 구하네. 이것 여기 free library 주소는 모든 프로세스마다 동일하지.
이번에는 뭐겠어. pThreadProc은 FreeLibrary( )일거고 modBaseAddr은 이젝션할 dll 로딩 주소이다.
노트패드 PID 23756이다.
인젝션 해주고
잘 됐는지 확인도 했지. 이제 이젝션 할게
process explorer로 보면 dll 목록에 빠져있을거야