VirtualBox Slirp NAT 패킷 힙 익스플로잇

Tip

AWS 해킹 배우기 및 연습하기:HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기: HackTricks Training GCP Red Team Expert (GRTE) Azure 해킹 배우기 및 연습하기: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks 지원하기

요약

  • VirtualBox는 패킷 버퍼(mbufs)가 인라인 메타데이터와 함수 포인터 콜백(pfFini, pfDtor)을 가진 커스텀 zone allocator에 존재하는, 크게 수정된 Slirp 포크를 포함한다.
  • 게스트는 신뢰된 m->m_len을 공격자가 제어하는 IP 헤더 길이로 덮어쓸 수 있으며, 이는 이후의 모든 경계 검사들을 무력화하고 infoleak 및 overwrite primitives를 유발한다.
  • checksum이 0이고 ip_len이 과도한 UDP 패킷을 악용하면, 게스트는 mbuf 꼬리와 인접 청크의 메타데이터를 exfiltrate하여 힙 및 zone 주소를 알아낼 수 있다.
  • 조작된 IP 옵션을 제공하면 ip_stripoptions()가 제자리에서 memcpy()로 너무 많은 데이터를 복사하도록 강제되어, 공격자가 다음 mbuf의 struct item 헤더를 덮어쓰고 그 zone 필드를 완전히 제어되는 데이터로 가리키게 할 수 있다.
  • 손상된 mbuf를 free하면 공격자가 제공한 인수를 가진 zone->pfFini()가 호출된다; 이를 memcpy@plt로 가리키게 하면 arbitrary copy/write primitive가 되어 non-PIE VirtualBox 바이너리 내부의 GOT 엔트리나 다른 제어 데이터로 향하도록 조종할 수 있다.

Packet allocator anatomy

VirtualBox는 각 인터페이스마다 zone_clust라는 zone에서 들어오는 모든 Ethernet 프레임을 할당한다. 각 0x800-byte 데이터 청크는 인라인 헤더가 앞에 붙는다:

struct item {
uint32_t magic;      // 0xdead0001
void    *zone;       // uma_zone_t pointer with callbacks
uint32_t ref_count;
LIST_ENTRY(item) list; // freelist / used list links
};

mbuf가 해제될 때 호출 스택 m_freem -> ... -> slirp_uma_free()는 인라인 헤더를 신뢰합니다:

  1. uma_zfree_arg()item = (struct item *)mem - 1을 재계산하고 item->zone을 검증해야 하지만 Assert()는 릴리스 빌드에서 컴파일되지 않습니다.
  2. slirp_uma_free()zone = item->zone을 로드하고 조건 없이 zone->pfFini(zone->pData, data_ptr, zone->size)를 실행한 다음 zone->pfDtor(...)를 호출합니다.

따라서 mbuf 헤더에 대한 모든 write-what-where는 free() 중에 제어 가능한 간접 호출로 이어집니다.

Infoleak via m->m_len override

VirtualBox는 ip_input() 상단에 다음을 추가했습니다:

if (m->m_len != RT_N2H_U16(ip->ip_len))
m->m_len = RT_N2H_U16(ip->ip_len);

Because the assignment happens before verifying the IP header, a guest can advertise any length up to 0xffff. The rest of the stack (ICMP, UDP, fragmentation handlers, etc.) assumes m->m_len is trustworthy and uses it to decide how many bytes to copy off the mbuf.

체크섬이 0인 UDP 패킷(즉 “no checksum”)을 사용하라. NAT fast-path는 payload 무결성을 검사하지 않고 m->m_len 바이트를 전달하므로 ip_len을 부풀리면 Slirp가 실제 버퍼를 넘어 읽고 heap residues를 게스트나 NAT 밖의 협력하는 외부 헬퍼로 반환하게 된다. 청크 크기가 2048 바이트이므로 leak은 다음을 포함할 수 있다:

  • 다음 mbuf의 inline struct item — freelist 순서와 실제 zone 포인터를 드러낸다.
  • magic 필드 같은 heap cookies — 이후 corruptions를 수행할 때 유효해 보이는 헤더를 만들도록 도움.

IP options로 인접 청크 헤더 덮어쓰기

