본문 바로가기

리버싱/x64dbg 디버거를 활용한 리버싱과 시스템 해킹의 원리

ch11 - 코드 인젝션

728x90

11.1 PE 재배치와 코드 패치 원리

11.2 DLL 인젝션

11.3 쓰레드 인젝션

11.4 코드 인젝션

11.5 프로세스 인젝션

 

11.1 PE 재배치와 코드 패치 원리

 1.1 PE 재배치(relocation)

 - 프로그램이 로드(load)될  주소에 이미 다른 프로그램이 적재되어 있다면, 다른 주소로 찾아서 로드된다. 

(로드될 위치가 바뀌면 실행 프로그램이 사용하는 주소들의 재배치가 필요)

 - 윈도우 PE 파일이 로드되는 주소는 IMAGE_OPTIONAL_HEADER의 ImageBase 값이다. 이 값을 기준으로 코드, 데이터 공간, 그리고 여러 섹션의 주소가 결정된다. 윈도우 비스타 이후에 추가된 ASLR기능이 사용되면, 실행될 때마다 로드되는 주소가 달라진다.

 

정적 라이브러리는 프로그램이 실행할 때 같이 올라가고

 - 동적 라이브러리 파일(DLL)은 프로세스가 요구할 때 메모리에 로드된다. 

동적 라이브러리는 필요할 때 로드하고 사용하지 않을 때 해제되기 때문에, 로드할 때마다 주소 재배치 과정이 발생한다.

DLL의 경우 프로그램 초기에 지정된 곳에 로드되지만, 실행 도중에 추가적인 DLL이 수행되는 경우 재배치가 발생한다.

만약, DLL이 다시 로드될 때 이전 ImageBase에 다른 DLL이 있다면 다른 비어있는 공간에 로드된다. -> PE 재배치

 

 1.2 재배치 정보

typedef struct _IMAGE_BASE_RELOCATION{

 DWORD VirtualAddress; //재배치가 시작되는 메모리 상의 RVA

 DWORD SizeOfBlock; // 재배치 영역의 크기(블록의 바이트 수)

 //WORD TypeOffset[1]; //재배치 정보의 타입(4비트 PE파일에선 0x3, 64비트 PE파일에선 0xA)과 오프셋(12비트)

}IMAGE_BASE_RELOCATION;

-> 재배치 관련 8바이트 구조체 정보로 .reloc 섹션에서 사용한다. IMAGE_BASE_RELOCATION; 이후에 존재하는 TypeOffset 데이터는 재배치 타입과 오프셋(실제 코드가 배치될 영역으로 최대 크기는 0xFFF로 더 커질 경우, IMAGE_BASE_RELOCATION 구조체를 다시 정의하고 표현해야 한다.)을 의미한다.

실제 주소 : virtualAddress에 오프셋을 더한 값

 

 1.3 PE 코드 패치

 : 긴급하게 오류를 수정할 필요가 있을 때 사용한다. 패치할 코드가 기존 코드 공간보다 적으면 상관없지만,

패치할 코드 > 기존 코드인 경우, 다음과 같이 다른 공간을 활용하게 된다.

  • 프로세스 메모리의 빈 영역에 설치할 수 있다.
  • 마지막 섹션을 확장한 후 설치할 수 있다.
  • 새로운 섹션을 추가한 후 설치할 수 있다.

-> 이렇게 패치된 코드가 다른 영역(패치할 코드가 커서 남는 다른 공간을 활용한 경우)을 활용하면 다음과 같이 바꿔줘야 한다.

먼저 원래 코드에서 패치코드로 옮겨갈 부분에 jmp 코드를 추가한다.

그리고 패치 코드의 마지막 부분에 원래 코드로 돌아가는 jmp 코드를 추가한다.

 

 4.4 API 후킹

후킹(hooking) : 운영체제와 애플리케이션 사이에 오가는 정보를 확인할 수 있는 것을 뜻한다. 윈도우는 이벤트 메시지 전달 방식으로 동작된다.

https://docs.microsoft.com/en-us/windows/win32/winmsg/using-hooks

 

