'DLL Injection'에 해당되는 글 1건

  1. 2008.12.14 [User Mode RootKit] CreateRemoteThread를 이용한 DLL INJECTION 1
2008. 12. 14. 00:04

[User Mode RootKit] CreateRemoteThread를 이용한 DLL INJECTION

CreateRemoteThread를 이용한 DLL INJECTION 기법 Written By Sim-hyeon, Choe

DLL INJECTION 기법에 대해서 예전에 제가 서술한 적이 있었습니다만 다시 기고합니다.

DLL INJECTION이 필요한 배경부터 설명드리겠습니다.

Win32기반의 Virtual Memory는 어떠한 구조를 지니고 있는지 여러분들은 잘 아시리라고 생각하고

있습니다. 4GB의 주소 공간에서 하위 주소 공간 2GB는 독립적인 프로세스를 위한 주소 공간, 그리고

상위 2GB는 커널을 위한 영역으로 존재하지요.
(물론 Windows Version에 따라서 주소 공간의 Limit가 다르기도 합니다.
이것은 또 옵션에 따라 변경이 가능합니다)

잘 아시다시피 Win32 운영체제는 Protected Mode를 기반으로 동작합니다. 커널 영역의 접근에 대한

보호가 필요하기도 하지만 각각의 프로세스들 역시 서로 격리되어 자신의 주소 공간 내에서만 접근이 가

능하지요. 이것은 프로세스들의 주소 공간에서 논리적인 주소(가상 주소)는 동일하나 물리 메모리에 맵

핑되는 주소의 위치는 서로 다르다는 것을 의미합니다. 그리고 두 프로세스가 서로 같은 논리 주소를 참

조하더라도 다른 물리 주소 공간을 참조할 수 있는 것은 페이지 테이블에서 논리 주소와 맵핑된 물리

주소를 가지고 있기 때문이겠지요.

중요한 것은 프로세스간 서로의 주소 공간에 대한 접근이 불가능하다는 점입니다. 물론 IPC와 같은 기법

으로 분명 프로세스간의 통신이 가능합니다. 하지만 이는 어디까지나 두 프로세스가 정상적으로 상호 프

로토콜을 가지고 접근하기 때문에 가능하지요. 그렇다면 방법이 존재하지 않는 것일까요..?

없다면 제가 글을 쓸 이유가 없겠지요.

우선 A Process와 B Process가 존재한다고 가정하겠습니다. 그리고 A Process는 Injector 역할의

Process라고 하겠습니다. 여기서 A Process가 Injector라는 의미는 공격자(악성코드)로 보시면 됩니

다. B Process는 Injection을 당하는(공격당하는 타깃) Process로 보시면 되겠습니다. 공격자(A

Process)의 목적은 B Process가 사용하고 있는 API를 Hook하여 자신의 목적 달성을 위한 코드를 수

행할 수 있도록 설치하는 것이라고 하겠습니다. API를 Hook을 한다는 것은 타깃 프로세스의 메모리를

수정함을 의미하는데, 가능한지 의문이 생기겠지요. 그리고 코드 역시 타깃 프로세스 내에 주입이 가능

한지 의문이 드실 겁니다. 그렇다면 여기서 어떻게 타깃 프로세스에 접근하여 그 메모리 내용을 수정 할

수 있을까요? 그에 대한 해답은 Debug API에 있습니다.

예를 들어, 우리가 VC와 같은 개발툴로 어떤 프로그램 소스를 작성하고 프로그램의 각 루틴들이 문제없

이 동작하는지를 살펴보기 위해서 디버깅을 하게 되는데, 원하는 특정 위치에 Break Point(중단점)을

설정하여 실행을 일시적으로 통제 할 수 있음을 아시고 계실 겁니다. 그리고 디버깅 중에 변수들의 값을

확인 할 수도 있고 변경도 가능하며, 심지어 디버깅 중에 코드의 수정도 가능하다는 사실 역시 아실 겁니

다. 우리가 위의 작업을 하고 있다고 하였을 때, 주시해야 할 부분은 VC는 "디버거 프로세스"가 되고

