주요 내용으로 건너뛰기

메모리 취약점:과거, 현재, 미래 (6) Address Space Layout Randomization (ASLR)

본 포스트에서는 ASLR의 원리와 변천사, 공격기법 및 그 효용성에 대하여 다룬다.

앞서 다루었던 memory error 들을 통해 취약점을 exploit 하기 위해서는 필수적으로 해당 프로세스의 주소 공간에 대한 정보가 필요하다. 때문에 공격을 성공시키려만 적합한 address 를 찾아서 그곳에 code를 삽입하거나, jump 하도록해야한다. 

그렇다면, 결국 이 '주소' 가 문제라면, 이 주소를 (쉽사리) 찾을 수 없도록 하는 것이 보안적으로 더 안전하지 않을까? 이런 아이디어가 결국에는 메모리가 위치하는 곳을 임의적으로 배치함으로써 공격의 가능성을 희박하게 만드는 방식으로 제안되었다. 

1) ASLR의 기본 원리

ASLR 보호기법은 공유 라이브러리, 스택 및 힙이 매핑되는 메모리 영역의 주소를 임의로 배치한다. 그 이유는 공격자가 익스플로잇 코드를 작성할 때 페이로드가 어느 위치에 삽입되야 하는지를 미리 예측할 수 없도록 함으로써 그 가능성을 희박하게 하기 위함이다. 

프로그램을 최초로 실행하는 상황을 가정해보자. 스택이 상태가 아래와 같다고 하자.

프로그램을 종료하였다가 다시 실행할 때, 아래의 내용을 보면 기존의 내용과 스택의 주소공간 배치가 달라졌다.

스택이 위치한 주소가 일정하게 고정되어 있는 것이 아니라, base 값이 옮겨진 것을 볼 수 있다. 만약 공격자가 앞서 사용했던 공격 페이로드를 무심코 다시 사용하는 상황을 가정해보자.

저장된 return pointer는 스택의 엉뚱한 곳을 덮어써버렸고, 그 내용은 control flow와 관련이 없는 무의미한 곳이었다. 이곳으로 함수 종료시점에 반환된다면 프로그램은 해당 주소(0xbfff0000)에서 헛돌다가 crash가 발생하고 종료될 것이다.

따라서 공격자는 기존에 사용하던 무난한 방식으로는 익스플로잇을 성공시킬 수 없다. 랜덤하게 변화되는 값을 추적하여 base와 offset을 일일이 계산해주어야하는 번거로움이 추가될 것이다.

2) ASLR의 역사

Pax Team이 2001년 최초로 ASLR(Address Space Layout Randomization)이라는 보호기법을 제안하였다. 이때에는 프로세스의 user space 부분만을 random하게 배치하는 것으로 시작하였다. 이를 통해 익스플로잇 가능성은 급격하게 줄어들었으며, 프로세스에 대한 공격 실패 로그를 통해 탐지될 가능성을 높이는 효과가 있었다. PaX에서 제안한 ASLR 설계는 시간이 지나며 많은 개선을 이루어냈다. 먼저는 mmap기반의 랜덤화를 지원하였고, 이후 동적으로 링크되는 코드들(예: shared object) 역시 프로그램이 실행될 때마다 offset이 랜덤하게 설정되었다. 이로인해 dynamically linked된 라이브러리 함수들의 주소가 매번 다른 곳에 위치하게 되므로 return to libc 공격을 수행하기 어렵게되었다. 이후에는 Stack 기반의 랜덤화를 추진하였다. 물론 코드 삽입 공격 자체를 원천봉쇄할 수는 없지만, 스택 기반의 랜덤화를 수행하면 그래도 코드를 주입하는 과정을 굉장히 어렵게 만들 수는 있었다. 2001년 8월에는 PIE(Position Independednt Executable)에 대한 랜덤화가 제안되었다. PIE는 dynamic shared object에서의 상황과 유사한데, 바이너리의 base address까지 랜덤화를 적용하는 것이다. return-to-plt 나 통상적인 ROP(return oriented programming)에 대한 위험을 경감시키는데 일조하였다. 2002년에는 커널에 대한 스택 기반 오버플로우 공격과 코드삽입 기법이 알려졌고 이에 따라 PaX 팀은 커널 영역에 대한 랜덤화도 추가하였다. 마지막으로 mmap 기반의 주소 랜덤화를 통해 return-into-libc 공격을 우회하였던 것 처럼, 프로세스의 Heap 영역에 대해서도 랜덤화를 적용하는 패치를 출시하였다. 

