Programming/Kernel / Driver2008. 12. 5. 17:39

로드된 모듈 리스트 보기

lm 명령을 이용하면 된다.

  • lm k : Kernel Mode 모듈 표시
  • lm u : User Mode 모듈 표시
  • lm m : 패턴을 검사하여 해당하는 것만 보여줌 <lm m my*>

lkd> lm
start    end        module name
00c80000 00c90000   NateOnHook40u   (export symbols)       C:\Program Files\NATEON\BIN\NateOnHook40u.dll
00cb0000 00cb9000   MgHookDll C (export symbols)       C:\Program Files\LG Software\On Screen Display\MgHookDll.dll
01000000 0106a000   windbg     (pdb symbols)         D:\Symbol\WebSymbol\windbg.pdb\D6EF677AA54441279479F0307F05A8941\windbg.pdb
016a0000 01784000   ext        (export symbols)       C:\Program Files\Debugging Tools for Windows\winext\ext.dll
01790000 017c1000   kext       (pdb symbols)          D:\Symbol\WebSymbol\kext.pdb\6B643FC4E9F94FF4ABA4CEF1FD6F89D61\kext.pdb


모듈의 심볼(Symbol) 검사

x 모듈!패턴 을 입력하면 된다.

lkd> x nt!Ke*
804f8c02 nt!KeQuerySystemTime = <no type information>
804f8c9e nt!KeEnableInterrupts = <no type information>
80500e38 nt!KeSwitchKernelStack = <no type information>
804fad32 nt!KeReadStateProcess = <no type information>
804f9188 nt!KeReleaseInterruptSpinLock = <no type information>


데이터 타입(Date Type) 표시

dt 데이터 타입 을 입력하면 된다.

lkd> dt _EPROCESS
  +0x000 Pcb              : _KPROCESS
  +0x06c ProcessLock      : _EX_PUSH_LOCK
  +0x070 CreateTime       : _LARGE_INTEGER
  +0x078 ExitTime         : _LARGE_INTEGER
  +0x080 RundownProtect   : _EX_RUNDOWN_REF
  +0x084 UniqueProcessId  : Ptr32 Void
  +0x088 ActiveProcessLinks : _LIST_ENTRY
  +0x090 QuotaUsage       : [3] Uint4B
  +0x09c QuotaPeak        : [3] Uint4B
  +0x0a8 CommitCharge     : Uint4B



메모리 덤프(Memory Dump)

d* 명령들을 이용하면 된다.

  • db : Byte 형식 + Ascii 로 표시
  • dd : 데이터를 4Byte 형식으로 표시

lkd> db 8053db18
8053db18  8b ff 55 8b ec 8b 45 08-8b 4d 0c 8b 55 14 89 48  ..U...E..M..U..H
8053db28  0c 8b 4d 10 89 48 10 03-ca 89 48 14 8b 4d 18 83  ..M..H....H..M..
8053db38  c1 fe 89 48 18 8b 4d 1c-89 48 20 66 8b 4d 20 66  ...H..M..H f.M f



디스어셈블리(Disassembly)

u 주소 를 이용하면 된다. 특정 함수를 디스어셈블리 하고 싶으면 uf 주소 를 하면 된다.

  • u 주소 : 주소에서 일부분만 디스어셈블리
  • u 주소1 주소2 : 주소1에서 주소 2까지 디스어셈블리

lkd> u 8053db18 or uf nt!NtOpenProcess
nt!KeInitializeProfile:
8053db18 8bff             mov     edi,edi
8053db1a 55               push    ebp
8053db1b 8bec             mov     ebp,esp
8053db1d 8b4508           mov     eax,[ebp+0x8]
8053db20 8b4d0c           mov     ecx,[ebp+0xc]


메모리 영역 속성 보기(VA Dump)

!vadump 명령을 사용하면 된다. 만약 특정 메모리의 속성을 보고 싶다면 !vprot 주소 명령을 사용하면 된다.

0:000> !vadump
BaseAddress:       00000000
RegionSize:        00010000
State:             00010000  MEM_FREE
Protect:           00000001  PAGE_NOACCESS

BaseAddress:       00010000
RegionSize:        00001000
State:             00001000  MEM_COMMIT
Protect:           00000004  PAGE_READWRITE
Type:              00020000  MEM_PRIVATE


0:000> !vprot 30c191c
BaseAddress: 030c1000
AllocationBase: 030c0000
AllocationProtect: 00000080 PAGE_EXECUTE_WRITECOPY
RegionSize: 00011000
State: 00001000 MEM_COMMIT
Protect: 00000010 PAGE_EXECUTE
Type: 01000000 MEM_IMAGE


프로세스 관련

모든 프로세스를 보기위해서는 !process 0 0 를 입력하면 된다. 디버거를 특정 프로세스에 붙이고 싶으면 .process /i [pid] 를 입력하면 된다.


lkd> !process 0 0
**** NT ACTIVE PROCESS DUMP ****
PROCESS 8a3a3490  SessionId: none  Cid: 0004    Peb: 00000000  ParentCid: 0000
   DirBase: 00780000  ObjectTable: e1001c70  HandleCount: 521.
   Image: System

PROCESS 8a184158  SessionId: none  Cid: 03f0    Peb: 7ffdd000  ParentCid: 0004
   DirBase: 17a40020  ObjectTable: e163dd70  HandleCount:  20.
   Image: smss.exe

PROCESS 89df4da0  SessionId: 0  Cid: 0440    Peb: 7ffd5000  ParentCid: 03f0
   DirBase: 17a40040  ObjectTable: e1c6cb18  HandleCount: 626.
   Image: csrss.exe

Posted by skensita
Programming/Kernel / Driver2008. 12. 5. 17:37

앞에서 VMWare와 WinDbg를 연동하는 방법을 살표보았다.

 

  연동만 되었을 뿐 안에서 사용하는 함수들의 이름 같은 것을 확인하기 위해서는 심볼이 필요하다.

 

  이번에는 심볼 경로를 설정하여 디버깅을 좀 더 쉽게 할 수 있도록 설정하는 방법을 정리해보겠다.^^

 

  이번에도 스노야님의 강의를 많이 참고 하였다 (감사합니다~ ^_^)