어떤 프로그램 소스라고 언급하였던 녀석은 "디버기(디버깅을 당하는) 프로세스"가 된다는 것입니다.

풀어서 한번 더 쉽게 설명드리자면, 우리가 어떤 프로그램을 작성하기 위해서는 이를 작성하기 위한

툴이 당연히 실행되고 있어야 할 것입니다. 그 툴이 VC를 말하구요. VC가 프로세스 상태가 되어야 한다

는 것이지요. 그리고 작성한 프로그램이 제대로 돌아가는지 테스트 해보기 위해서는 그 프로그램을 컴파

일하고 실행 파일을 생성하여 실행하게 되는데요.. 이것도 프로세스 상태로 존재함을 말씀드리는 겁니

다. 다만 여기서 작성한 프로그램은 그냥 실행이 아니라 "디버그 모드"로 실행시킴을 의미하지요.

핵심을 정리하자면, VC라는 프로세스로 디버깅이라는 작업을 통해서 작성한 프로그램을 타깃 프로세스

로 생성하여 그 주소 공간에 접근하고 수정(변수 및 코드 변경) 및 실행을 통제(브레이크 포인트) 할 수

있다는 사실입니다. 아무리 Protected Mode 기반의 OS라 할지라도 마이크로소프트사에서는 개발하고

있는 프로그램의 디버깅을 위해 이러한 메커니즘을 제공해 줄 수 밖에 없다는 것이지요. 하지만 DEBUG

API는 말 그대로 DEBUG를 위해서 존재하는 API들입니다.

MS에서도 충분히 보안에 대한 위험성을 인지하고 제약을 걸어 두었는데요.. DEBUG API는 반드시

DEBUG 모드로 프로세스를 ACCESS해야 사용이 가능합니다. 그리고 DEBUG API는 오버 헤드 또한 크

기 때문에 되도록이면 많은 사용을 기피해야 합니다. 디버깅 작업을 할 때 크게 느려지는 이유도 바로 거

기에 있지요. DLL Injection을 위해서 필요한 DEBUG API는 WriteProcessMemory 하나면 충분합니

다. API 이름 자체가 워낙 설명적이라 용도는 생략하겠습니다(MSDN 참고).


그럼 다시 본론으로 돌아와서 DLL INJECTION을 위해 수행되는 절차를 천천히 나열하면서

설명드리도록 하겠습니다..

1. 침투 대상이 되는 프로세스를 찾는다.
2. 디버그 모드의 권한으로 프로세스를 엑세스한다.
3. 프로세스 주소 공간에 인젝션 할 DLL Name 크기의 공간을 VirtualAllocEx로 할당한다.
4. 인젝션 할 DLL Name을 3번에서 할당한 주소 공간에 WriteProcessMemory로 쓴다.
5. CreateRemoteThread로 침투 대상이 되는 프로세스에 Thread를 생성한다.
6. 루트킷 DLL이 로드되는 시점에서 API Hook을 수행한다.



1. 침투 대상이 되는 프로세스를 찾는다.

    우선은 우리가 침투할 타켓 프로세스를 엑세스하기 전에 먼저 찾아야 할 것입니다.

    이것은 ToolHelp나 ADVAPI를 사용하여 현재 상주하고 있는 프로세스를 나열하고 그 정보를

    캡쳐 할 수 있도록 윈도우즈 자체에서 라이브러리를 제공해 주고 있습니다. 캡쳐한 정보에서

    는 우리가 원하는 프로세스 이름을 가지고 있는지 확인하고 일치하면 프로세스 아이디를

    얻어 올 수 있습니다.


2. 디버그 모드의 권한으로 프로세스를 엑세스한다.

    OpenProcess로 접근하고자하는 프로세스를 얻어온 아이디로 엑세스 할 수 있습니다.

    그중에 첫 번째 매개 변수는 프로세스를 어떤 권한을 가지고 접근할지에 대한 플래그들을

    줄 수 있는데, 우리가 인젝션을 위해 필요한 플래그들을 나열 해보면,

    PROCESS_CREATE_THREAD (CreateRemoteThread 허용)
    PROCESS_VM_OPERATION   (DEBUG 모드로 접근하기 위해서 반드시 적용해야 함)
    PROCESS_VM_WRITE          (WriteProcessMemory 허용)

    위의 3개 플래그가 필수가 되겠지요.


