API Hooking – '스텔스' 프로세스 (1)
또한 모든 프로세스를 후킹 하는 global hooking 방법에 대해서 살펴봅니다.
위 기법을 사용하여 특정 프로세스를 감추는 은폐(stealth) 기법에 대해 실습해 보겠습니다.
* 은폐(Stealth) 프로세스를 리버싱 전문 용어로 루트킷(Rootkit) 이라고 합니다.
보통 루트킷이라고 하면 커널 모드 후킹을 통한 프로세스, 파일, 레지스트리 등의 은폐 기술을 지칭합니다. 루트킷은 본 포스트의 설명 범위를 벗어나기 때문에 편의상 스텔스 프로세스라고 하겠습니다. (루트킷에 대한 내용은 커널에 대한 방대한 기반 지식을 요구합니다. 향후 제 블로그에서 하나씩 살펴볼 계획입니다.)
* 본문 내용을 편하게 읽기 위해서는 아래의 배경지식이 필요합니다.
☞ DLL Injection (DLL Injection – 다른 프로세스에 침투하기)
☞ API Hooking (API Hooking – 계산기, 한글을 배우다)
☞ API Hooking (API Hooking - 리버싱의 '꽃')
Tech Map
<Fig. 1>
API Code Patch 기법을 위 TechMap 에서 빨간색으로 표시하였습니다.
이 기법은 API Hooking 에서 가장 널리 사용됩니다.
그 이유는 대부분의 user mode API 를 자유롭게 후킹 할 수 있기 때문입니다.
* IAT Hooking 기법은 후킹하려는 API 가 프로세스의 IAT 에 존재하지 않을 경우 후킹이 불가능한 반면에 "API Code Patch" 기법은 그러한 제약 조건이 없습니다.
덧붙여 대상 프로세스의 메모리를 자유롭게 사용하기 위해 DLL Injection 기법을 사용하였습니다. (다음 번 주제에서 DLL 파일이 아닌 Code 자체를 Injection 하는 고급 기법에 대해서 살펴볼 예정입니다.)
API Code Patch 동작 원리
API Code Patch 를 통한 API Hooking 기법의 동작 원리에 대해서 알아보겠습니다.
* IAT Hooking 방식과 비교해 살펴보시면 더 쉽게 이해 될 것입니다.
☞ API Hooking (API Hooking – 계산기, 한글을 배우다)
IAT Hooking 방식이 프로세스의 특정 IAT 값을 조작해서 후킹을 하는 방식이라면, Code Patch 방식은 실제 API 코드 시작 5 byte 값을 JMP XXXX 명령어로 변경하는 방식입니다.
후킹된 API 가 호출되면 (패치된) JMP XXXX 명령어가 실행되어 후킹 함수로 제어가 넘어옵니다.
아래 그림은 Process Explorer(procexp.exe) 프로세스에 stealth.dll 파일을 인젝션 시킨 후 ntdll!ZwQuerySystemInformation() API 를 후킹하는 방법을 설명하는 그림입니다.
먼저 후킹 되기 전의 정상적인 프로세스 메모리의 모습입니다.
<Fig. 2>
(2) 해당 API 는 실행이 완료되면 호출 코드 다음 명령어 주소로 되돌아 갑니다.
이것은 매우 정상적인 상황의 API 호출 흐름입니다.
그럼 이제 API 가 후킹된 프로세스의 그림을 보시겠습니다.
stealth.dll 을 인젝션 하여 ntdll!ZwQuerySystemInformation() API 를 Code Patch 하였습니다.
<Fig. 3>
위 그림이 상당히 복잡하게 보입니다. 하나씩 차근차근 살펴보도록 하겠습니다.
먼저 stealth.dll 이 인젝션 되면서 ntdll!ZwQuerySystemInformation() API 를 후킹 하였습니다.
ntdll!ZwQuerySystemInformation() API 시작 주소(7C93D92E)의 5 byte 코드를 JMP 10001120 으로 덮어 써버린 것이죠. (5 byte 패치) 10001120 주소는 stealth!MyZwQuerySystemInformation() 함수 주소입니다.
이때 procexp.exe 코드에서 ntdll!ZwQuerySystemInformation() API 를 호출하면 코드 흐름은 아래 순서와 같습니다.
(2) 7C93D92E 주소에 있는 패치된 코드 JMP 10001120 에 의해서 10001120 주소(후킹 함수)로 점프합니다. 1000116A 주소의 CALL unhook() 명령어에 의해서 ntdll!ZwQuerySystemInformation() API 시작 5 byte 는 원래대로 복원됩니다.
(3) 1000119B 주소의 CALL EAX(7C93D92E) 명령어에 의해서 원본 함수(ntdll!ZwQuerySystemInformation) 가 호출됩니다. (unhook 상태이기 때문에 정상적으로 실행됩니다.)
(4) ntdll!ZwQuerySystemInformation() 의 실행이 완료되면 7C93D93A 주소의 RETN 10 명령에 의해 stealth.dll 코드 영역(자신을 호출한 위치)으로 리턴됩니다. 그리고 10001212 주소의 CALL hook() 명령어에 의해서 ntdll!ZwQuerySystemInformation() API 를 다시 후킹합니다. (시작 5 byte 를 JMP 10001120 명령어로 패치함)
(5) stealth!MyZwQuerySystemInformation() 의 실행이 완료되면 10001233 주소의 RETN 10 명령에 의해서 procexp.exe 프로세스 코드 위치로 리턴됩니다.
처음에는 좀 어려운 듯 보여도 몇 번만 읽어보시면 금방 이해하실 수 있으실 겁니다.
Code Patch 방법의 장점은 프로세스에서 사용되는 어떤 API 라도 후킹할 수 있다는 것입니다. IAT Hooking 의 경우에는 후킹 가능한 API 가 제한 된다는 사실과 비교해 보세요. (비록 코드는 조금 더 복잡하지만 말이죠.)
Code Patch 방법에 있어서 제한 사항은 후킹하려는 API 코드의 길이가 최소 5 byte 보다 커야 한다는 것입니다만, 모든 API 의 코드 크기는 5 byte 보다 크기 때문에 사실상 제한이 없다고 보시면 됩니다.
☞ API Hooking - '스텔스' 프로세스 (2)
ReverseCore
위 글이 도움이 되셨다면 추천(VIEW ON) 부탁 드려요~