윈도우 인터널즈

시스템 메커니즘 02 - 하드웨어 사이드 채널 취약점

묭묭.cpp 2024. 10. 21. 13:54

시스템 메커니즘 02 - 하드웨어 사이드 채널 취약점

하드웨어 사이드 채널 취약점

최신 CPU 내부 레지스터 연산, 데이터 이동 매우 빠름(피코초 단위)

  • 레지스터는 희소 자원임
  • 운영체제와 애플리케이션 코드는 항상 CPU에게 데이터를 레지스터에서 주 메모리로 이동하게 지시
    • 반대도 마찬가지

캐시(Cache)

  • CPU 내부에 위치해 직접적으로 액세스 가능한 메모리
  • 비용이 비싸지만 빠른 속도

램(RAM)

  • CPU에서 외부 버스를 통해 액세스 가능한 메모리
  • 느리지만 비용이 저렴하고 용량이 큼

메모리 계층

  • 메모리 위치에 따라 정의됨
  • CPU에 가까울수록 더 빠르고 용량이 작음

최신 CPU는 코어에 직접 엑세스 가능한 3 계층의 캐시 메모리 운용

  • L1, L2 캐시
    • 코어에 가장 가깝고 각 코어가 개별적 소유
  • L3 가장 멀리 있고 모든 CPU 코어 간 공유
    • 임베디드의 경우 L3 캐시가 존재하지 않음

8-02

캐시의 주요 특징

  • CPU의 레지스터와 비슷한 액세스 시간
    • 여전히 레지스터보단 느리지만
  • 메인 메모리의 액세스 시간은 100배 가량 더 느림
    • 메인 메모리의 액세스는 상당한 성능 저하

메인 메모리의 액세스 저하를 해결하기 위해 다양한 정책을 수행

  • 이런 정책에 따라 사이드 채널 공격(side-channel attacks)가 만들어짐
    • 스펙컬레이티브(Speculative) 공격이라고도 함
    • 엔드 유저의 보안성을 무력화하는 데 있어 효과적인 공격

비순차적 실행

최신 마이크로프로세서는 파이프라인(pipeline)을 활용하여 기계어를 실행

  • 파이프라인의 단계
    • 명령을 가져오고 해독
    • 레지스터 할당과 이름 변경
    • 명령 재배치
    • 실행과 종료 등
  • 메모리 성능 저하 문제 해결을 위한 대표적인 정책
    • 실행 엔진이 자원이 있을 때마다 순서와 상관없이 명령을 수행하게 함
    • CPU 코어를 최대한 활용하고자 커밋되거나 필요한 명령이 확실해질 때까지 수백개의 명령을 예상해 실행 가능

비순차적 실행(out-of-order execution)의 문제

  • 분기 명령(branch instruction)과 관련
    • 기계어 코드에서 2가지의 실행 흐름을 만듬
      • 올바른 실행 흐름은 이전에 실행한 명령에 따라 결정됨
    • 조건 계산의 경우 느린 램 메모리에 접근하는 이전 명령에 따라 성능 저하 발생 가능
    • 다음 올바른 명령의 비순차적 실행을 계속하기 전 조건을 정의하는 명령이 끝날 때까지 대기해야 함
      • 즉, 메모리 버스가 메모리 접근을 완료할 때까지 대기
      • 비슷한 문제로 간접 분기(indirect branch)에도 있음
  • CPU 실행 엔진은 분기의 대상을 알 수 없음
    • 보통 jump와 call 명령
    • 해당 주소를 메인 메모리에서 가져와야 함
      • 추측 수행(speculative execution)이라고도 함
      • CPU의 파이프라인이 다양한 명령을 병렬적으로 순서 없이 해석하고 수행하는 것을 의미
    • 그러나 결과가 레지스터에 저장되지 않고 메모리에 데이터를 쓰는 동작은 분기 명령 수행 전까지 보류됨

CPU 분기 예측기

어떤 분기 조건이 판단되기 전 CPU가 분기될 실행 흐름을 알 수 있는 방법

  • CPU 패키지에 포함된 분기 예측기와 분기 대상 예측기

분기 예측기

  • 분기가 완전히 결정되기 전에 미리 예측할 수 있게 함

구체적인 구현(CPU마다 다를 수 있음)

  • 분기 대상 버퍼(BTB, Branch Target Buffer)라는 내부 캐시
    • 인덱싱 함수를 통해 생성된 주소 태그를 사용하여 분기 목적지의 주소를 기록
      • 과거 조건 분기에 대한 기록임
    • 처음 분기 명령이 실행될 때 대상 주소가 여기에 저장됨
    • 보통 첫 실행에서는 실행 파이프라인이 멈추고, CPU는 조건 또는 대상 주소를 메인 메모리에서 가져오고자 대기됨
    • 같은 분기가 두 번째 실행될 때 BTB에 저장된 대상 주소가 사용되여 예측된 대상을 파이프라인으로 가져옴

