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

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

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

··피드·문의·개인정보·© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
vsock_poc — CVE-2021-26708 뒤에 숨은 버그 조사 | Kitploit
도구/GitHubGitHub/jordan9001/vsock_poc
Vulnerability AnalysisExploitationDebuggersPapers & ResearchLearning & EducationBinary Exploitation
GitHubjordan9001/vsock_poc

vsock_poc

CVE-2021-26708 뒤에 숨은 버그 조사

저장소 보기
28225년 전Kitploit 검토 완료

인기

모두 보기 →

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

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

vsock_poc

CVE-2021-26708 뒤에 숨은 버그 조사


이 저장소는 CVE-2021-26708에 대한 간단한 분석과 이 버그를 Use After Free 쓰기 프리미티브로 전환하는 방법을 담고 있습니다. 여기 있는 PoC는 완전한 익스플로잇이 아니라, 제가 이 버그를 조사할 때 사용한 테스트 환경입니다. kmalloc-64 캐시에서 해제된 엔트리를 성공적으로 사용할 수 있지만, 메모리를 정리하고 슬롯에 흥미로운 무언가를 배치하는 코드는 없습니다.

@a13xp0p0v가 보고한 재미있는 버그입니다. 패치가 너무 단순해서 제 눈길을 끌었습니다. 단순히 vsk->transport에 대한 참조를 락 밖에서 얻지 못하도록 5군데에서 막는 것이 전부였습니다. https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=c518adafa39f37858697ac9309c6cf1805581446

아래는 패치에서 use-after-free 프리미티브로 가는 과정에 대한 간단한 설명입니다. 이 버그를 탐구하려는 다른 사람들에게 도움이 되길 바랍니다.

환경 설정

리눅스 커널 5.10.13을 다운로드하고, 위에 표시된 패치를 수동으로 되돌렸습니다. 커널 빌드 및 실행에 대한 자세한 내용은 다음을 참조하세요.

https://fedoraproject.org/wiki/Building_a_custom_kernel

또한 kgdb로 커널 디버깅을 활성화하기 위해 부트 파라미터를 수정했습니다. 이전에 빌드한 vmlinux 파일과 함께 gdb를 사용하여 메인 커널의 모든 커널 심볼을 확보했지만, 로드 가능한 커널 모듈의 심볼은 확보하지 못했습니다. 취약점과 관련된 코드는 기본적으로 로드되지 않지만, PF_VSOCK 패밀리가 사용될 때 (커널 빌드 방식에 따라) 커널에 로드됩니다.

로드된 모듈의 심볼을 kgdb에서 얻기 위해, 먼저 vsock 소켓을 한 번 사용한 후, sudo cat /proc/modules | grep vsock 명령어로 관련 모듈의 베이스 주소를 확인했습니다. 그런 다음 gdb에서 (gdb) add-symbol-file ./net/vmw_vsock/vsock.ko 0xffffffffc0567000와 같이 실행하여 gdb가 해당 ko 파일의 심볼이 메모리의 어디에 있는지 알게 했습니다. 와 가 가장 관련이 깊었습니다.

vsock.ko
vmw_vsock_virtio_transport_common.ko

프리미티브 찾기

패치로부터 거꾸로 작업하는 것은 재미있습니다. 대부분의 취약점 사냥과 달리, 이미 올바른 위치를 보고 있다는 것을 확실히 알 수 있기 때문입니다. 이 경우 패치를 통해 transport에 대한 참조가 sock_lock을 획득하기 전에 저장된다는 것을 알 수 있습니다. transport가 변경되었지만 이전 참조가 사용되는 것이 취약점의 원인이라고 예상할 수 있습니다.

이러한 시나리오에서는 transport 자체가 동적으로 할당된 객체여서, 참조를 획득한 후 락을 획득하기 전에 해제되고 다른 객체로 교체될 수 있기를 기대합니다. 하지만 관련 모듈이 구현한 transport의 수명 주기를 추적해보면, 모두 전역 메모리에 있는 것으로 보입니다. 따라서 범위를 벗어난 항목을 찾기 위해 한 단계 더 깊이 살펴봐야 합니다.

해제 pt. 1

af_vsock.c에서 vsk->transport가 수정되는 두 곳을 찾을 수 있습니다. vsock_assign_transport와 vsock_deassign_transport입니다. vsock_assign_transport에서 기존 transport가 다르면 새 transport를 할당하기 전에 vsock_deassign_transport가 호출되는 것을 볼 수 있습니다. vsk->transport->destruct(vsk) 호출의 가능성을 여기에서 살펴보면, 루프백과 virtio transport 모두 vsk->trans 파라미터를 kfree하는 것을 여기에서 확인할 수 있습니다. 딩동! (1) 이 호출로 가는 경로와 (2) 소멸되기 전의 transport 참조를 사용하여 vsk->trans에 접근하는 취약한 함수가 경합할 수 있다면, 우리의 프리미티브를 얻을 수 있습니다.

vsock_deassign_transport로 가는 경로를 찾아보면, vsock_sk_destruct 또는 vsock_assign_transport에서 호출되는 것을 볼 수 있습니다. vsock_sk_destruct는 sock->destruct 함수로 설정되므로, __sys_close 호출이나 소멸 경로에서 사용 가능한 다른 호출(예: sock_put, sock_close, vsock_release)을 통해 여기에 도달할 수 있습니다.