한편, PaX Project가 Linux kernel에 대한 다양한 시리즈를 패치 형식으로 제공하는 동안 OpenBSD는 전격적으로 kernel의 mainstream에 추가해버렸다. [T. de Raadt, “Exploit Mitigation Techniques (in OpenBSD, of course),” http://www.openbsd.org/papers/ven05-deraadt/, Nov. 2005.] 뿐만 아니라 스택 영역에 대한 랜덤화, 스택 스매싱 보호기법(SSP, canary와 유사), 메모리 비 실행(W^X, NX와 같은 개념) 및 mmap 기반 주소 랜덤화와 같은 자체적인 보호기법을 독자적으로 추가하였다. 그러나 OpenBSD는 POSIX 표준을 따르기 위해 커널 스택에 대한 랜덤화를 지원하지 못했다. 무튼 당시에 OpenBSD팀과 PaX팀 사이에서는 누가 먼저 Randomization과 Non-executable memory 을 제안했는지, 그리고 그 제안은 표준을 위반한 것인지 등에 대한 치열한 논쟁이 이어졌었다. 2013년에는 OpenBSD 5.3이 PIE를 default로 적용한 최초의 mainstream OS가 되었다.

Red hat은 ExecShield를 2004에 출시하면서 ASLR을 추가하였다. 여기에는 Stack, mmap, Heap 등에 랜덤화가 적용되었고, PIE binaries 기능이 제공됨으로써 kernel-enforced한 랜덤화가 전폭적으로 제공되었다.

리눅스 커널은 2.6.12-rc1 커널이 출시된 2005년부터 stack과 mmap 기반의 랜덤화를 default로 제공하고 있다. 첫번째 패치를 적용한 Arjan van der Ven은 앞서 Red Hat ExecShield Project에 참여했던 장본인이기도하다. 2008년에는 리눅스커널에도 heap 랜덤화와 PIE가 지원되기 시작하다가 결국 2013년에 Kernel ASLR까지 적용되었다.

마이크로소프트 역시 ASLR에 대한 지원을 2006년 출시한 Windows Vista Beta 2에서 임시 적용하였는데 처음에는 stack과 heap, library 에 대해서만 수행하였다. 하지만 일부 연구자들에 의해 ASLR의 기능에 버그가 있다는 의구심이 제기되었고 MS가 반박하는 등의 설전이 벌어졌었다. Windows Vista가 정식으로 출시된 이후 다시 연구를 수행한 Whitehouse는 "Windows Vista에서 ASLR이 제공하는 보호 기능이 예상보다 강력하지는 않은 것 같다"는 결론을 내기도 했다. Microsfot는 ASLR 구현의 결함에 대해서 일부 시인했지만 구체적으로 어떠한 후속조치를 취했는지는 알려진 바 없으며 이후 Windows 7이 출시되었다. 

2010년, Alin Rad Pop이란 사람이 재미있는 통계를 발표했는데, Windows 에서 구동되는 3rd party 응용 프로그램들이 정작 ASLR 과 DEP 등의 보호기법을 사용하는데에 무심하다는 것을 보여주었다. 실제로 2010년 6월에 ASLR 보호기법을 100% 적용한 애플리케이션은 구글의 Chrome과 Adobe Flash Player가 유일했다. 그 밖에 인기있는 대표적인 응용프로그램인 Adobe Reader, Mozilla Firefox 및 Apple iTunes 등은 Windows 플랫폼에서 실행될 때 ASLR을 전역적으로 활용하지 못하고 있었다. [A. R. Pop, “DEP/ASLR Implementation Progress in Popular Third- party Windows Applications,” June 2010.]

또한 Apple은 macOS 10.5 (Mac OS X Leopard)부터 스택과 힙 영역에 대한 Non-Executable 을 지원하였는데, ASLR에 대해서는 부분적으로만 적용한 library randomization(Apple. Inc., “Mac OS X Leopard Security,” Oct. 2007.) 을 보였다. 이름에서도 짐작할 수 있듯이, full ASLR이 적용되지 못한 것이다. 이후에는 2011년 10.7(Mac OS X Lion)에서 애플리케이션에 대한 ASLR을, 2012년 10.8(OS X Mountain Lion)에서부터 커널을 포함한 시스템 전체에 ASLR을 적용하였다.

일반적으로, ASLR은 그동안 대부분 Kernel 수준에서 강제화된 형태로 사용되어왔다. 이러한 랜덤화 기법은 프로세스의 주소 공간(스택, 힙, mmap 영역)에 대하여 base가 되는 주소를 가지고 있다. 즉, 절대 주소들이 모두 랜덤화 되는 것처럼 보이지만 실제로 라이브러리 내의 상대적인 offset(예를 들어 임의의 두 객체 간의 위치의 거리차)는 고정되어 있다. 따라서 ASLR이 꺼진 상태라고 가정했을 때 각 객체의 offset 차이를 미리 계산해둘 수 있다면, ASLR이 적용되었을 때 찾은 값에서 해당 offset의 차이를 연산하면 찾아낼 수 있다. 때문에 공격자는 자신이 사용할 라이브러리의 절대 주소(return-into-libc 공격으로 사용할 함수 등)를 미리 찾아두었다가, ASLR시의 base와의 offset을 통해 그곳을 찾아낼 수 있게 된다. 

이러한 단점을 극복하기 위하여, S. Bhatkar 등은 fine-grained address space randomization이라는 기법을 제안하였다.  fine-grained 란 어떤 것을 아주 잘게 쪼개는 것을 의미하는데, 이는 기존의 ASLR이 '주소'를 랜덤화하는 것에만 착안하였던 것에 비해, 이제 Code 부분까지 쪼개 Randomization 까지 수행하는 것을 의미한다. 

  • “Address Obfuscation: an Efficient Approach to Combat a Broad Range of Memory Error Exploits,” in USENIX Security Symposium, August 2003.
  • “Efficient techniques for comprehensive protection from memory error exploits,” in USENIX Security Symposium, August 2005.
  • “Enhanced Operating System Security through Efficient and Fine-grained Address Space
    Randomization,” in USENIX Security Symposium, August 2012.
Just-In-Time Code Reuse: On the Effectiveness of Fine-Grained Address Space Layout Randomization

이러한 방법들은 처음에는 source code 기반의 randomization이었던 것에 반해, 최근에는 Binary-Level Fine-Grained ASLR에 대한 제안도 현재진행형이다.

  • “ILR: Where’d my gadgets go?,” In IEEE S&P, 2012
  • “Smashing the gadgets: Hindering return-oriented programming using in-place code randomization,” In IEEE S&P, 2012
  • “Binary stirring: Self-randomizing instruction addresses of legacy x86 binary code,” In ACM CCS, 2012
  • “Just-In-Time Code Reuse: On the Effectiveness of Fine-Grained Address Space Layout Randomization,” In IEEE S&P, 2013
  • “Oxymoron: Making fine-grained memory randomization practical by allowing code sharing,”  In USENIX, 2014

3) ASLR에 대한 공격 기법