8-03

분기 예측 실패(branch misprediction)

  • 예측이 틀렸을 경우 추측적 실행 결과가 폐기됨
  • 다른 경로가 CPU 파이프라인에 들어가고 올바른 분기의 실행이 재개됨
  • 낭비되는 CPU 사이클은 분기 조건을 기다리는 것보다 나쁘지 않음
    • CPU 캐시에 흔적이 남는다는 취약점이 있음

CPU 캐시

캐시 라인(cache lines)

  • 메모리와 캐시 사이의 데이터 이동에 정해진 블록 단위 크기

캐시 엔트리

  • 캐시 라인이 메모리에서 캐시에 복사될 때 만들어지는 것
  • 복사된 데이터와 요청된 메모리를 구분하는 태그 정보를 가지고 있음
    • 분기 대상 예측기와는 다르게 물리 메모리 주소로 찾음
      • 이렇게 하지 않으면 주소 공간의 변화를 처리하기가 복잡함

캐시는 주어진 물리 주소를 쪼개어 구분함

  • 상위 비트는 태그
    • 이는 메모리 주소가 속한 캐시 블록을 유일하게 구분하는 구분자
  • 하위 비트는 캐시 라인과 라인 내의 오프셋

CPU가 메모리를 읽는 과정

  1. 메모리를 읽거나 쓸 때 일치하는 캐시 엔트리를 체크함
  2. 대상 메모리의 데이터를 가지는 캐시를 찾았다면 캐시 히트가 발생
    • 즉시 캐시 라인에서 데이터를 읽거나 쓸 수 있음
  3. 그렇지 않다면 캐시 미스가 발생
    • 새로운 엔트리를 캐시에 만들고 캐시에서 데이터를 얻을 수 있도록 메인 메모리의 데이터를 복사함

CPU가 새로운 내용을 메모리 주소에 쓰기 명령을 받음

  1. 메모리와 대응되는 캐시 라인을 갱신함
  2. 그 후 일정 시점에 메모리 페이지에 적용된 캐시 정책에 따라 다시 RAM에도 쓰게 됨
    • 캐시 정책(Write-back, Write-through)
    • 캐시 일관성 프로토콜
      • 주 CPU가 캐시 블록을 갱신한 이후 최신이 아닌 데이터를 사용하지 않도록 보장

캐시 미스(Cache Miss)가 발생한 경우

  • 새로운 엔트리의 공간을 위해 CPU는 존재하는 캐시 블록을 밀어냄
    • 이런 동작은 배치 정책(placement policy)에 따라 수행됨

배치 정책에 따른 캐시 종류

  • 직접 매핑(Direct-mapped) 캐시
    • 배치 정책이 특정 가상 주소에 대해 하나의 블록만 대체할 수 있음
  • 전체 연관(Full-associative) 캐시
    • 어떤 캐시 항목이든 새로운 데이터를 갖고 있을 수 있게 하는 방식
      • 캐시 항목이 같은 블록 번호를 갖고 있어야 함
  • N 방향 집합 연관(N-way Set Associative) 캐시
    • 위 2가지 방법의 중간
      • 대부분의 캐시가 이 방식을 채택함
    • 방향은 캐시를 나눈 것을 의미함
    • 각각은 같은 용량을 가지고 같은 방식으로 찾음

4-Way Set Associative Cache

  • 4개의 물리 주소가 가리키는 데이터를 4개의 같은 캐시 라인에 각각 저장 가능함
    • 물론 캐시 라인의 태그는 다름

8-04

사이드 채널 공격

CPU 캐시의 구조적인 취약점을 이용함

  • CPU의 실행 엔진은 명령이 실제 처리될 때까지 연산 결과를 기록하지 않음
    • 여러 명령이 비순차적으로 실행되어도 레지스터와 메모리에 실제 영향을 미치지 않아도 캐시에는 구조적인 취약점이 있었음
  • 비순차 실행 엔진과 분기 예측기에 대한 공격 방식
  • 가장 파괴적이고 효과적인 하드웨어 사이드 채널 공격 2가지
    • 멜트다운(Meltdown)
    • 스펙터(Spectre)

멜트다운

악의적 데이터 캐시 로드(RDCL, Rogue Data Cache Load)라고도 함

  • 악성 유저 모드 프로세스가 모든 메모리를 읽는 것이 가능하게 함
    • 액세스 불가능한 커널 메모리에 액세스도 가능케 함
  • 비순차 실행 엔진이 공격 대상
  • 메모리 액세스와 메모리 액세스하는 명령의 권한 검사 사이의 경쟁 조건을 이용

