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



I. 디버깅

1. 기본 디버깅 환경

Windows Driver 디버깅을 위해선 다음 그림과 같이 디버깅용 컴퓨터가 필요합니다. 디버깅 컴퓨터에는 타겟 컴퓨터와 같은 운영체제가 로딩되어 있어야 하고 디버깅을 위한 프로그램이 필요합니다. 디버깅 프로그램은 DDK에서 제공하는데 자세한 사용법은 도움말을 참조하시기 바랍니다.

Debugger                         Debuggee

 

그림에서 있듯이 컴퓨터간의 데이터 교환은 RSC-232 직렬 통신을 통해 이루어 지는데 통신에 필요한 케이블은 다음 그림과 같이 만들어 사용하면 됩니다.

2. Windows 2000/XP에서 디버깅 환경

디버깅을 시작하기 이전에 컴퓨터간의 통신 속도를 맞춰줘야 하고 타겟 컴퓨터를 디버그 모드 설정 해야 합니다. Windows NT 계열에서는 다음과 같이 boot.ini 파일에 다음과 같이 추가해 주면 부팅 디버그 모드로 부팅할 일반 모드로 부팅할 사용자에게 묻는데 이때 디버그 모드를 선택해 주면 디버그 모드로 부팅하게 됩니다.

[boot loader]

timeout=30 default=multi(0)disk(0)rdisk(0)partition(1)\WINNT

[operating systems]

multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Microsoft Windows XP Professional" /fastdetect

multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Microsoft Windows XP Professional" /fastdetect /debugport=com1 /baudrate=57600

통신 속도 설정은 위의 내용을 보면 알겠지만 /debugport 직렬 케이블이 연결된 포트이고 /baudrate 통신 속도를 나타냅니다. 여기에 설정된 속도와 Windows 2000 DDK에서 제공하는 디버깅 프로그램에서의 속도를 맞춰주면 됩니다.

 

다음은 Windbg 실행하여 환경설정을 하고 디버깅 하는 방법입니다.

1 방법은 ddk 설치 같이 설치 되는 windbg 실행 방법이고, 2번의 방법은 최신 Windbg6.2.7.4 실행 방법입니다.  2번의 Windbg6.2.7.4 사용하는 것이 사용하기에도 쉽고 자세한 디버깅을 있습니다.

 

1) 기본 windbg실행

View->Options 선택했을 뜨는 창으로 포트, 속도 Flags 다음과 같이 설정해 주면 됩니다.

참고로 디버그 모드로 부팅하였는데 Windbg 디버깅에 관련된 아무런 내용도 출력되지 않을 시에는 Windbg에서 디버깅 명령인 ‘Ctrl+C’ ‘g+ Enter’ 번갈아 눌러 주시면 디버깅 내용이 출력됩니다.

 

 

 

2) Windbg 6.2.7.4

다운 방법: http://www.microsoft.com/whdc/ddk/debugging/installx86beta.mspx 에서

Install 32-bit Beta Version 6.2.7.4 (April 29, 2003) 8.2 MB 클릭하여 다운 받으시기 바랍니다.

 

실행 화면

 

kernel debugger 실행

 

 

속도와 포트 설정

 

위와 같이 하면 기본적인 디버깅은 설정이 됐습니다.

 

추가적으로,

File->Symbol File Path

: 드라이버를 컴파일 하면, xxx.pdb파일이 xxx.sys파일과 같이 생성되는데, 파일을 등록 해주면, break 경우( ) bluescreen) 해당 소스에 line 함수 명들과 같은 자세한 정보가 출력이 됩니다.

xxx.pdb파일을 symbol(심볼) 파일이라고 합니다. 파일은 컴파일 드라이버 파일에 대해 함수위치와 소스정보에 대해 가지고 있습니다. 그래서 관련 파일이 로딩되면, 컴파일 소스에 대한 정보(함수들, 소스의 line 정보(소스가 있다면)…) 얻을 있습니다.

등록방법은 File->Symbol File Path 클릭할 경우, 심볼 파일의 경로를 설정하여주면, 해당 경로의 심볼을 로딩하게 됩니다. 심볼이 바뀐 경우에는 Reload check 줍니다.

 

그림을 보면, 심볼이 세미콜론으로 2개가 등록이 되어있습니다.

SRV*c:\websymbols*http://msdl.microsoft.com/download/symbols

: web으로 관련 심볼을 다운받는 것입니다. 예전에 심볼을 미리 다운받아서 위치를 지정해 주었지만, 지금은 필요할 경우 웹에서 직접 다운 받는다. 심볼파일 중에는 ndis.sys 같은 커널 함수들에 대한 심볼들을 포함하고 있어서 커널 소스는 없어도 함수 이름 등은 수가 있습니다.

c:\symbols

: 경로는 사용자가 추가해 주는 심볼 파일 경로입니다. 사용자가 컴파일해서 만들어진 심볼파일을 폴더에 복사해놓으면 심볼이 로딩됩니다.

 

아래의 그림은 심볼이 등록되었음을 보여주고 있습니다.

 

File->Source File Path

소스의 경로를 지정해 줍니다. 소스를 로딩 경우, break 지점이 소스 내에 있으면, 해당 소스의 라인에서 p명령어를 치며, 디버깅을 나갈 있습니다.

 

View

