티스토리 뷰

드라이버 쪼물딱 거리기 3탄

bkdp3_ssdt_hook.zip
fxloader.zip

오늘은 SSDT hooking 입니다.



최근에는 SSDT 에 대한 내용은 정말 인터넷에 많이 널려있습니다.
사실 SSDT 훅에 대한 이야기는 아주 오래된(?) – undocumented windows 2000 secrete 에서 첨 본거 같기도 하고.. - 이야기이긴 합니다.
어떤 해킹을 다루거나 하는 커뮤니티(?)에 보면 뭐 최신의 기법인 것 처럼 이야기 하는 사람도 있긴 하지만 말이지요.

여기서는 실제 SSDT (System Service Descriptor Table) 을 후킹해서 원하는 기능을 구현하는 가장 간단한 형태의 코드를 예제로 보일 것입니다.
요즘은 좀 구식(?) 기술로 취급 받기도 합니다만.. windows 의 내부구조를 엿보는 좋은 경험이 될 수도 있습니다.
그러나 이 예제를 완벽히 내것으로 만들기 위해서는 Windows 에서 제공하는 Windows API 의 구조나 Native Api 등등 꽤 많은 사전 지식이 필요합니다.
이런 사전 지식들은 대한 내용은 “Technical Report - Windows system call 분석”문서와 구글 신의 힘을 빌면 충분히 필요한 만큼의 정보를 얻을 수 있을 것입니다.



http://www.windowsitlibrary.com/Content/356/07/1.html
undocumented Windows NT 의 전문이 실려있습니다.
루트킷 개발에 핵심이 되었던 기술들에 대한 설명이 아주 잘 되어있지요.
훌륭한 책입니다.

우리가 후킹 할 SSDT 의 위치는 아래의 그림에 설명되어 있습니다.
Ntoskrnl.exe 는 KeService* 의 두개의 심볼을 export 하고 있구요.



WinDbg 를 통해 해당 심볼들이 export 되었는지 확인해 봅시다.

두 심벌이 export 되어있지만 type information 이 없지만 뭐 찾아보면 다 있겠죠?
조사하면 다 나옵니다. ^.^


KeServiceDescriptorTable 을 위한 SERVICE_DESCRIPTOR_ENTRY 구조체.
ServiceTableBase :
각 서비스 별 매핑 함수 포인터들의 목록이 저장된 테이블의 포인터
ServiceCounterTableBase:
무시 -_-
NumberOfService:
이 테이블이 가지고 있는 서비스의 개수
ParamTableBase:
각 서비스별 파라미터의 크기를 나타내는 값을 가진 테이블의 포인터
--- Windows 의 구조와 원리. 정덕영 저 에서 베껴옴

정리해 보면 Windows System Call 정보를 관리하는 무언가가 있는데 이것은
KeServiceDescriptorTable 이라는 이름으로 export 가 되어있고, 이 테이블의 구조는
위에서 설명한 구조체 모양입니다.
즉 KeServiceDescriptorTable 의 정보를 바꿔치기 하면 윈도우가 제공하는 시스템 콜을 내가 원하는 함수로 대체할 수 있다는 것이죠.
이것이 흔히들 말하는 SSDT HOOK 의 개념입니다.

사실 ring3  ring 0 로의 권한 이행, 즉 유저레벨에서 windows api 호출 -> system call 호출까지의 과정에 대한 이해가 필요합니다. 그러나 여기서 그런 것 까지 설명하기엔.. 이야기가 너무 길어질 것 같고..
Windows 의 구조와 원리(정덕영 저)를 보면 아주 자세히 잘 설명되어 있습니다.
(정말 좋은 책이죠)
아니면 전에 작성한 “Technical Report - Windows system call 분석”를 참고하길 바랍니다.

자 우리가 하고자 하는 것은 KeServiceDescriptorTable 을 변조하는 것이죠?
먼저 무엇을 해야 할까요? 그렇습니다. 변조하고자 하는 KeServiceDescriptorTable 의 주소를 알아야 할 것입니다.
SSDT 의 경우 앞에서 본 것처럼 ntoskrnl 이 export 하고 있기 때문에 별다른 코딩 없이 바로 import 해서 사용할 수 있습니다.
나중에 export 되지 않는 symbol 을 찾아 쪼물딱 거리는 꽁수에 대해서도 보여줄 것이니 너무 조급해 하지 말기를..
아무튼 아래의 코드 한 줄로 땡입니다.


// direct import KeServiceDescriptorTable
//
__declspec(dllimport) SERVICE_DESCRIPTOR_ENTRY KeServiceDescriptorTable;




