Charpter 33 스텔스 프로세스
이번 챕터에서는 API 코드 패치를 통한 API 후킹 방법에 대해서 공부한다. 또한 모든 프로세스를 후킹하는 글로벌 후킹에 대해 공부하고, 이것을 사용하여 특정 프로세스를 감추는 스텔스 기법에 대해 알아볼것이다.
IAT 후킹 방식이 프로세스의 특정 IAT 값을 조작해서 후킹을 하는 방식이라면, 코드 패치 방식은 실제 API 코드 시작 5바이트 값을 JMP XXXXXXXX 명령어로 패치하는 방식이다.
다음은 후킹 되기 전의 정상적인 프로세스 메모리의 모습이다.
procexp.exe 코드에서 ntdll.ZwQuerySystemInformation() API를 호출하면 코드의 흐름은 다음과 같다.
1. procexp.exe의 00422CF7 주소의 명령어는 ZwQuerySystemInformation API를 호출하고 있다.
2. 해당 API 는 실행이 완료되면, 호출코드 다음 명령어 주소로 되돌아 간다.
다음은 API가 후킹된 프로세스 메모리의 모습니다. stealth.dll을 인젝션하여 ntdll.ZwQuerySystemInformation API를 코드 패치하였다.
위 그림의 호출 흐름은 다음과 같다.
1. 422CF7 주소에서 ntdll.ZwQuerySystemInformation() API를 호출(7C93D92E)
2. 7C93D92E 주소에 있는 패치된 코드 JMP 10001120에 의해서 10001120주소로 점프한다. 1000116A 주소의 CALL unhook() 명령어에 의해서 ntdll.ZwQuerySystemInformation API 시작 5바이트는 원래대로 복원된다.
3. 1000119B 주소의 CALL EAX(7C93D92E) 명령어에 의해서 원본 함수(ntdll.ZwQuerySystemInformation)가 호출된다.(언훅 상태이기 때문에 정상적으로 실행됨.
4.ntdll.ZwQuerySystemInformation()의 실행이 완료되면 7C93D93A 주소의 RETN 10 명령에 의해 stealth.dll 코드 영역으로 리턴된다. 그리고 10001212 주소의 CALL hook() 명령어에 의해서 ntdll.ZwQuerySystemInformation API를 다시 후킹한다.(시작 5바이트를 JMP로 패치)
5. stealth.MyZwQuerySystemInformation의 실행이 완료되면, 10001233주소의 RETN 10 명령에 의해서 procexp.exe 프로세스 코드 위치로 리턴된다.
이 챕터에서 설명하는 프로세스 은폐의 동작 원리는 '프로세스를 검색하는 API를 변조시키는 것'이다. 유저모드에서 프로세스를 검색하기 위한 API는 2종류가 있는데, CreateToolhelp32Snapshot과 EnumProcesses API 이다. 이 2가지 API들은 모두 내부적으로 ntdll.ZwQuerySystemInformation API를 호출하기 때문에, 위 2가지 API를 수정 할 것이 아니라 ZwQuerySystemInformation API만 수정 한다면 유저모드에서 프로세스 검색을 우회할 수 있다.
글로벌 API 후킹이란 현재 실행 중인 모든 프로세스와 앞으로 실행될 모든 프로세스에 대해서 API 후킹을 하는 것이다. 새로운 프로세스가 생성되려면 kernel32.CreateProcess API를 사용해야 한다. 프로세스를 실행시키는 다른 API들도 결국 내부적으로는 CreateProcess API를 호출한다. 따라서 현재 실행중인 모든 프로세스에 stealth.dll을 인젝션하고, stealth.dll에서 CreateProcess() API까지 같이 후킹하면 이후 실행되는 프로세스에게도 자동으로 stealth.dll을 인젝션 하도록 만들 수 있다.
모든 프로세스는 부모 프로세스(보통 explorer.exe)에서 CreateProcess를 이용하여 생성해주기 때문에 부모 프로세스의 CreateProcess API를 후킹하여 자식 프로세스에게 stealth.dll을 인젝션하도록 만들면 된다.
CreateProcess API를 후킹할 때는 kernel32.CreateProcessA(), kernel32.CreateProcessW() 두개의 API를 각각 후킹해야 한다.(ASCII 버전과 유니코드 버전)
허나 CreateProcess API를 후킹하는 것은 안정적이지 못하다. API를 후킹할때는 최대한 Low level의 API를 후킹하는 것이 좋다. 하여 Ntdll.ZwResumeThread() API를 후킹할 것이다. 글로벌 후킹을 하기 위해 이 API를 후킹하는 것이 현재 상황에서 가장 적합하다. 허나 ZwResumeThread API는 undocumented API라서 언제 바뀔지 알수가 없으므로, 안정성을 보장할 수 없다. 그렇기에 본 실습의 소스코드에서는 CreateProcessA, CreateProcessW API를 각각 후킹한다.
이제 소스코드 분석을 하게 될것이다.
위 함수는 HideProc.cpp 파일의 InjectAllProcess 함수이다. 소스코드를 보면 실행중인 모든 프로세스를 검색하여 각각 DLL 인젝션/이젝션을 수행한다. PID 100이하의 프로세스는 시스템 프로세스가 다수 있기 때문에 시스템의 안정성을 위해 DLL 인젝션을 하지 않는다.
위 소스코드는 stealth.dll 파일의 dllmain 함수이다. 소스코드를 보면 프로세스에 DLL 인젝션이 되었을때, CreateProcessA, CreateProcessW, ZwQuerySystemInformation API를 각각 후킹을 한다. 그리고 DLL 이젝션이 될때 각 API를 언후킹 해준다.
위 함수는 ZwQuerySystemInformation 함수를 후킹하여 사용하게 될 함수이다. STR_HIDE_PROCESS_NAME 변수는 숨기고자 하는 프로세스의 이름이 들어있는 변수이다. 그래서 소스코드를 읽어보면 모든 프로세스에 대해서 숨길 프로세스의 이름과 같은지 비교하고 이름이 같다면 ZwQuerySystemInformation 함수의 결과에서 숨길 프로세스의 내용을 지운다. 위 함수의 기본적인 순서는
unhook -> ZwQuerySystemInformation 실행 -> 다시 hook
위 순서를 따른다.
CreateProcess API의 후킹 함수의 경우 전체적인 unhook->정상 코드 실행-> 다시 hook의 뼈대는 같고 프로세스를 실행할때 dll을 인젝션 해준다.
7바이트 코드패치
다음은 일반적인 API의 시작 코드부분이다.
API의 최상위 부분을 보면 MOV EDI,EDI가 있는 것을 알 수 잇다. 이는 실행을 해도 아무의미가 없는 명령이다. 7바이트 코드 패치는 MOV EDI, EDI 명령과 이 명령위의 5개의 NOP 명령 총 7바이트를 패치하여 후킹을 시도하는 방식이다.
위 7바이트를 패치할 경우 위의 5바이트 패치와는 다르게 코드를 실행할때 API의 주소+2의 위치를 호출하면 정상적인 API 코드를 실행할 수 있으므로 unhook, hook의 과정을 거칠 필요가 없다.
대부분의 API는 함수 최상위 부분이 5개의 NOP과 MOV EDI,EDI로 구성 되어 있으나 그렇지 않은 API의 경우 7바이트 코드 패치를 적용시킬수 없다. 그렇기에 이러한 경우에는 5바이트 코드패치를 하는 수밖에 없다.
'리버싱핵심원리' 카테고리의 다른 글
| Reverse Engineering Chap 34 (0) | 2016.01.27 |
|---|---|
| Reverse Engineering Chap 3-6 (0) | 2016.01.27 |
| Reverse Engineering Chap 32 (0) | 2016.01.24 |
| Reverse Engineering Chap 28 (0) | 2016.01.24 |
| Reverse Engineering Chap 25~26 (0) | 2016.01.20 |