0. 개요

1. 윈도우 사용자 모드 후킹

.... 1.1 인젝션

........ 1.1.1 DLL 인젝션

................ 1.1.1.1 CreateRemoteThread()

................ 1.1.1.2 NtCreateThreadEx() / RtlCreateUserThread()

................ 1.1.1.3 QueueUserAPC()

................ 1.1.1.4 레지스트리 이용 ( AppInit_DLLs, AppCertDlls )

................ 1.1.1.5 SetThreadContext()

................ 1.1.1.6 Reflective DLL Injection

................ 1.1.1.7 Shims

........ 1.1.2 코드 인젝션

................ 1.1.2.1 기본적인 코드 인젝션

................ 1.1.2.2 Atom Bombing

................ 1.1.2.3 EWMI (Extra Window Memory Injection) / Powerloader Injection

................ 1.1.2.4 PROPagate

........ 1.1.3 취약점

........ 1.1.4 윈도우 비스타 이후의 변화

.... 1.2 후킹

........ 1.2.1 IAT 후킹

........ 1.2.2 Inline 후킹

.... 1.3 윈도우에서 제공되는 메시지 후킹

.... 1.4 기타

2. 리눅스 사용자 모드 후킹

.... 2.1 인젝션

........ 2.1.1 Ptrace()

.... 2.2 후킹

........ 2.2.1 PLT / GOT redirection

........ 2.2.2 Inline 후킹

.... 2.3 LD_PRELOAD









0. 개요

  프로그램의 흐름을 빼앗아서 본인이 원하는 코드를 실행시키는 기술을 후킹이라고 한다. 이를 위해서는 먼저 특정한 행위를 수행하는 코드가 존재해야 하기 때문에 이러한 코드를 프로그램에 삽입하는 방법(인젝션)이 필요할 것이며 삽입된 코드에서 실질적으로 프로그램의 흐름을 빼앗는 방법(후킹)이 있을 것이다. 여기서는 크게 윈도우와 리눅스 플랫폼에서의 후킹 및 인젝션 메커니즘을 설명할 것이고 이것은 사용자 모드에 한정한다.





1. 윈도우 사용자 모드 후킹

1.1 인젝션

  위에서 언급하였듯이 전제 조건으로서 먼저 원본 함수를 대체하거나 추가적인 기능을 가진 코드가 프로세스 내에 존재해야 할 것이며 이것 뿐 아니라 이 코드로 흐름이 넘어가도록 후킹을 설치하는 코드도 프로세스 내에 삽입되어 있어야 한다. 이렇게 두 종류의 코드가 존재하면 이후 이 설치 코드를 실행시킴으로써 후킹을 설치하여 궁극적으로 프로그램의 흐름을 빼앗고 원하는 행위를 수행할 수 있다.


  앞의 설명으로 알 수 있듯이 우리가 필요한 것은 저 코드를 삽입하는 것과 삽입된 코드를 실행하는 메커니즘이다. 일반적으로 인젝션은 코드 페이로드를 삽입시키는 코드 인젝션, 그리고 원하는 기능들을 모두 포함하는 DLL을 개발하고 이 파일을 삽입하는 방식인 DLL 인젝션 이렇게 두 가지가 존재한다. 이처럼 해당 프로세스의 가상 주소 공간에서 실행되는 코드들은 프로세스 자체의 권한을 가짐으로써 현 프로세스가 할 수 있는 모든 것을 할 수 있게 된다.


  참고로 지금까지 한 설명은 인젝션을 후킹의 목적으로 여기고 그것과 관련된 부분만 설명하였지만 후킹과 관련 없이 인젝션 자체적으로도 특별한 일을 할 수 있다. 꼭 후킹 함수와 후킹 함수를 설치하는 코드 이외에도 자신이 원하는 코드를 삽입하고 그 코드를 실행시킬 수 있다면 다양한 일을 할 수 있을 것이다. 이 부분은 뒤에서 알아본다.


  코드를 삽입하는 방법들은 뒤에서 차례대로 살펴볼 것이고, 여기서는 상대적으로 설명하기 간단한 코드를 실행시키는 메커니즘을 살펴보겠다. 먼저 DLL 인젝션의 경우 DLL Main에 수행하고자 하는 코드가 추가되어 있다면 DLL 인젝션만으로도 원하는 행위를 수행할 수 있다. DLL Main 부분은 DLL이 로드될 때 자동으로 실행되기 대문이다. DLL이 대상 프로세스 공간에 로드되면 대상 프로세스와 동일한 권한을 가지고 코드가 실행되므로 그 프로세스가 할 수 있는 모든 일을 할 수 있다. 그리고 당연히 가장 대표적인 행위가 후킹이다. 참고로 일반적인 API 후킹에서는 API를 후킹하는 함수를 설치하는 부분이 DLL Main에 존재해서 DLL 인젝션이 되자마자 바로 후킹 함수를 설치한다. 물론 후킹당한 원본 함수를 대체하거나 추가적인 기능은 DLL 내부의 다른 부분에 함수 형태로 존재할 것이다. 


  코드 인젝션인 경우에는 이렇게 자동으로 실행되는 부분은 없지만 WriteProcessMemory()로 페이로드를 써 넣은 후 이 부분을 CreateRemoteThread()를 통해 실행시킬 수 있다.





1.1.1 DLL 인젝션

  여기서는 코드를 삽입하는 방법 중에서 DLL 인젝션 기법을 살펴본다. 궁극적으로 DLL 인젝션은 대상 프로세스가 스스로 LoadLibrary()를 호출하게 만들어서 원하는 DLL을 로드하게 하는 것이다. 이렇게 원격 프로세스에 LoadLibrary()를 호출하게 하는 방식은 당연히 윈도우에서 기본적으로 제공되지는 않을 것이며 아래에서 설명할 여러 종류의 트릭들을 통해 수행된다.



1.1.1.1 CreateRemoteThread()

HANDLE WINAPI CreateRemoteThread (

    __in    HANDLE     hProcess,
    __in    LPSECURITY_ATTRIBUTES   lpThreadAttributes, 
    __in    SIZE_T      dwStackSize, 
    __in    LPTHREAD_START_ROUTINE  lpStartAddress, 
    __in    LPVOID      lpParameter, 
    __in    DWORD       dwCreationFlags, 
    __in    LPDWORD     lpThreadId
);


  이 함수는 임의의 프로세스의 가상 주소 공간에 스레드를 실행시키는 역할을 한다. hProcess는 대상 프로세스의 핸들이고, lpStartAddress는 스레드 함수의 주소이며 lpParameter는 스레드 함수의 파라미터이다. DLL 인젝션을 위해서는 이 함수가 원래 목표처럼 스레드를 생성하는 것이 아니라 실질적으로 LoadLibraryA() 함수를 호출시키게 만들어야 한다. 즉 스레드가 아닌 LoadLibraryA() 함수를 실행 시키는 것이고 이 함수의 인자에 자신이 작성한 DLL의 경로를 적어주는 것이다. 이에 따라 임의의 프로세스가 스스로 LoadLibraryA()를 호출하여 악의적인 DLL을 가상 주소 공간에 로드할 것이다. 


  먼저 OpenProcess()를 통해 대상 프로세스의 핸들을 구해야 할 것이고, 대상 프로세스의 메모리에 삽입할 DLL의 경로를 대상 프로세스에 써야 한다. 이것은 VirtualAllocEx() API를 이용해서 대상 프로세스의 메모리 공간에 버퍼를 할당한 후에 WriteProcessMemory() API를 통해 경로가 적힌 문자열을 써준다. 그리고 LoadLibraryA() API의 주소를 구해야 하는데 이것은 GetModuleHandle() API에 kernel32.dll을 인자로 넣어서 핸들을 구하고 GetProcAddress() API에 이 핸들과 LoadLibraryA 문자열을 넣음으로써 LoadLibraryA() 함수의 주소를 구하게 된다. 이제 처음에 설명했듯이 CreateRemoteThread() 함수를 실행하여 원하는 DLL을 로드한다.


  CreateRemoteThread()를 통해 어떻게 LoadLibraryA()가 호출될 수 있는지 조금 더 설명해 보자면 CreateRemoteThread()의 인자이자 원격에서 실행되는 스레드는 ThreadProc 콜백 함수이다. 이 스레드 프로시저는 인자가 4바이트 하나이며 반환값도 4바이트를 반환하는데 이 형태가 LoadLibraryA()와 같아서 이러한 트릭이 통하는 것이다.



1.1.1.2 NtCreateThreadEx() / RtlCreateUserThread()

  윈도우 7부터는 CreateRemoteThread()를 이용한 기법에 제한이 생겼지만 내부 함수인 NtCreateThreadEx() API를 사용하면 된다. 하지만 NtCreateThreadEx()가 윈도우 버전에 따라 수정될 수 있기 때문에 이것의 래퍼 함수인 RtlCreateUserThread()의 사용이 추천된다.



1.1.1.3 QueueUserAPC()


DWORD WINAPI QueueUserAPC(
  _In_ PAPCFUNC  pfnAPC,
  _In_ HANDLE    hThread,
  _In_ ULONG_PTR dwData
);


  APC(asynchronous procedure call)는 비동기 함수 호출 메커니즘으로써 일반적인 함수 호출인 SPC(synchronous procedure call)와 구별된다. 모든 스레드는 APC Queue를 가지고 있는데 이 Queue에는 APC 함수 및 파라미터를 저장할 수 있다. QueueUserAPC()는 사용자 모드 APC 객체를 APC Queue에 추가해주는 함수이다. 만약 APC Queue에 APC 함수가 추가되어 있고 동시에 해당 스레드가 Alertable State에 놓이면 이 APC 함수가 호출된다.


  이것을 이용하여 DLL 인젝션을 수행하는 방법을 알아보겠다. 먼저 첫 번째 인자인 pfnAPC에는 LoadLibrary()의 주소를 지정한다. 즉 호출함 함수로 LoadLibrary()를 설정하는 것이다. 이것은 CreateRemoteThread()를 이용한 방식과 비슷하다. 그리고 세 번째 인자 dwData도 비슷하게 VirtualAllocEx() 및 WriteProcessMemory()를 이용해 대상 프로세스에 써넣은 DLL 경로를 지정한다. 마지막으로 두 번째 인자인 hThread 즉 대상 쓰레드를 지정하면 된다. 이것은 인젝션할 프로세스의 PID를 이용해 GetMainThreadId로 스레드 ID를 구하고 OpenThread()로 해당 스레드의 핸들을 구한 후 지정하면 된다.


  이후 QueueUserAPC()를 호출하면 APC Queue에 APC 함수로서 LoadLibrary()가 그리고 인자로서 DLL의 경로가 Queue에 추가된다. 마지막으로 WiatForSingleObject()를 통해 대상 스레드를 Alertable State로 놓으면 LoadLibrary()가 호출된다.


  참고로 스레드를 Alertable State로 만들어주는 API로는 SleepEx(), SignalObjectAndWait(), WaitForSingleObjectEx(), WaitForMultipleObjectsEx(), 그리고 MsgWaitForMultipleObjectsEx()가 있다. Alertable State에 대해 조금 더 설명해 보기 전에 일반적으로 스레드들은 Ready, Running, Waiting State가 존재한다는 것을 기억해보자. 실제로는 이 3가지 State 외에도 Alertable State가 존재하는데 이것은 사용자가 조정할 수 있는 유일한 스레드의 상태이다.



