Control Flow Integrity for COTS Binaries

august 2013 USENIX  best paper awarded. (https://www.usenix.org/node/174767)

“Security thesis review는 보안 관련된 논문의 개인 review로 다수의견과 일치되지 않을 수도 있습니다.”

 

소개글.

Control Flow Integrity(이하 CFI)는 많이 연구되는 분야로 Return Oriented Program(ROP), Jump Oriented Program(JOP)와 같은 Code reuse 공격 기법에 대한 방어 기법이다. 2013년 발표된 이 논문에서는 CFI를 real-world에 적용하기 위한 방안에 대한 연구 내용을 다루고 있다.

 

배경.

기존의 CFI를 적용하기 위한 방법들은 COTS(상용) 바이너리에 적용하기 어려웠다. 그 이유는, CFI를 적용하기 위해 CF가 변경되는 모든 곳의 코드를 변경해야 하는 제약사항이 있는데 기존에 연구된 기법은 COTS 바이너리에는 일반적으로 포함되지 않는 정보들을 기반으로 하기 때문이다. 예를 들어, 해당 논문에서 많이 언급되는 Abadi는 relocation 테이블에 기반하였으나 해당 정보는 stripped된 바이너리에는 존재하지 않는 정보이고, 거의 모든 COTS 바이너리는 stripped된다. 때문에 어떤 외부 정보에도 의존하지 않고 바이너리 자체를 기반으로 CFI를 적용할 수 있는 방법이 필요했다.

 

목표.

논문에서는 연구 목표를 Modularity, Trasparency, Compiler independence and support for hand-coded assembly 세 가지로 설정했다.

Modularity : 바이너리의 실행 간 의존성이 있는 라이브러리 모듈 등에 대해 CFI가 독립적으로 그리고 전역적으로 적용될 것을 보장한다.

Transparency : 바이너리를 변경하는 작업이 대상 바이너리의 실행에 미치는 영향을 없도록 한다.

Compiler independence and support for hand-coded assembly : 컴파일러에 의해 생성된 정보 등에 의존하지 않고 어셈블리로 수작업 된 low-level 코드에도 적용할 수 있도록 한다. (eg, libc)

 

방법.

Disassembler

Disassemble 방식에는 linear, recursive 두 방법이 있고 각각의 장단점이 존재한다. recursive 방식은 Control Flow를 분석하지만 Indirect Control Flow를 해석하지 못하는 문제가 있고, linear 방식은 전체 코드를 분석할 수는 있지만 Data와 혼합된 코드를 해석할 수 없는 문제 등이 있다. Disassembly의 정확성은 실행 흐름 간 코드 주입을 통해 무결성을 확보하는 CFI의 핵심 중에 핵심이므로 매우 중요한 요소이기에 이를 개선하는 방안을 연구했다.

먼저, linear 방식을 적용했을 시에 발생하는 문제를 세 가지로 정의했다

  • Invalid Code
    • x86 instruction의 해석의 문제.
  • Direct control transfers outside the current module
    • Program linkage table(PLT), Global offset table(GOT)나 ICF를 사용해야 할 필요가 있는 경우. 그리고 현재 모듈 범위를 벗어나는 Direct control transfer의 경우.
  • Direct control transfers to the middle of an instructions
    • Disassembly 혹은 control-flow transfer를 잘 못 해석한 경우.

위와 같은 문제가 발생하는 이유를 몇 가지로 정리했다.

  • Code Pointer constants(CK)
    • 컴파일 시에 계산된 코드 주소
  • Computed code addresses(CC)
    • 실행 시에 계산되는 코드 주소
  • Exception handling addresses(EH)
    • 예외 처리 시에 사용되는 코드 주소
  • Exported symbol addresses(ES)
    • export되는 코드 주소
  • Return addresses(RA)
    • call 다음의 코드 주소

 

위와 같은 방법으로 향상된 방법의 디스어셈블러를 구축했다고 소개하며 이에 대한 내용은 (An In-Depth Analysis of Disassembly on Full-Scale x86/x64 Binaries : https://www.usenix.org/system/files/conference/usenixsecurity16/sec16_paper_andriesse.pdf ) 여기서 교차 확인할 수 있다.  또한 이 논문의 저자들이 작성한 다른 논문 및 프로젝트 (A Principled Approach for ROP Defense : http://seclab.cs.sunysb.edu/seclab/pubs/shadow15.pdf)에서 본인들이 사용했다고 소개한 A Platform for Secure Static Binary Instrumentation(http://seclab.cs.sunysb.edu/seclab/pubs/vee14.pdf)를 여기(http://www.seclab.cs.sunysb.edu/seclab/psi/)에서 공개하고 있다.

Implement of CFI

CFI의 구현은 Statically Instrumentation을 통해 Binary rewriting으로 이뤄진다. 주입되는 코드는 주로 ICF의 전/후에 Integrity의 검증을 하는 목적이다. 또한 해당 논문에서는 Dynamic Binary Translation(DBT)기법을 응용하여 동적 함수 포인터 호출 시에 이를 처리할 수 있는 방법도 함께 소개하고 있다. 동적 함수 포인터 호출 시에 해당 주소를 검증하며 호출/반환을 처리할 trampolin 함수를 호출 함으로서 무결성 검증을 해냈다.

 

특장점.

해당 논문은 또한 CFI가 가진 약점인 오버헤드를 최적화 하기 위해서 “분기 예측”, “주소 변환”, “투명성 희생”의 3가지 방안을 제시한다.

 

Shuffler Fast and Deployable Continuous

“Security thesis review는 보안 관련된 논문의 개인 review로 다수의견과 일치되지 않을 수도 있습니다.”

소개글.

Shuffle이란 단어는 카드 게임에서 카드를 섞을 때 사용되는 단어이다. shuffle을 ‘섞다’라고 한다면 Shuffler는 ‘섞는자’ 라고 하면 될까? Shuffler는 Control Flow Integrity와 동일한 Code-reuse attack = ‘실행 흐름의 변조/위조’ 를 방지하는 기술로 ROP, JOP와 같은 exploit 기법의 방어 기제에 대한 연구이다.

Shuffler 요약.

CFI기술은 compile 시 혹은 binary rewriting으로 실행 흐름에 무결성을 확보하기 위한 코드를 주입한다. 이를 통해 Control Flow의 변경 시(Call, Jmp 등) 목적지가 정당한 지를 검증하는 방식을 일반적으로 취하게 된다. 단점은, 그로 인해 CPU의 Branch Prediction을 방해하고 결과적으로 예측 성능을 크게 하락시킨다. 이런 문제를 해결하고자 CFI는 MS와 INTEL과 같은 대형 벤더의 협의를 통해 CPU 기능으로 추가하는 추세이다. 하지만 해당 기능이 CPU에 추가되기 전까지 그리고 해당 CPU세대로 세대교체가 이뤄지기 전까지 CFI는 널리 사용하기 어려운 기술이다.

Shffler는 CFI와 같이 무결성 수준의 높은 보장을 하지는 않지만, 대신 현실적으로 공략 불가능한 방법을 대안으로 제안한다. ROP 기법은 Gadget으로 사용되는 코드들의 주소가 일정하다는 전제가 필요하다. 이에 대해 간단히 언급하자면 ROP은 다수의 주소를 stack에 삽입해 이들로 ret하면서 shellcode를 생성하는 작업을 진행한다. 때문에 ASLR과 같은 보안 기법이 적용된 경우 사용하기 위해 전제가 필요한 기법이다.

Shuffler는 실행 시에 링킹되는 외부 모듈(Shared Object)들의 주소를 일정 주기로 변경하며, 이들을 리스트 화 한다. 그리고 이들을 참조하는 Control Flow 변경 코드들을 변경하여 해당 리스트를 참조하도록 redirect 시킨다. 이는 마치 ASLR이 주기적으로 외부 모듈들의 주소를 재배치하는 것과 유사하여 1차로 ROP을 사용하기 어렵게 만든다. 또한 모든 실행흐름 변경의 코드들이 Shuffler에 의해 생성된 리스트에서 참조되도록 만들기 때문에 2차로 ROP을 사용하기 어렵게 만드는 것이다.

부가적으로 리스트화 된 주소 리스트를 암호화하여 정보유출 등을 완화하는 기능 등이 있지만 주 기능은 위의 두 가지이다.

Shuffler의 장점은?

Shuffler는 CFI처럼 무결성 검증 코드를 추가로 삽입하지 않는다. 그렇기 때문에 기 존재하는 ELF binary에 적용하는 것이 아주 수월하다. 해당 논문에서도 Shared Object를 LD_PRELOAD하는 방식을 취하여 SO파일들을 주입시키는 것 만으로 다른 ELF파일들에 적용할 수 있었다고 말한다. 이것은 보안 측면에서 보안 기술을 개발자들에게 강제하는 것이 아닌, 외부 솔루션이나 모듈로 존재할 수 있게 만든다.

그리고 Shuffle의 주기를 조절하여 주기를 길게 할 수록 CFI에 비해 성능 향상이 이뤄질 수 있다. 실제 사용 시에 Shuffle을 1회로 적용하면 성능을 희생하지 않아도 된다.

Shuffler의 단점은?

Shuffler의 일부를 쉽게 예를 들면, 주소를 변경하고 해당 주소를 기입한 웹사이트들을 모두 변경해서 택배나 우편이 제대로 오게 만드는 방식이다. 이 때문에 주소가 기입되어 있는 곳들을 리스트 화 할 수 있어야 하고 해당 주소를 모두 변경해야 하는 두 작업이 가능해야 한다. 하지만 보통 많이 사용되는 컴파일 옵션으로는 주소지가 기입된 곳들을 리스트화 하는 것이 어렵다. 특히 정적인 방법으로는 100%는 거의 불가능하다. 때문에 해당 논문은 ‘-Wl,-q’ 옵션을 사용하여 링킹 시에 관련 정보를 남겨달라고 컴파일러에 요청하고 있다.

Shuffler 결론.

Shuffler는 CFI처럼 컴파일 시에 주입 혹은 Binary를 Rewriting하는 방식으로 무결성이라는 높은 보안 수준을 구현하고자 하지는 않는다. 하지만 현실적으로 사용 가능한 수준에서, ASLR처럼 주소를 Randomize하게 Shuffle하는 것을 일정 시간 간격으로 수행해 Control Flow에 관련된 공격 기법(ROP 등)을 방어할 수 있음을 보여줬다. 또한 논문의 제목대로 Diploy가능한 방식이므로 외부 모듈 혹은 솔루션으로 적용할 수 있는 기법이었고, 또한 CFI에 비해 희생해야 할 것이 없다시피 하기 때문에 매우 만족스러운 논문이었다.