ASLR에 대한 최초의 공격기법은 2001년 Nergal에 의해 Phrack magazine에 “The Advanced Return-Into-Lib(c) exploits (PaX Case study)”으로 게재되었다. 비록 이 글의 주된 주제는 Non-executable data protection 을 우회하는 것에 있었지만, PaX Randomization에 대한 부분도 함께 분석하였고 이를 종합하여 새로운 공격기법인 Return-into-PLT 라는 것을 제시하였다. 이는 Dynamic linker의 symbol resolution 과정을 이용하여, 해킹에 활용할만한 주요 symol들의 주소를 얻어내는 것이다. 하지만 이러한 방식의 공격은 바이너리에 PIE 가 적용되면서 사실상 어려워졌다.

2002년에는 T. Durden이 Phrack Magazine에 “Bypassing PaX ASLR Protection”을 기고하였다. 이 내용은 임의의 버퍼 오버플로우 취약점이 존재한다면 이는 곧 포맷 스트링 버그로 적절히 변환할 수 있다는 것인데, 이는 곧 취약한 프로세스에 대하여 그 주소공간에 대한 정보를 노출시킬 수 있게된다. 이러한 Memory Leak은 향후 ASLR에 대한 거의 모든 공격의 근간을 이루게 된다.

2004년, Hovav Shacham(*ROP 논문 저자)는 ACM CCS에서 “On the Effectiveness of Address-Space Randomization.”라는 제목의 논문을 발표하였다. 여기에서는 ASLR의 구현상의 결함이 존재하여 특히 32-bit 플랫폼에서는 그 기능이 제한적임을 보였다. 아키텍처 및 커널 등의 설게적 제약 때문에 ASLR 운영이 실제로 효과를 발휘할 수 있는 엔트로피는 굉장히 일부분에 지나지 않으며, 때문에 Brute-forcing 을 통해서 실용적인 시간안에 ASLR 시스템을 무력화시킬 수 있음을 공개하였다.

