Skip to content
KitploitKITPLOIT
도구블로그
Log in
제출
도구블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

피드문의개인정보© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
CVE-2024-38063- — IPv6를 통한 원격 커널 익스플로잇 | Kitploit
도구/GitHubGitHub/adminpentester/cve-2024-38063-
Memory ForensicsVulnerability AnalysisExploitationReverse EngineeringLearning & EducationBinary Exploitation
GitHubadminpentester/cve-2024-38063-

CVE-2024-38063-

IPv6를 통한 원격 커널 익스플로잇

저장소 보기
21112년 전아직 검토되지 않음

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

CVE-2024-38063-

IPv6를 통한 원격 커널 익스플로잇

CVE-2024-38063 - IPv6를 통한 원격 커널 익스플로잇 마커스 허친스

8월 13일 최신 Windows 패치가 나온 이후로 저는 tcpip.sys(TCP/IP 패킷 처리를 담당하는 커널 드라이버)에 깊이 빠져 있었습니다. Windows 커널에서 가장 쉽게 접근할 수 있는 부분에 CVSS 점수 9.8의 취약점이 있다는 것은 그냥 지나칠 수 없는 일이었습니다. 저는 이전에 IPv6(또는 이를 파싱하는 드라이버)를 본 적이 거의 없었기 때문에 이 취약점을 리버스 엔지니어링하는 것이 매우 어려울 것이라는 것을 알았지만, 좋은 학습 경험이 될 것이라고 생각했습니다.

대부분의 경우 tcpip.sys는 문서화가 거의 되어 있지 않습니다. 이전 버그에 대한 몇 가지 익스플로잇 분석 글을 찾을 수 있었습니다: 여기, 여기, 그리고 여기, 그 외에는 거의 없었습니다. 영어 Google 검색 결과 상위에 중국어로 작성된 글이 나오면, 나자신이 깊은 물에 빠져 힘들어질 것이라는 것을 즉시 알지만, 배워야 합니다. Google 번역이 평범한 성과를 냈음에도 불구하고, 그 게시물은 IPv6 조각화가 어떻게 작동하는지에 대한 엄청나게 상세한 통찰력을 제공했고, 좋은 출발점을 주었습니다.

나중에 몇몇 함수 이름을 검색하다가 Axel Souchet(일명 0vercl0k)가 작성한 동일한 2021년 취약점에 대한 또 다른 분석을 발견했습니다. 이 분석은 tcpip.sys의 내부를 훨씬 더 깊이 파고들었고, 문서화되지 않은 여러 구조체를 정의할 수 있는 충분한 정보를 제공했습니다.

가장 쉬운 패치 분석

일반적으로 취약점에 해당하는 코드 변경을 찾기 위해 패치를 리버스 엔지니어링하는 것만으로도 며칠 또는 몇 주가 걸릴 수 있지만, 이 경우에는 즉시 찾을 수 있었습니다. 사실 너무 쉬워서, 여러 사람들이 소셜 미디어에서 제가 틀렸다며 버그가 다른 곳에 있다고 말했습니다. 제가 그들의 말을 듣고 하루를 낭비하며 잘못된 드라이버를 리버스 엔지니어링했을까요? 우리는 결코 알 수 없을 것입니다.

전체 드라이버 파일에서 정확히 하나의 변경 사항이 있었고, 그것이 결국 실제 버그였습니다.

패치 설치 전후 tcpip.sys의 바인디프 개요.

전체 드라이버에서 단 하나의 함수만 수정되었습니다. 보통 하루 종일 20개 이상의 함수 변경 사항을 살펴보면서 어느 것을 봐야 하는지 알아내야 하지만, 이번에는 그렇지 않았습니다.

패치 전 Ipv6pProcessOptions().

패치 후 Ipv6pProcessOptions().

변경된 함수가 하나뿐만 아니라 코드 한 줄만 변경되었습니다.

매우 긴 이름의 Feature_2660322619__private_IsEnabledDeviceUsage_3() 함수는 Microsoft가 부분적인 패치 롤백을 가능하게 하기 위해 가끔 추가하는 것입니다. 이 호출은 전역 플래그 또는 레지스트리 설정의 존재 여부를 확인하며, 설정되어 있으면 함수가 false를 반환하여 원래 코드가 패치된 버전 대신 실행되도록 합니다.

Microsoft가 이렇게 하는 이유는 보안 패치가 때때로 의도치 않게 문제를 일으키기 때문입니다. 따라서 이 설정을 통해 관리자는 단일 취약점을 패치 해제할 수 있으며, 전체 월별 패치 롤업을 제거하고 시스템 보안을 급격히 약화시키지 않아도 됩니다.

이를 고려하면, 이 패치가 하는 일은 IppSendErrorList() 호출을 IppSendError()로 대체하는 것임이 분명하며, 이는 문제가 어떤 종류의 리스트와 관련되어 있음을 알려줍니다. 가장 쉬운 패치 diff였습니다(또는 그렇게 생각했습니다).

취약점은 선택 사항, 익스플로잇은 필수