1.1.1.4 레지스트리 이용 ( AppInit_DLLs, AppCertDlls )

[ HKLM\Software\Microsoft\Windows NT\CurrentVersion\Windows\AppInit_DLLs ]

  이 레지스트리 값은 디폴트로 비어있다. 하지만 여기에 원하는 DLL들의 경로를 써주면 USER32.dll을 로드하는 모든 프로세스에서 원하는 DLL들이 강제로 로드된다. 약간의 설명을 덧붙이자면 USER32.dll은 GUI 환경인 윈도우에서 기본적으로 사용하는 DLL로서, 이 DLL의 DLL_PROCESS_ATTACH 과정에서 AppInit DLL들을 LoadLibrary() 함수를 이용해 로드하는 것이다.


[ HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\AppCertDLLs ]


  이 외에도 AppCertDlls 레지스트리도 존재한다. 위와의 차이점은 CreateProcess(), CreateProcessAsUser(), CreateProcessWithLogonW(), CreateProcessWithTokenW(), WinExec()를 호출하는 프로세스에만 로드된다는 것이다.



1.1.1.5 SetThreadContext()

  먼저 DLL 삽입 후 다시 복귀하는 역할을 하는 인라인 어셈블리 루틴이 필요하다. 이 루틴은 LoadLibraryA()를 통해 악의적인 DLL을 삽입할 것이고 다시 실행을 재개 즉 복귀해야 한다. 참고로 복귀 주소는 현재 알 수 없기 때문에 뒤에서 추가해야 한다. 인젝션 프로그램은 대상 프로세스에 VirtualAllocEx()로 메모리를 할당하고 이 루틴을 써 넣는다. 이후 SuspendThread()로 대상 프로세스의 스레드를 정지시키고 GetThreadContext()로 Context를 얻는다. 그리고 Context의 EIP를 앞에서 설명한 루틴의 복귀 주소로 설정한다. 이후 EIP를 어셈블리 루틴의 시작 주소로 변경하고 SetThreadContext()로 Context를 저장한 후에 ResumeThread()로 실행을 재개한다. 결론적으로 변경된 EIP를 통해 어셈블리 루틴이 실행될 것이고 이것은 주어진 DLL을 삽입하고 아까 저장했던 원본 EIP의 주소로 ret한다.



1.1.1.6 Reflective DLL Injection

  대상 프로세스를 RWX 권한으로 오픈하고 VirtualAllocEx() 함수를 이용해 DLL이 들어갈 만한 메모리를 할당한다. 일반적으로 DLL은 드로퍼 형태로 리소스 섹션에 저장되어 있는데 이 DLL을 읽어와 할당한 곳에 복사한다. 중요한 것은 이 DLL은 현재 파일 형태로 존재한다는 점이다. 그래서 로더를 통해 로드된 것처럼 현재 파일 오프셋으로 존재하는 각 섹션을 가상 메모리에 올라온 형태로 재배치해야 한다. 이것은 블로그의 SimplePacker 문서에서도 패커를 개발하면서 다루었다. 이 뿐만 아니라 DLL에서 사용하는 함수들의 IAT도 수정해야 한다. 이 함수들의 주소는 LoadLibrary()와 GetProcAddress()를 이용해서 얻어올 수 있다. 참고로 이 함수들은 당연히 대상 프로세스 내에서 호출되기 때문이 이 함수들의 주소는 또 어떻게 얻어야 하냐는 문제가 발생한다. 이것은 대상 프로세스의 PEB를 찾고 이것을 통해 kernel32.dll의 메모리 주소를 찾은 후에 Export Table을 이용해 얻어내는 방식을 사용한다.


  지금까지의 과정을 보면 알겠지만 이 과정들은 모두 DLL을 직접 로드하는 방식이다. PE에 존재하여 실행 시 자동으로 로드된 경우나 도중에 LoadLibrary()를 호출한 경우에는 로더가 모든 과정을 알아서 해주지만 이 방식은 순수하게 로더의 역할을 수행해야 한다. 마지막으로 CreateRemoteThread()를 통해 그 주소를 엔트리 포인트로 하여 실행시킨다. 참고로 일반적인 코드 인젝션에서는 CreateRemoteThread()를 이용해 LoadLibrary()로 DLL을 로드함과 동시에 실행시키지만 여기서는 직접 로더의 역할을 통해 DLL을 로드하고 CreateRemoteThread()로는 직접 주소를 넣어 실행시키는 것이다.


  이것을 응용한 내용을 알게되어 먼저 정리부터 한 후에 추가하도록 하겠다. Reflective DLL Injection 방식은 먼저 대상 프로세스에 메모리를 할당하게 한다. 참고로 VirtualAllocEx() 대신 내부의 NtAllocateVirtualMemory()를 사용할 수도 있다. 이렇게 대신 사용할 수 있는 ntdll 함수들은 많이 존재하므로 단지 저 함수들만 사용해야 한다는 생각은 버리자. 전체는 아니지만 다음 링크에 간략하게 정리된 함수들이 있다.