2008년, T. Muller는 “ASLR Smack & Laugh Reference”에서 ASLR에 대한 조금 더 포괄적인 논의를 이끌어냈다. return-into-libc 및 그와 유사한 기법 (return-into-text, return-into-bss, return-into-data, return-into-return)을 이용하면 NX 뿐만 아니라 ASLR 역시 우회할 수 있다는 것이었다. 그는 "현재의 리눅스 표준에 구현된 ASLR은 굉장히 빈약하다"라고 평했는데, 이 때문인지 2개월 뒤에 제공된 리눅스 패치에서는 Heap-based ASLR과 PIE-based ASLR이 모두 포함되었다.

2009년에는 유명한 [G. Fresi-Roglia, L. Martignoni, R. Paleari, and D. Bruschi, “Surgically returning to randomized lib(c),” in ACSAC, Dec. 2009, pp. 60–69.] 논문이 발표되었다. 여기에서는 W^X와 ASLR이 동시에 걸려있는 바이너리에 대해 Return-oriented programming을 이용하여 익스플로잇을 할 수 있음이 입증되었다. 기본적으로 코드 조각(ROP-gadget)을 사슬처럼 연결하고, GOT(Global Offset Table)을 통해 동적으로 링크된 라이브러리의 base address를 계산한다. 그 이후에는 고전적인 return-to-libc 및 ROP와 유사한 공격을 수행하는 것이다. 이 공격은 intel x86 아키텍처 기준으로 95.6% 바이너리에 모두 잠재적인 취약점이 존재한다는 것을 보였으며(x86-64는 61.8%), 이는 현대의 운영체제들이 PIE를 도입해야만 한다는 필요성을 강조하기에 충분했다. 실제로 ASLR-protected이지만 non-PIE인 바이너리들에 대하여 현실적인 시간 안에 임의의 원하는 code snippet을 찾아내는 실용적인 도구(ROPgadget tool 등)까지 개발되었기 때문이다. 때문에 PIE 적용은 거의 필수화가 되었지만 다만 PIE를 적용할 때에 오버헤드가 발생하는 문제를 효율적으로 해결하는 방안 마련이 필요하였다.  (실제로 이후 현재 2019년 시점에서는 90%이상이 PIE enabled 된 것으로 보인다.) 


4) 요약 및 결론

ASLR의 공격 기법이 여전히 존재하는 것은 사실이지만 그럼에도 불구하고 ASLR은 그 자체만으로 버퍼 오버플로우뿐만 아니라 기타 여러 가지 종류의 메모리 오류에 대응하기 위한 효과적인 방어책이다. 

메모리 오류를 이용한 공격 기법은 대부분 적절한 메모리 주소를 찾아내는 것을 필수로 하는데 이에 착안하여 ASLR을 통해 프로세스의 주소 공간을 분산시킴으로써 공격자가 이를 예측할 수 없도록 만드는 것이다. 

다만 ASLR이 확률론적 성격을 갖기 때문에, 단지 그 내용이 '비밀'취급된다는 점에서만 유용한데 만약 memory leak을 통해 그 정보를 파악할 수 있게 된다면 이는 다시 무용해지고 말 것이다. 또한 아키텍처가 가지는 한계에 의해 Randomization의 정도에 차이가 있을 수 있고, base address 만을 변경시킨다 하더라도 이에 대한 offset들을 동적으로 계산한다면 쉽게 무력화시킬 수 있다는 단점이 있다. 따라서 변경된 address 를 무작위로 추측하거나, 아니면 다른 memory leak 취약점을 통해 정보를 획득한 후 공격하는 등의 공격 기법이 발표되었다. 

후에는 64bit 시스템으로 넘어오면서 full-ASLR 및 PIE가 적용되어 ASLR mitigation의 위력이 높아진 편이다.

ASLR은 바이너리뿐만 아니라 커널 및 OS 전체에 걸쳐 적용되야하는 기법이므로 Overhead가 심각히 고려되어야만 하는 주제이기에 학술적인 연구 이후에도 실무적으로 적용되는데 비교적 많은 시간이 소요되고 있다. 보안성을 높이되 효율적인 방법을 마련하는 것이 과제로 남아 있다.  

추후에는 Fine-grained ASLR에 대한 학습을 더 살펴보면 좋을 듯하다. 

Software Security Engineer

CPUU 님의 창작활동을 응원하고 싶으세요?

댓글

SNS 계정으로 간편하게 로그인하고 댓글을 남겨주세요.