MFC 에서 디버기을 메모리, 디스어셈블, 스택 등을 있듯이 여기서도 있습니다.

 

3. 디버깅 명령어

Windows 드라이버의 디버깅은 Windows Visual Studio와는 달리 사용자가 디버깅 명령을 직접 입력하는 방식으로 진행됩니다. 디버깅을 위해선 여러 가지 디버깅 명령어들을 다룰 있어야 되는데 디버깅 명령어에 대한 자세한 내용은 DDK 도움말을 참고하시기 바랍니다.

다음은 디버깅에서 많이 사용하는 명령어를 정리한 것으로 정도의 명령어로도 간단한 디버깅은 충분히 수행할 있을 것이라 생각됩니다.

명령어

Ctrl + C, Ctrl + Break

일시 중지

g

계속 진행

bp

Break Point 설정 ) bp SnSReceive

bl

Break Point List 보기

p

Asm 명령어 줄씩 디버깅

bc

Break Point 삭제 ) bp SnSReceive

db, dw, dd

메모리 덤프

Ln cs:eip

현제 타겟 컴퓨터의 실행 위치

?

명령어 도움말

.reboot

타겟 컴퓨터 재부팅

 

. !analyze -v

디버기(debugee) BlueScreen 발생하거나, 시스템에 문제가 있을 경우, Windbg 프로그램은 break 걸리게 된다. 이때 break 원인을 자세히 알기 위해서 !analyze -v명령을 명령줄에 치면, 자세한 정보를 얻을 있다.

 

 

 

 

 

4. Windows 98에서 디버깅 환경

디버깅을 시작하기 이전에 컴퓨터간의 통신 속도와 사용할 포트를 맞춰줘야 하고 디버깅이 시작될 타겟 컴퓨터를 디버그 모드 시작해야 합니다. Windows 98 에서는 컴퓨터간의 통신 속도를 맞추기 위해서 다음과 같은 절차가 필요합니다.

 

1) 타겟 컴퓨터에서 해야할 .

a)       타겟 컴퓨터에 98DDK 설치되어 있어야 한다.

b)       설치된 경로를 C:\98DDK라고 하면 C:\98DDK\bin\Runwdb98.bat 파일의 내용을 다음과 같이 수정해야한다.

수정전: @wdeb98 %1  /n  /x  /c:2  /r:19200  /f:runwdeb.wrf  ..\win.com

수정후: @wdeb98 %1  /n  /x  /c:1  /r:19200  /f:runwdeb.wrf  c:\windows\win.com

 

여기서 통신속도(/r) 사용자가 정해주는 값으로 19200 사용해도 되고 다른 값으로 사용해도 된다. 하지만 값은 반드시 디버거의 Rterm98.exe에서 사용하는 값과 같아야 한다. 디버깅을 하다가 종종 출력되는 내용이 깨지는 경우가 있는데 이럴때는 통신속도를 변경하여 알맞은 속도로 설정해주면 된다. 포트에 해당되는 것은 ‘/c’ 이다. 값도 기본적으로 2 되어있고 그대로 사용하면 된다. 중요한 것은 어떤 포트를 사용하느냐가 아니라 시리얼케이블이 연결된 포트와 /c 포트와 일치해야한다는 것이다. “/f:runwdeb.wrf” 심볼파일을 등록할 사용하는 파일로 부분에 대해서는 ‘c)’ 자세하게 기술하겠다.

c)       C:\98DDK\bin\ runwdeb.wrf 내용은 아래와 같다.

사용자가 드라이버 소스를 컴파일해서 만들어진 심볼파일을 SnSDrv.sym이라고 하면 위의

림과 같이 기입해주고 SnSDrv.sym 파일을 C:\98DDK\bin 복사해주면 디버깅시 심볼이 등록

된다.

 

98DDK에서는 이러한 심볼파일들을 다음과 같이 제공하고 있다.

98SE: 98ddk CD \DEBUG_WINDOWS98SE\Retail\

98: 98ddk CD \Dbg_sym\Retail\

 

아래의 그림은 디버깅이 시작될 심볼이 등록되었음을 보여주는 화변을 캡쳐한 것이다.

 

2) 디버깅 컴퓨터에서 해야할 .

a)       디버깅 컴퓨터에 98DDK 설치되어 있어야 한다.

b)       C:\98DDK\bin\Rterm98.exe 실행한다. 다음은 실행한 화면이다.

c)       메뉴에서 “Settings->Options ” 선택하면 아래와 같은 창이 뜬다.

d)       위의 그림과 같이 설정해주면 타겟 컴퓨터에서 설정한 통신속도와 포트가 일치한다.

e)       확인 누른다.

 

3) 디버깅하기

a)       이제 모든 환경이 설정되었다.

b)       디버깅 컴퓨터에서는 C:\98DDK\bin\Rterm98.exe 실행한다.

c)       타겟 컴퓨터에는 디버깅하고자하는 드라이버가 설치되어 있어야하고 심볼을 등록해주면 자세한 정보를 얻을 수가 있다.

d)       타겟 컴퓨터를 도스모드로 재부팅한다.

e)       C:\98DDK\bin\Runwdb98.bat 파일을 실행하면 디버깅 컴퓨터에 실행된 Rterm98 화면에서 디버깅되는 내용을 확인 있다.

Posted by skensita
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