[ http://sanseolab.tistory.com/57 ]

[ http://sanseolab.tistory.com/58 ]


  참고로 CreateRemoteThread()의 경우는 다음과 같다.

[ CreateRemoteThread() -> CreateRemoteThreadEx() -> NtCreateThreadEx() ]


  어쨌든 할당한 후 로더처럼 섹션에 맞게 써줄 것이고 (WriteProcessMemory / NtWriteVirtualMemory) 이후 CreateRemoteThread()를 이용해 쓰레드를 실행시킨다. 원래 존재하던 쓰레드들은 처음부터 상관 없이 잘 실행되고 있을 것이며 이후 이 인젝션된 DLL의 DllMain() 부분이 새로운 스레드로서 추가되어 실행되는 것이다.


  이것을 응용할 수 있는 방식으로 여러가지가 있을 것이며 개인적으로 정리된 문서를 보게되어 여기에 간략하게 추가하려고 한다. 링크는 다음과 같다.


[ https://zerosum0x0.blogspot.kr/2017/07/threadcontinue-reflective-injection.html ]


  사실 코드나 DLL 인젝션 방식들은 모두 탐지를 회피하기 위해 발전되고 있다. Reflective DLL 인젝션의 경우도 그러한데 문제는 눈치챘겠지만 코드를 실행시킬 때 CreateRemoteThread()를 이용한다는 것 자체가 조금 문제가 있다. 그래서 이것의 내부 함수 즉 앞에서 간략하게 쓴 ntdll 함수를 이용하던지 아니면 APC Queue를 이용한 방식으로서 코드를 실행시키는 방식으로 응용할 것이다. 이 문서에서 나온 방식은 이러한 함수들 대신 ResumeThread()와 NtContinue()를 이용해 실행시키는 방식이다.


  먼저 SuspendThread()로 대상 프로세스의 쓰레드를 Suspend 시킨다. 그리고 프로세스에 인젝션할 DLL의 공간 뿐만 아니라 Context가 저장될 부분도 메모리를 할당한다. 이 Context는 인젝션하는 프로그램 뿐만 아니라 대상 프로세스 내부에서도 추후에 사용될 것이다. 이후 섹션별로 DLL의 내용을 써준 후에 SetThreadContext()로 DllMain()의 위치를 지정한다. 또한 스택을 위해 스택 포인터를 더 낮은 주소로 맞추어 준다. 스택을 맞추는 것이 필요한 이유는 일반적인 경우와 이 방식이 다르기 때문이다. 새로 쓰레드로서 실행되는 경우에야 각 쓰레드 별로 스택이 할당되기 때문에 상관이 없지만 이 경우는 잘 실행되던 쓰레드를 멈추고 제어를 삽입한 DllMain() 부분으로 옮긴 것이기 때문이다.


  이후 ResumeThread()를 실행하면 DllMain() 부분이 실행되며 여기서 추가적인 작업을 수행할 수 있다. 당연히 현재 부분을 새로운 쓰레드로서 실행하기 위해서 CreateThread()를 이용해 작업을 수행한다. 중요한 점은 삽입된 코드는 이제 새로운 쓰레드로서 실행되지만 원본 프로그램의 경우 제어가 강제로 여기까지 (DllMain) 와버렸다. 하지만 우리는 아까 Context를 대상 프로세스의 메모리 공간에 복사해 놓았으며 동시에 스택도 오염되지 않게 스택 포인터를 바꾸어 놓았었다. 이제 DllMain()의 남은 부분에서는 저장된 Context를 인자로 넣고 NtContinue()를 실행하면 원래 진행하던 코드에서 그대로 진행할 수 있게 된다. 혹시 DllMain() 부분을 진행하다 오염되지 않게 스택 포인터를 저 멀리로 설정해 놓았었으므로 스택 메모리도 오염되지 않은 상태로 남아 있을 것이다.




1.1.1.7 Shims

  MS에서는 하위 호환을 위해 "Shim"이라는 개념을 도입했다. 이를 통해 개발자가 해당 애플리케이션을 직접 수정하지 않도록 해주는 편의성이 있지만 보안 문제가 생기게 된다. 이것은 후킹을 이용한 방식으로서 운영체제에게 이 애플리케이션을 어떻게 다루어야 하는지를 설정해줄 수 있다. 예를들면 인자 처리라던지 특정한 동작에 대한 처리 방식이라던지. 


  이러한 설정은 Shim database (sdb) 파일에 저장된다. 즉 소스 코드를 수정하여 다시 만들거나 패치를 적용하는 것 대신 이 파일을 통해 수정 사항을 적용시킬 수 있다. 해당 파일에서 사용할 수 있는 기능들을 보면 충분히 위험하게 사용될 수 있는 것들이 많이 보인다. "InjectDll"부터 시작하여 "DisableNX", "DisableSeh", "ForceAdminAccess", "ShellExecuteXP" 등이 있다.


  어떤 공격에서는 파워셸을 이용하여 sdbinst.exe 유틸리티를 실행시켜 악성 sdb를 등록시켰다. 이 sdb는 시스템 프로세스나 다른 애플리케이션에 대한 패치 또는 DLL 인젝션을 수행하여 공격하는 방식을 사용하였다.





1.1.2 코드 인젝션

1.1.2.1 기본적인 코드 인젝션

  지금까지 DLL 인젝션 방식을 설명했고 이제 코드 인젝션을 알아보겠다. 코드 인젝션은 설명하기는 더 간단하지만 실제로 구현하기는 상당히 까다롭다. 위에서 DLL의 경로를 담은 문자열을 대상 프로세스에 쓸 때 VirtualAllocEx() API를 이용해서 대상 프로세스의 메모리 공간에 버퍼를 할당한 후에 WriteProcessMemory() API를 통해 원격 프로세스에 메모리를 써 넣었다. 코드 인젝션은 문자열이 아니라 페이로드 전체를 VirtualAllocEx()와 WriteProcessMemory()를 통해 써 넣는다. 참고로 페이로드는 Thread Procedure 형태(ThreadProc 콜백 함수)로 만들 수도 있고 직접 어셈블리 루틴의 바이너리를 바이트 배열로 만들 수도 있으며 inline 어셈블리 루틴으로 만들 수도 있다.


  하지만 까다로운 부분이 존재하는데 DLL로 만든 경우에는 자동으로 컴파일되어 세세한 부분을 다룰 일이 없겠지만 이렇게 직접 코드 인젝션으로 페이로드를 삽입하는 경우에는 고려해야할 사항이 존재한다. 먼저 페이로드에서 API 호출이 사용되는 경우에는 그 주소를 호출하는 대상 프로세스에서의 주소가 다를 수 있기 때문에 (DLL 재배치나 ASLR 등으로 인하여) 정확한 주소를 알아낸 후에 추가하여야 한다. 또한 페이로드에서 함수 호출에 사용되는 인자 값 같이 데이터가 필요한 경우도 있는데 이 때에도 데이터 부분 또한 코드 페이로드 처럼 따로 삽입하고 코드 페이로드에서는 주소를 설정해 주어야 한다.


  어쨌든 DLL이 아니어서 자동으로 호출되는 메커니즘이 없기 때문에 위에서 설명했듯이 CreateRemoteThread() 함수를 사용하여 써 넣은 페이로드를 실행시킨다. 참고로 DLL 인젝션에서는 LoadLibrary() 함수를 실행시켰었다.



1.1.2.2 Atom Bombing

  이 방식은 일반적으로 사용되는 VirtualAllocEx(), WriteProcessMemory(), CreateRemoteThread() 등의 함수를 사용하지 않고 Atom Table이라는 메커니즘을 악용하여 코드를 삽입하고 실행시키는 메커니즘이다.


  공격자는 먼저 GlobalAddAtom()을 통해 인젝션할 코드를 Global Atom Table에 추가한다. 이후 ntdll!RtlDispatchAPC() [ QueueUseApc() -> NtQueueApcThread() -> ntdll!RtlDispatchAPC() ]를 통해 공격 대상 프로세스가 GlobalGetAtomName()을 호출하게 함으로써 추가한 코드를 읽게 한다. 이 메커니즘을 통해 WriteProcessMemory() 없이도 코드를 인젝션하게 된다.


  문제는 VirtualAllocEx()로 RWE 메모리를 할당하고 여기에 WriteProcessMemory()로 인젝션할 코드를 삽입한 위의 방식과는 달리 현재 코드는 인젝션되어 있지만 이 영역에는 실행 권한이 없다. 이 코드를 실행시키기 위해 ROP 체인을 이용하는데 사용되는 ROP 체인은 결과적으로 ZwAllocateVirtualMemory()를 호출하여 RWE 메모리를 할당하고 memcpy()로 인젝션한 코드를 복사하는 역할을 한다.


  마지막으로 실행 흐름을 다시 복구해주는 것도 가능하다.


http://www.reversenote.info/atombombing-stage1/ ]

http://www.reversenote.info/atombombing-stage2/ ]

http://www.reversenote.info/atombombing-stage3/ ]



* 서브클래싱

  EWMI와 PROPagate 인젝션을 정리하면서 윈도우의 서브클래싱과 관련된 이해 없이는 완벽히 파악할 수 없을 것 같다는 생각이 들었다.


[ https://docs.microsoft.com/en-us/windows/desktop/controls/subclassing-overview ]


  해당 링크를 보면 과거의 서브클래싱 방식과 현재의 서브클래싱 방식이 정리되어 있다. SetWindowLong() 및 SetWindowLongPtr()을 이용하는 방식은 과거 버전으로 보이며, Win32 GUI 프로그래밍 등에서 흔히 보던 방식은 최신 방식으로서 SetWindowSubclass() 방식이 사용되는 것 같다. 물론 아직도 정확한 이해는 하지 못했다. 추가적으로 SetProp() 함수는 개개의 윈도우에 대해 사용자 데이터를 저장하는데 사용된다.


  어쨌든 정리하자면 서브클래싱은 윈도우 메시지를 가로채는 일종의 후킹이며 분석하는 입장에서는 메시지 후킹 시의 SetWindowsHookEx() 메커니즘을 생각하면 될 것 같다.



1.1.2.3 EWMI (Extra Window Memory Injection) / Powerloader Injection

  이것은 Explorer Tray Winow (Shell_TrayWnd)의 추가적인 윈도우 메모리에 코드를 인젝션하는 방식이다. 윈도우 클래스를 등록할 때 애플리케이션은 메모리의 추가적인 바이트들을 설정할 수 있으며 이것이 Extra Window Memory라고 불린다. 물론 여기에는 충분한 공간이 없기 때문에 악성코드는 explorer.exe의 공유 섹션에 셸코드를 삽입하며 SendNotifyMessage()와 SetWindowLong()을 이용해 해당 셸코드를 가리키는 함수 포인터를 갖고 이것을 실행시킨다.


  GetWindowLong()은 윈도우 클래스 오브젝트의 EWM에 대한 주소 값을 얻어오는 역할을 하며 SetWindowLong()은 여기에 대한 주소 값을 변경해주는 역할을 한다. 이를 통해 악성코드는 윈도우 클래스에서 함수 포인터의 오프셋을 셸코드의 주소로 변경할 수 있다. 셸코드는 OpenSection()을 통해 공유 섹션을 오픈한 후에 직접 써 넣는다. 참고로 공유 섹션에는 RW 권한 밖에 없기 때문에 ROP 방식이 사용된다.


  마지막으로 해당 코드를 실행하기 위해서는 SendNotifyMessage()를 이용한다. 이 함수는 주어진 메시지를 해당 윈도우의 프로시저로 전달하여 다루게 한다. 즉 악성코드가 이것을 호출할 경우 explorer.exe 내부의 윈도우 프로시저가 트리거되는 것이다.


  참고로 Ensilo에서는 PowerLoaderEx라는 추가적인 방식을 소개하기도 했다.



1.1.2.4 PROPagate


[ https://modexp.wordpress.com/2018/08/23/process-injection-propagate/ ]


  위의 링크에 잘 정리되어 있어서 해당 링크를 참고하여 정리한다. 마지막 전체 소스 코드 부분을 보면 먼저 FindWinowEx() 류의 함수를 통해 특정 윈도우에 대한 핸들을 얻고, 이 핸들을 가지고 GetProp()으로 서브클래스 헤더에 대한 핸들을 얻는다. 참고로 이 예제에서는 윈도우7과 윈도우10 모두에 해당하는 "Progam"이라는 부모 윈도우 및 "SHELLDLL_DefView"라는 자식 윈도우를 대상으로 하며, 서브클래스 헤더는 "UxSubclassInfo"를 대상으로 한다.


  이후 explorer.exe를 대상으로 새로운 서브클래스 헤더 및 셸코드를 위한 공간을 할당하고 쓴다. 그리고 서브클래스 헤더의 pfnSubclass 필드를 셸코드의 주소로 변경하고 쓴다. 마지막으로 SetProp()을 이용해 explorer의 "UxSubclassInfo" 서브클래스의 프로시저가 셸코드 주소로 "업데이트"된다. 이제 PostMessage() 함수를 이용해 explorer에 WM_CLOSE 메시지를 보내면 셸코드가 실행된다.





1.1.3 취약점

  인젝션 기법은 아니지만 DLL Hijacking 취약점의 경우 악성 DLL을 로드하게 되며 DllMain()을 통해 로드 시에 코드가 실행되기 때문에 여기에 포함하기로 하였다.





1.1.4 윈도우 비스타 이후의 변화

  간단했던 윈도우 XP와 달리 비스타 부터는 변화가 많이 생겼다. 이를 설명하기 위해 바뀐 부분들을 위주로 더 자세히 설명해 나가도록 하겠다. DLL 인젝션을 수행하기 위해 가장 먼저 필요한 것이 대상 프로세스의 핸들이다. 우리는 인자로 우리가 필요한 접근 권한을 넣고 OpenProcess()를 호출하여 핸들을 얻는다. 이 접근 권한이 중요한데 예를들면 뒤에서 사용될 ReadProcessMemory() 함수를 사용하기 위해서는 프로세스에 대한 PROCESS_VM_READ 접근 권한이 있어야 한다. WriteProcessMemory() 함수는 PROCESS_VM_WRITE, PROCESS_VM_OPERATION 접근 권한이 필요하다. CreateRemoteThread()의 경우에는 PROCESS_CREATE_THREAD, PROCESS_QUERY_INFORMATION, PROCESS_VM_OPERATION, PROCESS_VM_WRITE, PROCESS_VM_READ 권한이 필요하다. 마지막으로 VirtualAllocEx()와 VirtualFreeEx()는 PROCESS_VM_OPERATION 권한이 필요하다. 일반적으로는 이것들을 모두 쓰기 보다는 PROCESS_ALL_ACCESS를 사용해서 OpenProcess()를 호출한다.


  중요한 점은 윈도우 비스타부터는 OpenProcess() 호출 시 호출하는 프로세스와 같거나 더 낮은 Integrity Level을 가진 프로세스에 대해서만 이러한 접근 권한을 얻어올 수 있다는 것이다. 만약 더 높은 Integrity Level을 가진 프로세스에 대해서 PROCESS_ALL_ACCESS 접근 권한을 얻어오려고 한다면 OpenProcess()는 에러를 반환한다. 권한과 관련한 내용은 이 블로그의 다른 글을 참고한다. 


  주제를 바꿔서 이제 원하는 권한을 가진 핸들을 받았다고 치고 위에서 언급한 함수들을 이용해 DLL 인젝션을 수행할 수 있다. 하지만 한가지 더 추가된 것이 있는데 Session Isolation 정책이 그것이다. 이 정책으로 인해 이제 CreateRemoteThread()는 같은 세션에 해당하는 프로세스들에만 DLL 인젝션을 수행할 수 있게 되었다. 참고로 리버싱 핵심원리에 따르면 ReadProcessMemory, WriteProcessMemory, VirtualAllocEx() 같은 함수들은 세션과 관련 없다고 한다.


  참고로 시스템 서비스들은 세션 0을 갖고 각 사용자들은 다른 숫자들을 부여받게 된다. 이에 따라 주로 DLL 인젝션의 대상이 되왔던 시스템 서비스들에게는 더이상 DLL 인젝션이 통하지 않는 것이다. 물론 이것도 우회하는 방법이 있는데 NtCreateThreadEx(), RtlCreateUserThread() 등의 네이티브 api를 사용하면 된다. 이 함수들을 통해 다른 세션의 프로세스에 DLL 인젝션을 수행할 수 있게 되었다.


  비록 이제 세션의 장벽을 넘었다고 해도 위에서 언급하였듯이 Integrity Level이라는 한계가 존재한다. 현재 프로세스가 관리자 권한을 가지고 있다고 해도 시스템 서비스들은 High Integrity Level보다 높은 System Integrity Level을 가지고 있다. 이것을 우회하는 방법은 프로세스에 SeDebugPrivilege 권한을 주는 것이다. 이 디버그 권한을 갖는다면 접근 통제 정책을 우회할 수 있기 때문에 System Integrity Level에 대해 DLL 인젝션을 수행할 수 있다. 하지만 이것도 한계가 있는데 SeDebugPrivilege 권한은 관리자 계정에 주어지기 때문이다. 그래서 관리자 권한 즉 High Integrity Level이어야 이 디버그 권한을 활성화시킬 수 있다. AdjustTokenPrivileges() 호출 시 인자로 상수 SE_DEBUG_NAME 즉 텍스트로 SeDebugPrivilege를 넣는 부분이 이 내용이다.


  예를들어 흔한 상황을 가정해 본다면 현재 프로세스는 관리자 권한이 아니라 표준 사용자 권한으로 실행 중일 것이며 우리는 DLL 인젝션을 수행하기 위해 세션이 같은 것을 찾아야 한다. explorer.exe는 항상 실행 중인 프로세스이며 이 두가지 즉 같은 세션에 같은 Medium 권한을 갖는다. 이에 따라 explorer.exe는 DLL 인젝션의 대상이 되는 경우가 많다. 관리자 권한 없이도 손쉽게 DLL 인젝션의 대상이 될 수 있기 때문이다. 만약 관리자 권한을 갖게 된다면 디버그 권한을 활성화시켜 이제 어디든 DLL 인젝션을 수행할 수 있게 된다.









1.2 후킹

  지금까지 원하는 코드를 DLL의 형태로든 코드 자체로든 삽입하였으며 실행시키는 메커니즘까지 알아보았다. 이제는 프로그램의 흐름을 가로채서 이 코드를 실행시키기는 후킹 방식을 알아보겠다. 후킹의 대상으로는 대표적으로 함수 호출이 있을 것이고 이 외에도 메시지나 이벤트 등이 있을 수 있다. 여기서는 가장 많이 사용되는 API 함수 호출 후킹을 대상으로 하겠다.


  후킹 기법도 여러 종류가 있겠지만 여기서는 가장 많이 사용되는 방식들인 IAT 후킹과 Inline 후킹에 대해서 다룰 것이다. 후킹은 간단하지만 목적에 따라 수많은 다양한 방식들이 나올 수 있으므로 간단하게만 정리한다.


  참고로 보안 솔루션들도 당연히 후킹을 통해 구현되는데 일반적으로 인라인 후킹이 많이 사용된다고 한다. 물론 IAT 후킹을 사용하는 제품들도 존재한다. 또한 DLL 내부에 구현되는데 (즉 대부분 DLL 인젝션을 사용한다) 후킹되어 실행할 루틴들이 각 함수로 구현되어 있으며 dllmain()에 후킹을 설치하는 루틴이 존재하여 DLL 인젝션 시에 dllmain()이 자동으로 실행되어 후킹을 설치한다. 이 DLL을 다른 말로 후킹 엔진이라고도 부른다. 후킹 엔진에는 오픈 소스인 EasyHook, Deviare2 외에도 마이크로소프트의 detours 그리고 상용 제품인 Madcodehook 등이 존재한다. 



1.2.1 IAT 후킹

  리버싱을 하다 보면 API 함수를 호출할 때 call 명령어에 의해 호출되는 주소가 직접적으로 해당 라이브러리에 위치한 API 함수의 시작 주소인 것이 아니라 간접 호출 방식을 사용함으로 인해 IAT 영역을 가리키는 것을 볼 수 있다. 이 IAT 영역의 주소에 그 API의 실제 주소가 들어 있으며 이를 통해 API가 호출되는 것이다. IAT 후킹은 IAT 영역에 위치한 후킹 대상 API 함수의 주소를 후킹 함수 주소로 변경하는 방식이다. 이에 따라 코드 영역에서는 똑같은 IAT 영역을 가리키지만 이 IAT에 들어있는 주소가 후킹 함수로 변경되어 있으므로 설치된 후킹 함수가 호출된다.



1.2.2 Inline 후킹

  기본적으로 Inline 후킹의 특징 상 여러 방식을 통해 코드를 수정하여 후킹 함수를 설치할 수 있다. 가장 대표적으로는 처음 5 바이트( mov edi, edi  /  push ebp  /  mov ebp, esp )를 jmp 문으로 수정하여 후킹 함수로 분기시키는 방식이다. 물론 우리가 수정함으로써 실행되지 못한 위의 명령어는 내부에서 따로 실행시켜 주어야 한다. 따로 실행시켜 주어야 할 5 바이트는 위와 같이 항상 ( mov edi, edi  /  push ebp  /  mov ebp, esp )이다. 또는 다른 방식을 사용할 수도 있다. "mov edi, edi" 명령어가 시작하는 주소의 바로 앞 5바이트를 jmp 문으로 수정한다. 이 부분은 항상 비어있는 공간이므로 안전하게 수정할 수 있다. 그리고 2바이트의 "mov edi, edi"를 해당 jmp 문 즉 jmp $-5로 변경한다. 이것은 short jmp이기 때문에 2바이트 밖에 차지하지 않는다. 


  지금까지는 x86에서의 설명이었고 x64에서는 다른 방식이 필요하다. 주소 공간이 커서 5바이트 jmp 명령어로는 부족할 수 있기 때문이다. x86 뿐만 아니라 x64에서의 인라인 후킹은 다음 문서를 참고한다. [ https://www.blackhat.com/docs/us-16/materials/us-16-Yavo-Captain-Hook-Pirating-AVs-To-Bypass-Exploit-Mitigations.pdf ]





1.3 윈도우에서 제공되는 메시지 후킹

HHOOK SetWindowsHookEx (
   int idHook,
   HOOKPROC lpfn,
   HINSTANCE hMod,
   DWORD dwThreadId
);


  SetWindowsHookEx() 함수는 메시지 후킹을 위해 지원되는 함수이다. Windows는 Event 구동 방식으로 동작하는데 이런 이벤트가 발생할 때 메시지를 통해 해당 어플리케이션에 통보되며 어플리케이션은 메시지를 받아서 분석한 후 해당하는 작업을 수행한다. 메시지 후킹은 중간에서 메시지를 가로채는 것이다. 예를들면 운영체제가 키보드에서 신호를 받아서 처리한 것이 메시지가 되고 이것이 메시지 큐에 있다가 거기에 맞는 응용 프로그램에 메시지를 넘겨준다.


  SetWindowsHookEx()는 인자로 dwThreadId를 받아서 대상 스레드(프로세스보다 더 구체적인)를 정하고 idHook을 통해 후킹할 메시지를 선택하며, lpfn은 그 스레드가 받는 특정 메시지를 후킹하여 대신 받아서 처리할 프로시저를 의미하고 마지막으로 그 프로시저가 들어 있는 DLL을 hMod로 받는다. 즉 특정한 스레드가 받는 특정한 메시지를 처리할 프로시저를 DLL 형태로 개발하고 이 함수를 이용하여 메시지 후킹을 설치하는 것이다.





1.4 기타

  이 항목에서는 여러가지 참고 사항들을 정리하기로 하겠다. 첫 번째 주제는 보안 프로그램 개발 시 사용자 모드 후킹을 어떻게 탐지할 것인가이다. 일반적으로 API 후킹의 경우 후킹 대상이 될만한 함수의 엔트리 포인트를 모니터링하여 방지한다고 한다. 이러한 모니터링도 결국은 미리 후킹을 해서 감시하는 것이다. 또한 코드 섹션의 해시 검사 또는 CRC 검사를 수행하여 변경되었는지를 검사할 수도 있다. 









2. 리눅스 사용자 모드 후킹

2.1 인젝션

2.1.1 Ptrace()

  ptrace()를 이용해 코드 인젝션 또는 .so 인젝션을 수행하는 방식이다. 요약해서 PTRACE_ATTACH를 이용해 대상 프로세스를 어태치하고 PTRACE_POKETEXT를 통해 메모리에 쓴 후 PTRACE_SETREGS로 EIP를 삽입한 코드로 변경한 후에 PTRACE_CONT로 실행을 재개한다. 물론 PTRACE_GETREGS와 PTRACE_SETREGS를 통해 문맥을 다시 복구하는 등의 절차도 필요하다.





2.2 후킹

2.2.1 PLT / GOT redirection

  윈도우에서는 API를 호출할 때 IAT에 있는 주소를 참조하여 간적 호출 방식을 사용하였다. 리눅스에서는 PLT와 GOT를 이용한 메커니즘이 사용된다. PLT/GOT redirection은 GOT에 존재하는 주소를 후킹 함수의 주소로 변경한다.



2.2.2 Inline 후킹

  인라인 후킹의 특징이 그렇듯, 윈도우에서처럼 라이브러리에 있는 함수의 시작 부분을 후킹 함수의 주소로 변경할 수도 있고 앞에서 설명했던 PLT 부분을 수정할 수도 있고 아니면 코드 섹션의 함수 호출 명령어의 주소를 직접 변경할 수도 있을 것이다.





2.3 LD_PRELOAD

  리눅스에서 프로그램을 실행하면, 즉 프로세스 생성 과정에서 로더가 라이브러리를 로딩하는데 이 때 만약 LD_PRELOAD 환경 변수가 설정되어 있다면 로더는 여기에 지정된 라이브러리를 먼저 로딩한다. 중요한 점은 이 중에서 libc 함수와 동일한 이름의 함수가 있다면 이 라이브러리의 함수가 호출된다는 것이다. 그러므로 후킹을 위해서는 후킹할 함수의 이름과 동일한 함수를 구현하여 공유 라이브러리를 개발하고 LD_PRELOAD 환경 변수에 이 파일의 경로를 써주면 이후 프로그램 실행 시 마다 원하는 함수가 후킹된다. 참고로 후킹 함수 내에서 원본 함수로 실행을 리다이렉트시키고 싶다면 dlsym() 함수를 통해 주소를 얻어서 사용할 수 있다. 이 방식은 일반적인 의미의 후킹과 인젝션이라고 불리기 힘들 수 있으나 (일종의 라이브러리 치환의 개념) 실질적으로 후킹과 인젝션 모두의 기능이 가능하다.



Posted by SanseoLab



1. 개념

  일반적인 사람들 입장에서 처음 컴퓨터를 사고 설치할 때 계정을 생성하고 등록할 것이다. 중요한 것은 이 계정이 관리자 그룹(Administrators)에 속한다는 점이다. 참고로 표준 사용자 그룹(Users)에도 동시에 속해 있다. 이후 다른 계정을 생성할 때가 있을텐데 이 때도 관리자 계정을 만들 수도 있고 대신 표준 사용자 계정을 만들 수도 있다. 만약 관리자 계정으로 새로운 계정을 만든다면 이 계정도 Administrators, Users 두 가지에 속해있는 것을 볼 수 있을 것이다. 대신 표준 사용자 계정을 선택하여 만든다면 단지 Users에만 속해있다.


  윈도우는 표준 사용자 계정으로 로그온한 경우 거기에 맞는 적절한 보안 토큰을 준다. 관리자 계정으로 로그온한 경우에는 2개의 토큰을 받는데, 앞서 말한 표준 사용자 계정이 받는 토큰과 비슷한 권한을 가진 보안 토큰 그리고 관리자 토큰 이렇게 2개를 받는다. 


  조금 더 자세히 설명해 보겠다. Integrity 메커니즘은 커널의 SRM(보안 참조 모니터)에 기반한 윈도우 보안 아키텍처이다. SRM은 보안 접근 토큰에 존재하는 (유저와 그룹의) SID를 객체의 보안 디스크립터의 접근 권한과 비교해서 접근 제어를 강제한다. Integriry level에는 System, High, Medium, Low, Untrusted가 있다. 정리하자면 LocalSystem, LocalService 등의 경우 System Integrity level이 할당되며, Administrators의 경우 High, Standard Users의 경우에는 Medium이 할당된다.


  이를 통해 앞의 설명을 보충하자면 표준 사용자(Standard Users Group)로 로그온한 경우에는 표준 사용자 접근 토큰을 받는다. 이 접근 토큰은 Medium Integrity Level이 할당되어 있다. 관리자 계정(Administrators group)으로 로그온한 경우 표준 사용자 접근 토큰과 관리자 접근 토큰 2개를 받는다. 표준 사용자 접근 토큰은 위와 같고 관리자 접근 토큰은 High Integrity Level이 할당되어 있다.


  이제 응용 프로그램을 실행한다고 하자. 현재 계정이 표준 사용자던지 관리자던지 실행은 Medium Integrity Level의 접근 토큰을 가지고 하게된다. 왜냐하면 애플리케이션 실행 시에 explorer.exe의 자식 프로세스로 실행되는데 이 explorer.exe가 Medium 레벨을 가지고 실행 중이기 때문이다. 그래서 대부분의 프로그램은 이렇게 실행될 것이다. 하지만 문제가 있는데 만약 응용 프로그램 내부에 관리자 권한이 필요한 부분이 있다고 하자. 이 경우에는 현재 접근 토큰이 Medium이기 때문에 High Integrity Level의 권한을 갖지 못하여 권한이 필요한 루틴이 에러를 반환하고 프로그램이 제대로 동작할 수 없을 것이다. 이럴때는 보통 마우스 우클릭 후 "관리자 권한으로 실행"을 통해 관리자 권한으로 실행할 수 있다. 가끔 오래된 프로그램의 경우 제대로 실행되지 않을 때 관리자 권한으로 실행하라는 답변을 얻을 수 있는데 이것이 그 때문이다. 일반적으로 UAC 메커니즘이 생기기 전인 Windows XP 시절에 만든 프로그램일 것이다.


  또한 사족으로 관리자 권한으로 실행된 프로그램에는 드래그 앤 드랍이 통하지 않는다는 사실이 있다. 예를들어 CMD를 관리자 권한으로 실행한 후 특정 파일을 실행시키고 싶어서 드래그 앤 드랍으로 CMD에 끌어다 놓으면 경로명이 자동으로 올라오지 않는 것을 볼 수 있다. 이것은 단지 CMD만 해당하는 것이 아니고 OllyDbg를 관리자 권한으로 실행하거나 HxD를 관리자 권한으로 실행한 후 바이너리를 끌어다 놓을 때 통하지 않는 것을 확인할 수 있다. 드래그 앤 드랍은 exeplorer.exe가 관리하는 것으로 추정되며 이것이 Medium 레벨을 가지므로 High 레벨을 갖는 프로세스에 영향을 끼칠 수 없기 때문이다. 그래서 드래그 앤 드랍 방식 외에 직접 파일을 읽어들이거나 최악의 경우 Windows XP에서 해당 작업을 수행하던지, 아니면 explorer.exe를 관리자 권한으로 실행시키는 방식이 사용되기도 한다.


  현재 계정이 표준 사용자라고 하자. 그렇다면 관리자 권한으로 실행시킬 때 UAC가 나타난다. 이 UAC를 보면 관리자의 비밀번호를 입력해야 한다. 즉 당연히 관리자의 비밀번호가 있어야만 관리자 접근 토큰을 받아 관리자 권한으로 프로그램을 실행시킬 수 있다. 하지만 현재 계정이 관리자라고 하더라도 관리자 권한으로 실행시키면 UAC가 뜨는 것을 볼 수 있다. 관리자 계정으로 로그온한 경우에도 explorer.exe가 Medium 권한으로 실행되며 이것의 자식 프로세스로 실행되는 것은 똑같기 때문에 Medium 접근 토큰으로 실행할 것다. 그리고 관리자 권한으로 실행한다면 High Integrity Level의 접근 토큰으로 실행할 것이다. 관리자 계정이 비록 관리자 접근 토큰을 소유하고 있지만 UAC가 뜨는 것은 같다. 물론 이 때는 비밀번호를 입력할 필요 없이 간단하게 확인 버튼만 누르면 된다. 사실 이것이 UAC의 핵심인데 관리자 권한을 가진 관리자 계정이라고 하더라도 평소에는 축소된 표준 사용자 권한을 가지며 지내다가 관리자 권한으로 프로그램을 실행할 때에는 사용자의 확인을 받고 실행하는 것이다. 그래서 비밀번호의 입력 차이만 있을 뿐 관리자 권한을 사용하여 프로그램을 실행할 때에는 UAC가 뜨는 것은 같게 된다. 참고로 이미 관리자 권한으로 실행 중인 프로세스가 관리자 권한을 필요로 하는 작업을 하는 경우에는 당연히 UAC가 뜨지 않을 것이다. UAC는 단지 더 높은 권한을 필요로 할 때에만 뜨는 메커니즘이다.


  윈도우 7부터는 UAC도 설정이 가능한데 당연히 UAC의 설정을 바꾸기 위해서도 관리자 권한이 필요하므로 UAC가 뜨는 것을 볼 수 있다. UAC 뿐만 아니라 다른 중요한 설정을 바꿀 때에도 마찬가지이다. 예를들면 UAC를 우회하여 방화벽의 설정을 바꾸는 악성코드도 존재한다. 언제 관리자 권한이 필요한지에 대해 더 알아보자면 아마 가장 흔한 경우가 응용 프로그램을 설치할 때일 것이다. 아니면 시스템 파일을 수정하거나 디바이스 드라이버를 설치할 때도 그렇다. 또한 속한 그룹에 따라 가질 수 있는 권한도 다른데, SeDebugPrivilege 같은 권한의 경우에는 관리자 그룹에 속한 계정이 가질 수 있다. 참고로 이 권한은 접근 통제를 우회할 수 있게 해준다. 만약 관리자 권한으로 실행 중인 어떤 프로세스가 이 권한을 활성화하였다면 System Integrity Level을 가지고 실행 중인 프로세스에 CreateRemoteThread(), WriteProcessMemory() 같은 함수를 사용할 수 있게 된다. 다시 말해서 (LocalSystem에 의해 생성되어) System Integrity level을 가진 프로세스의 경우 일반적으로는 (관리자 계정에 의해 실행되어) High Integrity level을 가진 프로세스에게 접근 권한이 없지만 비활성화된 SeDebugPrivilege를 활성화시킨다면 이것을 우회하여 System 권한을 가진 프로세스들에 접근할 수 있다는 것이다. 즉 관리자 계정의 경우 해당 과정을 거쳐 실질적으로는 (현재 High Integrity level임에도 불구하고 더 높은 권한인) System Integrity level에도 접근할 수 있는 권한이 존재한다고 볼 수 있다.


  어쨌든 이런 중요한 작업에 관리자 권한이 필요하다는 것은 당연할 것이고 만약 응용 프로그램이 관리자 권한을 필요로 할 경우에 어떻게 할 것인가에 대해서도 알아보자. 앞에서도 언급하였듯이 마우스 우클릭 후 "관리자 권한으로 실행"을 클릭할 수 있다. 또는 속성의 호환성 탭을 누른 후 "관리자 권한으로 이 프로그램 실행"을 선택해도 된다. 


  하지만 이것은 사용자의 입장이고 개발자로서도 개발한 프로그램이 관리자 권한을 필요로할 경우에는 실행 시에 자동으로 관리자 권한으로 실행되도록 프로그램을 개발할 필요가 있다. 이것은 VC++ 프로젝트 옵션에서 링크 -> Manifest file을 클릭하면 설정이 가능하다. level에는 asInvoker (응용 프로그램을 시작한 프로세스와 동일한 권한으로 응용 프로그램 시작), highestAvailable (최대한 높은 권한 수준으로 응용 프로그램 실행), requireAdministrator (관리자 권한으로 실행)이 있다. 이렇게 설정한 후 생성된 응용 프로그램을 보면 리소스 섹션에 xml 형태의 문자열이 설정한 레벨에 써져있는 것을 볼 수 있다. 프로그램 실행 시 이 레벨을 참고하여 실행하는데 만약 레벨이 requireAdministrator로 설정되어 있다면 UAC가 뜨는 것을 볼 수 있을 것이다. 


  참고로 highestAvailable는 조금 더 설명이 필요하다. 만약 현재 표준 사용자 계정으로 로그온한 경우라면 UAC가 뜨지 않을 것이다. Medium이 최대 권한이므로 관리자 권한을 요구하지 않을 것이기 때문이다. 하지만 관리자 계정이라면 최대 권한인 관리자 권한으로 실행되기 때문에 UAC가 뜨는 것을 볼 수 있다. 굳이 이러한 메커니즘을 만든 이유를 알아보자. 이것은 개발한 프로그램이 관리자 권한을 가진 경우에는 모든 기능을 사용할 수 있고 만약 표준 사용자 계정이라도 제한적인 기능을 사용할 수 있게 하려는 경우에 사용된다. 예를들면 regedit.exe (레지스트리 편집기)가 있다. 일반 사용자의 경우에도 HKCU 하이브의 어떤 값들은 수정할 권한이 있기 때문이다. 물론 관리자 권한이 없으므로 HKLM 하이브의 경우에는 손댈 수 없다.





2. UAC 우회

  앞에서도 설명하였듯이 사용자가 관리자 계정을 가지고 있더라도 관리자 권한이 필요한 프로그램을 실행할 경우에는 UAC가 뜨게 된다. UAC Bypass 즉 UAC 우회는 관리자 권한으로 실행 시 UAC가 뜨는 것을 우회하여 사용자가 인지하지 못하게 악성코드를 설치하거나 악의적인 작업을 하기 위해 사용된다. 악성코드 설치 시에 레지스트리 수정 같이 설치 시에 관리자 권한이 필요한 경우가 많기 때문일 것이고 악의적인 작업도 Medium 권한으로는 한계가 있다. UAC 메커니즘이 만들어지기 전인 Windows XP에서는 지금처럼 대부분 관리자 계정을 사용하였을 것이고 UAC 메커니즘도 없었기 때문에 악성코드는 사용자가 인식하지 못하게 관리자 권한을 가지고 악성코드를 설치하는 일이 훨씬 편했을 것이다.


  물론 앞에서도 설명했지만 굳이 관리자 권한 없이도 악의적인 행위를 수행할 수 있다. 어떤 랜섬웨어의 경우에는 자신이 SeDebugPrivilege 권한을 가지고 실행 중인지를 판단한 후 가지고 있지 않다면 단지 사용자 파일을 암호화하며 가지고 있다면 추가적으로 MBR도 암호화시키는 루틴을 가지고 있다. 하지만 자동 시작을 위해 레지스트리의 AutoRun에 값을 집어넣거나 서비스 및 드라이버 설치, 시스템 디렉터리에 손대기, 방화벽 설정 변경 등 악성코드로서 할 수 있는 많은 부분에 제약이 생기기 때문에 많은 악성코드들은 관리자 권한을 원한다.


  이제부터는 UAC bypass를 설명하고자 한다. UAC 우회는 전제 조건으로 먼저 사용자가 관리자 계정이어야 한다. 또한 대부분 UAC 옵션이 디폴트 옵션인 "Notify me only when programs try to make changes to my computer" (앱에서 사용자 모르게 컴퓨터를 변경하려는 경우에만 알림)를 선택한 경우에만 통하는 편이다. 공개된 방법들 중에는 UAC의 가장 강한 옵션인 "Always Notify"로 설정된 경우에는 제대로 동작하지 않는 기법들이 많다. 물론 위에서 언급하였듯이 대부분의 사용자는 관리자 계정을 가지고 컴퓨터를 사용할 것이며 UAC도 디폴트 설정으로 머물러 있을 것이기 때문에 버전에 맞고 취약점이 패치되지 않았다면 대부분 통할 것이다.


  그리고 공통적으로 사용되는 메커니즘이 있다. 윈도우 시스템 파일들은 Microsoft Windows publisher에서 서명한 사인이 존재하는데 (그렇기 때문에 사인받았다고 해도 3rd party 애플리케이션은 이와 관련이 없다) 이 중에서 리소스의 manifest에 <autoElevate>true<autoElevate> 즉 자동 권한 상승 속성이 삽입되어 있는 프로그램이 있다. 이 프로그램들은 당연히 관리자 권한으로 실행되지만 UAC 옵션이 "Notify me only when programs/apps try to make changes to my computer"로 설정되어 있다면(Always Notify와 Never Notify의 중간) 이름처럼 실행 시에 UAC가 뜨지 않는다. Administrators group에 속해 있다면 관리자 권한으로 실행되지만 UAC가 뜨지 않는 것이다. UAC bypass는 이러한 프로그램들을 대상으로 한다. 즉 악성코드가 이것이 설정된 애플리케이션을 실행시킬 때는 UAC가 뜨지 않으며 관리자 권한을 가진 이 프로그램이 악성코드를 실행하게 만들어서 자동으로 상승된 권한을 상속받은 악성코드를 실행하는 것이다.


  UAC Bypass 취약점의 경우 다양한 프로그램들을 이용하지만 기본적인 방식은 비슷한 경우가 많다. 먼저 앞에서도 언급하였지만 auto-elevate 속성을 가지며 취약점을 통해 악성코드를 실행하게 될 프로그램들로는 sysprep.exe, cliconfg.exe, mmc.exe 등이 있으며 이 프로그램들 외에도 최근에는 다른 프로그램들도 많이 발견되고 있는 중이다. 또한 이 프로그램이 가진 즉 UAC bypass에서 사용되는 취약점은 대부분 DLL Hijacking 방식이다. 다시말해 이 프로그램이 취약하여 로드할 DLL을 안전하게 검사하지 않아서 공격자가 만든 DLL을 로드하게 만드는 방식이다. 이제부터는 실제 예를들어서 설명할 것이다. 여기서 언급하는 취약점들은 모두 패치되어 더 이상 사용이 불가능하지만 메커니즘을 공부할 수는 있다.


  먼저 Mcx2Prov.exe를 이용하는 방식을 통해 설명해 보겠다. 이 프로그램은 cryptbase.dll에 대해 DLL Hijacking 취약점을 갖는데 이 취약점은 애플리케이션이 dll을 로드하기 위해 찾을 때 현재 디렉터리에서 가장 먼저 찾기 때문에 벌어진다. 그러므로 만약 우리가 악의적인 행위를 수행하는 dll을 만들어서 cryptbase.dll이라는 이름을 붙이고 그 디렉토리에 교체해 넣으면 이 dll을 로드할 것이다. 하지만 이 프로그램은 C:\Windows\System32에 존재하기 때문에 공격자가 만든 dll을 여기에 옮기는 것부터 문제가 생긴다. 왜냐하면 애초에 해당 디렉토리에 파일을 옮기는 것도 관리자 권한이 필요하기 때문이다. 여기서는 wusa.exe(Windows Update Standalone Installer)를 이용한다. 먼저 악의적인 dll을 만들고 이름을 cryptbase.dll로 짓는다. 이후 makecab 프로그램을 이용해 이 dll을 cabinet 파일로 만든다. 이제 wusa를 사용하여 이 파일을 /extract 옵션을 주고 Mcx2Prov.exe가 존재하는 디렉터리에 풀면 된다. 관리자 권한이 필요한 동작인 C:\Windows\System32에 파일을 이동시키는 행위를 auto-elevate 프로그램인 wusa를 이용하여 가능케 한 것이다. 참고로 wusa.exe의 이러한 문제점 때문에 이후 /extract 옵션이 제거되었다. 이제 Mcx2Prov.exe를 실행하면 변경된 cryptbase.dll을 로드하기 때문에 UAC 없이 dll로 만들었던 내용이 관리자 권한을 가지고 실행되는 것을 볼 수 있다.


  더 흔한 예시를 들어보겠다. 앞에서는 Mcx2Prov.exe를 가지고 설명했지만 사실 가장 많이 사용되었던 프로그램은 sysprep.exe이다. 이것도 위와 비슷하게 DLL Hijacking 취약점을 사용한 방식이고 앞에서 말했던 cryptbase.dll도 해당하는 dll 중 하나였다. 물론 이렇게 간단한 차이를 설명하기 위해 예시를 든 것은 아니고 이번에는 wusa 대신 IFileOperation 방식을 위주로 설명하고자 한다. 또한 다른 프로세스들도 마찬가지이지만 explorer.exe 같은 윈도우 자체 프로세스도 Medium Integrity Level을 가지고 있기 때문에 dll 인젝션이 가능하다는 것을 알 수 있다. CreateRemoteThread() 함수는 XP에서와 달리 제약 사항이 많아졌지만 대상 프로세스가 같은 세션에 존재하고 (explorer.exe 프로세스는 당연히 로그온 시에 생성됨과 동시에 실행할 프로세스의 부모이므로) 같거나 낮은 Integrity Level을 가지고 있다면 과거와 같이 DLL 인젝션에 사용될 수 있다.


  먼저 explorer.exe에 dll을 인젝션한다. 인젝션되는 dll에서는 IFileOperation이라는 COM 인터페이스를 이용하는데 여기에는 파일을 복사하고 삭제하는 메소드가 들어있다. 여기와 관련된 부분은 뒤에 추가하기로 한다. 이를 통해 보호된 디렉터리에(C:\Windows\System32 같은) 존재하는 cryptbase.dll을 지우고 공격자가 제작한 dll로 변경한다. UAC 없이 이 방식이 어떻게 관리자 권한을 갖을 수 있냐면 COM 오브젝트는 MS 서명이 되어있는 프로세스에서 사용되는 경우 UAC 없이 auto-elevated되기 때문이다. 즉 구체적으로 말하자면 IFileOperation 오브젝트는 만약 explorer.exe 같이 MS 서명이 되어있는 프로세스에서 (dll이 인젝션되었으므로 dll의 내용은 explorer와 같은 권한으로 실행된다) 사용되는 경우 auto elevated되어서 실행된다. 그래서 UAC 없이 관리자 권한으로 시스템 디렉터리에 쓸 수 있는 것이다.


  이 방식의 단점은 IFileOperation COM 오브젝트를 사용할 경우 UAC 옵션이 "Always Notify"라면 UAC가 뜬다는 점과, 과정 중 DLL 인젝션이 수반되기 때문에 보안 프로그램에서 탐지될 수 있다는 점이 있다.


  마지막으로 지금까지 봐왔던 것과는 조금 다른 방식을 설명하려고 한다. IFileOperation의 경우에는 dll 파일 및 이것을 프로세스에 인젝션하는 과정이 필요했다. wusa를 이용한 방식도 결국 DLL Hijack 방식이라는 것은 같기 때문에 dll이 필요하며 이것을 시스템 디렉터리로 이동시키는 작업이 필요했다. 이런 방식들은 보안 솔루션을 통해 탐지될 확률이 높다. 여기서는 레지스트리를 이용하는 방식을 다룬다. 먼저 취약점에 이용되는 레지스트리에 대한 배경 지식을 설명하자면 HKEY_CLASSES_ROOT(HKCR)HKEY_LOCAL_MACHINE\SOFTWARE\ClassesHKEY_CURRENT_USER\Software\Classes의 조합이다. 그리고 표준 사용자는(Medium Integrity Level) 관리자 권한이(High Integrity Level) 없어도 자신의 HKCU 레지스트리를 수정할 수 있다.


  여기서 사용되는 eventvwr.exe도 당연히 auto-elevate 속성을 가지고 있다. 이 프로그램은 실행 시 RegOpenKey()를 이용해 HKCU\Software\Classes\mscfile\shell\open\command를 읽지만 “NAME NOT FOUND”라는 결과를 받는다. 실패 이후에는 HKCR\mscfile\shell\open\command를 읽는데 값을 보면 mmc.exe의 경로이며 이것을 실행시키는 것을 볼 수 있다. 중요한 점은 eventvwr.exe가 auto-elevate 속성을 가지므로 UAC 없이 관리자 권한을 통해 실행된다는 점이고 이에 따라 mmc.exe도 관리자 권한으로 실행된다는 것이다. 우리가 이용할 취약점은 eventvwr가 HKCR을 읽기 전에 먼저 HKCU를 읽으며 표준 사용자로서 HKCU의 레지스트리를 조작할 수 있다는 점이다. 만약 우리가 HKCU\Software\Classes\mscfile\shell\open\command에 원하는 악성코드의 경로를 집어넣는다면 이 악성코드는 UAC 없이 관리자 권한으로 실행될 것이다.





3. 참고사항

- UAC는 디지털 서명의 유무에 따라 다르게 보여지는데, 응용 프로그램이 서명된 경우에는 윗 부분이 파랗게, 아닌 경우에는 노랗게 뜬다.


- 디폴트로 비활성화되어 있는 계정 중 Administrator라는 계정이 있다. 헷갈릴 수 있지만 그룹 Administrators에 속하는 이름이 Administrator라는 계정이다. 과거와 달리 현재는 디폴트로 비활성화되어 있기 때문에 볼 일이 없겠지만 굳이 이것을 언급하는 이유는 이 계정을 사용하면 UAC가 디폴트로 비활성화되어 있다는 특징 때문이다. 과거 XP의 자료들과 비교하여 혼동의 여지가 있을 수 있으므로 적어놓는다.


- 실행 도중에 관리자 권한 획득에 대해서 생각해볼 수 있다. 일반적으로 CreateProcess()를 사용하여 가능할 방법을 찾겠지만 불가능하다. 대신 ShellExecute() 또는 ShellExecuteEx()의 인자 또는 구조체에 "runas"를 넣고 호출한 경우에만 가능하다.


- 먼저 COM 등과 관련된 문서의 링크이다. [ http://sanseolab.tistory.com/49 ] 그리고 여기와 관련된 추가적인 내용은 다음과 같다.

  IUnknown 인터페이스는 COM에서 가장 기본적인 인터페이스이다. 그러므로 모든 COM 객체들은 만드시 최소한 이 인터페이스는 구현해야 하며 나아가 모든 COM 인터페이스는 반드시 여기에서 나온다. 그러므로 ActiveX 하에 설계된 컴포넌트들도 반드시 IUnknown 인터페이스를 구현해야 한다.

  IFileOperation 인터페이스는 IUnknown 인터페이스를 상속하며 Shell 아이템들에 대한 복사, 이동, 생성, 삭제 등의 메소드를 제공한다.


- Window XP에서는 Integrity Level 메커니즘이 존재하지 않으므로 당연히 UAC 관련된 기능도 없다. 그래도 비슷한 점이 많이 있는데 현재 관리자 그룹 즉 Administrators Group에 속한 계정이라면 SeDebugPrivilege를 Enable시킬 수 있다는 점이다. XP도 일반적으로는 이 권한이 Disabled되어 있다.

Posted by SanseoLab



목차

0. 개론

1. 경로

2. 실행

.... 2.1 취약점

........ 2.1.1 파일 포맷 취약점

........ 2.1.2 웹 브라우저 관련 취약점

........ 2.1.3 DLL Hijack

........ 2.1.4 운영체제 취약점

.... 2.2 매크로

.... 2.3 스크립트 파일

.... 2.4 Autorun.inf

.... 2.5 확장자 위장

3. 문서 쪽 정리





0. 개론

  개인적으로 악성코드 분석 중에서도 리버스 엔지니어링을 통한 바이너리 분석에만 집중하여 공부해 왔다. 원래 웹 쪽에는 관심이 없었을 뿐더러 취약점과 관련해서도 관련 지식이 많이 부족했기 때문이다. 하지만 악성코드 분석이라는게 순수하게 바이너리를 분석하기만 하는 것이 아니라 어떤 방식으로 유포되어 사용자 컴퓨터를 감염시키는지도 포함된다고 생각한다. 악성코드가 실행되는 그 순간 이후부터는 항상 해오던 것이기 때문에 여기에는 포함시키지 않기로 한다. 여기서는 그 직전까지의 상황을 위주로 설명한다. 웹도 그렇고 취약점도 그렇고 그다지 많이 알지 못하는 분야이기 때문에 많이 부족하며 많은 바이러스 분석 리포트를 읽어보고 추가할 내용이 생기면 반영하기로 하겠다.


  악성코드가 공격 대상의 컴퓨터에서 실행되기 위해서는 먼저 악성코드 바이너리가 공격 대상의 컴퓨터에 존재해야 하며 또한 이것을 실행시킬 메커니즘이 필요하다. 바이너리는 순수하게 그대로 존재할 수도 있고 실행 파일이지만 사용자에게 혼란을 줄 목적으로 다른 형태의 확장자를 가질 수도 있다. 이외에도 다른 파일에 암호화되어 삽입된 형태로 존재하여 드로퍼 형태로 이것을 추출한 후에 실행하는 메커니즘일 수도 있다. 마지막으로 다운로더 형태로 존재하여 실제 악성코드를 다운로드 받은 후에 실행시키는 메커니즘도 존재한다. 사실 가장 많은 방식은 웹 브라우저나 애플리케이션의 취약점을 이용한 방식이다. 임의 코드 실행 취약점은 뒤에서 살펴보겠지만 취약한 입력 등을 이용하여 공격자가 설정한 임의적인 쉘코드를 실행하는 방식이다. 그렇기 때문에 이 쉘코드 내부에 모든 기능을 넣기 힘드므로 드로퍼 방식을 사용해 다른 파일에서 바이너리를 추출하고 실행하던지 아니면 다운로더 방식으로서 악성코드를 다운로드 받고 실행하는 내용의 쉘코드가 사용된다.





1. 경로

  여기서는 악성코드에 감염되는 최초의 경로를 말하고자 한다. 가장 많이 이용되는 경로 중 하나는 스팸 메일일 것이다. 이것은 사회공학적인 방법을 이용해 링크를 클릭하게 할 수도 있고 위장 문서 파일을 다운로드하고 열어보게 만들 수도 있다. 감염된 사이트의 url을 클릭하게 만들어서 취약점을 이용한 공격이 가능하게 하거나 직접 악성코드를 다운로드받게 하는 것이다. 다운로드 받는 경우는 대부분 문서 파일 형태로서 매크로를 이용한 방식일 것이지만 확장자의 특징을 이용해 사용자가 이것이 실행 파일이 아닌 것처럼 생각하게 해 실행하게 만들 수도 있다.


  스팸 메일 외에도 피싱 방식도 감염된 사이트의 url을 클릭하게 만들 수 있다. 물론 많은 사람들이 이용하는 사이트가 취약해 감염된 경우에는 이런 방식 없이도 이 사이트에 접속한 많은 사람들이 직접 감염될 것이다. Drive-By-Download가 이런 방식으로서 특별히 실행 파일을 다운로드한다거나 실행하는 것 없이 이렇게 웹 서핑만 했는데도 악성코드에 감염될 수 있다. 참고로 워터링 홀 공격은 이 DBD 공격과 같은데 차이점이 있다면 불특정 다수가 아닌 특정한 집단을 목표로 한다는 점이다. 즉 APT 공격 중 하나이다. Malvertising 방식은 온라인 광고를 통해 악성코드를 유포시킨다. 사람들은 안전한 사이트에서는 광고도 안전하다고 여기는 경향이 있지만 사실 수 많은 광고를 모두 검증하기 어렵다. 광고 클릭 시 악성코드 다운로드를 유도하거나 취약점을 이용하는 감염된 사이트로 redirect 시킬 수 있다.


  앞의 스팸메일의 경우처럼 꼭 문서 파일이 아니더라도 웹하드나 p2p를 통해 주로 다운로드 받는 mp3나 동영상 같은 미디어 파일도 감염되어 있을 수 있다. 이것은 애플리케이션이 읽는 미디어 파일 포맷에 관한 취약점을 이용한다. 또 다른 방식으로 게임 핵이라는 이름으로 제공되지만 실제로는 악성코드에 감염시키는 내용이 들어있는 실행 파일일 수도 있다.


  최근에는 운영체제 자체의 취약점을 통해 같은 네트워크에 존재하기만 해도 즉 인터넷에 연결만되어 있어도 악성코드에 감염되는 경우가 발생하기도 했다.





2. 실행

2.1 취약점

2.1.1 파일 포맷 취약점

  동영상 또는 음악 파일을 재생하고 자막 파일을 읽어서 보여주는 미디어 플레이어나 문서를 처리하는 워드 프로그램 또는 파일을 압축 및 압축 해제하는 종류의 프로그램들은 공통적으로 파일을 읽어들인 후 특정 행위를 수행한다. 만약 이러한 애플리케이션에서 특정 영역을 읽어들일 때 오버플로가 발생하는 취약점이 존재한다면 파일의 이 취약점을 이용하는 쉘코드를 작성해 임의적인 코드를 실행시킬 수 있다. 이러한 형태의 취약점을 임의 코드 실행 취약점이라고 한다. 이 쉘코드는 악성코드를 다운로드하고 실행시키는 다운로더의 역할을 할 수도 있으며, 파일에 악성코드를 암호화하여 삽입한 경우 다운로드 대신 해당 바이너리를 디코딩하여 추출한 후에 실행시키는 드로퍼의 역할을 할 수도 있다.


   웹 브라우저 취약점 항목에서 설명하겠지만 어도비 플래시 플레이어의 경우 .swf 파일을 읽고 실행하는데 이 때 파싱하는 과정에서 파일 포맷 취약점이 존재할 수 있다. 또한 플레이어가 직접 읽어들이는 .swf 파일 외에도 ActionScript를 통해 읽어들이는 mp4 같은 미디어 파일도 마찬가지이다.


  그러므로 미디어 플레이어 같은 자주 사용되는 애플리케이션을 항상 최신 버전으로 업데이트할 필요가 있으며 출처가 확실치 않은 미디어 파일을 다운로드하지 않는 것도 방법이다. 이것은 웹 브라우저에서 실행되는 애플리케이션인 어도비 플래시 플레이어 같은 프로그램도 마찬가지이다.


2.1.2 웹 브라우저 관련 취약점

  앞에서는 간단하게 감염된 사이트에 접속하여 악성코드에 감염된다고만 설명하였다. 여기서는 조금 더 깊게 들어가서 어떤 방식을 통해 악성코드가 공격 대상의 컴퓨터에 주입되고 이후에 실행되는지에 대한 메커니즘을 다루고자 한다. 사실 요즘은 대부분의 악성코드가 여기서 설명할 Drive-By-Download 방식으로 웹 브라우저를 이용하여 감염시키는 경향이 있다. 


  일반적으로 Drive-By-Download 공격 방식에는 악성코드 경유지와 유포지 등의 단어가 등장한다. 여기서는 경유지란 최초로 방문하게 되는 감염된 페이지를 뜻할 것이고 중계지는 추적을 어렵게 만들기 위하여 유포지로 도착하기 전에 방문하게 되는 페이지들을 뜻할 것이다. 그리고 유포지는 실질적으로 취약점을 이용해 공격을 수행하는 내용이 들어있는 페이지를 뜻하며 마지막으로 악성코드가 실제로 저장된 저장소가 있다.


  공격자는 특정한 웹 사이트의 취약점을 이용하여 웹 서버에 악성 스크립트를 삽입하거나 직접 사이트를 변조할 수 있다. 일반적으로 iframe 태그를 통해 여러 중계지를 거쳐 유포지로 redirect 시키는 형태이다. 즉 공격자는 취약한 웹 페이지를 변조시켜 iframe 태그를 삽입하거나 이러한 역할을 하는 악성 스크립트를 삽입함으로써 경유지를 만든다.


  이제 사용자는 방문하고자 했던 사이트에서 여러 중계지를 거쳐 유포지로 redirect되었다고 가정하겠다. 유포지에서는 사용자의 브라우저와 운영체제 등을 검사하여 취약점을 찾고 실제로 취약점을 이용해 공격하는 역할을 수행한다. 취약점의 경우 조건이 맞아야 그 역할을 할 수 있는데 애초에 관련 프로그램이 설치되어 있지도 않다면 통할리가 만무하기 때문이다. 또한 설치되어 있다고 해도 해당하는 버전에 따라 취약점의 성공 여부도 다르므로 공격자의 입장에서는 사용자의 정확한 환경을 파악하고 이후 이것에 맞는 취약점을 사용할 것이다.


  이제 해당하는 취약점 예를들면 어도비 플래시 플레이어 취약점이 공격 가능하다고 하자. 유포지에서는 해당하는 취약점에 상응하는 악성 파일(이 경우에는 .swf)을 사용자로 유입시킨다. 취약점이 존재하는 사용자의 어도비 플래시 플레이어는 이 악성 파일을 읽어들일 것이고 이 과정에서 파일 내부의 쉘코드가 실행되어 실제 악성코드를 다운로드하고 실행시킨다. 물론 쉘코드는 swf 파일 내부에 인코딩되어 있는 악성코드를 추출하고 실행시키는 내용일 수도 있다. 어도비 플래시 플레이어와 관련된 취약점은 뒷 부분에서 다루도록 하겠다.


  정리해보자면 공격자는 악성코드 유포지로 사용자를 유도하기 위해 취약한 웹 사이트를 감염시켜 경유지로 만든다. 유포 페이지는 사용자가 어떤 취약한 애플리케이션을 사용하는지 검사하고 해당 취약점을 이용하는 곳이다. 만약 사용자가 어도비 플래시 플레이어의 취약한 버전을 사용한다고 판단될 경우 .swf 파일을 사용자의 웹 브라우저로 다운로드시킨다. 이 파일은 ActionScript 언어를 이용해 취약점을 이용하는 루틴으로 개발되었을 것이다. 어쨌든 플레이어는 이 파일을 읽음으로써 공격자가 원하는 임의적인 코드를 실행하게 된다. 이 코드의 내용은 궁극적으로 악성코드를 다운로드 받고 실행시킬 것이다. 이 악성코드는 악성코드 저장소에 존재한다.


  가장 유명한 어도비 플래시 플레이어에 관해서 더 알아보겠다. 이것은 .swf 파일을 실행한다. 이 파일은 플래시 파일로서 ActionScript라는 언어를 이용해서 개발한다. 파일 포맷 취약점 형태를 보면 어도비 플래시 플레이어에서 .swf 파일을 파싱하는 부분에서 발생할 수도 있고 ActionScript를 통해 .mp4 파일을 읽고 파싱하는 부분에서 발생할 수도 있다. 즉 이런 방식들은 파일 포맷 취약점을 이용하는 방식이다. 물론 애플리케이션 자체적인 취약점이 더 많을 것이다. 예를들면 Use-After-Free나 사용하는 라이브러리에서 제공되는 함수가 받은 인자를 제대로 처리하지 못할 때 즉 취약한 함수를 이용할 때에도 발생할 수 있다. 결론적으로 어도비 플래시 플레이어의 취약점을 이용하기 위해서는 악성 swf 파일이 필요한데 이 파일의 내용은 즉 ActionScript의 내용은 mp4 같은 미디어 파일의 파싱 취약점을 실행하는 간단한 내용일 수도 있고 자체적으로 취약점을 공격하는 내용이 들어있을 수도 있다.


  어도비 플래시 플레이어 외에도 이와 비슷한 Java Applet, MS Silverlight가 있다. 자바의 경우 취약한 .jar 파일이 사용되는데 JVM이 포함되어 있는 웹 브라우저가 자바 애플릿이 포함된 웹 사이트 접속 시에 JVM으로 애플릿을 다운로드하여 로컬에서 실행하는데 이 때 보안 관리자를 우회하여 악성코드에 감염시키는 것이다. 또한 이러한 애플리케이션 말고도 브라우저의 취약점 즉 MS IE 취약점을 사용할 수도 있을 것이다. 참고로 자바 취약점을 이용한 방식은 JVM에서 실행되기 때문에 브라우저가 강제로 종료되지 않으며 플래시 플레이어나 브라우저 자체 취약점의 경우에는 대부분 브라우저가 강제로 종료된다고 한다.


  마지막으로 대부분의 악의적인 행위를 수행하는 스크립트는 난독화되어 있다. 즉 경유지부터 중계지들 그리고 유포지까지 관련된 대부분의 사이트들의 악의적인 부분이 대부분 난독화되어 있다고 보면 된다. 스크립트 언어의 특성상 컴파일되지 않아 사람도 쉽게 읽을 수 있기 때문이다. 특히 자바스크립트의 경우에는 난독화 툴(대부분 Exploit Kit에서 제공된다)부터 이것을 해제하는 툴들이 많이 존재하는데 이쪽만 해도 공부할 내용이 많다.


2.1.3 DLL Hijack

  특정 DLL을 로드하는 애플리케이션이 DLL에 대한 검사 없이 로드한다고 하자. 이러한 경우 악의적인 사용자가 이름이 같은 악성 DLL을 만들고 함수 이름도 같게 만들어서 export시키고 실제 DLL 대신 이 DLL을 포함시켜 배포할 수 있다. 아니면 인라인 패치로 정상 DLL 파일에 악의적인 부분을 써 넣을 수도 있다. 물론 함수는 실제적인 기능이 아닌 악의적인 행위를 수행할 것이다. 사용자는 실행 파일이 안전하므로 거리낌없이 사용할 것이고 해당 실행 파일은 실행될 경우 악성 DLL을 로드하고 그 함수를 호출할 것이다. 


2.1.4 운영체제 취약점

  최근에는 윈도우의 SMBv2 원격코드 실행 취약점을 이용해 랜섬웨어가 전파되었다. 이 방식은 자신의 네트워크 대역 IP 및 랜덤으로 생성된 IP 대역을 스캔하여 SMB 취약점이 발견될 경우 랜섬웨어에 감염시키는 방식으로 전파된다.



2.2 매크로

  앞에서 문서 파일을 언급하였다. 이러한 형태의 취약점은 문서 파일 내의 매크로(MS 오피스 프로그램의 경우 VBA) 기능을 이용한 기법으로서 주로 다운로더나 드로퍼 기능을 가지며 이후 다운로드 또는 추출한 악성 바이너리를 실행시킨다. 일반적으로 메일 등의 첨부파일로 전파되는데 사용자가 해당 문서 파일을 열 경우 악의적인 매크로가 실행된다. 최신 오피스 프로그램은 디폴트로 매크로 기능이 꺼져 있지만 궁금증을 유발시켜 매크로 기능을 켜게 유인시키기도 한다. 



2.3 스크립트 파일

  다음 문서에 정리한다. [ http://sanseolab.tistory.com/41 ]




2.4 Autorun.inf

  USB나 외장 하드를 연결한 경우 최상위 디렉토리에 Autorun.inf라는 파일이 있다면 운영 체제는 이 파일을 실행하고 이후 디스크를 열게 된다. 내부적으로 만약 Autorun.inf 파일 내부에 open= 구문으로 지정된 파일이 실행된다.



2.5 확장자 위장

  사용자에게 악성 실행 파일의 확장자를 실행 파일이 아닌 것처럼 인식시키는 방식은 여러가지가 있다. 가장 간단한 방법은 사용자가 확장자 자동 숨김 기능을 사용한다는 가정 하에 " aaa.jpg.exe " 파일을 만들고 아이콘을 사진 처럼 변경하여 사용자 눈에 aaa.jpg로 인식시키는 방식이 있다. 이외에도 디렉토리 이미지도 자주 사용된다. exe 파일의 경우 확장자 자동 숨김을 통해 확장자는 가려져 있는데 아이콘이 디렉터리라면 당연히 디렉터리인줄 알고 더블클릭하여 실행시키는 방식이다. exe 파일은 아니지만 실행 가능한 파일로서 사용자에게 실행 파일이 아니라 데이터 파일로 인식시켜 의심을 사지 않게 하는 방법도 있다. 결론적으로 실행 파일의 이미지를 특정 파일 포맷 예를들면 사진이나 동영상 같은 이미지로 교체하고 확장자도 혼란스럽게 하여 사용자의 부주의를 통해 실행시키는 방식이다. 이러한 방식들은 아래에 설명하기로 한다. 


- 앞에서 언급한 스크립트 파일들. [ .js  .vbs  .hta  .wsf  .chm  .bat  .ps1 ]


- RLO

  먼저 charmap.exe를 실행하면 문자표라는 애플리케이션이 뜬다. 아랫 부분에 "유니코드로 이동"이라는 부분과 오른쪽에 빈 칸이 있다. 이 빈 칸에 202E 입력한다. 맨 왼쪽 맨 위에 아무것도 보이지 않는 빈 칸이 보인다. 마우스로 대보면 "U+202E: Right-To-Left-Override"라고 나온다. 이것을 클릭하고 아랫 부분에 복사를 누른다. (참고로 복사할 문자 부분에서 눈에는 보이지 않으므로 복사된건지 확인하기 힘들다)

  이후 실행 파일의 이름을 " aaa4pm.exe " 로 바꾼 후 aaa 바로 다음 부분에 커서를 두고 붙여넣기를 해보자. 그렇다면 " aaaexe.mp4 " 처럼 변경되는 것을 볼 수 있다. 이제 애플리케이션의 이미지만 바꾸면 동영상 파일로 착각할 수 있게된다. 이름 부분도 충분히 쓸데없이 길게 영어를 섞어서 해주면 잘 모를 것이다.


- .lnk

  바로가기 파일이다. 사용자로서는 바로가기 파일을 악성코드로 인식하기 어려울 수 있다. 바로가기 파일은 내부적으로는 실행 파일의 경로를 저장하고 있다가 더블클릭되면 해당 실행 파일을 실행시켜주는 파일이다. 속성을 눌러서 대상 부분을 보면 실행할 프로그램의 경로가 나와있다. 이것을 다음처럼 수정할 수 있다.


C:\Windows\System32\cmd.exe /c C:\Users\longa\Desktop\EJDbg\example2.exe


  위의 예는 cmd 명령어를 이용한 방식으로서 한계가 존재할 수 밖에 없다. 현재 주로 사용되는 방식을 알아보겠다. 물론 이것들도 모두 원래 실행할 프로그램의 경로가 존재해야 하는 곳에 특별한 값들을 집어넣는 방식이다.


  첫번째는 파워쉘이 있다. 앞에서도 말했듯 기본적인 cmd 명령어는 수행할 수 있는 능력에 한계가 있다. 하지만 파워쉘은 훨씬 많은 기능이 제공되기 때문에 다운로더로서도 그리고 다운로드 받은 악성코드를 실행하는 기능도 사용 가능하다. 


  두번째는 스크립트 언어이다. 해당 폼에는 명령어나 파워쉘 외에도 JavaScript(.js), VBScript(.vbs), VBScript Encoded Script(.vbe) 등의 스크립트 언어를 사용할 수 있다. 


- .scr

  화면 보호기 파일. 실행 가능하다.


- .pif

  오래된 확장자로서 중요한 부분만 설명해 보자면 윈도우의 로더의 경우 이 확장자를 가진 파일이 실행 가능한 파일이라면 실행시켜 준다. 예를들어서 아무 실행 파일의 확장자를 pif로 바꾸어 보면 exe 형태였을 때와 마찬가지로 실행 파일처럼 실행된다.


- .cpl

  dll과 동일하다고 할 수 있다. 차이점은 exe처럼 직접 실행 가능하다는 점이다. 실제로는 rundll32.exe를 통해 실행된다.





3. 문서 쪽 정리

  먼저 MS Office 쪽부터 설명하겠다. 기본적으로 매크로가 있으며 위에서 설명하였다. 그리고 오피스 애플리케이션들의 파일 포맷 취약점을 이용한 익스플로잇이 있을 것이다. 참고로 Office 관련 파일들 외에도 rtf도 많은 공격에 이용되고 있다. 또한 요즘에는 잘 알려지지 않았던 DDE (Dynamic Data Exchange)를 이용한 공격이 많아지고 있다.


  다음으로는 PDF가 있을 것이다. PDF에서는 JavaScript가 사용될 수 있다. 물론 브라우저처럼 어도비 리더도 샌드박스를 구현하여 방어 메커니즘을 가지고 있다. 많은 취약점은 샌드박스 우회 및 코드 실행을 위해 자바스크립트를 이용하여 취약점 공격을 통해 셸코드를 실행한다.



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

최근에 올라온 글

최근에 달린 댓글

글 보관함