==========================================================================================

 

자~ 레뒤 고~~

 

 

우선 커널 디버거를 켜보자~! 정상적으로 작동이 되었다면 "File - SymbolFilePath" 메뉴로 들어간 다음 아래 그림과 같이 경로를 지정해주자. 아래 경로는 MS사의 심볼 서버 경로입니다.

 

 

경로는 srv*C:\WebSymbols*http://msdl.microsoft.com/download/symbols 이다. 위 그림에서는 Reload란이 체크되어 있지 않지만 체크하도록 하자.!!

 

그리고 OK 버튼을 누르기 전에 아래 그림과 같이 C드라이브에 WebSymbols 디렉토리를 꼭 만들어 놓도록 하자. 

 

 

정상적으로 로드가 다시 되었다면 아래 그림과 같은 메시지를 출력할 것이다.

 

 

==========================================================================================

 

  앞에서 정리한 연동에 이어 심볼 경로까지 설정했다.

  이제 마음껏 커널을 파헤쳐보자!!@

 

  아~!! 커널을 파헤치기 전에 꼭 VMWare 스냅 샷을 찍어 놓도록 하자.

  예방 차원에서다.. 커널을 파헤치다 윈도우를 다시 깔아야 되는 수가 생기기 때문이다 ^_^

 

Posted by skensita
Programming/Kernel / Driver2008. 12. 5. 17:32

MS에서 배포한 커널 디버거인 WinDbg를 VMWare와 연동하는 것을 정리해보아야지~

아래 글은 내 생각대로 쓴 글도 있고 스노야님 께서 올려주신 강좌를 참조한(거의 배꼇다 ^^;) 부분도 있다.

 

 

대표적인 커널 디버거는 WinDbg와 SoftICE가 있지만 현재 SoftICE는 계발이 중지된 상태이다.

 

그래서 현재 가장 많이 사용되는 커널 디버거 툴이 WinDbg가 아닐까 한다. SoftICE와 다르게 WinDbg는 또 다른 컴퓨터를 디버깅할 수 있지 자기자신을 디버깅할 수 는 없다.

 

즉, 2대의 컴퓨터가 필요하다는 것이다. 하지만 VMWare를 이용하면 한 대의 PC로도 가능하다. 요즘은 PC의 성능이 좋아서 가상 머신정도는  돌리는 대는 별 지장이 없어보인다.

 

그럼 연동 하는 것을 스노야님 강좌를 참조하여 정리해 보자~!!!

 

==========================================================================================

 

우선 가상머신에 시리얼 포트를 추가해주어야 PC와 VMWare가 통신이 가능하다. 시리얼 포트는 "VM -> Settings -> Hardware" 탭에서 추가할 수 있다.

 

아래 그림은 시리얼 포트를 추가하는 모습을 차례대로 스크린 샷을 찍어 보았다.

 

 

 

 

 

 

 

 

 

여기에서 설정하는 파이프 이름이 WinDbg와 연동할 때 사용되는 이름이다.

 

 

 

 

위와 같이 하면 com_1이라고하는 시리얼 포트가 지정된다.(마지막 그림에서 I/O mode를 체크하는 이유는 잘 모르겠다 ^^;)  포트를 지정했으면 VMWare를 시작하여 부팅 시 디버그 모드가 작동할 수 있도록 boot.ini 파일을 작성하여야 한다.

 

아래는 boot.ini 파일을 수정한 그림과 수정 후 부팅과정에서 뜬 디버깅 모드이다.

만일 c: 루트디렉토리에 boot.ini 파일이 없다면 실행창에 "c:boot.ini"이라고 입력하면 창이 뜬다.

 

 

 

 

여기 까지 했으면 타겟 PC(여기서는 VMWare)의 설정은 끝났다. 이제 WinDbg를 설정하여 연결되도록 해야한다. 아래는 그림은 위에서 명명한 파이프 이름으로 WinDbg를 VMWare와 연동하는 모습이다.

 

 

설정은 끝났다. 정상적으로 연동이 되었다면 WinDbg를 켜놓은 상태에서 타겟 PC(VMWare)를 디버깅 모드로 부팅 시키면 아래 그림과 같이 WinDbg가 작동한다.

 

 

타겟 PC가 정상적으로 부팅된 후  WinDbg의 "Debug -> Break" 메뉴를 이용하여 타겟 PC의 커널을 마음껏 살펴 볼 수 있다. 아래 그림은 부팅 후 WinDbg를 이용하여 로드된 모듈을 확인한 모습이다.

 

 

 

==========================================================================================

 

이상으로 연동하는 것을 정리해 보았다.

Posted by skensita
Programming/Kernel / Driver2008. 12. 5. 17:22
Posted by skensita
Programming/Kernel / Driver2008. 12. 5. 17:03

커널단에서 프로세스를 보호하는 방법에 대해서 요약 작성 및 정리한 자료입니다.

 

 프로세스를 보호하는 드라이버를 거의 완벽에 가까울 정도로(?) 코딩하는 방법은 없을 지 고찰한 결과 아래와 같은 결론에 도달하게 되었습니다.

 목표 프로세스를 보호하는 방법

 

- 유저 모드 후킹

 

일단 기본적으로 프로세스를 조작하는 API인 NTDLL.DLL의 NtOpenProcess, NtTerminateProcess, NtReadVirtualMemory, NtWriteVirtualMemory, KERNEL32.DLL의 DebugActiveProcess 등이 후킹되어야 할 것입니다.

그리고 WINSTA.DLL의 WinStationTerminateProcess를 후킹하여 Terminal Service를 이용한 프로세스 종료를 차단합니다.

 

- 커널 후킹

 

ZwOpenProcess/ObReferenceObjectByPointer/PsLookupProcessByProcessId

