이 글은 SVRD의 MS08-066 : Catching and fixing a ProbeForRead / ProbeForWrite bypass를 번역한 것입니다.

afd.sys 드라이버는 소켓 연결을 처리하는 책임을 맡고 있습니다. MS08-066은 afd.sys에 존재하는 여러 개의 취약점으로 인해 커널 모드에서 임의의 코드가 실행될 수 있는 문제를 다루고 있습니다. 이 취약점들은 로컬에서만 공격 가능합니다.

이 취약점들 중 하나는 사용자 공간에서 넘어온 메모리 포인터와 길이가 ProbeForRead / ProbeForWrite 검증 함수 (역주: 링크된 MSDN을 보시면 알겠지만 이 함수들은 METHOD_NEITHER 방식으로 사용자 공간 메모리를 직접 참조하는 경우 사용됩니다. 두 함수는 사용자 공간에서 넘어온 포인터와 길이를 검증합니다. 포인터가 가리키는 메모리 주소가 사용자 공간이 아닌 커널 공간이거나, 주어진 길이가 지정한 값대로 정렬되어 있지 않으면 예외가 발생하도록 되어있습니다. 만약 이 함수를 우회할 수 있는 경우에는 사용자 공간에서 커널 주소를 넘길 수 있게 되므로, 목표하는 취약한 함수의 기능에 따라 커널 메모리를 조작하는 것이 가능해집니다. 불행히도 뒤에 언급된 것처럼 버퍼 길이를 0으로 쓴 경우 우회가 가능한데, 이 때문에 정수 오버플로우를 이용해서 우회하는 취약점이 있었고, 이를 다룬 것이 MS08-025입니다. milw0rm에 올라왔던 분석 보고서도 참고하시기 바랍니다.)를 우회하는 취약점입니다. 예전 포스트에서 이 함수를 우회하는 법과 드라이버 작성 시 이런 유형의 취약점을 방어하는 기법을 다룬 적이 있습니다. 그 때는 길이 값을 0으로 만든 다음 검증을 우회하는 기법을 다루었죠. 오늘은 드라이버가 넘겨받은 데이터의 범위를 제대로 검증하지 않아서 발생한 취약점을 다룹니다.

먼저 코드를 보시죠:

// 공격자는 OutputBuffer와OutputBufferLength 값을 제어할 수 있습니다.
void IOCTL_handler(...) {
[...]
        try {
            ProbeForWrite (OutputBuffer,
                                OutputBufferLength, //  [1] 길이가 0으로 넘어오면 커널 공간 주소도 통과할 수 있습니다.
                                sizeof (UCHAR));
            RtlCopyMemory(
                OutputBuffer,
                (PUCHAR)context+endpoint->Common.VcConnecting.RemoteSocketAddressOffset,
                endpoint->Common.VcConnecting.RemoteSocketAddressLength  // [2] 커널 공간 주소로 메모리를 복사하게 됩니다.
                );

        } except( AFD_EXCEPTION_FILTER(&status) ) {       
        }
[...]
}
드라이버가 막상 복사에 사용할 메모리 구간이 아닌, 다른 메모리 구간을 검증했기 때문에 취약점이 발생했습니다. 이 경우에 [1] 부분에서 공격자가 주소를 커널 공간 주소로 하고 길이를 0으로 해서 ProbeForWrite 검사를 우회하도록 하면, [2] 부분에서 드라이버가 커널 공간 주소와 endpoint->Common.VcConnecting.RemoteSocketAddressLength 를 이용하여 커널 공간 주소에 쓰기를 수행하는 상황이 발생합니다.

보안을 고려하는 개발자라면 OutputBufferLength를 아래와 같이 검증할 것입니다.

void IOCTL_handler(...) {
[...]
         if (OutputBufferLength!=endpoint->Common.VcConnecting.RemoteSocketAddressLength) {
             // 길이가 같지 않으므로 그냥 빠져나감.
             return;
         }
         try { 
            ProbeForWrite (OutputBuffer,
                                OutputBufferLength,
                                sizeof (UCHAR));
            RtlCopyMemory(
                OutputBuffer,
                (PUCHAR)context+endpoint->Common.VcConnecting.RemoteSocketAddressOffset,
                OutPutBufferLength 
                );
         } except( AFD_EXCEPTION_FILTER(&status) ) {
         }
[...]
}
이 블로그 포스트가 MS08-066을 이해하고 여러분의 코드를 안전하게 작성하는데 도움이 되었기를 바랍니다.