Using Hooks - Win32 apps

Learn more about: Using Hooks

docs.microsoft.com

 

ex) 키보드 입력 이벤트가 발생하면

1. WM_KEYDOWN메시지가 운영체제의 메시지 큐에 추가된다.

2. 운영체제는 이벤트 발생 애플리케이션을 파악하고 해당 애플리케이션 메시지 큐에 추가한다.

3. 애플리케이션은 자신의 메시지 큐에서 WM_KEYDOWN 메시지를 확인하고 해당 이벤트 핸들러를 수행한다.

 

 - 후크 체인(Hook Chain)

운영체제와 애플리케이션의 메시지 큐 사이에는 후크 체인(hook chain)을 둘 수 있다. 이벤트에 대한 후크 체인에는 수행할 API가 등록되는데 이러한 API는 키로깅이나 변조행위 등의 악의적인 행위일 수 있다.

하나의 이벤트에 여러 개의 API를 후크 체인으로 등록할 수 있다.

 

API 후킹은 SetWindowsHookEx()를 사용하여 수행할 수 있다.

HHOOK WINAPI SetWindowHookEx(

_In_ int idHook, //훅 타입

_In_ HOOKPROC lpfn, //훅 프로시저 함수 주소(DLL에 있는 함수)

_In_ HINSTANCE hMod, //DLL 핸들

_In_ DWORD dwThreadId //훅을 걸 쓰레드 ID로 0이면 모든 쓰레드가 대상

);

 

#첫 번째 매개변수 idHook에서 사용하는 주요 옵션

  • WH_GETMESSAGE : 우니도우 메시지가 체인
  • WH_KEYBOARD : 키보드 입력 관련 처리를 수행하는 체인
  • WH_MOUSE : 마우스 이벤트 관련 처리를 수행하는 체인

 - 키보드 후킹 프로그램 주요 코드

 

  • API 후킹 [KeyBoard]

keydown, syskeydown의 wparam에는 키보드의 동작이 들어가고 값이 printf로 출력된다.

후크체인으로 연결된 것으로 여러가지 기능을 엮을 수 있다.

 

  • API 후킹 [Mouse]

마우스 후킹으로 ncode가 0보다 작으면 오류로 처리하고 wparam에 x,y값을 기준으로 다음과 같이 좌표를 찍고 있다.

 

 

11.2 DLL 인젝션

 2.1 DLL 영역

