
Bad_Hoist라고 불리는, Fire30이 작성한 CVE-2018-4386 익스플로잇 구현의 Sleirsgoevy 버전에 대한 Writeup
[!Note] PS4에 대한 배경 정보:
PlayStation 4 콘솔은 커스텀 AMD x86-64 CPU(8코어)를 탑재하고 있으며, Orbis OS는 NetBSD의 일부 요소도 포함한 FreeBSD(v9.0) 기반입니다. 또한 Mono VM, WebKit과 같은 다양한 추가 오픈 소스 소프트웨어도 포함되어 있습니다.
PS4에서 사용되는 인터넷 브라우저는 실제로 오픈 소스 WebKit 프로젝트로 빌드되었습니다. 이것은 iOS, Wii U, 3DS, PS Vita, PS4의 브라우저에서 웹 페이지를 렌더링하는 오픈 소스 레이아웃 엔진입니다.
PS4 인터넷 브라우저는 실제로 2개의 별도 프로세스로 구성됩니다. 우리가 코드 실행을 위해 하이재킹하는 프로세스는 WebKit 코어 프로세스입니다(예를 들어 HTML 및 CSS 파싱, 이미지 디코딩, JavaScript 실행을 담당). 다른 하나는 나머지 모든 것을 처리합니다: 그래픽 표시, 컨트롤러 입력 수신, 기록 및 북마크 관리 등.
PS4 WebKit 브라우저는 여러 힙 할당자를 사용하며, 각각은 서로 다른 구성 요소에 사용됩니다. 다음과 같습니다:
CVE-2018-4386의 핵심은 PS4 펌웨어 6.XX에서 사용되는 WebKit(v605.1.15)의 JavaScriptCore (JSC) 엔진에 있는 논리 결함입니다. 이 결함은 BytecodeGenerator::hoistSloppyModeFunctionIfNecessary 함수에 있으며, sloppy mode JavaScript에서 변수 호이스팅의 부적절한 처리, 특히 루프 내에서 발생합니다.
for-in취약한 구성 요소 (ForInContext):
우리가 주로 타겟으로 하는 것은 ForInContext입니다. 이것은 JavaScriptCore가 for-in 루프의 상태를 관리하는 데 사용하는 내부 구조로, 현재 반복 변수와 열거 중인 속성 집합을 추적합니다.
for-in 루프 내부에서 함수 선언이 호이스팅될 때, 반복 변수가 덮어써지면 엔진은 연결된 ForInContext 객체를 _무효화_해야 합니다. 그러나 이 버그로 인해 이 무효화가 발생하지 않습니다. 이로 인해 반복 변수가 임의의 객체로 대체될 수 있습니다. 그럼에도 불구하고 엔진은 계속해서 변수를 문자열 속성 이름으로 취급합니다.
이후 op_get_direct_pname 바이트코드 핸들러가 호출되면, 타입 검사 없이 반복 변수를 문자열 객체로 직접 사용합니다.
문자열 대신 조작된 객체를 전달하여 타입 혼동(type-confusion)을 일으킴으로써, 우리는 메모리 손상을 달성하고 addrof, fakeobj, 임의 읽기/쓰기(Arbitrary Read/Write) 와 같은 유용한 익스플로잇 기본 요소까지 얻을 수 있습니다.
구조 ID (Structure ID): WTF::StringImpl과 같은 내부 표현을 포함하여 JavaScriptCore의 모든 객체에는 객체의 종류와 필드를 해석하는 방법을 엔진에 알려주는 구조 ID(또는 타입 태그)가 있습니다.
타입 혼동 (Type Confusion): 우리의 익스플로잇은 CVE-2018-4386 버그를 악용하여 JavaScript 객체가 StringImpl로 해석되도록 합니다. 이것이 create_impl() 함수의 목적이며, 이 함수는 WTF::StringImpl 타입 혼동 객체를 반환하고, 이후 이 객체는 이 write-up의 취약점 메커니즘 부분에서 앞서 언급한 임의의 객체로서 trigger() 함수에 전달될 수 있습니다.
그러나 이것이 작동하려면 메모리 레이아웃과 구조 ID가 엔진이 실제 문자열 객체에 대해 기대하는 것과 "충분히 가까워야" 합니다.
JSString::toIdentifier()는 내부적으로 무엇을 하나요?PS4 인터넷 브라우저의 WebKit JavaScriptCore 엔진에는 JSString::toIdentifier()라는 메서드가 있습니다. 이 메서드는 JavaScript 문자열 객체(JSString)를 내부 Identifier 표현으로 변환합니다.
이 Identifier는 엔진 전체에서 속성 이름, 변수 이름 및 JavaScript 엔진이 빠르고 빈번하게 참조해야 하는 기타 문자열을 효율적으로 비교, 저장 및 조회하는 데 사용됩니다.
이 메서드는 객체가 유효한 문자열인지, 구조 ID가 엔진이 문자열 객체에 대해 기대하는 것과 일치하는지 확인합니다. 문자열이 rope(문자열의 연결)인 경우 변환 전에 평탄화(flatten)할 수 있습니다. 그런 다음 문자열에 대한 기존 Identifier를 검색하거나, 존재하지 않으면 새로 생성합니다.
이 Identifier는 이후 빠른 속성 및 변수 조회를 위해 내부적으로 사용됩니다.
JSString::toIdentifier() 검사 우회 방법익스플로잇이 1024번 반복되는 for 루프에 진입하면, 각 반복마다 create_impl() 함수가 반환하는 32개의 새로운 구조 ID를 가진 새로운 타입 혼동 WTF::StringImpl 객체가 생성됩니다. 이 타입 혼동 객체가 사용되어 임의의 객체로 trigger()에 전달되면, JSC는 이 객체에 대해 JSString::toIdentifier()를 호출합니다.
JSString::toIdentifier()는 구조 ID의 특정 비트를 확인하여 객체가 유효한 문자열이거나 문자열로 취급될 수 있는지 확인합니다. 다양한 레이아웃과 구조 ID를 가진 많은 객체를 생성함으로써, 익스플로잇은 적어도 하나가 JSString::toIdentifier()의 내부 검사를 통과하는 구조 ID를 가질 확률을 높입니다.