이번 키로거를 만들어보기 전에 저번에도 키로거를 하나 만든적이 있는데 그건 PS/2 키보드만을 대상으로 하며
IDT 후킹을 하여 HAL 함수인 READ_PORT_UCHAR() 함수를 사용해 직접 키보드 컨트롤러 칩의 버퍼로부터 값을 읽어들이는 형식으로 제작을 해보았었습니다.

하지만 해당 방법은 USB 키보드는 로깅을 하지 못합니다.
그래서 USB 키보드 키로거를 만들어보기 위해 돌아다니다가 츠피(chpie)님이 작성하신 문서인 
USB_Keyboard_Sniffer_with_TD 문서를 봐보았습니다.

해당 문서에 나와있는 방법으로는 
UHCI는 주기억장치의 Shared Memory를 통해 OS와 통신을 하는데
바로 Shared Memory를 거치는 UHCI 패킷을 가로채는 방법으로 설명이 되어있습니다.

물론 위 방법으로도 제작을 해볼 것이지만 현재 
파일 시스템 필터 드라이버 
USB 버스 필터 드라이버
를 프로젝트로 해야하는 상황이라 필터 드라이버 공부도 할 겸 
필터 드라이버 형식으로 키로거를 제작해 보았습니다.

필터 드라이버를 제작해보기 전에
필터 드라이버가 무엇인지를 간단하게 알아보겠습니다.
( IRP가 무엇인지..IRP 동작방식.. 이런것만 설명해도 엄청난 양이..!! =_= )

윈도우는 커널-응용프로그램, 커널-커널 등 I/O 통신을 위해서 IRP라는 거대한 구조체를 가지고 관리를 하게 됩니다.
I/O관리자가 생성한 IRP는 적절한 디바이스 스택으로 전달되게 되는데 어떠한 디바이스 스택이던 I/O관리자가 생성시킨 IRP는 디바이스 스택에서 최상위 디바이스 오브젝트가 받게 됩니다.

그림으로 표현해 보았습니다.

( 그냥 머리속에 있는 내용을 대충 그리다보니.. 그림이 쫌 틀렸을 수도 있습니다. 정확한 그림은 책을 참조하시기 바랍니다. )
설명해보면
I/O관리자에서는 IRP를 생성시킵니다. 그리고 특정 디바이스에게 IRP를 보내면 그에 맞는 IO_STACK_LOCATION 구조체 멤버인 MajorFunction 멤버를 참조해 그에 맞는 드라이버에서 등록한 Dispatch 루틴을 호출하게 됩니다.

간단한 예로 특정 파일을 생성 시킬 땐 IRP_MJ_CREATE가 발생하게 되고 읽을 땐 IRP_MJ_READ .. 등등 이런 IRP_MJ_XXX
가 IO_STACK_LOCATION 내부에 설정되게 됩니다.

그리고 드라이버에서는 
DriverObject->MajorFunction[IRP_MJ_READ] = ReadDispatch;
이런식으로 DriverEntry에서 초기화 해둔 함수로 넘어가게 됩니다.

위 그림에서 Device Stack을 보시면 4개의 디바이스가 쌓여 있음을 알 수 있습니다.
잘 보시면 FDO 위 아래로 uFiDO, lFiDO가 있는것을 보실 수 있는데 바로 저게 필터 드라이버가 생성한 디바이스 오브젝트를 해당 디바이스 스택에 끼워 넣은 것입니다.

만약 제가 저 디바이스 스택에 필터 디바이스?를 넣으려면 저 스택 특정 위치에 끼워넣으면 됩니다.

FDO위로 설치되는 필터는 Upper Fileter Device Object 이며 아래 설치되는 필터는 Lower Filter Device Object입니다.
각각의 역할은 다르지만 제가 여기서 사용할 필터 드라이버 형식은 Upper만 사용할 것입니다.
그리고 무조건 해당 디바이스 스택의 최상위에 넣을 것입니다.