- 윈도우 운영체제의 프로세스 메모리 구조에서 DLL은 IAT에서 정보를 유지한다. 주로 공유 라이브러리이며, 프로세스 시작과 함께 DLL의 라이브러리들이 IAT에 매핑된다.

 DLL 프로그램의 시작 함수는DllMain()이다.

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpReserved) {

    switch (dwReason) { //더블 워드로 이유를 알려줌

        case DLL_PROCESS_ATTACH:

            // DLL이 프로세스 공간에 매핑 될 때

            // 원하는 코드 추가

            break;

        case DLL_PROCES_DETACH: 

            break;

        case DLL_THREAD_ATTACH: // 새로운 쓰레드가 생성될 때 DLL import

            break;

        case DLL_THREAD_DETACH: // DLL export

            break;

}

 

 - DLL 인젝션 : 임의의 DLL 파일을 원하는 프로세스의 메모리 영역에 DLL을 로드하는 것이다.

기본적으로 작동 중인 프로세스들은 모두 각각의 독립적인 메모리 영역을 가지고 있으며 이 영역을 임의로 접근하는 것은 불가능하지만 인젝션을 이용해 다른 프로세스의 메모리 영역을 접근 및 수정, 조작할 수 있다.

 

 - 다른 프로세스가 LoadLibrary() API를 호출하여 사용자가 원하는 DLL을 로딩하게 할 수 있다. 특정 프로세스에 임의의 DLL이 로드 되면 해당 DLL은 로드된 프로세스의 메모리에 대한 정당한 접근 권한이 생기기 때문에 사용자가 원하는 어떤 일이라도 할 수 있다.

DLL인젝션 방법 : 레지스트리, Debugging API, CreatRemoteThread, SetWindowsHookEx 등등..

 

 2.2 후킹함수 SetWindowHookEx()를 이용한 DLL 인젝션

 - Windows 메시지 후킹에 사용하는 함수인 SetWindowHookEx()를 이용해 대상 프로세스에 후킹 프로시저를 설치하는 동시에 DLL을 로드하는 방법

바로 DLL이 로드되지 않고 다음과 같은 과정을 거친다.

  • 1단계 : 인젝션 할 DLL을 먼저 자기 자신의 프로세스에 로드한다.
  • 2단계 : SetWindowsHookEx()에 대상 프로세스의 쓰레드 주소, 로드시킬 DLL 핸들, 후킹 프로시저로 동작할 프로시저 주소를 인자 값으로 넣어 호출한다.
  • 3단계 : 인젝션 대상 프로세스의 메시지 훅체인에 SetWindowsHookEx()에서 지정한 프로시저가 등록되면서 DLL이 자동으로 로드된다.

다음 예제는 후킹을 이용한 DLL 인젝션을 하는 프로그램으로 인젝션 툴에서 SetWindowsHookEx()를 호출해 훅 프로시저가 설치되면 대상 프로세스에 훅프로시저에 지정된 해당 타입 이벤트 메시지는 DLL인젝션 대상 프로세스로 보내지기 전에 훅 프로시저에게 먼저 전달된다.

SetWindowsHookEx()에서 지정한 이벤트 메시지가 발생하는 겨우 윈도우에서는 해당 프로시저를 포함하고 있는 DLL을 자신의 프로세스 메모리 공간에 로드한다.

 

bool UseSetLoadDll (char* Dllpath, DWORD ProcId ) {

    LRESULT (__stdcall *hHookProc)(int , WPARAM , LPARAM);

    hDll = LoadLibraryA (Dllpath); // DLL의 로드 [1단계]

    AfxMessageBox (_T("Dll loading Check Me! "));

    hHookProc = (LPMYPROCK)GetProcAddress (hDll, "MyProc"); //로드한 프로세스를 호출 [2단계]

    if (hHookProc != NULL) {

        AfxMessageBox (_T("Get Address Success"));

        SetWindowsHookEx (WH_KEYBOARD, (HOOKPROC)hHookProc, hDll, ProcId); //이벤트에 해당하는 id 및 정보

    } [3단계]

    return TRUE;

}

// DLL 내부 MyProc함수 - 2단계 과정

DLLexport LRESULT CALLBACK MyProc (int nCode, WPARAM wParam, LPARAM IParam) {

    // 수행 코드 추가

    if (nCode > 0) { //0보다 (정상적인 것)크면 다음 실행

        MessageBox (NULL, TEXT("MyProc.DLL"), TEXT("-=-=-=-=-"), MB_OK);

    }

    return ::CallNextHookEx (hHook, nCode, wParam, IParam);

}

 

ex1) 후킹을 이용한 DLL 인젝션을 하는 프로그램 코드

CALLBACK이 외부에서 접근한는 DLL

dll파일이기 때문에 속성에서 프로그램을 동적 라이브러리로 변경해줘야 한다.

 

 

그리고 visual 해당 파일의 상위 폴더 debug에 들어가면 다음과 같이 dll 파일이 생성된다.

dll파일을 사용하려면

1. 관리자 모드로 해야 작동한다.

2. 디버그 모드가 아닌 release 모드로 해줘야 한다.

 

