인텔의 CET(Control-flow Enforcement Technology)에 대해서 알아보자
CET는 CR4.CET 플래그가 1로 설정이 되어 있어야 enable 된다.
물론, 이 플래그는 ring 0에서만 설정할 수 있음.
함수의 프롤로그 부분에 endbr64라는 명령어를 본 적이 있을 것이다. 이 명령어는 indirect branch tracking의 기능을 한다.
말 그대로 indirect하게 branch가 바뀌는 jmp, call과 같은 명령어가 실행될 때 그것을 tracking 한다는 뜻이다.
CPU 내부적으로 state machine을 통해서 관리한다.
ring 3의 경우 ‘IA32_U_CET ’, 나머지의 경우 ’IA32_S_CET‘ MSR의 플래그를 통해서 상태가 관리된다.
물론 CET 기능이 제공되지 않는 옛날 CPU라면 이 기능은 지원되지 않는다. endbr64 명령어도 그냥 nop으로 처리된다.
편의상 user mode에 대해서만 설명함.
평상시에는 IA32_U_CET.TRACKER = IDLE 상태로 존재한다.
그러다가 indirect branch tracking에 감지되면 해당 플래그는 WAIT_FOR_ENDBRANCH 상태로 바뀐다.
그 후 다음 명령어가 endbr64(혹은 32비트의 경우 32)가 나온다면 이를 올바른 명령어라고 확인한 후 플래그는 다시 IDLE로 바뀐다.
만약 endbr64가 아니라면, CPU는 #CP exception을 발생시킨다.
예시를 통해 알아보자.
# case 1
call QWORD PTR [r15+0x38]
# case 2
jmp 0x8338d
case 1의 경우, case 2와 다르게 메모리에 있는 값을 읽어온 후 그 다음에 읽어온 값으로 jmp를 하게된다. 그러므로 indirect branch에 해당한다.
대다수의 JOP, COP의 경우 함수 중간으로 코드를 건너뛰기 때문에 이에 대처할 수 있는 보호기법이다.
return address를 덮는 공격기법들을 막기 위해서 등장하였다.
기존 스택과 같이 존재하는 또 다른 스택이다.
함수 프롤로그에서 return address가 저장될 때 기존 스택과 shadow stack 둘 다 저장되게 된다.
그리고 나서 함수 에필로그에서 리턴할 때 shadow stack과 기존 스택의 값을 비교하는데, 만약 값이 다르면 return address가 오염된 것이므로 이 때 프로그램을 종료시키는 등의 동작을 하게 된다.
대신, 기존 스택에 저장되는 지역변수, 매개변수나 다른 정보들은 shadow stack에 저장되지 않는다.
소프트웨어적으로 구현 / 하드웨어적으로 구현 둘 다 가능하다.
멀티스레드의 경우 각각의 stack 당 하나의 shadow stack을 가지게 된다.
ENV GLIBC_TUNABLES glibc.cpu.hwcaps=SHSTK
요런 식으로 설정해 줄 수 있다.