ZwOpenProcess는 ObReferenceObjectByPointer를 호출하고 이는 다시 PsLookupProcessByProcessId를 호출하는데, 이들을 후킹하면 프로세스를 여는걸 어느정도 방지할 수 있을겁니다.

 

KeAttachProcess / KeStackAttachProcess

일단 프로세스의 메모리를 읽고 쓸땐 이 API를 사용하여 Page를 대상 프로세스의 Page로 전환해야하는데, 이를 방지하는 역할을 합니다.

 

ZwReadVirtualMemory/ZwWriteVirtualMemory/ZwTerminateProcess

프로세스의 메모리를 보호하며, 프로세스를 종료로부터 보호합니다.

 

ZwOpenThread/ZwGetContextThread/ZwSetContextThread/ZwTerminateThread

쓰레드를 보호합니다. 또한 쓰레드의 종료를 차단합니다.

 

ZwCreateFile (IoCreateFile)

이 것을 후킹하여 드라이버 혹은 프로세스 파일에 접근하는걸 차단할 수 있습니다.

경우에 따라선 ntoskrnl.exe를 접근하는 것을 막아 후킹을 해제하는걸 막아줄 수 있습니다.

 

ZwQuerySystemInformation

이 것을 후킹하면 프로세스를 숨길 수 있습니다. 밑에서 소개하는 DKOM보다는 안정적입니다.

 

- Internal 함수 후킹

 

PspTerminateThreadByPointer:

프로세스 종료의 가장 근본적인 API로 쓰레드를 종료시키는 가장 저수준의 API입니다. 따라서 이 함수를 후킹해놓으면 프로세스를 함부로 종료할 수 없습니다. 이 API는 NtTerminateThread()에서 ObfDereferenceObject()전에 호출되므로 코드바이트로부터 이를 추적해나갈 수 있습니다.

 

KiAttachProcess:

KeAttachProcess/KeStackAttachProcess의 Internal 함수로 후킹할 수 있으면 해놓는게 좋습니다. KeAttachProcess()에서 호출하는데, 이것이 이미 후킹되어있었을 수도 있으므로, 파일로부터 구해와야 합니다. 이 방법은 제가 블로그에 쓴 적 있습니다.

참고: [Unexported Symbol] KiAttachProcess() 주소값 구하기.

 

- DKOM

 

적절한 DKOM은 강력한 무기가 될 수 있습니다. 아래와 같은 방법을 사용함으로써 프로세스를 효과적으로 보호할 수 있습니다.

 

1. DebugPort = NULL

디버거를 무력화하여 예외 처리를 프로세스에게 넘기는 방법으로, 디버거를 무력화시키는 방법중 하나입니다. 이 값이 NULL인지 아닌지 체크하는 방법도 비교적 괜찮은 방법이나, NT4/2000계열에서는 간혹 쓰레기값이 들어있는 경우가 있기 때문에 권장하지 않습니다.

 

EPROCESS에서의 DebugPort의 Offset 구하는 방법은 대략 아래와 같이 코딩할 수 있습니다.

 

VOID CalcHandleTableOffset(VOID)
{
 PEPROCESS Proc = IoGetCurrentProcess();
 HANDLE PID = PsGetCurrentProcessId();
 PVOID GuessHandleTable = NULL;
 int i = 0;
 for(i = 0; i < PAGE_SIZE; i+=4)
 {
  GuessHandleTable = *(PVOID *)((PCHAR)Proc + i);
  if(MmIsAddressValid(GuessHandleTable))
  {
   if(*(PHANDLE)((PCHAR)GuessHandleTable + 0x08) == PID)
   {
    HANDLE_TABLE_OFFSET = i;
    return;
   }
  }
 }
}

 

CalcHandleTableOffset();

DEBUG_PORT_OFFSET = HANDLE_TABLE_OFFSET - 8;

 

2. EPROCESS->ActiveProcessLinks Unlinking

프로세스를 숨겨서 보호합니다. 추가적으로 EPROCESS의 UniqueProcessId를 이상한 값으로 바꾸면 핸들을 얻는것을 어느정도 막을 수 있습니다. (물론 관련 ETHREAD 구조체에서 PID를 얻을수도 있기 때문에 이 방법이 꼭 좋은건 아닙니다.)

만약 프로세스 ID를 바꾸었다면 프로세스 종료 전에 복구하여야합니다.(그렇지 않으면 CID_HANDLE_DELETION 블루스크린을 맛보시게 될겁니다.)

그리고, 추가적으로 PspCidTable이라는 Unexported symbol에서 프로세스 Offset을 NULL로 만들어놓으면, 근본적으로 핸들을 얻을 수가 없을겁니다. PspCidTable은 PsLookupProcessByProcessId()에서 ExMapHandleToPointer(Ex)를 호출하기 전에 참조하는 심볼로, 바이트 코드를 트래버싱하거나, XP 이상에서는 KPCR->KdVersionBlock의 PspCidTable 필드를 참조해서 구할 수도 있습니다.

 

3. EPROCESS::HandleTable Unlinking

숨겨진 프로세스를 찾는 대표적인 방법으로는 크게 두가지가 있습니다. EPROCESS::HandleTable를 traverse 하거나 PspCidTable을 트레버싱하는 방법입니다. 물론, ETHREAD를 이용하거나 기타 여러가지 방법(brute-force 등등)을 이용하면 뚫리긴 합니다만, HandleTable을 끊음으로서 더욱 찾기 힘들게 할 수 있습니다.

 

위에서 언급한 ActiveProcessLinks와 핸들 테이블은 아래와 같은 LIST_ENTRY 구조입니다.

typedef struct _LIST_ENTRY {
    PLIST_ENTRY Flink;
    PLIST_ENTRY Blink;
} LIST_ENTRY, *PLIST_ENTRY;

 

 드라이버를 효과적으로 숨기는 방법

 

1. PsLoadedModuleList unlinking

PsLoadedModuleList는 DRIVER_OBJECT 구조체의 DriverSection에 의해 Point됩니다. 그리고 아래와 같은 구조를 가지고 있습니다.(출처: rootkit.com 책)