ex2) 후킹을 이용한 DLL 인젝션을 하는 프로그램 코드

 

 2.3 원격 쓰레드를 이용한 DLL 인젝션

 - CreateRemoteThread() : Windows에서 제공하는 API로서 인젝션 대상 프로세스에서 원격 쓰레드를 실행하여 DLL을 인젝션하는 방법이다.

 

  • 1) OpenProcess() API를 이용하여 대상 프로세스의 handle을 구한다.

      hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)

 

  • 2) VirtualAlloc() API를 이용하여 대상 프로세스에 로드시킬 DLL 경로를 저장할 메모리 공간을 확보한다. 프로세스에 추가할 DLL 크기만큼 메모리 할당한다.

      pRemoteBuf = VirtualAllocEx(hProcess, NULL, sizeDLL, MEM_COMMIT, PAGE_READWRITE);

 

  • 3) WriteProcessMemory() API를 이용하여 pathDLL을 VirtualAlloc() API로 확보한 메모리 공간에 DLL을 복사한다.

     WriteProcessMemory(hProcess, pRemoteBuf, pathDLL, sizeDLL, NULL);

 

  • 4) LoadLibraryW() API 주소를 구한다. GetProcAddress API를 이용해 "Kernel32.dll"의 "LoadLibraryW"의 함수의 주소를 얻어서 저장한다.

     pThread = GetProcAddress(GetModuleHandle("kernel32.dll"), "LoadLibraryW");

 

  •  5) CreateRemoteThread() API를 이용하여 DLL을 로드한 프로세스에서 쓰레드를 실행하게 한다. 프로세스에서 내부적으로 LoadLibrary() API가 호출되면서 DLL이 강제로 로드된다.

     hThread = CreateRemoteThread(hProcess, NULL, 0, pThread, pRemoteBuf, 0, NULL);

     waitForSingleObject(hThread, INFINITE);

ex) 원격 쓰레드를 이용한 DLL 인젝션을 하는 프로그램 코드

 -> 네이버의 index.html을 다운로드하는 프로그램이다.

11.3 쓰레드 인젝션

 - 쓰레드로 동작하는 실행 코드를 대상 프로세스의 메모리에 삽입하여 쓰레드로 동작시키는 방법이다.

일반적으로 코드 인젝션에서 많이 사용하는 DLL 인젝션의 경우 실제 실행되는 코드가 DLL 파일에 저장되어 있고 인젝션 된 쓰레드 코드는 DLL 인젝션과 다르게 실행 중인 쓰레드 코드가 파일로 남아있지 않고 메모리에서만 상주하는 코드 특징을 가지게 된다.

쓰레드 인젝션은 실제 실행 코드가 메모리에만 저장되어 있기 때문에 코드 은닉 및 안티 바이러스 탐지 우회에 용이하다.

 

 3.1  CreateRemoteThread() API를 이용한 쓰레드 인젝션

1. OpenProcess API를 이용하여 인젝션 대상 프로세스의 권한을 얻는다. 대상 프로세스에 대한 가능한 모든 권한(MAXIMUM_ALLOWED)을 가져올 수 있다.

2. 쓰레드 코드를 공간을 확보하기 위해 VirtualAlloc() API 함수를 호출한다. OpenProcess() API로 가져온 핸들을 가지고 대상 프로세스에 삽입할 쓰레드 코드와 쓰레드에서 사용할 데이터 공간을 실행/읽기/쓰기(PAGE_EXECUTE-READWRITE) 가능한 형태로 메모리 공간을 확보한다.

3. 대상 프로세스에 WriteProcessMemory() API를 이용하여 쓰레드 코드를 삽입한다.

4. CreateRemoteThread() API를 이용하여 삽입한 쓰레드 코드의 주소를 쓰레드 시작 주소로 인자값을 주어 쓰레드를 생성한다.

인젝터에서 CreateRemoteThread()를 호출하는 즉시 인젝션 대상 프로세스에서 새로운 쓰레드가 생성되어 삽입된 코드를 실행한다.

 

 3.2 쓰레드 EIP 변경을 이용한 쓰레드 인젝션

 - 쓰레드 인젝션 대상 프로세스의 메인 쓰레드를 중지 시킨 후에 쓰레드의 EIP를 변경하고 그 결과 쓰레드 생성 관련 함수로 명령어 실행 위치가 이동하게 되어 쓰레드를 인젝션 시킨다.

1. OpenProcess()를 이용하여 프로세스의 권한을 얻는다.

2. 쓰레드 코드를 공간을 확보하기 위해 VirtualAlloc()를 호출한다.

3. 대상 프로세스에 WriteProcessMemory()를 이용하여 쓰레드 코드를 삽입한다.

