
CVE-2024-38063(tcpip.sys의 RCE)에 대한 PoC
pip3 install scapy
스크립트의 다음 필드를 수정하세요:
iface <- 어댑터가 여러 개인 경우 패킷을 보내는 데 사용할 어댑터를 선택해야 합니다. 예: Linux의 "eth0" 또는 Windows의 "Hyper-V Virtual Ethernet Adapter". 기본 인터페이스를 사용하려면 비워 두세요.ip_addr <- 대상 시스템의 IP 주소 (IPv6)num_tries & num_batches <- 보낼 서로 다른 패킷 배치의 수. 더 많을수록 더 많은 힙 손상이 발생하고 취약점이 트리거될 확률이 높아집니다.mac_addr <- scapy가 MAC 주소를 찾을 수 없다고 불평하지 않는 한 비워 두세요. 아래 문제 해결 섹션을 참조하세요.스크립트를 실행하세요:
python3 cve-2024-38063.py
취약점을 재현하는 가장 쉬운 방법은 대상 시스템에서 bcdedit /set debug on을 사용하고 머신/VM을 재시작하는 것입니다. 이렇게 하면 기본 네트워크 어댑터 드라이버가 kdnic.sys가 되며, 이 드라이버는 패킷 병합을 매우 잘 수행합니다. 다른 설정에서 취약점을 재현하려면 시스템이 보낸 패킷을 병합할 수 있는 상태가 되도록 해야 합니다. 자세한 내용은 아래의 문제 해결 섹션을 읽어보세요.
기술적 세부 사항에 관심이 있다면 Marcus가 작성한 이 훌륭한 취약점 분석을 읽어보세요. 아래에 작성한 내용은 엄밀한 기술 분석이라기보다는 요약을 위한 것입니다.
NET_BUFFER 객체가 들어 있습니다. 오프셋 0x30에는 패킷이 얼마나 파싱되었는지를 나타내는 현재-오프셋 필드도 있습니다. 이 단계에서 오프셋 값은 일반적으로 0x28이며, IPv6 헤더가 파싱되었지만 그 외에는 아무것도 파싱되지 않았음을 나타냅니다.tcpip!Ipv6pReceiveDestinationOptions에서 "destination options" 확장 헤더를 처리할 때 파싱 오류가 발생하면 tcpip!IppSendErrorList가 호출됩니다. 이 함수는 연결 리스트의 각 패킷 객체에 대해 (현재 객체부터 시작하여) tcpip!IppSendError를 호출합니다.tcpip!IppSendError는 부작용이 있습니다. 버퍼링된 패킷 데이터를 시작 지점으로 "되돌리고" 현재-오프셋 필드를 0으로 재설정합니다.0x8C). 이는 드라이버가 IppSendError에서 "되돌려진" 다른 패킷들의 확장 헤더를 계속 파싱한다는 것을 의미합니다.0x28이 아니라 0입니다.Ipv6pReceiveFragment를 사용합니다. 이 함수는 프래그먼트 확장 헤더를 파싱하며, 현재 오프셋 값에서 0x30을 빼서 패킷의 비-헤더 데이터 길이를 계산할 때 패킷의 오프셋 필드가 최소 0x28이라고 가정합니다. 그런 다음 이 값은 단편화된 패킷을 재조립하는 역할을 하는 재조립 객체에 저장됩니다.IppSendError에 의해 되돌려진 패킷에 대해 호출됩니다. 오프셋 값은 0이며 Ipv6pReceiveFragment의 앞부분 어딘가에서 8로 증가합니다. 비-헤더 데이터 크기를 계산할 때 값이 언더플로우하여 0xffd8이 됩니다(뺄셈은 16비트로 수행됩니다).Ipv6pReassembleDatagram에서는 재조립된 패킷의 출력 버퍼 길이를 계산하는 데 사용됩니다. 그러나 모든 계산은 32비트로 수행되며 총 길이가 0xFFFF를 초과하지 않는지 확인하는 검사가 있습니다. 이 경우에는 검사를 통과합니다.Ipv6pReassemblyTimeout에서도 동일한 방식으로 사용됩니다. 그러나 여기서 계산은 16비트로 수행되며 정수 오버플로가 발생합니다. 이로 인해 나중에 데이터를 버퍼로 복사할 때 버퍼 오버플로가 발생합니다.Ipv6pReassemblyTimeout을 트리거하려면 프래그먼트의 송신자가 1분 동안 비활성 상태여야 합니다. 따라서 우리의 전략은 다음과 같습니다.
IppSendError를 트리거하기 위해 잘못된 형태의 destination options를 보낸 다음, 프래그먼트 패킷을 보냅니다.Ipv6pReceiveFragment에서 언더플로우를 발생시키고, 높은 16비트 값을 갖는 프래그먼트 데이터 길이로 새 재조립 객체를 만듭니다.Ipv6pReassemblyTimeout이 트리거되게 합니다.Ipv6pReassemblyTimeout의 버퍼 크기 계산에서 정수 오버플로를 발생시켜 힙 기반 버퍼 오버플로를 트리거합니다.스크립트의 패킷은 병합될 확률을 높이기 위해 대량으로 전송됩니다. 주요 페이로드는 매우 간단합니다.
또한 IPv6 헤더의 hop limit 및 flow label 필드를 수동으로 설정합니다. 취약점 때문에 버퍼링된 패킷 데이터가 재설정된다는 점을 기억하세요. 즉, 프래그먼트 패킷을 처리할 때 IPv6 헤더가 프래그먼트 헤더 데이터로 해석됩니다. IPv6 헤더의 hop limit 필드는 프래그먼트 헤더의 id 필드의 비트 중 하나로 해석됩니다. 이를 변경함으로써 여러 다른 프래그먼트에 대해 취약점을 트리거하고 여러 다른 손상을 일으켜 충돌 확률을 높입니다(어차피 PoC이므로). IP 헤더의 flow label 필드는 프래그먼트 헤더의 오프셋 및 "more indicators" 필드로 해석됩니다. 이 값을 1로 설정하면 이후에 더 많은 헤더가 온다는 것(따라서 나중에 Ipv6pReassemblyTimeout을 트리거할 수 있음)과 오프셋이 0임을 나타냅니다(이 id를 가진 첫 번째 패킷이 도착했기 때문).
Ipv6pReassemblyTimeout에서 사용하는 코드 경로가 원래 프래그먼트 패킷이 유니캐스트로 전송되어야 하기 때문에 유니캐스트를 사용합니다.작동하지 않는 경우 원인은 다음과 같을 수 있습니다.
Ether(raw(sr1(IPv6(dst={your_dest_ip})/ICMPv6EchoRequest()))).src, 하지만 이 방법은 때때로 작동하지 않습니다tcpip!Ipv6pReceiveDestinationOptions -> tcpip!Ipv6pProcessOptions -> tcpip!IppSendErrorList가 호출되는가?tcpip!Ipv6pProcessOptions에 중단점을 설정하고 [rcx]가 항상 0인지 확인하세요. 그렇다면 어떤 이유로 패킷이 병합되지 않는 것입니다.tcpip!Ipv6pReceiveFragment에 중단점을 설정하고 [rcx+0x30]이 0인지 확인하세요. 그렇지 않다면 어떤 이유로 취약점 트리거에 실패한 것입니다.