1. 개요

2. 분류

.... 2.1 Self Creation

.... 2.2 Debug Blocker / Self Debugging

.... 2.3 Process Hollowing / PE Image Switching / RunPE / Process Injection

.... 2.4 Process Doppelganging





1. 개요

  제목을 Process Hollowing이라고 썼지만 방식들 뿐만 아니라 이름도 다른 여러 기술들을 한번에 정리하려고 한다. 어떻게 보면 공통점이라고는 자식 프로세스를 실행하며 그 자식 프로세스에서 악의적인 행위가 이루어진다는 점만 있는것 같기도 하다.


  아래의 이름들은 "리버싱 핵심원리"나 여러 외국 자료들을 보고 정리한 것인데 사용하는 사람들마다 다른 이름을 붙여서 사용하거나 이름 자체에는 관심을 가지고 있지 않는 경우도 많기 때문에 이름 보다는 기술의 내용에 집중해야 할 것이다.


  이 글에서 가장 중점적인 내용은 Process Hollowing (RunPE, Process Injection 등) 기법이다. 코드 및 데이터를 인젝션하는 기법에는 첫 번째로 셸코드 및 프로시저 형태로 존재하는 루틴 자체를 인젝션하는 방식으로서 그냥 코드 인젝션이라고도 불리며, 두 번째로 DLL 형태로 만들어 이것을 인젝션하는 방식 (DllMain()에 악성 루틴이 존재하여 DLL 로드 시 실행)으로서 DLL 인젝션이라고 불리는 방식이 있다.


  Process Hollowing은 또 다른 이름인 Process Injection 처럼 프로세스에 프로세스 자체를 인젝션하는 (간단히 말해서) 방식이다. 다음에 나오는 프로세스 도플갱잉도 마찬가지로 Process Injection 방식으로 분류된다. 이러한 방식들 모두 인젝션 방식에 포함되므로 다른 인젝션 기법들과 함께 언급되는 경우가 많아서 굳이 이렇게 분류하기로 했다. 코드 인젝션과 DLL 인젝션은 다음 링크에서 후킹과 같이 정리되어 있으며 프로세스 인젝션은 이곳에서 2.3 및 2.4 항목을 통해 정리한다. http://sanseolab.tistory.com/28 ]





2. 분류

2.1 Self Creation

  가장 간단한 형태이다. 진행하면서 자기 자신을 (실행 파일) 자식 프로세스로 실행시킨다. 이 경우에는 부모로서 실행될 때와 자식으로서 실행될 때의 실행에 차이가 있어야 한다. 부모로 실행될 때는 자식 프로세스를 실행시키며 자식 프로세스로 실행될 때는 악의적인 행위를 해야 하기 때문이다. 이것은 Mutex 같은 기술들을 이용해서 분기를 나눌 수 있다.


  이러한 방식 외에 다른 방식도 존재한다. 실행시킬 때 CreateProcess()의 인자 중 dwCreationFlags를 CREATE_SUSPENDED (0x00000004)로 설정하여 대기 상태로 만든 후 GetThreadContext()로 Context를 읽어와 Entry Point를 수정한 후 SetThreadContext()로 Context를 설정하는 방식이다. 이를 통해 자식 프로세스로 실행될 때는 원래의 EP와는 다른 주소에서 실행될 수 있다. 이렇게 EP를 수정하는 방식 또한 여러 종류가 있으므로 뒤에서 따로 정리를 하겠다.





