Skip to content
KitploitKITPLOIT
도구익스플로잇블로그
Log in
제출
도구익스플로잇블로그
제출

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

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

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

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
CVE-2019-8601 — JavaScriptCore의 패치된 취약점 악용 | Kitploit
도구/GitHubGitHub/badaccess11/cve-2019-8601
Vulnerability AnalysisExploitationWeb Application ExploitationLearning & EducationPayload DevelopmentBinary Exploitation
GitHubbadaccess11/cve-2019-8601

CVE-2019-8601

JavaScriptCore의 패치된 취약점 악용

저장소 보기
17346년 전아직 검토되지 않음

인기

모두 보기 →

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

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

CVE-2019-8601 악용

이것은 원래 밴쿠버에서 열린 pwn2own 대회에서 Fluoroacetate가 발견한 WebKit 취약점에 대한 익스플로잇입니다. 제가 이 버그를 발견한 것은 아니지만, 익스플로잇 개발 기술을 연습하기 위해 이 익스플로잇을 작성했습니다. 이 익스플로잇에 대한 원본 분석 글은 Zero Day Initiative의 여기에 있습니다. 이 분석 글은 매우 훌륭하고 취약점을 이해하는 데 큰 도움이 되었지만, 취약점을 검증하는 사람의 관점에서 작성되었습니다. 처음부터 이 익스플로잇을 설계하려고 할 때 몇 가지 핵심 세부 사항이 누락되어 있음을 발견했고, ZDI 분석 글에서 놓친 부분을 채우고 복잡한 익스플로잇을 처음부터 설계하는 방법에 대한 실용적인 기술을 얻고자 합니다.

익스플로잇 단계

다음 단계는 JavaScriptCore(JSC) 내에서 임의 코드 실행을 달성하기 위한 개요입니다.

  • 취약점 식별
  • ASAN을 활성화한 상태에서 취약점 트리거 및 크래시
  • leakAddr 및 fakeObj 프리미티브 획득
  • 읽기 및 쓰기 프리미티브를 얻기 위해 배열 butterfly 손상
  • 읽기 및 쓰기 프리미티브를 사용하여 JSC 내에서 임의 코드 실행 달성

취약점 식별

악용될 취약점은 WebKit의 DFG JIT(Just-In-Time) 컴파일러가 생성한 코드에서 발생하는 정수 오버플로입니다. 이는 특히 compileNewArrayWithSpread 함수에서 발생합니다. 이 함수는 JavaScript 전개 구문을 사용하여 새 배열을 생성하는 코드가 DFG에 의해 JIT 컴파일될 때 호출됩니다.

compileNewArrayWithSpread

JIT 컴파일된 코드 내에서는 먼저 배열의 크기를 계산합니다. 이는 배열 생성자에 전달된 각 인수의 길이를 더하여 수행됩니다. 각 덧셈에 대한 크기를 계산하면서 크기의 오버플로를 확인합니다. 그런 다음 이 함수에서 계산된 길이를 전달하여 compileAllocateNewArray 함수를 호출합니다.

compileAllocateNewArrayWithSize

compileAllocateNewArray는 이전에 계산된 길이를 emitAllocateButterfly에 전달합니다.

emitAllocateButterfly

emitAllocateButterfly는 크기를 3비트 왼쪽 시프트하는데, 이는 8을 곱하는 것과 같습니다. 그러나 오버플로 확인이 없으므로 0x20000001과 같은 숫자가 0x8로 오버플로될 수 있습니다.

다음 C 프로그램은 이 취약점을 보여줍니다:

overflow-example2

overflow-example

이 취약점을 사용하여 JavaScript 엔진이 크기 0x20000001의 배열을 할당했다고 속일 수 있지만 실제로는 1개의 JSValue(8바이트)에 대한 공간만 할당됩니다. 이로 인해 경계를 벗어난(OOB) 읽기 및 쓰기(R/W) 프리미티브가 발생하며, 이를 활용하여 임의 R/W를 달성하고 최종적으로 원격 코드 실행(RCE)을 달성할 수 있습니다.

  • 취약점 식별

ASAN을 사용한 취약점 트리거

OOB 읽기가 발생하는 것을 확인하기 위해 JSC의 AddressSanitizer(ASAN) 빌드에서 이 취약점을 트리거해 보겠습니다.

이를 위해 WebKit 디렉터리에서 다음 명령을 실행할 수 있습니다:```bash Tools/Scripts/set-webkit-configuration --asan Tools/Scripts/build-jsc --jsc--only --debug

이것은 ASAN이 활성화된 JSC의 디버그 빌드를 빌드하여 취약점을 성공적으로 트리거했는지 여부를 확인할 수 있게 합니다.

다음은 exploit.js의 첫 번째 버전입니다.```javascript
function jitMe(array){
  return [...array]
}

let dummy = [1.1]
for(let i = 0; i < 200; i++){
  jitMe(dummy);
}

let a = []

let len = 0x20000001                                                                     

for(let i = 0; i < len; i++){
  a[i] = 1.1 
}

jitMe(a)

이것을 실행하면 다음과 같은 오류가 발생합니다:

Program terminated with signal SIGKILL, Killed. The program no longer exists.

