본문 바로가기

My Study/Programming&Theory

Hardware Manipulation ( Keyboard LED )

 이번 글에선 직접 하드웨어에 접근하는 코드를 작성해 Keyboard LED를 조작하는 방법에 대해서 알아보겠습니다.

먼저 대부분의 하드웨어는 컨트롤러 칩을 가지고 있습니다. 
이 글에서 필요한 키보드 또한 키보드 컨트롤러 칩을 가지고 있습니다.
그리고 컨트롤러 칩은 메모리 주소 지정이 가능한 Port를 가지고 있는데 이 포트 값은 컨트롤러 칩마다 다릅니다.

장치 관리자에 들어가서 보면 각 하드웨어 대한 정보들을 쭉 볼 수 있습니다.
전 키보드에 대해서만 봐보도록 하겠습니다.


위 그림과 같이 제 노트북은 표준 PS/2 키보드를 사용하고 있으며 사용 포트는 0x60, 0x64 포트를 하고 있습니다.
키보드에서 위에 HID 키보드 장치는 USB 키보드를 뜻합니다.

그리고 코드는 루트킷 책을 보며 이해했지만 좀 더 많은 자료를 원해서 키보드 컨트롤러에 대한 자료를 찾아보았습니다.
아주 좋은 자료가 있더군요.
간략하게 설명해보면
키보드 컨트롤러는 세개의 8bit 레지스터를 가지고 있다.
Input 버퍼에 값을 쓰려면 0x60 또는 0x64 포트에 값을 쓰면 된다.
Output 버퍼에서 값을 읽으려면 0x60포트를 읽으면 된다.
상태 레지스터는 0x64포트를 읽으면 된다.

만약 0x64 포트에 쓰기를 했다면 그 바이트는 명령 바이트로 해석된다.
만약 0x60 포트에 쓰기를 했다면 그 바이트는 데이터 바이트로 해석된다.
대충 이렇습니다.

그리고 각 레지스터에 있는 값이 무엇이냐에 따른 설명, 그리고 명령 데이터에 대한 설명도 쭉 있으니
읽어보면 재미있을 것 입니다.
또한 윈도우에서는 커널 레벨에서 동작하는 드라이버만 IO Port, 메모리, 인터럽트 사용이 자유롭기 때문에
유저 레벨에서 동작하는 어플에서는 이와 같은 내용을 구현할 수 없습니다.

먼저 DirverEntry 함수를 보시겠습니다.
Keyboard LED를 수정하는 함수는 콜백 루틴으로 구현했습니다.
KeSetTimerEx함수를 사용해 0.5초 마다 CustomDpc루틴을 호출하도록 했습니다.
CustomDpc루틴은 타이머가 셋팅될 때 DPC 큐에 들어가게 됩니다.
그렇기 때문에 IRQL이 DISPATCH_LEVEL에서 동작하기 때문에 CustomDpc루틴 내부에서는 스케쥴링이 일어날만한
코드를 작성하면 안됩니다. 왠만해선 DbgPrint 함수도 사용하지 않는 것이 좋습니다.

MyDpc 모듈입니다.

CheckKeyboardBuffer에서는 현재 Input 버퍼가 비었는지 안비었는지를 체크한 후 
비었다면 LED 변수에 지정된 값으로 Keyboard LED를 켜는 것 입니다.

여기서 사용된 함수를 보면 WRITE_PORT_UCHAR라는 함수가 있는데 
해당 함수는 지정된 포트에 특정 값을 쓰는 것 입니다.

그러면 그냥 _inp, _outp 같은 명령어를 쓰던지 아니면
in, out 어셈 명령어를 써도 되는데 왜 위 함수를 써야하냐만 
윈도우는 다양한 하드웨어 플랫폼과 다양한 CPU를 지원하려는 모습을 갖추고 있습니다.
그렇기 때문에 운영체제가 제공하는 READ_PORT_XXXX(), WRITE_PORT_XXXX() 함수를 사용하면 
나중에 플랫폼이 바뀌더라도 코드를 바꿀 필요가 없습니다.