typedef struct _MODULE_ENTRY
{
 LIST_ENTRY ModuleListEntry;
 DWORD Unknown1[4];
 DWORD BaseAddress;
 DWORD DriverStart;
 DWORD Unknown2;
 UNICODE_STRING Driver_Path;
 UNICODE_STRING Driver_Name;
} MODULE_ENTRY, *PMODULE_ENTRY;

 

ModuleListEntry를 순회해서 BaseAddress가 DriverObject->DriverStart와 같은 엔트리를 찾아내서 ModuleListEntry를 끊어주면 숨겨줄 수 있을겁니다.

 

2. IoGetDeviceObjectPointer Hook

심볼릭 링크로부터 DEVICE_OBJECT를 얻는 대표적인 방법으로 이를 후킹하면 DEVICE_OBJECT를 얻는걸 막을 수 있고, 결과적으로 드라이버를 2차적인 루트킷 스캐너로부터 숨길 수 있게됩니다. DEVICE_OBJECT는 DRIVER_OBJECT를 포인트하기 때문에, 필수적으로 후킹해야합니다.

 

3. ZwOpenKey() Hook

드라이버 서비스 키를 traversing 하여 드라이버 정보를 얻는것을 차단할 수 있습니다.

 

4. IoCreateFile() Hook

드라이버 파일을 접근하지 못하게 함으로써 드라이버에 대한 접근을 차단합니다. ZwCreateFile()은 IoCreateFile을 직접 호출하므로, IoCreateFile()을 후킹하면 탐지되기가 더 어려우며, 그만큼 효과를 발휘하게 됩니다.

 

5. ** 잡소리 **

보호된 프로세스로부터 드라이버 언로드 메시지가 왔을 때만 드라이버 언로드 핸들러를 설정합니다. 이는, 제 3자가 드라이버를 그냥 언로드하는걸 막을 수 있습니다. 

그리고 DeviceIoControl 메시지는 보호 대상 혹은 클라이언트의 메시지만 신뢰하는 것이 가장 좋습니다. (즉 말하자면 나머지는 쌩~ 까버리자는거죠.)

끝으로..

저는 위 방법들이 꼭 최선 해결책이라고 생각하진 않으며..

더 좋은 방어법이 있거나, 혹은 이를 우회하는 공격법이 존재할 수 있습니다.

그러므로, 자신만의 공격법이나 방어법을 만드는 것이 가장 좋은 방법이라고

생각이 됩니다.

Posted by skensita
Programming/Kernel / Driver2008. 12. 5. 16:58
Posted by skensita
Programming/Kernel / Driver2008. 12. 5. 16:53
Posted by skensita
Programming/Kernel / Driver2008. 12. 5. 16:39


HANDLE값은 무엇을 의미하는가?

HANDLE은 다음과 같이 define되어 있다.

#ifdef STRICT
typedef void *HANDLE;
#define DECLARE_HANDLE(name) struct name##__ { int unused; }; typedef struct name##__ *name
#else
typedef PVOID HANDLE;
#define DECLARE_HANDLE(name) typedef HANDLE name
#endif

HANDLE값은 무엇을 의미하는가?

Windows에서는 Process는 각각 HANDLE_TABLE이라는 구조체 형태를 가진,
ObjectTable에 의해 HANDLE을 관리한다. 그리고 Handle은 HandleTable에서의
Entry를 가르키는 색인값으로 사용되어 진다.
그럼으로 서로 다른 프로세스에서는 같은 HANDLE값이라도 다른 객체를 지시한다.
또 Kernel에서 생성된 HANDLE은 ObpKernelHandleTable이라는 특수한 HANDLE_TABLE
에 의해 관리되어 진다.

그럼 지금 부터 직접 접근하여 보자.
각 프로세스의 HANDLE_TABLE의 주소는 각 프로세스의 EPROCESS구조체에
저장되어 있다.

lkd> dt _EPROCESS
  +0x000 Pcb              : _KPROCESS
  +0x06c ProcessLock      : _EX_PUSH_LOCK
  +0x070 CreateTime       : _LARGE_INTEGER
  +0x078 ExitTime         : _LARGE_INTEGER
  +0x080 RundownProtect   : _EX_RUNDOWN_REF
  +0x084 UniqueProcessId  : Ptr32 Void
  +0x088 ActiveProcessLinks : _LIST_ENTRY
  +0x090 QuotaUsage       : [3] Uint4B
  +0x09c QuotaPeak        : [3] Uint4B
  +0x0a8 CommitCharge     : Uint4B
  +0x0ac PeakVirtualSize  : Uint4B
  +0x0b0 VirtualSize      : Uint4B
  +0x0b4 SessionProcessLinks : _LIST_ENTRY
  +0x0bc DebugPort        : Ptr32 Void
  +0x0c0 ExceptionPort    : Ptr32 Void
  +0x0c4 ObjectTable      : Ptr32 _HANDLE_TABLE //HANDLE_TABLE포인터
  +0x0c8 Token            : _EX_FAST_REF
   .......................
} EPROCESS, *PEPROCESS;

이 포인터 변수는 EPROCESS의 시작위치로 부터 0xC4만큼 떨어져 있다.
이 사실을 기반으로 특정한 Process의 ObjectTable주소를 알아내는 함수를 다음과
같이 작성할 수 있다.