제 추측으로는 너무 큰 배열을 할당하려고 할 때 너무 많은 메모리가 소비되었기 때문입니다. 이를 확인하기 위해 compileNewArrayWithSpread 내부에 m_jit.breakpoint() 호출을 추가하여 JIT 컴파일된 코드에 중단점을 추가했습니다. 이는 JIT 컴파일된 코드에 int3 명령어를 추가하는 역할을 합니다.

중단점을 추가한 후에도 해당 중단점이 적중되지 않는다는 것을 발견했고, 그런 다음 길이 0x20001을 테스트하기로 결정했습니다. 그 후 코드가 전혀 컴파일되지 않고 있다는 것을 깨달았기 때문에 DFG 컴파일러를 활성화하기 위해 반복 횟수를 더 추가했습니다.```javascript function jitMe(array){ for(let i = 0; i < 0x4000; i++){ let x = 1 + 1 } return [...array] }

let dummy = [1.1] for(let i = 0; i < 60; i++){ print(i) jitMe(dummy); }

let a = []

let len = 0x20000001

for(let i = 0; i < len; i++){ a[i] = 1.1 }

jitMe(a)

프로그램을 그대로 테스트하면 여전히 SIGKILL이 발생하지만, 더 작은 길이로 테스트하면 중단점이 적중됩니다. 이 시점에서 JSC가 거대한 배열을 처리하려 할 때 메모리가 부족한 것으로 보입니다.

이 문제를 해결하기 위해 더 작은 `a` 배열을 할당하고, 전개 구문을 사용하여 손상된 배열을 생성할 때 이를 여러 번 사용하여 다음과 같은 exploit.js를 만들기로 결정했습니다.```
function jitMe(array){
  for(let i = 0; i < 0x4000; i++){
    let x = 1 + 1
  }
  return [...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array]
}

let dummy = [1.1]
for(let i = 0; i < 100; i++){
  print(i)
  jitMe(dummy);
}

let a = []

let len = 0x20000010 / 0x10

for(let i = 0; i < len; i++){
  a[i] = 1.1
}

jitMe(a)

이 코드를 사용하여 SIGKILL 없이 중단점에 도달할 수 있었습니다! 보통 그렇듯이, 한 문제를 해결하면 또 다른 문제가 발생하는데, 이번에는 SIGABORT가 발생했습니다... gdb의 bt 명령어를 사용하면 operationNewArrayWithSize가 호출되었고, 이어 create가 호출된 것을 확인할 수 있습니다.backtrace1

JIT 컴파일된 코드가 operationNewArrayWithSize를 호출하는 것은 이상해 보이며, 어떤 이유로 JIT 코드가 JavaScript 엔진의 느린 경로(slow path)를 택해야 했을 것입니다.

slowcases

compileAllocateNewArrayWithSize에서 operationNewArrayWithSize로의 탈출(bailout)이 실제로 존재하는 것을 볼 수 있습니다. 그런 다음 정확히 왜 느린 경우로 탈출하는지 알아내야 합니다.

compileNewArrayWithSpread에서 shouldConvertLargeSizeToArrayStorage가 false로 설정되어 있으며, 느린 경로는 컴파일된 코드에 포함되지 않음을 확인할 수 있습니다.compileNewArrayWithSpread2

따라서 느린 경로가 emitAllocateJSObject 내 어딘가에서 발생하는 것이 타당합니다.

emitAllocateJSObject

emitAllocateJSObject는 emitAllocateJSCell을 호출하고, 이는 다시 emitAllocate를 호출합니다.

emitAllocate

emitAllocateWithNonNullAllocator

WebKit 할당자(Allocator)의 작동 방식을 모르면 이는 상당히 혼란스러워 보입니다. 따라서 저는 몇 개의 중단점을 추가하고 gdb에서 단계별로 실행해보기로 결정했습니다.

emitAllocateButterfly에 의해 호출된 emitAllocateVariableSized에 설정된 중단점에 도달한 후, 다음과 같은 어셈블리 코드를 보게 됩니다:assemblyEmitAllocateVariableSized

이는 JIT 컴파일러가 여기서 생성한 코드에 해당합니다:emitAllocateVariableSized

할당 크기에 0xf를 더한 후 오른쪽으로 4비트 시프트하는 것을 볼 수 있습니다. 그런 다음 느린 경로 분기에 해당하는 0x1f6과 비교됩니다. 이후 서브스페이스 할당자(subspace allocator)를 rsi로 이동시키고, 수행된 계산을 기반으로 이 포인터를 인덱싱합니다. 그런 다음 emitAllocateWithNonNullAllocator에 배치된 중단점으로 계속 이동하여 다음 어셈블리 코드를 확인합니다:

assemblyEmitAllocateWithNonNullAllocator.png

이는 JIT 컴파일러가 여기서 생성한 코드에 해당합니다:emitAllocateWithNonNullAllocator

이제 어셈블리의 일부를 단계별로 실행했으므로 무슨 일이 일어나고 있는지 조금 더 맥락을 알게 되었습니다. 두 개의 명령어를 더 진행하면 점프를 수행할 것임을 확인합니다:

stepFoward2

C++ 코드를 살펴보면 이는 이 할당자의 빈 리스트(free list)에 남은 공간이 없음을 의미하며, 따라서 팝(pop) 경로를 택할 것임을 추론할 수 있습니다.

jumpPerformed

도구 다운로드