변경된 코드를 찾기 위해 패치를 리버스 엔지니어링하는 것은 도전의 절반에 불과합니다(또는 이 경우에는 0.1% 미만). 나머지 과정은 코드베이스의 충분한 부분을 리버스 엔지니어링하여 무슨 일이 일어나고 있는지 이해하고, 어떤 종류의 취약점이 패치되었는지, 대상 코드에 도달하기 위해 요청을 어떻게 구성해야 하는지, 그리고 어떤 상태가 익스플로잇 가능한 조건을 만드는지 알아내는 것입니다.

첫 번째 부분은 충분히 쉽습니다. 변경된 부분은 Ipv6pProcessOptions()에 있습니다. 이는 IPv6이며 옵션 처리와 관련되어 있음을 알려줍니다. 따라서 RFC를 빠르게 확인하면 IPv6 옵션이 무엇인지, 어디서 찾을 수 있는지 정확히 알 수 있습니다.

위키피디아에서 가져온 대상 옵션 헤더 레이아웃.

좋습니다. 우리가 찾고 있는 것은 기본 IPv6 헤더 바로 뒤에 위치하는 대상 옵션 헤더인 것으로 보입니다. Python 라이브러리 'scapy'를 사용하여 테스트 IPv6 패킷을 구성해 보겠습니다.

참고: 스푸핑된 IP 주소를 사용한 DDoS 공격을 완화하기 위해 Windows는 원시 IP 패킷을 구성하는 기능을 제한합니다. 이러한 이유로 저는 Linux를 사용하여 개념 증명을 개발하기로 결정했습니다. Linux는 사용자가 원시 레이어 2 및 레이어 3 패킷을 구성하고 보낼 수 있지만, Python 스크립트를 root로 실행해야 합니다.

import sys
import struct
from scapy.all import *


def send_ipv6_option_packet(dest_ip):
	ethernet_header = Ether()
	ip_header = IPv6(dst=dest_ip)
	options_header = IPv6ExtHdrDestOpt()
	sendp(ethernet_header / ip_header / options_header)
	
	
if len(sys.argv) < 2:
	print('Use: python3 script.py <target_ipv6_address>')
	exit(-1)

send_ipv6_option_packet(sys.argv[1])

tcpip!Ipv6pProcessOptions에 중단점을 설정한 후 스크립트를 실행하면, 취약한 함수에 도달하기 위해 필요한 것은 빈 옵션 구조체가 있는 IPv6 패킷을 보내는 것임이 분명해졌습니다. 그런 다음 구조체에 몇 가지 잘못된 옵션을 추가하여 IppSendErrorList() 호출에 도달할 수 있는지 확인했습니다.

간단한 코드 검토 결과, 거의 모든 잘못된 옵션 형식이 IppSendErrorList 호출을 트리거할 수 있음을 나타냈습니다. 그래서 유효하지 않은 길이(65535바이트 미만)의 점보 패킷 옵션을 사용하기로 결정했습니다.

options_header = IPv6ExtHdrDestOpt(options=[Jumbo(jumboplen=0x1337)])

그렇다면 IppSendErrorList()는 실제로 무엇을 할까요? 코드는 매우 간단합니다.

전체 IppSendErrorList 함수.

코드는 연결 리스트를 반복하며 리스트의 모든 항목에 대해 IppSendError()를 호출합니다. 다시 한 번, 별들이 정렬되어 지금까지는 모든 것이 쉬웠습니다. IppSendErrorList가 리스트의 각 항목에 대해 IppSendError를 호출하는 것뿐이고, 패치가 IppSendErrorList 호출을 IppSendError로 대체한다면, 문제는 첫 번째 항목이 아닌 다른 리스트 항목에 대해 IppSendError가 호출될 때 발생합니다.

그렇다면 이것은 무엇의 리스트이며, 어떻게 만들 수 있을까요?

그는 리스트를 만든다, 그는 확인한다...52,567번

이 지점에서 상황은 명확에서 비정상적으로 어려움으로 바뀌었습니다. 다만 저는 이 어려움의 큰 부분이 제 두뇌 세포 중 하나가 심한 코로나 감염과 싸우느라 바빴기 때문이라고 생각합니다. 저는 코드의 일부를 이해하고, 잠이 들고, 그런 다음 알아낸 것을 잊어버리는 데 며칠을 허비했습니다. 전체 과정은 tcpip.sys의 일부를 리버스 엔지니어링하여 무슨 일이 일어나고 있는지 알아내는 데 일주일 이상이 걸렸습니다. 하지만 Axel의 블로그 게시물이 매우 도움이 되었습니다.

Axel이 리버스 엔지니어링한 함수와 구조체, 그리고 그것들이 다른 함수에 전달되는 방식을 살펴보면, Ipv6pProcessOptions()에 전달되는 유일한 인자가 해당 기사에서 정의된 것과 동일한 packet_t 구조체임이 분명합니다. 본질적으로, Ipv6pProcessOptions에 전달되고 IppSendErrorList에 의해 반복되는 포인터는 패킷의 연결 리스트입니다.