같은 잘못된 길이는 패킷을 ip_stripoptions()를 통하게 하여 overwrite 기본 동작으로 바꿀 수 있다(이는 IP header에 options가 있고 payload가 UDP/TCP일 때 트리거된다). 헬퍼는 m->m_len으로부터 복사 길이를 계산하고 memcpy()를 호출해 전송 헤더를 제거된 옵션 위로 이동시킨다:

  1. ip_len을 제공하여 계산된 이동 길이가 현재 mbuf를 넘어가도록 한다.
  2. IP options를 적은 수 포함시켜 Slirp가 stripping 경로로 진입하게 한다.
  3. memcpy()가 실행되면 다음 mbuf에서 읽어 현재 mbuf의 payload와 inline header에 덮어써 magic, zone, ref_count 등을 손상시킨다.

할당자는 동일 인터페이스의 패킷을 freelist 상에서 인접하게 유지하므로, 이 오버플로우는 적절한 heap grooming 이후에 다음 청크를 결정론적으로 덮친다.

uma_zone_t 위조로 pfFini 탈취하기

인접한 struct item을 손상시킬 수 있게 되면, 익스플로잇은 다음과 같이 진행된다:

  1. 유출된 heap 주소를 사용해 게스트가 완전히 제어하는 mbuf 안에 가짜 uma_zone 구조를 구축한다. 다음을 채운다:
    • pfFinimemcpy()의 PLT 엔트리
    • pData에 원하는 목적지 포인터(예: GOT 엔트리, vtable 슬롯, 함수 포인터 배열)
    • size에 복사할 바이트 수
    • 선택: pfDtor를 2단계 호출로 설정(예: 새로 쓴 함수 포인터를 호출하도록)
  2. 대상 mbuf의 zone 필드를 가짜 구조체 포인터로 덮어쓰고, list 포인터를 조정해 freelist 장부가 충돌을 피할 수 있을 만큼 일관되게 유지되게 한다.
  3. mbuf를 free한다. 그러면 slirp_uma_free()는 mbuf에 게스트 제어 데이터가 남아있는 동안 memcpy(dest=pData, src=item_data, n=size)를 실행해 arbitrary write를 발생시킨다.

Linux VirtualBox 바이너리는 non-PIE이므로 memcpysystem의 PLT 주소가 고정되어 있어 직접 사용할 수 있다. 게스트는 또 가로채진 호출이 실행될 때 참조가 유지되는 다른 mbuf 안에 /bin/sh 같은 문자열을 저장할 수 있다.

Fragmentation을 통한 Heap grooming

Slirp의 인터페이스별 zone은 깊이가 3072 청크이고 처음에는 연속 배열로 할당되어 freelist가 높은 주소에서 낮은 주소로 순회된다. 결정론적 인접성은 다음으로 달성할 수 있다:

  • NAT에 일정 크기의 IP_MF fragment를 다수 흘려보내어 reassembly 코드가 예측 가능한 mbuf 시퀀스를 할당하게 한다.
  • 타임아웃되는 fragment를 보내 특정 청크를 재활용하여 frees가 LIFO 순서로 freelist로 돌아가게 한다.
  • freelist walk에 대한 지식을 사용해 미래의 피해자 mbuf를 IP options overflow를 실을 mbuf 바로 뒤에 배치한다.

이러한 grooming은 오버플로우가 목표 struct item을 정확히 타격하도록 하고 가짜 uma_zone이 leak primitive의 범위 내에 남아있게 보장한다.

From arbitrary write to host code execution

memcpy-on-free primitive를 이용하면:

  1. 공격자가 제어하는 /bin/sh 문자열과 명령 버퍼를 안정적인 mbuf로 복사한다.
  2. primitive를 사용해 GOT 엔트리나 간접 호출 지점(예: NAT 장치 상태 내부의 함수 포인터)을 system()의 PLT 엔트리로 덮어쓴다.
  3. 덮어쓴 호출을 트리거한다. VirtualBox가 NAT 장치를 호스트 프로세스 내부에서 실행하므로 페이로드는 VirtualBox를 실행하는 사용자의 권한으로 실행되어 guest-to-host escape를 허용한다.

대체 페이로드로는 힙 메모리에 소형 ROP 체인을 심고 그 주소를 자주 호출되는 콜백에 복사하거나, pfFini/pfDtor 자체를 연쇄된 gadgets로 재지정해 반복적인 쓰기를 수행하는 방법이 있다.

References

Tip

AWS 해킹 배우기 및 연습하기:HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기: HackTricks Training GCP Red Team Expert (GRTE) Azure 해킹 배우기 및 연습하기: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks 지원하기