쫌 더 자세히 설명해보면 윈도우는 플랫폼의 변화에 따른 커널 코드의 변화를 최소화 시키기 위해 HAL계층이라는 것을 두었습니다. HALHardware Abstraction Layer 의 약자입니다. 즉, 하드웨어를 추상화시키는 역할을 담당하고 있죠.

위 그림과 같이 이해하시면 됩니다.

결론은 플랫폼의 영향을 받지 않기 위해 HAL함수를 써준다고 생각하시면 됩니다.
위에서 설명한 READ_PORT.... WRITE_PORT.... 함수들이 HAL함수입니다.

다시 코드로 돌아와 0x60포트에 0xED 값을 써주는 것은 LED 변경하겠다는 명령이고
그 아래는 어떠한 LED를 켤것인가에 대한 코드입니다.

8 비트 중에 하위 3비트가 LED 내용이 속합니다.
0번째 비트 : Scroll Lock
1번째 비트 : Num Lock
2번째 비트 : Caps Lock
이렇게 됩니다.
코드를 보면 LED 변수는 0~7까지 변하고 있는데 이렇게 되면 세개의 LED가 번갈아 가면서 깜빡이게 될 것입니다.

마지막으로 CheckKeyboardBuffer 모듈을 보겠습니다.
여기선 키보드 컨트롤러 내부에 있는 상태 레지스터에서 값을 읽어오는 것입니다.
위 링크로 걸어두었던 사이트에서 그 내용을 봐보겠습니다.

1번째 비트 값을 확인하면 되는 것입니다.
그 값이 0이면 비어있다는 것이므로 그 때 WRITE_PORT를 해주면 됩니다.
여기선 비어있으면 TRUE를 리턴하게끔 했습니다.

그리고 중간에 KeStallExecutionProcessor함수를 써줬는데 해당 함수는 인자로 들어간 시간만큼 현재 프로세서를 지연시키는 함수입니다. 단위는 마이크로 초 입니다. 위 함수를 쓰는 이유는 명령을 수행할 시간을 주기 위해서 입니다.
루트킷 책에 나와있는 설명으로는 이렇게 지연시켜주지 않고 바로바로 명령을 실행해버리면 매 다섯 번째 바이트만 실제적으로 쓰이게 된다고 나와있습니다.

언로드 루틴에서는 필히! KeCancelTimer함수를 사용해 타이머를 없애주어야 합니다.
만약 없애지 않을 경우 이러한 사태가 벌어질 수 있습니다.
1. 타이머가 취소되지 않은 상태에서 드라이버가 메모리에서 제거
2. 타이머 발동
3. 없는 쓰레기 코드를 실행하게 되므로 뻑..!
꼭 타이머를 없애줍시다.
또한 ExAllocatePool함수로 인해 메모리 할당한 부분도 ExFreePool함수로 해제 시켜주어야 합니다.

이제 모든 코드 설명은 끝났으므로 해당 드라이버를 커널에 로드시키면 어떻게 작동하는지 보여드리겠습니다.
집에 안좋은 디카가 하나 있길래 찍어봤습니다. ( 디카 사고 싶어요 ..ㅠ ㅠ )

리얼에서 하던 VM에서 하던 어짜피 같은 하드웨어를 사용하므로 VM에서 돌렸습니다.
드라이버를 커널에 로드시켜보니 LED가 깜빡 깜빡 거리는 것을 확인 할수 있습니다.

그리고 문서를 잘 보면 재미있는 부분이 있습니다.

0x64 포트에 값을 쓰는건 명령 바이트라고 했습니다. 그 때 0xfe를 전달하면 시스템이 리붓된다는 것입니다.
키보드 컨트롤러에 명령을 보냈는데 시스템이 리붓된다????

키보드 컨트롤러와 RESET 핀이 직접적으로 연결되어 있나 봅니다. 
나중에 학교가면 데탑을 가지고 직접 봐봐야겠습니다. 제 노트북은 소중하니까요....

사용 방법은 이렇습니다.
WRITE_PORT_UCHAR((PUCHAR)0x64,0xFE);
이렇게만 해주면 됩니다.

그리고 눈 딱 감고 리얼 머신에서 테스트 해본결과... 정상적인 리붓이 아닌 강제 리붓입니다. -_-;;;;;;;;;;;;;;;
사용 용도는 알아서 생각해보세요~

감사합니다 ^^;