그래서 Ipv6pProcessOptions()에 중단점을 설정하고 리스트를 검사했습니다.

list->Next 항목이 NULL입니다.

중단점이 적중될 때마다 리스트에는 패킷이 하나만 포함되어 있었습니다. 저는 리스트가 실제로 리스트가 되도록 하는 방법을 알아내는 데 인정하고 싶은 것보다 훨씬 더 많은 시간을 보냈습니다. 첫 번째 생각은 IPv6 조각화였습니다. IPv6는 발신자가 큰 패킷을 별도의 작은 패킷으로 분할할 수 있으며, 이를 리스트에 함께 유지하는 것이 합리적일 것입니다.

광범위한 리버스 엔지니어링 후에 제 가정이 옳았다는 것을 확인했지만, 조각 리스트는 우리가 여기서 다루고 있는 것과 관련이 없습니다.

사실 우연히 답을 찾았습니다. 가끔 리스트가 채워지곤 했지만, 그 이유는 불분명했습니다. 많은 맴돌이 끝에, 제 커널 중단점이 트리거될 때 커널 전체가 일시 중지되어 네트워크 어댑터가 패킷을 축적한다는 것을 깨달았습니다. 커널이 다시 시작되면 이 패킷들은 깔끔한 리스트로 스택 아래로 전달되어 tcpip.sys로 전달됩니다. 이는 커널이 일시 중지된 동안 패킷이 전송되었지만 다음 중단점이 적중되기 전에 처리되지 않은 경우에만 발생했습니다.

이 동작은 아마도 성능 최적화로, 낮은 처리량에서는 커널이 패킷을 개별적으로 처리하지만, 높은 볼륨에서는 패킷이 리스트로 구성되어 배치로 처리됩니다. 리스트는 아마도 프로토콜, 소스 주소 등과 같은 요소에 따라 분리되어 처리를 가속화하며, 따라서 우리의 리스트는 우리가 보낸 IPv6 패킷만 포함해야 합니다.

야, 너 DoS 좋아하지?

이제 높은 처리량 동안 패킷이 리스트로 결합된다는 것을 알았으므로, 가장 쉬운 옵션이 무엇인지 분명합니다. 아이러니하게도 우리의 DoS PoC는 DoS 조건을 트리거하기 위해 DoS를 사용해야 합니다. IPv6 패킷의 버스트로 시스템을 플러딩하면, 우리는 IppSendErrorList()에 전달되는 멋진 큰 리스트를 얻을 수 있을 것입니다.

처음에는 아무리 많은 패킷을 보내도 커널을 일시 중지하지 않으면 리스트를 n > 1로 만들 수 없었습니다. 하지만... 우리는 Python(고통스럽게 느림)을 VM(두 배로 고통스럽게 느림)에서 사용하고 있기 때문에, 아마도 몇 가지 설정을 조정해야 할 것입니다. 제 공격 시스템에서 발생하는 VM-ception 현상을 상쇄하기 위해 대상 VM을 단일 CPU 코어만 사용하도록 재구성하기로 결정했습니다.

좋습니다! 이제 패킷 리스트는 많은 항목을 포함하는 리스트입니다!

그러므로 VM 안의 VM은 DoS에 가장 좋은 옵션이 아니라는 것이 밝혀졌습니다. 누가 생각이나 했을까요? 하지만 결국 작동하게 만들었습니다. 이제 IppSendError()가 무엇을 하는지, 그리고 문제가 어느 부분에 있는지 알아내기만 하면 됩니다.

더 많은 리버싱... 다시... 영원히...

광범위한 리버스 엔지니어링 후에 IppSendError가 무엇을 하는지 훨씬 더 명확해졌습니다. 일반적인 상황에서는 net_buffer_list->Status를 0xC000021B(STATUS_DATA_NOT_ACCEPTED)로 설정하여 패킷을 비활성화합니다. 그런 다음 오류가 있는 패킷에 대한 정보를 포함하는 ICMP 오류를 발신자에게 다시 전송합니다.

IppSendError의 두 가지 관련 부분.

제 첫 번째 접근 방식은 tcpip.sys에서 net_buffer_list->Status 값을 무시하는 함수가 있는지 확인하는 것이었습니다. 이렇게 하면 드라이버가 정의되지 않았거나 예상치 못한 상태의 패킷을 처리하게 되어 익스플로잇 조건으로 이어질 수 있습니다.

패킷 처리를 담당하는 메인 루프.

모든 파싱 함수를 호출하는 루프가 오류 검사로 둘러싸여 있기 때문에(오류 코드가 설정되면 어디로도 갈 수 없음을 의미), 저는 이것이 잘못된 토끼굴이라고 판단했습니다. 대신 IppSendError로 돌아가서 오류 코드를 설정하기 전에 패킷 상태를 수정하는 코드 경로가 있는지 확인하여 경쟁 조건을 유발할 수 있는지 알아보기로 결정했습니다.

더 많은 리버스 엔지니어링 후에, IppSendError의 매우 아래쪽에서 다음 코드를 발견했습니다.

도구 다운로드