4. SuspendThread()를 이용하여 대상 프로세스의 쓰레드를 중지하고 현재 EIP의 위치를 저장한 후 대상프로세스에 삽입한 스텁 코드(stub code)로 호출하게 한다. EIP스텁 코드 위치로 변경한다.

5. 스텁 코드에서 쓰레드 생성 함수로 호출한다.

 

 - 인젝터에서 SuspendThread()를 호출하는 즉시 인젝션 대상 프로세스의 메인쓰레드가 임시 중단이 되며, 메인 쓰레드의 EIP 값을 인젝터에서 삽입한 스텁 코드가 저장된 메모리 주소로 변경한다.

 - 메인 쓰레드의 다음 실행 코드의 주소가 저장된 원본 EIP 값은 임시 저장한다. 인젝터에서 ResumeThread()를 호출하면 인젝션 대상 프로세스의 메인 쓰레드가 다시 시작되는데, 이때 EIP가 가리키고 있는 스텁 코드를 실행한다.

 

 3.3 스텁 코드 삽입

 : 쓰레드 인젝션 툴에서 인젝션 대상 프로그램에 사전에 주입한 어셈블리 코드이다.

이것은 실행 중인 쓰레드의 레지스터 정보, 리턴 주소, 플래그 정보를 스택에 저장한 후 사용자가 지정한 함수를 호출 후 EIP 및 스택 등을 원상 복귀하기 위한 어셈블리 코드이다.

 

-> 쓰레드에 인젝션 대상 프로세스에게 삽입하는 스텁 코드이다.

EIP를 변경한 뒤 가장 먼저 실행되는 코드로 PUSHAD와 PUSHFD를 이용하여 레지스터와 플래그 값을 백업하고 CreateThread() 함수 수행이 끝난 뒤에 POPAD와 POPFD를 이용하여 이전 상태의 EIP 흐름을 돌려준다.

 - 다음은 스텁 코드의 삽입 과정으로 EIP 변경을 이용한 쓰레드 인젝션 과정이다.

1단계 - OpenProcess(), OpenThread() API를 호출하여 인젝션 대상 프로세스에 대한 모든 권한을 얻는다.
          (
컨텍스트 수정 가능, 메모리 쓰기 가능 권한 등)

2단계 - WriteProcessMemory()를 이용하여 쓰레드로 동작할 바이너리 코드를 대상 프로세스에 삽입한다.

3단계 - WriteProcessMemory()를 이용하여 쓰레드로 동작할 바이너리에서 사용할 데이터를 삽입한다.

4단계 - 스텁 코드에 저장된 0xdeadbeef인젝션 대상 프로세스의 환경에 맞게 주소 값을 수정한다.

5단계SuspendThread()를 호출하여 인젝션 대상 프로세스의 메인 쓰레드를 중단하고 메인쓰레드의 컨텍스트에 저장            된 EIP 값을 스텁 코드가 저장된 메모리 주소로 변경한다.

6단계 - WriteProcessMemory()를 호출하여 인젝터 메모리에 로드된 스텁 코드를 복사하여 인젝션 대상 메모리에 삽입

7단계 - ResumeThread() API를 호출하여 메인 쓰레드를 재시작 (스텁 코드 실행 유도)

 

 - 쓰레드 컨텍스트 정보에서 EIP를 스텁 주소로 변경

 

 - memcpy() 함수를 호출허여 인젝터 메모리에 저장된 스텁 코드에 있는 0xdeadbeef 값을 유효한 주소 값으로 변경

 - 수정된 스텁 코드를 다음과 같이 WriteProcessMemory() API를 호출하여 인젝션 대상 프로세스에 삽입하고, ResumeThread()를 호출하여 메인 쓰레드의 재시작을 유도한다.

 