제가 설명해놓고 어렵군요. 솔직히 전 IRP처리 방식에 대해서 100% 이해를 하려면 조금 더 시간이 필요할 것 같습니다.

코드 레벨로 들어가보겠습니다.

여기서도 3가지를 고려해서 키보드 필터 드라이버를 제작해야 합니다.
1. PS/2 키보드일 경우
2. USB 키보드일 경우
3. XP인가 Win7인가?

사람들이 많이 사용하는 환경에서 글로벌하게 동작을 하려면 위 3가지 정도는 생각을 해주어야 합니다.
DeviceTree툴로 각 환경에서 어떻게 디바이스 스택이 구성되어 있는지 확인해 보겠습니다.

위 환경은 Windows 7에서의 환경입니다. USB 키보드를 안 꽂았을 때를 보면 HidUsb드라이버가 생성한 디바이스 스택을 보면 마우스 밖에 없고... 그리고 kbdclass 드라이버를 보면 그 아래 "\Device\KeyboardClass0" 디바이스만 있습니다.
해당 디바이스 스택에 필터를 설치하면 PS/2 키보드만 로깅이 가능하게 됩니다.

오른쪽 그림은 USB 키보드를 꽂았을 때 입니다. 빨간색으로 네모친 부분이 바로 새로 생긴 부분입니다.
이제 어떠한 디바이스를 공략해야 할까요??

일단 여러가지 테스트 해본결과입니다.
1. PS/2 키보드가 꼽혀있을 경우 KeyboardClass0, KeyboardClass1 둘다 필터해도 작동
2. USB 키보드가 꼽혀있을 경우 KeyboardClass0에 필터가 설치되어 있으면 키보드 작동 않함

두 가지를 만족시키기위해 공략해야하는 드라이버는 "\Driver\kbdclass" 드라이버 입니다.
해당 드라이버가 "\Deivce\KeyboardClass0", "\Device\KeyboardClass1" 둘다 생성시키기 때문입니다.
특히 "\Device\KeyboardClass1" 디바이스 경우 빨간색 네모친 디바이스 스택에 전부 포함되어 있습니다.

완성된 Hooking 코드를 보시겠습니다.
주석으로 설명을 달아놨으니 추가적인 설명은 하지 않겠습니다.

위 코드를 가지고 필터를 설치 한 후의 디바이스 스택을 봐보겠습니다.

제가 원한 곳에 전부 필터가 설치 됬으며 KeyboardClass0 디바이스가 있는 디바이스 스택엔 필터 설치가 않됬습니다.

이제 필터는 완벽하게 설치가 되었고 왔다갔다 하는 키 값을 가져오려면
IRP_MJ_READ Dispatch 루틴에서는 
현재 IRP를 하위 디바이스에게 복사를 하고 완료 루틴을 설정합니다.

완료 루틴을 설정해야 하는 이유는 키 값을 가지고 올라오는 IRP를 잡아내야하기 때문입니다.
그리고 완료 루틴안에서는 해당 키 값을 디스크에 저장시키면 끝입니다.

IRP_MJ_READ (Kbdclass) 에 대해 자세하게 나와있는 곳입니다. ( msdn )

일단 디스크 저장 기능 구현을 제외한 ( DbgPrint로 해당 키 값에 맞는 문자 출력 까지만.. ) 
필터 드라이버로 구현한 키로거 테스트 영상을 보시겠습니다.

보다 긴장된 마음을 갖기 위해 리얼 머신에서 테스트 했습니다.
(Ctrl + 마우스 휠 을 가지고 영상 크기 조절해서 보세요. )

잘 작동하는군요. :-) 이제 디스크에 파일 저장시키는것 까지 구현해서 마무리 작업을 들어가야겠습니다.
키보드 필터 드라이버는 .. 개인 재미삼아..ㅠ_ㅠ 다른 프로젝트가 있는데 그걸 못하고 있군요..;;

Posted by Ezbeat