멜트 다운의 공격 방식

  1. 악성 유저 모드 프로세스가 캐시 전체를 플러시함으로 시작
    • 이 명령은 유저 모드에서도 호출 가능함
  2. 금지된 커널 메모리 액세스 명령 후에 의도된 캐시를 채우는 명령을 붙여 실행
    • 프로브 배열(probe array)를 사용
    • 원래 프로세스는 커널 메모리에 액세스할 수 없으므로 프로세서에 의해 예외 발생
  3. 예외는 애플리케이션에 의해 인지되어 처리되지 못한다면 프로세스는 바로 종료됨
    • 그러나 비순차 실행 방식에 때문에 CPU는 이미 액세스 금지된 커널 메모리에 액세스하는 명령을 수행했고 캐시에 흔적이 남아 있음
      • 하지만 실제 반영되지 않은 상태
      • 따라서 RAM과 CPU 레지스터를 통해서는 알 수 없음
  4. 악성 애플리케이션은 전체 캐시를 탐색함
    • 이때 캐시를 채우는 데 걸리는 시간을 측정
    • 이 시간이 특정 기준보다 짧다면 캐시에 데이터가 존재하는 것
    • 공격자는 이것을 활용하여 커널 메모리의 정확한 데이터를 읽어낼 수 있게 됨

공식 멜트다운 연구 문서에서 인용한 내용

  • 1MB 배열 탐색에 대한 액세스 시간을 보여줌

8-05

  • 1개의 페이지를 제외하고 각 페이지 접근 시간이 유사함
  • 비밀 데이터를 한 번에 한 바이트씩 읽을 수 있고 한 바이트에 256개의 값만 있을 수 있다 가정
    • 캐시 히트가 일어난 정확한 페이지를 알아낼 수 있음
    • 공격자가 커널 메모리 어느 곳에 비밀 데이터가 있는지 알 수 있게 됨

스펙터

멜트다운과 유사함

  • 역시 비순차 실행 방식의 약점을 이용
  • 스펙터의 주 공격 대상은 분기 예측기와 분기 대상 예측기

스펙터 공격 방식

  1. 공격 개시 단계에서 공격자는 권한이 낮은 프로세스(공격자의 프로세스)에서 CPU 분기 예측기가 잘못된 학습을 하도록 반복적 연산 수행
  2. 두 번째 단게에서 공격자는 높은 권한을 가진 애플리케이션이 잘못된 예측 분기로 명령 에측을 수행하게 만듬
    • 이때 수행하는 명령은 공격 대상이 되는 프로세스의 중요한 정보를 마이크로아키텍처 채널로 옮김
      • 보통 CPU 캐시
  3. 마지막 단계에서 공격자는 권한이 낮은 프로세스로부터 CPU 캐시에 담긴 중요한 정보를 얻어냄
    • 이때 전체 캐시를 탐색하는 작업을 수행
      • 멜트다운 공격의 방식을 그대로 사용
    • 결과적으로 피해자의 비밀 데이터를 얻게 됨

스펙터 공격의 첫 번째 변종

  • 위의 방식으로 공격 대상 프로세스 메모리 상의 비밀 데이터를 얻어냄
    • CPU 분기 예측기가 잘못된 분기를 하도록 유발함으로 가능
  • 메모리상 버퍼의 범위 검사 함수에서는 보통 분기 동작을 포함
    • 버퍼가 비밀 데이터와 근접해있고 공격자가 분기 오프셋을 제어 가능하다고 했을 때
    • 공격자는 반복적으로 분기 예측기를 학습시켜 CPU가 해당 분기로 수행하게 함
  • 그 후 공격자는 CPU 캐시를 잘 정의된 방식으로 준비
    • 범위 검사 시 사용되는 메모리상의 버퍼 크기 정보와 같은 것은 캐시에 있으면 안됨
  • 범위 검사를 하는 분기문에서 에러가 발생할 오프셋을 제공했다 가정
    • CPU 분기 예측기는 예측된 실행 흐름을 따라가게됨
    • 그러나 흐름이 틀림
      • 명령은 범위를 넘어서는 데이터에 액세스하게 되고 그곳에는 비밀 정보가 담겨 있음
  • 결과적으로 공격자는 전체 캐시를 탐색하므로 비밀 데이터를 얻어낼 수 있게 됨
    • 멜트다운 공격과 비슷