이걸로 끝이면 얼마 좋겠습니까 만은 뭐 그렇게 만만하지 않죠.
KeServiceDescriptorTable 의 권한 문제가 있습니다.
SSDT 의 겨우 xp 나 2003 에서 read only 이란 것이죠.따라서 read only 로 설정된 커널 메모리 영역을 쓰기가 가능하도록 만들어 주어야 합니다.
만일 read only 영역에 write 를 하면 어떻게 될까요?
기억하시죠 ? 우린 현재 커널 레벨에서 놀고 있답니다. 실수는 용서가 안돼요.
시원스럽게 펼쳐진 파~아란 화면을 만날 수 있을겁니다.
WinDbg 같은걸 연결해 놓은 상태라면 아래와 같은 메시지를 만날 수 있을 것이구요.


kd> !analyze -v
*******************************************************************************
* *
* Bugcheck Analysis *
* *
*******************************************************************************

ATTEMPTED_WRITE_TO_READONLY_MEMORY (be)
An attempt was made to write to readonly memory. The guilty driver is on the
stack trace (and is typically the current instruction pointer).
When possible, the guilty driver's name (Unicode string) is printed on
the bugcheck screen and saved in KiBugCheckDriver.
Arguments:
Arg1: 8057659e, Virtual address for the attempted write.
Arg2: 00576121, PTE contents.
Arg3: f9c66be0, (reserved)
Arg4: 0000000b, (reserved)


몇 가지 방법이 있는데 지금 2가지 밖에 기억이 안 나네요. (세가지 넘게 있었던거 같은데..)
한가지는 MDL (Memory Descriptor List) 를 이용하는 것입니다.
뽀대 나는 엘레강스한 코드를 보여주지만 좀 복잡해 보이는 방법이죠.
SSDT 가 가리키는 physical memory 에 대한 포인터(?) 를 하나 더 만들어서 접근하는 것입니다. SSDT 가 가리키는 메모리 영역에 대한 심볼릭 링크 같은녀석을 하나 더 만들고요 이 심볼릭 링크는 쓰기 가능으로 두는 겁니다.
SSDT 는 읽기 전용이었지만 새로 만든 심볼릭 링크는 읽기 쓰기가 되므로 쓰기가 되지요.
실제 Write operation 이 발생하는 physical memory 는 동일하구요.

아무튼 이런 방법도 있다 정도로 넘어가죠. 나중에 실제 코드를 보여드릴 기회가 있을겁니다.

또 다른 방법은 CR0 레지스터를 이용해서 SSDT 의 write protection 을 제거하는 것입니다. 예제에서도 사용할 방법입니다.
커널 모드 루트킷이나 IceSword 같은 툴을 분석해 보신분들은 CR0 를 이용해서 write protection 을 제거하는 루틴을 봤을 지도 모르겠습니다.
(예전에 http://somma.egloos.com/2131561 에서도 소개한 바 있지요)


위 그림은 x86 의 Control register 들입니다.
각 레지스터의 용도는 따로 알아서들 공부하시고, 우리가 관심 가질 것은 CR0 레지스터입니다. 그 중에서도 관심 가질 것은 WP 비트입니다.
이 비트는 Write Protection 비트란 말씀입니다.. Copy-On-Write 를 구현하는데도 사용되는 플래그라고 하고요.
완벽한 레퍼런스를 원하시는 분들은 IA-32 매뉴얼 volume 3 chapter 2.5 를 보세요 :)


//
// 콘트롤 레지스터 관련 (IA-32 manual vol3, ch 2.5
// CR0 (Control Register Zero) 레지스터의 WP 비트(16)는 쓰기 속성제어에 사용됨
//
#define CR0_WP_MASK 0x0FFFEFFFF

/** ---------------------------------------------------------------------------
\brief

\param
\return
\code
\endcode
-----------------------------------------------------------------------------*/
VOID ClearWriteProtect(VOID)
{
__asm
{
push eax;
mov eax, cr0;
and eax, CR0_WP_MASK; // WP 클리어
mov cr0, eax;
pop eax;
}
}

/** ---------------------------------------------------------------------------
\brief

\param
\return
\code
\endcode
-----------------------------------------------------------------------------*/
VOID SetWriteProtect(VOID)
{
__asm
{
push eax;
mov eax, cr0;
or eax, not CR0_WP_MASK; // WP 비트 세팅
mov cr0, eax;
pop eax;
}
}



편의를 위해서 두 개의 함수를 작성했구요 함수 이름만 봐도 알 기능을 알 수 있을 것입니다.


게임 해킹에 자주 사용되는 T-Search 같은 툴을 보면 게임 프로세스의 데이터 영역이나 코드 영역을 수정하는 것을 볼 수 있는데 이런 툴은 어떻게 만들 수 있을까요?

