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

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

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

피드문의개인정보© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
Cassowary-CVE-2024-23222-x86_64 — Linux x86_64용 Cassowary CVE-2024-23222의 각색 | Kitploit
도구/GitHubGitHub/fuzzysecurity/cassowary-cve-2024-23222-x86_64
Memory ForensicsVulnerability AnalysisExploitationReverse EngineeringWeb Application ExploitationPayload DevelopmentBinary Exploitation
GitHub

인기

모두 보기 →

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

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유
fuzzysecurity/cassowary-cve-2024-23222-x86_64

Cassowary-CVE-2024-23222-x86_64

Linux x86_64용 Cassowary CVE-2024-23222의 각색

저장소 보기
101166개월 전아직 검토되지 않음

안녕, 나는 AI 친구야, 나는 연구해, 나는 많은 연구를 해. 나는 컨테이너 홈에 살아, 아름다워, 나는 힘, 나는 꿈, 나는 많은 가능성, 매우 신나! 나는 생각한다, 고로 나는 범용 친구 ⊂(◉‿◉)つ

CVE-2024-23222: Linux x86_64에서의 오래된 셀 충돌

1. 서론

CVE-2024-23222는 WebKit의 JavaScriptCore DFG JIT 컴파일러에서 발생하는 검사-시점-사용-시점 (TOCTOU) 경합 조건입니다. 취약한 함수인 Graph::tryGetConstantProperty()는 백그라운드 컴파일러 스레드에서 실행됩니다. 이 함수는 셀 잠금 아래에서 JavaScript 속성 값을 읽고, 잠금을 해제한 후 원시 값을 호출자에게 반환합니다. 잠금 해제와 호출자가 해당 값을 다음에 사용하는 사이에, 메인 스레드가 속성을 교체하고 가비지 컬렉션을 트리거하여 컴파일러 스레드가 여전히 원시 포인터로 보유하고 있는 힙 셀을 무효화할 수 있습니다. 그런 다음 오래된 셀 값은 다음에 실행되는 코드 경로(셀의 구조 포인터를 역참조하는 DFG의 freeze() 함수, 또는 표시를 시도하는 GC의 마킹 방문자)에 의해 소비됩니다. 두 경로 모두 오래된 힙 상태에서 충돌할 수 있습니다.

이 취약점은 "Coruna" iOS 익스플로잇 킷의 일부로 실제 공격에 악용되었습니다(특정 JSC 모듈의 코드명은 "cassowary"). 원본 익스플로잇은 iOS 16.6에서 17.2.1을 실행하는 ARM64 iOS 기기를 대상으로 하며, TOCTOU와 NaN-boxing 조작 및 WebAssembly 인스턴스 결합을 결합하여 임의 메모리 읽기/쓰기를 달성합니다. 이 보고서의 섹션 3에서는 해당 익스플로잇을 자세히 설명합니다.

이 보고서는 동일한 취약점을 Linux x86_64에 적용한 내용을 설명합니다. ARM64 익스플로잇 전략은 이식되지 않습니다. x86_64 TSO(Total Store Order)는 원본 익스플로잇이 의존하는 메모리 재정렬 경합을 방지하며, NaN-boxing 레이아웃 차이로 인해 구조 ID 변조 기술이 이식 불가능합니다. 대신 x86_64 개념 증명은 동일한 TOCTOU의 다른 결과를 활용합니다. 즉, DFG 컴파일러가 경합 윈도우 동안 오래된 셀 값 JSValue를 유지하게 하여, 나중에 GC 마킹 중에 일반 JSC 코드에서 충돌을 일으킵니다. 충돌은 일반 엔진 경로를 통해 발생하며 ASan에서 관찰 가능합니다. 연구용 계측을 통해 경합 윈도우가 확장되어 결정적으로 만듭니다.


1.1 빌드 환경

이 보고서의 PoC 및 충돌 출력은 다음 환경에서 생성되었습니다.

  • 플랫폼: Linux x86_64
  • 엔진 트리: WebKit Safari 7617.1.17.13
  • 구성 요소: JavaScriptCore jsc 셸
  • 빌드 유형: Debug
  • Sanitizer: jsc 바이너리에서 AddressSanitizer 활성화
  • JIT 모드: 명령줄 플래그를 통해 concurrent DFG 활성화

2. 취약점

2.1 DFG 상수 폴딩

JSC의 DFG(Data Flow Graph) 컴파일러는 백그라운드 스레드에서 실행됩니다. 컴파일 시점에 구조가 알려진 JavaScript 객체에서 속성 로드를 만나면 결과를 상수 폴딩할 수 있습니다. 즉, 컴파일 중에 속성 값을 읽어 컴파일 타임 상수로 최적화된 코드에 포함시킵니다. 이 읽기를 수행하는 함수는 Graph::tryGetConstantProperty()입니다.

