반환 지향형 프로그래밍
반환 지향형 프로그래밍(ROP: Return-oriented programming)은 실행 불가능한 메모리와 코드 사이닝 같은 보안 방어가 존재하는 경우에 공격자가 코드를 실행할 수 있게 하는 컴퓨터 보안 취약점 공격이다.[1]
이 기법에서 공격자는 프로그램 제어 흐름을 하이재킹하기 위해 콜 스택의 제어를 얻고, "가젯"이라고 불리는 신중히 선택된 기계어들을 실행한다.[2] 각 가젯은 일반적으로 연결됨으로써 의미를 구현하는 몇몇 명령어들과 함께 마지막에 반환 명령어(ret)로 끝난다. 이것들은 현재 프로그램 또는 공유 라이브러리 코드 내의 서브루틴에 위치하는데 이것은 Return-to-libc 공격을 사용하는 것이다. 같이 연결된 이러한 가젯들은 공격자가 임의적인 연산을 수행할 수 있게 한다.
반환 지향 프로그래밍은 스택 스매싱의 진화된 버전이다. 일반적으로 공격들의 이러한 종류들은 상대방이 프로그램의 버그(주로 버퍼 오버런)를 이용함으로써 콜 스택을 조작할 때 발생한다. 버퍼 오버런 시에 사용자가 제공하는 데이터를 적절한 경계 검사 없이 메모리에 저장하는 함수는 적정한 양보다 더 많은 데이터를 받아들일 수 있다. 데이터가 스택에 겹쳐써지면 초과된 데이터는 함수의 변수에 할당된 공간을 넘치게 되고 반환 주소도 겹쳐쓰게 된다. 이 주소는 이후에 함수에 의해서 제어 흐름을 다시 호출자로 넘겨줄 때 사용된다. 만약 겹쳐써졌다면 제어 흐름은 새로운 반환 주소로 넘겨지게 된다.
일반적인 버퍼 오버런 공격에서, 공격자는 간단하게 공격 코드(페이로드)를 스택에 쓰고 반환 주소를 이 새로운 명령어들의 위치로 겹쳐쓴다.[3] 최근들어 운영 체제들은 버퍼 오버플로 버그 익스플로잇과의 전쟁을 통해 데이터가 저장되는 메모리를 실행 불가능하게 마크한다(이 기법은 데이터 실행 방지로 알려져 있다). 데이터 실행 방지가 활성화되면 머신은 사용자가 쓸 수 있는 메모리 영역에 위치한 코드를 실행하지 않는다. 이 방어를 강화하기 위해 하드웨어가 지원하는 데이터 실행 방지도 사용 가능해 졌다.
데이터 실행 금지의 만연해진 구현으로 전통적인 버퍼 오버플로 취약점들이 위의 방식으로 익스플로잇하기 매우 힘들어졌다. 대신 공격자는 프로그램 코드 자체나 연결된 공유 라이브러리 같은 메모리에서 이미 마크된 실행 파일로만 제약되었다. libc 같은 공유 라이브러리는 종종 시스템 호출이나 다른 잠재적으로 유용한 기능을 수행하는 서브루틴을 포함하기 때문에 공격을 위해 찾아야 할 코드의 후보가 되었다.
Return-to-libc 공격에서 공격자는 위에서 언급한 것과 같이 버퍼 오버런 취약점을 익스플로잇 함으로써 프로그램 제어 흐름을 하이재킹한다. 공격 페이로드를 스택에 쓰는 대신, 공격자는 사용 가능한 라이브러리 함수를 고르고 반환 주소를 자신의 엔트리 위치로 겹쳐쓴다. 나아가 스택 위치들은 함수 호출 규약에 따라 적절한 파라미터를 신중하게 전달하기 위해 겹쳐써지게 되며, 공격자에게 유용한 기능을 수행한다.
64-bit x86 프로세서들의 증가로 첫 번째 인자를 스택 대신 레지스터에 전달하는 서브루틴 호출 규약으로 변화되었다. 이것은 버퍼 오버런 익스플로잇을 통해 공격자가 콜 스택을 단순히 조작함으로써 더 이상 라이브러리 함수 호출을 설치할 수 없게 되었다는 것을 의미한다. 공유 라이브러리 개발자들은 또한 공격자에게 유용하게 사용되는 함수들을 제거하고 제한하였다(시스템 호출 래퍼 같은). 결과적으로 return-into-library 공격은 성공하기가 더욱 어려워 졌다.
이후에는 버퍼 오버런 취약점을 익스플로잇하려는 간단한 공격들을 방어하는 기계에서, 전체 함수들 자체 대신에 라이브러리 함수들의 청크들 즉, 부분들을 사용하는 형태의 공격으로 진화되었다.[4] 이 기법은 스택에서 레지스터로 값을 pop 하는 명령어 열들을 갖는 함수를 찾는다. 이러한 코드열들의 신중한 선택은 공격자가 새로운 호출 규약 하에서 함수 호출을 수행하기 위해 적절한 값들을 적절한 레지스터에 넣게 한다. 공격의 나머지는 return-into-library 공격과 같다.
반환 지향 프로그래밍은 빌린 코드 청크 접근법에 기반하며 공격자에게 튜링 완전 기능(루프와 조건 분기를 포함하는)을 제공하기 위해 확장되었다.[5][6] 다시 말해서, 반환 지향 프로그래밍은 완전하게 기능적인, 공격자가 어떠한 요구되는 연산을 수행하기 위해 사용할 수 있는 "언어"를 제공한다.
반환 지향형 프로그래밍 공격은 다른 공격들 보다 우위를 갖는다. 잠재적으로 위험한 함수들을 공유 라이브러리에서 제거하는 것을 포함한, 위에서 언급한 익스플로잇 방어 기법 어느 것도 반환 지향형 프로그래밍 공격에 효과적이지 않다.
비록 반환 지향형 프로그래밍 공격들이 다양한 아키텍처에서 수행될 수 있지만,[7] Shacham의 페이퍼와 뒤의 주요한 작업들은 인텔 x86 아키텍처에 중점을 둔다. x86 아키텍처는 가변 길이 CISC 명령어 집합이다. x86에서 반환 지향형 프로그래밍은 명령어 집합이 매우 밀집하다는 사실에 장점을 갖는다. 즉, 마이트들의 어느 랜덤 순서열들도 유요한 x86 명령어들의 집합으로 변환될 수 있다.
그러므로 제어 흐름을 바꾸는 옵코드(주로 반환 명령어인 0xC3)를 찾는 것이 가능하며, 이후 바이너리에서 역으로 선행하는 바이트들(유용할 가능성이 있는 명령어들)을 찾는다. 이러한 명령어들의 집합인 "가젯"은 이후 버퍼 오버런 익스플로잇을 통해 첫 번째 가젯의 명령어의 주소로 반환 주소를 겹쳐씀으로써 묶일 수 있다. 차후 가젯들의 첫 번째 주소는 이후 스택에 성공적으로 써진다. 첫 번째 가젯이 끝날 때 반환 명령어가 실행될 것인데, 이것은 다음 가젯의 주소를 pop하고 그곳으로 점프할 것이다. 이 가젯의 끝날 때 체인은 세번째와 함께 계속된다. 작은 명령어 순서열을 묶음으로써 공격자는 임의적인 프로그램 행동을 이미 존재하는 라이브러리 코드에서 생산할 수 있게 된다. Shacham은 주어진 충분히 큰 양의 코드가 주어진다면(C 표준 라이브러리를 포함해서) 충분한 가젯들이 튜링 완전 기능을 위해 존재하게 될 것이라고 말한다.[7]
가젯들을 위치시키고 바이너리에 공격을 구성하는 것을 돕는 자동화된 툴이 개발되어 왔다.[8] ROPgadget이라 불리는 이 툴은 잠재적인 유용한 가젯들을 바이너리에서 찾고, 공격자에게 암의적인 명령어를 받을 셸을 생성할 공격 페이로드로 조합한다.
많은 기법들이 반환 지향형 프로그래밍에 기반한 공격을 막기 위해 제안되어 왔다.[9] 대부분은 프로그램과 라이브러리 코드의 위치를 랜덤화하는 것에 의존하는 것으로, 공격자가 정확하게 명령어들의 위치를 예상하지 못하게 하는 것이다. 이 기법의 꽤 흔한 구현은 주소 공간 배치 난수화(ASLR)이며, 공유 라이브러리들을 각 프로그램에 서로 다른 메모리 위치에 로드한다. 비록 현대 운영 체제에서 많이 쓰이고 있지만, ASLR은 정보 누수 공격에 취약하며 메모리에서 알려진 라이브러리 함수의 주소를 찾는 다른 접근법들이 존재한다. 만약 공격자가 성공적으로 알려진 명령어들의 위치를 결정할 수 있으면, 다른 모든 것들의 위치는 추론 가능해지고 반환 지향 프로그래밍 공격은 구성될 수 있다.
이 랜덤화 접근법은 단지 라이브러리 위치 뿐만 아니라 프로그램의 모든 명령어들을 각각 재위치시킴으로써 더 나아갈 수 있다.[10] 이것은 랜덤화된 명령어들을 런타임 시에 다시 모을 수 있도록 소프트웨어 동적 변환기 같은 광범위한 런타임 지원을 요구한다. 이 기법은 가젯을 찾고 활용하기 어렵게 만드는데 성공적이지만, 상당한 양의 오버헤드가 유발된다.
kBouncer이 사용하는 다른 접근법으로,[11] 운영 체제를 수정해서 반환 명령어들이 실제로 제어 흐름이 바뀌는지 추적하게 하는 것이 있다. 이것은 가젯 체이닝을 막지만 큰 성능 패널티를 유발한다.[11] 게다가 이것은 반환 지향형 프로그래밍이 반환 명령어 대신 제어 흐름 변경 명령어(jump 같은)를 사용하게 할 가능성이 있다. kBouncer는 이 유형의 수정된 공격에 효과적이지 않다.
스레드 코드 - 반환 지향형 프로그래밍은 스레드 코드의 재발견이다. Return-to-libc 공격