3번과 4번은 5번부터 설명드리고 왜 그러한 작업이 필요한지에 대해서 설명드리도록 하겠습니다.


5. CreateRemoteThread로 침투 대상이 되는 프로세스에 Thread를 생성한다.

    CreateRemoteThread는 [프로세스의 핸들 + CreateThread]의 조합이라고 보시면 됩니다.

    CreateThread는 현재 프로세스 내에서 쓰레드 생성이 가능한데, CreateRemoteThread는

    프로세스의 핸들을 매개 변수로 줄 수 있으므로, 타 프로세스에 쓰레드 생성을 가능케하지요.

    CreateRemoteThread에서 중요한 매개 변수들만 보면 아래와 같습니다.

    CreateRemoteThread(        프로세스 핸들,
                                              0,
                                                  NULL,
                            쓰레드 함수의 시작 주소,
                             쓰레드 함수의 파라미터,
                                                  NULL,
                                                       0,
                                                   NULL
                              );

    여기서 유의해야 할 부분이라면 CreateRemoteThread 함수를 실행하는 녀석은 "디버거 프로세스"

    라는 점입니다. 처음에 말씀드렸던 공격자 프로세스(Injector)를 의미합니다. 그리고

    CreateRemoteThead의 네 번째 매개 변수에는 쓰레드를 생성하여 호출할 함수의 주소가 되는데요.

    Caller 역시 "디버거 프로세스"라는 것입니다. 하지만 생성된 쓰레드의 Kernel Object는 "디버기 프

    로세스"의 소유며, 쓰레드의 실행도 "디버기 프로세스"입니다. 이것을 잘 구분하고 계셔야 됩니다.

    여기서 쓰레드의 시작점으로 호출할 함수의 주소는 LoadLibrary입니다. 그렇게 해야 비로소

    루트킷이 되는 DLL을 "디버기 프로세스"에서 로드하도록 만들 수 있을 테니까요.

    하지만 네 번째 매개 변수에 LoadLibrary라고 그냥 써버리면 안된다는 것입니다.

    왜냐하면, 공격자 프로세스의 LoadLibrary의 주소와 주입당하는 프로세스의 LoadLibrary를 참조하

    는 주소는 서로 다르기 때문이지요. 물론 LoadLibrary가 실제 KERNEL32.DLL에 위치하는 주소는

    동일합니다만 문제는 두 프로세스에서 LoadLibrary를 참조하는 주소(ILT의 주소)가 서로 다르다는

    것입니다. (이해를 위해서 또 많은 설명이 필요한데요. 궁금하시면 저에게 질문해 주시면 친절히 답

    변해 드리겠습니다)

    결국 GetProcAddress로 KERNEL32.DLL에 존재하는 LoadLibrary의 주소를 동적으로 구해와야 하

    지요. 공격자 프로세스나 주입당하는 프로세스나 KERNEL32.DLL이 맵핑되는 시작 번지는 항상 동일

    합니다. 그렇기 때문에 공격자 프로세스에서 로드된 KERNEL32.DLL의 GetProcAddress의 주소를

    구해도 전혀 문제가 되질 않는다는 겁니다.

    자~ 여기까지 왔으면 쓰레드 생성을 당한 디버기 프로세스(B Process)에서 LoadLibrary를 쓰레

    드 함수로 호출이 됩니다.

    (우리는 흔히 쓰레드를 사용하면 우리가 만든 사용자 함수를 엔트리에 두는데 그 대신에

     LoadLibrary라고 보시면 됩니다)

    여기서 중요합니다. 우리는 LoadLibrary( "우리의 루트킷 DLL" ) 형태로 호출을 원합니다.

    분명 LoadLibrary를 쓰레드의 시작 함수로 호출했지요? 그런데 위의 파라미터에서 존재하는 문자열

    은 어디에 존재하는가요? 우리는 "디버기 프로세스(공격당하는 타깃 프로세스)"에서 우리의 루트킷

    DLL 문자열을 선언 및 할당한 적은 어디에도 없다는 것입니다. 이해가 되십니까?

    그렇기 때문에 디버기 프로세스에 우리의 루트킷 DLL의 문자열을 할당 해 놓고 그 문자열의 시작 주

    소값을 아까 공격자 프로세스에서 CreateRemoteThread 할 때, 다섯 번째 매개 변수 파라미터에 함

    께 전달해 주어야하는 이유가 여기에 있습니다. 제가 5번부터 설명드린 이유도 여기에 있구요.

    이제 CreateRemoteThread 하기전에 3, 4번 과정을 먼저 수행해야 이유를 이해하실 수 있을 것입니

    다..


