카테고리 보관물: 프로그래밍/소스/팁

Undocument Native API 중 BOOLEAN 형의 함수 인자

Windows 에서 BOOL 형 과 BOOLEAN 형은 TRUE, FALSE를 구별하는 변수의 타입으로 사용되지만 서로 약간의 차이점이 존재합니다.
그것은 아래와 같이 선언됨에 따라서 BOOLEAN 으로 선언한 변수의 크기는 1바이트이며, BOOL 은 4바이트(32bit 시스템에서) 인 점입니다.

typedef unsigned char BOOLEAN
typedef int BOOL

일반적으로 프로그래밍을 할 경우에, 이 둘을 별로 구별하지 않고 사용하게 되는데요. 하지만 후킹(함수 포인터 변경 방식)을 하는 경우 원본 함수를 호출하려 할때 정확하지 않은 함수 인자를(즉, 사이즈가 다른) 사용하면 때때로 에러(ERROR_INVALID_PARAMETER)로 리턴 되는 경우가 있어 주의가 필요합니다.

특히, Windows 의 Undocument API 를 후킹하고자 할 때는 함수 프로토타입을 알수가 없어서 정확한 함수 인자를 알기가 어렵습니다.
이러한 Undocument API 함수의 프로토타입은 http://undocumented.ntinternals.net/ 와 Windows NT/2000 NATIVE API Reference 책을 참조하는게 보통입니다.

하지만 오늘 위 두 곳에서 정의한 함수 프로토타입이 틀릴 수도 있으므로 주의가 필요함을 알았습니다.

1. 우선 아래와 같은 함수를 확인하실 수 있습니다.

NTAPI
NtCreateNamedPipeFile(OUT PHANDLE             NamedPipeFileHandle,
                                  IN ACCESS_MASK DesiredAccess,
                                  IN POBJECT_ATTRIBUTES   ObjectAttributes,
                                  OUT PIO_STATUS_BLOCK    IoStatusBlock,
                                  IN ULONG                ShareAccess,
                                  IN ULONG                CreateDisposition,
                                  IN ULONG                CreateOptions,
                                  IN BOOLEAN              WriteModeMessage,
                           IN BOOLEAN              ReadModeMessage,
                           IN BOOLEAN              NonBlocking,
                                  IN ULONG                MaxInstances,
                                  IN ULONG                InBufferSize,
                                  IN ULONG                OutBufferSize,
                                  IN PLARGE_INTEGER       DefaultTimeOut );
위 함수를 보시면 인자의 총 바이트 수는 47바이트(4Byte * 11 + 1Byte * 3) 입니다.
실제로 그런지 확인해 보겠습니다.

2. XP 에서 NtCreateNamedPipeFIle 의 SDT 의 인덱스는 2C 입니다.

0: kd> u ntdll!NtCreateNamedPipeFile
ntdll!NtCreateNamedPipeFile:
7c93d715 b82c000000      mov     eax,2Ch
7c93d71a ba0003fe7f      mov     edx,offset SharedUserData!SystemCallStub (7ffe0
300)
7c93d71f ff12            call    dword ptr [edx]
7c93d721 c23800          ret     38h

3. SDT(Service Descriptor Table)은 아래와 같이 4개의 멤버를 가지고 있습니다.
(1) System Service Dispatch Table Address
(2) CounterTable Address
(3) Service Limit
(4) System Service Parameter Table Address
이중 4번째 인자는 각 함수별 인자에 대한 정보가 있는 곳입니다.

4. 아래와 같이 SDT 에서 System Service Parameter Table Address 가 있는 위치를 확인합니다. 흠.. 0x80519fc4 네요

0: kd> dd nt!KeServiceDescriptorTable
80563500  804e68b0 00000000 0000011c 80519fc4
80563510  00000000 00000000 00000000 00000000
80563520  00000000 00000000 00000000 00000000
80563530  00000000 00000000 00000000 00000000
80563540  00000002 00002710 bf80c227 00000000
80563550  badefa80 f753d4a0 8a96b0f0 80711040
80563560  00000000 00000000 ffeced30 ffffffff
80563570  e0d48b70 01c883c9 00000000 00000000