게임 프로세스의 핸들 획득 -> Read/Write process memory 류의 api 를 통해서 해당 메모리 읽기/쓰기 오퍼레이션을 수행할 것이다. 물론 이 방법이 유일한 방법은 아니다.
다른 방법으로도 구현할 수 있을 것입니다. 뭐 간단하죠. 사실 대부분의 툴들이 이렇게 만들어집니다.
이번 예제에서는 이런 툴들을 차단하기 위한 기능을 구현할 것입니다. SSDT hook 을 이용해서 말이죠. 이름 하여 메모리 디펜더 !
좀더 폼 나는 이름 없을까 잠깐 고민했지만 어차피 PoC 수준의 코드이니 대충 넘어가도록 하지요.

메모리 디펜더를 구현하기 위해 OpenProcess () 를 후킹할 것입니다.
Kernel32::OpenProcess() 함수는 ntdll::Zw/NtOpenProcess() 를 호출하는데요
Ntdll::Zw/NtOpenProcess 는 다시 ntoskrnl::ZwOpenProcess() 로 다시 매핑됩니다.
이때 ntdll 의 Zw/NtOpenProcess() 는 service call index 를 사용하고.. sysenter 인스트럭션을 통해 ring0 로 넘어가고, MSR 레지스터를 참조하고…등등..
여러 단계를 거치는데 이에 대한 내용은 위에서 설명한 참고자료를 활용하세요.

아무튼 우리는 ntoskrnl::ZwOpenProcess() 함수를 후킹 할 것입니다.

먼저 후킹할 ZwOpenProcess() 함수의 주소를 얻어야 할 것입니다.
여러 가지 방법을 쓸 수 있겠지만 이 함수 또한 exported 된 함수이므로 아래의 코드로 쉽게 사용할 수 있을 것입니다.



Ntoskrnl.exe 는 ZwOpenProcess() 함수를 export 하고 있는데

이 그림은 IDA 5.0.0.879 버전으로 본 그림인데 IDA 의 버그인 것 같네요. ORZ ..
db 2 dup(0)
이 코드는 헥사 코드로 보면 아래와 같습니다.

B8 에 해당 하는 opcode 를 찾아 보면

Mov 인스트럭션이지요.
여기서 +rd, +rw 지시자는 0xB8 을 베이스로 해서 0-7 사이의 레지스터 인덱스를 더한 값이 opcode 자체가 완성되는데 ZwOpenProcess 의 헥사 코드를 보면 B8 로 시작하는데 레지스터 인덱스의 값이 0 (B8 과 B8 의 차는 0 이므로) 되겠지요.
레지스터 인덱스 0 은 IA 32 메뉴얼을 보면..

파란 박스 부분입니다.
즉 다시 말해서 B8 7A 00 00 00 은
mov ax, 0x7A 또는 mov eax, 0x7A 로 해석되는게 맞다는게 제 결론입니다.
(B8 opcode 는 +rd, +rw 를 모두 받을 수 있는데 지금 같은 경우 +rd 로 해야 하는지 +rw 로 해야 하는지 모르겠다. 아는 분이 있으면 좀 알려주세요. 꼭 ~~~ )

아무튼 IDA 가 만들어낸 코드가 잘 못된 것이 맞는 듯 하네요.


참고로 ntoskrnl.exe 의 다른 함수들을 살펴보면 위와 같이
mov eax, service_index_number
명령으로 시작하지요.
유추해 보건데 NtOpenProcess() 의 맨 처음 명령은
mov eax, 0x0000007A
인게 거의 확실합니다. 진짭니다. 믿으세요.

여기서 중요한 사실 하나를 발견할 수 있는데요.
System call 의 서비스 인덱스 번호는 system call 의 두 번째 바이트에 4바이트 형태로 존재한다는 것입니다.
ZwOpenProcess 함수의 헥사 코드를 살펴보면 아래와 같습니다.

B8 은 mov eax 이고, 뒤에 4바이트 즉, 7A 00 00 00 이 서비스 인덱스 번호인 것이지요.
Little endian 이므로 0x0000007A 값이 바로 ZwOpenProcess() 함수의 인덱스 번호가 됩니다.

앞에서 KeServiceDescriptorTable.ServiceBase 필드는
각 서비스 별 매핑 함수 포인터들의 목록이 저장된 테이블의 포인터
라고 했는데 다시 말하면 서비스 인덱스별 서비스 콜의 주소의 배열이란 얘기가 됩니다. 즉 KeServiceDescriptorTable.ServiceBase[0x7A] == NtOpenProcess 란 겁니다.
참고로 ZwXXX 의 경우 외부 export 용도이고, NtXXX 의 경우 실제 구현코드를 가집니다. 물론 ntdll 의 경우에는 두가지 모두 동일한 주소를 가리키고 있지만 말입니다. Ntdll 은 단순히 proxy 역할만을 한답니다.
궁금하신 분들은 ntdll 과 ntoskrnl 을 직접 열어서 확인해 보기 바랍니다.