11.4 코드 인젝션

 4.1 코드 인젝션 정의

 : 실행 가능한 코드를 대상 메모리 영역으로 삽입하여 실행하는 것으로 은닉하여 실행하고자 하는 코드를 타 프로세스의 메모리 공간에서 실행할 수 있으며 안티 바이러스 프로그램의 탐지를 회피하기 위해 쓰이는 기술이다.

 

 - 코드 인젝션의 활용

  1. 프로그램의 기능 개선 및 버그 패치 - 프로그램 변경이 어렵다면 코드 인젝션으로 새로운 기능을 추가하거나 변경된 코드가 수행도리 수 있도록 할 수 있다.
  2. API를 후킹하여 다른 코드가 수행되게 할 수 있다. - 후킹 함수를 DLL 형태로 만들어 원하는 프로세스에 인젝션하고, 윈도우 SetWindowsHookEx() API를 활용하면 API 후킹이 수행된다.
  3. 응용 프로그램에 특별한 목적의 코드를 추가 - pc사용자를 감시하거나 관리하기 위한 경우 사용자의 동의가 필요
  4. 악성코드에서 활용 - 정상적인 프로세스에 백도어, 키로거, 스파이 기능을 추가하거나 악성코드에서 자신을 보호하거나 지속적으로 권한을 유지하기 위해 사용

 

 4.2 코드 인젝션 주요 동작 흐름

  • 1. 프로세스 권한 획득 : OpenProcess()

     코드를 삽입하고자 하는 대상 프로세스의 메모리 엑세스를 위해 접근 권한을 획득한다.

 

  • 2. 메모리 공간 할당 : VirtualAllocEX()

삽입될 코드가 저장된 영역을 할당한다. 일반적으로 힙 영역이 할당되며, 실행 가능한 옵션을 설정

 

  • 3. 코드 삽입 : WriteProcessMemory()

실행 가능한 코드를 메모리에 삽입한다.

 

  • 4. 코드 실행 : CreateRemoteThread()

삽입된 대상 코드를 실행한다.

 

-> API CreateRemoteThread()를 호출하는 경우 대상 프로세스에서 추가적으로 원격 쓰레드를 실행하여 삽입된 코드를 실행한다. 원격 쓰레드를 생성하지 않고 이미 실행 중인 쓰레드를 이용하여 삽입되는 코드를 실행하는 경우, 실행 중인 쓰레드를 임시 중단하고 해당 쓰레드 컨텍스트를 참조하여 사용자가 삽입한 코드를 실행한다.

 

 4.3 코드 인젝션 관련 주요 API 정보

OpenProcess() : 현재 실행 중인 프로세스에 대한 접근 핸들을 가져온다.

HANDLE WINAPI OpenProcess(

_In_ DWORD dwDesiredAccess, //접근 권한 설정

_In_ BOOL bInheritHandle,  // 일반적으로 NULL 설정

_In_ DWORD dwProcessId  //대상 프로세스 PID

);

 

  • dwDesiredAccesss 값으로 주로 사용하는 옵션 인자

PROCESS_ALL_ACCESS : 대상 프로세스에서 얻을 수 있는 모든 프로세스 엑세스 권한을 얻음

PROCESS_SUSPEND_RESUME : 대상 프로세스에서 실행 중인 메인 쓰레드 중단/재개 권한 획득

THREAD_QUERY_INFORMATION : 쓰레드 객체에 대한 정보 획득 권한을 얻음

PROCESS_VM_READ/PROCESS_VM_WRITE : 대상 프로세스 가상 메모리 접근 권한 획득

 

 

OpenThread() : 현재 실행 중인 쓰레드에 대한 접근 핸들을 가져온다.

HANDLE WINAPI OpenThread(

_In_ DWORD dwDesiredAccess, //접근 권한 설정

_In_ BOOL bInheritHandle,  // 상속 옵션

_In_ DWORD dwThreadId  // 쓰레드ID

);

 

VirtualAllocEx() : 대상 프로세스에 힙 메모리를 생성한다.

LPVOID WINAPI VirtualAllocEx (

  _In_ HANDLE hProcess, //대상 프로세스 핸들

  _In_opt_ LPVOID lpAddress, //일반적으로 NULL

  _In_ SIZE_T dwSize, //생성할 메모리 크기

  _In_ DWORD flAllocationType, //할당 옵션

  _In_ DWORD flProtect //메모리 보호 옵션

);

  • flAllocationType 주로 사용하는 옵션인자 - MEM_COMMIT : 예약된 메모리 페이지에 메모리를 할당

 