PHANDLE_TABLE GetObjectTable(DWORD Pid)
{
  NTSTATUS status;
  PEPROCESS Process;
  PHANDLE_TABLE ObjectTable;

status = PsLookupProcessByProcessId(Pid,&Process);
if(NT_SUCCESS(status))
{
  ObjectTable = (PHANDLE_TABLE)(*(ULONG*)((ULONG)Process + 0xC4));
  DbgPrint("Pid : %X PEPROCESS : %X ObjectTable :
                      %X\n",Pid,Process,ObjectTable);
  ObDereferenceObject(Process);
  return ObjectTable;
}
else
{
  DbgPrint("PsLookup error\n");
}
  ObDereferenceObject(Process);
return 0;
}

이 HANDLE_TABLE은 다음과 같은 형태를 갖는다.

typedef struct _HANDLE_TABLE
{
  ULONG TableCode;  //PHANDLE_TABLE_ENTRY**
  PEPROCESS QuotaProcess;
  PVOID UniqueProcessId;
  EX_PUSH_LOCK HandleTableLock [4];
  LIST_ENTRY HandleTableList;
  EX_PUSH_LOCK HandleContentionEvent;
  PHANDLE_TRACE_DEBUG_INFO DebugInfo;
  LONG ExtraInfoPages;
  ULONG FirstFree;
  ULONG LastFree;
  ULONG NextHandleNeedingPool;
  LONG HandleCount;
  ULONG Flags;
} HANDLE_TABLE, *PHANDLE_TABLE;


TableCode :
HADLE_TABLE_ENTRY의 주소를 갖는다.
이 ENTRY에 해당 Process들이 사용하고 있는 Object들이 있다.

QuotaProcess :
System의 경우 0을 가지며,
그외의 Process들은 자신의 PEPROCESS값을 갖는다.

UniqueProcessId :
이 HANDLE_TABLE의 소유주의 ProcessId값이다.

HandleTableLock :
EX_PUSH_LOCK 4개가 연결된 배열로 되어 있다.
EX_PUSH_LOCK은 다음과 같은 형태를 갖는다.

typedef struct _EX_PUSH_LOCK
{
  union
  {
       struct
       {
       ULONG Waiting:1;
       ULONG Exclusive:1;
       ULONG Shared:30;
       };
       ULONG Value;
       PVOID Ptr;
  };

} EX_PUSH_LOCK, *PEX_PUSH_LOCK;

HandleTableList :
다음 HandleTable로 연결된 ListEntry이다.

PHANDLE_TRACE_DEBUG_INFO :
해당 HANDLE_TABLE의 HANDLE Trace Debug정보를 갖는 구조체의 포인터이다.
Windows가 Debug모드로 시작되었을때만 사용하는 듯 하다.
HANDLE_TRACE_DEBUG_INFO는 다음과 같은 형태를 갖는다.

typedef struct _HANDLE_TRACE_DB_ENTRY
//HANDLE_TRACE_DEBUG_INFO가 사용하는 구조체
{
  CLIENT_ID    ClientId;
  HANDLE       Handle;
  ULONG        Type;
  PVOID        StackTrace[16];

} HANDLE_TRACE_DB_ENTRY; *PHANDLE_TRACE_DB_ENTRY;

typedef struct _HANDLE_TRACE_DEBUG_INFO
{
  ULONG CurrentStackIndex;
  HANDLE_TRACE_DB_ENTRY TraceDb[4096];

} HANDLE_TRACE_DEBUG_INFO, *PHANDLE_TRACE_DEBUG_INFO;


ExtraInfoPages :
부가적인 정보에 대한 Page의 Offset값을 갖는것으로 보이나,
주로 0으로 Set되어 있다.

FirstFree :
HandleTable에서 가장 처음으로 사용가능한(비어있는) Entry의 주소이며,
이 값이 바로 다음으로 생성될 HANDLE이다.

LastFree :

NextHandleNeedingPool :
다음번 핸들이 필요로 하는 Pool의 Size값이다.

HandleCount :
이 HandleTable에서 사용하고 있는 Handle의 총갯수이다.

Flags :
이 HandleTable의 Flag값이다.


TableCode가 Entry의 주소를 가르킨다고 하였는데,
HANDLE_TABLE_ENTRY는 다음과 같은 형태를 갖는다.

typedef struct _HANDLE_TABLE_ENTRY_INFO {
  ULONG AuditMask;
} HANDLE_TABLE_ENTRY_INFO, *PHANDLE_TABLE_ENTRY_INFO;


typedef struct _HANDLE_TABLE_ENTRY {
  union {
       PVOID                       Object;   //이 Handle이 가르키는 Object
       ULONG                       ObAttributes;  //Handle의 속성
       PHANDLE_TABLE_ENTRY_INFO    InfoTable;
       ULONG                       Value;
  } u1;
  union {
       ULONG                       GrantedAccess; //Handle이 Object에 접근하는 접근권한
       USHORT                      GrantedAccessIndex; //Access Index
       LONG                        NextFreeTableEntry; //다음번 사용가능한 Table의 Entry
  } u2;
  USHORT                          CreatorBackTraceIndex;
} HANDLE_TABLE_ENTRY, *PHANDLE_TABLE_ENTRY;


System의 HANDLE_TABLE을 WinDbg를 이용하여 살펴 보면서,
직접확인 하여 보자.
위에서 작성했던 GetObjectTable()함수를 이용하여 System Process의
ObjectTable을 구했더니 0xE1001D28 이었다.
이 주소를 기반으로 다음과 같이 WinDbg를 이용하여 표시해 보았다.

lkd> dt _HANDLE_TABLE 0xE1001D28
  +0x000 TableCode        : 0xe1002000
  +0x004 QuotaProcess     : (null)
  +0x008 UniqueProcessId  : 0x00000004
  +0x00c HandleTableLock  : [4] _EX_PUSH_LOCK
  +0x01c HandleTableList  : _LIST_ENTRY [ 0xe13f4144 - 0x80563ec8 ]
  +0x024 HandleContentionEvent : _EX_PUSH_LOCK
  +0x028 DebugInfo        : (null)
  +0x02c ExtraInfoPages   : 0
  +0x030 FirstFree        : 0x698
  +0x034 LastFree         : 0
  +0x038 NextHandleNeedingPool : 0x800
  +0x03c HandleCount      : 255
  +0x040 Flags            : 0
  +0x040 StrictFIFO       : 0y0

현재 이 ObjectTable의 TableCode값은 0xe1002000임을 알 수 있다.
0xe1002000의 메모리를 표시하여 보자.

e1002000 00000000 fffffffe 817bc9e9 001f0fff 817bc329 00000000 e1288de9 000f003f
e1002020 e1007149 00000000 e1291641 0002001f e1291571 00020019 e12870f1 00020019
e1002040 e12915d9 00020019 e12929f9 00020019 e1292991 00020019 e12913d1 00020019
e1002060 e12928f9 00020019 e1291339 00020019 e1293341 0002001f e1292651 00020019
e1002080 817b6209 001f0003 00000000 00000050 00000000 000000b4 00000000 00000058
e10020a0 00000000 00000054 00000000 0000005c 00000000 00000048 00000000 0000004c
e10020c0 00000000 00000044 00000000 00000060 00000000 00000064 00000000 00000068
e10020e0 00000000 0000006c 00000000 00000070 00000000 00000074 00000000 00000078
e1002100 00000000 0000007c 815b1331 02000003 00000000 00000080 817cc981 00000000
e1002120 8179fc39 001f03ff 00000000 000000bc 00000000 000000a4 00000000 00000094
e1002140 817da6c9 001f0003 00000000 0000009c e1302de9 000f000f 00000000 00000098
e1002160 00000000 000000ac 00000000 000000b0 e12fd2f9 000f000f 00000000 000000c0
e1002180 00000000 000000c4 00000000 000000c8 00000000 000000cc 00000000 000000d0
e10021a0 00000000 000000d4 00000000 000000d8 00000000 000000dc 00000000 000000e0
e10021c0 00000000 000000e4 00000000 000000e8 00000000 000000ec 00000000 000000f0
e10021e0 00000000 000000f4 00000000 000000f8 00000000 000000fc 00000000 00000100
e1002200 00000000 00000104 00000000 00000108 00000000 0000010c 00000000 00000110
e1002220 00000000 00000114 00000000 00000118 00000000 0000011c 00000000 00000120
e1002240 00000000 00000124 00000000 00000128 00000000 0000012c 00000000 00000130
e1002260 00000000 00000134 00000000 00000138 00000000 0000013c 00000000 0000014c
e1002280 00000000 00000288 00000000 00000140 00000000 00000144 00000000 00000148
e10022a0 00000000 00000088 00000000 00000150 00000000 00000154 00000000 00000158
e10022c0 00000000 0000015c 00000000 00000160 00000000 00000164 00000000 00000168
e10022e0 00000000 0000016c e1407499 00020019 815632b9 001f01ff 8157e7c1 001f03ff
e1002300 00000000 00000170 00000000 000006f8 e140a5e1 00020019 00000000 00000194
e1002320 81563f79 0012019f 00000000 00000180 00000000 0000018c 00000000 00000198

하나의 Object에 대하여 ObjectHeader와 GrantedAccess의 쌍으로 이루어져
있음을 볼 수 있다.

재밌는 것은 이 HandleTable의 첫번쨰 Entry는
ObjectHeader의 Pointer값으로 0을,
GrantedAccess값으로 0xfffffffe을 가짐으로 써, HandleTable의 시작지점임을
나타낸다.
이것은 보는 관점에 따라 여러가지의 의미가 있는데, HANDLE을 가지고 보자면,
0번 Handle은 존재할 수 없고, 4번 Handle이 처음이라는 것을 의미한다.
각 Handle의 Entry가 무언인지는 어떻게 알 수 있을까?
필자는 다음과 같은 식을 이용하여 계산한다.

EntryAddress = ObjectTable->TableCode + (Handle / 4) * 8

예를 들어, System Process의 0x350이라는 Handle에 대한 Entry의 주소는 다음과
같을 것이다.

EntryAddress = 0xe1002000 + (0x350/4) * 8
                   =
0xe10026A0


* Handle값은 위의 식에서 알 수 있다 싶이 늘 4의 배수이다.


이 Entry에 대한 정보를 WinDbg로 표시하여 보면,

lkd> dt _HANDLE_TABLE_ENTRY 0xe10026A0
  +0x000 Object           : 0xe138cd19
  +0x000 ObAttributes     : 0xe138cd19
  +0x000 InfoTable        : 0xe138cd19 _HANDLE_TABLE_ENTRY_INFO
  +0x000 Value            : 0xe138cd19
  +0x004 GrantedAccess    : 0x20019
  +0x004 GrantedAccessIndex : 0x19
  +0x006 CreatorBackTraceIndex : 2
  +0x004 NextFreeTableEntry : 131097

다음과 같이 올바른 Entry를 구했음을 알 수 있다.
(이건 어떻게 글을 읽는 독자에게 맞는지 보여줄 방법이 없다.
그냥 믿으세요 :p )

여기서 Object값은 이 Handle에 의해  참조되는 Object이다.
각 Object는 다음과 같은 형태를 갖는다.

typedef struct _OBJECT_TYPE_INFO {
  UNICODE_STRING  ObjectTypeName; //Object의 Type이름의 포인터
  UCHAR           Unknown[0x58];
  WCHAR           ObjectTypeNameBuffer[1];
} OBJECT_TYPE_INFO, *POBJECT_TYPE_INFO;

typedef struct _OBJECT_TYPE_INITIALIZER
{
  USHORT Length;
  UCHAR UseDefaultObject;
  UCHAR CaseInsensitive;
  ULONG InvalidAttributes;
  GENERIC_MAPPING GenericMapping;
  ULONG ValidAccessMask;
  UCHAR SecurityRequired;
  UCHAR MaintainHandleCount;
  UCHAR MaintainTypeList;
  POOL_TYPE PoolType;
  ULONG DefaultPagedPoolCharge;
  ULONG DefaultNonPagedPoolCharge;
  PVOID DumpProcedure;
  PVOID OpenProcedure;
  PVOID CloseProcedure;
  PVOID DeleteProcedure;
  PVOID ParseProcedure;
  PVOID SecurityProcedure;
  PVOID QueryNameProcedure;
  PVOID OkayToCloseProcedure;
} OBJECT_TYPE_INITIALIZER, *POBJECT_TYPE_INITIALIZER;


typedef struct _OBJECT_TYPE {
  ERESOURCE               Mutex;
  LIST_ENTRY              TypeList;
  UNICODE_STRING          Name;
  PVOID                   DefaultObject;
  ULONG                   Index;
  ULONG                   TotalNumberOfObjects;
  ULONG                   TotalNumberOfHandles;
  ULONG                   HighWaterNumberOfObjects;
  ULONG                   HighWaterNumberOfHandles;
  OBJECT_TYPE_INITIALIZER TypeInfo;
  ULONG                   Key;
  ERESOURCE               ObjectLocks[4];
} OBJECT_TYPE, *POBJECT_TYPE;




typedef struct _OBJECT_HEADER
{
  LONG PointerCount;   //이 Object를 가르키는 Pointer의 수
  union
  {
       LONG HandleCount; //이 Object를 대상으로 하는 Handle의 수
       PVOID NextToFree;
  };
  POBJECT_TYPE Type; //Object의 Type
  UCHAR NameInfoOffset;
  UCHAR HandleInfoOffset;
  UCHAR QuotaInfoOffset;
  UCHAR Flags;
  union
  {
       POBJECT_CREATE_INFORMATION ObjectCreateInfo;
       PVOID QuotaBlockCharged;
  };
  PSECURITY_DESCRIPTOR SecurityDescriptor;
  QUAD Body;
} OBJECT_HEADER, *POBJECT_HEADER,**PPOBJECT_HEADER;

PointerCount와 HandleCount로 이 Object가 몇번이나 참조되었는지,
핸들에 의해 사용되고 있는지를 알 수 있는데,
참조한 Object의 경우 ObDereferenceObject()함수를 사용하여,
반환을 하게 되는데, 이떄 ObDereferenceObject()함수는 PointerCount만 1감소시킨다.
얻은 Handle을 반환하고자 할 경우 ZwClose()함수를 사용하는데,
이때 ZwClose()함수는 PointerCount와 HandleCount을 각각 1씩 감소 시킨다.
이 HandleCount와 PointerCount가 0이 되었을떄 객체에 대한 완전한 반환이 이루어진다.
0x350이라는 Handle값의 ObjectHeader값으로 0xE138CD19라고 표시되는데,
실제 ObjectHeader의 주소는 이 값에서 1을 뺀값이 올바르다.
그럼으로 ObjectHeader의 주소는 0xE138CD18이다.
이 ObjectHeader에 대한 정보를 WinDbg로 표시하여 보면 다음과 같다.

lkd> dt _OBJECT_HEADER 0xE138CD18
  +0x000 PointerCount     : 1
  +0x004 HandleCount      : 1
  +0x004 NextToFree       : 0x00000001
  +0x008 Type             : 0x817b3bf8 _OBJECT_TYPE
  +0x00c NameInfoOffset   : 0 ''
  +0x00d HandleInfoOffset : 0 ''
  +0x00e QuotaInfoOffset  : 0 ''
  +0x00f Flags            : 0x2 ''
  +0x010 ObjectCreateInfo : 0x00000001 _OBJECT_CREATE_INFORMATION
  +0x010 QuotaBlockCharged : 0x00000001
  +0x014 SecurityDescriptor : (null)
  +0x018 Body             : _QUAD


lkd> dt _OBJECT_TYPE 0x817b3bf8
  +0x000 Mutex            : _ERESOURCE
  +0x038 TypeList         : _LIST_ENTRY [ 0x817b3c30 - 0x817b3c30 ]
  +0x040 Name             : _UNICODE_STRING "Key"
  +0x048 DefaultObject    : 0x80562240
  +0x04c Index            : 0x14
  +0x050 TotalNumberOfObjects : 0x29b
  +0x054 TotalNumberOfHandles : 0x299
  +0x058 HighWaterNumberOfObjects : 0x2cd
  +0x05c HighWaterNumberOfHandles : 0x2cb
  +0x060 TypeInfo         : _OBJECT_TYPE_INITIALIZER
  +0x0ac Key              : 0x2079654b
  +0x0b0 ObjectLocks      : [4] _ERESOURCE



출처 : http://blog.paran.com/rabbit3000/16563169
#

Posted by skensita
Programming/Kernel / Driver2008. 12. 5. 16:37

어떻게 하면 프로세스 리스트에서 숨겨진 프로세스만
골라낼 수 있을까?
이건 많은 이들이 궁금해 하는 주제인듯 하여,
아주 심플한 소스와 함께 한번 소개해 보겠다.
뭐, 별건 아니고 그냥 말그대로 숨겨진 프로세스를
효과적으로 잡아 내는것에 대한 소스이다.

1. 전역 변수 PEPROCESS SE_Proc; 를 만든다.

2. DriveEntry에 다음과 같은줄을 추가했다.
------------------------------------------------------
NTSTATUS DriverEntry (
IN PDRIVER_OBJECT pDriverObject,
IN PUNICODE_STRING pRegistryPath ) {
...
...
SE_Proc = PsGetCurrentProcess();
...
...
}

3. ZwQuerySystemInformation()을 SSDT Hook하여
다음 함수에 연결하였다.
-----------------------------------------------------------
NTSTATUS NTAPI NewZwQuerySystemInformation(
IN ULONG SystemInformationClass,
IN PVOID SystemInformation,
IN ULONG SystemInformationLength,
OUT PULONG ReturnLength
)
{
NTSTATUS rc;
CHAR Attack_Process_Name[PROCNAMELEN];
PEPROCESS eproc,currproc;
PLIST_ENTRY start_plist,plist_hTable = NULL;
DWORD *d_pid;
extern PsOffset_HandleTable,PsOffset_HandleList,PsOffset_hPID;
extern SE_Proc;
char *nameptr;
CHAR Hided_Process_Name[PROCNAMELEN];

InterlockedIncrement( &G_nLockUseCounter );
GetProcessName( Attack_Process_Name );

eproc = SE_Proc;
plist_hTable = (PLIST_ENTRY)((*(DWORD*)((DWORD)eproc +
PsOffset_HandleTable)) + PsOffset_HandleList);
start_plist = plist_hTable;

rc = ((ZWQUERYSYSTEMINFORMATION)(OldZwQuerySystemInformation)) (
SystemInformationClass,
SystemInformation,
SystemInformationLength,
ReturnLength );

if( NT_SUCCESS( rc ) )
{

if(5 == SystemInformationClass)
{
struct _SYSTEM_PROCESSES *curr = (struct _SYSTEM_PROCESSES *)SystemInformation;
struct _SYSTEM_PROCESSES *prev = NULL;

while(curr)
{
ANSI_STRING process_name;
RtlUnicodeStringToAnsiString( &process_name, &(curr->ProcessName), TRUE);
if( (255 > process_name.Length) && (0 < process_name.Length) )
{
d_pid = (DWORD*)(((DWORD)plist_hTable + PsOffset_hPID)
- PsOffset_HandleList);
if(curr->ProcessId != *d_pid)
{
DbgPrint("---Hided process---");
nameptr = (PCHAR)eproc + gProcessNameOffset;
strncpy(Hided_Process_Name, nameptr, NT_PROCNAMELEN);
Hided_Process_Name[NT_PROCNAMELEN] = 0;
DbgPrint("ProcessName:%s",Hided_Process_Name);
DbgPrint("ProcessId:0x%X",*d_pid);
DbgPrint("EPROCESS:0x%X",eproc);
DbgPrint("-------------------");
}

if(0 == strncmp( process_name.Buffer, MYPROCESS, NT_PROCNAMELEN))
{
DbgPrint("[Alarm] ProcessScan Detected\n");
DbgPrint("Called by %s\n",Attack_Process_Name);

if(prev)
{
if(curr->NextEntryDelta)
{
prev->NextEntryDelta += curr->NextEntryDelta;
}
else
{
prev->NextEntryDelta = 0;
}
}
else
{
if(curr->NextEntryDelta)
{
(char *)SystemInformation += curr->NextEntryDelta;
}
else
{
SystemInformation = NULL;
}
}
}
}
RtlFreeAnsiString(&process_name);
prev = curr;
if(curr->NextEntryDelta) ((char *)curr += curr->NextEntryDelta);
else
{
if(start_plist != plist_hTable)
{
do
{
d_pid = (DWORD*)(((DWORD)plist_hTable + PsOffset_hPID)
- PsOffset_HandleList);
if(*d_pid == 0)
{
plist_hTable = plist_hTable->Flink;
break;
}
PsLookupProcessByProcessId(*d_pid,&eproc);
nameptr = (PCHAR)eproc + gProcessNameOffset;
strncpy(Hided_Process_Name, nameptr, NT_PROCNAMELEN);
Hided_Process_Name[NT_PROCNAMELEN] = 0;
DbgPrint("---Hided process---");
DbgPrint("ProcessName:%s",Hided_Process_Name);
DbgPrint("ProcessId:0x%X",*d_pid);
DbgPrint("EPROCESS:0x%X",eproc);
DbgPrint("-------------------");
plist_hTable = plist_hTable->Flink;

}while(start_plist != plist_hTable);
}
curr = NULL;
}
}
}
}
InterlockedDecrement( &G_nLockUseCounter );
return rc;
}
----------------------------------------------------------

뭐, 이게 끝이다.
사용한 방법을 간단히 설명하자면,
ZwQuerySystemInformation()으로
프로세스 리스트를 구해오고자 할떄,
해당 프로세스 번째의 Pid와
HandleTable을 Tracing하면서
해당 번쨰의 Pid가 같은지 비교한다.
같지 않으면 거기엔 Process가 Hide가 된것이다.
ZwQuerySystemInformation()으로 구해진
프로세스 리스트가 끝났을떄,
아직 HandleTable의 Tracing이 아직 끝나지
않은 상태라면, 그 뒤로 숨겨진 프로세스들이 있다고 볼 수 있다.
그럼으로 그들을 출력해준다.
그런데, 마지막 프로세스는 Process Scan을 행한 프로세스
자신인데, PID는 0이다. 그럼으로 PID가 0일때는
출력하지 않고 멈춘다.

출력양식은 다음과 같다.
-------------------------------------------
---Hided process---
ProcessName:SomethingHided.exe
ProcessId:0xB47
EPROCESS:0x80485064
-------------------
-------------------------------------------

이 코드에서는 ZwQuerySystemInformation()내에서
처리하는 방식으로 했지만,
단순히 숨겨져 있는 프로세스 목록만 알고 싶다면,
HandleTable과 EPROCESS의 pListEntry를
이용하는 것이 좋을 것이다.

이 방법에서 조금 더 나아가면,
숨겨진 프로세스를 골라낼 수 있을 뿐만 아니라,
curr->NextEntry에 struct _SYSTEM_PROCESSES
형태로 데이터를 집어넣고 연결하여 프로세스 리스트에
반영되게 할 수도 있다. :)

