발자취

악성코드 Pre-Lab2. DLL Injection, DLL Ejection 본문

3-2/악성코드

악성코드 Pre-Lab2. DLL Injection, DLL Ejection

해린 2023. 10. 13. 01:12

이번 시간엔 DLL 인젝션과 DLL 이젝션에 관한 실습 3가지를 진행해 볼 예정이다.

(리버싱 핵심원리 23~24장) 

1. DLL 인젝션 1 - CreateRemoteThread(), LoadLibrary() 이용

지난 시간에 다운로드 받았던 소스코드.zip의 코드를 사용할 예정이다 (https://github.com/reversecore/book)
경로는 "소스코드\03_DLL_Injection_23_DLL_Injection\src\InjectDll\InjectDll.cpp"이다
 

1. InjectDll.exe, myhack.dll 파일 생성

지난 실습 때와 동일한 방법이므로 간단하게 적고 넘어갈 것이다!
 

비주얼 스튜디오 > 파일 > 새로 만들기 > 프로젝트 > 'InjectDll' 생성
 

생성된 프로젝트 파일에 소스코드 속 InjectDll.cpp 복사 붙여넣기
주의: ~~\source\repos\InjectDll\InjectDll 폴더!
 

비주얼 스튜디오 솔루션 탐색기 > 소스 파일 > 추가 > 기존 항목 > InjectDll.cpp
 

디버그 > InjectDll 속성 > 문자 집합 '유니코드 문자 집합 사용'으로 변경
 

★중요!
98, 99번 줄을 주석 처리해줘야 한다. SetPrivilege() 함수 부분!
관리자 권한을 요구하는 부분이기 때문에 주석 처리해주지 않으면 나중에 실행할 때 귀찮아진다...
주석 처리해도 별 영향 없기 때문에 빌드 전에 꼭 주석 처리 해주는 것을 추천한다~!
(요거 안했다가 다시 재빌드하러 돌아왔다,,)
 

빌드 > 솔루션 빌드 > 터미널에 빌드 성공이 뜨면 된다!
 

~~\source\repos\InjectDll\Debug 폴더에 정상적으로 InjectDll.exe 파일이 생긴 것을 확인 가능하다
 
같은 방식으로 myhack,dll 파일도 생성해준다.

주의할 점은, 디버그 > myhack 속성 >
1. 구성 형식: 동적 라이브러리(.dll)
2. 문자 집합: 유니코드 문자 집합 사용\
으로 변경해줘야 한다.
 
 

2. 본격 인젝션 실습 시작

myhack.dll을 인젝션할 타겟은 notepad.exe이다.
 

notepad.exe를 실행하고 procexp.exe를 실행하여 PID를 확인했다.
notepad.exe의 PID는 "4288"이라고 적혀 있다. (PID 숫자는 실행할 때마다 매번 달라지고, 사람마다 다름)
 

DebugView.exe 실행 화면

DebugView.exe를 실행한다. DebugView는 디버그에 관련된 문자를 화면에 출력해주는 프로그램이다.
아직 notepad와 관련된 문구가 출력되지 않고 있는 것을 확인할 수 있다.
 

InjectDll.exe와 myhack.dll은 같은 파일에 있어야 하므로, InjectDll.exe 파일이 있는 폴더 안에 myhack.dll을 복붙해준다.
 

Windows PowerShell을 실행하고, cd 명령어를 이용하여 InjectDll.exe와 myhack.dll 파일이 존재하는 경로로 이동해준다.
ls 명령어로 해당 폴더에 두 파일이 존재하고 있는지 한번 더 확인해줬다.
 

이제 인젝션을 해줄 차례이다.
[실행할 파일] [타겟 프로그램의 PID] [인젝션할 파일]을 입력해줘야 한다.
이때 인젝션할 파일의 완전한 경로를 적어줘야 한다.
.\InjectDll.exe 4288 C:\Users\~\source\repos\InjectDll\Debug\myhack.dll을 입력해줬다.
결과로 success!!! 라는 문구가 떴다. 인젝션에 성공한 것이다.
 
제대로 인젝션이 되었는지 확인할 수 있는 방법은 다음과 같다.
1. DebugView를 보면 <myhack.dll> Injection!!! 이라는 문구가 보인다.
2. procexp를 보면 notepad에 myhack.dll이 로드된 것을 확인할 수 있다.
    ㄴ Find > Find Handle or DLL > 'myhack.dll' 검색
3. C:\Users\~\source\repos\InjectDll\Debug 경로로 가보면 index.html 파일이 생긴 것을 확인할 수 있다. 열어보면 naver에 접속된다.
 
 

2. DLL 인젝션 2 - 인젝터를 이용하지 않고 레지스트리 편집기를 이용하여 인젝션하는 방식

1. myhack2.dll 파일 생성

주의!
구성 형식: 동적 라이브러리(.dll)
문자 집합: 유니코드 문자 집합 사용
 
 

2. 레지스트리 편집기에서 설정 수정

윈도우 메뉴 > Windows 관리 도구 > 레지스트리 편집기 실행
 

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows 경로로 들어가주면 위와 같은 화면이 뜬다.
우리가 수정해줄 요소는 'AppInit_DLLs'과 'LoadAppInit_DLLs' 이 두가지이다.
 

AppInit_DLLs에는 myhack2.dll이 있는 경로를 입력해주고, LoadAppInit_DLLs는 1로 설정해준다
이는 매번 컴퓨터를 부팅할 때마다 실행되도록 설정하는 것이다.
이렇게 설정해주면 user32.dll을 로딩한 모든 프로세스들이 myhack2.dll을 로드하도록 만들 수 있다.
 
설정을 마친 뒤 윈도우를 재부팅해준다.
 
procexp를 켜서 Find > Find Handle or DLL > 'myhack2.dll' 검색해보면 되게 많은 수의 exe 파일들이 myhack2.dll을 로드하고 있는 것을 확인할 수 있다.
 
실습이 끝난 뒤에는 레지스트리 편집기를 켜서 다시 원상복구 시켜줘야한다
 
 

3. DLL 이젝션

Ejection: Inject되어 있는 dll을 다시 꺼내주는 것
 
코드는 03_DLL_Injection\24_DLL_Ejection\src\EjectDll\EjectDll.cpp
 

151, 152번 줄 주석 처리해준다.
 

1. 인젝션

이젝션을 하기 위해서는 우선 인젝션이 되어 있어야 한다
 

notepad의 PID 확인
 
procexp > Find > Find Handle or DLL > 'myhack' > notepad 있는지 확인 (있다면 인젝션 된 것이다)
 
 

2. 이젝션

PowerShell에서 cd C:\Users\~\source\repos\EjectDll\Debug 후
.\EjectDll.exe 한 뒤
procexp > Find > Find Handle or DLL > 'myhack' > notepad가 사라진 거 확인 가능!


4. 코드 해석

1. InjectDll.cpp

InjectDll.cpp - 1
InjectDll.cpp - 2
InjectDll.cpp - 3
InjectDll.cpp - 4

이 부분은 InjectDll.cpp 코드의 핵심이라고 할 수 있는 InjectDll() 함수이다.

hProcess: 프로세스를 참조함
hThread: 스레드를 참조함
HMODULE: 모듈을 나타내는 자료형
DwBufSize: szDllPath의 경로의 문자열 길이에 1을 더한 다음 캐릭터 사이즈를 곱해줌. 즉, 이 경로명(szDllPath)의 길이만큼의 사이즈를 지원해주는 것.

[63] 대상 프로세스(노트패드)에 인젝션을 하기 위해서 노트패드라는 프로세스를 참조해야함. 그래서 PID 정보를 가지고 해당되는 프로세스 자체를 참조할 수 있도록 하는 과정임.
OpenProcess로 프로세스를 오픈함. PROCESS_ALL_ACCESS는 프로세스 오픈해서 프로세스 전체에 접근한다는 뜻. dwPID에 노트패드 PID를 전달받아 접근함.
근데 !hProcess니까 프로세스 오픈에 실패했다면~의 경우에 대한 코드임.

[70] 오픈한 프로세스에 가상 메모리 공간을 할당해줘야 함. hProcess(노트패드)에 dwBufSize 만큼(dll의 경로명 만큼)의 공간을 할당.
MEM_COMMIT: 이 옵션을 쓰면 “실제로” 해당 블록에 가상 메모리 페이지를 할당함
  (<-> MEM_RESERVE: 할당을 실제로 하지 않고 이 만큼의 공간을 쓰겠다고 예약만 함)
PAGE_READWRITE: 할당한 메모리 공간에 읽고 쓸 수 있는 권한을 줌.
pRemoteBuf라는 포인터를 가지고 할당 받은 가상 메모리 공간에 액세스함

[73]
hProcess내에서 pRemoteBuf가 가리키는 공간에 szDllPath의 내용을 적어줌 (dwBufSize 크기만큼)
-> 여기까지 하면 노트패드 안에서 인젝되고자 하는 dll 정보가 노트패드의 가상 메모리 공간에 들어감


InjectDll.cpp - 5

[76] LoadLibrary(): 필요한 라이브러리를 로드함
여기서는 이 LoadLibrary가 정의된 경로를 알아내기 위해 kernel32.dll에 액세스하여 이 dll의 정보를 알아냄.

[77] 주소를 알기 위해서 GetProcAddress를 사용함 (LoadLibrary의 주소를 가져와서 pThreadProc에 들어감 (LoadLibraryW는 LoadLibrary의 유니코드 버전임)
LPTHREAD_START_ROUTINE: 주소를 스레드의 시작 주소처럼 캐스팅하기 위해서 스레드의 시작 루틴을 가리키는 포인터로 만든거임
로드라이브러리의 함수를 스레드의 시작 루틴으로 사용하기 위해서 강제로 캐스팅함. 이 타입의 사이즈와 GetProcAddress의 사이즈가 같아서 강제로 캐스팅해도 에러안났음.

[80] CreateRemoteThread: 스레드를 실행할 때 사용하는 함수. hProcess는 노트패드. 노트패드 안에서 pThreadProc(=LoadLibraryW의 주소)를 실행한다. 즉, 노트패드 안에서 LoadLibrary 함수를 실행한다. 자신이 로드하고자하는 dll 정보가 있어야 LoadLibrary룰 사용할 수 있기 때문에 LoadLibrary를 실행하기 위해서 pRemoteBuf(=실행하고자하는 dll 정보)라는 파라미터를 전해줌
-> myhack.dll을 notepad 안에 로드함. 파일 전체가 노트패드 안에 들어가는거임. 위에서는 공간을 할당했다면 여기서는 진짜로 myhack.dll의 파일 내용 자체가 들어가는거임.

[80] 할당하고 myhack.dll이 실행됨. 로드를 하면서 파일도 다운로드하게…
[81] WaitForSingleObject(): 두번째 인자는 시간 정보가 들어가야하는데 인피니트니까 hThread가 종료될 때까지 기다린다~ 스레드는 독립적으로 실행되는데 이 스레드가 종료가되면 기본적으로 기다리는걸 끝냄

[83] 스레드와 프로세스 클로즈


InjectDll.cpp - 6

[89] main 함수.
[93] 프로그램 이름, 타겟의 PID, 인젝할 프로그램의 경로명을 입력받음

*유니코드 vs 멀티바이트: 둘다 데이터 타입을 표현하는 코드 형식
• 유니코드: wchar_t - 영문, 한글 둘다 2바이트라서 한글이 깨지지 않음.
• 멀티바이트: char - 주로 우리가 사용. 영문(1바이트)과 한글(2바이트)을 다르게 표기함. 그래서 한글이 깨질 수 있음.

TCHAR: 유니코드, 멀티바이트를 둘다 지원해줌.

(L”index.html”): 유니코드 문자열을 나타낼 때 L을 붙임
“index.html” 멀티바이트 문자열을 나타낼 때 이렇게 씀

[102] InjectDll에 첫번째 인자(노트패드의 PID), 두번째 인자(myhack.dll의 완전한 경로)가 들어가면 success 문자열 출력함
_tstol: PID를 문자열로 받아 숫자로 바꿔주는 거임
DWORD: PID값을 4바이트로 지정해줌


LoadLibrary 함수가 어떻게 호출되는지?

InjectDll.exe 안에서는 Kernel32.dll에 있는 LoadLibraryW의 주소를 체크했음.
근데 사실 우리가 원하는건 notepad.exe 안의 Kernel32.dll에 있는 LoadLibraryW 주소를 알고 싶은 것암
근데 결과적으로 (exe 파일이 다른데도) InjectDll.exe에서 찾아온 주소를 가지고도 알아서 잘 실행됐음.
왜?
kernel32.dll은 운영체제가 제공하는 dll이라 없는 정보가 없음.
kernel32.dll은 거의 모든 프로세스에서 쓰이기 때문에 맨처음에 사용되는 특정 프로세스 A에만 kernel32.dll을 넣어두고 그 뒤에 사용하는 애들은 그 주소만 참조해서 사용하는 것!
그래서 InjectDll.exe 속 kernel32.dll과 notepad.exe 속 kernel32.dll는 결국 같은 애다… 그래서 그냥 injectDll 속 kernel32.dll을 알아내서 사용해도 상관이 없었다~


2. myhack.cpp

이 코드는 Index.html을 다운로드 받는 역할을 한다.

myhack.cpp - 1

[4] #Pragma comment(lib, “urlmon.lib”): 라이브러리를 링크할 때 사용함. 이 라이브러리를 사용해서 해당 url(html 파일)을 다운로드 받겠다~

myhack.cpp - 2

[11] ThreadProc에 정의된 내용
• szPath 배열 (초기화한 상황)
• GetModuleFileName: 모듈의 파일 이름을 가져옴. g_hMod(=NULL)가 가리키는 모듈에서 그 모듈의 경로명을 가져와서 szPath에 넣어줌. g_hMod가 가리키는 모듈이 없으니까 현재 실행중인 프로세스의 경로명임. 즉, InjectDll.exe의 경로명을 가져오게 하는거임.
• _tcsrchr(==strrchr): szPath 안에서 ‘\\’가 가장 마지막으로 나오는 위치를 검색해줌.
• [22] 그 가장 마지막으로 나오는 위치 다음 경로에 DEF_FILE_NAME(=index.html)을 넣어줌
• [24] DEF_URL 상에서 다운로드 받아서 szPath에 넣어줌

myhack.cpp - 3

[29] DllMain: 시작했을 때 가장 먼저 실행되는 부분

ATTACH: 프로세스 안에서 dll이 연결되어서 dll 실행이 가능하게 해주는 키워드

[38] OutputDebugString: <myhack.dll> Injection!!! 이라는 문자열이 출력되게 함
[39] 스레드를 만들어서 ThreadProc을 실행함.


3. EjectDll.cpp

EjectDll.cpp - 1

앞에서 인젝션 했던 걸 빼는 코드이다.
코드 설명에 앞서, dll 인젝션하는 방식은 두가지 방식이 있다.
- explicit: 명시적으로 인젝션. 해당 dll을 내가 필요할 때 넣었다가 빼는 것이다. 사용자들이 프로그램을 작성할 때 사용자가 만든 dll을 기본적으로 직접 인젝션하고 빼는 작업을 한다. 우리가 지금까지 한 게 explicit 인젝션 방식이다.
- implicit: 암묵적으로 인젝션. 함수는 라이브러리에 저장되어 있다. 우리가 dll을 명시적으로 넣지 않아도 함수를 사용하면 그 함수가 정의되어 있는 dll이 로드되는 것. (KernelDll, user32Dll 등 시스템이 제공해주는 dll)
-> 결과적으로 explicit한 경우에만 이젝션해주면 된다. 명시적으로 인젝션해줬으니까 이젝션도 명시적으로 빼줘야 하는 것. implicit는 프로그램을 실행할 때 로드됐다가 종료될때 빠진다. explicit 방법으로 인젝션했더라도, 꼭 이젝션을 하지 않아도 상관은 없지만 메모리 공간이 낭비된다.

[17] PROCESSENTRY32: 프로세스 정보를 가지고 있는 구조체.
[18] CreateToolhelp32Snapshot: 현재 동작하고 있는 모든 프로세스의 정보를 스냅샷으로 찍음.
[21] process32First: 스냅샷에서의 최초, 처음의 프로세스 정보를 찾아서 pe에 넣어줌. Pe는 스냅샷에 저장된 첫번째 프로세스다.
[24] pe의 sxExeFile과 10번 줄에서 인자로 받은 szProcessName을 비교해서 같으면 찾은거임 (!는 같다는 뜻..)
[26] dwPID를 찾은 프로세스의 아이디정보로 저장해줌.
[30] 만약 찾지 못했으면 pe값을 다음 요소로 넘어감. 첫번째 프로세스 보고 아니면 두번째 프로세스 보고 나올때까지 찾음


EjectDll.cpp - 2

[97] 앞에서 FindProcessPID를 할 때는 SNAPALL를 썼는데 여기서는 TH32CS_SNAPMODULE을 사용함. 스냅 모듈은 특정 프로세스만 볼 수 있게 해주는 거임. 노트패드의 해당 프로세스에만 초점을 맞추고 MODULE(=여기선 dll)을 가져옴.

또 하나씩 검사해나가면서 myhack.dll와 일치하는걸 찾아나감.

[102] dll이름(myhack.dll)과 비교해서 myhack.dll을 찾아냄. szModule은 모듈의 이름, szExePath는 모듈의 경로명임. dll이 모듈의 이름에 들어갈 수도 있고 경로명에 들어갈 수 있으니까 둘 중 하나에라도 매칭되면 발견한걸로 보고 빠져나옴.


EjectDll.cpp - 3

[116] 인젝션처럼 프로세스를 오픈하고, 어쩌구 하는 과정을 똑같이 거쳐야 함.
dwPID(=노트패드 프로세스) 오픈함.

[122] GetModuleHandle로 “kernel32.dll”을 가리킴?

[123] GetProcAddress로 hModule안에서 FreeLibrary 함수의 주소를 가져옴.
GetProcAddress의 리턴값은 이런 식이 사실 아닌데, 이 리턴값과 LPTHREAD_START_ROUTINE의 리턴값의 사이즈가 같아서 가능함.

[124] hProcess: 노트패드
pThreadProc: FreeLibrary 함수의 주소
ModBaseAddr: myhack.dll의 베이스 주소(freeLibrary로 이 주소를 알아냄?)

[127] 해당 스레드가 종료될 때까지 기다림. 종료가 되면 빠져나감.

FreeLibrary가 kernel32.dll에서 사용하는 앤데, EjectDll.dll에서 사용도 하고 있음. 얜 Kernel32.dll에서 정의하고 있기 때문에 액세스하는데 문제가 없다~


EjectDll.cpp - 4

이젝션을 하기 위해서는 PID 값을 알아야 함.

[133] PID값이 초기화된 값으로 설정되어 있음.

[141] FindProcessPID 함수로 PID를 찾아줘야 함. notepad.exe의 PID를 찾음

[144] PID 없으면 에러메시지
[148] 있으면 PID를 출력해줌

[155] PID 정보와 dll 이름이 인자로 들어가서, 이젝션됨!

5. 생각해보기

1. 공격자가 특정 실행 파일에 인젝션된 악성 DLL을 이젝션 함으로써 얻게 되는 이익은 무엇인가?

인젝션해도 꼭 이젝션 안해도 됨. 물론 메모리의 효율성을 위해서 하는 게 좋지만..
그렇다면 공격자는 굳이 인젝션한걸 이젝션하려고 할까?
명시적으로 인젝션, 이젝션한다는 것은 필요할 때 로드했다가 뺀다는 의미.

이젝션을 하지 않으면 해당 악성 프로세스가 동작하는 동안은 사용자의 메모리 안에 dll이 계속 남아있을거임. 그러면 방어자가 해당 프로세스를 검사하다가 악성 dll로 탐지될 수 있음! 그러면 악성 행위 자체가 노출될 수 있기 때문에, DLL 탐지 이전에 DLL 이젝션을 해줘야 탐지를 피할 수 있다. 언제 탐지할 지는 잘 모르지만, 최대한 쓰고 빨리 이젝션 해줘야 탐지 당하지 않을 수 있다!


2. DLL 인젝션 (CreateRemoteThread() 함수와 LoadLibrary() 함수 이용)과 DLL 인젝션 (AppInit_DLLs와 LoadAppInit_DLLs 이용)의 가장 큰 차이점은 무엇인가? 두 개의 DLL 인젝션 기법이 악성 DLL 인젝션에 사용된다고 가정하면, 두 가지 기법 중에서 탐지가 더 쉬운 기법은 무엇이며, 공격이 더 쉬운 기법은 무엇인지 작성하고 그 근거를 서술하시오.

전자는 함수를 쓴 방식, 후자는 레지스트리를 이용한 방식이다.

탐지가 더 쉽다는 건 노력을 덜해서 탐지할 수 있다는 얘기.
공격이 더 쉽다는 건 공격을 하기 위한 노력을 덜 해도 된다는 얘기.

레지스트리는 값만 설정해주기만하면 되고, 부팅할 때마다 적용이 되기 때문에 공격이 쉽고 편한 방식이다.
반면, 함수를 쓰는 방식은 함수도 다 써야하고 주소도 받아와야 한다.

그러나 레지스트리는 그 부분만 확인하면 돼서 탐지도 더 쉽다.

공격이 쉽다는건 탐지가 쉽다는 의미와 같다. 공격에 노력을 덜 들이면 탐지가 더 쉽기 때문.