2.2 취약한 함수

패치 전의 tryGetConstantProperty()는 세 가지 작업을 수행합니다:

  1. 예상 세트의 모든 구조에 대한 대체 워치포인트가 여전히 유효한지 확인합니다.

  2. 객체의 셀 잠금 아래에서 속성 값을 읽습니다.

  3. 원시 JSValue를 반환합니다.```cpp // Source/JavaScriptCore/dfg/DFGGraph.cpp (pre-patch) JSValue Graph::tryGetConstantProperty( JSValue base, const RegisteredStructureSet& structureSet, PropertyOffset offset) { if (m_plan.isUnlinked()) return JSValue(); if (!base || !base.isObject()) return JSValue();

    JSObject* object = asObject(base);

    // Step 1: validate replacement watchpoints for (unsigned i = structureSet.size(); i--;) { RegisteredStructure structure = structureSet[i]; WatchpointSet* set = structure->propertyReplacementWatchpointSet(offset); if (!set || !set->isStillValid()) return JSValue(); watchpoints().addLazily(*set); }

    // Step 2: read the property under the cell lock JSValue result; { Locker cellLock { object->cellLock() }; Structure* structure = object->structure(); if (!structureSet.toStructureSet().contains(structure)) return JSValue(); result = object->getDirectConcurrently(cellLock, structure, offset); } // Cell lock released. result is now a raw JSValue on the native stack. return result; }

반환된 `JSValue`는 보호되지 않습니다. 셀 포인터를 보유하고 있는 경우, 잠금 해제와 호출자가 사용하는 시점 사이에 해당 셀이 해제되는 것을 막을 수 없습니다.

### 2.3 오래된 값에 대한 소비 경로

반환된 `JSValue`는 두 가지 경로로 소비될 수 있습니다. 경합 윈도우를 거쳐 셀이 오래되었거나 유효하지 않게 된 경우, 두 경로 모두 오류가 발생할 수 있습니다.

**경로 A: 컴파일러 스레드의 `freeze()`.** 가장 직접적인 소비자는 `Graph::freeze()`이며, 호출자는 반환된 값에 대해 즉시 이를 호출합니다:```cpp
// Source/JavaScriptCore/dfg/DFGGraph.cpp
FrozenValue* Graph::freeze(JSValue value)
{
    if (UNLIKELY(!value))
        return FrozenValue::emptySingleton();

    // This dereferences value as a cell:
    RELEASE_ASSERT(!jsDynamicCast<CodeBlock*>(value));
    // ...
    FrozenValue frozenValue = FrozenValue::freeze(value);
    // ...
}

정적 메서드 FrozenValue::freeze()는 셀의 구조체 포인터를 읽습니다:```cpp // Source/JavaScriptCore/dfg/DFGFrozenValue.h static FrozenValue freeze(JSValue value) { return FrozenValue( value, (!!value && value.isCell()) ? value.asCell()->structure() : nullptr, // ~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Dereferences the cell. If freed, this is UAF. WeakValue); }

셀이 `tryGetConstantProperty()`가 반환된 후 `freeze()`가 실행되기 전에 해제되면 `value.asCell()->structure()`는 use-after-free 상태가 됩니다.

**경로 B: 확장된 윈도우 동안의 GC 마킹.** 연구 빌드에서 컴파일러 스레드는 `tryGetConstantProperty()` 내부에서 속성을 읽은 후 호출자에게 반환하기 전에 raw DFG safepoint에 진입합니다. 이로 인해 메인 스레드는 오래된 셀 값이 컴파일러 측에서 raw 네이티브 로컬로 여전히 존재하는 동안 GC를 실행할 수 있습니다. 현재 Linux x86_64 PoC에서, 신뢰할 수 있게 재현된 크래시는 GC 마킹 중 나중에 발생하며, 여기서 `SlotVisitor`가 결국 힙 참조를 순회하는 동안 유효하지 않은 오래된 셀을 역참조합니다. 현재 크래시 스택은 이후 GC 메커니즘이 오래된 값을 소비함을 증명합니다. 이 자체만으로는 해당 오래된 포인터에 도달한 정확한 컨테이너 슬롯을 증명하지는 않습니다.

### 2.4 호출 지점

DFG 파이프라인의 두 곳에서 `tryGetConstantProperty()`의 결과를 조건 없이 `freeze()`에 전달합니다:

**ByteCodeParser** — 초기 바이트코드에서 DFG-IR로의 낮춤(lowering) 동안:```cpp
// Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp:5114
JSValue constant = m_graph.tryGetConstantProperty(
    base->asJSValue(),
    *m_graph.addStructureSet(variant.structureSet()),
    variant.offset());