VirtualProtect()/VirtualProtetEx() : 가상 메모리 보호

옵션

 

WriteProcessMemory() : 대상 프로세스의 지정 주소 공간에 데이터 쓰기

 

LoadLibrary() : 지정한 라이브러리 파일을 메모리 로딩

HINSTANCE loadLibrary(

LPCTSTR lpLibFileName

);

 

GetProcAddress() : 현재 실행 중인 프로세스에 대한 주소를 가져온다.

CreateRemoteThread() : 대상 프로세스의 메모리 영역에 저장된 코드를 실행하는 쓰레드 실행

 

SuspendThread() & ResumeThread()
  • 현재 쓰레드를 중지

DWORD WINAPI SuspendThread(

 _In_ HANDLE hThread // 중지 대상 쓰레드 핸들

);

 

  • 현재 멈춘 쓰레드를 재시작 시킨다.

DWORD WINAPI ResumeThread(

 _In_ HANDLE hThread // 재시작 대상 쓰레드 핸들

);

 

 

ex) 코드 인젝션 프로그램 코드

 -kernel32.dll을 가져오고 szbuf[i] 변수에 user32, MessageBoxA, Malware Analysis, test 코드를 인젝션한 것이다.

Open Process에서 실행되는 프로그램의 권한을 가져오고 Alloc에서 메모리를 할당한다.

WriteProcessMemory에서 변수를 다루고 ThreadProc()에서 injection code - threadProc인 것만큼의 공간을 계산해서 그 공간에 코드를 인젝션 시킨다.

 

11.5 프로세스 인젝션

: 프로세스에 프로세스 코드 자체를 삽입하고, 그것을 수행시키는 방법이다.

DLL 형태나 쓰레드 형태가 아닌, 메인 프로세스에 코드를 추가하거나 변조하고 실행시켜서 프로세스가 중지된 상태에서만 가능한다. 프로세스 인젝션은 프로세스 할로잉(Process Hollowing), 프로세스 도풀갱잉(Process Dopplganging) 등이 있다.

 

 5.1 프로세스 할로잉

 - 프로세스 할로잉을 수행하는 과정

  • 1) SUSPENDED 모드로 프로세스를 실행
먼저 CreateProcess()를 이용하며 CREATE_SUSPENDED를 플래그로 사용하면 대기(suspend) 상태의 프로세스가 생성된다. 이후 생성된 프로세스의 Image Base 주소를 알아야 한다. 그래야 이 주소를 기반으로 원본 바이너리를 메모리에서 Unmap할 수 있기 때문
 

 - CreateProcess() API의 형식

 

  • 2) EIP 정보 가져오기

원본 코드 섹션을 메모리에서 해제하기 위해 주소 정보를 가져오기 위해서 프로세스의 ImageBase를 알아야하고 NtQueryProcessInformation()을 이용하여 PEB를 구할 수 있다.

PEB의 구조체의 0x08번째 값이 ImageBaseAddress이고 PEB는 GetThreadContex()나 Wow64GetThreadContext를 이용해서도 구할 수 있다.

 

  • 3) 코드 섹션의 메모리를 해제하기
ZwUnmapViewOfSection() 또는 NtUnmapViewOfSection()를 호출하는데 특정 프로세스의 가상 주소 공간에서 섹션을 메모리 해제한다.
매개변수로 대상 프로세서의 핸들과 ImageBase 주소를 사용하고 이 과정으로 메모리에 로드된 원본 실행파일은 메모리에서 삭제됨

 

  • 4) 코드 섹션 공간 할당하기
해제된 주소에 대하여 VirtualAllocEx() API를 사용하여 다시 메모리를 할당하고 실행파일의 ImageBase 주소를 두 번째 매개변수로 지정한다.
사용할 공간은 실행되어야 하므로 보호 옵션은 PAGE_EXECUTE_READWRITE으로 하고 만약 ImageBase 주소가 다르면, PEBImageBase 값도 수정해야 한다.

 

  • 5) 새로운 코드 쓰기