vsock_assign_transport로 가는 가장 관련성 높은 경로는 vsock_stream_connect를 통하는 것이지만, 소켓이 특정 몇 가지 상태에 있어야 하며, vsock_deassign_transport가 호출되는 경우는 transport가 변경될 때뿐입니다. 그리고 새 transport가 NULL이 아니라면 vsk->trans 파라미터를 교체합니다.

사용

해제 경로 중 어떤 것이 우리에게 적합한지 너무 깊이 파고들기 전에, vsk->trans 멤버를 소멸된 transport에 대한 유효하지 않은 참조로 사용하는 유효한 경로가 있는지 확인해야 합니다. transport가 유효하지 않은 참조로 사용될 수 있는 모든 지점을 체계적으로 확인할 수 있습니다. 이러한 구멍을 추적하면 vsk->trans가 사용되는 지점을 찾을 수 있습니다. 가장 좋은 경로는 vsock_stream_setsockopt 여기를 통하는 것으로 보이며, transport->notify_buffer_size가 루프백 및 virtio transport의 vsk->trans 내부 오프셋에 쓰기를 수행합니다 바로 여기. trans가 이미 해제된 상태에서 해당 위치에서 사용되면, kmalloc-64 할당에서 오프셋 0x28에 u32 값을 쓰게 됩니다.

경합

프리미티브로 vsock_stream_setsockopt를 사용하는 것은 transport에 대한 참조를 얻는 시점과 sock_lock을 얻는 시점 사이의 경합에 달려 있습니다. 이 윈도우는 작고, 거기까지 도달하는 명령어도 많습니다. 그래서 여기서는 리눅스의 멋진 기능인 userfaultfd를 사용하여 우리의 기회를 높일 수 있습니다. 이 메커니즘을 통해 페이지 폴트를 사용자 모드에서 원하는 대로 처리할 수 있습니다. 자세한 내용은 https://man7.org/linux/man-pages/man2/ioctl_userfaultfd.2.html 및 https://man7.org/linux/man-pages/man2/userfaultfd.2.html을 참조하세요.

이를 통해 한 스레드(게이터)가 sock_lock을 획득한 다음 사용자 메모리에 접근하여 페이지 폴트를 발생시키도록 할 수 있습니다. 해당 스레드를 (락을 계속 쥐고 있는 상태로) 원하는 시간 동안 일시 중지시킬 수 있습니다. 해당 락을 획득하려는 다른 스레드들은 게이터가 해제되어 락을 놓을 때까지 대기하게 됩니다. 소멸을 수행할 스레드와 유효하지 않은 참조를 사용할 스레드를 줄 세울 수 있습니다. 둘 다 sock_lock을 기다리게 되며, 이제 경합에서 이길 좋은 기회를 갖게 됩니다. 소멸을 수행하는 스레드가 다음으로 락을 획득하도록 선택되면, setsockopt 호출은 그 후에 완료됩니다. 해제(및 교체)된 후의 vsk->trans 포인터를 사용하게 됩니다.

대신 setsockopt 호출이 먼저 실행되면 경합에서 지게 되지만, 전체 과정을 안전하게 다시 시도할 수 있습니다.

해제 pt. 2

이것을 구축하려고 할 때 한동안 잘못된 길로 갔습니다. close와 타임아웃을 통해 vsock_deassign_transport를 호출하려 했지만, 많은 참조 카운트 검사로 인해 실제 해제가 너무 늦게 이루어졌습니다.

덧붙여, 이러한 경로를 디버깅하는 것은 어려울 수 있습니다. 시스템 콜 close에 중단점을 걸면 매우 자주 트리거될 것입니다. 조건부 중단점을 사용하여 적절한 스레드에서만 멈추도록 해도 시스템이 매우 느려집니다. 이에 대한 흥미로운 우회 방법은 조건이 맞을 때만 bpf_trace_printk를 호출하는 tracepoint와 함께 ebpf를 사용하는 것입니다. 그런 다음 kgdb 중단점을 bpf_trace_printk에 설정하면 올바른 지점 근처에 도달할 수 있습니다. kprobes에서는 이미 중단점 핸들러 안에 있기 때문에 이 방법이 작동하지 않습니다. ebpf에 bpf_trace_kgdb_break 호출을 추가하는 것이 커널에 좋은 추가 기능이 될 수 있다고 생각합니다.

마침내 vsock_assign_transport 경로로 전환했을 때, 빠르게 해결되었습니다. 요구 사항을 충족하기 위해, 먼저 수신 대기 서버가 없는 상태에서 VM_ADDR_CID_LOCAL에 연결합니다. 그러면 루프백 transport를 얻지만, 연결이 타임아웃되거나 실패하면 상태가 SS_UNCONNECTED로 돌아갑니다. 이렇게 하면 VM_ADDR_CID_HOST보다 큰 주소로 다시 연결할 수 있으며, transport가 변경되어 기존 transport를 소멸시키고 해제를 유발합니다. 중요한 것은 실제로 등록된 transport_g2h 또는 transport_h2g가 없어야 하므로, 새 transport는 NULL이 되고 해제된 참조가 vsk->trans에 남게 됩니다.

익스플로잇

이 모든 것을 정리하면 권한 상승에 사용할 수 있는 신뢰할 수 있는 use-after-free를 얻을 수 있습니다.

이 저장소는 초기 use-after-free에 도달하는 것까지만 다룹니다. 이제 kmalloc-64 캐시에서 이전에 virtio_vsock_sock이 할당된 오프셋에 값을 쓰는 프리미티브를 가지게 되었습니다. 여기서 분석을 멈추기로 결정했습니다. 왜냐하면, 제가 여기서 모든 것을 다 해야 합니까?

도구 다운로드