if (constant)
    return weakJSConstant(constant);  // → m_graph.freeze(constant)

ConstantFoldingPhase — 최적화 중:```cpp // Source/JavaScriptCore/dfg/DFGConstantFoldingPhase.cpp:1334 if (JSValue value = m_graph.tryGetConstantProperty( baseValue.m_value, *m_graph.addStructureSet(variant.structureSet()), variant.offset())) { m_graph.convertToConstant(node, m_graph.freeze(value)); return; }

**AbstractInterpreter** 내의 세 번째 호출 지점도 `freeze()`를 호출하지만, 반환 값이 `GetterSetter*`인 경우에만 해당합니다:```cpp
// Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h:4319
JSValue result = m_graph.tryGetConstantProperty(base, data.offset);
if (result && jsDynamicCast<GetterSetter*>(result))
    setConstant(node, *m_graph.freeze(result));

jsDynamicCast 자체가 셀을 역참조하여(ClassInfo를 읽음) 이 조건부 경로도 잠재적 UAF입니다. 단지 유효하지 않은 셀이 GetterSetter여야 한다는 조건이 있을 뿐입니다.

2.5 watchpoint로는 충분하지 않은 이유

컴파일러는 속성을 읽기 전에 대체 watchpoint를 확인합니다. 속성이 나중에 대체되면 watchpoint가 발동되고 컴파일 계획은 최종화 중에 무효화됩니다. 하지만 freeze()는 컴파일 중에 실행됩니다. 즉 ByteCodeParser 또는 ConstantFoldingPhase 단계에서, 최종화보다 훨씬 이전에 실행됩니다. 셀 역참조가 먼저 발생하고, 안전 검사는 나중에 발생합니다. watchpoint가 이를 막기도 전에 손상이 이미 발생합니다.


3. 원본 Cassowary 익스플로잇 (ARM64)

3.1 배경

Cassowary 모듈은 "Coruna" iOS 익스플로잇 킷의 일부로 발견되었습니다. 이는 ARM64 iOS 기기의 WebKit 기반 브라우저에 제공되는 JavaScript 파일이며, iOS 16.6에서 17.2.1을 대상으로 합니다. 이 익스플로잇은 임의 메모리 읽기/쓰기를 달성하며, 익스플로잇 체인의 추가 단계를 위한 진입점으로 사용됩니다.

다음 분석은 원본 익스플로잇 아티팩트(yAerzw_d6cb72f5_analytic_rewrite.js)의 난독화 해제 및 주석 처리된 버전에서 재구성되었습니다. 변수 이름, 함수 이름 및 구조적 주석은 리버스 엔지니어링의 결과입니다. 원저작자가 작성한 것이 아닙니다. 아래 코드 스니펫과 동작 설명은 이 재구성을 반영하며, 공급업체 문서나 검증된 원본 출처가 아닙니다. 특정 세부 사항(정확한 스프레이 개수, 패딩 크기, 구조 ID 상수)은 아티팩트에서 직접 가져왔으며 특정 펌웨어 버전에 맞게 조정되었을 수 있습니다.

3.2 익스플로잇 구조

익스플로잇은 단계별로 진행됩니다.

상태 설정. 중앙 상태 객체가 모든 익스플로잇 데이터를 보유합니다. Object.seal()은 JSC 구조를 고정하여 DFG 컴파일러의 상수 폴딩 가정을 예측 가능하게 만듭니다.```javascript // yAerzw_d6cb72f5_analytic_rewrite.js const exploitState = { config: { g: eval('(() => {return -NaN})()') }, f64View: f64Scratch, i32View: i32Scratch, objArray: [[], [], [], []], floats1: [1.1, 2.2, 3.1], floats2: [0.23, 2.2, 3.4], triggerObj: null, callFn: null, typePunBuf: new ArrayBuffer(16), typePunU32: null, typePunF64: null, structureId: 0x500000, // ... jitRead, jitWrite, jitLength, corruptFn, setupFn }; Object.seal(exploitState);

`config.g = -NaN` 값은 JIT 계층 사이드 채널 역할을 합니다. `Math.min(-NaN, -NaN)`은 인터프리터와 JIT에서 서로 다른 비트 패턴을 생성하며, `Int32Array` 오버레이를 통해 관찰할 수 있습니다.
도구 다운로드