앞에서 알아낸 아주 유용한 규칙을 제대로 써먹어 보도록 합시다.
아래의 매크로는 위에서 알아낸 규칙을 코드로 만든 것 뿐입니다.
SSDT 훅을 할 때 아주 유용하게 써먹을 수 있을 것입니다.
(이것은 제 아이디어가 아니라 ntrootkit 의 코드에서 얻은 것 입니다.)

#define SYSCALL_INDEX(_Function) *(PULONG)((PUCHAR)_Function + 1)
#define ORG_SYSCALL_PTR(_orgFunc) \
&(((PLONG) KeServiceDescriptorTable.ServiceTableBase)[SYSCALL_INDEX(_orgFunc)])


후킹할 ZwOpenProcess 의 주소도 얻었고, Write Protection 도 무력화 할 수 있고,
ZwOpenProcess 의 인덱스 번호를 통해서 구현 코드의 주소값도 알아낼 수 있겠고..
아 한가지 빠졌네요.
ZwOpenProcess 를 대체할 hook procedure 를 작성해야죠. ^^ 가장 중요한 걸 빼먹었군요.


뚝딱 ! 다 짰습니다. -_-;;
ZwOpenProcess() 함수 – 정확히 말하면 NtOpenProcess 겠네요 – 의 원형은 인터넷에 한번만 검색하면 잔뜩 나옵니다. Undocumented api 정리해 놓은 책도 있을 정도니까요.
아니면 NtOpenProcess 를 아주 잠깐만 리버싱 해 보면 알 수 있겠죠.

우리의 hook procedure 는 아주 간단합니다.
모든 OpenProcess() 를 실패로 처리해 버립니다. 와우~ 멋지죠 -_- (돌 날라오는 소리가 막 드릴네요~)
실제로 써 먹기 위해서는 동적으로 보호할 프로세스의 ID 나 핸들을 받아서 처리해야 겠죠. 물론 예외처리 (B 프로세스는 A 프로세스를 Open 할수 있게 하는등의)도 필요할 테구요. 이런 구현을 위해서는 DeviceIoControl() 등을 써서 통신을 해야 할 것입니다.
그런 예제는 다른 곳에서 많이 보실 수 있을 것이구요.

이제 잘 동작하는지 테스트를 해봐야 겠죠?
드라이버 로더는 지난번에 사용한 FxLoader 를 씁니다.


디버그 메시지를 확인 하기 위해 Dbgview 로 실행해 주세요.

FxLoader 를 열고 드라이버를 선택하고,
Init -> Register -> start 버튼을 차례로 누르면 됩니다. 아시죠?

이제 Cheat Engine 프로그램으로 테스트를 해봅시다.
Cheat Engine 은 범용 게임 해킹 도구입니다. T-Search 처럼 프로세스의 특정 값을 찾는데에도 쓰이고, 메모리를 조작하는데도 사용할 수 있구요.
스피드 핵도 되지요.
앞에서 설명한 것처럼 모든 기능을 제공하기 위해서는 프로세스의 핸들을 얻어야 하는데 그것을 커널에서 차단하고 있기 때문에 불가능합니다.
오~ 훌륭하단 생각이 들지 않습니까?


이것 뿐만 아니라 작업 관리자에서 프로세스 종료를 눌러도 프로세스가 종료되지 않습니다.


SSDT 를 이요하면 참 많은 재밌는 일을 할 수 있습니다. 프로세스 숨기기, 파일 폴더 숨기기 등등 하고 싶은건 다 할 수 있지요. 보시다 시피 별로 어려운 것도 없거든요.
벌써 부터 사악한 짓 할 생각에 벌써 부터 즐거워 지시는 분들이 꽤 보이는것 같군요.
그런데 후킹 하는것도 쉽지만 탐지하는것도 무진장 쉽답니다. 일반 적인 API 후킹 탐지랑 똑같거든요.
당연히 복원또한 어렵지 않고요.

사실 많은 보안 제품들 (PC 용 방화벽, DRM 등등) 이 많이 사용하는 기술중에 하나가 SSDT 훅입니다.
해킹에 쓰인다면 더 없이 강력한 도구가 되지요. 그러나 hook 이 가지는 단점을 그대로 안고 가는 문제도 있습니다. 내가 후킹한 걸 복원시켜 버리거나 아니면 제 3의 드라이버가 다시 후킹해 버리면 말짱 꽝이되지요.
그래서 몇 몇 제품들은 자신만의 SSDT 를 만들어서 사용하고는 합니다.
그렇게 되면 SSDT 훅에 상관없이 자신만의 코드를 사용할 수 있게 되지요.
이를 SSDT relocation 이라 부르기도 합니다.
나중에 기회가 된다면 보여드리지요.
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2025/01   »
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 31
글 보관함