
CVE-2022-32932는 제가 ANE 커널 인터페이스에서 발견한 또 다른 취약점입니다. 이는 이중 페치(double fetch) 문제로, 흥미로운 OOB 쓰기(out-of-bounds write)로 이어졌습니다.
H11ANEIn::patchMutableSurface() (H11ANEIn::ANE_ProgramSendRequest_gated에서 도달 가능)는 model.hwx에 mutable 프로시저가 있고 initInfo 섹션도 있을 때 호출됩니다. 저는 그러한 모델을 찾아봤지만 찾을 수 없어서, 미리 컴파일된 모델 중 하나를 패치하고 CVE-2022-32845를 사용하여 로드했습니다. 기본 앱 샌드박스에서 취약한 코드 경로에 도달하는 데 CVE-2022-32845가 꼭 필요한 것은 아니며, 동일한 결과를 얻으려면 사용자 지정 mlmodel을 컴파일하는 것만으로 충분합니다. CVE-2022-32845에 대한 자세한 내용은 제 발표 슬라이드에서 확인할 수 있습니다.
ZinComputeProgramUpdateMutables()는 H11ANEIn::patchMutableSurface()가 호출하는 또 다른 함수이며, 함수 프로토타입은 다음과 같습니다:
ZinComputeProgramStatus __cdecl ZinComputeProgramUpdateMutables(
uint64_t procedureId,
const ZinComputeProgramInitInfo *init_info,
const ANECMutableProcedureInfo *mutable_procedure_info,
uint64_t mut_procedure_info_size,
void *MUTK_kernel_section,
uint64_t MUTK_kernel_section_size);
init_info: 직렬화된 입력을 포함하는 initInfo 섹션입니다. 직렬화 함수 serialize_initinfo_section()는 weightBufs 익스플로잇 소스 코드에서 찾을 수 있습니다. mutable_procedure_info: 공격자가 제공하는 공유 IOSurface 버퍼이며, weightBufs 익스플로잇에서는 weightsBuffer라고도 합니다. mut_procedure_info_size: mutable_procedure_info 표면 버퍼의 크기를 나타냅니다. MUTK_kernel_section: (또는 MUTK) 프로그램 로딩 단계에서 커널이 생성하는 IOSurface 객체의 매핑 버퍼입니다. MUTK_kernel_section_size: 변경 가능한 커널 섹션(mutable kernel section)의 크기입니다.

88-92 루프는 mutable_procedure_info 객체 내의 MutableWeight 객체 개수를 계산한 다음, 93에서 MutableWeight 배열의 할당 크기를 계산합니다. 그 후 100에서 ANECMutableWeight 객체 배열이 할당되고, 111-127 루프에서 ANECGetMutableWeight()를 통해 적절한 weight 버퍼/크기 쌍으로 채워집니다.
ANECGetMutableOperationInfo()는 공유 메모리에서 opsInfo 객체를 반환합니다:
opsInfo *__fastcall ANECGetMutableOperationInfo(const ANECMutableProcedureInfo *MutableProcedureInfo, unsigned int id)
{
unsigned int weight_buffer_size; // w8
opsInfo *opInfo; // x0
weight_buffer_size = MutableProcedureInfo->header.weight_buffer_size;
if ( !weight_buffer_size )
return 0LL;
opInfo = (opsInfo *)((char *)MutableProcedureInfo + MutableProcedureInfo->wb_offsets[id]);
while ( opInfo->op_index != id )
{
if ( !--weight_buffer_size )
return 0LL;
}
return opInfo;
}
ANECGetMutableWeight 의사 코드는 다음과 같습니다:
void __fastcall ANECGetMutableWeight(
const ANECMutableProcedureInfo *procedure_info,
weightInfo *a2,
ANECMutableWeight *a3)
{
uint64_t wi_size; // x9
wi_size = a2->wi_size;
a3->_weightBuf = (char *)procedure_info + a2->wi_off;
a3->_weightBufSize = wi_size;
}
ANECGetMutableWeightInfo 의사 코드는 다음과 같습니다:
weightInfo *__fastcall ANECGetMutableWeightInfo(
const ANECMutableProcedureInfo *MutableProcedureInfo,
opsInfo *a2,
unsigned int a3)
{
if ( a2->op_count <= a3 )
return 0LL;
else
return (weightInfo *)((char *)MutableProcedureInfo + a2->op_offsets[a3]);
}
저는 이미 “Apple Neural Engine 공격하기” 슬라이드에서 ANECMutableProcedureInfo의 형식을 설명했으므로, 아직 읽지 않으셨다면 읽어보세요. 구조체 정의는 weightBufs 익스플로잇의 'aneProgram.h'에서 찾을 수 있습니다.
눈치채셨겠지만, ANECGetMutableOperationInfo()->op_count는 두 번 페치됩니다. 한 번은 ANECMutableWeight 배열을 할당하기 위해 크기를 계산할 때, 또 한 번은 이 배열을 채울 때입니다.
mutable_procedure_info 버퍼는 공유 메모리이므로, 공격자는 별도의 스레드를 사용하여 첫 번째 사용과 두 번째 사용 사이에 opsInfo->op_count 값을 변경할 수 있습니다. 이로 인해 크기 불일치가 발생하며, kalloc var zone, kheap defaul 또는 커널 맵에서 흥미로운 OOB 쓰기로 이어질 수 있습니다.
이 취약점은 여러 가지 흥미로운 방식으로 활용될 수 있습니다. 예를 들어, 공격자는 93번째 줄에서 total_count = 0x1000;으로 설정한 다음 opsInfo->count를 더 큰 값으로 증가시켜 ANECGetMutableWeight()에서 데이터가 범위를 벗어나 복사되게 할 수 있습니다.
OOB 쓰기가 매핑되지 않은 메모리 영역에 도달하면 커널은 아래 표시된 명령어에서 패닉(panic)이 발생합니다:
com.apple.driver.AppleH11ANEInterface:__text:FFFFFE0008913D08 EXPORT _ANECGetMutableWeight
com.apple.driver.AppleH11ANEInterface:__text:FFFFFE0008913D08 _ANECGetMutableWeight ; CODE XREF: _ZinComputeProgramUpdateMutables+270↓p
com.apple.driver.AppleH11ANEInterface:__text:FFFFFE0008913D08 LDP X8, X9, [X1,#8]
com.apple.driver.AppleH11ANEInterface:__text:FFFFFE0008913D0C ADD X8, X0, X8
com.apple.driver.AppleH11ANEInterface:__text:FFFFFE0008913D10 STP X8, X9, [X2] // <---- Kernel panic
com.apple.driver.AppleH11ANEInterface:__text:FFFFFE0008913D14 RET
이 버그는 두 개의 64비트 값, 즉 사용자 공유 버퍼를 가리키는 커널 주소와 (반)임의의 64비트 값을 쓸 수 있는 강력한 프리미티브를 제공합니다.
개념 증명은 독자의 몫으로 남겨 둡니다. 그러나 weightBufs 익스플로잇에는 취약한 코드 경로에 도달하는 데 필요한 모든 것이 포함되어 있습니다. 행운을 빕니다 :-).