2.2 Debug Blocker / Self Debugging

  이 방식은 Self Creation 방식에서 더 발전된 기술이다. 코드 가상화 방식을 사용하지 않는 여러 프로텍터들에서 사용되는 방식이기도 하다. 개인적으로는 PEspin 프로텍터를 분석하면서 이 기술을 공부하는 것을 추천한다. PEspin은 이 방식 외에도 여러가지 안티 디버깅 / 안티 덤프 기술이 사용되지만 난이도가 높지 않아서 공부하기 괜찮은 도구이다. [ http://sanseolab.tistory.com/34 ]


  부모 프로세스는 디버거로서 동작하며 자식 프로세스는 디버기로서 동작하는 방식이다. CreateProcess()의 인자 중 dwCreationFlags를 DEBUG_PROCESS (0x00000001) | DEBUG_ONLY_THIS_PROCESS (0x00000002)로 설정하여 자식 프로세스를 디버그 모드로 실행시킨다. 디버거와 디버기의 관계이기 때문에 디버기에 대한 제어가 가능해져서 다양한 방식으로 안티 디버깅을 수행할 수 있다.


  먼저 이 방식은 기본적으로 디버기에서 실행 중에 예외를 발생시키고 이 경우 디버거로 그 제어가 오기 때문에 상황에 맞게 추가적인 조작을 하는 메커니즘이다.


  예외를 발생시키는 방식은 개인적으로 3가지를 파악했으며 더 많은 종류가 있을 수 있다. 하나는 유효하지 않은 명령어를 이용하는 방식이고 다른 하나는 Nanomites라고 불리는데 INT 3 (0xCC) 명령어를 이용한다. 또한 Guard Pages라는 방식도 있는데 특정 코드 영역을 VirtualProtect()를 이용해 실행 불가능하게 만들어서 예외를 발생시킨다. 


  참고로 디버거의 경우 WaitForDebugEvent()와 ContinueDebugEvent()를 이용한 루프를 돌며 디버그 이벤트를 기다린다. 이 경우 DEBUG_EVENT Structure  [ https://msdn.microsoft.com/en-us/library/windows/desktop/ms679308(v=vs.85).aspx ] 부분을 보면 알겠지만 여러 디버그 이벤트들이 존재하는데 예를들면 DLL 로드도 거기에 포함된다. 이벤트 중에서 EXCEPTION_DEBUG_EVENT (0x00000001)를 필터링 하고 이 중에서도 EXCEPTION_RECORD structure [ https://msdn.microsoft.com/en-us/library/windows/desktop/aa363082(v=vs.85).aspx ]의 ExceptionCode에 따라서 필터링을 수행해야 한다.


  참고로 INT 3 명령어를 이용한 Nanomites 방식으로 예외를 일으키는 경우에는 EXCEPTION_DEBUG_EVENT 중에서 ExceptionCode가 EXCEPTION_BREAKPOINT (0x80000003)인 경우에만 디버거가 인식하도록 필터링을 해야할 것이며 이때 원하는 행위를 수행할 것이다. 또한 Guard Pages의 경우에는 EXCEPTION_ACCESS_VIOLATION (0xC0000005)인 경우에 특정 행위를 수행할 것이다.


  지금까지 말했던 특정 행위는 다른 것들과는 달리 디버거와 디버기의 관계이기 때문에 여러 다양한 방식이 사용될 수 있다. 예를들어 간단하게 GetThreadContext()로 문맥을 얻고 수정한 후 (대부분 EIP) SetThreadContext()로 설정하는 방식이 있을 수 있다. 또 다른 방식도 존재하는데 아예 WriteProcessMemory()를 이용해 다른 부분에 저장해 놓았던 코드를 예외가 발생한 부분에 새로 써넣는 방식도 존재한다.





2.3 Process Hollowing / PE Image Switching / RunPE / Process Injection

  주로 악성코드에서 많이 사용되는 방식이라서 그런지 붙여진 이름들이 많다. 또한 세부적인 기술들도 매우 다양한 편이다. 기본적으로 정상 프로그램을 CreateProcess()로 실행시키는데 인자는 CREATE_SUSPENDED를 넣는다. 이후 메모리 내부에 매핑되어 있는 원본 코드 및 데이터 섹션들을 Unmap시킨 후 악성코드를 써넣고 실행시키는 방식이다. 각 단계별로 살펴볼 내용이 많다.


  이 방식을 사용하면 겉은 정상 프로그램이지만 실제로 하는 행위는 악성이기 때문에 여러 모로 인기가 많다고 한다. 그리고 실행 자체가 정상 프로그램이었기 때문에 보여지는 프로세스 이름이나 권한을 그대로 가지고 있으며 드로퍼나 다운로더를 이용해서 악성코드 바이너리를 파일로 생성한 후 실행하는 방식과 같이 파일 형태로 존재하지 않아서 탐지하기도 쉽지 않다. 물론 일반적으로 정상 프로그램에 써질 패킹 또는 암호화된 악성코드가 이 Process Hollowing을 수행하는 바이너리 안에는 존재하기 때문에 정적 분석으로 내부에 의심스러운 부분을 찾을 수 있겠지만 어떠한 악성코드는 심지어 네트워크를 통해 메모리로 악성코드(파일)를 받아서 인젝션하는 경우도 있다고 한다.


  악성코드가 대상으로 하는 정상 프로그램으로는 여러 가지가 있겠지만 개인적으로는 svchost.exe를 대상으로 하는 것을 많이 본 것 같다. 악성코드를 써넣는 방식부터 Context를 수정하는 방식까지 종류가 다양하기 때문에 차례대로 살펴보겠다.



2.3.1 시작

  먼저 CreateProcess()를 이용하 CREATE_SUSPENDED를 플래그로 사용한다. 이후 생성된 프로세스의 Image Base 주소를 알아야 한다. 그래야 이 주소를 기반으로 원본 바이너리를 메모리에서 Unmap할 수 있기 때문이다.


  이것은 두 가지가 있다. 하나는 NtQueryProcessInformation()을 이용한 방식이다. 이 함수를 호출함으로써 PEB를 구할 수 있다. PEB를 구하는 이유는 PEB에 해당 프로세스에 대한 정보가 담겨 있기 때문이며 이후 이 PEB + a 주소에 해당하는 주소를 읽어오면 대상 프로세스에 대한 특정 정보를 획득할 수 있다. PEB는 이 외에도 GetThreadContext()를 이용해 구할 수 있다. CREATE_SUSPENDED로 생성된 프로세스의 경우 Context의 EBX에는 PEB의 주소가 들어가 있다. 이렇게 PEB의 주소를 구했는데 PEB + 0x08 오프셋에는 해당 프로세스의 Image Base 주소가 들어가 있으며 이 주소에 대하여 ReadProcessMemory() 함수를 호출하여 대상 프로세스의 Image Base 주소를 구한다.


  이제 NtUnmapViewOfSection() 함수를 호출하는데 인자로는 대상 프로세스의 핸들과 대상 프로세스의 Image Base 주소를 넣는다. 이를 통해 메모리에 로드된 원본 실행 파일은 Unmap된다. 


  그리고 이 주소에 대하여 VirtualAllocEx() 함수를 이용해 다시 메모리를 할당한다. 권한과 관련된 세부 사항은 무시하겠다. 참고로 그냥 메모리를 할당하는 것은 아니고 악성코드 실행 파일의 Image Base 주소를 구한 후 그 주소에 맞게 Image Base 주소를 맞추어서 할당한다. 그리고 Image Base 주소가 서로 다르다면 PEB의 Image Base 값도 수정해주어야 한다. 이제 WirteProcessMemory()로 악성코드의 실행 파일을 메모리에 올라온 것 처럼 섹션마다 각각 해당되는 오프셋에 써준다.


  이렇게 VirtualAllocEx()  ->  WriteProcessMemory()를 사용하여 악성코드를 쓰는 방식 외에도 다른 방식이 존재한다. NtCreateSection() 함수는 섹션 객체를 생성해 주는 함수로서 인자로는 파일 핸들을 받는다. 즉 악성코드가 파일로 존재하는 경우에는 NtCreateSection()에 파일 핸들을 넣고 섹션 객체를 생성한 후 NtMapViewOfSection()을 통해 매핑할 수 있다.



2.3.2 Entry Point

  마지막으로 Entry Point를 수정한 후 ResumeThread()를 통해 쓰레드를 실행하면 정상적으로 실행된다. EP 부분은 아까 위에서도 언급했지만 자세히 다루어야 한다.


  먼저 배경지식부터 말해보자면 CREATE_SUSPENDED로 실행되어 대기하는 순간 EBX는 PEB의 주소가 들어가 있다고 했다. EAX에는 Entry Point의 주소가 들어가 있다. 우리는 아직 Context를 수정하지 않았기 때문에 EAX에는 정상 프로그램의 EP가 그대로 들어가 있게 되서 악성코드 바이너리의 EP로 수정해야 할 필요가 있다. 이것은 간단하게는 GetThreadContext()로 구한 Context에서 EAX 부분을 수정한 후 SetThreadContext()로 설정하면 된다. 이 외에도 다른 방식이 존재하는데 NtQueueApcThread()를 이용한 방식이다. 자세한 사항은 잘 모르겠지만 이 함수의 인자로 Entry Point를 지정하면 해당 주소에서 실행되게 할 수 있다.


  마지막으로 Context 수정 부분에서 EAX 대신 EIP를 EP로 변경할 수도 있다. 이것은 개인적으로 이해하기 힘든 부분이었다. CREATE_SUSPENDED로 실행시키는 경우 PE Loader가 도중에 멈출 것이고 이후 EAX를 EP로 수정해 놓으면 나머지 초기화(Import Table 복구 등)를 진행한 후 설정된 EP로 가서 실행될 것이라고 생각했다. 하지만 EIP를 바꾸어버리면 제어가 바로 EP로 가기 때문에 Import Table이 복구되지 않아 정상적으로 실행되지 않을거라고 생각했던 것과는 달리 정상적으로 실행되었다.


  완벽하게 파악하지는 못했지만 마지막에 ResumeThread()를 하는 순간 자동으로 Import Table 복구 등 초기화를 담당하는 부분이 실행된다는 것을 확인할 수 있었다. 이것은 Dump를 떠서야 정확하게 파악할 수 있었는데 왜냐하면 Debugger로 Attach하는 순간에도 Import Table이 복구되기 때문이다. 확실한 것은 EP 부근에 무한 루프를 걸어놓고 ResumeThread() 전 후로 덤프를 떠보면 이후에 Import Table이 복구된다는 것이다.


  결론적으로 말하자면 특정 쓰레드가 실행될 즈음에 나머지 초기화가 이루어 진다. 먼저 ResumeThread()의 경우 당연히 쓰레드를 실행하는 역할을 하기 때문이다. 그렇다면 디버거의 경우는 무엇인가라는 생각에 알아보다가 추가적인 사항을 알 수 있었다. [ http://hooked-on-mnemonics.blogspot.kr/2013/01/debugging-hollow-processes.html ]


  간략하게 정리하자면 프로세스를 CREATE_SUSPENDED로 실행할 경우 아직 PEB가 완벽하게 초기화되지 않아서 OllyDbg 1.10의 경우 Attach가 불가능하다. 하지만 Sleep() 함수를 호출하는 간단한 Dummy 쓰레드를 CreateRemoteThread()로 인젝션하면 (쓰레드를 인젝션하는 것이기 때문에 자동으로 실행까지 된다) 비록 Main 쓰레드는 계속 Suspend 상태로 있다고 하더라도 이 Dummy 쓰레드가 실행됨으로써 프로세스 자체의 초기화가 완료되서 (PEB가 완전히 초기화 된다) 이후 OllyDbg 1.10으로 Attach가 가능해진다는 내용이다.


  개인적인 생각이지만 아마 OllyDbg 2에서는 이러한 귀찮음을 방지하기 위해서 Attach하기 전에 쓰레드를 인젝션하기 때문에 CREATE_SUSPENDED 상태인 프로세스도 Attach할 수 있으며 대신 부작용으로 Import Table 및 PEB의 초기화가 완료된다는 것이 확실해 보인다.


  결론적으로 프로세스가 Suspended 상태더라도 쓰레드가 실행되면 적어도 PEB 및 Import Table이 복구되며 구체적으로 무엇이 이러한 역할을 해주는지는 알 수 없지만 이것은 유저 모드에서는 파악할 수 없고 커널 디버깅이 필요한 부분으로 보인다.



2.3.3 Double Process Hollowing

  기술적으로 추가된 부분은 없고 응용한 방식이다. Ursnif라는 악성코드에서 사용되어서 이렇게 이름붙여졌다. 먼저 svchost.exe를 대상으로 Process Hollowing 방식을 사용한다. 문제는 이것이 SYSTEM Integrity Level을 가지고 있어서 Privilege Escalation을 위해 이 부분을 거친다고 한다. 이후 explorer.exe를 대상으로 Process Hollowing 방식을 사용하며 실제 악성 루틴은 여기에서 실행된다고 한다. 


  직접 분석해보지 않아서 몇몇 사항들은 아직 완벽하게는 모르겠다. 먼저 svchost.exe가 평소에 SYSTEM 레벨로 실행된다는 것은 알지만 아무나 실행시켜도 SYSTEM 레벨로 실행됨으로 인해 왜 아직까지도 Process Hollowing 공격의 좋은 공격 대상이 되는지를 이해할 수 없다. 다른 하나는 explorer.exe를 대상으로 Process Hollowing을 하기 위해서는 SYSTEM 권한이 필요한 것인지이다. 마지막으로 explorer.exe에 인젝션하는 것이 아니라 이 방식을 사용한다는 것인데 그럼 원본 explorer는 종료되는 것인지 아니면 2개가 실행되는 것인지 또 종료되서 Process Hollowing으로 실행되는 거라면 그 거대한 explorer의 바이너리 전체를 메모리에서 Unmap시키는 것인지 등이다. 



2.3.4 정리

  악성코드에서 많이 사용되다 보니 보안 프로그램들의 주요한 관심을 받고 있으며 결론적으로 후킹의 대상이 되어 탐지되는 경우가 많아서 앞의 API 함수들을 사용하기 보다는 이러한 함수 내부에서 사용되는 ntdll 함수들을 사용하는 경향이 많다고 한다. 각각의 함수들에 상응하는 ntdll 함수들을 정리하겠다.


CreateProcess() / NtCreateUserProcess()

VirtualAllocEx() / NtAllocateVirtualMemory()

ReadProcessMemory() / NtReadVirtualMemory()

WriteProcessMemory() / NtWriteVirtualMemory()

GetThreadContext() / NtGetContextThread()

SetThreadContext() / NtSetContextThread()

ResumeThread() / NtResumeThread()



2.3.5 분류

- PEB 얻기

NtQueryProcessInformation()

/

GetThreadContext()


- 공간 할당 및 쓰기

VirtualAllocEx()  ->  WriteProcessMemory()

/

NtCreateSection()  -> NtMapViewOfSection()


- 시작 주소 설정 및 시작

Context 조작  ->  SetThreadContext()  ->  ResumeThread()

/

NtQueueAPCThread()





2.4 Process Doppelganging

  이 기술은 크게 두 가지로 나뉜다. 하나는 트랜잭션과 관련된 내용이며 다른 하나는 실행과 관련된 내용이다. 이것을 나누어서 정리하겠다.



2.4.1 커널 트랜잭션 관리자 (Kernel Transaction Manager)

  윈도우는 비스타 이후로 많은 기능들이 추가되었다. 대표적으로 UAC나 볼륨 섀도 복사본과 관련된 내용은 악성코드에도 많이 사용되기 때문에 익숙할 것이다. 추가된 내용에는 이 두 가지 말고도 많이 있는데 막상 깊게 공부할 계기는 없었다. 그러던 중 Process Doppelganging이라는 기술을 공부하면서 KTM과 관련된 내용이 사용되는 것을 보게 되었고 이것도 비스타 이후 추가된 대표적인 기능 중 하나라는 것을 알게 되었다.


  NTFS에 추가된 기능으로서 기본적으로 DataBase와 관련된 개념으로 보인다. 완벽한 이해는 불가능하고 예제를 보면서 이해한 내용을 설명해 보자면 애플리케이션 업데이트나 여러 파일들의 이름 변경 같은 작업 중에 에러가 발생할 수 있다. 이러한 경우 에러가 발생하기 전에 이미 업데이트나 수정이 된 파일들이 있을 것이며 다른 파일들은 아직 원본 상태로 남아 있을 것이다. 그렇기 때문에 어디까지 작업이 완료되었는지 파악하는 것이나 변경 내용들을 모두 기록하고 복구하는 작업들은 매우 힘들다고 한다. Transaction 기능은 업데이트 등의 작업 시에 실제 파일을 건들지 않고 트랜잭션 객체를 이용해 수정한 후 마지막에 Commit을 함으로써 한 번에 작업을 완료할 수 있게 하며 도중에 에러 발생 시 Rollback 시킬 수도 있다.



2.4.2 트랜잭션 객체를 이용해 쓰기

  이러한 방식을 이용한 것인데 먼저 CreateTransaction()을 통해 트랜잭션 객체를 생성한다. 이후 CreateFileTransacted()로 트랜잭션 동작에 사용될 파일을 오픈한다. 이 핸들을 가지고 WriteFile()을 이용해 쓰는 것이다. 참고로 일반적인 방식과 달리 정상적인 파일이 아니라 트랜잭션 파일을 대상으로 한다. 그리고 NtCreateSection()로 이 파일에 대한 핸들을 인자로 넣고 섹션 객체를 생성한다. 이 과정을 통해 만들어졌던 트랜잭션 파일은 RollbackTransaction()을 통해 밀어버린다.



2.4.3 프로세스 실행

  Process Hollowing에서 정리했던 NtCreateSection()은 NtMapViewOfSection()과 함께 사용되었다. 하지만 여기에서는 이 섹션 자체를 사용한다. 프로세스 실행 시 NtCreateProcessEx()를 호출하는데 여기에 인자가 바로 섹션 객체에 대한 핸들이기 때문이다. 사실 이 함수와 관련된 내용도 이야기할 것이 있는데 다음 링크를 참고하자. [ http://sanseolab.tistory.com/58 ]


  이렇게 직접적으로 실행하는 것이기 때문에 추가적으로 설정해야할 일이 많다. NtQueryInformationProcess(), NtReadVirtualMemory()를 통해 PEB 주소를 얻어와 Image Base 수정 같은 것은 기본이며 프로세스의 이름 등도 설정해주어야 한다. 이것은 RtlCreateProcessParametersEx() 함수를 이용하여 Parameter Block을 생성한 후 대상 프로세스에 메모리를 할당하고 써주기까지 해야 한다. 이렇게 써준 Parameter Block의 주소도 PEB에서 수정해 주어야 한다.


  마지막으로는 NtCreateThreadEx()를 이용해 쓰레드를 실행시킴으로써 끝난다.



2.4.4 장단점

  초기 자료이기는 하지만 PPT를 보면 장점이 인상 깊다. 여러 제품들에서 탐지되지 않았다는 것은 지금은 모르므로 제외하고 그 외에도 Fileless라는 점이 있을 것이다. 또 다른 점으로는 Unmapping 시에 보안 솔루션에서 탐지되기 쉬운 편인지 모르겠지만 언매핑을 하지 않아서 보통 탐지되지 않는다는 내용도 보인다. 그리고 DLL 인젝션에도 응용될 수 있다는 점도 있다.


  단점으로는 먼저 NTFS의 KTM이 비스타부터 적용되었기 때문에 비스타 이후의 운영체제에서만 동작하며 이유는 모르지만 윈도우 10에서는 Blue Screen이 뜬다고 한다. 그리고 앞에서의 내용과는 달리 NtCreateThreadEx()를 이용해 실행시키는데 이 함수가 CreateRemoteThread()처럼 스레드를 생성하는 함수이다. 즉 보안 제품의 드라이버에서 사용되는 PsSetCreateThreadNotifyRoutine()에 의해 탐지될 수 있다고 한다.



2.4.5 링크

- 가장 쉽게 잘 정리된 링크 : [ http://kali-km.tistory.com/entry/Process-Doppelganging-1 ]


- PPT 자료 : [ https://www.blackhat.com/docs/eu-17/materials/eu-17-Liberman-Lost-In-Transaction-Process-Doppelganging.pdf ]


- 이것을 구현한 간략한 소스 코드들 : [ https://github.com/Spajed/processrefund

[ https://github.com/KernelMode/Process_Doppelganging/blob/master/main.cpp ]




'악성코드 분석' 카테고리의 다른 글

Autoit 스크립트  (0) 2018.04.01
CreateProcess / CreateThread 내부  (1) 2018.03.03
델파이 바이너리 분석 방법론  (1) 2018.02.25
IDA Pro 시그니처 사용 및 제작 [ Flirt ]  (1) 2018.02.25
Anti-AV와 Anti-VM (Sandbox)  (1) 2017.12.30
Posted by SanseoLab

블로그 이미지
Malware Analyst
SanseoLab

태그목록

공지사항

Yesterday
Today
Total

달력

 « |  » 2024.4
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30

최근에 올라온 글

최근에 달린 댓글

글 보관함