-본자료는 듀얼님이 작성하신것으로 스크랩글임을 밝혀둡니다
Posted by skensita
Programming/Kernel / Driver2008. 12. 5. 13:11

작업 관리자를 보면 현재 실행되고 있는 프로세스들이 있습니다.

이 프로세스들은 아주 간단한 손터치로 인해 강제 종료를 손쉽게 할 수 있습니다.

 

 

자 그럼 작업 관리자를 통해서 프로세스 강제 종료 보호하는 방법은 어떤 것 들이 있을까요?

 

제가 설명드리고자 하는 방법은 ZwOpenProcess()함수를 후킹하는 방법에 소개를 하겠습니다.

(SSDT 후킹 방법은 제 블로그에 올린 강좌나 다른 인터넷 자료를 참고하세요...^^;)

 

ZwOpenProcess()함수를 후킹하여 해당 함수의 리턴값을 "STATUS_INVALID_CID"로

리턴하면 됩니다.

 

아주 쉽죠? 이렇게 하면 다음 화면처럼 해당 프로세스를 강제 종료하지 못합니다.

 

 

참고) ZwOpenProcess()함수를 후킹하면 나쁜 목적으로 해당 프로세스에 접근하여 메모리를

        참조하는 것 또한  방지가 됩니다.

Posted by skensita