스펙터 공격의 두 번째 변종

  • CPU 분기 대상 예측기를 악용
    • 간접 분기문을 공격함
  • 공격자에 의해 잘못 학습된 간접 분기문은 공격 대상 프로세스의 임의 메모리 데이터를 읽어낼 수 있음
  • 잘못된 분기 대상을 학습시키고 BTB를 이용해 추측을 실행할 때 잘못된 위치로 분기하게 만듬
  • 공격 대상 메모리상에서 분기 대상 메모리는 가젯(Gadget)을 가리켜야 함
    • 가젯 - 비밀 데이터에 액세스할 수 있고 캐시에 저장할 수 있게 하는 명령
    • 공격자는 간접적으로 공격 대상 프로세스가 있는 한 개 이상의 메모리나 레지스터의 내용을 조정할 필요가 있음
    • 보통 API가 신뢰할 수 없는 입력 데이터를 받아들였을 때 가능해짐
  • 공격자는 분기 예측기를 학습시킨 뒤 CPU 캐시를 플러시하고 권한 높은 서비스를 수행
    • 서비스를 구현한 프로세스는 공격자에게 제어되는 프로세스와 유사한 간접 분기문이 구현되어 있어야 함
  • CPU 분기 예측기는 잘못된 주소에 있는 가젯을 실행하게 됨
    • 첫 번째 스펙터 방식과 멜트다운에서처럼 CPU 캐시에 문제를 일으킴
  • 그 결과로 낮은 권한에서도 데이터를 읽는게 가능해짐

기타 사이드 채널 공격

추측성 저장소 우회(SSB, Speculative Store Bypass)

  • CPU 명령 최적화로 인해 발생
    • 이전의 저장 작업에 의존하지 않고 평가한 로드 명령의 결과가 처리될 때까지 대기하지 않고 예상해서 실행할 수 있게함
  • 예측이 틀렸다면 로드 명령은 연산이 오래된 데이터를 읽게 되어 중요한 정보를 저장할 가능성이 있음
  • 이러한 추측 실행 중 실행된 다른 작업으로 전달될 수 있음
  • 이후에 메모리에 액세스할 수 있고 마이크로아키텍처(CPU 캐시)의 잘못된 상태를 유발
    • 공격자는 이 잘못된 상태 정보로 비밀 데이터를 취득함

포섀도우(Foreshadow) L1TF라고도 함

  • 하드웨어 인클레이브(SGX)에서 비밀을 훔치고 일반 유저 모드에서 일반적인 공격으로 시도됨

현대 추측 실행 엔진의 2가지 하드웨어 결함을 악용

  • 액세스할 수 없는 가상 메모리에 대한 예측
    • PTE의 유효 비트가 없는 데이터에 액세스할 때 예외 발생
    • 그러나 해당 PTE가 해석되면 CPU는 읽었던 데이터를 기반으로 추측을 수행
    • 다른 사이드 채널 공격처럼 실제 명령이 반영되지는 않지만 취약점을 이용 가능
    • 더 나아가 특정 상황에서는 게스트 물리 주소를 해석할 때 유효하지 않은 2단계 주소 변환 테이블 엔트리를 만나면 동일한 취약점 발생
  • CPU 코어 논리 프로세서(하이퍼스레드)의 추론
    • 하이퍼스레딩 환경에서는 2개의 논리 스레드가 단일 캐시를 공유함
    • 하나의 논리 프로세서가 높은 권한으로 코드를 수행하는 중 다른 논리프로세서가 다른 논리 프로세서의 취약점을 이용할 수 있음

인텔 CPU는 캐시되거나 되지 않은 메모리에 액세스하기 위해 중간 버퍼를 가짐

  • 그리고 마이크로 명령을 재배치함
  • 마이크로아키텍처 데이터 샘플링(MDS, Microarchitectural Data Sampling) 류의 공격은 이런 중간 버퍼의 취약점을 이용함

중간 버퍼들

  • 저장 버퍼(Store buffers)
    • 저장 연산을 하며 내부의 임시 마이크로아키텍처 버퍼에 데이터를 씀
    • CPU로 하여금 캐시나 주 메모리에 데이터를 실제로 쓰기 전 명령을 계속 수행할 수 있게 함
    • 로드 연산이 같은 메모리에 있는 데이터를 대상으로 하면 프로세서는 저장 버퍼에서 데이터를 바로 가져옴
  • 채우기 버퍼(Fill buffers)
    • 첫 번째 단계의 캐시 미스 데이터를 모으기 위한 요소
    • CPU 캐시와 CPU 비순차 실행 엔진 사이에 위치
    • 이전 메모리 요청에 대한 데이터를 유지하고 해당 데이터는 추측 수행되는 로드 동작에 넘어갈 수 있음
  • 로드 포트(Load ports)
    • 메모리나 I/O 포트에서 로드 동작을 수행하고자 사용하는 CPU 내부 구성 요소

이런 버퍼들은 보통 하나의 CPU 코어에 속함

  • SMT 스레드 사이에 공유됨
  • 비밀 데이터가 이 사이에 공유될 수 있음을 의미
반응형