WirteProcessMemory()로 실행 코드의 주소를 해당되는 섹션에 기록한다. 코드가 파일로 있을 경우에는 NtCreateSection() API에 파일 핸들을 전달하여 코드섹션을 생성할 수 있다.
그리고 NtMapViewOfSection() API를 통해 매핑할 수 있다.

 

  • 6) EIP 수정하고 계속 실행하기
쓰레드 컨텍스트에는 레지스터 정보를 볼 수 있다.
CREATE_SUSPENDED로 실행되어 대기하는 순간의 EBXPEB의 주소가 들어가 있고, EAX에는 EP(Entry Point)가 저장되어 있다. 컨텍스트에서 EAX를 변경된 코드의 EP로 변경하면 된다.
SetThreadContext() API를 사용하여 변경된 컨텍스트로 설정하고 쓰레드 컨텍스트에서 EIP 레지스터 값을 직접 변경한 코드섹션의 EP로 변경해도 된다.
ResumeThread() API를 사용하여 대기 상태에 머물러있는 프로세스의 실행을 재개한다.

 

 5.2 프로세스 도플갱잉

 : 파일로부터 실행하지만 원본 파일을 제거하야 파일 기반으로 탐지하는 백신이 탐지하기 어렵게 하는 방식으로 원래 파일과 같은 새로운 파일을 만들지만, 트랜잭션 롤백(rollback) 기능을 사용하여 파일 생성이 반영되지 않게 하여 파일이 남지 않는다.

  • (1)트랜잭션을 통한 코드 쓰기
 : 트랜잭션(transaction) 기능은 업데이트나 특정 작업을 진행할 때, 중간에 오류가 나면 쉽게 롤백하기 위한 것으로 작업 과정을 트랜젝션에 넣고 수행하지만 메모리상에서만 수행되며 파일로는 반영되지 않는다.
CreateTransaction()로 트랜잭션을 생성한 후, CreateFileTransacted()로 트랜잭션에 사용할 더미 파일(dummy file)의 핸들을 연다.
이 핸들을 가지고 WriteFile()를 이용해 쓴다.
NtCreateSection() API로 이 파일에 대한 핸들을 가지고 섹션 객체를 생성한다.
다음으로 RollbackTransaction() API를 통해 트랜잭션을 롤백한다. 이러한 과정을 통하여 더미파일로 만든 섹션이 존재하지만, 더미파일은 사라진다.
 
  • (2)새로운 프로세스 실행하기
일반적으로 프로세스를 실행할 때는 CreateProcess(), WinExec(), ShellExecute()와 같은 API를 사용하는데, 파일 경로가 필요하다. 여기에서 새로운 섹션에 코드를 작성하였으므로, 메모리에서 읽어 수행하는 방법을 사용할 수 있다.
NtCreateProcessEx() API에 생성한 섹션의 핸들을 매개변수로 줄 수 있다.
 
  • (3)프로세스 파라미터와 PEB 설정
프로세스가 실제로 동작되기 위해서 프로세스의 파라미터나 PEB 등을 설정해야 한다. NtQueryInformationProcess() API를 사용하여 PEB 주소를 가져온다.
NtReadVirtualMemory() API를 사용하여 PEB 구조체의 ImageBase 주소에서 메모리를 읽는다. AddressOfEntryPoint를 읽어와 ImageBase를 더해서 EP를 구한다.
 
RtlCreateProcessParametersEx() API를 이용하여 파라미터 블록을 생성한다.
대상 프로세스에 NtAllocateVirtualMemory() API를 사용하여 메모리를 할당한다.
그 공간에 NtWriteVirtualMemory() API를 사용하여 파라미터 블록을 쓴다.
 
  • (4) 새로운 코드로 실행하기
앞에서 가져온 PEB 주소로 베이스 주소를 사용하여 앞에서 작성한 파라미터를 NtWriteVirtualMemory() API를 사용하여 PEB 구조체를 수정하고 마지막으로는 NtCreateThreadEx() API를 이용하여 새롭게 구한 EP쓰레드를 실행시킨다.