3. 프로세스 주소 공간에 인젝션 할 DLL Name 크기의 공간을 VirtualAllocEx로 할당한다.

    Win32 API를 살펴보면, 함수 이름에서 접미사 "Ex"가 붙은 API를 보실 수 있을 겁니다.

    함수 이름에서 알 수 있듯이 VirtualAlloc은 가상 주소 공간을 할당하는 API입니다.

    물론 현재 프로세스 내에서가 되겠지요. 하지만 이 함수에 Ex가 붙으므로 프로세스 핸들을

    매개 변수로 줄 수 있습니다. 결국 다른 프로세스에 가상 주소 공간을 할당 할 수 있음을 의미하지요.

    VirtuallAllocEx로 이제 타깃 프로세스로 할당 할 수 있음을 알 수 있습니다.

    (함수에 대한 자세한 설명은 MSDN을 참고하시길..)


4. 인젝션 할 DLL Name을 3번에서 할당한 주소 공간에 WriteProcessMemory로 쓴다.

    문자열 크기 만큼 타깃 프로세스에 할당했으면 이제 그 문자열을 쓰면 되겠습니다..

    이제 1~5번까지 모두 연결이 되나요? 결론은 타깃 프로세스에서 수행되는 쓰레드의 시작 함수가

    타깃 프로세스에 맵핑된 KERNEL32.DLL의 LoadLibrary가 될 것이고, LoadLibrary할 DLL은

    루트킷이 되는 DLL이 되겠지요. 물론 그 DLL의 이름은 타깃 프로세스 주소 공간에 이미 위치하기

    때문에 LoadLibrary( "루트킷 DLL" )가 타겟 프로세스에서 무사히 수행이 될 것입니다.


6. 루트킷 DLL이 로드되는 시점에서 API Hook을 수행한다.

    그렇다면 API Hook을 어떻게 할 수 있을까요? 그 비밀은 LoadLibrary API 내부에서 로드하기 위한

    여러 과정을 수행하게 되는데요. 중요한건 로드하고 나면 로드된 DLL의 DllMainCRTStartup을 호출

    합니다. DllMainCRTStartup은 다시 DllMain을 호출하구요.

    DLL에도 메인 함수가 있습니다. 이것은 프로그래밍시 메인 함수를 생략 할 수도 있습니다.

    (컴파일 될 때 컴파일러가 묵시적으로 기입하게 되지요)

    아무튼 중요한건 DLL 메인 함수를 호출하면서 매개 변수로 DLL_PROCESS_ATTACH라는 파라미터

    를 함께 넘겨주게 됩니다. 한마디로 DLL이 로드될 때 발생하는 이벤트라고 보시면 되겠지요.

    그 파라미터가 dwReason인데, dwReason == DLL_PROCESS_ATTACH 임을 체크한 후에 우리가
    하고자하는 루틴(API Hook)을 수행해주면 끝입니다.


    결국은 CreateRemoteThread 한방으로 인젝션 및 후킹까지 이루어 질 수 있음을 의미하지요.

    설명이 길었네요. 여기서 잘 이해가 안되시거나 궁금하신 점이 있으신 분들은 여기서 저에게 질문해 

    주시거나 메일로 질문해 주시면 친절히 설명해 드리도록 하겠습니다... 이상입니다.