5. 각 함수의 인자 값을 확인해면 아래와 같습니다.
0: kd> db 80519fc4
80519fc4  18 20 2c 2c 40 2c 40 44-0c 08 18 18 08 04 04 0c  . ,,@,@D……..
80519fd4  10 18 08 08 0c 04 08 08-04 04 0c 08 0c 04 04 20  ……………
80519fe4  08 10 0c 14 0c 2c 10 0c-0c 1c 20 10 38 10 14 20  …..,…. .8..
80519ff4  24 24 1c 14 10 20 10 34-14 08 0c 08 04 04 04 04  $$… .4……..
8051a004  0c 08 28 04 1c 18 08 18-0c 18 08 18 0c 08 0c 04  ..(………….
8051a014  10 00 0c 10 28 08 08 10-1c 04 08 0c 04 10 08 00  ….(………..
8051a024  08 04 08 0c 28 08 04 10-04 04 0c 0c 28 04 24 28  ….(…….(.$(
8051a034  30 0c 0c 0c 18 0c 0c 0c-0c 30 10 0c 10 0c 0c 0c  0……..0……
NtCreateNamedPipeFile 의 위치(인덱스)가 2C (십진수로 44) 인 곳을 확인해 보면 함수 인자의 총 합이 38 (십진수로 56) 임을 확인 할 수 있습니다.
즉, 함수 선언에 있는 것처럼 인자의 총 합이 47바이트(4Byte * 11 + 1Byte * 3) 가 아니라 56 바이트(통상 4Byte * 14) 이어야 합니다.
즉, BOOLEAN 형의 인자 세개가 잘못 된 것이며, 4Byte 타입으로 선언해야 합니다.

이러한 함수를 몇개 더 확인해 본 결과, 함수 선언중 BOOLEAN 인자로 되어 있는것은 전부 이렇게 인자 바이트 합이 일치하지 않았으며, BOOLEAN 타입을 4Byte 타입으로 바꾸어서(ULONG) 계산하면 일치함을 확인했습니다.
또한 이렇게 수정한 결과 후킹후 원본 함수 호출 시 ERROR_INVALID_PARAMETER 가 발생하지 않음을 확인했습니다.

위 내용에 대해서 2000 SP4, XP SP2 에 환경에서 확인해 보았습니다.

하이브리드 후킹

루트킷에 보면 하이브리드 후킹에 대한 설명이 나와있습니다.
간단히 말해서 DLL Injection 없이 커널모드 에서 유저모드의 IAT(Import Address Table)을 바로 후킹하는 것입니다.

이 후킹 방식의 주요 포인트는 PsSetLoadImageNotifyRoutine() 함수를 이용하여 바이너리 이미지가 메모리로 로드되는 시점을 알 수 있다는 것과 KUSER_SHARED_DATA 영역이라 불리는 커널모드와 유저모드에서 동일한 물리 메모리 주소를 가리키는 영역을 이용한다는 것입니다.

NOTE :  PsSetLoadImageNotifyRoutine 으로 등록한 콜백함수가 호출되는 시점이 이미지들이 로딩되는 프로세스와 같은 Process Context 인 점이 중요 합니다.

NOTE : 커널모드에서의 KUSER_SHARED_DATA : 0xFFDF0000 + 4K
           유저모드에서의 KUSER_SHARED_DATA : 0x7FFE0000 + 4K


하지만 책에 있는 샘플을 그대로 동작시켰을때 정상적으로 후킹되지 않음을 알 수 있습니다.
이는 PsSetLoadImageNotifyRoutine 콜백 함수로 특정 DLL 이 로드 되었다고 알려진 시점에 바로 후킹에 들어갔기 때문입니다. 즉, DLL 이 로드 되었다고 해도 해당 DLL의 익스포트 함수를 어플리케이션의 IAT(PE Format 에서 OriginalFirstThunk 가 아닌 FirstThunk)에 적용하기 전 시점일수(거의 대부분) 있기 때문입니다.

요지는 루트킷 책의 샘플을 이용하여 하이브리드 후킹을 했을때 후킹이 안된 경우는 위 문제 때문일 것이라는… ^^; (이미 아시는 내용이시라면 Pass ~~ )

디버깅한 내용들을 스크린 샷으로 찍어서 설명 드렸으면 더 좋았을 텐데.. 귀차니즘으로 ^^;
아래는 하이브리드 후킹에 대해서 그림으로 간단히 요약해 본 것입니다.
(그림에 대한 설명은 루트킷 책을 참고하세요)

그림을 클릭하시면 크게 보실 수 있습니다

KUSER_SHARED_DATA

그림을 클릭하시면 크게 보실 수 있습니다

하이브리드 후킹

역시 말로 설명하는 것보다 글이나 그림은 어렵군요. ^^;

※ 지나가는 소리 : 루트킷 책 원서와 번역본 모두 PsSetImageLoadNotifyRoutine 함수라고 나와있네요. 오타이며, 정확한 함수명은 PsSetLoadImageNotifyRoutine 입니다. ^^;

서비스가 정적 로딩되어 있으면 OpenService() 실패?

“정적 로딩으로 설정된 드라이버를 OpenService() 함수로 열었을때 정상적으로 열릴까?”


1. 드라이버를 정적로딩, 즉 Start Type을 SERVICE_SYSTEM_START(0x00000001)로 설정해 놓았습니다.

2. 부팅시에 드라이버가 로딩되었습니다.

3. 해당 드라이버(서비스)를 OpenService() 함수로 열었습니다. (왜 이런짓을 했어야 했는지 설명하기는 복잡해서 생략)

4. 저는 당연히 성공으로 리턴될 줄 알았는데 실패로 뜨는 군요
   GetLastError() 값 : ERROR_SERVICE_DOES_NOT_EXIT
   분명히 드라이버(서비스)는 로딩되어 있는데…

어쨌든 이러한 증상이 오히려 도움이 됐습니다만.. 혹시 이에 대해 설명해 주실 분 있으신지..


2008년 처음 포스팅인데 허접하네요… TT;

9X 에서 커널모드에서 프로세스 ID 얻기

9X 용 드라이버 개발해 본지가 호랑이 담배 필적이라…아주 잊고 있었는데..
(조금)아는 분이랑 메신저 대화하다가.. 9X 커널모드에서 타 프로세스 ID 얻는 방법이 갑자기 궁금해졌습니다.
분명 2X 계열과는 틀렸었는데(커널모드에서 얻은 프로세스 ID와 유저모드의 프로세스 ID가 매치되지 않는)… ‘뭐였지?’ 하면서 기억들 더듬 더듬…


1. 유저모드에서 얻은 자기 자신 프로세스 ID를 드라이버로 보냅니다.
2. 커널모드에서 1. 번에서 프로세스 ID를 보내온 프로세스(자기 자신) 핸들을 얻습니다.
    – VWIN32_GetCurrentProcessHandle() 함수를 이용해서 유저모드(Ring 3) 프로세스 핸들을 얻습니다.
3. 1번의 유저모드의 프로세스 ID와 2번의 커널모드 프로세스 핸들을 ‘^’ 연산을 해서 Obsfuscator를 얻습니다.
    – 예) ULONG AppProcessID : 유저모드 프로세스 ID
            PVOID ProcessHandle : 커널모드 프로세스 핸들
            ProcessHandle = VWIN32_GetCurrentProcessHandle();
            ULONG Obsfuscator = (ULONG)ProcessHandle ^ AppProcessID;
4. 이제 필터 드라이버(파일 시스템이든 TDI 레이어 이든)등에서 특정 연산을 호출하는 프로세스의 프로세스 ID는 해당 연산에서 얻어진 프로세스 핸들과 Obsfuscator를 ‘^’ 연산을 통해 얻어질 수 있습니다.
     – 예) PVOID CurrentProcessHandle = VWIN32_GetCurrentProcessHandle();
            ULONG CurrentProcessID = (ULONG)CurrentProcessHandle ^ Obsfuscator;

요새는 거의 9X 용 드라이버를 개발하는 경우가 없으실테니 필요가 없는 내용일 것입니다.
더군다나 맞는지도 모르겠고.. 확인해볼 플랫폼이나 테스트 해 볼 9X용 드라이버도 없으니..

지금 돌이켜보면 9X 용 드라이버 개발할때가 참 색다른 맛이 있었던것 같네요.
WDM 이다 WDF 다 하면서 점점 드라이버 개발 진입이 쉬워지기는 하지만.. 그래도 나름 그때가 묘한 매력이 있었던것 같습니다.

미니필터 드라이버에서 파일 풀패스명 얻기

미니필터 드라이버에서 I/O 연산시 파일 풀패스명을 얻는 함수를 작성하였습니다.
미니필터 드라이버에서는 파일명 및 파일 정보를 얻기 위해 필터 매니저에서 제공하는 FltGetFileNameInformation() 함수를 사용하여 얻을 수 있지만 이 함수가 실패하는 경우가 있습니다. 그것도 자주…(자세한 내용은 WDK 도움말을 참조하십시오)
 

위 함수는 Pre/Post Operation Callback 루틴에서 아래와 같이 사용하시면 됩니다.

※ 주의 : 코드는 실제 적용 코드에서 블로깅을 위해 수정된 것입니다. 사용하실때는 코드 검증및 테스트를 하시고 사용하시기 바랍니다.

미니필터 드라이버 기반 파일 모니터링 프로그램이 어느정도 완성되었습니다. 현재 테스트 중이고, 곧 업로드 하도록 하겠습니다.

[스크린샷]
사용자 삽입 이미지

드라이버 로딩 권한 설정

미니필터 드라이버가 XP에서는 로딩이 잘 되는데 비스타에서 테스트 해보니 FilterLoad() 함수가 0x80070522(ERROR_PRIVILEGE_NOT_HELD) 에러를 내며 로딩이 실패(물론 관리자 권한으로 실행해도) 하네요.
WDK 도움말을 잘 읽어보니..(역시 문서를 잘 읽어야 되는데.. 쩝) FilterLoad() 함수 호출자는 SeLoadDriverPrivilege (SE_LOAD_DRIVER_PRIVILEGE) 을 가져야 한다고 적혀있습니다.

그래서 프로그램이 필터드라이버 로딩전에 SeLoadDriverPrivilege 권한을 Enable 하도록 하는 함수를 만들었습니다.

이 함수를 FilterLoad() 함수전에 아래와 같이 SeLoadDriverPrivilege 를 인자로 해서 호출해 주면 됩니다.

※ 주의 : 코드는 실제 적용 코드에서 블로깅을 위해 수정된 것입니다. 사용하실때는 코드 검증및 테스트를 하시고 사용하시기 바랍니다.

Vista 에서 프로그램을 관리자 권한으로 실행하도록 하는 방법

개발된 프로그램이 관리자 권한이 필요한 것이라면 Vista 에서 실행시 에러가 뜨는 경우가 있습니다.
이때 해결할 수 있는 방법은 Vista 의 UAC 기능을 죽이거나  탐색기에서 해당 프로그램에 오른쪽 마우스 클릭후 ‘관리자 권한으로 실행’ 을 하는 것입니다.
이러한 것은 일반 사용자에게 불편을 줄 수 있으므로 처음부터 관리자 권한으로 실행되는 프로그램을 개발할 필요가 있습니다.


1. 관리자 권한을 요구하는 manifest 파일 만들기
 
예를 들어 Test.exe 라는 프로그램을 만든다고 할 때 이래와 같이 우선 Test.exe.manifest 라는 파일을 작성합니다.
참고로 name 에는 프로그램 이름을 적으시면 되고, description에는 프로그램 설명을 적으시면 됩니다.


2. 프로그램에 manifest 파일 추가 방법


  (1) 방법 1

Visaul Studio의 프로젝트 속성 -> 구성 속성 -> 매니페스트 도구 -> 입력 및 출력 에 보면 추가 매니페스트 파일 있는데 여기다가 manifest 파일이 있는 경로를 적어줍니다.


예) ../Bin/Test.exe.manifest

 이렇게 한 후 빌드를 하면 경고가 하나 뜨지만 빌드가 잘 되는 것을 확인 할 수 있습니다.


  (2) 방법 2

실행 파일 먼저 빌드되고 나중에 manifest 파일을 빌드해서 넣는 방법은 프로젝트 속성 -> 구성 속성 -> 빌드 이벤트 -> 빌두 후 이벤트에 보면 명령줄이 있는데 여기에 아래와 같이 작성하면 됩니다.


mt.exe -manifest Test.exe.manifest -outputresource:Test.exe;#1


3. 결론


빌드가 된 실행파일을 비스타에서 보면 아이콘 옆에 관리자 권한이 필요한 프로그램이라는 표시로 방패가 추가된 것을 보실수 있습니다. 위 두가지 방법 말고도 manifest 파일을 프로젝트에 추가시키는 방법이 하나가 더 있는데, 그에 대해서는 http://himskim.egloos.com/1524155 를 참조하십시오.

VMWare를 이용한 커널 디버깅

그동안 MS Virtual PC를 사용하다가 VMWare를 설치해 보았다. (이유는 없다.ㅋㅋ)
설치하고 나니 Virtual PC 보다 체감속도가 더 빠른듯한 느낌… 더군다나.. SoftICE가 잘 동작한다..(Virtual PC에서는 XP를 설치했을때 SoftICE가 깨졌었는데.. 혹시 방법 아시는 분 리플 부탁)

– VMWare에서 SoftIce 동작


VMWare 에서의 SoftICE 동작은 아래 사이트 참조 바람
http://www.vmware.com/support/kb/enduser/std_adp.php?p_faqid=965&p_created=1049834747&p_sid=l6hHm4Mg
물론 DriverStudio 3.1 이상에서는 Remote SoftICE를 제공하지만 텍스트 모드가 정감도 있고.. 그럴바에는 그냥 WinDbg 사용하는게..ㅋㅋ

VMWare – WinDbg를 이용한 커널 디버깅에 필요한 설정
http://addtech.woto.net/toto/doc/debugkernel.htm
위 사이트에서 관련 문서를 찾을 수 있으나.. 이 사이트가 운영을 중단한 상태라서(언제 문서가 없어질지 모르므로) 간략히 요약해 두어야 겠다. (Virtual PC에서와 거의 유사한 설정)

1. WinDbg 설정
– 바로가이 아이콘에 실행시 적용할 파라미터 설정
=>C:\WINDDK\windbg\windbg.exe -k com:port=\\.\pipe\xcom,pipe
즉, 통신은 파이프를 이용할 것이며, 포트명은 xcom으로 명명

2. Virtual PC
– Serial Port 추가
=> Type : Output to named pipe
=> Named pipe : \\.\pipe\xcom

3. Virtual PC 의 OS
– Boot.ini 수정 : 이는 일반적인 WinDbg 사용할때의 호스트 설정과 동일
=> multi(0)disk(0)rdisk(0)partition(1)\WINNT=”Microsoft Windows 2000 Debug” /fastdetect /debugport=COM1 /baudrate=115200

4. Virtual PC OS 를 Debug Mode 로 부팅 및 WinDbg 실행(파이프를 이용해햐 하므로 나중에 실행해야 한다. File->Kernel Debugger 의 COM Port 사용은 하지 않아도 됨)

5. 끝.. 즐거운 커널 디버깅 세계로…

SoftICE vs Daemon4.0

정말 간만에 SoftICE를 실행하게 되었다. 그동안 커널 디버깅 할 일도 없었는데.. 단지 태스크바의 아이콘을 클릭하다가 Start SoftICE 아이콘을 클릭하게 됐는데.. (Manual 로 시작하도록 설정되어 있었다)
이런.. 아래와 같은 에러창이 뜨는게 아닌가.. [Error code 31]


즉, SCM(Service Control Manager)이 SoftICE 드라이버 로딩에 실패했다는 얘긴데..
하루종일 원인을 찾다가… 결국 생각해 낸것이.. 최근에 내가 뭘 설치했었나? 그러다가 번뜩 생각난것이 Daemon 4.0 버전을 인스톨한 게 기억이 났다.(Need For Speed ‘Most Wanted’ 를 실행하기 위해서 4.0버전 이상이 필요)
그래서 Daemon을 지우고 재설치를 하는 동안 보게 된 메시지 (전에 설치할때는 이걸 왜 못봤을까나)


즉, Daemon 4.0 은 SPTD 드라이버를 설치해야되는데 커널 디버거와 호환되지 않는다는것…(커널 디버거가 실행되는지 검사하는 방법은 인터넷이나 관련 책을 뒤져 보면 많이 나옴)

어쨌든..레지스트리를 뒤져보니..


헉.. start 값이 0 으로 설정되어 있다… 그럼 이 이후에 커널 디버거를 로드하려면 에러가 뜨게 된 것…
이 값을 ‘4’ (Disable)로 바꾸고 재부팅 해보니.. Daemon 초기화 실패.. 그대신 SoftICE는 Manual 로 실행 정상….
결국… Daemon4 를 쓰면서 SoftICE를 사용하려면.. SPTD 드라이버를 로딩 안시키든가, SoftICE를 먼저 로딩시켜서 사용하는 방법 밖에는… (Daemon4를 언인스톨 해도 SPTD 드라이버는 언인스톨 되지 않음)

아무튼 오늘 이것때문에 삽질했다..

WDF (Windows Driver Foundation)

WDF(Windows Driver Foundation)는 차세대 WIndows 드라이버 모델로 WIndows 2000 이상 시스템에서 지원된다. 객체 지향적이고, 이벤트 방식이라는데… 뭐 결국 장애가 발생해도 OS 자체에 영향을 덜 주기위한 설계라는….

KMDF(Kernet Mode Driver Framwork)은 커널모드에서 돌아가는 드라이버를 말하고, UMDF(User Mode Driver Framework)은 말 그대로 사용자 모드에서 동작한다는데(Vista에서 제공 될 것이며, 이후 XP에서도 동작하도록 할 예정)…

아래 그림은 롱혼(이제는 비스타)에서 WDF 로 지원가능한 장치및 드라이버
(출처 : http://www.microsft.com/whdc)


% 참고 : SDV : Static Driver Verifier

결국 Antivirus filters 와 Ethernet Devices는 WDF를 하지 않는 다는 것은 FSD(File System Driver), NDIS(Network Driver Interface Specification)쪽은 WDF 를 적용하기에는 조금은 무리인듯…ㅋㅋ

IO Request Flow

I/O Flow To Kernel-Mode WDF Driver

I/O Flow To User-Mode WDF Driver